GNU Readline: comment effacer la ligne d’entrée?

J’utilise GNU Readline à la manière “select”, en enregistrant une fonction de rappel comme ceci:

rl_callback_handler_install("", on_readline_input); 

Et ensuite, en joignant rl_callback_read_char comme rappel de ma boucle select() pour STDIN_FILENO . Tout cela est plutôt standard et fonctionne bien.

Maintenant, mon programme imprime de manière asynchrone des messages à l’écran, parfois entrelacés avec les entrées de l’utilisateur. Une session “propre” ressemblerait à ceci:

 user input SERVER OUTPUT SERVER OUTPUT user input SERVER OUTPUT 

Mais que se passe-t-il si l’utilisateur est au milieu d’une ligne lorsque la réponse du serveur arrive? Puis ça devient moche:

 user input SERVER OUTPUT user inSERVER OUTPUT put SERVER OUTPUT 

J’ai résolu ce rl_line_buffer simplement en imprimant une nouvelle ligne avant la sortie du serveur si l’utilisateur avait saisi quoi que ce soit (c’est facile à vérifier en vérifiant rl_line_buffer ), puis en effectuant rl_forced_update_display() après avoir imprimé la sortie du serveur. Maintenant ça ressemble à ça:

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

C’est mieux, mais toujours pas parfait. Le problème survient lorsque l’utilisateur a tapé une ligne entière mais n’a pas encore appuyé sur Entrée – cela ressemble à ceci:

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

Ceci est mauvais car il apparaît à l’utilisateur qu’il a tapé trois commandes (trois réponses pour trois entrées est tout aussi possible que trois réponses pour deux entrées, ce qui est réellement arrivé).

Un bidouillage méchant (qui fonctionne) consiste à faire ceci:

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

Je pensais pouvoir améliorer cela en imprimant des caractères de retour arrière (‘\ b’) au lieu de " - INCOMPLETE" , mais cela ne semble rien faire du tout sur mon terminal (gnome-terminal sur Ubuntu Hardy). printf("ABC\b"); imprime simplement ABC , pour une raison quelconque.

Alors, comment puis-je effacer la ligne d’entrée incomplète? Soit en imprimant les backspaces d’une manière ou d’une autre (je peux trouver combien il faut en imprimer – c’est strlen(rl_line_buffer) ), soit en utilisant des fonctionnalités de Readline que je ne connais pas encore?

    Avec des espaces? Essayez d’imprimer "\b \b" pour chaque caractère que vous souhaitez “supprimer” au lieu d’un seul '\b' .


    modifier

    Comment ça marche
    Supposons que vous avez écrit “Hello, world!” à l’écran et que vous voulez remplacer “world!” avec “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 */ 

    Portabilité
    Je ne suis pas sûr, mais la norme décrit spécifiquement le comportement de ‘\ b’ et ‘\ r’, comme cela a été décrit dans les réponses à votre question.

    Section 5.2.2 Sémantique d’affichage des caractères

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

    Après beaucoup de piratage, j’ai pu obtenir ce mécanisme. J’espère que d’autres le trouveront utile. Il n’utilise même pas select (), mais j’espère que vous comprendrez.

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

    Une chose à faire est d’utiliser \r pour sauter au début de la ligne pour la sortie du serveur. Vous pouvez ensuite utiliser des spécificateurs de largeur de champ pour cadrer à droite la sortie vers le rest de la ligne. En réalité, cela écrasera tout ce que l’utilisateur a déjà entré.

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

    Avant de le faire, vous souhaiterez peut-être fflush(stdout) pour vous assurer que les tampons sont dans un état cohérent.

    J’ai essayé de séparer la sortie du serveur et l’entrée utilisateur avec ncurses windows. La sortie du serveur est simulée avec un thread. Le programme est exécuté jusqu’à ce que vous ensortingez une ligne commençant par ‘q’.

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

    Est-ce que l’une de ces fonctions vous aide?

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

    De plus, pouvez-vous modérer la sortie du serveur – faire en sorte que la sortie du serveur soit contrôlée de sorte qu’elle n’apparaisse que quand et où vous le souhaitez, plutôt que de simplement s’étendre sur ce que l’utilisateur tape? Par exemple, si votre application fonctionne en mode curses, pouvez-vous avoir une fenêtre divisée avec une ligne ou deux en bas dans une sous-fenêtre réservée à une entrée utilisateur et le rest de la sortie (sortie serveur et entrée utilisateur acceptée) dans une deuxième sous-fenêtre au-dessus?

    Cela semble aussi fonctionner:

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