【问题标题】:GNU Readline: how to clear the input line?GNU Readline:如何清除输入行?
【发布时间】:2010-12-03 11:29:11
【问题描述】:

我以“选择”方式使用 GNU Readline,方法是注册一个回调函数,如下所示:

rl_callback_handler_install("", on_readline_input);

然后连接rl_callback_read_char 作为STDIN_FILENOselect() 循环的回调。这些都是非常标准的东西,而且效果很好。

现在,我的程序将消息异步打印到屏幕上,有时会与用户的输入交错。 “干净”的会话如下所示:

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

我想我可以通过打印退格 ('\b') 字符而不是 " - INCOMPLETE" 来改进这一点,但这在我的终端(Ubuntu Hardy 上的 gnome-terminal)上似乎没有任何作用。 printf("ABC\b"); 只是打印 ABC,无论出于何种原因。

那么我怎样才能删除不完整的输入行呢?通过以某种方式打印退格(我可以计算出要打印多少个 - 它是 strlen(rl_line_buffer)),或者通过使用一些我还不知道的 Readline 工具?

【问题讨论】:

    标签: c readline


    【解决方案1】:

    经过相当多的黑客攻击,我能够获得这种机制。我希望其他人会发现它有用。它甚至不使用 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();
        }
    

    【讨论】:

    • 这应该是公认的解决方案。它可能很古怪,但它应该能正确处理多行输入。
    【解决方案2】:

    有空格?尝试为您要“删除”的每个字符打印"\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' 的行为,如您的问题的答案中所述。

    Section 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.
    

    【讨论】:

    • 一些终端/终端仿真器对退格字符有不同的行为。 pmg 有正确的想法。
    • 这在我的终端中确实有效。但是如果没有更好地理解它的工作原理以及它的便携性,我选择使用 '\r' 代替(如另一个答案中所建议的那样)。
    • 在使用 '\r' 代码一段时间后改变了我的想法...我更喜欢这个,因为它不需要在最后用空格叠印(我更喜欢叠印在服务器输出之前,所以这个解决方案最适合我,更不用说它是对我最初问题的最直接答案)。
    【解决方案3】:

    您可以做的一件事是使用\r 跳转到服务器输出的行首。然后,您可以使用字段宽度说明符将输出右填充到行的其余部分。这实际上会覆盖用户已经输入的任何内容。

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

    您可能需要fflush(stdout) 以确保缓冲区处于一致状态,然后再执行此操作。

    【讨论】:

    • 这很聪明,我实现了它并使用了一段时间。最后我发现我更喜欢pmg发布的“\b \b”解决方案。但是,好一个!
    【解决方案4】:

    我尝试使用 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();
    }
    

    【讨论】:

    【解决方案5】:

    这些功能有帮助吗?

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

    另外,您能否调解服务器输出 - 控制服务器输出,使其仅在您希望的时间和地点出现,而不仅仅是覆盖用户输入的内容?例如,如果您的应用程序在 curses 模式下运行,您是否可以在一个为用户输入和其余输出(服务器输出和接受的用户输入)保留的子窗口的底部有一个拆分窗口,底部有一两行上面的第二个子窗口?

    【讨论】:

    • 如果你建议我让我的 readline 应用程序也成为一个 curses 应用程序,这似乎是不可能的:stackoverflow.com/questions/691652/…
    • 我试过rl_reset_line_state()rl_clear_message()...这些都没有帮助。我会尽可能尝试更多的 readline 函数,但我想我已经经历了很多看起来很有趣的函数。
    • @John Zwinck:我还没有努力推动 readline 库,以了解其中是否有用。如果 readline 不能与 curses 一起工作(这并不奇怪),那么有两种可能性:(1)忽略建议,或者(2)修改应用程序以使用 curses 而不是 readline。那(选项 2)肯定是更多的工作。
    【解决方案6】:

    这似乎也有效:

    rl_clear_visible_line();
    printf(...);
    rl_reset_line_state();
    rl_redisplay();
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2021-03-24
      • 1970-01-01
      • 2019-08-16
      • 2010-09-14
      • 2022-01-07
      • 2023-03-24
      • 1970-01-01
      相关资源
      最近更新 更多