//------------------------------------------------------------------------------ void add_to_history(const char* line) { int dupe_mode; const unsigned char* c; // Maybe we shouldn't add this line to the history at all? c = (const unsigned char*)line; if (isspace(*c) && get_clink_setting_int("history_ignore_space") > 0) { return; } // Skip leading whitespace while (*c) { if (!isspace(*c)) { break; } ++c; } // Skip empty lines if (*c == '\0') { return; } // Check if the line's a duplicate of and existing history entry. dupe_mode = get_clink_setting_int("history_dupe_mode"); if (dupe_mode > 0) { int where = find_duplicate(c); if (where >= 0) { if (dupe_mode > 1) { remove_history(where); } else { return; } } } // All's well. Add the line. using_history(); add_history(line); ++g_new_history_count; }
//------------------------------------------------------------------------------ void save_history() { int max_history; char buffer[512]; get_history_file_name(buffer, sizeof(buffer)); // Get max history size. max_history = get_clink_setting_int("history_file_lines"); max_history = (max_history == 0) ? INT_MAX : max_history; if (max_history < 0) { unlink(buffer); return; } // Write new history to the file, and truncate to our maximum. if (append_history(g_new_history_count, buffer) != 0) { write_history(buffer); } if (max_history != INT_MAX) { history_truncate_file(buffer, max_history); } g_new_history_count = 0; }
//------------------------------------------------------------------------------ void save_history() { int max_history; char buffer[1024]; const char* c; if (get_clink_setting_int("persist_history") == 0) { return; } get_history_file_name(buffer, sizeof(buffer)); // Get max history size. c = rl_variable_value("history-size"); max_history = (c != NULL) ? atoi(c) : 1000; // Write new history to the file, and truncate to our maximum. if (append_history(g_new_history_count, buffer) != 0) { write_history(buffer); } history_truncate_file(buffer, max_history); }
//------------------------------------------------------------------------------ static void load_history() { char buffer[1024]; if (get_clink_setting_int("persist_history")) { get_history_file_name(buffer, sizeof(buffer)); read_history(buffer); } using_history(); }
//------------------------------------------------------------------------------ int getc_impl(FILE* stream) { int printable; int alt; int i; while (1) { wchar_t wc[2]; char utf8[4]; alt = 0; i = GETWCH_IMPL(&alt); // MSB is set if value represents a printable character. printable = (i & 0x80000000); i &= ~printable; // Treat esc like cmd.exe does - clear the line. if (i == 0x1b) { if (rl_editing_mode == emacs_mode && get_clink_setting_int("esc_clears_line")) { using_history(); rl_delete_text(0, rl_end); rl_point = 0; rl_redisplay(); continue; } } // Mask off top bits, they're used to track ALT key state. if (i < 0x80 || (i == 0xe0 && !printable)) { break; } // Convert to utf-8 and insert directly into rl's line buffer. wc[0] = (wchar_t)i; wc[1] = L'\0'; WideCharToMultiByte(CP_UTF8, 0, wc, -1, utf8, sizeof(utf8), NULL, NULL); rl_insert_text(utf8); rl_redisplay(); } alt = RL_ISSTATE(RL_STATE_MOREINPUT) ? 0 : alt; alt = alt ? 0x80 : 0; return i|alt; }
//------------------------------------------------------------------------------ static int get_setting_int(lua_State* state) { int i; const char* c; if (lua_gettop(state) == 0) { return 0; } if (lua_isnil(state, 1) || !lua_isstring(state, 1)) { return 0; } c = lua_tostring(state, 1); i = get_clink_setting_int(c); lua_pushinteger(state, i); return 1; }
//------------------------------------------------------------------------------ static void display_matches(char** matches, int match_count, int longest) { int i; char** new_matches; CONSOLE_SCREEN_BUFFER_INFO csbi; WORD text_attrib; HANDLE std_out_handle; wchar_t buffer[512]; int show_matches = 2; int match_colour; // Process matches and recalculate the longest match length. new_matches = match_display_filter(matches, match_count); longest = 0; for (i = 0; i < (match_count + 1); ++i) { int len = (int)strlen(new_matches[i]); longest = (len > longest) ? len : longest; } std_out_handle = GetStdHandle(STD_OUTPUT_HANDLE); GetConsoleScreenBufferInfo(std_out_handle, &csbi); // Get the console's current colour settings match_colour = get_clink_setting_int("match_colour"); if (match_colour == -1) { // Pick a suitable foreground colour, check fg isn't the same as bg, and set. text_attrib = csbi.wAttributes; text_attrib ^= 0x08; if ((text_attrib & 0xf0) == (text_attrib & 0x0f)) { text_attrib ^= FOREGROUND_INTENSITY; } } else { text_attrib = csbi.wAttributes & 0xf0; text_attrib |= (match_colour & 0x0f); } SetConsoleTextAttribute(std_out_handle, text_attrib); // If there's lots of matches, check with the user before displaying them // This matches readline's behaviour, which will get skipped (annoyingly) if ((rl_completion_query_items > 0) && (match_count >= rl_completion_query_items)) { DWORD written; _snwprintf( buffer, sizeof_array(buffer), L"\nDisplay all %d possibilities? (y or n)", match_count ); WriteConsoleW(std_out_handle, buffer, wcslen(buffer), &written, NULL); while (show_matches > 1) { int c = rl_read_key(); switch (c) { case 'y': case 'Y': case ' ': show_matches = 1; break; case 'n': case 'N': case 0x7f: show_matches = 0; break; } } } // Get readline to display the matches. if (show_matches > 0) { // Turn of '/' suffix for directories. RL assumes '/', which isn't the // case, plus clink uses colours instead. int j = _rl_complete_mark_directories; _rl_complete_mark_directories = 0; rl_display_match_list(new_matches, match_count, longest); _rl_complete_mark_directories = j; } else { rl_crlf(); } // Reset console colour back to normal. SetConsoleTextAttribute(std_out_handle, csbi.wAttributes); rl_forced_update_display(); rl_display_fixed = 1; // Tidy up. for (i = 0; i < match_count; ++i) { free(new_matches[i]); } free(new_matches); }
/* Taken from msvcrt.dll's getextendedkeycode() ELSE SHFT CTRL ALTS 00000000`723d36e0 1c 000d 000d 000a a600 00000000`723d36ea 35 002f 003f 9500 a400 00000000`723d36f4 47 47e0 47e0 77e0 9700 00000000`723d36fe 48 48e0 48e0 8de0 9800 00000000`723d3708 49 49e0 49e0 86e0 9900 00000000`723d3712 4b 4be0 4be0 73e0 9b00 00000000`723d371c 4d 4de0 4de0 74e0 9d00 00000000`723d3726 4f 4fe0 4fe0 75e0 9f00 00000000`723d3730 50 50e0 50e0 91e0 a000 00000000`723d373a 51 51e0 51e0 76e0 a100 00000000`723d3744 52 52e0 52e0 92e0 a200 00000000`723d374e 53 53e0 53e0 93e0 a300 home 01 00 00 00 01 00 24 00 47 00 00 00 00 00 00 00 end 01 00 00 00 01 00 23 00 4f 00 00 00 00 00 00 00 pgup 01 00 00 00 01 00 21 00 49 00 00 00 00 00 00 00 pgdn 01 00 00 00 01 00 22 00 51 00 00 00 00 00 00 00 */ static int getc_internal(int* alt) { static int carry = 0; // Multithreading? What's that? static const int CTRL_PRESSED = LEFT_CTRL_PRESSED|RIGHT_CTRL_PRESSED; int key_char; int key_vk; int key_sc; int key_flags; HANDLE handle; DWORD mode; // Clear all flags so the console doesn't do anything special. This prevents // key presses such as Ctrl-C and Ctrl-S from being swallowed. handle = GetStdHandle(STD_INPUT_HANDLE); GetConsoleMode(handle, &mode); loop: key_char = 0; key_vk = 0; key_sc = 0; key_flags = 0; *alt = 0; // Read a key or use what was carried across from a previous call. if (carry) { key_flags = ENHANCED_KEY; key_char = carry; carry = 0; } else { HANDLE handle_stdout = GetStdHandle(STD_OUTPUT_HANDLE); CONSOLE_SCREEN_BUFFER_INFO csbi; DWORD i; INPUT_RECORD record; const KEY_EVENT_RECORD* key; int altgr_sub; GetConsoleScreenBufferInfo(handle_stdout, &csbi); // Check for a new buffer size for simulated SIGWINCH signals. i = (csbi.dwSize.X << 16) | csbi.dwSize.Y; if (!g_knownBufferSize || g_knownBufferSize != i) { simulate_sigwinch(csbi.dwCursorPosition); g_knownBufferSize = i; goto loop; } // Fresh read from the console. SetConsoleMode(handle, 0); ReadConsoleInputW(handle, &record, 1, &i); if (record.EventType != KEY_EVENT) { goto loop; } key = &record.Event.KeyEvent; key_char = key->uChar.UnicodeChar; key_vk = key->wVirtualKeyCode; key_sc = key->wVirtualScanCode; key_flags = key->dwControlKeyState; #if defined(DEBUG_GETC) && defined(_DEBUG) { static int id = 0; int i; printf("\n%03d: %s ", id++, key->bKeyDown ? "+" : "-"); for (i = 2; i < sizeof(*key) / sizeof(short); ++i) { printf("%04x ", ((unsigned short*)key)[i]); } } #endif if (key->bKeyDown == FALSE) { // Some times conhost can send through ALT codes, with the resulting // Unicode code point in the Alt key-up event. if (key_vk == VK_MENU && key_char) { goto end; } goto loop; } // Windows supports an AltGr substitute which we check for here. As it // collides with Readline mappings Clink's support can be disabled. altgr_sub = !!(key_flags & LEFT_ALT_PRESSED); altgr_sub &= !!(key_flags & (LEFT_CTRL_PRESSED|RIGHT_CTRL_PRESSED)); altgr_sub &= !!key_char; altgr_sub &= get_clink_setting_int("use_altgr_substitute"); if (!altgr_sub) { *alt = !!(key_flags & LEFT_ALT_PRESSED); } } // No Unicode character? Then some post-processing is required to make the // output compatible with whatever standard Linux terminals adhere to and // that which Readline expects. if (key_char == 0) { int i; // The numpad keys such as PgUp, End, etc. don't come through with the // ENHANCED_KEY flag set so we'll infer it here. static const int enhanced_vks[] = { VK_UP, VK_DOWN, VK_LEFT, VK_RIGHT, VK_HOME, VK_END, VK_INSERT, VK_DELETE, VK_PRIOR, VK_NEXT, }; for (i = 0; i < sizeof_array(enhanced_vks); ++i) { if (key_vk == enhanced_vks[i]) { key_flags |= ENHANCED_KEY; break; } } // Differentiate enhanced keys depending on modifier key state. MSVC's // runtime does something similar. Slightly non-standard. if (key_flags & ENHANCED_KEY) { static const int mod_map[][4] = { //Nrml Shft Ctrl CtSh { 0x47, 0x61, 0x77, 0x21 }, // Gaw! home { 0x48, 0x62, 0x54, 0x22 }, // HbT" up { 0x49, 0x63, 0x55, 0x23 }, // IcU# pgup { 0x4b, 0x64, 0x73, 0x24 }, // Kds$ left { 0x4d, 0x65, 0x74, 0x25 }, // Met% right { 0x4f, 0x66, 0x75, 0x26 }, // Ofu& end { 0x50, 0x67, 0x56, 0x27 }, // PgV' down { 0x51, 0x68, 0x76, 0x28 }, // Qhv( pgdn { 0x52, 0x69, 0x57, 0x29 }, // RiW) insert { 0x53, 0x6a, 0x58, 0x2a }, // SjX* delete }; for (i = 0; i < sizeof_array(mod_map); ++i) { int j = 0; if (mod_map[i][j] != key_sc) { continue; } j += !!(key_flags & SHIFT_PRESSED); j += !!(key_flags & CTRL_PRESSED) << 1; carry = mod_map[i][j]; break; } // Blacklist. if (!carry) { goto loop; } key_vk = 0xe0; } else if (!(key_flags & CTRL_PRESSED)) { goto loop; } // This builds Ctrl-<key> map to match that as described by Readline's // source for the emacs/vi keymaps. #define CONTAINS(l, r) (unsigned)(key_vk - l) <= (r - l) else if (CONTAINS('A', 'Z')) key_vk -= 'A' - 1; else if (CONTAINS(0xdb, 0xdd)) key_vk -= 0xdb - 0x1b; else if (key_vk == 0x32) key_vk = 0; else if (key_vk == 0x36) key_vk = 0x1e; else if (key_vk == 0xbd) key_vk = 0x1f; else goto loop; #undef CONTAINS key_char = key_vk; } else if (!(key_flags & ENHANCED_KEY) && key_char > 0x7f) { key_char |= 0x8000000; } end: #if defined(DEBUG_GETC) && defined(_DEBUG) printf("\n%08x '%c'", key_char, key_char); #endif SetConsoleMode(handle, mode); return key_char; }
//------------------------------------------------------------------------------ static char* call_readline_impl(const char* prompt) { static int initialised = 0; int expand_result; char* text; char* expanded; char* prepared_prompt; char cwd_cache[MAX_PATH]; // Turn off EOL wrapping as Readline will take care of it. { HANDLE handle_stdout = GetStdHandle(STD_OUTPUT_HANDLE); SetConsoleMode(handle_stdout, ENABLE_PROCESSED_OUTPUT); } // Initialisation if (!initialised) { initialise_clink_settings(); initialise_lua(); initialise_fwrite(); load_history(); history_inhibit_expansion_function = history_expand_control; rl_catch_signals = 0; rl_startup_hook = initialise_hook; initialised = 1; } // If no prompt was provided assume the line is prompted already and // extract it. If a prompt was provided filter it through Lua. prepared_prompt = NULL; if (prompt == NULL) { prepared_prompt = extract_prompt(1); // Even though we're not going to display filtered result the extracted // prompt is run through Lua. This is a little bit of a hack, but helps // to keep behaviour consistent. if (prepared_prompt != NULL) { char buffer[1024]; str_cpy(buffer, prepared_prompt, sizeof(buffer)); lua_filter_prompt(buffer, sizeof_array(buffer)); } } else { prepared_prompt = filter_prompt(prompt); } GetCurrentDirectory(sizeof_array(cwd_cache), cwd_cache); do { // Call readline rl_already_prompted = (prompt == NULL); text = readline(prepared_prompt ? prepared_prompt : ""); if (!text) { goto call_readline_epilogue; } // Expand history designators in returned buffer. expanded = NULL; expand_result = expand_from_history(text, &expanded); if (expand_result > 0 && expanded != NULL) { free(text); text = expanded; // If there was some expansion then display the expanded result. if (expand_result > 0) { hooked_fprintf(NULL, "History expansion: %s\n", text); } } // Should we read the history from disk. if (get_clink_setting_int("history_io")) { load_history(); add_to_history(text); save_history(); } else add_to_history(text); } while (!text || expand_result == 2); call_readline_epilogue: free_prompt(prepared_prompt); SetCurrentDirectory(cwd_cache); return text; }