Skip to content

Commit

Permalink
autocomplete entity names in the repl (#798)
Browse files Browse the repository at this point in the history
  • Loading branch information
kostmo authored Oct 29, 2022
1 parent 0efc6b5 commit 8c01fc1
Showing 1 changed file with 49 additions and 8 deletions.
57 changes: 49 additions & 8 deletions src/Swarm/TUI/Controller.hs
Original file line number Diff line number Diff line change
Expand Up @@ -784,23 +784,64 @@ handleREPLEvent = \case
newform <- use $ uiState . to (set promptUpdateL t)
uiState . uiReplForm .= newform

-- | Try to complete the last word in a partially entered REPL prompt using
-- things reserved words and names in scope.
data CompletionType
= FunctionName
| EntityName
deriving (Eq)

-- | Try to complete the last word in a partially-entered REPL prompt using
-- reserved words and names in scope (in the case of function names) or
-- entity names (in the case of string literals).
tabComplete :: AppState -> REPLPrompt -> REPLPrompt
tabComplete _ p@(SearchPrompt {}) = p
tabComplete s (CmdPrompt t mms)
| (m : ms) <- mms = CmdPrompt (replaceLast m t) (ms ++ [m])
| T.null lastWord = CmdPrompt t []
| otherwise = case matches of
-- Case 1: If completion candidates have already been
-- populated via case (3), cycle through them.
-- Note that tabbing through the candidates *does* update the value
-- of "t", which one might think would narrow the candidate list
-- to only that match and therefore halt the cycling.
-- However, the candidate list only gets recomputed (repopulated)
-- if the user subsequently presses a non-Tab key. Thus the current
-- value of "t" is ignored for all Tab presses subsequent to the
-- first.
| (m : ms) <- mms = CmdPrompt (replacementFunc m) (ms ++ [m])
-- Case 2: Require at least one letter to be typed in order to offer completions for
-- function names.
-- We allow suggestions for Entity Name strings without anything having been typed.
| T.null lastWord && completionType == FunctionName = CmdPrompt t []
-- Case 3: Typing another character in the REPL clears the completion candidates from
-- the CmdPrompt, so when Tab is pressed again, this case then gets executed and
-- repopulates them.
| otherwise = case candidateMatches of
[] -> CmdPrompt t []
[m] -> CmdPrompt (completeWith m) []
-- Perform completion with the first candidate, then populate the list
-- of all candidates with the current completion moved to the back
-- of the queue.
(m : ms) -> CmdPrompt (completeWith m) (ms ++ [m])
where
completeWith m = T.append t (T.drop (T.length lastWord) m)
lastWord = T.takeWhileEnd isIdentChar t
-- checks the "parity" of the number of quotes. If odd, then there is an open quote.
hasOpenQuotes = (== 1) . (`mod` 2) . T.count "\""

completionType =
if hasOpenQuotes t
then EntityName
else FunctionName

replacementFunc = T.append $ T.dropWhileEnd replacementBoundaryPredicate t
completeWith m = T.append t $ T.drop (T.length lastWord) m
lastWord = T.takeWhileEnd replacementBoundaryPredicate t
candidateMatches = filter (lastWord `T.isPrefixOf`) replacementCandidates

(replacementCandidates, replacementBoundaryPredicate) = case completionType of
EntityName -> (entityNames, (/= '"'))
FunctionName -> (possibleWords, isIdentChar)

names = s ^.. gameState . baseRobot . robotContext . defTypes . to assocs . traverse . _1
possibleWords = reservedWords ++ names
matches = filter (lastWord `T.isPrefixOf`) possibleWords

theEntityMap = s ^. gameState . entityMap
entityNames = M.keys $ entitiesByName theEntityMap

-- | Validate the REPL input when it changes: see if it parses and
-- typechecks, and set the color accordingly.
Expand Down

0 comments on commit 8c01fc1

Please sign in to comment.