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+ }
131215bool 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
259366bool LineEditor::IsPrintable (int ch) const {
0 commit comments