GNU Readline: как очистить строку ввода? - PullRequest
5 голосов
/ 03 октября 2009

Я использую GNU Readline способом «выбора», регистрируя функцию обратного вызова следующим образом:

rl_callback_handler_install("", on_readline_input);

И затем подключение rl_callback_read_char в качестве обратного вызова для моего select() цикла для STDIN_FILENO. Это все довольно стандартные вещи и отлично работает.

Теперь моя программа асинхронно печатает сообщения на экране, иногда чередующиеся с вводом от пользователя. «Чистая» сессия выглядела бы так:

user input
SERVER OUTPUT
SERVER OUTPUT
user input
SERVER OUTPUT

Но что, если пользователь находится посередине строки, когда приходит ответ сервера? Тогда становится некрасиво:

user input
SERVER OUTPUT
user inSERVER OUTPUT
put
SERVER OUTPUT

Я исправил это, просто напечатав новую строку перед выводом на сервер, если пользователь что-то набрал (это легко узнать, проверив rl_line_buffer), а затем выполнил rl_forced_update_display() после печати вывода на сервере. Теперь это выглядит так:

user input
SERVER OUTPUT
user in
SERVER OUTPUT
user input
SERVER OUTPUT

Это лучше, но все же не идеально. Проблема возникает, когда пользователь набрал всю строку, но еще не нажал Enter - тогда это выглядит так:

user input
SERVER OUTPUT
user input
SERVER OUTPUT
user input
SERVER OUTPUT

Это плохо, потому что пользователю кажется, что он набрал три команды (три ответа для трех входов - это как три ответа для двух входов, что на самом деле и произошло).

Гадкий хак (который работает) должен сделать это:

user input
SERVER OUTPUT
user input - INCOMPLETE
SERVER OUTPUT
user input
SERVER OUTPUT

Я подумал, что мог бы улучшить это, печатая символы backspace ('\ b') вместо " - INCOMPLETE", но это, похоже, ничего не делает на моем терминале (gnome-terminal на Ubuntu Hardy). printf("ABC\b"); просто печатает ABC по любой причине.

Так как я могу стереть неполную строку ввода? Либо, печатая символы возврата (как я могу выяснить, сколько печатать - это strlen(rl_line_buffer)), либо используя какое-либо средство Readline, о котором я еще не знаю?

Ответы [ 6 ]

3 голосов
/ 09 июня 2010

После довольно большого взлома мне удалось заполучить этот механизм. Я надеюсь, что другие люди найдут это полезным. Он даже не использует select (), но я надеюсь, что вы поймете.

#include <readline/readline.h>
    #include <readline/history.h>
    #include <stdio.h>
    #include <unistd.h>
    #include <stdlib.h>

    const char const* prompt = "PROMPT> ";

    void printlog(int c) {
        char* saved_line;
        int saved_point;
        saved_point = rl_point;
        saved_line = rl_copy_text(0, rl_end);
        rl_set_prompt("");
        rl_replace_line("", 0);
        rl_redisplay();
        printf("Message: %d\n", c);
        rl_set_prompt(prompt);
        rl_replace_line(saved_line, 0);
        rl_point = saved_point;
        rl_redisplay();
        free(saved_line);
    }


    void handle_line(char* ch) {
        printf("%s\n", ch);
        add_history(ch);
    }

    int main() {
        int c = 1;

        printf("Start.\n");
        rl_callback_handler_install(prompt, handle_line);

        while (1) {
            if (((++c) % 5) == 0) {
                printlog(c);
            }

            usleep(10);
            rl_callback_read_char();
        }
        rl_callback_handler_remove();
    }
3 голосов
/ 03 октября 2009

с пробелами? Попробуйте напечатать "\b \b" для каждого символа, который вы хотите «удалить», а не для одного '\b'.


Редактировать

Как это работает
Предположим, вы написали «Привет, мир!» на дисплей устройства, и вы хотите заменить "мир!" с "Джимом"

Hello, world!
             ^ /* active position */ /* now write "\b \b" */
               /* '\b' moves the active position back;
               // ' ' writes a space (erases the '!')
               // and another '\b' to go back again */
Hello, world
            ^ /* active position */ /* now write "\b \b" again */
Hello, worl
           ^ /* active position */ /* now write "\b \b" 4 times ... */
Hello, 
       ^ /* active position */ /* now write "Jim." */
Hello, Jim.
           ^ /* active position */

Портативность
Я не уверен, но Стандарт конкретно описывает поведение '\ b' и '\ r', как было описано в ответах на ваш вопрос.

Раздел 5.2.2 Семантика отображения символов

> 1   The active position is that location on a display device where the next character output by
>     the fputc function would appear. The intent of writing a printing character (as defined
>     by the isprint function) to a display device is to display a graphic representation of
>     that character at the active position and then advance the active position to the next
>     position on the current line. The direction of writing is locale-specific. If the active
>     position is at the final position of a line (if there is one), the behavior of the display devic e
>     is unspecified.
>  
> 2   Alphabetic escape sequences representing nongraphic characters in the execution
>     character set are intended to produce actions on display devices as follows:
>     \a (alert) Produces an audible or visible alert without changing the active position.
>     \b (backspace) Moves the active position to the previous position on the current line. If
>        the active position is at the initial position of a line, the behavior of the display
>        device is unspecified.
>     \f ( form feed) Moves the active position to the initial position at the start of the next
>        logical page.
>     \n (new line) Moves the active position to the initial position of the next line.
>     \r (carriage return) Moves the active position to the initial position of the current line.
>     \t (horizontal tab) Moves the active position to the next horizontal tabulation position
>        on the current line. If the active position is at or past the last defined horizontal
>        tabulation position, the behavior of the display device is unspecified.
>     \v (vertical tab) Moves the active position to the initial position of the next vertical
>         tabulation position. If the active position is at or past the last defined vertical
>         tabulation position, the behavior of the display device is unspecified.
>  
> 3   Each of these escape sequences shall produce a unique implementation-defined value
>     which can be stored in a single char object. The external representations in a text file
>     need not be identical to the internal representations, and are outside the scope of this
>     International Standard.
1 голос
/ 03 октября 2009

Вы можете использовать \r, чтобы перейти к началу строки для вывода на сервер. Затем вы можете использовать спецификаторы ширины поля, чтобы дополнить вывод правой частью остальной части строки. Фактически это перезапишет все, что пользователь уже ввел.

fprintf (stdout, "\r%-20s\n", "SERVER OUTPUT");

Возможно, вы захотите fflush(stdout), чтобы убедиться, что буферы находятся в согласованном состоянии, прежде чем вы это сделаете.

0 голосов
/ 16 декабря 2016

Это также, кажется, работает:

rl_clear_visible_line();
printf(...);
rl_reset_line_state();
rl_redisplay();
0 голосов
/ 03 октября 2009

Помогает ли какая-либо из этих функций?

  • rl_reset_line_state()
  • rl_clear_message()
  • rl_delete_text()
  • rl_kill_text()

Кроме того, можете ли вы посредничать с выходом сервера - управлять выходом сервера так, чтобы он отображался только когда и где вы хотите, а не просто растягивать то, что печатает пользователь? Например, если ваше приложение работает в режиме curses, вы могли бы иметь разделенное окно со строкой или двумя внизу в одном подокне, зарезервированном для пользовательского ввода, а остальные выходные данные (серверный вывод и принятый пользовательский ввод) в второе подокно над ним?

0 голосов
/ 03 октября 2009

Я попытался разделить вывод сервера и ввод пользователя с помощью окон ncurses. Выход сервера моделируется потоком. Программа запускается до тех пор, пока вы не введете строку, начинающуюся с 'q'.

#include <unistd.h> 
#include <curses.h> 
#include <pthread.h> 

WINDOW *top, *bottom;

int win_update( WINDOW *win, void *data ){
  wprintw(win,"%s", (char*)data ); wrefresh(win);
  return 0;
}

void *top_thread( void *data ){
  char buff[1024];
  int i=0;
  while(1){
    snprintf(buff, 1024, "SERVER OUTPUT: %i\n", i++ );
    use_window( top, win_update, (void*)buff );
    sleep(1);
  }
  return NULL;
}

int main(){
  initscr();
  int maxy, maxx;
  getmaxyx( stdscr, maxy, maxx );

  top = newwin(maxy-1,maxx,0,0);
  wsetscrreg(top,0,maxy-1); idlok(top,1); scrollok(top,1);
  pthread_t top_tid;
  pthread_create(&top_tid, NULL, top_thread, NULL);

  bottom = newwin(1,maxx,maxy-1,0);
  char buff[1024], input[maxx];
  do{
    werase(bottom); wmove(bottom,0,0);
    wprintw(bottom,"input> " ); wrefresh(bottom);
    wgetnstr(bottom,input,sizeof(input));
    snprintf(buff, 1024, "user input: '%s'\n", input );
    use_window( top, win_update, (void*)buff );
  }while( input[0] != 'q' );

  endwin();
}
...