Skip to content

Commit 80cefba

Browse files
fxlianglotem
authored andcommitted
feat(tool): rime_api_console with autosuggestions feature like zsh-autosuggestions
1 parent 34bfaf6 commit 80cefba

File tree

2 files changed

+137
-28
lines changed

2 files changed

+137
-28
lines changed

tools/line_editor.cc

Lines changed: 135 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
#include <cctype>
44
#ifdef _WIN32
55
#include <conio.h>
6+
#include <windows.h>
67
#else
78
#include <termios.h>
89
#include <unistd.h>
@@ -43,6 +44,7 @@ bool LineEditor::ReadLine(std::string* out) {
4344
history_position_ = history_.size();
4445
browsing_history_ = false;
4546
saved_line_.clear();
47+
suggestion_.clear();
4648
last_rendered_length_ = 0;
4749
RefreshLine(line, cursor);
4850
while (true) {
@@ -71,6 +73,7 @@ bool LineEditor::ReadLine(std::string* out) {
7173
}
7274
}
7375
}
76+
suggestion_.clear();
7477
last_rendered_length_ = 0;
7578
return true;
7679
}
@@ -88,12 +91,22 @@ bool LineEditor::ReadLine(std::string* out) {
8891
if (cursor > 0) {
8992
line.erase(cursor - 1, 1);
9093
--cursor;
94+
UpdateSuggestion(line);
9195
RefreshLine(line, cursor);
9296
} else {
9397
EmitBell();
9498
}
9599
continue;
96100
}
101+
if (ch == 9) { // Tab
102+
if (!suggestion_.empty()) {
103+
line.append(suggestion_);
104+
cursor = line.size();
105+
suggestion_.clear();
106+
RefreshLine(line, cursor);
107+
}
108+
continue;
109+
}
97110
if (HandleEscapeSequence(ch, &line, &cursor))
98111
continue;
99112
if (IsPrintable(ch)) {
@@ -103,6 +116,7 @@ bool LineEditor::ReadLine(std::string* out) {
103116
}
104117
line.insert(cursor, 1, static_cast<char>(ch));
105118
++cursor;
119+
UpdateSuggestion(line);
106120
RefreshLine(line, cursor);
107121
// editing after history recall should detach from history
108122
if (browsing_history_) {
@@ -128,6 +142,76 @@ int LineEditor::ReadChar() {
128142
#endif
129143
}
130144

145+
void LineEditor::RefreshLine(const std::string& line, size_t cursor) {
146+
// Carriage return to start of line
147+
putchar('\r');
148+
// Print current line content
149+
fputs(line.c_str(), stdout);
150+
151+
// Print suggestion in dim color if available
152+
if (!suggestion_.empty() && cursor == line.length()) {
153+
#ifdef _WIN32
154+
HANDLE hConsole = GetStdHandle(STD_OUTPUT_HANDLE);
155+
CONSOLE_SCREEN_BUFFER_INFO consoleInfo;
156+
GetConsoleScreenBufferInfo(hConsole, &consoleInfo);
157+
WORD originalAttrs = consoleInfo.wAttributes;
158+
// Use Dark Gray (Bright Black) for suggestion
159+
// Preserve background
160+
WORD bg = originalAttrs & (BACKGROUND_BLUE | BACKGROUND_GREEN |
161+
BACKGROUND_RED | BACKGROUND_INTENSITY);
162+
SetConsoleTextAttribute(hConsole, bg | FOREGROUND_INTENSITY);
163+
fputs(suggestion_.c_str(), stdout);
164+
SetConsoleTextAttribute(hConsole, originalAttrs);
165+
#else
166+
// ANSI escape code for dim text (usually gray)
167+
fputs("\x1b[90m", stdout);
168+
fputs(suggestion_.c_str(), stdout);
169+
// Reset color
170+
fputs("\x1b[0m", stdout);
171+
#endif
172+
}
173+
174+
// Clear to end of line if previous line was longer
175+
size_t current_length = line.length() + suggestion_.length();
176+
if (current_length < last_rendered_length_) {
177+
#ifdef _WIN32
178+
// Print spaces to clear
179+
for (size_t i = current_length; i < last_rendered_length_; ++i) {
180+
putchar(' ');
181+
}
182+
// Move cursor back to end of current content
183+
HANDLE hConsole = GetStdHandle(STD_OUTPUT_HANDLE);
184+
CONSOLE_SCREEN_BUFFER_INFO consoleInfo;
185+
GetConsoleScreenBufferInfo(hConsole, &consoleInfo);
186+
COORD pos = consoleInfo.dwCursorPosition;
187+
pos.X -= static_cast<SHORT>(last_rendered_length_ - current_length);
188+
SetConsoleCursorPosition(hConsole, pos);
189+
#else
190+
// ANSI escape code to clear from cursor to end of line
191+
fputs("\x1b[K", stdout);
192+
#endif
193+
}
194+
last_rendered_length_ = current_length;
195+
196+
// Move cursor to correct position
197+
// We are currently at the end of the printed content (line + suggestion)
198+
// We need to move back by (line.length() - cursor) + suggestion.length()
199+
size_t move_back = (line.length() - cursor) + suggestion_.length();
200+
if (move_back > 0) {
201+
#ifdef _WIN32
202+
HANDLE hConsole = GetStdHandle(STD_OUTPUT_HANDLE);
203+
CONSOLE_SCREEN_BUFFER_INFO consoleInfo;
204+
GetConsoleScreenBufferInfo(hConsole, &consoleInfo);
205+
COORD pos = consoleInfo.dwCursorPosition;
206+
pos.X -= static_cast<SHORT>(move_back);
207+
SetConsoleCursorPosition(hConsole, pos);
208+
#else
209+
printf("\x1b[%zuD", move_back);
210+
#endif
211+
}
212+
213+
fflush(stdout);
214+
}
131215
bool LineEditor::HandleEscapeSequence(int ch,
132216
std::string* line,
133217
size_t* cursor) {
@@ -155,6 +239,11 @@ bool LineEditor::HandleEscapeSequence(int ch,
155239
if (*cursor < line->size()) {
156240
++(*cursor);
157241
RefreshLine(*line, *cursor);
242+
} else if (!suggestion_.empty()) {
243+
line->append(suggestion_);
244+
*cursor = line->size();
245+
suggestion_.clear();
246+
RefreshLine(*line, *cursor);
158247
} else {
159248
EmitBell();
160249
}
@@ -183,6 +272,11 @@ bool LineEditor::HandleEscapeSequence(int ch,
183272
if (*cursor < line->size()) {
184273
++(*cursor);
185274
RefreshLine(*line, *cursor);
275+
} else if (!suggestion_.empty()) {
276+
line->append(suggestion_);
277+
*cursor = line->size();
278+
suggestion_.clear();
279+
RefreshLine(*line, *cursor);
186280
} else {
187281
EmitBell();
188282
}
@@ -212,48 +306,61 @@ void LineEditor::RecallHistory(int direction,
212306
EmitBell();
213307
return;
214308
}
309+
suggestion_.clear();
215310
if (!browsing_history_) {
216311
browsing_history_ = true;
217312
saved_line_ = *line;
218313
history_position_ = history_.size();
219314
}
315+
316+
size_t pos = history_position_;
220317
if (direction < 0) {
221-
if (history_position_ == 0) {
222-
EmitBell();
223-
return;
318+
while (pos > 0) {
319+
--pos;
320+
if (history_[pos].compare(0, saved_line_.size(), saved_line_) == 0) {
321+
history_position_ = pos;
322+
*line = history_[history_position_];
323+
*cursor = line->size();
324+
RefreshLine(*line, *cursor);
325+
return;
326+
}
224327
}
225-
--history_position_;
226-
*line = history_[history_position_];
227328
} else {
228-
if (history_position_ + 1 >= history_.size()) {
229-
*line = saved_line_;
230-
browsing_history_ = false;
231-
history_position_ = history_.size();
232-
} else {
233-
++history_position_;
234-
*line = history_[history_position_];
329+
while (pos < history_.size()) {
330+
++pos;
331+
if (pos == history_.size()) {
332+
*line = saved_line_;
333+
browsing_history_ = false;
334+
history_position_ = history_.size();
335+
*cursor = line->size();
336+
RefreshLine(*line, *cursor);
337+
return;
338+
}
339+
if (history_[pos].compare(0, saved_line_.size(), saved_line_) == 0) {
340+
history_position_ = pos;
341+
*line = history_[history_position_];
342+
*cursor = line->size();
343+
RefreshLine(*line, *cursor);
344+
return;
345+
}
235346
}
236347
}
237-
*cursor = line->size();
238-
RefreshLine(*line, *cursor);
348+
EmitBell();
239349
}
240350

241-
void LineEditor::RefreshLine(const std::string& line, size_t cursor) {
242-
printf("\r");
243-
fwrite(line.data(), 1, line.size(), stdout);
244-
size_t clear_width = 0;
245-
if (last_rendered_length_ > line.size()) {
246-
clear_width = last_rendered_length_ - line.size();
247-
}
248-
for (size_t i = 0; i < clear_width; ++i) {
249-
putchar(' ');
351+
void LineEditor::UpdateSuggestion(const std::string& line) {
352+
suggestion_.clear();
353+
if (line.empty()) {
354+
return;
250355
}
251-
printf("\r");
252-
if (cursor > 0) {
253-
fwrite(line.data(), 1, cursor, stdout);
356+
357+
// Search history backwards for a match
358+
for (auto it = history_.rbegin(); it != history_.rend(); ++it) {
359+
if (it->length() > line.length() && it->substr(0, line.length()) == line) {
360+
suggestion_ = it->substr(line.length());
361+
return;
362+
}
254363
}
255-
fflush(stdout);
256-
last_rendered_length_ = line.size();
257364
}
258365

259366
bool LineEditor::IsPrintable(int ch) const {

tools/line_editor.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ class LineEditor {
1616
bool HandleEscapeSequence(int ch, std::string* line, size_t* cursor);
1717
void RecallHistory(int direction, std::string* line, size_t* cursor);
1818
void RefreshLine(const std::string& line, size_t cursor);
19+
void UpdateSuggestion(const std::string& line);
1920
bool IsPrintable(int ch) const;
2021
void EmitBell() const;
2122

@@ -24,5 +25,6 @@ class LineEditor {
2425
size_t history_position_ = 0;
2526
bool browsing_history_ = false;
2627
std::string saved_line_;
28+
std::string suggestion_;
2729
size_t last_rendered_length_ = 0;
2830
};

0 commit comments

Comments
 (0)