on master help sign in
Merge pull request #180 from david-christiansen/completion-work
d53fccfEdwin Brady
src/Core/ProofShell.hs+18−16
@@ -58,20 +58,22 @@ processCommand (Tac e) state
58 | otherwise = (state, "No proof in progress")
58 | otherwise = (state, "No proof in progress")
59
59
60runShell :: ShellState -> Idris ShellState
60runShell :: ShellState -> Idris ShellState
61+runShell st = runInputT defaultSettings $ runShell' st
62+ where
61−runShell st = do (prompt, parser) <-
63+ runShell' st = do (prompt, parser) <-
62− maybe (return ("TT# ", parseCommand))
64+ maybe (return ("TT# ", parseCommand))
63− (\st -> do outputStrLn . render . pretty $ st
65+ (\st -> do outputStrLn . render . pretty $ st
64− return (show (thname st) ++ "# ", parseTactic))
66+ return (show (thname st) ++ "# ", parseTactic))
65− (prf st)
67+ (prf st)
66− x <- getInputLine prompt
68+ x <- getInputLine prompt
67− cmd <- case x of
69+ cmd <- case x of
68− Nothing -> return $ Right Quit
70+ Nothing -> return $ Right Quit
69− Just input -> return (parser input)
71+ Just input -> return (parser input)
70− case cmd of
72+ case cmd of
71− Left err -> do outputStrLn (show err)
73+ Left err -> do outputStrLn (show err)
72− runShell st
74+ runShell' st
73− Right cmd -> do let (st', r) = processCommand cmd st
75+ Right cmd -> do let (st', r) = processCommand cmd st
74− outputStrLn r
76+ outputStrLn r
75− if (not (exitNow st')) then runShell st'
77+ if (not (exitNow st')) then runShell' st'
76− else return st'
78+ else return st'
77
79
src/Idris/AbsSyntax.hs+2−2
@@ -148,10 +148,10 @@ setSO s = do i <- getIState
148 putIState $ (i { compiled_so = s })
148 putIState $ (i { compiled_so = s })
149
149
150getIState :: Idris IState
150getIState :: Idris IState
151−getIState = lift get
151+getIState = get
152
152
153putIState :: IState -> Idris ()
153putIState :: IState -> Idris ()
154−putIState = lift . put
154+putIState = put
155
155
156getName :: Idris Int
156getName :: Idris Int
157getName = do i <- getIState;
157getName = do i <- getIState;
src/Idris/AbsSyntaxTree.hs+1−1
@@ -136,7 +136,7 @@ idrisInit = IState initContext [] [] emptyContext emptyContext emptyContext
136
136
137-- The monad for the main REPL - reading and processing files and updating
137-- The monad for the main REPL - reading and processing files and updating
138-- global state (hence the IO inner monad).
138-- global state (hence the IO inner monad).
139−type Idris = InputT (StateT IState IO)
139+type Idris = StateT IState IO
140
140
141-- Commands in the REPL
141-- Commands in the REPL
142
142
src/Idris/Completion.hsnew file+143
@@ -0,0 +1,143 @@
1+module Idris.Completion (replCompletion, proverCompletion) where
2+
3+import Core.Evaluate (ctxtAlist)
4+import Core.TT
5+
6+import Idris.AbsSyntaxTree
7+
8+import Control.Monad.State.Strict
9+
10+import Data.List
11+import Data.Maybe
12+
13+import System.Console.Haskeline
14+
15+-- TODO: add specific classes of identifiers to complete, fx metavariables
16+-- | A specification of the arguments that REPL commands can take
17+data CmdArg = ExprArg -- ^ The command takes an expression
18+ | NameArg -- ^ The command takes a name
19+ | FileArg -- ^ The command takes a file
20+ | ModuleArg -- ^ The command takes a module name
21+ | OptionArg -- ^ The command takes an option
22+
23+-- | Information about how to complete the various commands
24+cmdArgs :: [(String, CmdArg)]
25+cmdArgs = [ (":t", ExprArg)
26+ , (":miss", NameArg)
27+ , (":missing", NameArg)
28+ , (":i", NameArg)
29+ , (":info", NameArg)
30+ , (":total", NameArg)
31+ , (":l", FileArg)
32+ , (":load", FileArg)
33+ , (":m", ModuleArg) -- NOTE: Argumentless form is a different command
34+ , (":p", NameArg)
35+ , (":prove", NameArg)
36+ , (":a", NameArg)
37+ , (":addproof", NameArg)
38+ , (":rmproof", NameArg)
39+ , (":showproof", NameArg)
40+ , (":c", FileArg)
41+ , (":compile", FileArg)
42+ , (":js", FileArg)
43+ , (":javascript", FileArg)
44+ , (":set", OptionArg)
45+ , (":unset", OptionArg)
46+ ]
47+commands = map fst cmdArgs
48+
49+-- | A specification of the arguments that tactics can take
50+data TacticArg = NameTArg -- ^ Names: n1, n2, n3, ... n
51+ | ExprTArg
52+ | AltsTArg
53+
54+-- | A list of available tactics and their argument requirements
55+tacticArgs :: [(String, Maybe TacticArg)]
56+tacticArgs = [ ("intro", Nothing)
57+ , ("intros", Nothing)
58+ , ("refine", Just ExprTArg)
59+ , ("rewrite", Just ExprTArg)
60+ , ("let", Nothing) -- FIXME syntax for let
61+ , ("focus", Just ExprTArg)
62+ , ("exact", Just ExprTArg)
63+ , ("reflect", Just ExprTArg)
64+ , ("try", Just AltsTArg)
65+ ] ++ map (\x -> (x, Nothing)) [
66+ "intro", "intros", "compute", "trivial", "solve", "attack",
67+ "state", "term", "undo", "qed", "abandon", ":q"
68+ ]
69+tactics = map fst tacticArgs
70+
71+-- | Convert a name into a string usable for completion. Filters out names
72+-- that users probably don't want to see.
73+nameString :: Name -> Maybe String
74+nameString (UN ('@':_)) = Nothing
75+nameString (UN ('#':_)) = Nothing
76+nameString (UN n) = Just n
77+nameString (NS n _) = nameString n
78+nameString _ = Nothing
79+
80+-- FIXME: Respect module imports
81+-- | Get the user-visible names from the current interpreter state.
82+names :: Idris[String]
83+names = do i <- get
84+ let ctxt = tt_ctxt i
85+ return $ nub $ mapMaybe (nameString . fst) $ ctxtAlist ctxt
86+
87+
88+modules :: Idris [String]
89+modules = do i <- get
90+ return $ map show $ imported i
91+
92+
93+
94+completeWith :: [String] -> String -> [Completion]
95+completeWith ns n = if uniqueExists
96+ then [simpleCompletion n]
97+ else map simpleCompletion prefixMatches
98+ where prefixMatches = filter (isPrefixOf n) ns
99+ uniqueExists = n `elem` prefixMatches
100+
101+completeName :: [String] -> String -> Idris [Completion]
102+completeName extra n = do ns <- names
103+ return $ completeWith (extra ++ ns) n
104+
105+completeExpr :: [String] -> CompletionFunc Idris
106+completeExpr extra = completeWord Nothing " \t" (completeName extra)
107+
108+completeOption :: CompletionFunc Idris
109+completeOption = completeWord Nothing " \t" completeOpt
110+ where completeOpt = return . (completeWith ["errorcontext", "showimplicits"])
111+
112+isWhitespace :: Char -> Bool
113+isWhitespace = (flip elem) " \t\n"
114+
115+-- | Get the completion function for a particular command
116+completeCmd :: String -> CompletionFunc Idris
117+completeCmd cmd (prev, next) = fromMaybe completeCmdName $ fmap completeArg $ lookup cmd cmdArgs
118+ where completeArg FileArg = completeFilename (prev, next)
119+ completeArg NameArg = completeExpr [] (prev, next) -- FIXME only complete one name
120+ completeArg OptionArg = completeOption (prev, next)
121+ completeArg ModuleArg = noCompletion (prev, next) -- FIXME do later
122+ completeArg ExprArg = completeExpr [] (prev, next)
123+ completeCmdName = return $ ("", completeWith commands cmd)
124+
125+replCompletion :: CompletionFunc Idris
126+replCompletion (prev, next) = case firstWord of
127+ ':':cmdName -> completeCmd (':':cmdName) (prev, next)
128+ _ -> completeExpr [] (prev, next)
129+ where firstWord = fst $ break isWhitespace $ dropWhile isWhitespace $ reverse prev
130+
131+
132+completeTactic :: [String] -> String -> CompletionFunc Idris
133+completeTactic as tac (prev, next) = fromMaybe completeTacName $ fmap completeArg $ lookup tac tacticArgs
134+ where completeTacName = return $ ("", completeWith tactics tac)
135+ completeArg Nothing = noCompletion (prev, next)
136+ completeArg (Just NameTArg) = noCompletion (prev, next) -- this is for binding new names!
137+ completeArg (Just ExprTArg) = completeExpr as (prev, next)
138+ completeArg (Just AltsTArg) = noCompletion (prev, next) -- TODO
139+
140+
141+proverCompletion :: [String] -> CompletionFunc Idris
142+proverCompletion assumptions (prev, next) = completeTactic assumptions firstWord (prev, next)
143+ where firstWord = fst $ break isWhitespace $ dropWhile isWhitespace $ reverse prev
src/Idris/Prover.hs+24−7
@@ -13,8 +13,10 @@ import Idris.ElabTerm
13import Idris.Parser
13import Idris.Parser
14import Idris.Error
14import Idris.Error
15import Idris.DataOpts
15import Idris.DataOpts
16+import Idris.Completion
16
17
17import System.Console.Haskeline
18import System.Console.Haskeline
19+import System.Console.Haskeline.History
18import Control.Monad.State
20import Control.Monad.State
19
21
20import Util.Pretty
22import Util.Pretty
@@ -37,10 +39,18 @@ showProof lit n ps
37 where bird = if lit then "> " else ""
39 where bird = if lit then "> " else ""
38 break = "\n" ++ bird
40 break = "\n" ++ bird
39
41
42+proverSettings :: ElabState [PDecl] -> Settings Idris
43+proverSettings e = setComplete (proverCompletion assumptionNames) defaultSettings
44+ where assumptionNames = case envAtFocus (proof e) of
45+ OK env -> names env
46+ names [] = []
47+ names ((MN _ _, _) : bs) = names bs
48+ names ((n, _) : bs) = show n : names bs
49+
40prove :: Context -> Bool -> Name -> Type -> Idris ()
50prove :: Context -> Bool -> Name -> Type -> Idris ()
41prove ctxt lit n ty
51prove ctxt lit n ty
42 = do let ps = initElaborator n ctxt ty
52 = do let ps = initElaborator n ctxt ty
43− (tm, prf) <- ploop True ("-" ++ show n) [] (ES (ps, []) "" Nothing)
53+ (tm, prf) <- ploop True ("-" ++ show n) [] (ES (ps, []) "" Nothing) Nothing
44 iLOG $ "Adding " ++ show tm
54 iLOG $ "Adding " ++ show tm
45 iputStrLn $ showProof lit n prf
55 iputStrLn $ showProof lit n prf
46 i <- getIState
56 i <- getIState
@@ -108,18 +118,25 @@ lifte :: ElabState [PDecl] -> ElabD a -> Idris a
108lifte st e = do (v, _) <- elabStep st e
118lifte st e = do (v, _) <- elabStep st e
109 return v
119 return v
110
120
111−ploop :: Bool -> String -> [String] -> ElabState [PDecl] -> Idris (Term, [String])
121+ploop :: Bool -> String -> [String] -> ElabState [PDecl] -> Maybe History -> Idris (Term, [String])
112−ploop d prompt prf e
122+ploop d prompt prf e h
113 = do i <- getIState
123 = do i <- getIState
114 when d $ liftIO $ dumpState i (proof e)
124 when d $ liftIO $ dumpState i (proof e)
125+ (x, h') <- runInputT (proverSettings e) $
126+ -- Manually track the history so that we can use the proof state
127+ do _ <- case h of
128+ Just history -> putHistory history
129+ Nothing -> return ()
115− x <- getInputLine (prompt ++ "> ")
130+ l <- getInputLine (prompt ++ "> ")
131+ h' <- getHistory
132+ return (l, Just h')
116 (cmd, step) <- case x of
133 (cmd, step) <- case x of
117 Nothing -> fail "Abandoned"
134 Nothing -> fail "Abandoned"
118 Just input -> do return (parseTac i input, input)
135 Just input -> do return (parseTac i input, input)
119 case cmd of
136 case cmd of
120 Right Abandon -> fail "Abandoned"
137 Right Abandon -> fail "Abandoned"
121 _ -> return ()
138 _ -> return ()
122− (d, st, done, prf') <- idrisCatch
139+ (d, st, done, prf') <- idrisCatch
123 (case cmd of
140 (case cmd of
124 Left err -> do iputStrLn (show err)
141 Left err -> do iputStrLn (show err)
125 return (False, e, False, prf)
142 return (False, e, False, prf)
@@ -140,7 +157,7 @@ ploop d prompt prf e
140 return (True, st, False, prf ++ [step]))
157 return (True, st, False, prf ++ [step]))
141 (\err -> do iputStrLn (show err)
158 (\err -> do iputStrLn (show err)
142 return (False, e, False, prf))
159 return (False, e, False, prf))
143− if done then do (tm, _) <- elabStep st get_term
160+ if done then do (tm, _) <- elabStep st get_term
144 return (tm, prf')
161 return (tm, prf')
145− else ploop d prompt prf' st
162+ else ploop d prompt prf' st h'
146
163
src/Idris/REPL.hs+10−13
@@ -16,6 +16,7 @@ import Idris.Primitives
16import Idris.Coverage
16import Idris.Coverage
17import Idris.UnusedArgs
17import Idris.UnusedArgs
18import Idris.Docs
18import Idris.Docs
19+import Idris.Completion
19
20
20import Paths_idris
21import Paths_idris
21import Util.System
22import Util.System
@@ -49,23 +50,23 @@ import Data.List
49import Data.Char
50import Data.Char
50import Data.Version
51import Data.Version
51
52
52−repl :: IState -> [FilePath] -> Idris ()
53+repl :: IState -> [FilePath] -> InputT Idris ()
53repl orig mods
54repl orig mods
54 = H.catch
55 = H.catch
55 (do let prompt = mkPrompt mods
56 (do let prompt = mkPrompt mods
56 x <- getInputLine (prompt ++ "> ")
57 x <- getInputLine (prompt ++ "> ")
57 case x of
58 case x of
58− Nothing -> do iputStrLn "Bye bye"
59+ Nothing -> do lift $ iputStrLn "Bye bye"
59 return ()
60 return ()
60 Just input -> H.catch
61 Just input -> H.catch
61− (do ms <- processInput input orig mods
62+ (do ms <- lift $ processInput input orig mods
62 case ms of
63 case ms of
63 Just mods -> repl orig mods
64 Just mods -> repl orig mods
64 Nothing -> return ())
65 Nothing -> return ())
65 ctrlC)
66 ctrlC)
66 ctrlC
67 ctrlC
67− where ctrlC :: SomeException -> Idris ()
68+ where ctrlC :: SomeException -> InputT Idris ()
68− ctrlC e = do iputStrLn (show e)
69+ ctrlC e = do lift $ iputStrLn (show e)
69 repl orig mods
70 repl orig mods
70
71
71mkPrompt [] = "Idris"
72mkPrompt [] = "Idris"
@@ -489,17 +490,13 @@ help =
489 ([":q",":quit"], "", "Exit the Idris system")
490 ([":q",":quit"], "", "Exit the Idris system")
490 ]
491 ]
491
492
492−idrisCompletion :: CompletionFunc (StateT IState IO)
493−idrisCompletion (before, after) = return (before, [])
494
493
495−haskelineSettings :: Settings (StateT IState IO)
494+replSettings :: Settings Idris
496−haskelineSettings = setComplete idrisCompletion defaultSettings
495+replSettings = setComplete replCompletion defaultSettings
497
496
498-- invoke as if from command line
497-- invoke as if from command line
499idris :: [Opt] -> IO IState
498idris :: [Opt] -> IO IState
500−idris opts = execStateT (runInputT haskelineSettings $ idrisMain opts) idrisInit
501−
502−--runInputT haskelineSettings $ execStateT (idrisMain opts) idrisInit
499+idris opts = execStateT (idrisMain opts) idrisInit
503
500
504idrisMain :: [Opt] -> Idris ()
501idrisMain :: [Opt] -> Idris ()
505idrisMain opts =
502idrisMain opts =
@@ -554,7 +551,7 @@ idrisMain opts =
554 when ok $ case newoutput of
551 when ok $ case newoutput of
555 [] -> return ()
552 [] -> return ()
556 (o:_) -> process "" (NewCompile o)
553 (o:_) -> process "" (NewCompile o)
557− when runrepl $ repl ist inputs
554+ when runrepl $ runInputT replSettings $ repl ist inputs
558 ok <- noErrors
555 ok <- noErrors
559 when (not ok) $ liftIO (exitWith (ExitFailure 1))
556 when (not ok) $ liftIO (exitWith (ExitFailure 1))
560 where
557 where
src/Main.hs+1−1
@@ -39,7 +39,7 @@ import Paths_idris
39
39
40main = do xs <- getArgs
40main = do xs <- getArgs
41 let opts = parseArgs xs
41 let opts = parseArgs xs
42− execStateT (runInputT haskelineSettings $ runIdris opts) idrisInit
42+ execStateT (runIdris opts) idrisInit
43
43
44runIdris :: [Opt] -> Idris ()
44runIdris :: [Opt] -> Idris ()
45runIdris opts = do
45runIdris opts = do