2009-10-02 13 views
5

używam GNU readline w modzie „Wybierz”, rejestrując funkcję zwrotną tak:GNU Readline: jak wyczyścić linię wejściową?

rl_callback_handler_install("", on_readline_input); 

a następnie podpinania rl_callback_read_char jako zwrotnego dla mojego select() pętli dla STDIN_FILENO. To wszystko jest dość standardowe i działa dobrze.

Teraz mój program asynchronicznie drukuje wiadomości na ekranie, czasami przeplatany danymi wejściowymi od użytkownika. "Czysta" sesja wyglądałaby następująco:

user input 
SERVER OUTPUT 
SERVER OUTPUT 
user input 
SERVER OUTPUT 

Ale co, jeśli użytkownik jest w połowie linii, gdy przychodzi odpowiedź serwera? Potem robi się brzydkie:

user input 
SERVER OUTPUT 
user inSERVER OUTPUT 
put 
SERVER OUTPUT 

Naprawiłem to po prostu drukując przełamane przed wyjściem serwera, jeśli użytkownik wpisał coś (to łatwo stwierdzić sprawdzając rl_line_buffer), a następnie robi rl_forced_update_display() po wydrukowaniu wyjście serwera . Teraz wygląda to tak:

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

To jest lepsze, ale wciąż nie jest doskonałe. Problem pojawia się, gdy użytkownik wpisze całą linię, ale jeszcze nie nacisnąć Enter - wtedy wygląda to tak:

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

To jest złe, ponieważ wydaje się, że użytkownik wpisał trzy polecenia (trzy odpowiedzi dla trzy wejścia są tak samo możliwe, jak trzy odpowiedzi na dwa wejścia, co tak naprawdę się wydarzyło).

Paskudny Hack (które działa) jest, aby to zrobić:

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

Pomyślałem mogę poprawić to poprzez drukowanie znaków zamiast " - INCOMPLETE" Backspace ('\ b'), ale nie wydaje się rób cokolwiek w moim terminalu (gnome-terminal na Ubuntu Hardy). printf("ABC\b"); po prostu drukuje ABC, bez względu na przyczynę.

Jak mogę usunąć niekompletną linię wejściową? Albo przez drukowanie tylnych przestrzeni w jakiś sposób (mogę dowiedzieć się, ile do wydrukowania - to jest strlen(rl_line_buffer)), lub za pomocą jakiejś funkcji Readline, o której jeszcze nie wiem?

Odpowiedz

3

Ze spacjami? Spróbuj wydrukować "\b \b" dla każdej postaci, którą chcesz "usunąć" zamiast pojedynczego '\b'.


Edit

Jak to działa
Przypuśćmy, że napisałem "Hello, world!" do urządzenia wyświetlającego i chcesz zastąpić "świat!" z "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 */ 

Przenośność
Nie jestem pewien, ale standard konkretnie opisuje zachowanie \ „b” i „\ r” jak to zostało opisane w odpowiedzi na twoje pytanie.

Sekcja 5.2.2 znakowy wyświetlacz semantyka

> 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

Niektóre terminale/emulatory terminali mają różne zachowanie dla charakteru Backspace. pmg ma dobry pomysł. –

+0

To działa w moim terminalu. Ale bez lepszego zrozumienia, dlaczego to działa i jak (nie) jest to przenośne, wybrałem użycie '\ r' zamiast tego (jak zasugerowano w innej odpowiedzi). –

+0

Zmieniono zdanie po użyciu kodu '\ r' na chwilę ... Naprawdę to lubię, ponieważ nie wymaga nadpisywania spacji na końcu (wolę nadpisywać przed wyjściem serwera, więc to rozwiązanie najlepiej mi odpowiada, nie wspominając już o tym, że jest to najbardziej bezpośrednia odpowiedź na moje oryginalne pytanie). –

0

starałem się oddzielić wyjścia serwera i danych wprowadzanych przez użytkownika z ncurses okien. Wyjście serwera jest symulowane za pomocą wątku. Program działa, dopóki nie wprowadzisz linii zaczynającej się od "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

Niestety, curses i readline nie mieszają się. Zobacz http://stackoverflow.com/questions/691652/using-gnu-readline-how-can-i-add-ncurses-in-the-same-program (również zadawane przeze mnie). –

1

Jedną rzeczą, jaką można zrobić jest użycie \r aby przeskoczyć do początku linii do produkcji serwera. Następnie możesz użyć specyfikatorów szerokości pola, aby wstawić wyjście do reszty linii. W rezultacie nadpisze to, co już wprowadził użytkownik.

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

Możesz fflush(stdout) aby upewnić się, że bufory są w stanie spójnym zanim to zrobisz.

+0

To jest sprytne i wdrożyłem go i użyłem go przez jakiś czas. W końcu znalazłem preferowane rozwiązanie "\ b \ b" opublikowane przez pmg. Ale dobry! –

0

Czy którakolwiek z tych funkcji jest pomocna?

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

Można także pośredniczyć wyjście serwera - mają wyjście serwer kontrolowany tak, że wydaje się to tylko wtedy, kiedy i gdzie chcesz do, a nie tylko rozrastać się nad tym, co pisze użytkownik? Na przykład, jeśli twoja aplikacja działa w trybie przekleństw, czy możesz mieć podzielone okno z linią lub dwie na dole w jednym z pod-okien zarezerwowanych dla danych wprowadzanych przez użytkownika, a pozostałe dane wyjściowe (wyjście serwera i akceptowane dane wejściowe użytkownika) w drugie pod-okno powyżej?

+0

Jeśli sugerujesz, żebym uczynił moją aplikację readline również aplikacją curses, wydaje się to niemożliwe: http://stackoverflow.com/questions/691652/using-gnu-readline-how-can-i-add-ncurses -in-ten-program –

+0

Próbowałem 'rl_reset_line_state()' i 'rl_clear_message()' ... żadne z nich nie jest pomocne. Spróbuję trochę więcej funkcji readline, kiedy mogę, ale myślę, że przeszedłem już całkiem sporo ciekawych. –

+0

@John Zwinck: Nie pchnąłem biblioteki readline wystarczająco mocno, aby wiedzieć, czy którekolwiek z nich były użyteczne. A jeśli readline nie zadziała z klątwami (co nie jest wielką niespodzianką), to istnieją dwie możliwości: (1) zignoruj ​​sugestię lub (2) zrewiduj aplikację, aby używać przekleństw zamiast readline. To (opcja 2) to zdecydowanie więcej pracy. –

3

Po dość wielu włamaniach udało mi się uzyskać ten mechanizm. Mam nadzieję, że inni ludzie uznają to za przydatne. Nie używa nawet select(), ale mam nadzieję, że zrozumiesz.

#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

Zobacz przykład działania: http://github.com/dpc/xmppconsole/blob/master/src/io.c. –

+0

To powinno być akceptowane rozwiązanie. Może to być dziwne, ale powinno poprawnie obsługiwać wejście wielowierszowe. –

0

To również wydaje się działać:

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