module System.Console.Shell.Backend.Haskeline (haskelineBackend, ShellacState) where import System.Console.Shell.Backend import System.Console.Haskeline hiding (completeFilename) import qualified System.Console.Haskeline.History as History import System.Console.Haskeline.IO import System.IO import Data.IORef import Control.Monad.State import Data.Maybe(fromMaybe) data ShellacState = ShellacState { inputState :: InputState, wordBreakChars :: IORef String, completer :: IORef CompletionFunction, defaultCompleter :: IORef (Maybe (String -> IO [String])) } initShellacState :: IO ShellacState initShellacState = do wbcsRef <- newIORef filenameWordBreakChars complRef <- newIORef (wrapHaskelineCompleter listFiles) dcomplRef <- newIORef Nothing let completionWrapper = \line -> do wbcs <- readIORef wbcsRef compl <- readIORef complRef dcompl <- readIORef dcomplRef wrapShellacCompleter wbcs compl dcompl line is <- initializeInput Settings {complete = completionWrapper, autoAddHistory = False, historyFile = Nothing} return ShellacState {inputState = is, wordBreakChars = wbcsRef, completer = complRef, defaultCompleter = dcomplRef} queryInputState :: ShellacState -> (InputT IO a) -> IO a queryInputState = queryInput . inputState -------------- haskelineBackend :: ShellBackend ShellacState haskelineBackend = ShBackend { initBackend = initShellacState, shutdownBackend = closeInput . inputState, outputString = \ss -> queryInputState ss . outputter, flushOutput = \_ -> hFlush stdout, getSingleChar = \ss pre -> queryInputState ss $ getInputChar pre, getInput = \ss pre -> queryInputState ss $ getInputLine pre, addHistory = \ss line -> queryInputState ss $ modifyHistory $ History.addHistory line, setWordBreakChars = \ss -> writeIORef (wordBreakChars ss), getWordBreakChars = readIORef . wordBreakChars, onCancel = cancelInput . inputState, setAttemptedCompletionFunction = \ss -> writeIORef (completer ss), setDefaultCompletionFunction = \ss -> writeIORef (defaultCompleter ss), completeFilename = \_ -> fmap (map replacement) . listFiles, completeUsername = \_ _ -> return [], clearHistoryState = \ss -> queryInputState ss $ putHistory History.emptyHistory, setMaxHistoryEntries = \ss n -> let stifle = if n < 0 then Nothing else Just n in queryInputState ss $ modifyHistory $ History.stifleHistory stifle, getMaxHistoryEntries = \ss -> queryInputState ss $ fmap (fromMaybe (-1) . History.stifleAmount) getHistory, readHistory = \ss file -> History.readHistory file >>= queryInputState ss . putHistory, writeHistory = \ss file -> queryInputState ss getHistory >>= History.writeHistory file } outputter :: BackendOutput -> InputT IO () outputter (RegularOutput str) = outputStr str outputter (InfoOutput str) = outputStr str -- Haskeline has no way to directly write to stderr. -- (outputStr may or may not go to the tty, depending on whether -- we're running in interactive or file mode.) -- So instead, we just use hPutStr and rely on ghc>=6.12's I/O encoding. outputter (ErrorOutput str) = liftIO $ hPutStr stderr str wrapShellacCompleter :: String -> CompletionFunction -> Maybe (String -> IO [String]) -> CompletionFunc IO wrapShellacCompleter breakChars f mg (left,right) = do let (rword,rleft') = break (`elem` breakChars) left let (left', word) = (reverse rleft', reverse rword) result <- f (left',word,right) completions <- case result of Nothing -> case mg of Nothing -> return [] Just g -> g word Just (str,[]) -> return [str] Just (_,alts) -> return alts return (rleft', map makeCompletion completions) -- a hack to avoid adding a trailing space to completed folders. -- I could go a little further and test whether it corresponds to an -- actual file. makeCompletion :: String -> Completion makeCompletion "" = simpleCompletion "" makeCompletion s = (simpleCompletion s) { isFinished = not (last s `elem` "/\\") } longestPrefix :: [String] -> String longestPrefix = foldl1 commonPrefix where commonPrefix (x:xs) (y:ys) | x==y = x : commonPrefix xs ys commonPrefix _ _ = "" wrapHaskelineCompleter :: (String -> IO [Completion]) -> CompletionFunction wrapHaskelineCompleter f (_,w,_) = do ws <- fmap (map replacement) (f w) return $ case ws of [] -> Nothing _ -> Just (longestPrefix ws,ws)