Не удается выполнить запись в стандартный ввод

#haskell

#haskell

Вопрос:

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

Я пробовал использовать hPutStr следующим образом:

hPutStr stdin "Test"

но когда я запускаю его, я сталкиваюсь с этой ошибкой:

<stdin>: hPutStr: illegal operation (handle is not open for writing)

Кто-нибудь знает, как обойти это ограничение или, может быть, просто другой способ напечатать что-то на стандартном вводе?

Комментарии:

1. Возможно, вы хотели записать на стандартный вывод? hPutStr stdout "Test" ?

2. Нет, я хочу записать в стандартный ввод, чтобы пользователь мог редактировать то, что напечатано, прежде чем отправлять его обратно в программу через приглашение.

3. Возможно, я ошибаюсь, но я не думаю, что вы можете писать в стандартный интерфейс. Вам придется выполнить более сложную работу для чтения и записи в терминал. Возможно, вы захотите использовать библиотеку, такую как haskeline или repline, которая предоставляет искомую функциональность и многое другое, или посмотрите, как они реализуют эту функциональность.

4. не уверен, но если вы напишете в stdin, как пользователь прочитает сообщение? Если сообщение отображается на экране, его следует перенаправить на стандартный вывод, не так ли?

5. Подумайте об этом так. Стандартный ввод вашего REPL — это конец чтения канала, конец записи, в который записывает ваш терминал . Итак, вам нужно взаимодействовать с вашим терминалом, а не с REPL, чтобы изменить то, что ваш REPL видит как входные данные.

Ответ №1:

К сожалению, вы не можете попасть туда, куда хотите, по выбранному вами пути.

Вместо этого вам нужно будет сделать следующее:

  1. Настройте stdin так, чтобы он не был буферизован, чтобы вы получали ввод пользователя, как только это произойдет.
  2. Когда они появятся, выведите в стандартный вывод то, что, как вы помните, они набрали в прошлый раз. (Не забудьте сначала удалить все, что они уже ввели.)
  3. Запишите где-нибудь, сбоку, в переменной в вашей программе текущий текст того, что они набрали в прошлый раз.
  4. По мере получения новых входных данных из стандартного интерфейса, используйте его для обновления как текста, отображаемого на экране, так и текста, записанного в вашей переменной. При настройках терминала по умолчанию на экране автоматически появятся новые буквы и цифры, знаки препинания и тому подобное, поэтому вам нужно только обновить вашу переменную; но вам нужно будет специально обрабатывать такие вещи, как backspace.
  5. Как только пользователь нажмет enter, посмотрите, что находится в вашей переменной, вместо того, чтобы брать именно то, что вы получили из stdin.

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

Комментарии:

1. Возможно, вам вообще не нужна библиотека: хорошо rlwrap работает извне.

Ответ №2:

Это невозможно напрямую; стандартный ввод недоступен для записи.

Наиболее простым решением, использующим только Haskell, является использование библиотеки, которая обеспечивает редактирование строк и историю для вас, наиболее популярной из которых является haskeline . Предполагая, например, что у вас есть eval :: String -> IO Value действие, REPL с историей будет выглядеть следующим образом:

 import Control.Monad.IO.Class (liftIO)
import System.Console.Haskeline
  ( InputT
  , defaultSettings
  , getInputLine
  , outputStrLn
  , runInputT
  )

repl :: IO ()
repl = runInputT defaultSettings loop
 where
   loop :: InputT IO ()
   loop = do
     entry <- getInputLine "> "        -- R
     case entry of
       Nothing -> pure ()
       Just line -> do
         result <- liftIO $ eval line  -- E
         outputStrLn $ show result     -- P
         loop                          -- L
  

getInputLine будет выдано Nothing , когда стандартный ввод закрыт, если пользователь вводит управляющий код для EOF (напримерCtrl, D) или если ввод передается в ваш REPL и канал закрыт. В дополнение к редактированию и истории, Haskeline предлагает автозаполнение, которое может оказаться полезным для REPL.

Низкотехнологичным решением является запуск вашей программы под оболочкой, которая обеспечивает редактирование в стиле GNU readline, например rlwrap . Полностью общим решением является создание собственного редактирования с использованием библиотеки пользовательского интерфейса консоли, наиболее популярной из которых является brick .