2009-10-02 14 views
5

que utilizan GNU Readline a la moda "seleccionar", mediante el registro de una llamada de retorno de este modo:GNU Readline: cómo borrar la línea de entrada?

rl_callback_handler_install("", on_readline_input); 

Y luego conectar rl_callback_read_char como la devolución de llamada para mi lazo para select()STDIN_FILENO. Eso es todo bastante estándar, y funciona bien.

Ahora, mi programa imprime de forma asincrónica mensajes en la pantalla, a veces intercalados con las entradas del usuario. Una sesión de "limpia" se vería así:

user input 
SERVER OUTPUT 
SERVER OUTPUT 
user input 
SERVER OUTPUT 

Pero lo que si el usuario está a medio camino a través de una línea cuando llega la respuesta del servidor? A continuación, se pone feo:

user input 
SERVER OUTPUT 
user inSERVER OUTPUT 
put 
SERVER OUTPUT 

i fijo esto simplemente mediante la impresión de un salto de línea antes de la salida del servidor si el usuario hubiera tecleado nada (esto es fácil de decir comprobando rl_line_buffer), y luego hacer rl_forced_update_display() después de imprimir la salida del servidor . Ahora se ve así:

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

Esto es mejor, pero aún así no es perfecto. El problema viene cuando el usuario ha introducido toda una línea, pero aún no se presione Enter - a continuación, se ve así:

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

Esto es malo, ya que se muestra al usuario que escriben tres comandos (tres respuestas para tres entradas son tan posibles como tres respuestas para dos entradas, que es lo que realmente sucedió).

un corte desagradable (que funciona) es hacer esto:

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

pensé que podría mejorar esta imprimiendo caracteres de retroceso en lugar de " - INCOMPLETE" ('\ b'), pero eso no parece hacer cualquier cosa en mi terminal (gnome-terminal en Ubuntu Hardy). printf("ABC\b"); simplemente imprime ABC, por el motivo que sea.

Entonces, ¿cómo puedo borrar la línea de entrada incompleta? Ya sea por impresión de espacios invertidos de alguna manera (puedo averiguar cuántos para imprimir - es strlen(rl_line_buffer)), o mediante el uso de algunas instalaciones de Readline que todavía no conozco?

Respuesta

3

¿Con espacios? Intente imprimir "\b \b" para cada carácter que desee "eliminar" en lugar de uno solo '\b'.


Edición

Cómo funciona
Supongamos que usted ha escrito "Hola, mundo!" al dispositivo de visualización y desea reemplazar "mundo!" con "Jim"

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 */ 

Portabilidad
No estoy seguro, pero la norma describe específicamente el comportamiento de como se ha descrito en las respuestas a su pregunta '\ r' '\ b'.

sección de representación semántica 5.2.2 Carácter

> 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. 
+0

Algunos terminales/emuladores de terminal tienen un comportamiento diferente para el carácter de retroceso. pmg tiene la idea correcta. –

+0

Esto funciona en mi terminal. Pero sin una mejor comprensión de por qué funciona y cómo (no portátil) es, he elegido usar '\ r' en su lugar (como se sugiere en otra respuesta). –

+0

Cambié de opinión después de usar el código '\ r' por un tiempo ... Me gusta esto mejor, porque no requiere sobreimpresión con espacios al final (prefiero hacer la sobreimpresión antes de la salida del servidor, por lo que la solución me sienta mejor, sin mencionar que es la respuesta más directa a mi pregunta original). –

0

yo tratamos de separar de salida del servidor y la entrada del usuario con las ventanas de ncurses. La salida del servidor se simula con un hilo. El programa se ejecuta hasta que ingrese una línea que comienza con '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(); 
} 
+0

Desafortunadamente, las maldiciones y la línea de lectura no se mezclan. Ver http://stackoverflow.com/questions/691652/using-gnu-readline-how-can-i-add-ncurses-in-the-same-program (también preguntado por mí). –

1

Una cosa que puede hacer es utilizar \r para saltar al principio de la línea para la salida del servidor. Luego puede usar los especificadores de ancho de campo para aplicar un pad derecho a la salida al resto de la línea. Esto, en efecto, sobrescribirá lo que el usuario ya haya ingresado.

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

Es posible que desee fflush(stdout) para asegurar que los amortiguadores están en un estado coherente antes de hacer eso.

+0

Esto es inteligente y lo implementé y usé por un tiempo. Al final encontré que prefería la solución "\ b \ b" publicada por pmg. Pero, ¡bien! –

0

¿Alguna de estas funciones ayuda?

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

Además, puede que mediar en la salida del servidor - han controlado la salida del servidor para que sólo aparece cuando y donde lo desee para, en lugar de simplemente extender sobre lo que el usuario está escribiendo? Por ejemplo, si su aplicación se ejecuta en modo curses, ¿podría tener una ventana dividida con una línea o dos en la parte inferior de una ventana secundaria reservada para la entrada del usuario y el resto de la salida (salida del servidor y entrada de usuario aceptada) en una segunda ventana debajo de ella?

+0

Si está sugiriendo que haga que mi aplicación readline también sea una aplicación de curses, parece ser imposible: http://stackoverflow.com/questions/691652/using-gnu-readline-how-can-i-add-ncurses -en el mismo programa –

+0

Intenté 'rl_reset_line_state()' y 'rl_clear_message()' ... ninguno de estos es útil. Intentaré algunas funciones de lectura más cuando pueda, pero creo que ya pasé por muchas más interesantes. –

+0

@John Zwinck: No he presionado la biblioteca readline lo suficiente como para saber si alguno de ellos fue útil. Y si readline no funciona con curses (no es una gran sorpresa), entonces hay dos posibilidades: (1) ignorar la sugerencia o (2) revisar la aplicación para usar curses en lugar de readline. Eso (opción 2) es definitivamente más trabajo. –

3

Después de bastante hackeo, pude obtener este mecanismo. Espero que otras personas lo encuentren útil. Ni siquiera usa select(), pero espero que entiendas el punto.

#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(); 
    } 
+0

Consulte http://github.com/dpc/xmppconsole/blob/master/src/io.c para obtener un ejemplo de trabajo. –

+0

Esta debería ser la solución aceptada. Puede ser peculiar, pero debe manejar la entrada de línea múltiple correctamente. –

0

Esto también parece funcionar:

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