/* this function displays the current history entry */ void _el_display_history() { int line_len; int h_len; _el_set_cursor(-rl_point); if ((!(_el_hs.entries)) || (!_el_mb2w (_el_hs.entries[_el_hs.offset]->line, &_el_wide))) { return; } h_len = (int)wcslen(_el_wide); line_len = (int)wcslen(_el_line_buffer); if (h_len > (_EL_BUF_LEN - 1)) { h_len = _EL_BUF_LEN - 1; _el_wide[_EL_BUF_LEN - 1] = '\0'; } wcscpy_s(_el_line_buffer, _el_line_buffer_size, _el_wide); wcscpy_s(_el_print, _el_line_buffer_size, _el_wide); wcscpy_s(_el_line_buffer, _el_line_buffer_size, _el_wide); rl_point = (int)h_len; if (h_len < line_len) { _el_add_char(_el_print, _T(' '), line_len - h_len); } _el_print_string(_el_print); _el_set_cursor(h_len); }
/* this function copies the "filename" string to "name" if filename is a NULL pointer, %APPDATA%\.history is copied to "name" */ int _el_find_history_file(const char *filename, wchar_t **name) { wchar_t *appdata = NULL; size_t n; if (!filename) { _wgetenv_s(&n, NULL, 0, _T("APPDATA")); if (n) { if ((appdata = malloc((n + 1) * sizeof(wchar_t)))) { _wgetenv_s(&n, appdata, n, _T("APPDATA")); } else { n = 0; } } if (n) { n = (wcslen(appdata) + _EL_ENV_BUF_LEN); if (!(*name = malloc(n * sizeof(wchar_t)))) { return -1; } swprintf_s(*name, n, _T("%s\\"), appdata); } wcscat_s(*name, n, _T(".history")); } else { if (!_el_mb2w((char *)filename, name)) { return -1; } } if (appdata) { free(appdata); } return 0; }
/* main readline function */ char *readline(const char *prompt) { wchar_t buf[_EL_CONSOLE_BUF_LEN]; char **array = NULL; char *ret_string = NULL; int start = 0; int end = 0; int compl_pos = -1; int n = 0; int index = 0; int len = 0; int line_len = 0; int old_width = 0; int width = 0; UINT32 ctrl = 0; UINT32 special = 0; COORD coord; DWORD count = 0; INPUT_RECORD irBuffer; CONSOLE_SCREEN_BUFFER_INFO sbInfo; static int piped_input_finished = FALSE; if (piped_input_finished) { return NULL; } _el_ctrl_c_pressed = FALSE; _el_line_buffer = NULL; _el_temp_print = NULL; _el_next_compl = NULL; rl_line_buffer = NULL; _el_file_name = NULL; _el_dir_name = NULL; _el_old_arg = NULL; _el_wide = NULL; _el_text = NULL; _el_text_mb = NULL; _el_compl_array = NULL; _el_completer_word_break_characters = NULL; rl_point = 0; rl_attempted_completion_over = 0; _el_compl_index = 0; _el_n_compl = 0; _el_h_in = NULL; _el_h_out = NULL; wcscpy_s(_el_basic_file_break_characters, _EL_MAX_FILE_BREAK_CHARACTERS, _EL_BASIC_FILE_BREAK_CHARACTERS); memset(&coord, 0, sizeof(COORD)); memset(buf, 0, _EL_CONSOLE_BUF_LEN * sizeof(wchar_t)); memset(&irBuffer, 0, sizeof(INPUT_RECORD)); /* allocate buffers */ _el_line_buffer_size = _EL_BUF_LEN + 1; _el_line_buffer = (wchar_t *)malloc(_el_line_buffer_size * sizeof(wchar_t)); if (!_el_mb2w((char *)rl_basic_word_break_characters, &_el_basic_word_break_characters)) { _el_clean_exit(); return NULL; } if (rl_completer_word_break_characters) { if (!_el_mb2w((char *)rl_completer_word_break_characters, &_el_completer_word_break_characters)) { _el_clean_exit(); return NULL; } } if (!(_el_line_buffer)) { _el_clean_exit(); return NULL; } memset(_el_line_buffer, 0, _el_line_buffer_size * sizeof(wchar_t)); rl_attempted_completion_over = 0; _el_print = (wchar_t *)malloc(_el_line_buffer_size * sizeof(wchar_t)); if (!(_el_print)) { _el_clean_exit(); return NULL; } memset(_el_print, 0, _el_line_buffer_size * sizeof(wchar_t)); rl_prompt = _strdup(prompt); if (!(rl_prompt)) { _el_clean_exit(); return NULL; } if (!_el_mb2w((char *)prompt, &_el_prompt)) { _el_clean_exit(); return NULL; } _el_prompt_len = (int)wcslen(_el_prompt); /* get I/O handles for current console */ _el_h_in = GetStdHandle(STD_INPUT_HANDLE); _el_h_out = GetStdHandle(STD_OUTPUT_HANDLE); if ((!(_el_h_in)) || (!(_el_h_out))) { _el_clean_exit(); return NULL; } /* set console modes */ _el_prev_in_cm_saved = GetConsoleMode(_el_h_in, &_el_prev_in_cm); _el_prev_out_cm_saved = GetConsoleMode(_el_h_out, &_el_prev_out_cm); SetConsoleMode(_el_h_in, ENABLE_PROCESSED_INPUT | ENABLE_EXTENDED_FLAGS | ENABLE_INSERT_MODE | ENABLE_QUICK_EDIT_MODE); SetConsoleMode(_el_h_out, ENABLE_PROCESSED_OUTPUT); SetConsoleCtrlHandler((PHANDLER_ROUTINE) _el_signal_handler, TRUE); rl_point = 0; while ((buf[0] != VK_RETURN) && (!_el_ctrl_c_pressed) && _el_line_buffer) { /* get screen buffer info from the current console */ if (!GetConsoleScreenBufferInfo(_el_h_out, &sbInfo)) { _el_clean_exit(); return NULL; } _el_temp_print_size = sbInfo.dwSize.X + 1; if (!(_el_temp_print = realloc(_el_temp_print, _el_temp_print_size * sizeof(wchar_t)))) { _el_clean_exit(); return NULL; } _el_temp_print[0] = _T('\0'); /* compute the current visible console width */ width = sbInfo.srWindow.Right - sbInfo.srWindow.Left + 1; /* if the user has changed the window size update the view */ if (old_width != width) { line_len = (int)wcslen(_el_line_buffer); sbInfo.dwCursorPosition.X = 0; if (old_width) { n = (_el_prompt_len + line_len - 1) / old_width; sbInfo.dwCursorPosition.Y -= n; coord.Y = sbInfo.dwCursorPosition.Y; } if (!SetConsoleCursorPosition(_el_h_out, sbInfo.dwCursorPosition)) { _el_clean_exit(); return NULL; } if (_el_print_string(_el_prompt)) { _el_clean_exit(); return NULL; } if (_el_set_cursor(_el_prompt_len)) { _el_clean_exit(); return NULL; } if (_el_print_string(_el_line_buffer)) { _el_clean_exit(); return NULL; } if (_el_set_cursor(line_len)) { _el_clean_exit(); return NULL; } if (old_width && (old_width < width)) { coord.X = 0; coord.Y += (_el_prompt_len + line_len - 1) / width + 1; FillConsoleOutputCharacter(_el_h_out, _T(' '), sbInfo.dwSize.X * (n + 2), coord, &count); } } old_width = width; /* wait for console events */ if (!PeekConsoleInput(_el_h_in, &irBuffer, 1, &count)) { /* Check we're possibly piped from another program. */ BOOL ret; char *pos, buf[8192]; DWORD to_read = sizeof(buf); /* Can't guarantee Unicode here, we don't control the data passed through the pipe. */ pos = buf; memset(pos, 0, sizeof(buf)); do { ret = ReadFile(_el_h_in, pos, to_read, &count, 0); pos += count; to_read -= count; } while (ret && to_read > 0); if (pos == buf) { _el_clean_exit(); return NULL; } else if (to_read > 0) { /* Tried to fill the buf, but there's nothing anymore. Force finish. */ piped_input_finished = TRUE; } buf[pos - buf] = '\0'; ret_string = _strdup(buf); _el_clean_exit(); return ret_string; } if (count) { if ((irBuffer.EventType == KEY_EVENT) && irBuffer.Event.KeyEvent.bKeyDown) { /* the user pressed a key */ ctrl = (irBuffer.Event.KeyEvent.dwControlKeyState & (LEFT_CTRL_PRESSED | RIGHT_CTRL_PRESSED)); if (irBuffer.Event.KeyEvent.uChar.UnicodeChar == _T('\n')) { if (!ReadConsoleInput(_el_h_in, &irBuffer, 1, &count)) { _el_clean_exit(); return NULL; } buf[0] = VK_RETURN; continue; } if (irBuffer.Event.KeyEvent.uChar.UnicodeChar == _T('\0')) { /* if it is a special key, just remove it from the buffer */ if (!ReadConsoleInput(_el_h_in, &irBuffer, 1, &count)) { _el_clean_exit(); return NULL; } special = irBuffer.Event.KeyEvent.wVirtualKeyCode; /* parse the special key */ switch (special) { /* arrow left, arrow right HOME and END keys */ case VK_LEFT: case VK_RIGHT: case VK_HOME: case VK_END: if (_el_move_cursor(special, ctrl)) { _el_clean_exit(); return NULL; } break; /* arrow up: display previous history element (if any) after recording the current command line */ case VK_UP: if (_el_display_prev_hist()) { _el_clean_exit(); return NULL; } break; /* page up: display the first history element (if any) after recording the current command line */ case VK_PRIOR: if (_el_display_first_hist()) { _el_clean_exit(); return NULL; } break; /* arrow down: display next history element (if any) after recording the current command line */ case VK_DOWN: if (_el_display_next_hist()) { _el_clean_exit(); return NULL; } break; case VK_NEXT: /* page down: display last history element (if any) after recording the current command line */ if (_el_display_last_hist()) { _el_clean_exit(); return NULL; } break; /* delete char */ case VK_DELETE: if (rl_point != wcslen(_el_line_buffer)) { if (_el_delete_char(VK_DELETE, 1)) { _el_clean_exit(); return NULL; } _el_compl_index = 0; compl_pos = -1; } break; } } else { /* if it is a normal key, remove it from the buffer */ memset(buf, 0, _EL_CONSOLE_BUF_LEN * sizeof(wchar_t)); if (!ReadConsole(_el_h_in, buf, 1, &count, NULL)) { _el_clean_exit(); return NULL; } /* then parse it */ switch (buf[0]) { /* backspace */ case VK_BACK: if (rl_point) { _el_compl_index = 0; compl_pos = -1; if (_el_delete_char(VK_BACK, 1)) { _el_clean_exit(); return NULL; } } break; /* TAB: do completion */ case VK_TAB: if ((!array) || (rl_point != compl_pos)) { _el_free_array(array); index = 0; if (_el_text) { free(_el_text); _el_text = NULL; } if (!(_el_text = _el_get_compl_text(&start, &end))) { _el_clean_exit(); return NULL; } if (_el_old_arg) { _el_old_arg[0] = _T('\0'); } if (!_el_w2mb(_el_text, &_el_text_mb)) { _el_clean_exit(); return NULL; } if (!_el_w2mb(_el_line_buffer, &rl_line_buffer)) { _el_clean_exit(); return NULL; } array = (rl_attempted_completion_function ? rl_attempted_completion_function(_el_text_mb, start, end) : rl_completion_matches(_el_text_mb, (rl_completion_entry_function ? rl_completion_entry_function : rl_filename_completion_function))); if (!array) { _el_clean_exit(); return NULL; } } if (!array[index]) { index = 0; } if (array[index]) { if (!_el_mb2w(array[index], &_el_next_compl)) { _el_clean_exit(); return NULL; } len = 0; if (_el_old_arg) { len = (int)wcslen(_el_old_arg); #if 0 fwprintf(stderr, _T("VK_TAB) _el_old_arg = '%s', len = %d\n"), _el_old_arg, len); fflush(stderr); #endif } if (!len) { len = (int)wcslen(_el_text); } if (len) { if (_el_delete_char(VK_BACK, len)) { _el_clean_exit(); return NULL; } } len = (int)wcslen(_el_next_compl); if (!(_el_old_arg = realloc(_el_old_arg, (len + 1) * sizeof(wchar_t)))) { return NULL; } _el_old_arg[len] = _T('\0'); memcpy(_el_old_arg, _el_next_compl, len * sizeof(wchar_t)); line_len = (int)wcslen(_el_line_buffer); if (_el_insert_char(_el_next_compl, len)) { _el_clean_exit(); return NULL; } free(_el_next_compl); _el_next_compl = NULL; compl_pos = ((rl_point && (!wcschr(_el_completer_word_break_characters ? _el_completer_word_break_characters : _el_basic_word_break_characters, _el_line_buffer[rl_point - 1]))) ? rl_point : -1); ++index; } break; /* ENTER: move the cursor to end of line, then return to the caller program */ case VK_RETURN: if (_el_set_cursor((int)wcslen(_el_line_buffer) - rl_point)) { _el_clean_exit(); return NULL; } break; /* delete word */ case 0x17: /* CTRL + W */ if (ctrl) { if (!rl_point) { break; } n = 1; while (((rl_point - n) > 0) && (iswspace(_el_line_buffer[rl_point - n]))) { ++n; } while ((rl_point - n) && (!iswspace(_el_line_buffer[rl_point - n]))) { ++n; } if (rl_point - n) { --n; } _el_compl_index = 0; compl_pos = -1; if (_el_delete_char(VK_BACK, n)) { _el_clean_exit(); return NULL; } break; } /* delete until end of line */ case 0x0B: /* CTRL + K */ if (ctrl) { line_len = (int)wcslen(_el_line_buffer); if (rl_point < line_len) { _el_compl_index = 0; compl_pos = -1; if (_el_delete_char(VK_DELETE, line_len - rl_point)) { _el_clean_exit(); return NULL; } } break; } /* beginning-of-line */ case 0x01: /* CTRL + A */ if (_el_move_cursor(VK_HOME, 0)) { _el_clean_exit(); return NULL; } break; /* end-of-line */ case 0x05: /* CTRL + E */ if (_el_move_cursor(VK_END, 0)) { _el_clean_exit(); return NULL; } break; /* forward-char */ case 0x06: /* CTRL + F */ if (_el_move_cursor(VK_RIGHT, 0)) { _el_clean_exit(); return NULL; } break; /* backward-char */ case 0x02: /* CTRL + B */ if (_el_move_cursor(VK_LEFT, 0)) { _el_clean_exit(); return NULL; } break; /* previous-line */ case 0x10: /* CTRL + P */ if (_el_display_prev_hist()) { _el_clean_exit(); return NULL; } break; /* next-line */ case 0x0E: /* CTRL + N */ if (_el_display_next_hist()) { _el_clean_exit(); return NULL; } break; /* delete char */ case 0x04: /* CTRL + D */ if (rl_point != wcslen(_el_line_buffer)) { if (_el_delete_char(VK_DELETE, 1)) { _el_clean_exit(); return NULL; } _el_compl_index = 0; compl_pos = -1; } break; /* if it is a printable character, print it NOTE: I have later commented out the iswprint() check since for instance it prevents the euro sign from being printed */ default: /*if (iswprint(buf[0])) {*/ _el_compl_index = 0; compl_pos = -1; if (_el_insert_char(buf, 1)) { _el_clean_exit(); return NULL; } /*}*/ } } } /* if it was not a keyboard event, just remove it from buffer */ else if (!ReadConsoleInput(_el_h_in, &irBuffer, 1, &count)) { _el_clean_exit(); return NULL; } } else { /* wait for console input */ WaitForSingleObject(_el_h_in, INFINITE); } } printf("\n"); history_set_pos(history_length()); /* if CTRL+C has been pressed, return an empty string */ if (_el_line_buffer) { if (_el_ctrl_c_pressed) { n = (int)wcslen(_el_line_buffer) - rl_point; if (n) { _el_set_cursor(n); } _el_line_buffer[0] = _T('\0'); } _el_w2mb(_el_line_buffer, &rl_line_buffer); ret_string = _strdup(rl_line_buffer); } _el_clean_exit(); return ret_string; }