diff options
author | niksaz <nikitasazanovich@gmail.com> | 2016-05-01 13:34:45 +0200 |
---|---|---|
committer | Ben Gamari <ben@smart-cactus.org> | 2016-05-01 23:29:49 +0200 |
commit | 533037cc58a7c50e1c014e27e8b971d53e7b47bd (patch) | |
tree | 6bdc053c60cc3b8d2254a0cc14569c2207c317f8 /ghc | |
parent | 18676a4a0dfe79e2704e48be5c8716a656825efe (diff) | |
download | haskell-533037cc58a7c50e1c014e27e8b971d53e7b47bd.tar.gz |
Greater customization of GHCi prompt
This patch is trying to redesign the :set prompt option to take not a
String but a Haskell function, like [String] -> Int -> IO String, where
[String] is the list of the names of the currently loaded modules and
Int is the line number. Currently you may set prompt function with
**:set promt-function [String] -> Int -> IO String** option and old
version is also available - :set prompt String.
So, it looks like I've almost completed this patch:
1) Now we have a lot of escape sequences - 13 to be exact. Most of them
are similar to bash prompt escape sequences. Thus they are quite handy.
2) We may use the special escape sequence to call shell functions, for
example "%call(ls -l -a)".
3) We may use :set prompt-function to set PFunction to handle prompt.
It is just [String] -> Int -> IO String.
Reviewers: erikd, austin, mpickering, bgamari
Reviewed By: mpickering, bgamari
Subscribers: mpickering, thomie
Differential Revision: https://phabricator.haskell.org/D2084
GHC Trac Issues: #5850
Diffstat (limited to 'ghc')
-rw-r--r-- | ghc/GHCi/UI.hs | 257 | ||||
-rw-r--r-- | ghc/GHCi/UI/Monad.hs | 9 |
2 files changed, 210 insertions, 56 deletions
diff --git a/ghc/GHCi/UI.hs b/ghc/GHCi/UI.hs index a335aea827..c04bf2d194 100644 --- a/ghc/GHCi/UI.hs +++ b/ghc/GHCi/UI.hs @@ -96,6 +96,9 @@ import Data.List ( find, group, intercalate, intersperse, isPrefixOf, nub, partition, sort, sortBy ) import Data.Maybe import qualified Data.Map as M +import Data.Time.LocalTime ( getZonedTime ) +import Data.Time.Format ( formatTime, defaultTimeLocale ) +import Data.Version ( showVersion ) import Exception hiding (catch) import Foreign @@ -105,6 +108,7 @@ import System.Directory import System.Environment import System.Exit ( exitWith, ExitCode(..) ) import System.FilePath +import System.Info import System.IO import System.IO.Error import System.IO.Unsafe ( unsafePerformIO ) @@ -113,6 +117,8 @@ import Text.Printf import Text.Read ( readMaybe ) import Text.Read.Lex (isSymbolChar) +import Unsafe.Coerce + #ifndef mingw32_HOST_OS import System.Posix hiding ( getEnv ) #else @@ -129,8 +135,8 @@ data GhciSettings = GhciSettings { availableCommands :: [Command], shortHelpText :: String, fullHelpText :: String, - defPrompt :: String, - defPrompt2 :: String + defPrompt :: PromptFunction, + defPromptCont :: PromptFunction } defaultGhciSettings :: GhciSettings @@ -139,7 +145,7 @@ defaultGhciSettings = availableCommands = ghciCommands, shortHelpText = defShortHelpText, defPrompt = default_prompt, - defPrompt2 = default_prompt2, + defPromptCont = default_prompt_cont, fullHelpText = defFullHelpText } @@ -328,7 +334,10 @@ defFullHelpText = " :set args <arg> ... set the arguments returned by System.getArgs\n" ++ " :set prog <progname> set the value returned by System.getProgName\n" ++ " :set prompt <prompt> set the prompt used in GHCi\n" ++ - " :set prompt2 <prompt> set the continuation prompt used in GHCi\n" ++ + " :set prompt-cont <prompt> set the continuation prompt used in GHCi\n" ++ + " :set prompt-function <expr> set the function to handle the prompt\n" ++ + " :set prompt-cont-function <expr>" ++ + "set the function to handle the continuation prompt\n" ++ " :set editor <cmd> set the command used for :edit\n" ++ " :set stop [<n>] <cmd> set the command to run when a breakpoint is hit\n" ++ " :unset <option> ... unset options\n" ++ @@ -357,7 +366,7 @@ defFullHelpText = " :show paths show the currently active search paths\n" ++ " :show language show the currently active language flags\n" ++ " :show <setting> show value of <setting>, which is one of\n" ++ - " [args, prog, prompt, editor, stop]\n" ++ + " [args, prog, editor, stop]\n" ++ " :showi language show language flags for interactive evaluation\n" ++ "\n" @@ -372,12 +381,14 @@ findEditor = do return "" #endif -default_progname, default_prompt, default_prompt2, default_stop :: String +default_progname, default_stop :: String default_progname = "<interactive>" -default_prompt = "%s> " -default_prompt2 = "%s| " default_stop = "" +default_prompt, default_prompt_cont :: PromptFunction +default_prompt = generatePromptFunctionFromString "%s> " +default_prompt_cont = generatePromptFunctionFromString "%s| " + default_args :: [String] default_args = [] @@ -438,8 +449,8 @@ interactiveUI config srcs maybe_exprs = do GHCiState{ progname = default_progname, args = default_args, evalWrapper = eval_wrapper, - prompt = defPrompt config, - prompt2 = defPrompt2 config, + prompt = default_prompt, + prompt_cont = default_prompt_cont, stop = default_stop, editor = default_editor, options = [], @@ -689,8 +700,23 @@ fileLoop hdl = do incrementLineNo return (Just l') -mkPrompt :: GHCi String -mkPrompt = do +formatCurrentTime :: String -> IO String +formatCurrentTime format = + getZonedTime >>= return . (formatTime defaultTimeLocale format) + +getUserName :: IO String +getUserName = do +#ifdef mingw32_HOST_OS + getEnv "USERNAME" + `catchIO` \e -> do + putStrLn $ show e + return "" +#else + getLoginName +#endif + +getInfoForPrompt :: GHCi (SDoc, [String], Int) +getInfoForPrompt = do st <- getGHCiState imports <- GHC.getContext resumes <- GHC.getResumeContext @@ -707,30 +733,127 @@ mkPrompt = do pan <- GHC.getHistorySpan hist return (brackets (ppr (negate ix) <> char ':' <+> ppr pan) <> space) + let dots | _:rs <- resumes, not (null rs) = text "... " | otherwise = empty rev_imports = reverse imports -- rightmost are the most recent - modules_bit = - hsep [ char '*' <> ppr m | IIModule m <- rev_imports ] <+> - hsep (map ppr [ myIdeclName d | IIDecl d <- rev_imports ]) - -- use the 'as' name if there is one myIdeclName d | Just m <- ideclAs d = m | otherwise = unLoc (ideclName d) - deflt_prompt = dots <> context_bit <> modules_bit + modules_names = + ['*':(moduleNameString m) | IIModule m <- rev_imports] ++ + [moduleNameString (myIdeclName d) | IIDecl d <- rev_imports] + line = 1 + line_number st + + return (dots <> context_bit, modules_names, line) - f ('%':'l':xs) = ppr (1 + line_number st) <> f xs - f ('%':'s':xs) = deflt_prompt <> f xs - f ('%':'%':xs) = char '%' <> f xs - f (x:xs) = char x <> f xs - f [] = empty +parseCallEscape :: String -> (String, String) +parseCallEscape s + | not (all isSpace beforeOpen) = ("", "") + | null sinceOpen = ("", "") + | null sinceClosed = ("", "") + | null cmd = ("", "") + | otherwise = (cmd, tail sinceClosed) + where + (beforeOpen, sinceOpen) = span (/='(') s + (cmd, sinceClosed) = span (/=')') (tail sinceOpen) + +checkPromptStringForErrors :: String -> Maybe String +checkPromptStringForErrors ('%':'c':'a':'l':'l':xs) = + case parseCallEscape xs of + ("", "") -> Just ("Incorrect %call syntax. " ++ + "Should be %call(a command and arguments).") + (_, afterClosed) -> checkPromptStringForErrors afterClosed +checkPromptStringForErrors ('%':'%':xs) = checkPromptStringForErrors xs +checkPromptStringForErrors (_:xs) = checkPromptStringForErrors xs +checkPromptStringForErrors "" = Nothing + +generatePromptFunctionFromString :: String -> PromptFunction +generatePromptFunctionFromString promptS = \_ _ -> do + (context, modules_names, line) <- getInfoForPrompt + + let + processString :: String -> GHCi SDoc + processString ('%':'s':xs) = + liftM2 (<>) (return modules_list) (processString xs) + where + modules_list = context <> modules_bit + modules_bit = hsep $ map text modules_names + processString ('%':'l':xs) = + liftM2 (<>) (return $ ppr line) (processString xs) + processString ('%':'d':xs) = + liftM2 (<>) (liftM text formatted_time) (processString xs) + where + formatted_time = liftIO $ formatCurrentTime "%a %b %d" + processString ('%':'t':xs) = + liftM2 (<>) (liftM text formatted_time) (processString xs) + where + formatted_time = liftIO $ formatCurrentTime "%H:%M:%S" + processString ('%':'T':xs) = do + liftM2 (<>) (liftM text formatted_time) (processString xs) + where + formatted_time = liftIO $ formatCurrentTime "%I:%M:%S" + processString ('%':'@':xs) = do + liftM2 (<>) (liftM text formatted_time) (processString xs) + where + formatted_time = liftIO $ formatCurrentTime "%I:%M %P" + processString ('%':'A':xs) = do + liftM2 (<>) (liftM text formatted_time) (processString xs) + where + formatted_time = liftIO $ formatCurrentTime "%H:%M" + processString ('%':'u':xs) = + liftM2 (<>) (liftM text user_name) (processString xs) + where + user_name = liftIO $ getUserName + processString ('%':'w':xs) = + liftM2 (<>) (liftM text current_directory) (processString xs) + where + current_directory = liftIO $ getCurrentDirectory + processString ('%':'o':xs) = + liftM ((text os) <>) (processString xs) + processString ('%':'a':xs) = + liftM ((text arch) <>) (processString xs) + processString ('%':'N':xs) = + liftM ((text compilerName) <>) (processString xs) + processString ('%':'V':xs) = + liftM ((text $ showVersion compilerVersion) <>) (processString xs) + processString ('%':'c':'a':'l':'l':xs) = do + respond <- liftIO $ do + (code, out, err) <- + readProcessWithExitCode + (head list_words) (tail list_words) "" + `catchIO` \e -> return (ExitFailure 1, "", show e) + case code of + ExitSuccess -> return out + _ -> do + hPutStrLn stderr err + return "" + liftM ((text respond) <>) (processString afterClosed) + where + (cmd, afterClosed) = parseCallEscape xs + list_words = words cmd + processString ('%':'%':xs) = + liftM ((char '%') <>) (processString xs) + processString (x:xs) = + liftM (char x <>) (processString xs) + processString "" = + return empty + + processString promptS +mkPrompt :: GHCi String +mkPrompt = do + st <- getGHCiState dflags <- getDynFlags - return (showSDoc dflags (f (prompt st))) + (context, modules_names, line) <- getInfoForPrompt + prompt_string <- (prompt st) modules_names line + let prompt_doc = context <> prompt_string + + return (showSDoc dflags prompt_doc) queryQueue :: GHCi (Maybe String) queryQueue = do @@ -811,7 +934,7 @@ runOneCommand eh gCmd = do multiLineCmd q = do st <- getGHCiState let p = prompt st - setGHCiState st{ prompt = prompt2 st } + setGHCiState st{ prompt = prompt_cont st } mb_cmd <- collectCommand q "" `GHC.gfinally` modifyGHCiState (\st' -> st' { prompt = p }) return mb_cmd @@ -904,7 +1027,7 @@ checkInputForLayout stmt getStmt = do _other -> do st1 <- getGHCiState let p = prompt st1 - setGHCiState st1{ prompt = prompt2 st1 } + setGHCiState st1{ prompt = prompt_cont st1 } mb_stmt <- ghciHandle (\ex -> case fromException ex of Just UserInterrupt -> return Nothing _ -> case fromException ex of @@ -2276,8 +2399,18 @@ setCmd str case toArgs rest of Right [prog] -> setProg prog _ -> liftIO (hPutStrLn stderr "syntax: :set prog <progname>") - Right ("prompt", rest) -> setPrompt $ dropWhile isSpace rest - Right ("prompt2", rest) -> setPrompt2 $ dropWhile isSpace rest + + Right ("prompt", rest) -> + setPromptString setPrompt (dropWhile isSpace rest) + "syntax: set prompt <string>" + Right ("prompt-function", rest) -> + setPromptFunc setPrompt $ dropWhile isSpace rest + Right ("prompt-cont", rest) -> + setPromptString setPromptCont (dropWhile isSpace rest) + "syntax: :set prompt-cont <string>" + Right ("prompt-cont-function", rest) -> + setPromptFunc setPromptCont $ dropWhile isSpace rest + Right ("editor", rest) -> setEditor $ dropWhile isSpace rest Right ("stop", rest) -> setStop $ dropWhile isSpace rest _ -> case toArgs str of @@ -2371,30 +2504,47 @@ setStop str@(c:_) | isDigit c setGHCiState st{ breaks = new_breaks } setStop cmd = modifyGHCiState (\st -> st { stop = cmd }) -setPrompt :: String -> GHCi () -setPrompt = setPrompt_ f err - where - f v st = st { prompt = v } - err st = "syntax: :set prompt <prompt>, currently \"" ++ prompt st ++ "\"" +setPrompt :: PromptFunction -> GHCi () +setPrompt v = modifyGHCiState (\st -> st {prompt = v}) -setPrompt2 :: String -> GHCi () -setPrompt2 = setPrompt_ f err - where - f v st = st { prompt2 = v } - err st = "syntax: :set prompt2 <prompt>, currently \"" ++ prompt2 st ++ "\"" +setPromptCont :: PromptFunction -> GHCi () +setPromptCont v = modifyGHCiState (\st -> st {prompt_cont = v}) -setPrompt_ :: (String -> GHCiState -> GHCiState) -> (GHCiState -> String) -> String -> GHCi () -setPrompt_ f err value = do - st <- getGHCiState +setPromptFunc :: (PromptFunction -> GHCi ()) -> String -> GHCi () +setPromptFunc fSetPrompt s = do + -- We explicitly annotate the type of the expression to ensure + -- that unsafeCoerce# is passed the exact type necessary rather + -- than a more general one + let exprStr = "(" ++ s ++ ") :: [String] -> Int -> IO String" + (HValue funValue) <- GHC.compileExpr exprStr + fSetPrompt (convertToPromptFunction $ unsafeCoerce funValue) + where + convertToPromptFunction :: ([String] -> Int -> IO String) + -> PromptFunction + convertToPromptFunction func = (\mods line -> liftIO $ + liftM text (func mods line)) + +setPromptString :: (PromptFunction -> GHCi ()) -> String -> String -> GHCi () +setPromptString fSetPrompt value err = do if null value - then liftIO $ hPutStrLn stderr $ err st - else case value of - '\"' : _ -> case reads value of - [(value', xs)] | all isSpace xs -> - setGHCiState $ f value' st - _ -> - liftIO $ hPutStrLn stderr "Can't parse prompt string. Use Haskell syntax." - _ -> setGHCiState $ f value st + then liftIO $ hPutStrLn stderr $ err + else case value of + ('\"':_) -> + case reads value of + [(value', xs)] | all isSpace xs -> + setParsedPromptString fSetPrompt value' + _ -> liftIO $ hPutStrLn stderr + "Can't parse prompt string. Use Haskell syntax." + _ -> + setParsedPromptString fSetPrompt value + +setParsedPromptString :: (PromptFunction -> GHCi ()) -> String -> GHCi () +setParsedPromptString fSetPrompt s = do + case (checkPromptStringForErrors s) of + Just err -> + liftIO $ hPutStrLn stderr err + Nothing -> + fSetPrompt $ generatePromptFunctionFromString s setOptions wds = do -- first, deal with the GHCi opts (+s, +t, etc.) @@ -2480,8 +2630,8 @@ unsetOptions str defaulters = [ ("args" , setArgs default_args) , ("prog" , setProg default_progname) - , ("prompt" , setPrompt default_prompt) - , ("prompt2", setPrompt2 default_prompt2) + , ("prompt" , setPrompt default_prompt) + , ("prompt-cont", setPromptCont default_prompt_cont) , ("editor" , liftIO findEditor >>= setEditor) , ("stop" , setStop default_stop) ] @@ -2559,8 +2709,6 @@ showCmd str = do cmds = [ action "args" $ liftIO $ putStrLn (show (GhciMonad.args st)) , action "prog" $ liftIO $ putStrLn (show (progname st)) - , action "prompt" $ liftIO $ putStrLn (show (prompt st)) - , action "prompt2" $ liftIO $ putStrLn (show (prompt2 st)) , action "editor" $ liftIO $ putStrLn (show (editor st)) , action "stop" $ liftIO $ putStrLn (show (stop st)) , action "imports" $ showImports @@ -2868,7 +3016,8 @@ listHomeModules w = do completeSetOptions = wrapCompleter flagWordBreakChars $ \w -> do return (filter (w `isPrefixOf`) opts) - where opts = "args":"prog":"prompt":"prompt2":"editor":"stop":flagList + where opts = "args":"prog":"prompt":"prompt-cont":"prompt-function": + "prompt-cont-function":"editor":"stop":flagList flagList = map head $ group $ sort allNonDeprecatedFlags completeSeti = wrapCompleter flagWordBreakChars $ \w -> do @@ -2877,7 +3026,7 @@ completeSeti = wrapCompleter flagWordBreakChars $ \w -> do completeShowOptions = wrapCompleter flagWordBreakChars $ \w -> do return (filter (w `isPrefixOf`) opts) - where opts = ["args", "prog", "prompt", "prompt2", "editor", "stop", + where opts = ["args", "prog", "editor", "stop", "modules", "bindings", "linker", "breaks", "context", "packages", "paths", "language", "imports"] diff --git a/ghc/GHCi/UI/Monad.hs b/ghc/GHCi/UI/Monad.hs index 306fa2132f..260d92c008 100644 --- a/ghc/GHCi/UI/Monad.hs +++ b/ghc/GHCi/UI/Monad.hs @@ -15,6 +15,7 @@ module GHCi.UI.Monad ( GHCiState(..), setGHCiState, getGHCiState, modifyGHCiState, GHCiOption(..), isOptionSet, setOption, unsetOption, Command(..), + PromptFunction, BreakLocation(..), TickArray, getDynFlags, @@ -67,8 +68,8 @@ data GHCiState = GHCiState progname :: String, args :: [String], evalWrapper :: ForeignHValue, -- ^ of type @IO a -> IO a@ - prompt :: String, - prompt2 :: String, + prompt :: PromptFunction, + prompt_cont :: PromptFunction, editor :: String, stop :: String, options :: [GHCiOption], @@ -137,6 +138,10 @@ data Command -- ^ 'CompletionFunc' for arguments } +type PromptFunction = [String] + -> Int + -> GHCi SDoc + data GHCiOption = ShowTiming -- show time/allocs after evaluation | ShowType -- show the type of expressions |