summaryrefslogtreecommitdiff
path: root/ghc
diff options
context:
space:
mode:
authorniksaz <nikitasazanovich@gmail.com>2016-05-01 13:34:45 +0200
committerBen Gamari <ben@smart-cactus.org>2016-05-01 23:29:49 +0200
commit533037cc58a7c50e1c014e27e8b971d53e7b47bd (patch)
tree6bdc053c60cc3b8d2254a0cc14569c2207c317f8 /ghc
parent18676a4a0dfe79e2704e48be5c8716a656825efe (diff)
downloadhaskell-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.hs257
-rw-r--r--ghc/GHCi/UI/Monad.hs9
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