如何在Haskeline的运行时更改制表符完成的内容? [英] How to change Tab-completed content at runtime in Haskeline?

查看:52
本文介绍了如何在Haskeline的运行时更改制表符完成的内容?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我想编写一个文本界面,其中提供了一些默认命令.该程序支持这些命令的制表符完成.

I want to write a text interface, which provides some default commands. This program supports tab completion of those commands.

该程序还记录用户输入并将其存储在 StateData 中.现在,我希望该程序支持这些用户输入的制表符完成.例如:

This program also records user inputs and stores it in StateData. And now I want this program to support tab completion of those user inputs. For example:

*Main > main
> read a<tab> -- press tab and no suggestions (read is a default command)
> read abcde
...
> read a<tab> -- press tab
abcde         -- suggestions

是否可以在不使用 IORef 之类的不安全机制的情况下做到这一点?有没有一种方法可以将更新的 st loop (在 repl 中)传递到 replSettings startState (在中)repl )?

Is it possible to do that without using unsafe mechanism like IORef? Is there a way to pass updated st from loop (in repl) to replSettings startState (in repl)?

我是Haskeline的新手,感谢您的宝贵时间.

I am new to Haskeline and thanks for your time.

repl :: StateData -> IO()
repl startState                    =  runInputT (replSettings startState) $ loop startState
  where
    loop :: StateData              -> InputT IO ()
    loop st                        =  do
      inputL                       <- getInputLine "> "
      case inputL                  of
           Nothing                 -> return ()
           Just "quit"             -> outputStrLn "--Exited--" >> return ()
           Just ipt                -> do (opt, st')     <- process ipt `runStateT` st
                                         ...
                                         loop st'

replSettings :: StateData -> Settings IO
replSettings st =
  Settings
    { complete       = replCompletion st,
      historyFile    = Just "history.txt",
      autoAddHistory = True
    }

replCompletion :: StateData -> CompletionFunc IO
replCompletion st = completeWordWithPrev Nothing [' '] st (\x y -> return $ completionGenerator x y)

completionGenerator :: String -> String -> StateData -> [Completion]
completionGenerator "" c st = 
  commandSuggestion c (updatesSuggestions st) -- I wish to update it at run time
completionGenerator p  c st = ...

推荐答案

IORef 并非不安全;您已经在 IO 中,因此这是在此处添加可变状态的一种非常合理的方法.

IORef isn’t unsafe; you’re already in IO, so it’s a perfectly reasonable way to add mutable state here.

但是,如果要避免使用 IO ,则可以简单地将 StateT StateData IO 用作 InputT 的基础单子,从而完成 Settings 中的功能.看来您已经尝试使用 StateT .这是一个完整的示例,该示例仅将每个条目添加到列表中,然后自动将其自动完成:

But if you want to avoid IO, you can simply use StateT StateData IO as the underlying monad for InputT, and thus the completion function in Settings. It seems you’re already trying to use StateT anyway. Here’s a complete example that just adds every entry to a list and autocompletes them naïvely:

import Control.Monad.Trans.Class (lift)
import Control.Monad.Trans.State (StateT, evalStateT, get, modify)
import Data.List (isPrefixOf)
import System.Console.Haskeline

type StateData = [String]

main :: IO ()
main = repl []

repl :: StateData -> IO ()
repl startState
  = flip evalStateT startState
  $ runInputT replSettings loop
  where
    loop :: InputT (StateT StateData IO) ()
    loop = do
      inputL <- getInputLine "> "
      case inputL of
        Nothing -> pure ()
        Just "quit" -> outputStrLn "--Exited--"
        Just ipt -> do
          -- Just add each entry to the state directly.
          lift $ modify (ipt :)
          loop

replSettings :: Settings (StateT StateData IO)
replSettings = Settings
  { complete       = replCompletion
  , historyFile    = Just "history.txt"
  , autoAddHistory = True
  }

replCompletion :: CompletionFunc (StateT StateData IO)
replCompletion = completeWordWithPrev Nothing " " completionGenerator

completionGenerator :: String -> String -> StateT StateData IO [Completion]
completionGenerator prefix suffix = do
  st <- get
  -- Trivial completion that just ignores the suffix.
  pure $ fmap (\ s -> Completion s s True)
    $ filter (prefix `isPrefixOf`) st

也可以使用 MonadState (来自 mtl )编写完成生成器,以使其无法访问 IO 和其他代码同样可以使用这种纯状态,而与 IO 无关.但是否则,因为您已经在此代码的 IO 中,所以 StateT StateData IO / get / modify ReaderT(IORef StateData)IO / readIORef / modifyIORef 没什么不同.

The completion generator could also be written using MonadState (from mtl) to insulate it from being able to access IO, and other code could likewise use this pure state while being agnostic to IO. But otherwise, since you’re already in IO in this code, StateT StateData IO / get / modify are no different than ReaderT (IORef StateData) IO / readIORef / modifyIORef.

实际上,如果您在代码中放置了 IORef StateData ,则假定它是一种更复杂的记录类型,则后者是使它的某些部分可变而其他部分不可变的好方法.

In fact, if you put an IORef in StateData, supposing it’s a more complex record type in your code, the latter is a good way to make some parts of it mutable and others immutable.

data StateData = StateData
  { mutableThing   :: !(IORef Thing)
  , immutableStuff :: !Stuff
  …
  }

这篇关于如何在Haskeline的运行时更改制表符完成的内容?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆