module Language.PureScript.Interactive.Completion
( CompletionM
, liftCompletionM
, completion
, completion'
, formatCompletions
) where
import Prelude.Compat
import Protolude (ordNub)
import Control.Monad.IO.Class (MonadIO(..))
import Control.Monad.State.Class (MonadState(..))
import Control.Monad.Trans.Reader (asks, runReaderT, ReaderT)
import Data.List (nub, isPrefixOf, isInfixOf, isSuffixOf, sortBy, stripPrefix)
import Data.Map (keys)
import Data.Maybe (mapMaybe)
import qualified Data.Text as T
import qualified Language.PureScript as P
import qualified Language.PureScript.Interactive.Directive as D
import Language.PureScript.Interactive.Types
import System.Console.Haskeline
type CompletionM = ReaderT PSCiState IO
liftCompletionM
:: (MonadState PSCiState m, MonadIO m)
=> CompletionM a
-> m a
liftCompletionM act = do
st <- get
liftIO $ runReaderT act st
completion
:: (MonadState PSCiState m, MonadIO m)
=> CompletionFunc m
completion = liftCompletionM . completion'
completion' :: CompletionFunc CompletionM
completion' = completeWordWithPrev Nothing " \t\n\r([" findCompletions
findCompletions :: String -> String -> CompletionM [Completion]
findCompletions prev word = do
let ctx = completionContext (words (reverse prev)) word
completions <- concat <$> traverse getCompletions ctx
return $ sortBy directivesFirst completions
where
getCompletions :: CompletionContext -> CompletionM [Completion]
getCompletions = fmap (mapMaybe (either (prefixedBy word) Just)) . getCompletion
getCompletion :: CompletionContext -> CompletionM [Either String Completion]
getCompletion ctx =
case ctx of
CtxFilePath f -> map Right <$> listFiles f
CtxModule -> map Left <$> getModuleNames
CtxIdentifier -> map Left <$> ((++) <$> getIdentNames <*> getDctorNames)
CtxType pre -> map (Left . (pre ++)) <$> getTypeNames
CtxFixed str -> return [Left str]
CtxDirective d -> return (map Left (completeDirectives d))
completeDirectives :: String -> [String]
completeDirectives = map (':' :) . D.directiveStringsFor
prefixedBy :: String -> String -> Maybe Completion
prefixedBy w cand = if w `isPrefixOf` cand
then Just (simpleCompletion cand)
else Nothing
directivesFirst :: Completion -> Completion -> Ordering
directivesFirst (Completion _ d1 _) (Completion _ d2 _) = go d1 d2
where
go (':' : xs) (':' : ys) = compare xs ys
go (':' : _) _ = LT
go _ (':' : _) = GT
go xs ys = compare xs ys
formatCompletions :: (String, [Completion]) -> [String]
formatCompletions (unusedR, completions) = actuals
where
unused = reverse unusedR
actuals = map ((unused ++) . replacement) completions
data CompletionContext
= CtxDirective String
| CtxFilePath String
| CtxModule
| CtxIdentifier
| CtxType String
| CtxFixed String
deriving (Show)
completionContext :: [String] -> String -> [CompletionContext]
completionContext _ w | "::" `isInfixOf` w = [CtxType (w `endingWith` "::")]
completionContext ws _ | lastSatisfies ("::" `isSuffixOf`) ws = [CtxType ""]
completionContext [] _ = [CtxDirective "", CtxIdentifier, CtxFixed "import"]
completionContext ws w | headSatisfies (":" `isPrefixOf`) ws = completeDirective ws w
completionContext ws w | headSatisfies (== "import") ws = completeImport ws w
completionContext _ _ = [CtxIdentifier]
endingWith :: String -> String -> String
endingWith str stop = aux "" str
where
aux acc s@(x:xs)
| stop `isPrefixOf` s = reverse (stop ++ acc)
| otherwise = aux (x:acc) xs
aux acc [] = reverse (stop ++ acc)
completeDirective :: [String] -> String -> [CompletionContext]
completeDirective ws w =
case ws of
[] -> [CtxDirective w]
(x:xs) -> case D.directivesFor <$> stripPrefix ":" x of
Just [dir] -> directiveArg xs dir
_ -> []
directiveArg :: [String] -> Directive -> [CompletionContext]
directiveArg [] Browse = [CtxModule]
directiveArg [] Show = map CtxFixed replQueryStrings
directiveArg _ Type = [CtxIdentifier]
directiveArg _ Kind = [CtxType ""]
directiveArg _ _ = []
completeImport :: [String] -> String -> [CompletionContext]
completeImport ws w' =
case (ws, w') of
(["import"], _) -> [CtxModule]
_ -> []
headSatisfies :: (a -> Bool) -> [a] -> Bool
headSatisfies p str =
case str of
(c:_) -> p c
_ -> False
lastSatisfies :: (a -> Bool) -> [a] -> Bool
lastSatisfies _ [] = False
lastSatisfies p xs = p (last xs)
getLoadedModules :: CompletionM [P.Module]
getLoadedModules = asks (map fst . psciLoadedExterns)
getModuleNames :: CompletionM [String]
getModuleNames = moduleNames <$> getLoadedModules
getIdentNames :: CompletionM [String]
getIdentNames = do
importedVals <- asks (keys . P.importedValues . psciImports)
exportedVals <- asks (keys . P.exportedValues . psciExports)
importedValOps <- asks (keys . P.importedValueOps . psciImports)
exportedValOps <- asks (keys . P.exportedValueOps . psciExports)
return . nub $ map (T.unpack . P.showQualified P.showIdent) importedVals
++ map (T.unpack . P.showQualified P.runOpName) importedValOps
++ map (T.unpack . P.showIdent) exportedVals
++ map (T.unpack . P.runOpName) exportedValOps
getDctorNames :: CompletionM [String]
getDctorNames = do
imports <- asks (keys . P.importedDataConstructors . psciImports)
return . nub $ map (T.unpack . P.showQualified P.runProperName) imports
getTypeNames :: CompletionM [String]
getTypeNames = do
importedTypes <- asks (keys . P.importedTypes . psciImports)
exportedTypes <- asks (keys . P.exportedTypes . psciExports)
importedTypeOps <- asks (keys . P.importedTypeOps . psciImports)
exportedTypeOps <- asks (keys . P.exportedTypeOps . psciExports)
return . nub $ map (T.unpack . P.showQualified P.runProperName) importedTypes
++ map (T.unpack . P.showQualified P.runOpName) importedTypeOps
++ map (T.unpack . P.runProperName) exportedTypes
++ map (T.unpack . P.runOpName) exportedTypeOps
moduleNames :: [P.Module] -> [String]
moduleNames = ordNub . map (T.unpack . P.runModuleName . P.getModuleName)