#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:
К сожалению, вы не можете попасть туда, куда хотите, по выбранному вами пути.
Вместо этого вам нужно будет сделать следующее:
- Настройте stdin так, чтобы он не был буферизован, чтобы вы получали ввод пользователя, как только это произойдет.
- Когда они появятся, выведите в стандартный вывод то, что, как вы помните, они набрали в прошлый раз. (Не забудьте сначала удалить все, что они уже ввели.)
- Запишите где-нибудь, сбоку, в переменной в вашей программе текущий текст того, что они набрали в прошлый раз.
- По мере получения новых входных данных из стандартного интерфейса, используйте его для обновления как текста, отображаемого на экране, так и текста, записанного в вашей переменной. При настройках терминала по умолчанию на экране автоматически появятся новые буквы и цифры, знаки препинания и тому подобное, поэтому вам нужно только обновить вашу переменную; но вам нужно будет специально обрабатывать такие вещи, как backspace.
- Как только пользователь нажмет 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
.