1 /* linenoise.c -- guerrilla line editing library against the idea that a
2 *
3 * Copyright (c) 2010, Salvatore Sanfilippo <antirez at gmail dot com>
4 * Copyright (c) 2010, Pieter Noordhuis <pcnoordhuis at gmail dot com>
5 *
6 * All rights reserved.
7 *
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted provided that the following conditions are met:
10 *
11 * * Redistributions of source code must retain the above copyright notice,
12 * this list of conditions and the following disclaimer.
13 * * Redistributions in binary form must reproduce the above copyright
14 * notice, this list of conditions and the following disclaimer in the
15 * documentation and/or other materials provided with the distribution.
16 * * Neither the name of Redis nor the names of its contributors may be used
17 * to endorse or promote products derived from this software without
18 * specific prior written permission.
19 *
20 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
23 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
24 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
25 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
26 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
27 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
28 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
29 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
30 * POSSIBILITY OF SUCH DAMAGE.
31 *
32 * line editing lib needs to be 20,000 lines of C code.
33 *
34 * You can find the latest source code at:
35 *
36 * http://github.com/antirez/linenoise
37 *
38 * Does a number of crazy assumptions that happen to be true in 99.9999% of
39 * the 2010 UNIX computers around.
40 *
41 * References:
42 * - http://invisible-island.net/xterm/ctlseqs/ctlseqs.html
43 * - http://www.3waylabs.com/nw/WWW/products/wizcon/vt220.html
44 *
45 * Todo list:
46 * - Switch to gets() if $TERM is something we can't support.
47 * - Filter bogus Ctrl+<char> combinations.
48 * - Win32 support
49 *
50 * Bloat:
51 * - Completion?
52 * - History search like Ctrl+r in readline?
53 *
54 * List of escape sequences used by this program, we do everything just
55 * with three sequences. In order to be so cheap we may have some
56 * flickering effect with some slow terminal, but the lesser sequences
57 * the more compatible.
58 *
59 * CHA (Cursor Horizontal Absolute)
60 * Sequence: ESC [ n G
61 * Effect: moves cursor to column n (1 based)
62 *
63 * EL (Erase Line)
64 * Sequence: ESC [ n K
65 * Effect: if n is 0 or missing, clear from cursor to end of line
66 * Effect: if n is 1, clear from beginning of line to cursor
67 * Effect: if n is 2, clear entire line
68 *
69 * CUF (Cursor Forward)
70 * Sequence: ESC [ n C
71 * Effect: moves cursor forward of n chars
72 *
73 * The following are used to clear the screen: ESC [ H ESC [ 2 J
74 * This is actually composed of two sequences:
75 *
76 * cursorhome
77 * Sequence: ESC [ H
78 * Effect: moves the cursor to upper left corner
79 *
80 * ED2 (Clear entire screen)
81 * Sequence: ESC [ 2 J
82 * Effect: clear the whole screen
83 *
84 */
85
86 #include "mongo/platform/basic.h"
87
88 #ifdef _WIN32
89
90 #include <conio.h>
91 #include <io.h>
92 #define strcasecmp _stricmp
93 #define strdup _strdup
94 #define isatty _isatty
95 #define write _write
96 #define STDIN_FILENO 0
97
98 #else /* _WIN32 */
99
100 #include <cctype>
101 #include <signal.h>
102 #include <stdlib.h>
103 #include <string.h>
104 #include <sys/ioctl.h>
105 #include <sys/types.h>
106 #include <termios.h>
107 #include <unistd.h>
108 #include <wctype.h>
109
110 #endif /* _WIN32 */
111
112 #include "linenoise.h"
113 #include "linenoise_utf8.h"
114 #include "mk_wcwidth.h"
115 #include <errno.h>
116 #include <fcntl.h>
117 #include <sstream>
118 #include <stdio.h>
119 #include <string>
120 #include <vector>
121
122 #include "mongo/util/errno_util.h"
123
124 using std::string;
125 using std::vector;
126
127 using std::unique_ptr;
128
129 using linenoise_utf8::UChar8;
130 using linenoise_utf8::UChar32;
131 using linenoise_utf8::copyString8to32;
132 using linenoise_utf8::copyString32;
133 using linenoise_utf8::copyString32to8;
134 using linenoise_utf8::strlen32;
135 using linenoise_utf8::strncmp32;
136 using linenoise_utf8::write32;
137 using linenoise_utf8::Utf8String;
138 using linenoise_utf8::Utf32String;
139
140 struct linenoiseCompletions {
141 vector<Utf32String> completionStrings;
142 };
143
144 #define LINENOISE_DEFAULT_HISTORY_MAX_LEN 1000
145 #define LINENOISE_MAX_LINE 4096
146
147 // make control-characters more readable
148 #define ctrlChar(upperCaseASCII) (upperCaseASCII - 0x40)
149
150 /**
151 * Recompute widths of all characters in a UChar32 buffer
152 * @param text input buffer of Unicode characters
153 * @param widths output buffer of character widths
154 * @param charCount number of characters in buffer
155 */
recomputeCharacterWidths(const UChar32 * text,char * widths,int charCount)156 static void recomputeCharacterWidths(const UChar32* text, char* widths, int charCount) {
157 for (int i = 0; i < charCount; ++i) {
158 widths[i] = mk_wcwidth(text[i]);
159 }
160 }
161
162 /**
163 * Calculate a new screen position given a starting position, screen width and character count
164 * @param x initial x position (zero-based)
165 * @param y initial y position (zero-based)
166 * @param screenColumns screen column count
167 * @param charCount character positions to advance
168 * @param xOut returned x position (zero-based)
169 * @param yOut returned y position (zero-based)
170 */
calculateScreenPosition(int x,int y,int screenColumns,int charCount,int & xOut,int & yOut)171 static void calculateScreenPosition(
172 int x, int y, int screenColumns, int charCount, int& xOut, int& yOut) {
173 xOut = x;
174 yOut = y;
175 int charsRemaining = charCount;
176 while (charsRemaining > 0) {
177 int charsThisRow =
178 (x + charsRemaining < screenColumns) ? charsRemaining : screenColumns - x;
179 xOut = x + charsThisRow;
180 yOut = y;
181 charsRemaining -= charsThisRow;
182 x = 0;
183 ++y;
184 }
185 if (xOut == screenColumns) { // we have to special-case line wrap
186 xOut = 0;
187 ++yOut;
188 }
189 }
190
191 /**
192 * Calculate a column width using mk_wcswidth()
193 * @param buf32 text to calculate
194 * @param len length of text to calculate
195 */
calculateColumnPosition(UChar32 * buf32,int len)196 static int calculateColumnPosition(UChar32* buf32, int len) {
197 int width = mk_wcswidth(reinterpret_cast<const int*>(buf32), len);
198 if (width == -1)
199 return len;
200 else
201 return width;
202 }
203
isControlChar(UChar32 testChar)204 static bool isControlChar(UChar32 testChar) {
205 return (testChar < ' ') || // C0 controls
206 (testChar >= 0x7F && testChar <= 0x9F); // DEL and C1 controls
207 }
208
209 struct PromptBase { // a convenience struct for grouping prompt info
210 Utf32String promptText; // our copy of the prompt text, edited
211 char* promptCharWidths; // character widths from mk_wcwidth()
212 int promptChars; // chars in promptText
213 int promptExtraLines; // extra lines (beyond 1) occupied by prompt
214 int promptIndentation; // column offset to end of prompt
215 int promptLastLinePosition; // index into promptText where last line begins
216 int promptPreviousInputLen; // promptChars of previous input line, for clearing
217 int promptCursorRowOffset; // where the cursor is relative to the start of the prompt
218 int promptScreenColumns; // width of screen in columns
219 int promptPreviousLen; // help erasing
220 int promptErrorCode; // error code (invalid UTF-8) or zero
221
PromptBasePromptBase222 PromptBase() : promptPreviousInputLen(0) {}
223 };
224
225 struct PromptInfo : public PromptBase {
PromptInfoPromptInfo226 PromptInfo(const UChar8* textPtr, int columns) {
227 promptExtraLines = 0;
228 promptLastLinePosition = 0;
229 promptPreviousLen = 0;
230 promptScreenColumns = columns;
231 Utf32String tempUnicode(textPtr);
232
233 // strip control characters from the prompt -- we do allow newline
234 UChar32* pIn = tempUnicode.get();
235 UChar32* pOut = pIn;
236 while (*pIn) {
237 UChar32 c = *pIn;
238 if ('\n' == c || !isControlChar(c)) {
239 *pOut = c;
240 ++pOut;
241 }
242 ++pIn;
243 }
244 *pOut = 0;
245 promptChars = pOut - tempUnicode.get();
246 promptText = tempUnicode;
247
248 int x = 0;
249 for (int i = 0; i < promptChars; ++i) {
250 UChar32 c = promptText[i];
251 if ('\n' == c) {
252 x = 0;
253 ++promptExtraLines;
254 promptLastLinePosition = i + 1;
255 } else {
256 ++x;
257 if (x >= promptScreenColumns) {
258 x = 0;
259 ++promptExtraLines;
260 promptLastLinePosition = i + 1;
261 }
262 }
263 }
264 promptIndentation = promptChars - promptLastLinePosition;
265 promptCursorRowOffset = promptExtraLines;
266 }
267 };
268
269 // Used with DynamicPrompt (history search)
270 //
271 static const Utf32String forwardSearchBasePrompt(reinterpret_cast<const UChar8*>("(i-search)`"));
272 static const Utf32String reverseSearchBasePrompt(
273 reinterpret_cast<const UChar8*>("(reverse-i-search)`"));
274 static const Utf32String endSearchBasePrompt(reinterpret_cast<const UChar8*>("': "));
275 static Utf32String previousSearchText; // remembered across invocations of linenoise()
276
277 // changing prompt for "(reverse-i-search)`text':" etc.
278 //
279 struct DynamicPrompt : public PromptBase {
280 Utf32String searchText; // text we are searching for
281 char* searchCharWidths; // character widths from mk_wcwidth()
282 int searchTextLen; // chars in searchText
283 int direction; // current search direction, 1=forward, -1=reverse
284
DynamicPromptDynamicPrompt285 DynamicPrompt(PromptBase& pi, int initialDirection)
286 : searchTextLen(0), direction(initialDirection) {
287 promptScreenColumns = pi.promptScreenColumns;
288 promptCursorRowOffset = 0;
289 Utf32String emptyString(1);
290 searchText = emptyString;
291 const Utf32String* basePrompt =
292 (direction > 0) ? &forwardSearchBasePrompt : &reverseSearchBasePrompt;
293 size_t promptStartLength = basePrompt->length();
294 promptChars = promptStartLength + endSearchBasePrompt.length();
295 promptLastLinePosition =
296 promptChars; // TODO fix this, we are asssuming that the history prompt won't wrap (!)
297 promptPreviousLen = promptChars;
298 Utf32String tempUnicode(promptChars + 1);
299 memcpy(tempUnicode.get(), basePrompt->get(), sizeof(UChar32) * promptStartLength);
300 memcpy(&tempUnicode[promptStartLength],
301 endSearchBasePrompt.get(),
302 sizeof(UChar32) * (endSearchBasePrompt.length() + 1));
303 tempUnicode.initFromBuffer();
304 promptText = tempUnicode;
305 calculateScreenPosition(
306 0, 0, pi.promptScreenColumns, promptChars, promptIndentation, promptExtraLines);
307 }
308
updateSearchPromptDynamicPrompt309 void updateSearchPrompt(void) {
310 const Utf32String* basePrompt =
311 (direction > 0) ? &forwardSearchBasePrompt : &reverseSearchBasePrompt;
312 size_t promptStartLength = basePrompt->length();
313 promptChars = promptStartLength + searchTextLen + endSearchBasePrompt.length();
314 Utf32String tempUnicode(promptChars + 1);
315 memcpy(tempUnicode.get(), basePrompt->get(), sizeof(UChar32) * promptStartLength);
316 memcpy(&tempUnicode[promptStartLength], searchText.get(), sizeof(UChar32) * searchTextLen);
317 size_t endIndex = promptStartLength + searchTextLen;
318 memcpy(&tempUnicode[endIndex],
319 endSearchBasePrompt.get(),
320 sizeof(UChar32) * (endSearchBasePrompt.length() + 1));
321 tempUnicode.initFromBuffer();
322 promptText = tempUnicode;
323 }
324
updateSearchTextDynamicPrompt325 void updateSearchText(const UChar32* textPtr) {
326 Utf32String tempUnicode(textPtr);
327 searchTextLen = tempUnicode.chars();
328 searchText = tempUnicode;
329 updateSearchPrompt();
330 }
331 };
332
333 class KillRing {
334 static const int capacity = 10;
335 int size;
336 int index;
337 char indexToSlot[10];
338 vector<Utf32String> theRing;
339
340 public:
341 enum action { actionOther, actionKill, actionYank };
342 action lastAction;
343 size_t lastYankSize;
344
KillRing()345 KillRing() : size(0), index(0), lastAction(actionOther) {
346 theRing.reserve(capacity);
347 }
348
kill(const UChar32 * text,int textLen,bool forward)349 void kill(const UChar32* text, int textLen, bool forward) {
350 if (textLen == 0) {
351 return;
352 }
353 Utf32String killedText(text, textLen);
354 if (lastAction == actionKill && size > 0) {
355 int slot = indexToSlot[0];
356 int currentLen = theRing[slot].length();
357 int resultLen = currentLen + textLen;
358 Utf32String temp(resultLen + 1);
359 if (forward) {
360 memcpy(temp.get(), theRing[slot].get(), currentLen * sizeof(UChar32));
361 memcpy(&temp[currentLen], killedText.get(), textLen * sizeof(UChar32));
362 } else {
363 memcpy(temp.get(), killedText.get(), textLen * sizeof(UChar32));
364 memcpy(&temp[textLen], theRing[slot].get(), currentLen * sizeof(UChar32));
365 }
366 temp[resultLen] = 0;
367 temp.initFromBuffer();
368 theRing[slot] = temp;
369 } else {
370 if (size < capacity) {
371 if (size > 0) {
372 memmove(&indexToSlot[1], &indexToSlot[0], size);
373 }
374 indexToSlot[0] = size;
375 size++;
376 theRing.push_back(killedText);
377 } else {
378 int slot = indexToSlot[capacity - 1];
379 theRing[slot] = killedText;
380 memmove(&indexToSlot[1], &indexToSlot[0], capacity - 1);
381 indexToSlot[0] = slot;
382 }
383 index = 0;
384 }
385 }
386
yank()387 Utf32String* yank() {
388 return (size > 0) ? &theRing[indexToSlot[index]] : 0;
389 }
390
yankPop()391 Utf32String* yankPop() {
392 if (size == 0) {
393 return 0;
394 }
395 ++index;
396 if (index == size) {
397 index = 0;
398 }
399 return &theRing[indexToSlot[index]];
400 }
401 };
402
403 class InputBuffer {
404 UChar32* buf32; // input buffer
405 char* charWidths; // character widths from mk_wcwidth()
406 int buflen; // buffer size in characters
407 int len; // length of text in input buffer
408 int pos; // character position in buffer ( 0 <= pos <= len )
409
410 void clearScreen(PromptBase& pi);
411 int incrementalHistorySearch(PromptBase& pi, int startChar);
412 int completeLine(PromptBase& pi);
413 void refreshLine(PromptBase& pi);
414
415 public:
InputBuffer(UChar32 * buffer,char * widthArray,int bufferLen)416 InputBuffer(UChar32* buffer, char* widthArray, int bufferLen)
417 : buf32(buffer), charWidths(widthArray), buflen(bufferLen - 1), len(0), pos(0) {
418 buf32[0] = 0;
419 }
preloadBuffer(const UChar8 * preloadText)420 void preloadBuffer(const UChar8* preloadText) {
421 size_t ucharCount;
422 int errorCode;
423 copyString8to32(buf32, preloadText, buflen + 1, ucharCount, errorCode);
424 recomputeCharacterWidths(buf32, charWidths, ucharCount);
425 len = ucharCount;
426 pos = ucharCount;
427 }
428 int getInputLine(PromptBase& pi);
length(void) const429 int length(void) const {
430 return len;
431 }
432 };
433
434 // Special codes for keyboard input:
435 //
436 // Between Windows and the various Linux "terminal" programs, there is some
437 // pretty diverse behavior in the "scan codes" and escape sequences we are
438 // presented with. So ... we'll translate them all into our own pidgin
439 // pseudocode, trying to stay out of the way of UTF-8 and international
440 // characters. Here's the general plan.
441 //
442 // "User input keystrokes" (key chords, whatever) will be encoded as a single value.
443 // The low 21 bits are reserved for Unicode characters. Popular function-type keys
444 // get their own codes in the range 0x10200000 to (if needed) 0x1FE00000, currently
445 // just arrow keys, Home, End and Delete. Keypresses with Ctrl get ORed with
446 // 0x20000000, with Alt get ORed with 0x40000000. So, Ctrl+Alt+Home is encoded
447 // as 0x20000000 + 0x40000000 + 0x10A00000 == 0x70A00000. To keep things complicated,
448 // the Alt key is equivalent to prefixing the keystroke with ESC, so ESC followed by
449 // D is treated the same as Alt + D ... we'll just use Emacs terminology and call
450 // this "Meta". So, we will encode both ESC followed by D and Alt held down while D
451 // is pressed the same, as Meta-D, encoded as 0x40000064.
452 //
453 // Here are the definitions of our component constants:
454 //
455 // Maximum unsigned 32-bit value = 0xFFFFFFFF; // For reference, max 32-bit value
456 // Highest allocated Unicode char = 0x001FFFFF; // For reference, max Unicode value
457 static const int META = 0x40000000; // Meta key combination
458 static const int CTRL = 0x20000000; // Ctrl key combination
459 static const int SPECIAL_KEY = 0x10000000; // Common bit for all special keys
460 static const int UP_ARROW_KEY = 0x10200000; // Special keys
461 static const int DOWN_ARROW_KEY = 0x10400000;
462 static const int RIGHT_ARROW_KEY = 0x10600000;
463 static const int LEFT_ARROW_KEY = 0x10800000;
464 static const int HOME_KEY = 0x10A00000;
465 static const int END_KEY = 0x10C00000;
466 static const int DELETE_KEY = 0x10E00000;
467 static const int PAGE_UP_KEY = 0x11000000;
468 static const int PAGE_DOWN_KEY = 0x11200000;
469
470 static const char* unsupported_term[] = {"dumb", "cons25", "emacs", NULL};
471 static linenoiseCompletionCallback* completionCallback = NULL;
472
473 #ifdef _WIN32
474 static HANDLE console_in, console_out;
475 static DWORD oldMode;
476 static WORD oldDisplayAttribute;
477 #else
478 static struct termios orig_termios; /* in order to restore at exit */
479 #endif
480
481 static KillRing killRing;
482
483 static int rawmode = 0; /* for atexit() function to check if restore is needed*/
484 static int atexit_registered = 0; /* register atexit just 1 time */
485 static int historyMaxLen = LINENOISE_DEFAULT_HISTORY_MAX_LEN;
486 static int historyLen = 0;
487 static int historyIndex = 0;
488 static UChar8** history = NULL;
489
490 // used to emulate Windows command prompt on down-arrow after a recall
491 // we use -2 as our "not set" value because we add 1 to the previous index on down-arrow,
492 // and zero is a valid index (so -1 is a valid "previous index")
493 static int historyPreviousIndex = -2;
494 static bool historyRecallMostRecent = false;
495
496 static void linenoiseAtExit(void);
497
isUnsupportedTerm(void)498 static bool isUnsupportedTerm(void) {
499 char* term = getenv("TERM");
500 if (term == NULL)
501 return false;
502 for (int j = 0; unsupported_term[j]; ++j)
503 if (!strcasecmp(term, unsupported_term[j])) {
504 return true;
505 }
506 return false;
507 }
508
beep()509 static void beep() {
510 fprintf(stderr, "\x7"); // ctrl-G == bell/beep
511 fflush(stderr);
512 }
513
linenoiseHistoryFree(void)514 void linenoiseHistoryFree(void) {
515 if (history) {
516 for (int j = 0; j < historyLen; ++j)
517 free(history[j]);
518 historyLen = 0;
519 free(history);
520 history = 0;
521 }
522 }
523
enableRawMode(void)524 static int enableRawMode(void) {
525 #ifdef _WIN32
526 if (!console_in) {
527 console_in = GetStdHandle(STD_INPUT_HANDLE);
528 console_out = GetStdHandle(STD_OUTPUT_HANDLE);
529
530 GetConsoleMode(console_in, &oldMode);
531 SetConsoleMode(console_in,
532 oldMode & ~(ENABLE_LINE_INPUT | ENABLE_ECHO_INPUT | ENABLE_PROCESSED_INPUT));
533 }
534 return 0;
535 #else
536 struct termios raw;
537
538 if (!isatty(0))
539 goto fatal;
540 if (!atexit_registered) {
541 atexit(linenoiseAtExit);
542 atexit_registered = 1;
543 }
544 if (tcgetattr(0, &orig_termios) == -1)
545 goto fatal;
546
547 raw = orig_termios; /* modify the original mode */
548 /* input modes: no break, no CR to NL, no parity check, no strip char,
549 * no start/stop output control. */
550 raw.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON);
551 /* output modes - disable post processing */
552 // this is wrong, we don't want raw output, it turns newlines into straight linefeeds
553 // raw.c_oflag &= ~(OPOST);
554 /* control modes - set 8 bit chars */
555 raw.c_cflag |= (CS8);
556 /* local modes - echoing off, canonical off, no extended functions,
557 * no signal chars (^Z,^C) */
558 raw.c_lflag &= ~(ECHO | ICANON | IEXTEN | ISIG);
559 /* control chars - set return condition: min number of bytes and timer.
560 * We want read to return every single byte, without timeout. */
561 raw.c_cc[VMIN] = 1;
562 raw.c_cc[VTIME] = 0; /* 1 byte, no timer */
563
564 /* put terminal in raw mode after flushing */
565 if (tcsetattr(0, TCSADRAIN, &raw) < 0)
566 goto fatal;
567 rawmode = 1;
568 return 0;
569
570 fatal:
571 errno = ENOTTY;
572 return -1;
573 #endif
574 }
575
disableRawMode(void)576 static void disableRawMode(void) {
577 #ifdef _WIN32
578 SetConsoleMode(console_in, oldMode);
579 console_in = 0;
580 console_out = 0;
581 #else
582 if (rawmode && tcsetattr(0, TCSADRAIN, &orig_termios) != -1)
583 rawmode = 0;
584 #endif
585 }
586
587 // At exit we'll try to fix the terminal to the initial conditions
linenoiseAtExit(void)588 static void linenoiseAtExit(void) {
589 disableRawMode();
590 }
591
getScreenColumns(void)592 static int getScreenColumns(void) {
593 int cols;
594 #ifdef _WIN32
595 CONSOLE_SCREEN_BUFFER_INFO inf;
596 GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &inf);
597 cols = inf.dwSize.X;
598 #else
599 struct winsize ws;
600 cols = (ioctl(1, TIOCGWINSZ, &ws) == -1) ? 80 : ws.ws_col;
601 #endif
602 // cols is 0 in certain circumstances like inside debugger, which creates further issues
603 return (cols > 0) ? cols : 80;
604 }
605
getScreenRows(void)606 static int getScreenRows(void) {
607 int rows;
608 #ifdef _WIN32
609 CONSOLE_SCREEN_BUFFER_INFO inf;
610 GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &inf);
611 rows = 1 + inf.srWindow.Bottom - inf.srWindow.Top;
612 #else
613 struct winsize ws;
614 rows = (ioctl(1, TIOCGWINSZ, &ws) == -1) ? 24 : ws.ws_row;
615 #endif
616 return (rows > 0) ? rows : 24;
617 }
618
setDisplayAttribute(bool enhancedDisplay)619 static void setDisplayAttribute(bool enhancedDisplay) {
620 #ifdef _WIN32
621 if (enhancedDisplay) {
622 CONSOLE_SCREEN_BUFFER_INFO inf;
623 GetConsoleScreenBufferInfo(console_out, &inf);
624 oldDisplayAttribute = inf.wAttributes;
625 BYTE oldLowByte = oldDisplayAttribute & 0xFF;
626 BYTE newLowByte;
627 switch (oldLowByte) {
628 case 0x07:
629 // newLowByte = FOREGROUND_BLUE | FOREGROUND_INTENSITY; // too dim
630 // newLowByte = FOREGROUND_BLUE; // even dimmer
631 newLowByte =
632 FOREGROUND_BLUE | FOREGROUND_GREEN; // most similar to xterm appearance
633 break;
634 case 0x70:
635 newLowByte = BACKGROUND_BLUE | BACKGROUND_INTENSITY;
636 break;
637 default:
638 newLowByte = oldLowByte ^ 0xFF; // default to inverse video
639 break;
640 }
641 inf.wAttributes = (inf.wAttributes & 0xFF00) | newLowByte;
642 SetConsoleTextAttribute(console_out, inf.wAttributes);
643 } else {
644 SetConsoleTextAttribute(console_out, oldDisplayAttribute);
645 }
646 #else
647 if (enhancedDisplay) {
648 if (write(1, "\x1b[1;34m", 7) == -1)
649 return; /* bright blue (visible with both B&W bg) */
650 } else {
651 if (write(1, "\x1b[0m", 4) == -1)
652 return; /* reset */
653 }
654 #endif
655 }
656
657 /**
658 * Display the dynamic incremental search prompt and the current user input line.
659 * @param pi PromptBase struct holding information about the prompt and our screen position
660 * @param buf32 input buffer to be displayed
661 * @param len count of characters in the buffer
662 * @param pos current cursor position within the buffer (0 <= pos <= len)
663 */
dynamicRefresh(PromptBase & pi,UChar32 * buf32,int len,int pos)664 static void dynamicRefresh(PromptBase& pi, UChar32* buf32, int len, int pos) {
665 // calculate the position of the end of the prompt
666 int xEndOfPrompt, yEndOfPrompt;
667 calculateScreenPosition(
668 0, 0, pi.promptScreenColumns, pi.promptChars, xEndOfPrompt, yEndOfPrompt);
669 pi.promptIndentation = xEndOfPrompt;
670
671 // calculate the position of the end of the input line
672 int xEndOfInput, yEndOfInput;
673 calculateScreenPosition(xEndOfPrompt,
674 yEndOfPrompt,
675 pi.promptScreenColumns,
676 calculateColumnPosition(buf32, len),
677 xEndOfInput,
678 yEndOfInput);
679
680 // calculate the desired position of the cursor
681 int xCursorPos, yCursorPos;
682 calculateScreenPosition(xEndOfPrompt,
683 yEndOfPrompt,
684 pi.promptScreenColumns,
685 calculateColumnPosition(buf32, pos),
686 xCursorPos,
687 yCursorPos);
688
689 #ifdef _WIN32
690 // position at the start of the prompt, clear to end of previous input
691 CONSOLE_SCREEN_BUFFER_INFO inf;
692 GetConsoleScreenBufferInfo(console_out, &inf);
693 inf.dwCursorPosition.X = 0;
694 inf.dwCursorPosition.Y -= pi.promptCursorRowOffset /*- pi.promptExtraLines*/;
695 SetConsoleCursorPosition(console_out, inf.dwCursorPosition);
696 DWORD count;
697 FillConsoleOutputCharacterA(console_out,
698 ' ',
699 pi.promptPreviousLen + pi.promptPreviousInputLen,
700 inf.dwCursorPosition,
701 &count);
702 pi.promptPreviousLen = pi.promptIndentation;
703 pi.promptPreviousInputLen = len;
704
705 // display the prompt
706 if (write32(1, pi.promptText.get(), pi.promptChars) == -1)
707 return;
708
709 // display the input line
710 if (write32(1, buf32, len) == -1)
711 return;
712
713 // position the cursor
714 GetConsoleScreenBufferInfo(console_out, &inf);
715 inf.dwCursorPosition.X = xCursorPos; // 0-based on Win32
716 inf.dwCursorPosition.Y -= yEndOfInput - yCursorPos;
717 SetConsoleCursorPosition(console_out, inf.dwCursorPosition);
718 #else // _WIN32
719 char seq[64];
720 int cursorRowMovement = pi.promptCursorRowOffset - pi.promptExtraLines;
721 if (cursorRowMovement > 0) { // move the cursor up as required
722 snprintf(seq, sizeof seq, "\x1b[%dA", cursorRowMovement);
723 if (write(1, seq, strlen(seq)) == -1)
724 return;
725 }
726 // position at the start of the prompt, clear to end of screen
727 snprintf(seq, sizeof seq, "\x1b[1G\x1b[J"); // 1-based on VT100
728 if (write(1, seq, strlen(seq)) == -1)
729 return;
730
731 // display the prompt
732 if (write32(1, pi.promptText.get(), pi.promptChars) == -1)
733 return;
734
735 // display the input line
736 if (write32(1, buf32, len) == -1)
737 return;
738
739 // we have to generate our own newline on line wrap
740 if (xEndOfInput == 0 && yEndOfInput > 0)
741 if (write(1, "\n", 1) == -1)
742 return;
743
744 // position the cursor
745 cursorRowMovement = yEndOfInput - yCursorPos;
746 if (cursorRowMovement > 0) { // move the cursor up as required
747 snprintf(seq, sizeof seq, "\x1b[%dA", cursorRowMovement);
748 if (write(1, seq, strlen(seq)) == -1)
749 return;
750 }
751 // position the cursor within the line
752 snprintf(seq, sizeof seq, "\x1b[%dG", xCursorPos + 1); // 1-based on VT100
753 if (write(1, seq, strlen(seq)) == -1)
754 return;
755 #endif
756
757 pi.promptCursorRowOffset = pi.promptExtraLines + yCursorPos; // remember row for next pass
758 }
759
760 /**
761 * Refresh the user's input line: the prompt is already onscreen and is not redrawn here
762 * @param pi PromptBase struct holding information about the prompt and our screen position
763 */
refreshLine(PromptBase & pi)764 void InputBuffer::refreshLine(PromptBase& pi) {
765 // check for a matching brace/bracket/paren, remember its position if found
766 int highlight = -1;
767 if (pos < len) {
768 /* this scans for a brace matching buf32[pos] to highlight */
769 int scanDirection = 0;
770 if (strchr("}])", buf32[pos]))
771 scanDirection = -1; /* backwards */
772 else if (strchr("{[(", buf32[pos]))
773 scanDirection = 1; /* forwards */
774
775 if (scanDirection) {
776 int unmatched = scanDirection;
777 for (int i = pos + scanDirection; i >= 0 && i < len; i += scanDirection) {
778 /* TODO: the right thing when inside a string */
779 if (strchr("}])", buf32[i]))
780 --unmatched;
781 else if (strchr("{[(", buf32[i]))
782 ++unmatched;
783
784 if (unmatched == 0) {
785 highlight = i;
786 break;
787 }
788 }
789 }
790 }
791
792 // calculate the position of the end of the input line
793 int xEndOfInput, yEndOfInput;
794 calculateScreenPosition(pi.promptIndentation,
795 0,
796 pi.promptScreenColumns,
797 calculateColumnPosition(buf32, len),
798 xEndOfInput,
799 yEndOfInput);
800
801 // calculate the desired position of the cursor
802 int xCursorPos, yCursorPos;
803 calculateScreenPosition(pi.promptIndentation,
804 0,
805 pi.promptScreenColumns,
806 calculateColumnPosition(buf32, pos),
807 xCursorPos,
808 yCursorPos);
809
810 #ifdef _WIN32
811 // position at the end of the prompt, clear to end of previous input
812 CONSOLE_SCREEN_BUFFER_INFO inf;
813 GetConsoleScreenBufferInfo(console_out, &inf);
814 inf.dwCursorPosition.X = pi.promptIndentation; // 0-based on Win32
815 inf.dwCursorPosition.Y -= pi.promptCursorRowOffset - pi.promptExtraLines;
816 SetConsoleCursorPosition(console_out, inf.dwCursorPosition);
817 DWORD count;
818 if (len < pi.promptPreviousInputLen)
819 FillConsoleOutputCharacterA(
820 console_out, ' ', pi.promptPreviousInputLen, inf.dwCursorPosition, &count);
821 pi.promptPreviousInputLen = len;
822
823 // display the input line
824 if (highlight == -1) {
825 if (write32(1, buf32, len) == -1)
826 return;
827 } else {
828 if (write32(1, buf32, highlight) == -1)
829 return;
830 setDisplayAttribute(true); /* bright blue (visible with both B&W bg) */
831 if (write32(1, &buf32[highlight], 1) == -1)
832 return;
833 setDisplayAttribute(false);
834 if (write32(1, buf32 + highlight + 1, len - highlight - 1) == -1)
835 return;
836 }
837
838 // position the cursor
839 GetConsoleScreenBufferInfo(console_out, &inf);
840 inf.dwCursorPosition.X = xCursorPos; // 0-based on Win32
841 inf.dwCursorPosition.Y -= yEndOfInput - yCursorPos;
842 SetConsoleCursorPosition(console_out, inf.dwCursorPosition);
843 #else // _WIN32
844 char seq[64];
845 int cursorRowMovement = pi.promptCursorRowOffset - pi.promptExtraLines;
846 if (cursorRowMovement > 0) { // move the cursor up as required
847 snprintf(seq, sizeof seq, "\x1b[%dA", cursorRowMovement);
848 if (write(1, seq, strlen(seq)) == -1)
849 return;
850 }
851 // position at the end of the prompt, clear to end of screen
852 snprintf(seq, sizeof seq, "\x1b[%dG\x1b[J", pi.promptIndentation + 1); // 1-based on VT100
853 if (write(1, seq, strlen(seq)) == -1)
854 return;
855
856 if (highlight == -1) { // write unhighlighted text
857 if (write32(1, buf32, len) == -1)
858 return;
859 } else { // highlight the matching brace/bracket/parenthesis
860 if (write32(1, buf32, highlight) == -1)
861 return;
862 setDisplayAttribute(true);
863 if (write32(1, &buf32[highlight], 1) == -1)
864 return;
865 setDisplayAttribute(false);
866 if (write32(1, buf32 + highlight + 1, len - highlight - 1) == -1)
867 return;
868 }
869
870 // we have to generate our own newline on line wrap
871 if (xEndOfInput == 0 && yEndOfInput > 0)
872 if (write(1, "\n", 1) == -1)
873 return;
874
875 // position the cursor
876 cursorRowMovement = yEndOfInput - yCursorPos;
877 if (cursorRowMovement > 0) { // move the cursor up as required
878 snprintf(seq, sizeof seq, "\x1b[%dA", cursorRowMovement);
879 if (write(1, seq, strlen(seq)) == -1)
880 return;
881 }
882 // position the cursor within the line
883 snprintf(seq, sizeof seq, "\x1b[%dG", xCursorPos + 1); // 1-based on VT100
884 if (write(1, seq, strlen(seq)) == -1)
885 return;
886 #endif
887
888 pi.promptCursorRowOffset = pi.promptExtraLines + yCursorPos; // remember row for next pass
889 }
890
891 #ifndef _WIN32
892
893 /**
894 * Read a UTF-8 sequence from the non-Windows keyboard and return the Unicode (UChar32) character it
895 * encodes
896 *
897 * @return UChar32 Unicode character
898 */
readUnicodeCharacter(void)899 static UChar32 readUnicodeCharacter(void) {
900 static UChar8 utf8String[5];
901 static size_t utf8Count = 0;
902 while (true) {
903 UChar8 c;
904 if (read(0, &c, 1) <= 0)
905 return 0;
906 if (c <= 0x7F) { // short circuit ASCII
907 utf8Count = 0;
908 return c;
909 } else if (utf8Count < sizeof(utf8String) - 1) {
910 utf8String[utf8Count++] = c;
911 utf8String[utf8Count] = 0;
912 UChar32 unicodeChar[2];
913 size_t ucharCount;
914 int errorCode;
915 copyString8to32(unicodeChar, utf8String, 2, ucharCount, errorCode);
916 if (ucharCount && errorCode == 0) {
917 utf8Count = 0;
918 return unicodeChar[0];
919 }
920 } else {
921 utf8Count = 0; // this shouldn't happen: got four bytes but no UTF-8 character
922 }
923 }
924 }
925
926 namespace EscapeSequenceProcessing { // move these out of global namespace
927
928 // This chunk of code does parsing of the escape sequences sent by various Linux terminals.
929 //
930 // It handles arrow keys, Home, End and Delete keys by interpreting the sequences sent by
931 // gnome terminal, xterm, rxvt, konsole, aterm and yakuake including the Alt and Ctrl key
932 // combinations that are understood by linenoise.
933 //
934 // The parsing uses tables, a bunch of intermediate dispatch routines and a doDispatch
935 // loop that reads the tables and sends control to "deeper" routines to continue the
936 // parsing. The starting call to doDispatch( c, initialDispatch ) will eventually return
937 // either a character (with optional CTRL and META bits set), or -1 if parsing fails, or
938 // zero if an attempt to read from the keyboard fails.
939 //
940 // This is rather sloppy escape sequence processing, since we're not paying attention to what the
941 // actual TERM is set to and are processing all key sequences for all terminals, but it works with
942 // the most common keystrokes on the most common terminals. It's intricate, but the nested 'if'
943 // statements required to do it directly would be worse. This way has the advantage of allowing
944 // changes and extensions without having to touch a lot of code.
945
946 // This is a typedef for the routine called by doDispatch(). It takes the current character
947 // as input, does any required processing including reading more characters and calling other
948 // dispatch routines, then eventually returns the final (possibly extended or special) character.
949 //
950 typedef UChar32 (*CharacterDispatchRoutine)(UChar32);
951
952 // This structure is used by doDispatch() to hold a list of characters to test for and
953 // a list of routines to call if the character matches. The dispatch routine list is one
954 // longer than the character list; the final entry is used if no character matches.
955 //
956 struct CharacterDispatch {
957 unsigned int len; // length of the chars list
958 const char* chars; // chars to test
959 CharacterDispatchRoutine* dispatch; // array of routines to call
960 };
961
962 // This dispatch routine is given a dispatch table and then farms work out to routines
963 // listed in the table based on the character it is called with. The dispatch routines can
964 // read more input characters to decide what should eventually be returned. Eventually,
965 // a called routine returns either a character or -1 to indicate parsing failure.
966 //
doDispatch(UChar32 c,CharacterDispatch & dispatchTable)967 static UChar32 doDispatch(UChar32 c, CharacterDispatch& dispatchTable) {
968 for (unsigned int i = 0; i < dispatchTable.len; ++i) {
969 if (static_cast<unsigned char>(dispatchTable.chars[i]) == c) {
970 return dispatchTable.dispatch[i](c);
971 }
972 }
973 return dispatchTable.dispatch[dispatchTable.len](c);
974 }
975
976 static UChar32 thisKeyMetaCtrl = 0; // holds pre-set Meta and/or Ctrl modifiers
977
978 // Final dispatch routines -- return something
979 //
normalKeyRoutine(UChar32 c)980 static UChar32 normalKeyRoutine(UChar32 c) {
981 return thisKeyMetaCtrl | c;
982 }
upArrowKeyRoutine(UChar32 c)983 static UChar32 upArrowKeyRoutine(UChar32 c) {
984 return thisKeyMetaCtrl | UP_ARROW_KEY;
985 }
downArrowKeyRoutine(UChar32 c)986 static UChar32 downArrowKeyRoutine(UChar32 c) {
987 return thisKeyMetaCtrl | DOWN_ARROW_KEY;
988 }
rightArrowKeyRoutine(UChar32 c)989 static UChar32 rightArrowKeyRoutine(UChar32 c) {
990 return thisKeyMetaCtrl | RIGHT_ARROW_KEY;
991 }
leftArrowKeyRoutine(UChar32 c)992 static UChar32 leftArrowKeyRoutine(UChar32 c) {
993 return thisKeyMetaCtrl | LEFT_ARROW_KEY;
994 }
homeKeyRoutine(UChar32 c)995 static UChar32 homeKeyRoutine(UChar32 c) {
996 return thisKeyMetaCtrl | HOME_KEY;
997 }
endKeyRoutine(UChar32 c)998 static UChar32 endKeyRoutine(UChar32 c) {
999 return thisKeyMetaCtrl | END_KEY;
1000 }
pageUpKeyRoutine(UChar32 c)1001 static UChar32 pageUpKeyRoutine(UChar32 c) {
1002 return thisKeyMetaCtrl | PAGE_UP_KEY;
1003 }
pageDownKeyRoutine(UChar32 c)1004 static UChar32 pageDownKeyRoutine(UChar32 c) {
1005 return thisKeyMetaCtrl | PAGE_DOWN_KEY;
1006 }
deleteCharRoutine(UChar32 c)1007 static UChar32 deleteCharRoutine(UChar32 c) {
1008 return thisKeyMetaCtrl | ctrlChar('H');
1009 } // key labeled Backspace
deleteKeyRoutine(UChar32 c)1010 static UChar32 deleteKeyRoutine(UChar32 c) {
1011 return thisKeyMetaCtrl | DELETE_KEY;
1012 } // key labeled Delete
ctrlUpArrowKeyRoutine(UChar32 c)1013 static UChar32 ctrlUpArrowKeyRoutine(UChar32 c) {
1014 return thisKeyMetaCtrl | CTRL | UP_ARROW_KEY;
1015 }
ctrlDownArrowKeyRoutine(UChar32 c)1016 static UChar32 ctrlDownArrowKeyRoutine(UChar32 c) {
1017 return thisKeyMetaCtrl | CTRL | DOWN_ARROW_KEY;
1018 }
ctrlRightArrowKeyRoutine(UChar32 c)1019 static UChar32 ctrlRightArrowKeyRoutine(UChar32 c) {
1020 return thisKeyMetaCtrl | CTRL | RIGHT_ARROW_KEY;
1021 }
ctrlLeftArrowKeyRoutine(UChar32 c)1022 static UChar32 ctrlLeftArrowKeyRoutine(UChar32 c) {
1023 return thisKeyMetaCtrl | CTRL | LEFT_ARROW_KEY;
1024 }
escFailureRoutine(UChar32 c)1025 static UChar32 escFailureRoutine(UChar32 c) {
1026 beep();
1027 return -1;
1028 }
1029
1030 // Handle ESC [ 1 ; 3 (or 5) <more stuff> escape sequences
1031 //
1032 static CharacterDispatchRoutine escLeftBracket1Semicolon3or5Routines[] = {upArrowKeyRoutine,
1033 downArrowKeyRoutine,
1034 rightArrowKeyRoutine,
1035 leftArrowKeyRoutine,
1036 escFailureRoutine};
1037 static CharacterDispatch escLeftBracket1Semicolon3or5Dispatch = {
1038 4, "ABCD", escLeftBracket1Semicolon3or5Routines};
1039
1040 // Handle ESC [ 1 ; <more stuff> escape sequences
1041 //
escLeftBracket1Semicolon3Routine(UChar32 c)1042 static UChar32 escLeftBracket1Semicolon3Routine(UChar32 c) {
1043 c = readUnicodeCharacter();
1044 if (c == 0)
1045 return 0;
1046 thisKeyMetaCtrl |= META;
1047 return doDispatch(c, escLeftBracket1Semicolon3or5Dispatch);
1048 }
escLeftBracket1Semicolon5Routine(UChar32 c)1049 static UChar32 escLeftBracket1Semicolon5Routine(UChar32 c) {
1050 c = readUnicodeCharacter();
1051 if (c == 0)
1052 return 0;
1053 thisKeyMetaCtrl |= CTRL;
1054 return doDispatch(c, escLeftBracket1Semicolon3or5Dispatch);
1055 }
1056 static CharacterDispatchRoutine escLeftBracket1SemicolonRoutines[] = {
1057 escLeftBracket1Semicolon3Routine, escLeftBracket1Semicolon5Routine, escFailureRoutine};
1058 static CharacterDispatch escLeftBracket1SemicolonDispatch = {
1059 2, "35", escLeftBracket1SemicolonRoutines};
1060
1061 // Handle ESC [ 1 <more stuff> escape sequences
1062 //
escLeftBracket1SemicolonRoutine(UChar32 c)1063 static UChar32 escLeftBracket1SemicolonRoutine(UChar32 c) {
1064 c = readUnicodeCharacter();
1065 if (c == 0)
1066 return 0;
1067 return doDispatch(c, escLeftBracket1SemicolonDispatch);
1068 }
1069 static CharacterDispatchRoutine escLeftBracket1Routines[] = {
1070 homeKeyRoutine, escLeftBracket1SemicolonRoutine, escFailureRoutine};
1071 static CharacterDispatch escLeftBracket1Dispatch = {2, "~;", escLeftBracket1Routines};
1072
1073 // Handle ESC [ 3 <more stuff> escape sequences
1074 //
1075 static CharacterDispatchRoutine escLeftBracket3Routines[] = {deleteKeyRoutine, escFailureRoutine};
1076 static CharacterDispatch escLeftBracket3Dispatch = {1, "~", escLeftBracket3Routines};
1077
1078 // Handle ESC [ 4 <more stuff> escape sequences
1079 //
1080 static CharacterDispatchRoutine escLeftBracket4Routines[] = {endKeyRoutine, escFailureRoutine};
1081 static CharacterDispatch escLeftBracket4Dispatch = {1, "~", escLeftBracket4Routines};
1082
1083 // Handle ESC [ 5 <more stuff> escape sequences
1084 //
1085 static CharacterDispatchRoutine escLeftBracket5Routines[] = {pageUpKeyRoutine, escFailureRoutine};
1086 static CharacterDispatch escLeftBracket5Dispatch = {1, "~", escLeftBracket5Routines};
1087
1088 // Handle ESC [ 6 <more stuff> escape sequences
1089 //
1090 static CharacterDispatchRoutine escLeftBracket6Routines[] = {pageDownKeyRoutine, escFailureRoutine};
1091 static CharacterDispatch escLeftBracket6Dispatch = {1, "~", escLeftBracket6Routines};
1092
1093 // Handle ESC [ 7 <more stuff> escape sequences
1094 //
1095 static CharacterDispatchRoutine escLeftBracket7Routines[] = {homeKeyRoutine, escFailureRoutine};
1096 static CharacterDispatch escLeftBracket7Dispatch = {1, "~", escLeftBracket7Routines};
1097
1098 // Handle ESC [ 8 <more stuff> escape sequences
1099 //
1100 static CharacterDispatchRoutine escLeftBracket8Routines[] = {endKeyRoutine, escFailureRoutine};
1101 static CharacterDispatch escLeftBracket8Dispatch = {1, "~", escLeftBracket8Routines};
1102
1103 // Handle ESC [ <digit> escape sequences
1104 //
escLeftBracket0Routine(UChar32 c)1105 static UChar32 escLeftBracket0Routine(UChar32 c) {
1106 return escFailureRoutine(c);
1107 }
escLeftBracket1Routine(UChar32 c)1108 static UChar32 escLeftBracket1Routine(UChar32 c) {
1109 c = readUnicodeCharacter();
1110 if (c == 0)
1111 return 0;
1112 return doDispatch(c, escLeftBracket1Dispatch);
1113 }
escLeftBracket2Routine(UChar32 c)1114 static UChar32 escLeftBracket2Routine(UChar32 c) {
1115 return escFailureRoutine(c); // Insert key, unused
1116 }
escLeftBracket3Routine(UChar32 c)1117 static UChar32 escLeftBracket3Routine(UChar32 c) {
1118 c = readUnicodeCharacter();
1119 if (c == 0)
1120 return 0;
1121 return doDispatch(c, escLeftBracket3Dispatch);
1122 }
escLeftBracket4Routine(UChar32 c)1123 static UChar32 escLeftBracket4Routine(UChar32 c) {
1124 c = readUnicodeCharacter();
1125 if (c == 0)
1126 return 0;
1127 return doDispatch(c, escLeftBracket4Dispatch);
1128 }
escLeftBracket5Routine(UChar32 c)1129 static UChar32 escLeftBracket5Routine(UChar32 c) {
1130 c = readUnicodeCharacter();
1131 if (c == 0)
1132 return 0;
1133 return doDispatch(c, escLeftBracket5Dispatch);
1134 }
escLeftBracket6Routine(UChar32 c)1135 static UChar32 escLeftBracket6Routine(UChar32 c) {
1136 c = readUnicodeCharacter();
1137 if (c == 0)
1138 return 0;
1139 return doDispatch(c, escLeftBracket6Dispatch);
1140 }
escLeftBracket7Routine(UChar32 c)1141 static UChar32 escLeftBracket7Routine(UChar32 c) {
1142 c = readUnicodeCharacter();
1143 if (c == 0)
1144 return 0;
1145 return doDispatch(c, escLeftBracket7Dispatch);
1146 }
escLeftBracket8Routine(UChar32 c)1147 static UChar32 escLeftBracket8Routine(UChar32 c) {
1148 c = readUnicodeCharacter();
1149 if (c == 0)
1150 return 0;
1151 return doDispatch(c, escLeftBracket8Dispatch);
1152 }
escLeftBracket9Routine(UChar32 c)1153 static UChar32 escLeftBracket9Routine(UChar32 c) {
1154 return escFailureRoutine(c);
1155 }
1156
1157 // Handle ESC [ <more stuff> escape sequences
1158 //
1159 static CharacterDispatchRoutine escLeftBracketRoutines[] = {upArrowKeyRoutine,
1160 downArrowKeyRoutine,
1161 rightArrowKeyRoutine,
1162 leftArrowKeyRoutine,
1163 homeKeyRoutine,
1164 endKeyRoutine,
1165 escLeftBracket0Routine,
1166 escLeftBracket1Routine,
1167 escLeftBracket2Routine,
1168 escLeftBracket3Routine,
1169 escLeftBracket4Routine,
1170 escLeftBracket5Routine,
1171 escLeftBracket6Routine,
1172 escLeftBracket7Routine,
1173 escLeftBracket8Routine,
1174 escLeftBracket9Routine,
1175 escFailureRoutine};
1176 static CharacterDispatch escLeftBracketDispatch = {16, "ABCDHF0123456789", escLeftBracketRoutines};
1177
1178 // Handle ESC O <char> escape sequences
1179 //
1180 static CharacterDispatchRoutine escORoutines[] = {upArrowKeyRoutine,
1181 downArrowKeyRoutine,
1182 rightArrowKeyRoutine,
1183 leftArrowKeyRoutine,
1184 homeKeyRoutine,
1185 endKeyRoutine,
1186 ctrlUpArrowKeyRoutine,
1187 ctrlDownArrowKeyRoutine,
1188 ctrlRightArrowKeyRoutine,
1189 ctrlLeftArrowKeyRoutine,
1190 escFailureRoutine};
1191 static CharacterDispatch escODispatch = {10, "ABCDHFabcd", escORoutines};
1192
1193 // Initial ESC dispatch -- could be a Meta prefix or the start of an escape sequence
1194 //
escLeftBracketRoutine(UChar32 c)1195 static UChar32 escLeftBracketRoutine(UChar32 c) {
1196 c = readUnicodeCharacter();
1197 if (c == 0)
1198 return 0;
1199 return doDispatch(c, escLeftBracketDispatch);
1200 }
escORoutine(UChar32 c)1201 static UChar32 escORoutine(UChar32 c) {
1202 c = readUnicodeCharacter();
1203 if (c == 0)
1204 return 0;
1205 return doDispatch(c, escODispatch);
1206 }
1207 static UChar32 setMetaRoutine(UChar32 c); // need forward reference
1208 static CharacterDispatchRoutine escRoutines[] = {
1209 escLeftBracketRoutine, escORoutine, setMetaRoutine};
1210 static CharacterDispatch escDispatch = {2, "[O", escRoutines};
1211
1212 // Initial dispatch -- we are not in the middle of anything yet
1213 //
escRoutine(UChar32 c)1214 static UChar32 escRoutine(UChar32 c) {
1215 c = readUnicodeCharacter();
1216 if (c == 0)
1217 return 0;
1218 return doDispatch(c, escDispatch);
1219 }
1220 static CharacterDispatchRoutine initialRoutines[] = {
1221 escRoutine, deleteCharRoutine, normalKeyRoutine};
1222 static CharacterDispatch initialDispatch = {2, "\x1B\x7F", initialRoutines};
1223
1224 // Special handling for the ESC key because it does double duty
1225 //
setMetaRoutine(UChar32 c)1226 static UChar32 setMetaRoutine(UChar32 c) {
1227 thisKeyMetaCtrl = META;
1228 if (c == 0x1B) { // another ESC, stay in ESC processing mode
1229 c = readUnicodeCharacter();
1230 if (c == 0)
1231 return 0;
1232 return doDispatch(c, escDispatch);
1233 }
1234 return doDispatch(c, initialDispatch);
1235 }
1236
1237 } // namespace EscapeSequenceProcessing // move these out of global namespace
1238
1239 #endif // #ifndef _WIN32
1240
1241 // linenoiseReadChar -- read a keystroke or keychord from the keyboard, and translate it
1242 // into an encoded "keystroke". When convenient, extended keys are translated into their
1243 // simpler Emacs keystrokes, so an unmodified "left arrow" becomes Ctrl-B.
1244 //
1245 // A return value of zero means "no input available", and a return value of -1 means "invalid key".
1246 //
linenoiseReadChar(void)1247 static UChar32 linenoiseReadChar(void) {
1248 #ifdef _WIN32
1249
1250 INPUT_RECORD rec;
1251 DWORD count;
1252 int modifierKeys = 0;
1253 bool escSeen = false;
1254 while (true) {
1255 ReadConsoleInputW(console_in, &rec, 1, &count);
1256 #if 0 // helper for debugging keystrokes, display info in the debug "Output" window in the debugger
1257 {
1258 if ( rec.EventType == KEY_EVENT ) {
1259 //if ( rec.Event.KeyEvent.uChar.UnicodeChar ) {
1260 char buf[1024];
1261 sprintf(
1262 buf,
1263 "Unicode character 0x%04X, repeat count %d, virtual keycode 0x%04X, "
1264 "virtual scancode 0x%04X, key %s%s%s%s%s\n",
1265 rec.Event.KeyEvent.uChar.UnicodeChar,
1266 rec.Event.KeyEvent.wRepeatCount,
1267 rec.Event.KeyEvent.wVirtualKeyCode,
1268 rec.Event.KeyEvent.wVirtualScanCode,
1269 rec.Event.KeyEvent.bKeyDown ? "down" : "up",
1270 (rec.Event.KeyEvent.dwControlKeyState & LEFT_CTRL_PRESSED) ?
1271 " L-Ctrl" : "",
1272 (rec.Event.KeyEvent.dwControlKeyState & RIGHT_CTRL_PRESSED) ?
1273 " R-Ctrl" : "",
1274 (rec.Event.KeyEvent.dwControlKeyState & LEFT_ALT_PRESSED) ?
1275 " L-Alt" : "",
1276 (rec.Event.KeyEvent.dwControlKeyState & RIGHT_ALT_PRESSED) ?
1277 " R-Alt" : ""
1278 );
1279 OutputDebugStringA( buf );
1280 //}
1281 }
1282 }
1283 #endif
1284 if (rec.EventType != KEY_EVENT) {
1285 continue;
1286 }
1287 // Windows provides for entry of characters that are not on your keyboard by sending the
1288 // Unicode characters as a "key up" with virtual keycode 0x12 (VK_MENU == Alt key) ...
1289 // accept these characters, otherwise only process characters on "key down"
1290 if (!rec.Event.KeyEvent.bKeyDown && rec.Event.KeyEvent.wVirtualKeyCode != VK_MENU) {
1291 continue;
1292 }
1293 modifierKeys = 0;
1294 // AltGr is encoded as ( LEFT_CTRL_PRESSED | RIGHT_ALT_PRESSED ), so don't treat this
1295 // combination as either CTRL or META we just turn off those two bits, so it is still
1296 // possible to combine CTRL and/or META with an AltGr key by using right-Ctrl and/or
1297 // left-Alt
1298 if ((rec.Event.KeyEvent.dwControlKeyState & (LEFT_CTRL_PRESSED | RIGHT_ALT_PRESSED)) ==
1299 (LEFT_CTRL_PRESSED | RIGHT_ALT_PRESSED)) {
1300 rec.Event.KeyEvent.dwControlKeyState &= ~(LEFT_CTRL_PRESSED | RIGHT_ALT_PRESSED);
1301 }
1302 if (rec.Event.KeyEvent.dwControlKeyState & (RIGHT_CTRL_PRESSED | LEFT_CTRL_PRESSED)) {
1303 modifierKeys |= CTRL;
1304 }
1305 if (rec.Event.KeyEvent.dwControlKeyState & (RIGHT_ALT_PRESSED | LEFT_ALT_PRESSED)) {
1306 modifierKeys |= META;
1307 }
1308 if (escSeen) {
1309 modifierKeys |= META;
1310 }
1311 if (rec.Event.KeyEvent.uChar.UnicodeChar == 0) {
1312 switch (rec.Event.KeyEvent.wVirtualKeyCode) {
1313 case VK_LEFT:
1314 return modifierKeys | LEFT_ARROW_KEY;
1315 case VK_RIGHT:
1316 return modifierKeys | RIGHT_ARROW_KEY;
1317 case VK_UP:
1318 return modifierKeys | UP_ARROW_KEY;
1319 case VK_DOWN:
1320 return modifierKeys | DOWN_ARROW_KEY;
1321 case VK_DELETE:
1322 return modifierKeys | DELETE_KEY;
1323 case VK_HOME:
1324 return modifierKeys | HOME_KEY;
1325 case VK_END:
1326 return modifierKeys | END_KEY;
1327 case VK_PRIOR:
1328 return modifierKeys | PAGE_UP_KEY;
1329 case VK_NEXT:
1330 return modifierKeys | PAGE_DOWN_KEY;
1331 default:
1332 continue; // in raw mode, ReadConsoleInput shows shift, ctrl ...
1333 } // ... ignore them
1334 } else if (rec.Event.KeyEvent.uChar.UnicodeChar ==
1335 ctrlChar('[')) { // ESC, set flag for later
1336 escSeen = true;
1337 continue;
1338 } else {
1339 // we got a real character, return it
1340 return modifierKeys | rec.Event.KeyEvent.uChar.UnicodeChar;
1341 }
1342 }
1343
1344 #else
1345 UChar32 c;
1346 c = readUnicodeCharacter();
1347 if (c == 0)
1348 return 0;
1349
1350 // If _DEBUG_LINUX_KEYBOARD is set, then ctrl-^ puts us into a keyboard debugging mode
1351 // where we print out decimal and decoded values for whatever the "terminal" program
1352 // gives us on different keystrokes. Hit ctrl-C to exit this mode.
1353 //
1354 #define _DEBUG_LINUX_KEYBOARD
1355 #if defined(_DEBUG_LINUX_KEYBOARD)
1356 if (c == ctrlChar('^')) { // ctrl-^, special debug mode, prints all keys hit, ctrl-C to get out
1357 printf("\nEntering keyboard debugging mode (on ctrl-^), press ctrl-C to exit this mode\n");
1358 while (true) {
1359 unsigned char keys[10];
1360 int ret = read(0, keys, 10);
1361
1362 if (ret <= 0) {
1363 printf("\nret: %d\n", ret);
1364 }
1365 for (int i = 0; i < ret; ++i) {
1366 UChar32 key = static_cast<UChar32>(keys[i]);
1367 char* friendlyTextPtr;
1368 char friendlyTextBuf[10];
1369 const char* prefixText = (key < 0x80) ? "" : "0x80+";
1370 UChar32 keyCopy = (key < 0x80) ? key : key - 0x80;
1371 if (keyCopy >= '!' && keyCopy <= '~') { // printable
1372 friendlyTextBuf[0] = '\'';
1373 friendlyTextBuf[1] = keyCopy;
1374 friendlyTextBuf[2] = '\'';
1375 friendlyTextBuf[3] = 0;
1376 friendlyTextPtr = friendlyTextBuf;
1377 } else if (keyCopy == ' ') {
1378 friendlyTextPtr = const_cast<char*>("space");
1379 } else if (keyCopy == 27) {
1380 friendlyTextPtr = const_cast<char*>("ESC");
1381 } else if (keyCopy == 0) {
1382 friendlyTextPtr = const_cast<char*>("NUL");
1383 } else if (keyCopy == 127) {
1384 friendlyTextPtr = const_cast<char*>("DEL");
1385 } else {
1386 friendlyTextBuf[0] = '^';
1387 friendlyTextBuf[1] = keyCopy + 0x40;
1388 friendlyTextBuf[2] = 0;
1389 friendlyTextPtr = friendlyTextBuf;
1390 }
1391 printf("%d x%02X (%s%s) ", key, key, prefixText, friendlyTextPtr);
1392 }
1393 printf("\x1b[1G\n"); // go to first column of new line
1394
1395 // drop out of this loop on ctrl-C
1396 if (keys[0] == ctrlChar('C')) {
1397 printf("Leaving keyboard debugging mode (on ctrl-C)\n");
1398 fflush(stdout);
1399 return -2;
1400 }
1401 }
1402 }
1403 #endif // _DEBUG_LINUX_KEYBOARD
1404
1405 EscapeSequenceProcessing::thisKeyMetaCtrl = 0; // no modifiers yet at initialDispatch
1406 return EscapeSequenceProcessing::doDispatch(c, EscapeSequenceProcessing::initialDispatch);
1407 #endif // #_WIN32
1408 }
1409
1410 /**
1411 * Free memory used in a recent command completion session
1412 *
1413 * @param lc pointer to a linenoiseCompletions struct
1414 */
freeCompletions(linenoiseCompletions * lc)1415 static void freeCompletions(linenoiseCompletions* lc) {
1416 lc->completionStrings.clear();
1417 }
1418
1419 /**
1420 * convert {CTRL + 'A'}, {CTRL + 'a'} and {CTRL + ctrlChar( 'A' )} into ctrlChar( 'A' )
1421 * leave META alone
1422 *
1423 * @param c character to clean up
1424 * @return cleaned-up character
1425 */
cleanupCtrl(int c)1426 static int cleanupCtrl(int c) {
1427 if (c & CTRL) {
1428 int d = c & 0x1FF;
1429 if (d >= 'a' && d <= 'z') {
1430 c = (c + ('a' - ctrlChar('A'))) & ~CTRL;
1431 }
1432 if (d >= 'A' && d <= 'Z') {
1433 c = (c + ('A' - ctrlChar('A'))) & ~CTRL;
1434 }
1435 if (d >= ctrlChar('A') && d <= ctrlChar('Z')) {
1436 c = c & ~CTRL;
1437 }
1438 }
1439 return c;
1440 }
1441
1442 // break characters that may precede items to be completed
1443 static const char breakChars[] = " =+-/\\*?\"'`&<>;|@{([])}";
1444
1445 // maximum number of completions to display without asking
1446 static const size_t completionCountCutoff = 100;
1447
1448 /**
1449 * Handle command completion, using a completionCallback() routine to provide possible substitutions
1450 * This routine handles the mechanics of updating the user's input buffer with possible replacement
1451 * of text as the user selects a proposed completion string, or cancels the completion attempt.
1452 * @param pi PromptBase struct holding information about the prompt and our screen position
1453 */
completeLine(PromptBase & pi)1454 int InputBuffer::completeLine(PromptBase& pi) {
1455 linenoiseCompletions lc;
1456 char c = 0;
1457
1458 // completionCallback() expects a parsable entity, so find the previous break character and
1459 // extract a copy to parse. we also handle the case where tab is hit while not at end-of-line.
1460 int startIndex = pos;
1461 while (--startIndex >= 0) {
1462 if (strchr(breakChars, buf32[startIndex])) {
1463 break;
1464 }
1465 }
1466 ++startIndex;
1467 int itemLength = pos - startIndex;
1468 Utf32String unicodeCopy(&buf32[startIndex], itemLength);
1469 Utf8String parseItem(unicodeCopy);
1470
1471 // get a list of completions
1472 completionCallback(reinterpret_cast<char*>(parseItem.get()), &lc);
1473
1474 // if no completions, we are done
1475 if (lc.completionStrings.size() == 0) {
1476 beep();
1477 freeCompletions(&lc);
1478 return 0;
1479 }
1480
1481 // at least one completion
1482 int longestCommonPrefix = 0;
1483 int displayLength = 0;
1484 if (lc.completionStrings.size() == 1) {
1485 longestCommonPrefix = lc.completionStrings[0].length();
1486 } else {
1487 bool keepGoing = true;
1488 while (keepGoing) {
1489 for (size_t j = 0; j < lc.completionStrings.size() - 1; ++j) {
1490 char c1 = lc.completionStrings[j][longestCommonPrefix];
1491 char c2 = lc.completionStrings[j + 1][longestCommonPrefix];
1492 if ((0 == c1) || (0 == c2) || (c1 != c2)) {
1493 keepGoing = false;
1494 break;
1495 }
1496 }
1497 if (keepGoing) {
1498 ++longestCommonPrefix;
1499 }
1500 }
1501 }
1502 if (lc.completionStrings.size() != 1) { // beep if ambiguous
1503 beep();
1504 }
1505
1506 // if we can extend the item, extend it and return to main loop
1507 if (longestCommonPrefix > itemLength) {
1508 displayLength = len + longestCommonPrefix - itemLength;
1509 if (displayLength > buflen) {
1510 longestCommonPrefix -= displayLength - buflen; // don't overflow buffer
1511 displayLength = buflen; // truncate the insertion
1512 beep(); // and make a noise
1513 }
1514 Utf32String displayText(displayLength + 1);
1515 memcpy(displayText.get(), buf32, sizeof(UChar32) * startIndex);
1516 memcpy(&displayText[startIndex],
1517 &lc.completionStrings[0][0],
1518 sizeof(UChar32) * longestCommonPrefix);
1519 int tailIndex = startIndex + longestCommonPrefix;
1520 memcpy(&displayText[tailIndex],
1521 &buf32[pos],
1522 sizeof(UChar32) * (displayLength - tailIndex + 1));
1523 copyString32(buf32, displayText.get(), buflen + 1);
1524 pos = startIndex + longestCommonPrefix;
1525 len = displayLength;
1526 refreshLine(pi);
1527 return 0;
1528 }
1529
1530 // we can't complete any further, wait for second tab
1531 do {
1532 c = linenoiseReadChar();
1533 c = cleanupCtrl(c);
1534 } while (c == static_cast<char>(-1));
1535
1536 // if any character other than tab, pass it to the main loop
1537 if (c != ctrlChar('I')) {
1538 freeCompletions(&lc);
1539 return c;
1540 }
1541
1542 // we got a second tab, maybe show list of possible completions
1543 bool showCompletions = true;
1544 bool onNewLine = false;
1545 if (lc.completionStrings.size() > completionCountCutoff) {
1546 int savePos = pos; // move cursor to EOL to avoid overwriting the command line
1547 pos = len;
1548 refreshLine(pi);
1549 pos = savePos;
1550 printf("\nDisplay all %u possibilities? (y or n)",
1551 static_cast<unsigned int>(lc.completionStrings.size()));
1552 fflush(stdout);
1553 onNewLine = true;
1554 while (c != 'y' && c != 'Y' && c != 'n' && c != 'N' && c != ctrlChar('C')) {
1555 do {
1556 c = linenoiseReadChar();
1557 c = cleanupCtrl(c);
1558 } while (c == static_cast<char>(-1));
1559 }
1560 switch (c) {
1561 case 'n':
1562 case 'N':
1563 showCompletions = false;
1564 freeCompletions(&lc);
1565 break;
1566 case ctrlChar('C'):
1567 showCompletions = false;
1568 freeCompletions(&lc);
1569 if (write(1, "^C", 2) == -1)
1570 return -1; // Display the ^C we got
1571 c = 0;
1572 break;
1573 }
1574 }
1575
1576 // if showing the list, do it the way readline does it
1577 bool stopList = false;
1578 if (showCompletions) {
1579 int longestCompletion = 0;
1580 for (size_t j = 0; j < lc.completionStrings.size(); ++j) {
1581 itemLength = lc.completionStrings[j].length();
1582 if (itemLength > longestCompletion) {
1583 longestCompletion = itemLength;
1584 }
1585 }
1586 longestCompletion += 2;
1587 int columnCount = pi.promptScreenColumns / longestCompletion;
1588 if (columnCount < 1) {
1589 columnCount = 1;
1590 }
1591 if (!onNewLine) { // skip this if we showed "Display all %d possibilities?"
1592 int savePos = pos; // move cursor to EOL to avoid overwriting the command line
1593 pos = len;
1594 refreshLine(pi);
1595 pos = savePos;
1596 }
1597 size_t pauseRow = getScreenRows() - 1;
1598 size_t rowCount = (lc.completionStrings.size() + columnCount - 1) / columnCount;
1599 for (size_t row = 0; row < rowCount; ++row) {
1600 if (row == pauseRow) {
1601 printf("\n--More--");
1602 fflush(stdout);
1603 c = 0;
1604 bool doBeep = false;
1605 while (c != ' ' && c != '\r' && c != '\n' && c != 'y' && c != 'Y' && c != 'n' &&
1606 c != 'N' && c != 'q' && c != 'Q' && c != ctrlChar('C')) {
1607 if (doBeep) {
1608 beep();
1609 }
1610 doBeep = true;
1611 do {
1612 c = linenoiseReadChar();
1613 c = cleanupCtrl(c);
1614 } while (c == static_cast<char>(-1));
1615 }
1616 switch (c) {
1617 case ' ':
1618 case 'y':
1619 case 'Y':
1620 printf("\r \r");
1621 pauseRow += getScreenRows() - 1;
1622 break;
1623 case '\r':
1624 case '\n':
1625 printf("\r \r");
1626 ++pauseRow;
1627 break;
1628 case 'n':
1629 case 'N':
1630 case 'q':
1631 case 'Q':
1632 printf("\r \r");
1633 stopList = true;
1634 break;
1635 case ctrlChar('C'):
1636 if (write(1, "^C", 2) == -1)
1637 return -1; // Display the ^C we got
1638 stopList = true;
1639 break;
1640 }
1641 } else {
1642 printf("\n");
1643 }
1644 if (stopList) {
1645 break;
1646 }
1647 for (int column = 0; column < columnCount; ++column) {
1648 size_t index = (column * rowCount) + row;
1649 if (index < lc.completionStrings.size()) {
1650 itemLength = lc.completionStrings[index].length();
1651 fflush(stdout);
1652 if (write32(1, lc.completionStrings[index].get(), itemLength) == -1)
1653 return -1;
1654 if (((column + 1) * rowCount) + row < lc.completionStrings.size()) {
1655 for (int k = itemLength; k < longestCompletion; ++k) {
1656 printf(" ");
1657 }
1658 }
1659 }
1660 }
1661 }
1662 fflush(stdout);
1663 freeCompletions(&lc);
1664 }
1665
1666 // display the prompt on a new line, then redisplay the input buffer
1667 if (!stopList || c == ctrlChar('C')) {
1668 if (write(1, "\n", 1) == -1)
1669 return 0;
1670 }
1671 if (write32(1, pi.promptText.get(), pi.promptChars) == -1)
1672 return 0;
1673 #ifndef _WIN32
1674 // we have to generate our own newline on line wrap on Linux
1675 if (pi.promptIndentation == 0 && pi.promptExtraLines > 0)
1676 if (write(1, "\n", 1) == -1)
1677 return 0;
1678 #endif
1679 pi.promptCursorRowOffset = pi.promptExtraLines;
1680 refreshLine(pi);
1681 return 0;
1682 }
1683
1684 /**
1685 * Clear the screen ONLY (no redisplay of anything)
1686 */
linenoiseClearScreen(void)1687 void linenoiseClearScreen(void) {
1688 #ifdef _WIN32
1689 COORD coord = {0, 0};
1690 CONSOLE_SCREEN_BUFFER_INFO inf;
1691 HANDLE screenHandle = GetStdHandle(STD_OUTPUT_HANDLE);
1692 GetConsoleScreenBufferInfo(screenHandle, &inf);
1693 SetConsoleCursorPosition(screenHandle, coord);
1694 DWORD count;
1695 FillConsoleOutputCharacterA(screenHandle, ' ', inf.dwSize.X * inf.dwSize.Y, coord, &count);
1696 #else
1697 if (write(1, "\x1b[H\x1b[2J", 7) <= 0)
1698 return;
1699 #endif
1700 }
1701
clearScreen(PromptBase & pi)1702 void InputBuffer::clearScreen(PromptBase& pi) {
1703 linenoiseClearScreen();
1704 if (write32(1, pi.promptText.get(), pi.promptChars) == -1)
1705 return;
1706 #ifndef _WIN32
1707 // we have to generate our own newline on line wrap on Linux
1708 if (pi.promptIndentation == 0 && pi.promptExtraLines > 0)
1709 if (write(1, "\n", 1) == -1)
1710 return;
1711 #endif
1712 pi.promptCursorRowOffset = pi.promptExtraLines;
1713 refreshLine(pi);
1714 }
1715
1716 /**
1717 * Incremental history search -- take over the prompt and keyboard as the user types a search
1718 * string, deletes characters from it, changes direction, and either accepts the found line (for
1719 * execution orediting) or cancels.
1720 * @param pi PromptBase struct holding information about the (old, static) prompt and our
1721 * screen position
1722 * @param startChar the character that began the search, used to set the initial direction
1723 */
incrementalHistorySearch(PromptBase & pi,int startChar)1724 int InputBuffer::incrementalHistorySearch(PromptBase& pi, int startChar) {
1725 size_t bufferSize;
1726 size_t ucharCount;
1727 int errorCode;
1728
1729 // if not already recalling, add the current line to the history list so we don't have to
1730 // special case it
1731 if (historyIndex == historyLen - 1) {
1732 free(history[historyLen - 1]);
1733 bufferSize = sizeof(UChar32) * len + 1;
1734 unique_ptr<UChar8[]> tempBuffer(new UChar8[bufferSize]);
1735 copyString32to8(tempBuffer.get(), buf32, bufferSize);
1736 history[historyLen - 1] =
1737 reinterpret_cast<UChar8*>(strdup(reinterpret_cast<const char*>(tempBuffer.get())));
1738 }
1739 int historyLineLength = len;
1740 int historyLinePosition = pos;
1741 UChar32 emptyBuffer[1];
1742 char emptyWidths[1];
1743 InputBuffer empty(emptyBuffer, emptyWidths, 1);
1744 empty.refreshLine(pi); // erase the old input first
1745 DynamicPrompt dp(pi, (startChar == ctrlChar('R')) ? -1 : 1);
1746
1747 dp.promptPreviousLen = pi.promptPreviousLen;
1748 dp.promptPreviousInputLen = pi.promptPreviousInputLen;
1749 dynamicRefresh(
1750 dp, buf32, historyLineLength, historyLinePosition); // draw user's text with our prompt
1751
1752 // loop until we get an exit character
1753 int c;
1754 bool keepLooping = true;
1755 bool useSearchedLine = true;
1756 bool searchAgain = false;
1757 UChar32* activeHistoryLine = 0;
1758 while (keepLooping) {
1759 c = linenoiseReadChar();
1760 c = cleanupCtrl(c); // convert CTRL + <char> into normal ctrl
1761
1762 switch (c) {
1763 // these characters keep the selected text but do not execute it
1764 case ctrlChar('A'): // ctrl-A, move cursor to start of line
1765 case HOME_KEY:
1766 case ctrlChar('B'): // ctrl-B, move cursor left by one character
1767 case LEFT_ARROW_KEY:
1768 case META + 'b': // meta-B, move cursor left by one word
1769 case META + 'B':
1770 case CTRL + LEFT_ARROW_KEY:
1771 case META + LEFT_ARROW_KEY: // Emacs allows Meta, bash & readline don't
1772 case ctrlChar('D'):
1773 case META + 'd': // meta-D, kill word to right of cursor
1774 case META + 'D':
1775 case ctrlChar('E'): // ctrl-E, move cursor to end of line
1776 case END_KEY:
1777 case ctrlChar('F'): // ctrl-F, move cursor right by one character
1778 case RIGHT_ARROW_KEY:
1779 case META + 'f': // meta-F, move cursor right by one word
1780 case META + 'F':
1781 case CTRL + RIGHT_ARROW_KEY:
1782 case META + RIGHT_ARROW_KEY: // Emacs allows Meta, bash & readline don't
1783 case META + ctrlChar('H'):
1784 case ctrlChar('J'):
1785 case ctrlChar('K'): // ctrl-K, kill from cursor to end of line
1786 case ctrlChar('M'):
1787 case ctrlChar('N'): // ctrl-N, recall next line in history
1788 case ctrlChar('P'): // ctrl-P, recall previous line in history
1789 case DOWN_ARROW_KEY:
1790 case UP_ARROW_KEY:
1791 case ctrlChar('T'): // ctrl-T, transpose characters
1792 case ctrlChar('U'): // ctrl-U, kill all characters to the left of the cursor
1793 case ctrlChar('W'):
1794 case META + 'y': // meta-Y, "yank-pop", rotate popped text
1795 case META + 'Y':
1796 case 127:
1797 case DELETE_KEY:
1798 case META + '<': // start of history
1799 case PAGE_UP_KEY:
1800 case META + '>': // end of history
1801 case PAGE_DOWN_KEY:
1802 keepLooping = false;
1803 break;
1804
1805 // these characters revert the input line to its previous state
1806 case ctrlChar('C'): // ctrl-C, abort this line
1807 case ctrlChar('G'):
1808 case ctrlChar('L'): // ctrl-L, clear screen and redisplay line
1809 keepLooping = false;
1810 useSearchedLine = false;
1811 if (c != ctrlChar('L')) {
1812 c = -1; // ctrl-C and ctrl-G just abort the search and do nothing else
1813 }
1814 break;
1815
1816 // these characters stay in search mode and update the display
1817 case ctrlChar('S'):
1818 case ctrlChar('R'):
1819 if (dp.searchTextLen == 0) { // if no current search text, recall previous text
1820 if (previousSearchText.length()) {
1821 dp.updateSearchText(previousSearchText.get());
1822 }
1823 }
1824 if ((dp.direction == 1 && c == ctrlChar('R')) ||
1825 (dp.direction == -1 && c == ctrlChar('S'))) {
1826 dp.direction = 0 - dp.direction; // reverse direction
1827 dp.updateSearchPrompt(); // change the prompt
1828 } else {
1829 searchAgain = true; // same direction, search again
1830 }
1831 break;
1832
1833 // job control is its own thing
1834 #ifndef _WIN32
1835 case ctrlChar('Z'): // ctrl-Z, job control
1836 disableRawMode(); // Returning to Linux (whatever) shell, leave raw mode
1837 raise(SIGSTOP); // Break out in mid-line
1838 enableRawMode(); // Back from Linux shell, re-enter raw mode
1839 {
1840 bufferSize = historyLineLength + 1;
1841 unique_ptr<UChar32[]> tempUnicode(new UChar32[bufferSize]);
1842 copyString8to32(tempUnicode.get(),
1843 history[historyIndex],
1844 bufferSize,
1845 ucharCount,
1846 errorCode);
1847 dynamicRefresh(dp, tempUnicode.get(), historyLineLength, historyLinePosition);
1848 }
1849 continue;
1850 break;
1851 #endif
1852
1853 // these characters update the search string, and hence the selected input line
1854 case ctrlChar('H'): // backspace/ctrl-H, delete char to left of cursor
1855 if (dp.searchTextLen > 0) {
1856 unique_ptr<UChar32[]> tempUnicode(new UChar32[dp.searchTextLen]);
1857 --dp.searchTextLen;
1858 dp.searchText[dp.searchTextLen] = 0;
1859 copyString32(tempUnicode.get(), dp.searchText.get(), dp.searchTextLen + 1);
1860 dp.updateSearchText(tempUnicode.get());
1861 } else {
1862 beep();
1863 }
1864 break;
1865
1866 case ctrlChar('Y'): // ctrl-Y, yank killed text
1867 break;
1868
1869 default:
1870 if (!isControlChar(c) && c <= 0x0010FFFF) { // not an action character
1871 unique_ptr<UChar32[]> tempUnicode(new UChar32[dp.searchTextLen + 2]);
1872 copyString32(tempUnicode.get(), dp.searchText.get(), dp.searchTextLen + 2);
1873 tempUnicode[dp.searchTextLen] = c;
1874 tempUnicode[dp.searchTextLen + 1] = 0;
1875 dp.updateSearchText(tempUnicode.get());
1876 } else {
1877 beep();
1878 }
1879 } // switch
1880
1881 // if we are staying in search mode, search now
1882 if (keepLooping) {
1883 bufferSize = historyLineLength + 1;
1884 activeHistoryLine = new UChar32[bufferSize];
1885 copyString8to32(
1886 activeHistoryLine, history[historyIndex], bufferSize, ucharCount, errorCode);
1887 if (dp.searchTextLen > 0) {
1888 bool found = false;
1889 int historySearchIndex = historyIndex;
1890 int lineLength = ucharCount;
1891 int lineSearchPos = historyLinePosition;
1892 if (searchAgain) {
1893 lineSearchPos += dp.direction;
1894 }
1895 searchAgain = false;
1896 while (true) {
1897 while ((dp.direction > 0) ? (lineSearchPos < lineLength)
1898 : (lineSearchPos >= 0)) {
1899 if (strncmp32(dp.searchText.get(),
1900 &activeHistoryLine[lineSearchPos],
1901 dp.searchTextLen) == 0) {
1902 found = true;
1903 break;
1904 }
1905 lineSearchPos += dp.direction;
1906 }
1907 if (found) {
1908 historyIndex = historySearchIndex;
1909 historyLineLength = lineLength;
1910 historyLinePosition = lineSearchPos;
1911 break;
1912 } else if ((dp.direction > 0) ? (historySearchIndex < historyLen - 1)
1913 : (historySearchIndex > 0)) {
1914 historySearchIndex += dp.direction;
1915 bufferSize =
1916 strlen(reinterpret_cast<char*>(history[historySearchIndex])) + 1;
1917 delete[] activeHistoryLine;
1918 activeHistoryLine = new UChar32[bufferSize];
1919 copyString8to32(activeHistoryLine,
1920 history[historySearchIndex],
1921 bufferSize,
1922 ucharCount,
1923 errorCode);
1924 lineLength = ucharCount;
1925 lineSearchPos = (dp.direction > 0) ? 0 : (lineLength - dp.searchTextLen);
1926 } else {
1927 beep();
1928 break;
1929 }
1930 }; // while
1931 }
1932 if (activeHistoryLine) {
1933 delete[] activeHistoryLine;
1934 }
1935 bufferSize = historyLineLength + 1;
1936 activeHistoryLine = new UChar32[bufferSize];
1937 copyString8to32(
1938 activeHistoryLine, history[historyIndex], bufferSize, ucharCount, errorCode);
1939 dynamicRefresh(dp,
1940 activeHistoryLine,
1941 historyLineLength,
1942 historyLinePosition); // draw user's text with our prompt
1943 }
1944 } // while
1945
1946 // leaving history search, restore previous prompt, maybe make searched line current
1947 PromptBase pb;
1948 pb.promptChars = pi.promptIndentation;
1949 Utf32String tempUnicode(pb.promptChars + 1);
1950 copyString32(tempUnicode.get(), &pi.promptText[pi.promptLastLinePosition], pb.promptChars + 1);
1951 tempUnicode.initFromBuffer();
1952 pb.promptText = tempUnicode;
1953 pb.promptExtraLines = 0;
1954 pb.promptIndentation = pi.promptIndentation;
1955 pb.promptLastLinePosition = 0;
1956 pb.promptPreviousInputLen = historyLineLength;
1957 pb.promptCursorRowOffset = dp.promptCursorRowOffset;
1958 pb.promptScreenColumns = pi.promptScreenColumns;
1959 pb.promptPreviousLen = dp.promptChars;
1960 if (useSearchedLine && activeHistoryLine) {
1961 historyRecallMostRecent = true;
1962 copyString32(buf32, activeHistoryLine, buflen + 1);
1963 len = historyLineLength;
1964 pos = historyLinePosition;
1965 }
1966 if (activeHistoryLine) {
1967 delete[] activeHistoryLine;
1968 }
1969 dynamicRefresh(pb, buf32, len, pos); // redraw the original prompt with current input
1970 pi.promptPreviousInputLen = len;
1971 pi.promptCursorRowOffset = pi.promptExtraLines + pb.promptCursorRowOffset;
1972 previousSearchText = dp.searchText; // save search text for possible reuse on ctrl-R ctrl-R
1973 return c; // pass a character or -1 back to main loop
1974 }
1975
isCharacterAlphanumeric(UChar32 testChar)1976 static bool isCharacterAlphanumeric(UChar32 testChar) {
1977 return iswalnum(testChar);
1978 }
1979
getInputLine(PromptBase & pi)1980 int InputBuffer::getInputLine(PromptBase& pi) {
1981 // The latest history entry is always our current buffer
1982 if (len > 0) {
1983 size_t bufferSize = sizeof(UChar32) * len + 1;
1984 unique_ptr<char[]> tempBuffer(new char[bufferSize]);
1985 copyString32to8(reinterpret_cast<UChar8*>(tempBuffer.get()), buf32, bufferSize);
1986 linenoiseHistoryAdd(tempBuffer.get());
1987 } else {
1988 linenoiseHistoryAdd("");
1989 }
1990 historyIndex = historyLen - 1;
1991 historyRecallMostRecent = false;
1992
1993 // display the prompt
1994 if (write32(1, pi.promptText.get(), pi.promptChars) == -1)
1995 return -1;
1996
1997 #ifndef _WIN32
1998 // we have to generate our own newline on line wrap on Linux
1999 if (pi.promptIndentation == 0 && pi.promptExtraLines > 0)
2000 if (write(1, "\n", 1) == -1)
2001 return -1;
2002 #endif
2003
2004 // the cursor starts out at the end of the prompt
2005 pi.promptCursorRowOffset = pi.promptExtraLines;
2006
2007 // kill and yank start in "other" mode
2008 killRing.lastAction = KillRing::actionOther;
2009
2010 // when history search returns control to us, we execute its terminating keystroke
2011 int terminatingKeystroke = -1;
2012
2013 // if there is already text in the buffer, display it first
2014 if (len > 0) {
2015 refreshLine(pi);
2016 }
2017
2018 // loop collecting characters, respond to line editing characters
2019 while (true) {
2020 int c;
2021 if (terminatingKeystroke == -1) {
2022 c = linenoiseReadChar(); // get a new keystroke
2023 } else {
2024 c = terminatingKeystroke; // use the terminating keystroke from search
2025 terminatingKeystroke = -1; // clear it once we've used it
2026 }
2027 c = cleanupCtrl(c); // convert CTRL + <char> into normal ctrl
2028
2029 if (c == 0) {
2030 return len;
2031 }
2032
2033 if (c == -1) {
2034 refreshLine(pi);
2035 continue;
2036 }
2037
2038 if (c == -2) {
2039 if (write32(1, pi.promptText.get(), pi.promptChars) == -1)
2040 return -1;
2041 refreshLine(pi);
2042 continue;
2043 }
2044
2045 // ctrl-I/tab, command completion, needs to be before switch statement
2046 if (c == ctrlChar('I') && completionCallback) {
2047 if (pos == 0) // SERVER-4967 -- in earlier versions, you could paste previous output
2048 continue; // back into the shell ... this output may have leading tabs.
2049 // This hack (i.e. what the old code did) prevents command completion
2050 // on an empty line but lets users paste text with leading tabs.
2051
2052 killRing.lastAction = KillRing::actionOther;
2053 historyRecallMostRecent = false;
2054
2055 // completeLine does the actual completion and replacement
2056 c = completeLine(pi);
2057
2058 if (c < 0) // return on error
2059 return len;
2060
2061 if (c == 0) // read next character when 0
2062 continue;
2063
2064 // deliberate fall-through here, so we use the terminating character
2065 }
2066
2067 switch (c) {
2068 case ctrlChar('A'): // ctrl-A, move cursor to start of line
2069 case HOME_KEY:
2070 killRing.lastAction = KillRing::actionOther;
2071 pos = 0;
2072 refreshLine(pi);
2073 break;
2074
2075 case ctrlChar('B'): // ctrl-B, move cursor left by one character
2076 case LEFT_ARROW_KEY:
2077 killRing.lastAction = KillRing::actionOther;
2078 if (pos > 0) {
2079 --pos;
2080 refreshLine(pi);
2081 }
2082 break;
2083
2084 case META + 'b': // meta-B, move cursor left by one word
2085 case META + 'B':
2086 case CTRL + LEFT_ARROW_KEY:
2087 case META + LEFT_ARROW_KEY: // Emacs allows Meta, bash & readline don't
2088 killRing.lastAction = KillRing::actionOther;
2089 if (pos > 0) {
2090 while (pos > 0 && !isCharacterAlphanumeric(buf32[pos - 1])) {
2091 --pos;
2092 }
2093 while (pos > 0 && isCharacterAlphanumeric(buf32[pos - 1])) {
2094 --pos;
2095 }
2096 refreshLine(pi);
2097 }
2098 break;
2099
2100 case ctrlChar('C'): // ctrl-C, abort this line
2101 killRing.lastAction = KillRing::actionOther;
2102 historyRecallMostRecent = false;
2103 errno = EAGAIN;
2104 --historyLen;
2105 free(history[historyLen]);
2106 // we need one last refresh with the cursor at the end of the line
2107 // so we don't display the next prompt over the previous input line
2108 pos = len; // pass len as pos for EOL
2109 refreshLine(pi);
2110 if (write(1, "^C", 2) == -1)
2111 return -1; // Display the ^C we got
2112 return -1;
2113
2114 case META + 'c': // meta-C, give word initial Cap
2115 case META + 'C':
2116 killRing.lastAction = KillRing::actionOther;
2117 historyRecallMostRecent = false;
2118 if (pos < len) {
2119 while (pos < len && !isCharacterAlphanumeric(buf32[pos])) {
2120 ++pos;
2121 }
2122 if (pos < len && isCharacterAlphanumeric(buf32[pos])) {
2123 if (buf32[pos] >= 'a' && buf32[pos] <= 'z') {
2124 buf32[pos] += 'A' - 'a';
2125 }
2126 ++pos;
2127 }
2128 while (pos < len && isCharacterAlphanumeric(buf32[pos])) {
2129 if (buf32[pos] >= 'A' && buf32[pos] <= 'Z') {
2130 buf32[pos] += 'a' - 'A';
2131 }
2132 ++pos;
2133 }
2134 refreshLine(pi);
2135 }
2136 break;
2137
2138 // ctrl-D, delete the character under the cursor
2139 // on an empty line, exit the shell
2140 case ctrlChar('D'):
2141 killRing.lastAction = KillRing::actionOther;
2142 if (len > 0 && pos < len) {
2143 historyRecallMostRecent = false;
2144 memmove(buf32 + pos, buf32 + pos + 1, sizeof(UChar32) * (len - pos));
2145 --len;
2146 refreshLine(pi);
2147 } else if (len == 0) {
2148 --historyLen;
2149 free(history[historyLen]);
2150 return -1;
2151 }
2152 break;
2153
2154 case META + 'd': // meta-D, kill word to right of cursor
2155 case META + 'D':
2156 if (pos < len) {
2157 historyRecallMostRecent = false;
2158 int endingPos = pos;
2159 while (endingPos < len && !isCharacterAlphanumeric(buf32[endingPos])) {
2160 ++endingPos;
2161 }
2162 while (endingPos < len && isCharacterAlphanumeric(buf32[endingPos])) {
2163 ++endingPos;
2164 }
2165 killRing.kill(&buf32[pos], endingPos - pos, true);
2166 memmove(
2167 buf32 + pos, buf32 + endingPos, sizeof(UChar32) * (len - endingPos + 1));
2168 len -= endingPos - pos;
2169 refreshLine(pi);
2170 }
2171 killRing.lastAction = KillRing::actionKill;
2172 break;
2173
2174 case ctrlChar('E'): // ctrl-E, move cursor to end of line
2175 case END_KEY:
2176 killRing.lastAction = KillRing::actionOther;
2177 pos = len;
2178 refreshLine(pi);
2179 break;
2180
2181 case ctrlChar('F'): // ctrl-F, move cursor right by one character
2182 case RIGHT_ARROW_KEY:
2183 killRing.lastAction = KillRing::actionOther;
2184 if (pos < len) {
2185 ++pos;
2186 refreshLine(pi);
2187 }
2188 break;
2189
2190 case META + 'f': // meta-F, move cursor right by one word
2191 case META + 'F':
2192 case CTRL + RIGHT_ARROW_KEY:
2193 case META + RIGHT_ARROW_KEY: // Emacs allows Meta, bash & readline don't
2194 killRing.lastAction = KillRing::actionOther;
2195 if (pos < len) {
2196 while (pos < len && !isCharacterAlphanumeric(buf32[pos])) {
2197 ++pos;
2198 }
2199 while (pos < len && isCharacterAlphanumeric(buf32[pos])) {
2200 ++pos;
2201 }
2202 refreshLine(pi);
2203 }
2204 break;
2205
2206 case ctrlChar('H'): // backspace/ctrl-H, delete char to left of cursor
2207 killRing.lastAction = KillRing::actionOther;
2208 if (pos > 0) {
2209 historyRecallMostRecent = false;
2210 memmove(buf32 + pos - 1, buf32 + pos, sizeof(UChar32) * (1 + len - pos));
2211 --pos;
2212 --len;
2213 refreshLine(pi);
2214 }
2215 break;
2216
2217 // meta-Backspace, kill word to left of cursor
2218 case META + ctrlChar('H'):
2219 if (pos > 0) {
2220 historyRecallMostRecent = false;
2221 int startingPos = pos;
2222 while (pos > 0 && !isCharacterAlphanumeric(buf32[pos - 1])) {
2223 --pos;
2224 }
2225 while (pos > 0 && isCharacterAlphanumeric(buf32[pos - 1])) {
2226 --pos;
2227 }
2228 killRing.kill(&buf32[pos], startingPos - pos, false);
2229 memmove(buf32 + pos,
2230 buf32 + startingPos,
2231 sizeof(UChar32) * (len - startingPos + 1));
2232 len -= startingPos - pos;
2233 refreshLine(pi);
2234 }
2235 killRing.lastAction = KillRing::actionKill;
2236 break;
2237
2238 case ctrlChar('J'): // ctrl-J/linefeed/newline, accept line
2239 case ctrlChar('M'): // ctrl-M/return/enter
2240 killRing.lastAction = KillRing::actionOther;
2241 // we need one last refresh with the cursor at the end of the line
2242 // so we don't display the next prompt over the previous input line
2243 pos = len; // pass len as pos for EOL
2244 refreshLine(pi);
2245 historyPreviousIndex = historyRecallMostRecent ? historyIndex : -2;
2246 --historyLen;
2247 free(history[historyLen]);
2248 return len;
2249
2250 case ctrlChar('K'): // ctrl-K, kill from cursor to end of line
2251 killRing.kill(&buf32[pos], len - pos, true);
2252 buf32[pos] = '\0';
2253 len = pos;
2254 refreshLine(pi);
2255 killRing.lastAction = KillRing::actionKill;
2256 historyRecallMostRecent = false;
2257 break;
2258
2259 case ctrlChar('L'): // ctrl-L, clear screen and redisplay line
2260 clearScreen(pi);
2261 break;
2262
2263 case META + 'l': // meta-L, lowercase word
2264 case META + 'L':
2265 killRing.lastAction = KillRing::actionOther;
2266 if (pos < len) {
2267 historyRecallMostRecent = false;
2268 while (pos < len && !isCharacterAlphanumeric(buf32[pos])) {
2269 ++pos;
2270 }
2271 while (pos < len && isCharacterAlphanumeric(buf32[pos])) {
2272 if (buf32[pos] >= 'A' && buf32[pos] <= 'Z') {
2273 buf32[pos] += 'a' - 'A';
2274 }
2275 ++pos;
2276 }
2277 refreshLine(pi);
2278 }
2279 break;
2280
2281 case ctrlChar('N'): // ctrl-N, recall next line in history
2282 case ctrlChar('P'): // ctrl-P, recall previous line in history
2283 case DOWN_ARROW_KEY:
2284 case UP_ARROW_KEY:
2285 killRing.lastAction = KillRing::actionOther;
2286 // if not already recalling, add the current line to the history list so we don't
2287 // have to special case it
2288 if (historyIndex == historyLen - 1) {
2289 free(history[historyLen - 1]);
2290 size_t tempBufferSize = sizeof(UChar32) * len + 1;
2291 unique_ptr<UChar8[]> tempBuffer(new UChar8[tempBufferSize]);
2292 copyString32to8(tempBuffer.get(), buf32, tempBufferSize);
2293 history[historyLen - 1] = reinterpret_cast<UChar8*>(
2294 strdup(reinterpret_cast<const char*>(tempBuffer.get())));
2295 }
2296 if (historyLen > 1) {
2297 if (c == UP_ARROW_KEY) {
2298 c = ctrlChar('P');
2299 }
2300 if (historyPreviousIndex != -2 && c != ctrlChar('P')) {
2301 historyIndex = 1 + historyPreviousIndex; // emulate Windows down-arrow
2302 } else {
2303 historyIndex += (c == ctrlChar('P')) ? -1 : 1;
2304 }
2305 historyPreviousIndex = -2;
2306 if (historyIndex < 0) {
2307 historyIndex = 0;
2308 break;
2309 } else if (historyIndex >= historyLen) {
2310 historyIndex = historyLen - 1;
2311 break;
2312 }
2313 historyRecallMostRecent = true;
2314 size_t ucharCount;
2315 int errorCode;
2316 copyString8to32(buf32, history[historyIndex], buflen, ucharCount, errorCode);
2317 len = pos = ucharCount;
2318 refreshLine(pi);
2319 }
2320 break;
2321
2322 case ctrlChar('R'): // ctrl-R, reverse history search
2323 case ctrlChar('S'): // ctrl-S, forward history search
2324 terminatingKeystroke = incrementalHistorySearch(pi, c);
2325 break;
2326
2327 case ctrlChar('T'): // ctrl-T, transpose characters
2328 killRing.lastAction = KillRing::actionOther;
2329 if (pos > 0 && len > 1) {
2330 historyRecallMostRecent = false;
2331 size_t leftCharPos = (pos == len) ? pos - 2 : pos - 1;
2332 char aux = buf32[leftCharPos];
2333 buf32[leftCharPos] = buf32[leftCharPos + 1];
2334 buf32[leftCharPos + 1] = aux;
2335 if (pos != len)
2336 ++pos;
2337 refreshLine(pi);
2338 }
2339 break;
2340
2341 case ctrlChar('U'): // ctrl-U, kill all characters to the left of the cursor
2342 if (pos > 0) {
2343 historyRecallMostRecent = false;
2344 killRing.kill(&buf32[0], pos, false);
2345 len -= pos;
2346 memmove(buf32, buf32 + pos, sizeof(UChar32) * (len + 1));
2347 pos = 0;
2348 refreshLine(pi);
2349 }
2350 killRing.lastAction = KillRing::actionKill;
2351 break;
2352
2353 case META + 'u': // meta-U, uppercase word
2354 case META + 'U':
2355 killRing.lastAction = KillRing::actionOther;
2356 if (pos < len) {
2357 historyRecallMostRecent = false;
2358 while (pos < len && !isCharacterAlphanumeric(buf32[pos])) {
2359 ++pos;
2360 }
2361 while (pos < len && isCharacterAlphanumeric(buf32[pos])) {
2362 if (buf32[pos] >= 'a' && buf32[pos] <= 'z') {
2363 buf32[pos] += 'A' - 'a';
2364 }
2365 ++pos;
2366 }
2367 refreshLine(pi);
2368 }
2369 break;
2370
2371 // ctrl-W, kill to whitespace (not word) to left of cursor
2372 case ctrlChar('W'):
2373 if (pos > 0) {
2374 historyRecallMostRecent = false;
2375 int startingPos = pos;
2376 while (pos > 0 && buf32[pos - 1] == ' ') {
2377 --pos;
2378 }
2379 while (pos > 0 && buf32[pos - 1] != ' ') {
2380 --pos;
2381 }
2382 killRing.kill(&buf32[pos], startingPos - pos, false);
2383 memmove(buf32 + pos,
2384 buf32 + startingPos,
2385 sizeof(UChar32) * (len - startingPos + 1));
2386 len -= startingPos - pos;
2387 refreshLine(pi);
2388 }
2389 killRing.lastAction = KillRing::actionKill;
2390 break;
2391
2392 case ctrlChar('Y'): // ctrl-Y, yank killed text
2393 historyRecallMostRecent = false;
2394 {
2395 Utf32String* restoredText = killRing.yank();
2396 if (restoredText) {
2397 bool truncated = false;
2398 size_t ucharCount = restoredText->length();
2399 if (ucharCount > static_cast<size_t>(buflen - len)) {
2400 ucharCount = buflen - len;
2401 truncated = true;
2402 }
2403 memmove(buf32 + pos + ucharCount,
2404 buf32 + pos,
2405 sizeof(UChar32) * (len - pos + 1));
2406 memmove(buf32 + pos, restoredText->get(), sizeof(UChar32) * ucharCount);
2407 pos += ucharCount;
2408 len += ucharCount;
2409 refreshLine(pi);
2410 killRing.lastAction = KillRing::actionYank;
2411 killRing.lastYankSize = ucharCount;
2412 if (truncated) {
2413 beep();
2414 }
2415 } else {
2416 beep();
2417 }
2418 }
2419 break;
2420
2421 case META + 'y': // meta-Y, "yank-pop", rotate popped text
2422 case META + 'Y':
2423 if (killRing.lastAction == KillRing::actionYank) {
2424 historyRecallMostRecent = false;
2425 Utf32String* restoredText = killRing.yankPop();
2426 if (restoredText) {
2427 bool truncated = false;
2428 size_t ucharCount = restoredText->length();
2429 if (ucharCount >
2430 static_cast<size_t>(killRing.lastYankSize + buflen - len)) {
2431 ucharCount = killRing.lastYankSize + buflen - len;
2432 truncated = true;
2433 }
2434 if (ucharCount > killRing.lastYankSize) {
2435 memmove(buf32 + pos + ucharCount - killRing.lastYankSize,
2436 buf32 + pos,
2437 sizeof(UChar32) * (len - pos + 1));
2438 memmove(buf32 + pos - killRing.lastYankSize,
2439 restoredText->get(),
2440 sizeof(UChar32) * ucharCount);
2441 } else {
2442 memmove(buf32 + pos - killRing.lastYankSize,
2443 restoredText->get(),
2444 sizeof(UChar32) * ucharCount);
2445 memmove(buf32 + pos + ucharCount - killRing.lastYankSize,
2446 buf32 + pos,
2447 sizeof(UChar32) * (len - pos + 1));
2448 }
2449 pos += ucharCount - killRing.lastYankSize;
2450 len += ucharCount - killRing.lastYankSize;
2451 killRing.lastYankSize = ucharCount;
2452 refreshLine(pi);
2453 if (truncated) {
2454 beep();
2455 }
2456 break;
2457 }
2458 }
2459 beep();
2460 break;
2461
2462 #ifndef _WIN32
2463 case ctrlChar('Z'): // ctrl-Z, job control
2464 disableRawMode(); // Returning to Linux (whatever) shell, leave raw mode
2465 raise(SIGSTOP); // Break out in mid-line
2466 enableRawMode(); // Back from Linux shell, re-enter raw mode
2467 if (write32(1, pi.promptText.get(), pi.promptChars) == -1)
2468 break; // Redraw prompt
2469 refreshLine(pi); // Refresh the line
2470 break;
2471 #endif
2472
2473 // DEL, delete the character under the cursor
2474 case 127:
2475 case DELETE_KEY:
2476 killRing.lastAction = KillRing::actionOther;
2477 if (len > 0 && pos < len) {
2478 historyRecallMostRecent = false;
2479 memmove(buf32 + pos, buf32 + pos + 1, sizeof(UChar32) * (len - pos));
2480 --len;
2481 refreshLine(pi);
2482 }
2483 break;
2484
2485 case META + '<': // meta-<, beginning of history
2486 case PAGE_UP_KEY: // Page Up, beginning of history
2487 case META + '>': // meta->, end of history
2488 case PAGE_DOWN_KEY: // Page Down, end of history
2489 killRing.lastAction = KillRing::actionOther;
2490 // if not already recalling, add the current line to the history list so we don't
2491 // have to special case it
2492 if (historyIndex == historyLen - 1) {
2493 free(history[historyLen - 1]);
2494 size_t tempBufferSize = sizeof(UChar32) * len + 1;
2495 unique_ptr<UChar8[]> tempBuffer(new UChar8[tempBufferSize]);
2496 copyString32to8(tempBuffer.get(), buf32, tempBufferSize);
2497 history[historyLen - 1] = reinterpret_cast<UChar8*>(
2498 strdup(reinterpret_cast<const char*>(tempBuffer.get())));
2499 }
2500 if (historyLen > 1) {
2501 historyIndex = (c == META + '<' || c == PAGE_UP_KEY) ? 0 : historyLen - 1;
2502 historyPreviousIndex = -2;
2503 historyRecallMostRecent = true;
2504 size_t ucharCount;
2505 int errorCode;
2506 copyString8to32(buf32, history[historyIndex], buflen, ucharCount, errorCode);
2507 len = pos = ucharCount;
2508 refreshLine(pi);
2509 }
2510 break;
2511
2512 // not one of our special characters, maybe insert it in the buffer
2513 default:
2514 killRing.lastAction = KillRing::actionOther;
2515 historyRecallMostRecent = false;
2516 if (c & (META | CTRL)) { // beep on unknown Ctrl and/or Meta keys
2517 beep();
2518 break;
2519 }
2520 if (len < buflen) {
2521 if (isControlChar(c)) { // don't insert control characters
2522 beep();
2523 break;
2524 }
2525 if (len == pos) { // at end of buffer
2526 buf32[pos] = c;
2527 ++pos;
2528 ++len;
2529 buf32[len] = '\0';
2530 int inputLen = calculateColumnPosition(buf32, len);
2531 if (pi.promptIndentation + inputLen < pi.promptScreenColumns) {
2532 if (inputLen > pi.promptPreviousInputLen)
2533 pi.promptPreviousInputLen = inputLen;
2534 /* Avoid a full update of the line in the
2535 * trivial case. */
2536 if (write32(1, reinterpret_cast<UChar32*>(&c), 1) == -1)
2537 return -1;
2538 } else {
2539 refreshLine(pi);
2540 }
2541 } else { // not at end of buffer, have to move characters to our right
2542 memmove(buf32 + pos + 1, buf32 + pos, sizeof(UChar32) * (len - pos));
2543 buf32[pos] = c;
2544 ++len;
2545 ++pos;
2546 buf32[len] = '\0';
2547 refreshLine(pi);
2548 }
2549 } else {
2550 beep(); // buffer is full, beep on new characters
2551 }
2552 break;
2553 }
2554 }
2555 return len;
2556 }
2557
2558 string preloadedBufferContents; // used with linenoisePreloadBuffer
2559 string preloadErrorMessage;
2560
2561 /**
2562 * linenoisePreloadBuffer provides text to be inserted into the command buffer
2563 *
2564 * the provided text will be processed to be usable and will be used to preload
2565 * the input buffer on the next call to linenoise()
2566 *
2567 * @param preloadText text to begin with on the next call to linenoise()
2568 */
linenoisePreloadBuffer(const char * preloadText)2569 void linenoisePreloadBuffer(const char* preloadText) {
2570 if (!preloadText) {
2571 return;
2572 }
2573 int bufferSize = strlen(preloadText) + 1;
2574 unique_ptr<char[]> tempBuffer(new char[bufferSize]);
2575 strncpy(&tempBuffer[0], preloadText, bufferSize);
2576
2577 // remove characters that won't display correctly
2578 char* pIn = &tempBuffer[0];
2579 char* pOut = pIn;
2580 bool controlsStripped = false;
2581 bool whitespaceSeen = false;
2582 while (*pIn) {
2583 unsigned char c = *pIn++; // we need unsigned so chars 0x80 and above are allowed
2584 if ('\r' == c) { // silently skip CR
2585 continue;
2586 }
2587 if ('\n' == c || '\t' == c) { // note newline or tab
2588 whitespaceSeen = true;
2589 continue;
2590 }
2591 if (isControlChar(c)) { // remove other control characters, flag for message
2592 controlsStripped = true;
2593 *pOut++ = ' ';
2594 continue;
2595 }
2596 if (whitespaceSeen) { // convert whitespace to a single space
2597 *pOut++ = ' ';
2598 whitespaceSeen = false;
2599 }
2600 *pOut++ = c;
2601 }
2602 *pOut = 0;
2603 int processedLength = pOut - tempBuffer.get();
2604 bool lineTruncated = false;
2605 if (processedLength > (LINENOISE_MAX_LINE - 1)) {
2606 lineTruncated = true;
2607 tempBuffer[LINENOISE_MAX_LINE - 1] = 0;
2608 }
2609 preloadedBufferContents = tempBuffer.get();
2610 if (controlsStripped) {
2611 preloadErrorMessage += " [Edited line: control characters were converted to spaces]\n";
2612 }
2613 if (lineTruncated) {
2614 preloadErrorMessage += " [Edited line: the line length was reduced from ";
2615 char buf[128];
2616 snprintf(buf, sizeof(buf), "%d to %d]\n", processedLength, (LINENOISE_MAX_LINE - 1));
2617 preloadErrorMessage += buf;
2618 }
2619 }
2620
2621 /**
2622 * linenoise is a readline replacement.
2623 *
2624 * call it with a prompt to display and it will return a line of input from the user
2625 *
2626 * @param prompt text of prompt to display to the user
2627 * @return the returned string belongs to the caller on return and must be freed to prevent
2628 * memory leaks
2629 */
linenoise(const char * prompt)2630 char* linenoise(const char* prompt) {
2631 if (isatty(STDIN_FILENO)) { // input is from a terminal
2632 UChar32 buf32[LINENOISE_MAX_LINE];
2633 char charWidths[LINENOISE_MAX_LINE];
2634 if (!preloadErrorMessage.empty()) {
2635 printf("%s", preloadErrorMessage.c_str());
2636 fflush(stdout);
2637 preloadErrorMessage.clear();
2638 }
2639 PromptInfo pi(reinterpret_cast<const UChar8*>(prompt), getScreenColumns());
2640 if (isUnsupportedTerm()) {
2641 if (write32(1, pi.promptText.get(), pi.promptChars) == -1)
2642 return 0;
2643 fflush(stdout);
2644 if (preloadedBufferContents.empty()) {
2645 unique_ptr<char[]> buf8(new char[LINENOISE_MAX_LINE]);
2646 if (fgets(buf8.get(), LINENOISE_MAX_LINE, stdin) == NULL) {
2647 return NULL;
2648 }
2649 size_t len = strlen(buf8.get());
2650 while (len && (buf8[len - 1] == '\n' || buf8[len - 1] == '\r')) {
2651 --len;
2652 buf8[len] = '\0';
2653 }
2654 return strdup(buf8.get()); // caller must free buffer
2655 } else {
2656 char* buf8 = strdup(preloadedBufferContents.c_str());
2657 preloadedBufferContents.clear();
2658 return buf8; // caller must free buffer
2659 }
2660 } else {
2661 if (enableRawMode() == -1) {
2662 return NULL;
2663 }
2664 InputBuffer ib(buf32, charWidths, LINENOISE_MAX_LINE);
2665 if (!preloadedBufferContents.empty()) {
2666 ib.preloadBuffer(reinterpret_cast<const UChar8*>(preloadedBufferContents.c_str()));
2667 preloadedBufferContents.clear();
2668 }
2669 int count = ib.getInputLine(pi);
2670 disableRawMode();
2671 printf("\n");
2672 if (count == -1) {
2673 return NULL;
2674 }
2675 size_t bufferSize = sizeof(UChar32) * ib.length() + 1;
2676 unique_ptr<UChar8[]> buf8(new UChar8[bufferSize]);
2677 copyString32to8(buf8.get(), buf32, bufferSize);
2678 return strdup(reinterpret_cast<char*>(buf8.get())); // caller must free buffer
2679 }
2680 } else { // input not from a terminal, we should work with piped input, i.e. redirected stdin
2681 unique_ptr<char[]> buf8(new char[LINENOISE_MAX_LINE]);
2682 if (fgets(buf8.get(), LINENOISE_MAX_LINE, stdin) == NULL) {
2683 return NULL;
2684 }
2685
2686 // if fgets() gave us the newline, remove it
2687 int count = strlen(buf8.get());
2688 if (count > 0 && buf8[count - 1] == '\n') {
2689 --count;
2690 buf8[count] = '\0';
2691 }
2692 return strdup(buf8.get()); // caller must free buffer
2693 }
2694 }
2695
2696 /* Register a callback function to be called for tab-completion. */
linenoiseSetCompletionCallback(linenoiseCompletionCallback * fn)2697 void linenoiseSetCompletionCallback(linenoiseCompletionCallback* fn) {
2698 completionCallback = fn;
2699 }
2700
linenoiseAddCompletion(linenoiseCompletions * lc,const char * str)2701 void linenoiseAddCompletion(linenoiseCompletions* lc, const char* str) {
2702 lc->completionStrings.push_back(Utf32String(reinterpret_cast<const UChar8*>(str)));
2703 }
2704
linenoiseHistoryAdd(const char * line)2705 int linenoiseHistoryAdd(const char* line) {
2706 if (historyMaxLen == 0) {
2707 return 0;
2708 }
2709 if (history == NULL) {
2710 history = reinterpret_cast<UChar8**>(malloc(sizeof(UChar8*) * historyMaxLen));
2711 if (history == NULL) {
2712 return 0;
2713 }
2714 memset(history, 0, (sizeof(char*) * historyMaxLen));
2715 }
2716 UChar8* linecopy = reinterpret_cast<UChar8*>(strdup(line));
2717 if (!linecopy) {
2718 return 0;
2719 }
2720 if (historyLen == historyMaxLen) {
2721 free(history[0]);
2722 memmove(history, history + 1, sizeof(char*) * (historyMaxLen - 1));
2723 --historyLen;
2724 if (--historyPreviousIndex < -1) {
2725 historyPreviousIndex = -2;
2726 }
2727 }
2728
2729 // convert newlines in multi-line code to spaces before storing
2730 UChar8* p = linecopy;
2731 while (*p) {
2732 if (*p == '\n') {
2733 *p = ' ';
2734 }
2735 ++p;
2736 }
2737 history[historyLen] = linecopy;
2738 ++historyLen;
2739 return 1;
2740 }
2741
linenoiseHistorySetMaxLen(int len)2742 int linenoiseHistorySetMaxLen(int len) {
2743 if (len < 1) {
2744 return 0;
2745 }
2746 if (history) {
2747 int tocopy = historyLen;
2748 UChar8** newHistory = reinterpret_cast<UChar8**>(malloc(sizeof(UChar8*) * len));
2749 if (newHistory == NULL) {
2750 return 0;
2751 }
2752 if (len < tocopy) {
2753 tocopy = len;
2754 }
2755 memcpy(newHistory, history + historyMaxLen - tocopy, sizeof(UChar8*) * tocopy);
2756 free(history);
2757 history = newHistory;
2758 }
2759 historyMaxLen = len;
2760 if (historyLen > historyMaxLen) {
2761 historyLen = historyMaxLen;
2762 }
2763 return 1;
2764 }
2765
2766 namespace {
linenoiseFileError(mongo::ErrorCodes::Error code,const char * what,const char * filename)2767 mongo::Status linenoiseFileError(mongo::ErrorCodes::Error code,
2768 const char* what,
2769 const char* filename) {
2770 std::stringstream ss;
2771 ss << "Unable to " << what << " file " << filename << ": " << mongo::errnoWithDescription();
2772 return {code, ss.str()};
2773 }
2774 } // namespace
2775
2776 /* Save the history in the specified file. */
linenoiseHistorySave(const char * filename)2777 mongo::Status linenoiseHistorySave(const char* filename) {
2778 FILE* fp;
2779 #if _POSIX_C_SOURCE >= 1 || _XOPEN_SOURCE || _POSIX_SOURCE || defined(__APPLE__)
2780 int fd = open(filename, O_WRONLY | O_TRUNC | O_CREAT, S_IRUSR | S_IWUSR);
2781 if (fd == -1) {
2782 return linenoiseFileError(mongo::ErrorCodes::FileOpenFailed, "open()", filename);
2783 }
2784 fp = fdopen(fd, "wt");
2785 if (fp == NULL) {
2786 // We've already failed, so no need to report any close() failure.
2787 (void)close(fd);
2788 return linenoiseFileError(mongo::ErrorCodes::FileOpenFailed, "fdopen()", filename);
2789 }
2790 #else
2791 fp = fopen(filename, "wt");
2792 if (fp == NULL) {
2793 return linenoiseFileError(mongo::ErrorCodes::FileOpenFailed, "fopen()", filename);
2794 }
2795 #endif // _POSIX_C_SOURCE >= 1 || _XOPEN_SOURCE || _POSIX_SOURCE || defined(__APPLE__)
2796
2797 for (int j = 0; j < historyLen; ++j) {
2798 if (history[j][0] != '\0') {
2799 if (fprintf(fp, "%s\n", history[j]) < 0) {
2800 // We've already failed, so no need to report any fclose() failure.
2801 (void)fclose(fp);
2802 return linenoiseFileError(
2803 mongo::ErrorCodes::FileStreamFailed, "fprintf() to", filename);
2804 }
2805 }
2806 }
2807 // Closing fp also causes fd to be closed.
2808 if (fclose(fp) != 0) {
2809 return linenoiseFileError(mongo::ErrorCodes::FileStreamFailed, "fclose()", filename);
2810 }
2811 return mongo::Status::OK();
2812 }
2813
2814 /* Load the history from the specified file. */
linenoiseHistoryLoad(const char * filename)2815 mongo::Status linenoiseHistoryLoad(const char* filename) {
2816 FILE* fp = fopen(filename, "rt");
2817 if (fp == NULL) {
2818 if (errno == ENOENT) {
2819 // Not having a history file isn't an error condition.
2820 // For example, it's always the case when the shell is run for the first time.
2821 return mongo::Status::OK();
2822 }
2823 return linenoiseFileError(mongo::ErrorCodes::FileOpenFailed, "fopen()", filename);
2824 }
2825
2826 char buf[LINENOISE_MAX_LINE];
2827 while (fgets(buf, LINENOISE_MAX_LINE, fp) != NULL) {
2828 char* p = strchr(buf, '\r');
2829 if (!p) {
2830 p = strchr(buf, '\n');
2831 }
2832 if (p) {
2833 *p = '\0';
2834 }
2835 if (p != buf) {
2836 linenoiseHistoryAdd(buf);
2837 }
2838 }
2839 // fgets() returns NULL on error or EOF (with nothing read).
2840 // So if we aren't EOF, it must have been an error.
2841 if (!feof(fp)) {
2842 // We've already failed, so no need to report any fclose() failure.
2843 (void)fclose(fp);
2844 return linenoiseFileError(mongo::ErrorCodes::FileStreamFailed, "fgets() from", filename);
2845 }
2846 if (fclose(fp) != 0) {
2847 return linenoiseFileError(mongo::ErrorCodes::FileStreamFailed, "fclose()", filename);
2848 }
2849 return mongo::Status::OK();
2850 }
2851