crossline.c (55128B)
1 /* crossline.c -- Version 1.0 2 * 3 * Crossline is a small, self-contained, zero-config, MIT licensed, 4 * cross-platform, readline and libedit replacement. 5 * 6 * Press <F1> to get full shortcuts list. 7 * 8 * You can find the latest source code and description at: 9 * 10 * https://github.com/jcwangxp/crossline 11 * 12 * ------------------------------------------------------------------------ 13 * 14 * MIT License 15 * 16 * Copyright (c) 2019, JC Wang (wang_junchuan@163.com) 17 * 18 * Permission is hereby granted, free of charge, to any person obtaining a copy 19 * of this software and associated documentation files (the "Software"), to deal 20 * in the Software without restriction, including without limitation the rights 21 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 22 * copies of the Software, and to permit persons to whom the Software is 23 * furnished to do so, subject to the following conditions: 24 * 25 * The above copyright notice and this permission notice shall be included in all 26 * copies or substantial portions of the Software. 27 * 28 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 29 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 30 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 31 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 32 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 33 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 34 * SOFTWARE. 35 * ------------------------------------------------------------------------ 36 */ 37 38 #include <stdio.h> 39 #include <stdlib.h> 40 #include <string.h> 41 #include <errno.h> 42 #include <ctype.h> 43 #include <stdint.h> 44 45 #ifdef _WIN32 46 #include <io.h> 47 #include <conio.h> 48 #include <windows.h> 49 #ifndef STDIN_FILENO 50 #define STDIN_FILENO _fileno(stdin) 51 #define STDOUT_FILENO _fileno(stdout) 52 #endif 53 #define isatty _isatty 54 #define strcasecmp _stricmp 55 #define strncasecmp _strnicmp 56 static int s_crossline_win = 1; 57 #else 58 #include <unistd.h> 59 #include <termios.h> 60 #include <fcntl.h> 61 #include <signal.h> 62 #include <sys/ioctl.h> 63 #include <sys/stat.h> 64 static int s_crossline_win = 0; 65 #endif 66 67 #include "crossline.h" 68 69 /*****************************************************************************/ 70 71 // Default word delimiters for move and cut 72 #define CROSS_DFT_DELIMITER " !\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~" 73 74 #define CROSS_HISTORY_MAX_LINE 256 // Maximum history line number 75 #define CROSS_HISTORY_BUF_LEN 4096 // History line length 76 #define CROSS_HIS_MATCH_PAT_NUM 16 // History search pattern number 77 78 #define CROSS_COMPLET_MAX_LINE 1024 // Maximum completion word number 79 #define CROSS_COMPLET_WORD_LEN 64 // Completion word length 80 #define CROSS_COMPLET_HELP_LEN 256 // Completion word's help length 81 #define CROSS_COMPLET_HINT_LEN 128 // Completion syntax hints length 82 83 // Make control-characters readable 84 #define CTRL_KEY(key) (key - 0x40) 85 // Build special key code for escape sequences 86 #define ALT_KEY(key) (key + ((KEY_ESC+1)<<8)) 87 #define ESC_KEY3(ch) ((KEY_ESC<<8) + ch) 88 #define ESC_KEY4(ch1,ch2) ((KEY_ESC<<8) + ((ch1)<<16) + ch2) 89 #define ESC_KEY6(ch1,ch2,ch3) ((KEY_ESC<<8) + ((ch1)<<16) + ((ch2)<<24) + ch3) 90 #define ESC_OKEY(ch) ((KEY_ESC<<8) + ('O'<<16) + ch) 91 92 /*****************************************************************************/ 93 94 enum { 95 KEY_TAB = 9, // Autocomplete. 96 KEY_BACKSPACE = 8, // Delete character before cursor. 97 KEY_ENTER = 13, // Accept line. (Linux) 98 KEY_ENTER2 = 10, // Accept line. (Windows) 99 KEY_ESC = 27, // Escapce 100 KEY_DEL2 = 127, // It's treaded as Backspace is Linux 101 KEY_DEBUG = 30, // Ctrl-^ Enter keyboard debug mode 102 103 #ifdef _WIN32 // Windows 104 105 KEY_INSERT = (KEY_ESC<<8) + 'R', // Paste last cut text. 106 KEY_DEL = (KEY_ESC<<8) + 'S', // Delete character under cursor. 107 KEY_HOME = (KEY_ESC<<8) + 'G', // Move cursor to start of line. 108 KEY_END = (KEY_ESC<<8) + 'O', // Move cursor to end of line. 109 KEY_PGUP = (KEY_ESC<<8) + 'I', // Move to first line in history. 110 KEY_PGDN = (KEY_ESC<<8) + 'Q', // Move to end of input history. 111 KEY_UP = (KEY_ESC<<8) + 'H', // Fetch previous line in history. 112 KEY_DOWN = (KEY_ESC<<8) + 'P', // Fetch next line in history. 113 KEY_LEFT = (KEY_ESC<<8) + 'K', // Move back a character. 114 KEY_RIGHT = (KEY_ESC<<8) + 'M', // Move forward a character. 115 116 KEY_CTRL_DEL = (KEY_ESC<<8) + 147, // Cut word following cursor. 117 KEY_CTRL_HOME = (KEY_ESC<<8) + 'w', // Cut from start of line to cursor. 118 KEY_CTRL_END = (KEY_ESC<<8) + 'u', // Cut from cursor to end of line. 119 KEY_CTRL_UP = (KEY_ESC<<8) + 141, // Uppercase current or following word. 120 KEY_CTRL_DOWN = (KEY_ESC<<8) + 145, // Lowercase current or following word. 121 KEY_CTRL_LEFT = (KEY_ESC<<8) + 's', // Move back a word. 122 KEY_CTRL_RIGHT = (KEY_ESC<<8) + 't', // Move forward a word. 123 KEY_CTRL_BACKSPACE = (KEY_ESC<<8) + 127, // Cut from start of line to cursor. 124 125 KEY_ALT_DEL = ALT_KEY(163), // Cut word following cursor. 126 KEY_ALT_HOME = ALT_KEY(151), // Cut from start of line to cursor. 127 KEY_ALT_END = ALT_KEY(159), // Cut from cursor to end of line. 128 KEY_ALT_UP = ALT_KEY(152), // Uppercase current or following word. 129 KEY_ALT_DOWN = ALT_KEY(160), // Lowercase current or following word. 130 KEY_ALT_LEFT = ALT_KEY(155), // Move back a word. 131 KEY_ALT_RIGHT = ALT_KEY(157), // Move forward a word. 132 KEY_ALT_BACKSPACE = ALT_KEY(KEY_BACKSPACE), // Cut from start of line to cursor. 133 134 KEY_F1 = (KEY_ESC<<8) + ';', // Show help. 135 KEY_F2 = (KEY_ESC<<8) + '<', // Show history. 136 KEY_F3 = (KEY_ESC<<8) + '=', // Clear history (need confirm). 137 KEY_F4 = (KEY_ESC<<8) + '>', // Search history with current input. 138 139 #else // Linux 140 141 KEY_INSERT = ESC_KEY4('2','~'), // vt100 Esc[2~: Paste last cut text. 142 KEY_DEL = ESC_KEY4('3','~'), // vt100 Esc[3~: Delete character under cursor. 143 KEY_HOME = ESC_KEY4('1','~'), // vt100 Esc[1~: Move cursor to start of line. 144 KEY_END = ESC_KEY4('4','~'), // vt100 Esc[4~: Move cursor to end of line. 145 KEY_PGUP = ESC_KEY4('5','~'), // vt100 Esc[5~: Move to first line in history. 146 KEY_PGDN = ESC_KEY4('6','~'), // vt100 Esc[6~: Move to end of input history. 147 KEY_UP = ESC_KEY3('A'), // Esc[A: Fetch previous line in history. 148 KEY_DOWN = ESC_KEY3('B'), // Esc[B: Fetch next line in history. 149 KEY_LEFT = ESC_KEY3('D'), // Esc[D: Move back a character. 150 KEY_RIGHT = ESC_KEY3('C'), // Esc[C: Move forward a character. 151 KEY_HOME2 = ESC_KEY3('H'), // xterm Esc[H: Move cursor to start of line. 152 KEY_END2 = ESC_KEY3('F'), // xterm Esc[F: Move cursor to end of line. 153 154 KEY_CTRL_DEL = ESC_KEY6('3','5','~'), // xterm Esc[3;5~: Cut word following cursor. 155 KEY_CTRL_HOME = ESC_KEY6('1','5','H'), // xterm Esc[1;5H: Cut from start of line to cursor. 156 KEY_CTRL_END = ESC_KEY6('1','5','F'), // xterm Esc[1;5F: Cut from cursor to end of line. 157 KEY_CTRL_UP = ESC_KEY6('1','5','A'), // xterm Esc[1;5A: Uppercase current or following word. 158 KEY_CTRL_DOWN = ESC_KEY6('1','5','B'), // xterm Esc[1;5B: Lowercase current or following word. 159 KEY_CTRL_LEFT = ESC_KEY6('1','5','D'), // xterm Esc[1;5D: Move back a word. 160 KEY_CTRL_RIGHT = ESC_KEY6('1','5','C'), // xterm Esc[1;5C: Move forward a word. 161 KEY_CTRL_BACKSPACE = 31, // xterm Cut from start of line to cursor. 162 KEY_CTRL_UP2 = ESC_OKEY('A'), // vt100 EscOA: Uppercase current or following word. 163 KEY_CTRL_DOWN2 = ESC_OKEY('B'), // vt100 EscOB: Lowercase current or following word. 164 KEY_CTRL_LEFT2 = ESC_OKEY('D'), // vt100 EscOD: Move back a word. 165 KEY_CTRL_RIGHT2 = ESC_OKEY('C'), // vt100 EscOC: Move forward a word. 166 167 KEY_ALT_DEL = ESC_KEY6('3','3','~'), // xterm Esc[3;3~: Cut word following cursor. 168 KEY_ALT_HOME = ESC_KEY6('1','3','H'), // xterm Esc[1;3H: Cut from start of line to cursor. 169 KEY_ALT_END = ESC_KEY6('1','3','F'), // xterm Esc[1;3F: Cut from cursor to end of line. 170 KEY_ALT_UP = ESC_KEY6('1','3','A'), // xterm Esc[1;3A: Uppercase current or following word. 171 KEY_ALT_DOWN = ESC_KEY6('1','3','B'), // xterm Esc[1;3B: Lowercase current or following word. 172 KEY_ALT_LEFT = ESC_KEY6('1','3','D'), // xterm Esc[1;3D: Move back a word. 173 KEY_ALT_RIGHT = ESC_KEY6('1','3','C'), // xterm Esc[1;3C: Move forward a word. 174 KEY_ALT_BACKSPACE = ALT_KEY(KEY_DEL2), // Cut from start of line to cursor. 175 176 KEY_F1 = ESC_OKEY('P'), // EscOP: Show help. 177 KEY_F2 = ESC_OKEY('Q'), // EscOQ: Show history. 178 KEY_F3 = ESC_OKEY('R'), // EscOP: Clear history (need confirm). 179 KEY_F4 = ESC_OKEY('S'), // EscOP: Search history with current input. 180 181 KEY_F1_2 = ESC_KEY4('[', 'A'), // linux Esc[[A: Show help. 182 KEY_F2_2 = ESC_KEY4('[', 'B'), // linux Esc[[B: Show history. 183 KEY_F3_2 = ESC_KEY4('[', 'C'), // linux Esc[[C: Clear history (need confirm). 184 KEY_F4_2 = ESC_KEY4('[', 'D'), // linux Esc[[D: Search history with current input. 185 186 #endif 187 }; 188 189 /*****************************************************************************/ 190 191 typedef struct crossline_completions_t { 192 int num; 193 char word[CROSS_COMPLET_MAX_LINE][CROSS_COMPLET_WORD_LEN]; 194 char help[CROSS_COMPLET_MAX_LINE][CROSS_COMPLET_HELP_LEN]; 195 char hints[CROSS_COMPLET_HINT_LEN]; 196 crossline_color_e color_word[CROSS_COMPLET_MAX_LINE]; 197 crossline_color_e color_help[CROSS_COMPLET_MAX_LINE]; 198 crossline_color_e color_hints; 199 } crossline_completions_t; 200 201 static char s_word_delimiter[64] = CROSS_DFT_DELIMITER; 202 static char s_history_buf[CROSS_HISTORY_MAX_LINE][CROSS_HISTORY_BUF_LEN]; 203 static uint32_t s_history_id = 0; // Increase always, wrap until UINT_MAX 204 static char s_clip_buf[CROSS_HISTORY_BUF_LEN]; // Buf to store cut text 205 static crossline_completion_callback s_completion_callback = NULL; 206 static int s_paging_print_line = 0; // For paging control 207 static int s_got_resize = 0; // Window size changed 208 static crossline_color_e s_prompt_color = CROSSLINE_COLOR_DEFAULT; 209 210 static char* crossline_readline_edit (char *buf, int size, const char *prompt, int has_input, int in_his); 211 static int crossline_history_dump (FILE *file, int print_id, char *patterns, int sel_id, int paging); 212 213 #define isdelim(ch) (NULL != strchr(s_word_delimiter, ch)) // Check ch is word delimiter 214 215 // Debug macro. 216 #if 0 217 static FILE *s_crossline_debug_fp = NULL; 218 #define crossline_debug(...) \ 219 do { \ 220 if (NULL == s_crossline_debug_fp) { s_crossline_debug_fp = fopen("crossline_debug.txt", "a"); } \ 221 fprintf (s_crossline_debug_fp, __VA_ARGS__); \ 222 fflush (s_crossline_debug_fp); \ 223 } while (0) 224 #else 225 #define crossline_debug(...) 226 #endif 227 228 /*****************************************************************************/ 229 230 static char* s_crossline_help[] = { 231 " Misc Commands", 232 " +-------------------------+--------------------------------------------------+", 233 " | F1 | Show edit shortcuts help. |", 234 " | Ctrl-^ | Enter keyboard debugging mode. |", 235 " +-------------------------+--------------------------------------------------+", 236 " Move Commands", 237 " +-------------------------+--------------------------------------------------+", 238 " | Ctrl-B, Left | Move back a character. |", 239 " | Ctrl-F, Right | Move forward a character. |", 240 " | Up, ESC+Up | Move cursor to up line. (For multiple lines) |", 241 " | Ctrl-Up, Alt-Up | (Ctrl-Up, Alt-Up only supports Windows/Xterm) |", 242 " | Down, ESC+Down | Move cursor to down line. (For multiple lines) |", 243 " | Ctrl-Down,Alt-Down | (Ctrl-Down, Alt-Down only support Windows/Xterm)|", 244 " | Alt-B, ESC+Left, | Move back a word. |", 245 " | Ctrl-Left, Alt-Left | (Ctrl-Left, Alt-Left only support Windows/Xterm)|", 246 " | Alt-F, ESC+Right, | Move forward a word. |", 247 " | Ctrl-Right, Alt-Right | (Ctrl-Right,Alt-Right only support Windows/Xterm)|", 248 " | Ctrl-A, Home | Move cursor to start of line. |", 249 " | Ctrl-E, End | Move cursor to end of line. |", 250 " | Ctrl-L | Clear screen and redisplay line. |", 251 " +-------------------------+--------------------------------------------------+", 252 " Edit Commands", 253 " +-------------------------+--------------------------------------------------+", 254 " | Ctrl-H, Backspace | Delete character before cursor. |", 255 " | Ctrl-D, DEL | Delete character under cursor. |", 256 " | Alt-U | Uppercase current or following word. |", 257 " | Alt-L | Lowercase current or following word. |", 258 " | Alt-C | Capitalize current or following word. |", 259 " | Alt-\\ | Delete whitespace around cursor. |", 260 " | Ctrl-T | Transpose character. |", 261 " +-------------------------+--------------------------------------------------+", 262 " Cut&Paste Commands", 263 " +-------------------------+--------------------------------------------------+", 264 " | Ctrl-K, ESC+End, | Cut from cursor to end of line. |", 265 " | Ctrl-End, Alt-End | (Ctrl-End, Alt-End only support Windows/Xterm) |", 266 " | Ctrl-U, ESC+Home, | Cut from start of line to cursor. |", 267 " | Ctrl-Home, Alt-Home | (Ctrl-Home, Alt-Home only support Windows/Xterm)|", 268 " | Ctrl-X | Cut whole line. |", 269 " | Alt-Backspace, | Cut word to left of cursor. |", 270 " | Esc+Backspace, | |", 271 " | Clt-Backspace | (Clt-Backspace only supports Windows/Xterm) |", 272 " | Alt-D, ESC+Del, | Cut word following cursor. |", 273 " | Alt-Del, Ctrl-Del | (Alt-Del,Ctrl-Del only support Windows/Xterm) |", 274 " | Ctrl-W | Cut to left till whitespace (not word). |", 275 " | Ctrl-Y, Ctrl-V, Insert | Paste last cut text. |", 276 " +-------------------------+--------------------------------------------------+", 277 " Complete Commands", 278 " +-------------------------+--------------------------------------------------+", 279 " | TAB, Ctrl-I | Autocomplete. |", 280 " | Alt-=, Alt-? | List possible completions. |", 281 " +-------------------------+--------------------------------------------------+", 282 " History Commands", 283 " +-------------------------+--------------------------------------------------+", 284 " | Ctrl-P, Up | Fetch previous line in history. |", 285 " | Ctrl-N, Down | Fetch next line in history. |", 286 " | Alt-<, PgUp | Move to first line in history. |", 287 " | Alt->, PgDn | Move to end of input history. |", 288 " | Ctrl-R, Ctrl-S | Search history. |", 289 " | F4 | Search history with current input. |", 290 " | F1 | Show search help when in search mode. |", 291 " | F2 | Show history. |", 292 " | F3 | Clear history (need confirm). |", 293 " +-------------------------+--------------------------------------------------+", 294 " Control Commands", 295 " +-------------------------+--------------------------------------------------+", 296 " | Enter, Ctrl-J, Ctrl-M | EOL and accept line. |", 297 " | Ctrl-C, Ctrl-G | EOF and abort line. |", 298 " | Ctrl-D | EOF if line is empty. |", 299 " | Alt-R | Revert line. |", 300 " | Ctrl-Z | Suspend Job. (Linux Only, fg will resume edit) |", 301 " +-------------------------+--------------------------------------------------+", 302 " Note: If Alt-key doesn't work, an alternate way is to press ESC first then press key, see above ESC+Key.", 303 " Note: In multiple lines:", 304 " Up/Down and Ctrl/Alt-Up, Ctrl/Alt-Down will move between lines.", 305 " Up key will fetch history when cursor in first line or end of last line(for quick history move)", 306 " Down key will fetch history when cursor in last line.", 307 " Ctrl/Alt-Up, Ctrl/Alt-Down will just move between lines.", 308 NULL}; 309 310 static char* s_search_help[] = { 311 "Patterns are separated by ' ', patter match is case insensitive:", 312 " (Hint: use Ctrl-Y/Ctrl-V/Insert to paste last paterns)", 313 " select: choose line including 'select'", 314 " -select: choose line excluding 'select'", 315 " \"select from\": choose line including \"select from\"", 316 " -\"select from\": choose line excluding \"select from\"", 317 "Example:", 318 " \"select from\" where -\"order by\" -limit: ", 319 " choose line including \"select from\" and 'where'", 320 " and excluding \"order by\" or 'limit'", 321 NULL}; 322 323 /*****************************************************************************/ 324 325 // Main API to read a line, return buf if get line, return NULL if EOF. 326 static char* crossline_readline_internal (const char *prompt, char *buf, int size, int has_input) 327 { 328 int not_support = 0, len; 329 330 if ((NULL == buf) || (size <= 1)) 331 { return NULL; } 332 if (!isatty(STDIN_FILENO)) { // input is not from a terminal 333 not_support = 1; 334 } else { 335 char *term = getenv("TERM"); 336 if (NULL != term) { 337 if (!strcasecmp(term, "dumb") || !strcasecmp(term, "cons25") || !strcasecmp(term, "emacs")) 338 { not_support = 1; } 339 } 340 } 341 if (not_support) { 342 if (NULL == fgets(buf, size, stdin)) 343 { return NULL; } 344 for (len = (int)strlen(buf); (len > 0) && (('\n'==buf[len-1]) || ('\r'==buf[len-1])); --len) 345 { buf[len-1] = '\0'; } 346 return buf; 347 } 348 349 return crossline_readline_edit (buf, size, prompt, has_input, 0); 350 } 351 char* crossline_readline (const char *prompt, char *buf, int size) 352 { 353 return crossline_readline_internal (prompt, buf, size, 0); 354 } 355 char* crossline_readline2 (const char *prompt, char *buf, int size) 356 { 357 return crossline_readline_internal (prompt, buf, size, 1); 358 } 359 360 // Set move/cut word delimiter, defaut is all not digital and alphabetic characters. 361 void crossline_delimiter_set (const char *delim) 362 { 363 if (NULL != delim) { 364 strncpy (s_word_delimiter, delim, sizeof(s_word_delimiter) - 1); 365 s_word_delimiter[sizeof(s_word_delimiter) - 1] = '\0'; 366 } 367 } 368 369 void crossline_history_show (void) 370 { 371 crossline_history_dump (stdout, 1, NULL, 0, isatty(STDIN_FILENO)); 372 } 373 374 void crossline_history_clear (void) 375 { 376 memset (s_history_buf, 0, sizeof (s_history_buf)); 377 s_history_id = 0; 378 } 379 380 int crossline_history_save (const char *filename) 381 { 382 if (NULL == filename) { 383 return -1; 384 } else { 385 FILE *file = fopen(filename, "wt"); 386 if (file == NULL) { return -1; } 387 crossline_history_dump (file, 0, NULL, 0, 0); 388 fclose(file); 389 } 390 return 0; 391 } 392 393 int crossline_history_load (const char* filename) 394 { 395 int len; 396 char buf[CROSS_HISTORY_BUF_LEN]; 397 FILE *file; 398 399 if (NULL == filename) { return -1; } 400 file = fopen(filename, "rt"); 401 if (NULL == file) { return -1; } 402 while (NULL != fgets(buf, CROSS_HISTORY_BUF_LEN, file)) { 403 for (len = (int)strlen(buf); (len > 0) && (('\n'==buf[len-1]) || ('\r'==buf[len-1])); --len) 404 { buf[len-1] = '\0'; } 405 if (len > 0) { 406 buf[CROSS_HISTORY_BUF_LEN-1] = '\0'; 407 strcpy (s_history_buf[(s_history_id++) % CROSS_HISTORY_MAX_LINE], buf); 408 } 409 } 410 fclose(file); 411 return 0; 412 } 413 414 // Register completion callback. 415 void crossline_completion_register (crossline_completion_callback pCbFunc) 416 { 417 s_completion_callback = pCbFunc; 418 } 419 420 // Add completion in callback. Word is must, help for word is optional. 421 void crossline_completion_add_color (crossline_completions_t *pCompletions, const char *word, 422 crossline_color_e wcolor, const char *help, crossline_color_e hcolor) 423 { 424 if ((NULL != pCompletions) && (NULL != word) && (pCompletions->num < CROSS_COMPLET_MAX_LINE)) { 425 strncpy (pCompletions->word[pCompletions->num], word, CROSS_COMPLET_WORD_LEN); 426 pCompletions->word[pCompletions->num][CROSS_COMPLET_WORD_LEN - 1] = '\0'; 427 pCompletions->color_word[pCompletions->num] = wcolor; 428 pCompletions->help[pCompletions->num][0] = '\0'; 429 if (NULL != help) { 430 strncpy (pCompletions->help[pCompletions->num], help, CROSS_COMPLET_HELP_LEN); 431 pCompletions->help[pCompletions->num][CROSS_COMPLET_HELP_LEN - 1] = '\0'; 432 pCompletions->color_help[pCompletions->num] = hcolor; 433 } 434 pCompletions->num++; 435 } 436 } 437 void crossline_completion_add (crossline_completions_t *pCompletions, const char *word, const char *help) 438 { 439 crossline_completion_add_color (pCompletions, word, CROSSLINE_COLOR_DEFAULT, help, CROSSLINE_COLOR_DEFAULT); 440 } 441 442 // Set syntax hints in callback. 443 void crossline_hints_set_color (crossline_completions_t *pCompletions, const char *hints, crossline_color_e color) 444 { 445 if ((NULL != pCompletions) && (NULL != hints)) { 446 strncpy (pCompletions->hints, hints, CROSS_COMPLET_HINT_LEN - 1); 447 pCompletions->hints[CROSS_COMPLET_HINT_LEN - 1] = '\0'; 448 pCompletions->color_hints = color; 449 } 450 } 451 void crossline_hints_set (crossline_completions_t *pCompletions, const char *hints) 452 { 453 crossline_hints_set_color (pCompletions, hints, CROSSLINE_COLOR_DEFAULT); 454 } 455 456 /*****************************************************************************/ 457 458 int crossline_paging_set (int enable) 459 { 460 int prev = s_paging_print_line >=0; 461 s_paging_print_line = enable ? 0 : -1; 462 return prev; 463 } 464 465 int crossline_paging_check (int line_len) 466 { 467 char *paging_hints = "*** Press <Space> or <Enter> to continue . . ."; 468 int i, ch, rows, cols, len = (int)strlen(paging_hints); 469 470 if ((s_paging_print_line < 0) || !isatty(STDIN_FILENO) || !isatty(STDOUT_FILENO)) { return 0; } 471 crossline_screen_get (&rows, &cols); 472 s_paging_print_line += (line_len + cols - 1) / cols; 473 if (s_paging_print_line >= (rows - 1)) { 474 printf ("%s", paging_hints); 475 ch = crossline_getch(); 476 if (0 == ch) { crossline_getch(); } // some terminal server may send 0 after Enter 477 // clear paging hints 478 for (i = 0; i < len; ++i) { printf ("\b"); } 479 for (i = 0; i < len; ++i) { printf (" "); } 480 for (i = 0; i < len; ++i) { printf ("\b"); } 481 s_paging_print_line = 0; 482 if ((' ' != ch) && (KEY_ENTER != ch) && (KEY_ENTER2 != ch)) { 483 return 1; 484 } 485 } 486 return 0; 487 } 488 489 /*****************************************************************************/ 490 491 void crossline_prompt_color_set (crossline_color_e color) 492 { 493 s_prompt_color = color; 494 } 495 496 void crossline_screen_clear () 497 { 498 int ret = system (s_crossline_win ? "cls" : "clear"); 499 (void) ret; 500 } 501 502 #ifdef _WIN32 // Windows 503 504 int crossline_getch (void) 505 { 506 fflush (stdout); 507 return _getch(); 508 } 509 void crossline_screen_get (int *pRows, int *pCols) 510 { 511 CONSOLE_SCREEN_BUFFER_INFO inf; 512 GetConsoleScreenBufferInfo (GetStdHandle(STD_OUTPUT_HANDLE), &inf); 513 *pCols = inf.srWindow.Right - inf.srWindow.Left + 1; 514 *pRows = inf.srWindow.Bottom - inf.srWindow.Top + 1; 515 *pCols = *pCols > 1 ? *pCols : 160; 516 *pRows = *pRows > 1 ? *pRows : 24; 517 } 518 int crossline_cursor_get (int *pRow, int *pCol) 519 { 520 CONSOLE_SCREEN_BUFFER_INFO inf; 521 GetConsoleScreenBufferInfo (GetStdHandle(STD_OUTPUT_HANDLE), &inf); 522 *pRow = inf.dwCursorPosition.Y - inf.srWindow.Top; 523 *pCol = inf.dwCursorPosition.X - inf.srWindow.Left; 524 return 0; 525 } 526 void crossline_cursor_set (int row, int col) 527 { 528 CONSOLE_SCREEN_BUFFER_INFO inf; 529 GetConsoleScreenBufferInfo (GetStdHandle(STD_OUTPUT_HANDLE), &inf); 530 inf.dwCursorPosition.Y = (SHORT)row + inf.srWindow.Top; 531 inf.dwCursorPosition.X = (SHORT)col + inf.srWindow.Left; 532 SetConsoleCursorPosition (GetStdHandle(STD_OUTPUT_HANDLE), inf.dwCursorPosition); 533 } 534 void crossline_cursor_move (int row_off, int col_off) 535 { 536 CONSOLE_SCREEN_BUFFER_INFO inf; 537 GetConsoleScreenBufferInfo (GetStdHandle(STD_OUTPUT_HANDLE), &inf); 538 inf.dwCursorPosition.Y += (SHORT)row_off; 539 inf.dwCursorPosition.X += (SHORT)col_off; 540 SetConsoleCursorPosition (GetStdHandle(STD_OUTPUT_HANDLE), inf.dwCursorPosition); 541 } 542 void crossline_cursor_hide (int bHide) 543 { 544 CONSOLE_CURSOR_INFO inf; 545 GetConsoleCursorInfo (GetStdHandle(STD_OUTPUT_HANDLE), &inf); 546 inf.bVisible = !bHide; 547 SetConsoleCursorInfo (GetStdHandle(STD_OUTPUT_HANDLE), &inf); 548 } 549 550 void crossline_color_set (crossline_color_e color) 551 { 552 CONSOLE_SCREEN_BUFFER_INFO info; 553 static WORD dft_wAttributes = 0; 554 WORD wAttributes = 0; 555 if (!dft_wAttributes) { 556 GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &info); 557 dft_wAttributes = info.wAttributes; 558 } 559 if (CROSSLINE_FGCOLOR_DEFAULT == (color&CROSSLINE_FGCOLOR_MASK)) { 560 wAttributes |= dft_wAttributes & (FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE | FOREGROUND_INTENSITY); 561 } else { 562 wAttributes |= (color&CROSSLINE_FGCOLOR_BRIGHT) ? FOREGROUND_INTENSITY : 0; 563 switch (color&CROSSLINE_FGCOLOR_MASK) { 564 case CROSSLINE_FGCOLOR_RED: wAttributes |= FOREGROUND_RED; break; 565 case CROSSLINE_FGCOLOR_GREEN: wAttributes |= FOREGROUND_GREEN;break; 566 case CROSSLINE_FGCOLOR_BLUE: wAttributes |= FOREGROUND_BLUE; break; 567 case CROSSLINE_FGCOLOR_YELLOW: wAttributes |= FOREGROUND_RED | FOREGROUND_GREEN; break; 568 case CROSSLINE_FGCOLOR_MAGENTA: wAttributes |= FOREGROUND_RED | FOREGROUND_BLUE; break; 569 case CROSSLINE_FGCOLOR_CYAN: wAttributes |= FOREGROUND_GREEN | FOREGROUND_BLUE; break; 570 case CROSSLINE_FGCOLOR_WHITE: wAttributes |= FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE;break; 571 } 572 } 573 if (CROSSLINE_BGCOLOR_DEFAULT == (color&CROSSLINE_BGCOLOR_MASK)) { 574 wAttributes |= dft_wAttributes & (BACKGROUND_RED | BACKGROUND_GREEN | BACKGROUND_BLUE | BACKGROUND_INTENSITY); 575 } else { 576 wAttributes |= (color&CROSSLINE_BGCOLOR_BRIGHT) ? BACKGROUND_INTENSITY : 0; 577 switch (color&CROSSLINE_BGCOLOR_MASK) { 578 case CROSSLINE_BGCOLOR_RED: wAttributes |= BACKGROUND_RED; break; 579 case CROSSLINE_BGCOLOR_GREEN: wAttributes |= BACKGROUND_GREEN;break; 580 case CROSSLINE_BGCOLOR_BLUE: wAttributes |= BACKGROUND_BLUE; break; 581 case CROSSLINE_BGCOLOR_YELLOW: wAttributes |= BACKGROUND_RED | BACKGROUND_GREEN; break; 582 case CROSSLINE_BGCOLOR_MAGENTA: wAttributes |= BACKGROUND_RED | BACKGROUND_BLUE; break; 583 case CROSSLINE_BGCOLOR_CYAN: wAttributes |= BACKGROUND_GREEN | BACKGROUND_BLUE; break; 584 case CROSSLINE_BGCOLOR_WHITE: wAttributes |= BACKGROUND_RED | BACKGROUND_GREEN | BACKGROUND_BLUE;break; 585 } 586 } 587 if (color & CROSSLINE_UNDERLINE) 588 { wAttributes |= COMMON_LVB_UNDERSCORE; } 589 SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), wAttributes); 590 } 591 592 #else // Linux 593 594 int crossline_getch () 595 { 596 char ch = 0; 597 struct termios old_term, cur_term; 598 fflush (stdout); 599 if (tcgetattr(STDIN_FILENO, &old_term) < 0) { perror("tcsetattr"); } 600 cur_term = old_term; 601 cur_term.c_lflag &= ~(ICANON | ECHO | ISIG); // echoing off, canonical off, no signal chars 602 cur_term.c_cc[VMIN] = 1; 603 cur_term.c_cc[VTIME] = 0; 604 if (tcsetattr(STDIN_FILENO, TCSANOW, &cur_term) < 0) { perror("tcsetattr"); } 605 if (read(STDIN_FILENO, &ch, 1) < 0) { /* perror("read()"); */ } // signal will interrupt 606 if (tcsetattr(STDIN_FILENO, TCSADRAIN, &old_term) < 0) { perror("tcsetattr"); } 607 return ch; 608 } 609 void crossline_screen_get (int *pRows, int *pCols) 610 { 611 struct winsize ws = {}; 612 (void)ioctl (1, TIOCGWINSZ, &ws); 613 *pCols = ws.ws_col; 614 *pRows = ws.ws_row; 615 *pCols = *pCols > 1 ? *pCols : 160; 616 *pRows = *pRows > 1 ? *pRows : 24; 617 } 618 int crossline_cursor_get (int *pRow, int *pCol) 619 { 620 int i; 621 char buf[32]; 622 printf ("\e[6n"); 623 for (i = 0; i < (char)sizeof(buf)-1; ++i) { 624 buf[i] = (char)crossline_getch (); 625 if ('R' == buf[i]) { break; } 626 } 627 buf[i] = '\0'; 628 if (2 != sscanf (buf, "\e[%d;%dR", pRow, pCol)) { return -1; } 629 (*pRow)--; (*pCol)--; 630 return 0; 631 } 632 void crossline_cursor_set (int row, int col) 633 { 634 printf("\e[%d;%dH", row+1, col+1); 635 } 636 void crossline_cursor_move (int row_off, int col_off) 637 { 638 if (col_off > 0) { printf ("\e[%dC", col_off); } 639 else if (col_off < 0) { printf ("\e[%dD", -col_off); } 640 if (row_off > 0) { printf ("\e[%dB", row_off); } 641 else if (row_off < 0) { printf ("\e[%dA", -row_off); } 642 } 643 void crossline_cursor_hide (int bHide) 644 { 645 printf("\e[?25%c", bHide?'l':'h'); 646 } 647 648 void crossline_color_set (crossline_color_e color) 649 { 650 if (!isatty(STDOUT_FILENO)) { return; } 651 printf ("\033[m"); 652 if (CROSSLINE_FGCOLOR_DEFAULT != (color&CROSSLINE_FGCOLOR_MASK)) 653 { printf ("\033[%dm", 29 + (color&CROSSLINE_FGCOLOR_MASK) + ((color&CROSSLINE_FGCOLOR_BRIGHT)?60:0)); } 654 if (CROSSLINE_BGCOLOR_DEFAULT != (color&CROSSLINE_BGCOLOR_MASK)) 655 { printf ("\033[%dm", 39 + ((color&CROSSLINE_BGCOLOR_MASK)>>8) + ((color&CROSSLINE_BGCOLOR_BRIGHT)?60:0)); } 656 if (color & CROSSLINE_UNDERLINE) 657 { printf ("\033[4m"); } 658 } 659 660 #endif // #ifdef _WIN32 661 662 /*****************************************************************************/ 663 664 static void crossline_show_help (int show_search) 665 { 666 int i; 667 char **help = show_search ? s_search_help : s_crossline_help; 668 printf (" \b\n"); 669 for (i = 0; NULL != help[i]; ++i) { 670 printf ("%s\n", help[i]); 671 if (crossline_paging_check ((int)strlen(help[i])+1)) 672 { break; } 673 } 674 } 675 676 static void str_to_lower (char *str) 677 { 678 for (; '\0' != *str; ++str) 679 { *str = (char)tolower (*str); } 680 } 681 682 // Match including(no prefix) and excluding(with prefix: '-') patterns. 683 static int crossline_match_patterns (const char *str, char *word[], int num) 684 { 685 int i; 686 char buf[CROSS_HISTORY_BUF_LEN]; 687 688 strncpy (buf, str, sizeof(buf) - 1); 689 buf[sizeof(buf) - 1] = '\0'; 690 str_to_lower (buf); 691 for (i = 0; i < num; ++i) { 692 if ('-' == word[i][0]) { 693 if (NULL != strstr (buf, &word[i][1])) 694 { return 0; } 695 } else if (NULL == strstr (buf, word[i])) 696 { return 0; } 697 } 698 return 1; 699 } 700 701 // Split pattern string to individual pattern list, handle composite words embraced with " ". 702 static int crossline_split_patterns (char *patterns, char *pat_list[], int max) 703 { 704 int i, num = 0; 705 char *pch = patterns; 706 707 if (NULL == patterns) { return 0; } 708 while (' ' == *pch) { ++pch; } 709 while ((num < max) && (NULL != pch)) { 710 if (('"' == *pch) || (('-' == *pch) && ('"' == *(pch+1)))) { 711 if ('"' != *pch) { *(pch+1) = '-'; } 712 pat_list[num++] = ++pch; 713 if (NULL != (pch = strchr(pch, '"'))) { 714 *pch++ = '\0'; 715 while (' ' == *pch) { ++pch; } 716 } 717 } else { 718 pat_list[num++] = pch; 719 if (NULL != (pch = strchr (pch, ' '))) { 720 *pch = '\0'; 721 while (' ' == *(++pch)) ; 722 } 723 } 724 } 725 for (i = 0; i < num; ++i) 726 { str_to_lower (pat_list[i]); } 727 return num; 728 } 729 730 // If patterns is not NULL, will filter history. 731 // If sel_id > 0, return the real id+1 in history buf, else return history number dumped. 732 static int crossline_history_dump (FILE *file, int print_id, char *patterns, int sel_id, int paging) 733 { 734 uint32_t i; 735 int id = 0, num=0; 736 char *pat_list[CROSS_HIS_MATCH_PAT_NUM], *history; 737 738 num = crossline_split_patterns (patterns, pat_list, CROSS_HIS_MATCH_PAT_NUM); 739 for (i = s_history_id; i < s_history_id + CROSS_HISTORY_MAX_LINE; ++i) { 740 history = s_history_buf[i % CROSS_HISTORY_MAX_LINE]; 741 if ('\0' != history[0]) { 742 if ((NULL != patterns) && !crossline_match_patterns (history, pat_list, num)) 743 { continue; } 744 if (sel_id > 0) { 745 if (++id == sel_id) 746 { return (i % CROSS_HISTORY_MAX_LINE) + 1; } 747 continue; 748 } 749 if (print_id) { fprintf (file, "%4d %s\n", ++id, history); } 750 else { fprintf (file, "%s\n", history); } 751 if (paging) { 752 if (crossline_paging_check ((int)strlen(history)+(print_id?7:1))) 753 { break; } 754 } 755 } 756 } 757 return id; 758 } 759 760 // Search history, input will be initial search patterns. 761 static int crossline_history_search (char *input) 762 { 763 uint32_t his_id = 0, count; 764 char pattern[CROSS_HISTORY_BUF_LEN], buf[8] = "1"; 765 766 printf (" \b\n"); 767 if (NULL != input) { 768 strncpy (pattern, input, sizeof(pattern) - 1); 769 pattern[sizeof(pattern) - 1] = '\0'; 770 } 771 // Get search patterns 772 if (NULL == crossline_readline_edit(pattern, sizeof (pattern), "Input Patterns <F1> help: ", (NULL!=input), 1)) 773 { return 0; } 774 strncpy (s_clip_buf, pattern, sizeof(s_clip_buf) - 1); 775 s_clip_buf[sizeof(s_clip_buf) - 1] = '\0'; 776 count = crossline_history_dump (stdout, 1, pattern, 0, 1); 777 if (0 == count) { return 0; } // Nothing found, just return 778 // Get choice 779 if (NULL == crossline_readline_edit (buf, sizeof (buf), "Input history id: ", (1==count), 1)) 780 { return 0; } 781 his_id = atoi (buf); 782 if (('\0' != buf[0]) && ((his_id > count) || (his_id <= 0))) { 783 printf ("Invalid history id: %s\n", buf); 784 return 0; 785 } 786 return crossline_history_dump (stdout, 1, pattern, his_id, 0); 787 } 788 789 // Show completions returned by callback. 790 static int crossline_show_completions (crossline_completions_t *pCompletions) 791 { 792 int i, j, ret = 0, word_len = 0, with_help = 0, rows, cols, word_num; 793 794 if (('\0' != pCompletions->hints[0]) || (pCompletions->num > 0)) { 795 printf (" \b\n"); 796 ret = 1; 797 } 798 // Print syntax hints. 799 if ('\0' != pCompletions->hints[0]) { 800 printf ("Please input: "); 801 crossline_color_set (pCompletions->color_hints); 802 printf ("%s", pCompletions->hints); 803 crossline_color_set (CROSSLINE_COLOR_DEFAULT); 804 printf ("\n"); 805 } 806 if (0 == pCompletions->num) { return ret; } 807 for (i = 0; i < pCompletions->num; ++i) { 808 if ((int)strlen(pCompletions->word[i]) > word_len) 809 { word_len = (int)strlen(pCompletions->word[i]); } 810 if ('\0' != pCompletions->help[i][0]) { with_help = 1; } 811 } 812 if (with_help) { 813 // Print words with help format. 814 for (i = 0; i < pCompletions->num; ++i) { 815 crossline_color_set (pCompletions->color_word[i]); 816 printf ("%s", pCompletions->word[i]); 817 for (j = 0; j < 4+word_len-(int)strlen(pCompletions->word[i]); ++j) 818 { printf (" "); } 819 crossline_color_set (pCompletions->color_help[i]); 820 printf ("%s", pCompletions->help[i]); 821 crossline_color_set (CROSSLINE_COLOR_DEFAULT); 822 printf ("\n"); 823 if (crossline_paging_check((int)strlen(pCompletions->help[i])+4+word_len+1)) 824 { break; } 825 } 826 return ret; 827 } 828 829 // Print words list in multiple columns. 830 crossline_screen_get (&rows, &cols); 831 word_num = (cols - 1 - word_len) / (word_len + 4) + 1; 832 for (i = 1; i <= pCompletions->num; ++i) { 833 crossline_color_set (pCompletions->color_word[i-1]); 834 printf ("%s", pCompletions->word[i-1]); 835 crossline_color_set (CROSSLINE_COLOR_DEFAULT); 836 for (j = 0; j < ((i%word_num)?4:0)+word_len-(int)strlen(pCompletions->word[i-1]); ++j) 837 { printf (" "); } 838 if (0 == (i % word_num)) { 839 printf ("\n"); 840 if (crossline_paging_check (word_len)) 841 { return ret; } 842 } 843 } 844 845 if (pCompletions->num % word_num) { printf ("\n"); } 846 return ret; 847 } 848 849 static int crossline_updown_move (const char *prompt, int *pCurPos, int *pCurNum, int off, int bForce) 850 { 851 int rows, cols, len = (int)strlen(prompt), cur_pos=*pCurPos; 852 crossline_screen_get (&rows, &cols); 853 if (!bForce && (*pCurPos == *pCurNum)) { return 0; } // at end of last line 854 if (off < 0) { 855 if ((*pCurPos+len)/cols == 0) { return 0; } // at first line 856 *pCurPos -= cols; 857 if (*pCurPos < 0) { *pCurPos = 0; } 858 crossline_cursor_move (-1, (*pCurPos+len)%cols-(cur_pos+len)%cols); 859 } else { 860 if ((*pCurPos+len)/cols == (*pCurNum+len)/cols) { return 0; } // at last line 861 *pCurPos += cols; 862 if (*pCurPos > *pCurNum) { *pCurPos = *pCurNum - 1; } // one char left to avoid history shortcut 863 crossline_cursor_move (1, (*pCurPos+len)%cols-(cur_pos+len)%cols); 864 } 865 return 1; 866 } 867 868 // Refreash current print line and move cursor to new_pos. 869 static void crossline_refreash (const char *prompt, char *buf, int *pCurPos, int *pCurNum, int new_pos, int new_num, int bChg) 870 { 871 int i, pos_row, pos_col, len = (int)strlen(prompt); 872 static int rows = 0, cols = 0; 873 874 if (bChg || !rows || s_crossline_win) { crossline_screen_get (&rows, &cols); } 875 if (!bChg) { // just move cursor 876 pos_row = (new_pos+len)/cols - (*pCurPos+len)/cols; 877 pos_col = (new_pos+len)%cols - (*pCurPos+len)%cols; 878 crossline_cursor_move (pos_row, pos_col); 879 } else { 880 buf[new_num] = '\0'; 881 if (bChg > 1) { // refreash as less as possbile 882 printf ("%s", &buf[bChg-1]); 883 } else { 884 pos_row = (*pCurPos + len) / cols; 885 crossline_cursor_move (-pos_row, 0); 886 crossline_color_set (s_prompt_color); 887 printf ("\r%s", prompt); 888 crossline_color_set (CROSSLINE_COLOR_DEFAULT); 889 printf ("%s", buf); 890 } 891 if (!s_crossline_win && new_num>0 && !((new_num+len)%cols)) { printf("\n"); } 892 for (i=*pCurNum-new_num; i > 0; --i) { printf (" "); } 893 if (!s_crossline_win && *pCurNum>new_num && !((*pCurNum+len)%cols)) { printf("\n"); } 894 pos_row = (new_num+len)/cols - (*pCurNum+len)/cols; 895 if (pos_row < 0) { crossline_cursor_move (pos_row, 0); } 896 printf ("\r"); 897 pos_row = (new_pos+len)/cols - (new_num+len)/cols; 898 crossline_cursor_move (pos_row, (new_pos+len)%cols); 899 } 900 *pCurPos = new_pos; 901 *pCurNum = new_num; 902 } 903 904 static void crossline_print (const char *prompt, char *buf, int *pCurPos, int *pCurNum, int new_pos, int new_num) 905 { 906 *pCurPos = *pCurNum = 0; 907 crossline_refreash (prompt, buf, pCurPos, pCurNum, new_pos, new_num, 1); 908 } 909 910 // Copy part text[cut_beg, cut_end] from src to dest 911 static void crossline_text_copy (char *dest, const char *src, int cut_beg, int cut_end) 912 { 913 int len = cut_end - cut_beg; 914 len = (len < CROSS_HISTORY_BUF_LEN) ? len : (CROSS_HISTORY_BUF_LEN - 1); 915 if (len > 0) { 916 memcpy (dest, &src[cut_beg], len); 917 dest[len] = '\0'; 918 } 919 } 920 921 // Copy from history buffer to dest 922 static void crossline_history_copy (const char *prompt, char *buf, int size, int *pos, int *num, int history_id) 923 { 924 strncpy (buf, s_history_buf[history_id % CROSS_HISTORY_MAX_LINE], size - 1); 925 buf[size - 1] = '\0'; 926 crossline_refreash (prompt, buf, pos, num, (int)strlen(buf), (int)strlen(buf), 1); 927 } 928 929 /*****************************************************************************/ 930 931 // Convert ESC+Key to Alt-Key 932 static int crossline_key_esc2alt (int ch) 933 { 934 switch (ch) { 935 case KEY_DEL: ch = KEY_ALT_DEL; break; 936 case KEY_HOME: ch = KEY_ALT_HOME; break; 937 case KEY_END: ch = KEY_ALT_END; break; 938 case KEY_UP: ch = KEY_ALT_UP; break; 939 case KEY_DOWN: ch = KEY_ALT_DOWN; break; 940 case KEY_LEFT: ch = KEY_ALT_LEFT; break; 941 case KEY_RIGHT: ch = KEY_ALT_RIGHT; break; 942 case KEY_BACKSPACE: ch = KEY_ALT_BACKSPACE; break; 943 } 944 return ch; 945 } 946 947 // Map other function keys to main key 948 static int crossline_key_mapping (int ch) 949 { 950 switch (ch) { 951 #ifndef _WIN32 952 case KEY_HOME2: ch = KEY_HOME; break; 953 case KEY_END2: ch = KEY_END; break; 954 case KEY_CTRL_UP2: ch = KEY_CTRL_UP; break; 955 case KEY_CTRL_DOWN2: ch = KEY_CTRL_DOWN; break; 956 case KEY_CTRL_LEFT2: ch = KEY_CTRL_LEFT; break; 957 case KEY_CTRL_RIGHT2: ch = KEY_CTRL_RIGHT; break; 958 case KEY_F1_2: ch = KEY_F1; break; 959 case KEY_F2_2: ch = KEY_F2; break; 960 case KEY_F3_2: ch = KEY_F3; break; 961 case KEY_F4_2: ch = KEY_F4; break; 962 #endif 963 case KEY_DEL2: ch = KEY_BACKSPACE; break; 964 } 965 return ch; 966 } 967 968 #ifdef _WIN32 // Windows 969 // Read a KEY from keyboard, is_esc indicats whether it's a function key. 970 static int crossline_getkey (int *is_esc) 971 { 972 int ch = crossline_getch (), esc; 973 if ((GetKeyState (VK_CONTROL) & 0x8000) && (KEY_DEL2 == ch)) { 974 ch = KEY_CTRL_BACKSPACE; 975 } else if ((224 == ch) || (0 == ch)) { 976 *is_esc = 1; 977 ch = crossline_getch (); 978 ch = (GetKeyState (VK_MENU) & 0x8000) ? ALT_KEY(ch) : ch + (KEY_ESC<<8); 979 } else if (KEY_ESC == ch) { // Handle ESC+Key 980 *is_esc = 1; 981 ch = crossline_getkey (&esc); 982 ch = crossline_key_esc2alt (ch); 983 } else if (GetKeyState (VK_MENU) & 0x8000 && !(GetKeyState (VK_CONTROL) & 0x8000) ) { 984 *is_esc = 1; ch = ALT_KEY(ch); 985 } 986 return ch; 987 } 988 989 void crossline_winchg_reg (void) { } 990 991 #else // Linux 992 993 // Convert escape sequences to internal special function key 994 static int crossline_get_esckey (int ch) 995 { 996 int ch2; 997 if (0 == ch) { ch = crossline_getch (); } 998 if ('[' == ch) { 999 ch = crossline_getch (); 1000 if ((ch>='0') && (ch<='6')) { 1001 ch2 = crossline_getch (); 1002 if ('~' == ch2) { ch = ESC_KEY4 (ch, ch2); } // ex. Esc[4~ 1003 else if (';' == ch2) { 1004 ch2 = crossline_getch(); 1005 if (('5' != ch2) && ('3' != ch2)) 1006 { return 0; } 1007 ch = ESC_KEY6 (ch, ch2, crossline_getch()); // ex. Esc[1;5B 1008 } 1009 } else if ('[' == ch) { 1010 ch = ESC_KEY4 ('[', crossline_getch()); // ex. Esc[[A 1011 } else { ch = ESC_KEY3 (ch); } // ex. Esc[A 1012 } else if ('O' == ch) { 1013 ch = ESC_OKEY (crossline_getch()); // ex. EscOP 1014 } else { ch = ALT_KEY (ch); } // ex. Alt+Backspace 1015 return ch; 1016 } 1017 1018 // Read a KEY from keyboard, is_esc indicats whether it's a function key. 1019 static int crossline_getkey (int *is_esc) 1020 { 1021 int ch = crossline_getch(); 1022 if (KEY_ESC == ch) { 1023 *is_esc = 1; 1024 ch = crossline_getch (); 1025 if (KEY_ESC == ch) { // Handle ESC+Key 1026 ch = crossline_get_esckey (0); 1027 ch = crossline_key_mapping (ch); 1028 ch = crossline_key_esc2alt (ch); 1029 } else { ch = crossline_get_esckey (ch); } 1030 } 1031 return ch; 1032 } 1033 1034 static void crossline_winchg_event (int arg) 1035 { s_got_resize = 1; } 1036 static void crossline_winchg_reg (void) 1037 { 1038 struct sigaction sa; 1039 sigemptyset(&sa.sa_mask); 1040 sa.sa_flags = 0; 1041 sa.sa_handler = &crossline_winchg_event; 1042 sigaction (SIGWINCH, &sa, NULL); 1043 s_got_resize = 0; 1044 } 1045 1046 #endif // #ifdef _WIN32 1047 1048 /*****************************************************************************/ 1049 1050 /* Internal readline from terminal. has_input indicates buf has inital input. 1051 * in_his will disable history and complete shortcuts 1052 */ 1053 static char* crossline_readline_edit (char *buf, int size, const char *prompt, int has_input, int in_his) 1054 { 1055 int pos = 0, num = 0, read_end = 0, is_esc; 1056 int ch, len, new_pos, copy_buf = 0, i, len2; 1057 uint32_t history_id = s_history_id, search_his; 1058 char input[CROSS_HISTORY_BUF_LEN]; 1059 crossline_completions_t completions; 1060 1061 prompt = (NULL != prompt) ? prompt : ""; 1062 if (has_input) { 1063 num = pos = (int)strlen (buf); 1064 crossline_text_copy (input, buf, pos, num); 1065 } else 1066 { buf[0] = input[0] = '\0'; } 1067 crossline_print (prompt, buf, &pos, &num, pos, num); 1068 crossline_winchg_reg (); 1069 1070 do { 1071 is_esc = 0; 1072 ch = crossline_getkey (&is_esc); 1073 ch = crossline_key_mapping (ch); 1074 1075 if (s_got_resize) { // Handle window resizing for Linux, Windows can handle it automatically 1076 new_pos = pos; 1077 crossline_refreash (prompt, buf, &pos, &num, 0, num, 0); // goto beginning of line 1078 printf ("\x1b[J"); // clear to end of screen 1079 crossline_refreash (prompt, buf, &pos, &num, new_pos, num, 1); 1080 s_got_resize = 0; 1081 } 1082 1083 switch (ch) { 1084 /* Misc Commands */ 1085 case KEY_F1: // Show help 1086 crossline_show_help (in_his); 1087 crossline_print (prompt, buf, &pos, &num, pos, num); 1088 break; 1089 1090 case KEY_DEBUG: // Enter keyboard debug mode 1091 printf(" \b\nEnter keyboard debug mode, <Ctrl-C> to exit debug\n"); 1092 while (CTRL_KEY('C') != (ch=crossline_getch())) 1093 { printf ("%3d 0x%02x (%c)\n", ch, ch, isprint(ch) ? ch : ' '); } 1094 crossline_print (prompt, buf, &pos, &num, pos, num); 1095 break; 1096 1097 /* Move Commands */ 1098 case KEY_LEFT: // Move back a character. 1099 case CTRL_KEY('B'): 1100 if (pos > 0) 1101 { crossline_refreash (prompt, buf, &pos, &num, pos-1, num, 0); } 1102 break; 1103 1104 case KEY_RIGHT: // Move forward a character. 1105 case CTRL_KEY('F'): 1106 if (pos < num) 1107 { crossline_refreash (prompt, buf, &pos, &num, pos+1, num, 0); } 1108 break; 1109 1110 case ALT_KEY('b'): // Move back a word. 1111 case ALT_KEY('B'): 1112 case KEY_CTRL_LEFT: 1113 case KEY_ALT_LEFT: 1114 for (new_pos=pos-1; (new_pos > 0) && isdelim(buf[new_pos]); --new_pos) ; 1115 for (; (new_pos > 0) && !isdelim(buf[new_pos]); --new_pos) ; 1116 crossline_refreash (prompt, buf, &pos, &num, new_pos?new_pos+1:new_pos, num, 0); 1117 break; 1118 1119 case ALT_KEY('f'): // Move forward a word. 1120 case ALT_KEY('F'): 1121 case KEY_CTRL_RIGHT: 1122 case KEY_ALT_RIGHT: 1123 for (new_pos=pos; (new_pos < num) && isdelim(buf[new_pos]); ++new_pos) ; 1124 for (; (new_pos < num) && !isdelim(buf[new_pos]); ++new_pos) ; 1125 crossline_refreash (prompt, buf, &pos, &num, new_pos, num, 0); 1126 break; 1127 1128 case CTRL_KEY('A'): // Move cursor to start of line. 1129 case KEY_HOME: 1130 crossline_refreash (prompt, buf, &pos, &num, 0, num, 0); 1131 break; 1132 1133 case CTRL_KEY('E'): // Move cursor to end of line 1134 case KEY_END: 1135 crossline_refreash (prompt, buf, &pos, &num, num, num, 0); 1136 break; 1137 1138 case CTRL_KEY('L'): // Clear screen and redisplay line 1139 crossline_screen_clear (); 1140 crossline_print (prompt, buf, &pos, &num, pos, num); 1141 break; 1142 1143 case KEY_CTRL_UP: // Move to up line 1144 case KEY_ALT_UP: 1145 crossline_updown_move (prompt, &pos, &num, -1, 1); 1146 break; 1147 1148 case KEY_ALT_DOWN: // Move to down line 1149 case KEY_CTRL_DOWN: 1150 crossline_updown_move (prompt, &pos, &num, 1, 1); 1151 break; 1152 1153 /* Edit Commands */ 1154 case KEY_BACKSPACE: // Delete char to left of cursor (same with CTRL_KEY('H')) 1155 if (pos > 0) { 1156 memmove (&buf[pos-1], &buf[pos], num - pos); 1157 crossline_refreash (prompt, buf, &pos, &num, pos-1, num-1, 1); 1158 } 1159 break; 1160 1161 case KEY_DEL: // Delete character under cursor 1162 case CTRL_KEY('D'): 1163 if (pos < num) { 1164 memmove (&buf[pos], &buf[pos+1], num - pos - 1); 1165 crossline_refreash (prompt, buf, &pos, &num, pos, num - 1, 1); 1166 } else if ((0 == num) && (ch == CTRL_KEY('D'))) // On an empty line, EOF 1167 { printf (" \b\n"); read_end = -1; } 1168 break; 1169 1170 case ALT_KEY('u'): // Uppercase current or following word. 1171 case ALT_KEY('U'): 1172 for (new_pos = pos; (new_pos < num) && isdelim(buf[new_pos]); ++new_pos) ; 1173 for (; (new_pos < num) && !isdelim(buf[new_pos]); ++new_pos) 1174 { buf[new_pos] = (char)toupper (buf[new_pos]); } 1175 crossline_refreash (prompt, buf, &pos, &num, new_pos, num, 1); 1176 break; 1177 1178 case ALT_KEY('l'): // Lowercase current or following word. 1179 case ALT_KEY('L'): 1180 for (new_pos = pos; (new_pos < num) && isdelim(buf[new_pos]); ++new_pos) ; 1181 for (; (new_pos < num) && !isdelim(buf[new_pos]); ++new_pos) 1182 { buf[new_pos] = (char)tolower (buf[new_pos]); } 1183 crossline_refreash (prompt, buf, &pos, &num, new_pos, num, 1); 1184 break; 1185 1186 case ALT_KEY('c'): // Capitalize current or following word. 1187 case ALT_KEY('C'): 1188 for (new_pos = pos; (new_pos < num) && isdelim(buf[new_pos]); ++new_pos) ; 1189 if (new_pos<num) 1190 { buf[new_pos] = (char)toupper (buf[new_pos]); } 1191 for (; new_pos<num && !isdelim(buf[new_pos]); ++new_pos) ; 1192 crossline_refreash (prompt, buf, &pos, &num, new_pos, num, 1); 1193 break; 1194 1195 case ALT_KEY('\\'): // Delete whitespace around cursor. 1196 for (new_pos = pos; (new_pos > 0) && (' ' == buf[new_pos]); --new_pos) ; 1197 memmove (&buf[new_pos], &buf[pos], num - pos); 1198 crossline_refreash (prompt, buf, &pos, &num, new_pos, num - (pos-new_pos), 1); 1199 for (new_pos = pos; (new_pos < num) && (' ' == buf[new_pos]); ++new_pos) ; 1200 memmove (&buf[pos], &buf[new_pos], num - new_pos); 1201 crossline_refreash (prompt, buf, &pos, &num, pos, num - (new_pos-pos), 1); 1202 break; 1203 1204 case CTRL_KEY('T'): // Transpose previous character with current character. 1205 if ((pos > 0) && !isdelim(buf[pos]) && !isdelim(buf[pos-1])) { 1206 ch = buf[pos]; 1207 buf[pos] = buf[pos-1]; 1208 buf[pos-1] = (char)ch; 1209 crossline_refreash (prompt, buf, &pos, &num, pos<num?pos+1:pos, num, 1); 1210 } else if ((pos > 1) && !isdelim(buf[pos-1]) && !isdelim(buf[pos-2])) { 1211 ch = buf[pos-1]; 1212 buf[pos-1] = buf[pos-2]; 1213 buf[pos-2] = (char)ch; 1214 crossline_refreash (prompt, buf, &pos, &num, pos, num, 1); 1215 } 1216 break; 1217 1218 /* Cut&Paste Commands */ 1219 case CTRL_KEY('K'): // Cut from cursor to end of line. 1220 case KEY_CTRL_END: 1221 case KEY_ALT_END: 1222 crossline_text_copy (s_clip_buf, buf, pos, num); 1223 crossline_refreash (prompt, buf, &pos, &num, pos, pos, 1); 1224 break; 1225 1226 case CTRL_KEY('U'): // Cut from start of line to cursor. 1227 case KEY_CTRL_HOME: 1228 case KEY_ALT_HOME: 1229 crossline_text_copy (s_clip_buf, buf, 0, pos); 1230 memmove (&buf[0], &buf[pos], num-pos); 1231 crossline_refreash (prompt, buf, &pos, &num, 0, num - pos, 1); 1232 break; 1233 1234 case CTRL_KEY('X'): // Cut whole line. 1235 crossline_text_copy (s_clip_buf, buf, 0, num); 1236 // fall through 1237 case ALT_KEY('r'): // Revert line 1238 case ALT_KEY('R'): 1239 crossline_refreash (prompt, buf, &pos, &num, 0, 0, 1); 1240 break; 1241 1242 case CTRL_KEY('W'): // Cut whitespace (not word) to left of cursor. 1243 case KEY_ALT_BACKSPACE: // Cut word to left of cursor. 1244 case KEY_CTRL_BACKSPACE: 1245 new_pos = pos; 1246 if ((new_pos > 1) && isdelim(buf[new_pos-1])) { --new_pos; } 1247 for (; (new_pos > 0) && isdelim(buf[new_pos]); --new_pos) ; 1248 if (CTRL_KEY('W') == ch) { 1249 for (; (new_pos > 0) && (' ' != buf[new_pos]); --new_pos) ; 1250 } else { 1251 for (; (new_pos > 0) && !isdelim(buf[new_pos]); --new_pos) ; 1252 } 1253 if ((new_pos>0) && (new_pos<pos) && isdelim(buf[new_pos])) { new_pos++; } 1254 crossline_text_copy (s_clip_buf, buf, new_pos, pos); 1255 memmove (&buf[new_pos], &buf[pos], num - pos); 1256 crossline_refreash (prompt, buf, &pos, &num, new_pos, num - (pos-new_pos), 1); 1257 break; 1258 1259 case ALT_KEY('d'): // Cut word following cursor. 1260 case ALT_KEY('D'): 1261 case KEY_ALT_DEL: 1262 case KEY_CTRL_DEL: 1263 for (new_pos = pos; (new_pos < num) && isdelim(buf[new_pos]); ++new_pos) ; 1264 for (; (new_pos < num) && !isdelim(buf[new_pos]); ++new_pos) ; 1265 crossline_text_copy (s_clip_buf, buf, pos, new_pos); 1266 memmove (&buf[pos], &buf[new_pos], num - new_pos); 1267 crossline_refreash (prompt, buf, &pos, &num, pos, num - (new_pos-pos), 1); 1268 break; 1269 1270 case CTRL_KEY('Y'): // Paste last cut text. 1271 case CTRL_KEY('V'): 1272 case KEY_INSERT: 1273 if ((len=(int)strlen(s_clip_buf)) + num < size) { 1274 memmove (&buf[pos+len], &buf[pos], num - pos); 1275 memcpy (&buf[pos], s_clip_buf, len); 1276 crossline_refreash (prompt, buf, &pos, &num, pos+len, num+len, 1); 1277 } 1278 break; 1279 1280 /* Complete Commands */ 1281 case KEY_TAB: // Autocomplete (same with CTRL_KEY('I')) 1282 case ALT_KEY('='): // List possible completions. 1283 case ALT_KEY('?'): 1284 if (in_his || (NULL == s_completion_callback) || (pos != num)) 1285 { break; } 1286 buf[pos] = '\0'; 1287 completions.num = 0; 1288 completions.hints[0] = '\0'; 1289 s_completion_callback (buf, &completions); 1290 if (completions.num >= 1) { 1291 if (KEY_TAB == ch) { 1292 len2 = len = (int)strlen(completions.word[0]); 1293 // Find common string for autocompletion 1294 for (i = 1; (i < completions.num) && (len > 0); ++i) { 1295 while ((len > 0) && strncasecmp(completions.word[0], completions.word[i], len)) { len--; } 1296 } 1297 if (len > 0) { 1298 if (len2 > num) len2 = num; 1299 while ((len2 > 0) && strncasecmp(completions.word[0], &buf[num-len2], len2)) { len2--; } 1300 new_pos = num - len2; 1301 if (new_pos+i+1 < size) { 1302 for (i = 0; i < len; ++i) { buf[new_pos+i] = completions.word[0][i]; } 1303 if (1 == completions.num) { buf[new_pos + (i++)] = ' '; } 1304 crossline_refreash (prompt, buf, &pos, &num, new_pos+i, new_pos+i, 1); 1305 } 1306 } 1307 } 1308 } 1309 if (((completions.num != 1) || (KEY_TAB != ch)) && crossline_show_completions(&completions)) 1310 { crossline_print (prompt, buf, &pos, &num, pos, num); } 1311 break; 1312 1313 /* History Commands */ 1314 case KEY_UP: // Fetch previous line in history. 1315 if (crossline_updown_move (prompt, &pos, &num, -1, 0)) { break; } // check multi line move up 1316 case CTRL_KEY('P'): 1317 if (in_his) { break; } 1318 if (!copy_buf) 1319 { crossline_text_copy (input, buf, 0, num); copy_buf = 1; } 1320 if ((history_id > 0) && (history_id+CROSS_HISTORY_MAX_LINE > s_history_id)) 1321 { crossline_history_copy (prompt, buf, size, &pos, &num, --history_id); } 1322 break; 1323 1324 case KEY_DOWN: // Fetch next line in history. 1325 if (crossline_updown_move (prompt, &pos, &num, 1, 0)) { break; } // check multi line move down 1326 case CTRL_KEY('N'): 1327 if (in_his) { break; } 1328 if (!copy_buf) 1329 { crossline_text_copy (input, buf, 0, num); copy_buf = 1; } 1330 if (history_id+1 < s_history_id) 1331 { crossline_history_copy (prompt, buf, size, &pos, &num, ++history_id); } 1332 else { 1333 history_id = s_history_id; 1334 strncpy (buf, input, size - 1); 1335 buf[size - 1] = '\0'; 1336 crossline_refreash (prompt, buf, &pos, &num, (int)strlen(buf), (int)strlen(buf), 1); 1337 } 1338 break; //case UP/DOWN 1339 1340 case ALT_KEY('<'): // Move to first line in history. 1341 case KEY_PGUP: 1342 if (in_his) { break; } 1343 if (!copy_buf) 1344 { crossline_text_copy (input, buf, 0, num); copy_buf = 1; } 1345 if (s_history_id > 0) { 1346 history_id = s_history_id < CROSS_HISTORY_MAX_LINE ? 0 : s_history_id-CROSS_HISTORY_MAX_LINE; 1347 crossline_history_copy (prompt, buf, size, &pos, &num, history_id); 1348 } 1349 break; 1350 1351 case ALT_KEY('>'): // Move to end of input history. 1352 case KEY_PGDN: 1353 if (in_his) { break; } 1354 if (!copy_buf) 1355 { crossline_text_copy (input, buf, 0, num); copy_buf = 1; } 1356 history_id = s_history_id; 1357 strncpy (buf, input, size-1); 1358 buf[size-1] = '\0'; 1359 crossline_refreash (prompt, buf, &pos, &num, (int)strlen(buf), (int)strlen(buf), 1); 1360 break; 1361 1362 case CTRL_KEY('R'): // Search history 1363 case CTRL_KEY('S'): 1364 case KEY_F4: // Search history with current input. 1365 if (in_his) { break; } 1366 crossline_text_copy (input, buf, 0, num); 1367 search_his = crossline_history_search ((KEY_F4 == ch) ? buf : NULL); 1368 if (search_his > 0) 1369 { strncpy (buf, s_history_buf[search_his-1], size-1); } 1370 else { strncpy (buf, input, size-1); } 1371 buf[size-1] = '\0'; 1372 crossline_print (prompt, buf, &pos, &num, (int)strlen(buf), (int)strlen(buf)); 1373 break; 1374 1375 case KEY_F2: // Show history 1376 if (in_his || (0 == s_history_id)) { break; } 1377 printf (" \b\n"); 1378 crossline_history_show (); 1379 crossline_print (prompt, buf, &pos, &num, pos, num); 1380 break; 1381 1382 case KEY_F3: // Clear history 1383 if (in_his) { break; } 1384 printf(" \b\n!!! Confirm to clear history [y]: "); 1385 if ('y' == crossline_getch()) { 1386 printf(" \b\nHistory are cleared!"); 1387 crossline_history_clear (); 1388 history_id = 0; 1389 } 1390 printf (" \b\n"); 1391 crossline_print (prompt, buf, &pos, &num, pos, num); 1392 break; 1393 1394 /* Control Commands */ 1395 case KEY_ENTER: // Accept line (same with CTRL_KEY('M')) 1396 case KEY_ENTER2: // same with CTRL_KEY('J') 1397 crossline_refreash (prompt, buf, &pos, &num, num, num, 0); 1398 printf (" \b\n"); 1399 read_end = 1; 1400 break; 1401 1402 case CTRL_KEY('C'): // Abort line. 1403 case CTRL_KEY('G'): 1404 crossline_refreash (prompt, buf, &pos, &num, num, num, 0); 1405 if (CTRL_KEY('C') == ch) { printf (" \b^C\n"); } 1406 else { printf (" \b\n"); } 1407 num = pos = 0; 1408 errno = EAGAIN; 1409 read_end = -1; 1410 break;; 1411 1412 case CTRL_KEY('Z'): 1413 #ifndef _WIN32 1414 raise(SIGSTOP); // Suspend current process 1415 crossline_print (prompt, buf, &pos, &num, pos, num); 1416 #endif 1417 break; 1418 1419 default: 1420 if (!is_esc && isprint(ch) && (num < size-1)) { 1421 memmove (&buf[pos+1], &buf[pos], num - pos); 1422 buf[pos] = (char)ch; 1423 crossline_refreash (prompt, buf, &pos, &num, pos+1, num+1, pos+1); 1424 copy_buf = 0; 1425 } 1426 break; 1427 } // switch( ch ) 1428 fflush(stdout); 1429 } while ( !read_end ); 1430 1431 if (read_end < 0) { return NULL; } 1432 if ((num > 0) && (' ' == buf[num-1])) { num--; } 1433 buf[num] = '\0'; 1434 if (!in_his && (num > 0) && strcmp(buf,"history")) { // Save history 1435 if ((0 == s_history_id) || strncmp (buf, s_history_buf[(s_history_id-1)%CROSS_HISTORY_MAX_LINE], CROSS_HISTORY_BUF_LEN)) { 1436 strncpy (s_history_buf[s_history_id % CROSS_HISTORY_MAX_LINE], buf, CROSS_HISTORY_BUF_LEN); 1437 s_history_buf[s_history_id % CROSS_HISTORY_MAX_LINE][CROSS_HISTORY_BUF_LEN - 1] = '\0'; 1438 history_id = ++s_history_id; 1439 copy_buf = 0; 1440 } 1441 } 1442 1443 return buf; 1444 }