/* 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); }
/* insert character(s) on the command line */ int _el_insert_char(wchar_t *buf, int n) { int c; int eff_n; int line_len; line_len = (int)wcslen(_el_line_buffer); /* get line length from the current logical cursor position to the end, including the terminal '\0' */ c = (int)wcslen(&(_el_line_buffer[rl_point])) + 1; eff_n = n; /* if the buffer is not large enough, cut down the number of inserted chars */ if ((line_len + n) >= _EL_BUF_LEN) { eff_n = _EL_BUF_LEN - line_len - 1; } /* make the insertion */ memmove(&_el_line_buffer[rl_point + eff_n], &_el_line_buffer[rl_point], c * sizeof(wchar_t)); memcpy(&_el_line_buffer[rl_point], buf, eff_n * sizeof(wchar_t)); /* copy the inserted chars into the string for subsequent printing */ memcpy(_el_print, &_el_line_buffer[rl_point], (c + eff_n) * sizeof(wchar_t)); _el_print[c + eff_n] = _T('\0'); /* set the new logical cursor position */ rl_point += eff_n; /* print the insertion */ if (_el_print_string(_el_print)) { return -1; } /* set the new cursor position */ if (_el_set_cursor(eff_n)) { return -1; } if (!_el_w2mb(_el_line_buffer, &rl_line_buffer)) { return -1; } 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; }
/* delete character(s) on the command line */ int _el_delete_char(UINT32 vk, int n) { int c; int line_len; int eff_n; line_len = (int)wcslen(_el_line_buffer); eff_n = n; switch (vk) { case VK_DELETE: /* the character in correspondence of the cursor is to be deleted; check that we are not going to delete more characters than those which exist between the logical cursor position and the end of line */ if ((line_len - rl_point) < n) { eff_n = line_len - rl_point; } break; case VK_BACK: /* the character before the cursor is to be deleted check that we are not going to delete more characters than those which exist between the logical cursor position and the beginning of line */ if ((rl_point - n) >= 0) { rl_point -= n; } else { eff_n = rl_point; rl_point = 0; } /* with backspace we need to reposition the cursor as well */ if (_el_set_cursor(-eff_n)) { return -1; } break; default: return -1; } c = (int)wcslen(&_el_line_buffer[rl_point]) + 1; /* cut out deleted characters */ memmove(&_el_line_buffer[rl_point], &_el_line_buffer[rl_point + eff_n], (c - eff_n) * sizeof(wchar_t)); /* copy the characters from the current cursor position to end of line in a string */ memcpy(_el_print, &_el_line_buffer[rl_point], (c - eff_n) * sizeof(wchar_t)); _el_print[c - eff_n] = '\0'; /* add spaces to the string to be printed to clear out "tails" from the command line */ _el_add_char(_el_print, _T(' '), n); if (!_el_w2mb(_el_line_buffer, &rl_line_buffer)) { return -1; } /* print out and goodbye */ return _el_print_string(_el_print); }