GNU Readline (libreadline): асинхронное отображение выходного сообщения

При использовании строки чтения (блокировки) для пользовательского ввода я хотел бы выводить строки текста на консоль асинхронно из другого потока. Кроме того, я хотел бы, чтобы приглашение строки чтения и текущая строка частичного ввода были удалены из консоли, строка вывода была записана, затем восстановлена ​​строка строки чтения и частичная строка пользователя - так, чтобы создавалось впечатление, что вывод был написан «выше» Подсказка.

С помощью какой комбинации функций повторного отображения строки чтения (или иным образом) это может быть достигнуто?

(Документация по функции повторного отображения: http://cnswww.cns.cwru.edu/php/chet/readline/readline.html#SEC35)

демонстрация проблемы:

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

    bool run = true;

    void* log_thread(void*)
    {
            while (run)
            {
                    sleep(1);
                    // WHAT TO DO HERE?
                    write(1, "tick\n", 5);
            }
    }

    int main()
    {
            pthread_t t;
            pthread_create(&t, 0, log_thread, 0);

            while (true)
            {
                    char* p = readline("? ");
                    free(p);

                    if (!p)
                            break;
            }

            run = false;
            pthread_join(t,0);
    }

сборка:

$ g++ -pthread -lreadline test.cpp
$ ./a.out

наблюдаемый результат: (ввод "foo \ nbar \ n" набирается медленно)

? tick
ftick
otick
otick

? tick
tick
bartick
tick

? tick
^C

желаемый результат: (ввод "foo \ nbar \ n" набирается медленно)

tick
tick
tick
tick
tick
? foo

tick
tick
tick
tick
tick
? bar

tick
? ^C

person Andrew Tomazos    schedule 15.02.2012    source источник
comment
Что ты уже пробовал? ;-)   -  person alk    schedule 04.03.2012


Ответы (2)


Я делаю это в консольной версии моей программы omphalos (https://github.com/dankamongmen/omphalos < / а>). Этот конкретный код взят с https://github.com/dankamongmen/omphalos/blob/master/src/ui/tty/tty.c.

У меня есть:

// Call whenever we generate output, so that the prompt is updated
static inline void
wake_input_thread(void){
    if(input_tid){
            pthread_kill(*input_tid,SIGWINCH);
            rl_redisplay(); // FIXME probably need call from readline contex
    }
    pthread_mutex_unlock(&promptlock);

}

и

static inline void
clear_for_output(FILE *fp){
    fputc('\r',fp);
}

Когда что-то хочет напечатать, оно берет блокировку и вызывает clear_for_output (), перемещая курсор в начало текущей строки. При необходимости в это время он может изменить приглашение, вызвав rl_set_prompt (). По завершении он вызывает wake_input_thread (), снимая блокировку и вызывая повторное отображение.

Я не уверен, работает ли это в том случае, если вы набрали больше строки текста и сомневаетесь в этом, и не заботитесь о формальном обнаружении того, что, вероятно, является новой и удручающей ошибкой прямо сейчас, чтобы вы могли поэкспериментируйте с этим сами.

person nick black    schedule 11.06.2012
comment
С тех пор я определил, что при условии правильной доставки SIGWINCH (т. Е. Потоку в контексте libreadline) в rl_redisplay () нет необходимости. Я удалил его из своего кода, и все работает так же хорошо, как и раньше. - person nick black; 26.06.2012
comment
assert(fputc('\r',fp) != EOF); мне кажется ошибкой. Вы не должны полагаться на побочные эффекты функций, вызываемых в assert(), по очевидной причине: когда assert скомпилирован, вызов функции также будет ... - person Alastair; 13.05.2013
comment
Это решение неверно не только в том случае, если текста больше, чем строка, но и потому, что оно не очищает строку, если строка ввода длиннее, чем напечатанная строка (обходной путь - напечатать \r<lot of spaces>\r) - person user202729; 02.10.2019
comment
спасибо Аластеру за исправление (добрые глаза!) - свой ответ отредактировал. Спасибо @ user202729 за напоминание отредактировать ответ. - person nick black; 03.10.2019

Функции, которые следует использовать:

  • rl_clear_visible_line(). Печать \r не принесет результата, потому что она просто перемещает курсор в начало строки, не удаляя содержимое строки, плюс она не работает должным образом, когда имеется более одной строки ввода.
  • rl_on_new_line(); rl_redisplay(); (или rl_forced_update_display();): после печати.

Похоже, что эти две функции можно вызывать из любого потока; однако это может привести к возникновению условий гонки (в документации ничего не сказано о том, безопасно ли использовать функции readline из нескольких потоков), поэтому лучше использовать rl_event_hook и rl_getc_function (потому что rl_event_hook не вызывается, когда удерживается клавиша) для вызова функции для основной поток. Также не забывайте обрабатывать инструкции, когда не работает readline функция.

person user202729    schedule 02.10.2019