From 27ecb32ade06bb20032fdc580c5a3dbc4f63db88 Mon Sep 17 00:00:00 2001 From: fwcd Date: Sat, 13 Jun 2020 00:20:33 +0200 Subject: [PATCH 01/11] WIP: Find expression at cursor in Completions --- src/Curry/LanguageServer/Features/Completion.hs | 14 +++++++++----- src/Curry/LanguageServer/Features/Definition.hs | 2 +- src/Curry/LanguageServer/Features/Hover.hs | 2 +- src/Curry/LanguageServer/Utils/Env.hs | 7 ++++--- 4 files changed, 15 insertions(+), 10 deletions(-) diff --git a/src/Curry/LanguageServer/Features/Completion.hs b/src/Curry/LanguageServer/Features/Completion.hs index 99fb334..6c80450 100644 --- a/src/Curry/LanguageServer/Features/Completion.hs +++ b/src/Curry/LanguageServer/Features/Completion.hs @@ -15,6 +15,7 @@ import Curry.LanguageServer.Logging import Curry.LanguageServer.Utils.Conversions (ppToText) import Curry.LanguageServer.Utils.General (rmDupsOn) import Curry.LanguageServer.Utils.Env (valueInfoType, typeInfoKind) +import Curry.LanguageServer.Utils.Syntax (elementAt, HasExpressions (..)) import qualified Data.Map as M import Data.Maybe (maybeToList) import qualified Data.Text as T @@ -23,12 +24,15 @@ import qualified Language.Haskell.LSP.Types.Lens as J fetchCompletions :: ModuleStoreEntry -> T.Text -> J.Position -> IO [J.CompletionItem] fetchCompletions entry query pos = do - -- TODO: Context-awareness (through nested envs?) let env = maybeToList $ compilerEnv entry - valueCompletions = valueBindingToCompletion <$> ((CT.allBindings . CE.valueEnv) =<< env) - typeCompletions = typeBindingToCompletion <$> ((CT.allBindings . CE.tyConsEnv) =<< env) - keywordCompletions = keywordToCompletion <$> keywords - completions = rmDupsOn (^. J.label) $ filter (matchesQuery query) $ valueCompletions ++ typeCompletions ++ keywordCompletions + maybeExpr = elementAt pos <$> expressions <$> moduleAST entry + completions = case maybeExpr of + Just expr -> [] + Nothing -> let valueCompletions = valueBindingToCompletion <$> ((CT.allBindings . CE.valueEnv) =<< env) + typeCompletions = typeBindingToCompletion <$> ((CT.allBindings . CE.tyConsEnv) =<< env) + keywordCompletions = keywordToCompletion <$> keywords + in rmDupsOn (^. J.label) $ filter (matchesQuery query) $ valueCompletions ++ typeCompletions ++ keywordCompletions + logs INFO $ "fetchCompletions: Found " ++ show (length completions) ++ " completions with query '" ++ show query ++ "'" return completions where keywords = ["case", "class", "data", "default", "deriving", "do", "else", "external", "fcase", "free", "if", "import", "in", "infix", "infixl", "infixr", "instance", "let", "module", "newtype", "of", "then", "type", "where", "as", "ccall", "forall", "hiding", "interface", "primitive", "qualified"] diff --git a/src/Curry/LanguageServer/Features/Definition.hs b/src/Curry/LanguageServer/Features/Definition.hs index 80e9fe0..d004509 100644 --- a/src/Curry/LanguageServer/Features/Definition.hs +++ b/src/Curry/LanguageServer/Features/Definition.hs @@ -27,7 +27,7 @@ fetchDefinitions store entry pos = do definition :: IndexStore -> J.Position -> LM J.Location definition store pos = do - (qident, spi) <- findAtPos pos + (qident, spi) <- qualIdentAtPos pos qident' <- lift $ runMaybeT $ CT.origName <$> lookupValueInfo qident let ident = CI.qidIdent $ qident ident' = CI.qidIdent <$> qident' diff --git a/src/Curry/LanguageServer/Features/Hover.hs b/src/Curry/LanguageServer/Features/Hover.hs index a8a259c..1d013a0 100644 --- a/src/Curry/LanguageServer/Features/Hover.hs +++ b/src/Curry/LanguageServer/Features/Hover.hs @@ -29,7 +29,7 @@ fetchHover entry pos = runMaybeT $ do hoverAt :: J.Position -> LM J.Hover hoverAt pos = do - (ident, spi) <- findAtPos pos + (ident, spi) <- qualIdentAtPos pos valueInfo <- lookupValueInfo ident let msg = J.HoverContents $ J.markedUpContent "curry" $ ppToText (CT.origName valueInfo) <> " :: " <> ppToText (valueInfoType valueInfo) range = currySpanInfo2Range spi diff --git a/src/Curry/LanguageServer/Utils/Env.hs b/src/Curry/LanguageServer/Utils/Env.hs index 5a2aaa0..8923cfc 100644 --- a/src/Curry/LanguageServer/Utils/Env.hs +++ b/src/Curry/LanguageServer/Utils/Env.hs @@ -4,7 +4,7 @@ module Curry.LanguageServer.Utils.Env ( LookupEnv, LM, runLM, - findAtPos, + qualIdentAtPos, valueInfoType, typeInfoKind ) where @@ -12,6 +12,7 @@ module Curry.LanguageServer.Utils.Env ( -- Curry Compiler Libraries + Dependencies import qualified Curry.Base.Ident as CI import qualified Curry.Base.SpanInfo as CSPI +import qualified Curry.Syntax as CS import qualified Base.Kinds as CK import qualified Base.Types as CT import qualified CompilerEnv as CE @@ -35,8 +36,8 @@ runLM :: LM a -> CE.CompilerEnv -> ModuleAST -> IO (Maybe a) runLM lm = curry $ runReaderT $ runMaybeT lm -- | Finds identifier and (occurrence) span info at a given position. -findAtPos :: J.Position -> LM (CI.QualIdent, CSPI.SpanInfo) -findAtPos pos = do +qualIdentAtPos :: J.Position -> LM (CI.QualIdent, CSPI.SpanInfo) +qualIdentAtPos pos = do (env, ast) <- lift ask let mident = moduleIdentifier ast exprIdent = joinFst $ qualIdentifier <.$> (withSpanInfo <$> (elementAt pos $ expressions ast)) From b463595b61abeac99bb55f87665adbea349cc04e Mon Sep 17 00:00:00 2001 From: fwcd Date: Sat, 13 Jun 2020 01:10:27 +0200 Subject: [PATCH 02/11] Split completions into different functions --- .../LanguageServer/Features/Completion.hs | 37 +++++++++++++------ src/Curry/LanguageServer/Utils/General.hs | 6 +++ test/resources/Test.curry | 4 +- 3 files changed, 33 insertions(+), 14 deletions(-) diff --git a/src/Curry/LanguageServer/Features/Completion.hs b/src/Curry/LanguageServer/Features/Completion.hs index 6c80450..76a5223 100644 --- a/src/Curry/LanguageServer/Features/Completion.hs +++ b/src/Curry/LanguageServer/Features/Completion.hs @@ -9,32 +9,47 @@ import qualified CompilerEnv as CE import qualified Env.TypeConstructor as CETC import qualified Env.Value as CEV +import Control.Applicative (Alternative (..)) import Control.Lens ((^.)) import Curry.LanguageServer.IndexStore (ModuleStoreEntry (..)) import Curry.LanguageServer.Logging import Curry.LanguageServer.Utils.Conversions (ppToText) import Curry.LanguageServer.Utils.General (rmDupsOn) import Curry.LanguageServer.Utils.Env (valueInfoType, typeInfoKind) -import Curry.LanguageServer.Utils.Syntax (elementAt, HasExpressions (..)) +import Curry.LanguageServer.Utils.Syntax (elementAt, ModuleAST, HasExpressions (..)) import qualified Data.Map as M -import Data.Maybe (maybeToList) +import Data.Maybe (maybeToList, isJust) import qualified Data.Text as T import qualified Language.Haskell.LSP.Types as J import qualified Language.Haskell.LSP.Types.Lens as J fetchCompletions :: ModuleStoreEntry -> T.Text -> J.Position -> IO [J.CompletionItem] fetchCompletions entry query pos = do - let env = maybeToList $ compilerEnv entry - maybeExpr = elementAt pos <$> expressions <$> moduleAST entry - completions = case maybeExpr of - Just expr -> [] - Nothing -> let valueCompletions = valueBindingToCompletion <$> ((CT.allBindings . CE.valueEnv) =<< env) - typeCompletions = typeBindingToCompletion <$> ((CT.allBindings . CE.tyConsEnv) =<< env) - keywordCompletions = keywordToCompletion <$> keywords - in rmDupsOn (^. J.label) $ filter (matchesQuery query) $ valueCompletions ++ typeCompletions ++ keywordCompletions + let env = compilerEnv entry + ast = moduleAST entry + completions = (expressionCompletions ast pos) <|> (generalCompletions env query) - logs INFO $ "fetchCompletions: Found " ++ show (length completions) ++ " completions with query '" ++ show query ++ "'" + logs INFO $ "fetchCompletions: Found " ++ show (length completions) ++ " completions with query '" ++ show query return completions + +expressionCompletions :: Maybe ModuleAST -> J.Position -> [J.CompletionItem] +expressionCompletions ast pos = do + expr <- maybeToList $ elementAt pos =<< (expressions <$> ast) + case expr of + -- TODO: Implement expression completions + _ -> [] + +generalCompletions :: Maybe CE.CompilerEnv -> T.Text -> [J.CompletionItem] +generalCompletions env query = rmDupsOn (^. J.label) $ filter (matchesQuery query) $ valueCompletions env ++ typeCompletions env ++ keywordCompletions + +valueCompletions :: Maybe CE.CompilerEnv -> [J.CompletionItem] +valueCompletions env = valueBindingToCompletion <$> ((CT.allBindings . CE.valueEnv) =<< maybeToList env) + +typeCompletions :: Maybe CE.CompilerEnv -> [J.CompletionItem] +typeCompletions env = typeBindingToCompletion <$> ((CT.allBindings . CE.tyConsEnv) =<< maybeToList env) + +keywordCompletions :: [J.CompletionItem] +keywordCompletions = keywordToCompletion <$> keywords where keywords = ["case", "class", "data", "default", "deriving", "do", "else", "external", "fcase", "free", "if", "import", "in", "infix", "infixl", "infixr", "instance", "let", "module", "newtype", "of", "then", "type", "where", "as", "ccall", "forall", "hiding", "interface", "primitive", "qualified"] -- | Tests whether a completion item matches the user's query. diff --git a/src/Curry/LanguageServer/Utils/General.hs b/src/Curry/LanguageServer/Utils/General.hs index f2ce023..fbf78f1 100644 --- a/src/Curry/LanguageServer/Utils/General.hs +++ b/src/Curry/LanguageServer/Utils/General.hs @@ -6,6 +6,7 @@ module Curry.LanguageServer.Utils.General ( nth, pair, dup, + guarded, wordAtIndex, wordAtPos, wordsWithSpaceCount, pointRange, emptyRange, @@ -28,6 +29,7 @@ module Curry.LanguageServer.Utils.General ( tripleToPair ) where +import Control.Applicative import Control.Monad (join) import Control.Monad.Trans.Maybe import qualified Data.ByteString as B @@ -70,6 +72,10 @@ pair x y = (x, y) dup :: a -> (a, a) dup x = (x, x) +-- | Creates a wrapped alternative value if it satisfies the predicate, otherwise empty +guarded :: Alternative f => (a -> Bool) -> a -> f a +guarded p x = if p x then pure x else empty + -- | Finds the word at the given offset. wordAtIndex :: Int -> T.Text -> Maybe T.Text wordAtIndex n = wordAtIndex' n . wordsWithSpaceCount diff --git a/test/resources/Test.curry b/test/resources/Test.curry index fc2e801..9c4ef5e 100644 --- a/test/resources/Test.curry +++ b/test/resources/Test.curry @@ -1,7 +1,5 @@ module Test where -import Demo - f x = case x of _ -> 3 - _ -> 5 + _ -> 4 From ec3692005c898077ec9888e4fce7db816285f8f3 Mon Sep 17 00:00:00 2001 From: fwcd Date: Sat, 13 Jun 2020 01:12:37 +0200 Subject: [PATCH 03/11] Add stub for declaration completions --- src/Curry/LanguageServer/Features/Completion.hs | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/Curry/LanguageServer/Features/Completion.hs b/src/Curry/LanguageServer/Features/Completion.hs index 76a5223..956901e 100644 --- a/src/Curry/LanguageServer/Features/Completion.hs +++ b/src/Curry/LanguageServer/Features/Completion.hs @@ -16,7 +16,7 @@ import Curry.LanguageServer.Logging import Curry.LanguageServer.Utils.Conversions (ppToText) import Curry.LanguageServer.Utils.General (rmDupsOn) import Curry.LanguageServer.Utils.Env (valueInfoType, typeInfoKind) -import Curry.LanguageServer.Utils.Syntax (elementAt, ModuleAST, HasExpressions (..)) +import Curry.LanguageServer.Utils.Syntax (elementAt, ModuleAST, HasExpressions (..), HasDeclarations (..)) import qualified Data.Map as M import Data.Maybe (maybeToList, isJust) import qualified Data.Text as T @@ -27,7 +27,9 @@ fetchCompletions :: ModuleStoreEntry -> T.Text -> J.Position -> IO [J.Completion fetchCompletions entry query pos = do let env = compilerEnv entry ast = moduleAST entry - completions = (expressionCompletions ast pos) <|> (generalCompletions env query) + completions = expressionCompletions ast pos + <|> declarationCompletions ast pos + <|> generalCompletions env query logs INFO $ "fetchCompletions: Found " ++ show (length completions) ++ " completions with query '" ++ show query return completions @@ -39,6 +41,13 @@ expressionCompletions ast pos = do -- TODO: Implement expression completions _ -> [] +declarationCompletions :: Maybe ModuleAST -> J.Position -> [J.CompletionItem] +declarationCompletions ast pos = do + expr <- maybeToList $ elementAt pos =<< (declarations <$> ast) + case expr of + -- TODO: Implement declaration completions + _ -> [] + generalCompletions :: Maybe CE.CompilerEnv -> T.Text -> [J.CompletionItem] generalCompletions env query = rmDupsOn (^. J.label) $ filter (matchesQuery query) $ valueCompletions env ++ typeCompletions env ++ keywordCompletions From a6944af4aeb81864cb23cb734a029e18060a8f23 Mon Sep 17 00:00:00 2001 From: fwcd Date: Sat, 13 Jun 2020 01:42:54 +0200 Subject: [PATCH 04/11] WIP: Add module completions --- .../LanguageServer/Features/Completion.hs | 32 ++++++++++++++++--- src/Curry/LanguageServer/Reactor.hs | 3 +- src/Curry/LanguageServer/Utils/Syntax.hs | 1 + 3 files changed, 31 insertions(+), 5 deletions(-) diff --git a/src/Curry/LanguageServer/Features/Completion.hs b/src/Curry/LanguageServer/Features/Completion.hs index 956901e..959ad7b 100644 --- a/src/Curry/LanguageServer/Features/Completion.hs +++ b/src/Curry/LanguageServer/Features/Completion.hs @@ -3,6 +3,8 @@ module Curry.LanguageServer.Features.Completion (fetchCompletions) where -- Curry Compiler Libraries + Dependencies import qualified Curry.Base.Ident as CI +import qualified Curry.Base.SpanInfo as CSPI +import qualified Curry.Syntax as CS import qualified Base.TopEnv as CT import qualified Base.Types as CTY import qualified CompilerEnv as CE @@ -11,24 +13,25 @@ import qualified Env.Value as CEV import Control.Applicative (Alternative (..)) import Control.Lens ((^.)) -import Curry.LanguageServer.IndexStore (ModuleStoreEntry (..)) +import Curry.LanguageServer.IndexStore (storedModules, IndexStore, ModuleStoreEntry (..)) import Curry.LanguageServer.Logging import Curry.LanguageServer.Utils.Conversions (ppToText) import Curry.LanguageServer.Utils.General (rmDupsOn) import Curry.LanguageServer.Utils.Env (valueInfoType, typeInfoKind) -import Curry.LanguageServer.Utils.Syntax (elementAt, ModuleAST, HasExpressions (..), HasDeclarations (..)) +import Curry.LanguageServer.Utils.Syntax (elementAt, elementContains, ModuleAST, HasExpressions (..), HasDeclarations (..)) import qualified Data.Map as M import Data.Maybe (maybeToList, isJust) import qualified Data.Text as T import qualified Language.Haskell.LSP.Types as J import qualified Language.Haskell.LSP.Types.Lens as J -fetchCompletions :: ModuleStoreEntry -> T.Text -> J.Position -> IO [J.CompletionItem] -fetchCompletions entry query pos = do +fetchCompletions :: IndexStore -> ModuleStoreEntry -> T.Text -> J.Position -> IO [J.CompletionItem] +fetchCompletions store entry query pos = do let env = compilerEnv entry ast = moduleAST entry completions = expressionCompletions ast pos <|> declarationCompletions ast pos + <|> importCompletions store ast pos <|> generalCompletions env query logs INFO $ "fetchCompletions: Found " ++ show (length completions) ++ " completions with query '" ++ show query @@ -48,6 +51,18 @@ declarationCompletions ast pos = do -- TODO: Implement declaration completions _ -> [] +importCompletions :: IndexStore -> Maybe ModuleAST -> J.Position -> [J.CompletionItem] +importCompletions store ast pos = do + CS.Module _ _ _ _ _ is _ <- maybeToList ast + CS.ImportDecl _ _ _ mid spec <- maybeToList $ elementAt pos is + case spec of + Just (CS.Importing _ is) | not (null is) -> case last is of + CS.Import _ ident -> moduleCompletions store + _ -> [] + +moduleCompletions :: IndexStore -> [J.CompletionItem] +moduleCompletions store = moduleToCompletion <$> ((maybeToList . moduleAST) =<< snd <$> storedModules store) + generalCompletions :: Maybe CE.CompilerEnv -> T.Text -> [J.CompletionItem] generalCompletions env query = rmDupsOn (^. J.label) $ filter (matchesQuery query) $ valueCompletions env ++ typeCompletions env ++ keywordCompletions @@ -69,6 +84,15 @@ matchesQuery query item = query `T.isPrefixOf` (item ^. J.label) completionFrom :: T.Text -> J.CompletionItemKind -> Maybe T.Text -> Maybe T.Text -> J.CompletionItem completionFrom label ciKind detail doc = J.CompletionItem label (Just ciKind) detail (J.CompletionDocString <$> doc) Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing +-- | Converts a module to a completion item. +moduleToCompletion :: CS.Module a -> J.CompletionItem +moduleToCompletion (CS.Module _ _ _ mid _ _ _) = item + where name = T.pack $ CI.moduleName mid + ciKind = J.CiModule + detail = Nothing + doc = Nothing + item = completionFrom name ciKind detail doc + -- TODO: Reimplement the following functions in terms of bindingToQualSymbols and a conversion from SymbolInformation to CompletionItem -- | Converts a Curry value binding to a completion item. diff --git a/src/Curry/LanguageServer/Reactor.hs b/src/Curry/LanguageServer/Reactor.hs index 02c7607..ae2d3ec 100644 --- a/src/Curry/LanguageServer/Reactor.hs +++ b/src/Curry/LanguageServer/Reactor.hs @@ -106,11 +106,12 @@ reactor lf rin = do pos = req ^. J.params . J.position normUri <- liftIO $ normalizeUriWithPath uri vfile <- liftIO $ Core.getVirtualFileFunc lf normUri + store <- get completions <- fmap (join . maybeToList) $ runMaybeT $ do entry <- I.getModule normUri vfile <- liftMaybe =<< (liftIO $ Core.getVirtualFileFunc lf normUri) query <- liftMaybe $ wordAtPos pos $ VFS.virtualFileText vfile - liftIO $ fetchCompletions entry query pos + liftIO $ fetchCompletions store entry query pos let maxCompletions = 25 items = take maxCompletions completions incomplete = length completions > maxCompletions diff --git a/src/Curry/LanguageServer/Utils/Syntax.hs b/src/Curry/LanguageServer/Utils/Syntax.hs index 36eddad..c72d223 100644 --- a/src/Curry/LanguageServer/Utils/Syntax.hs +++ b/src/Curry/LanguageServer/Utils/Syntax.hs @@ -6,6 +6,7 @@ module Curry.LanguageServer.Utils.Syntax ( HasIdentifier (..), ModuleAST, elementAt, + elementContains, moduleIdentifier ) where From cef1237e4abd47e1cbceeaa043adf4aef7a492f3 Mon Sep 17 00:00:00 2001 From: fwcd Date: Sat, 13 Jun 2020 03:22:59 +0200 Subject: [PATCH 05/11] WIP: Reparse AST on-the-fly for more accurate completions --- src/Curry/LanguageServer/Compiler.hs | 5 +-- .../LanguageServer/Features/Completion.hs | 35 +++++++++++++------ src/Curry/LanguageServer/Reactor.hs | 5 ++- 3 files changed, 30 insertions(+), 15 deletions(-) diff --git a/src/Curry/LanguageServer/Compiler.hs b/src/Curry/LanguageServer/Compiler.hs index e26ebfb..59dd974 100644 --- a/src/Curry/LanguageServer/Compiler.hs +++ b/src/Curry/LanguageServer/Compiler.hs @@ -4,7 +4,8 @@ module Curry.LanguageServer.Compiler ( FileLoader, compileCurryFileWithDeps, compilationToMaybe, - failedCompilation + failedCompilation, + parseCurryModule ) where -- Curry Compiler Libraries + Dependencies @@ -125,7 +126,7 @@ loadAndCheckCurryModule opts fl m fp = do return ce -- | Loads a single module. -loadCurryModule :: CO.Options -> CI.ModuleIdent -> String -> FilePath -> CYIO (CE.CompEnv (CS.Module())) +loadCurryModule :: CO.Options -> CI.ModuleIdent -> String -> FilePath -> CYIO (CE.CompEnv (CS.Module ())) loadCurryModule opts m src fp = do -- Parse the module (lex, ast) <- parseCurryModule opts m src fp diff --git a/src/Curry/LanguageServer/Features/Completion.hs b/src/Curry/LanguageServer/Features/Completion.hs index 959ad7b..6b0df7d 100644 --- a/src/Curry/LanguageServer/Features/Completion.hs +++ b/src/Curry/LanguageServer/Features/Completion.hs @@ -4,21 +4,25 @@ module Curry.LanguageServer.Features.Completion (fetchCompletions) where -- Curry Compiler Libraries + Dependencies import qualified Curry.Base.Ident as CI import qualified Curry.Base.SpanInfo as CSPI +import Curry.Base.Monad (runCYIOIgnWarn) +import qualified Curry.Files.Filenames as CFN import qualified Curry.Syntax as CS import qualified Base.TopEnv as CT import qualified Base.Types as CTY import qualified CompilerEnv as CE +import qualified CompilerOpts as CO import qualified Env.TypeConstructor as CETC import qualified Env.Value as CEV import Control.Applicative (Alternative (..)) import Control.Lens ((^.)) +import Curry.LanguageServer.Compiler (parseCurryModule) import Curry.LanguageServer.IndexStore (storedModules, IndexStore, ModuleStoreEntry (..)) import Curry.LanguageServer.Logging import Curry.LanguageServer.Utils.Conversions (ppToText) -import Curry.LanguageServer.Utils.General (rmDupsOn) +import Curry.LanguageServer.Utils.General (rmDupsOn, wordAtPos) import Curry.LanguageServer.Utils.Env (valueInfoType, typeInfoKind) -import Curry.LanguageServer.Utils.Syntax (elementAt, elementContains, ModuleAST, HasExpressions (..), HasDeclarations (..)) +import Curry.LanguageServer.Utils.Syntax (elementAt, elementContains, HasExpressions (..), HasDeclarations (..)) import qualified Data.Map as M import Data.Maybe (maybeToList, isJust) import qualified Data.Text as T @@ -26,32 +30,43 @@ import qualified Language.Haskell.LSP.Types as J import qualified Language.Haskell.LSP.Types.Lens as J fetchCompletions :: IndexStore -> ModuleStoreEntry -> T.Text -> J.Position -> IO [J.CompletionItem] -fetchCompletions store entry query pos = do - let env = compilerEnv entry +fetchCompletions store entry content pos = do + let query = maybe "" id $ wordAtPos pos content + env = compilerEnv entry ast = moduleAST entry - completions = expressionCompletions ast pos - <|> declarationCompletions ast pos - <|> importCompletions store ast pos + + -- Workaround: Re-parse AST since stored AST is not updated after errors + ast' <- case (\(CS.Module _ _ _ mid _ _ _) -> parseCurryModule CO.defaultOptions mid (T.unpack content) $ CFN.moduleNameToFile mid) <$> ast of + Just p -> do + out <- runCYIOIgnWarn p + return $ case out of + Right (_, m) -> Just m + _ -> Nothing + _ -> return Nothing + + let completions = expressionCompletions ast' pos + <|> declarationCompletions ast' pos + <|> importCompletions store ast' pos <|> generalCompletions env query logs INFO $ "fetchCompletions: Found " ++ show (length completions) ++ " completions with query '" ++ show query return completions -expressionCompletions :: Maybe ModuleAST -> J.Position -> [J.CompletionItem] +expressionCompletions :: Maybe (CS.Module a) -> J.Position -> [J.CompletionItem] expressionCompletions ast pos = do expr <- maybeToList $ elementAt pos =<< (expressions <$> ast) case expr of -- TODO: Implement expression completions _ -> [] -declarationCompletions :: Maybe ModuleAST -> J.Position -> [J.CompletionItem] +declarationCompletions :: Maybe (CS.Module a) -> J.Position -> [J.CompletionItem] declarationCompletions ast pos = do expr <- maybeToList $ elementAt pos =<< (declarations <$> ast) case expr of -- TODO: Implement declaration completions _ -> [] -importCompletions :: IndexStore -> Maybe ModuleAST -> J.Position -> [J.CompletionItem] +importCompletions :: IndexStore -> Maybe (CS.Module a) -> J.Position -> [J.CompletionItem] importCompletions store ast pos = do CS.Module _ _ _ _ _ is _ <- maybeToList ast CS.ImportDecl _ _ _ mid spec <- maybeToList $ elementAt pos is diff --git a/src/Curry/LanguageServer/Reactor.hs b/src/Curry/LanguageServer/Reactor.hs index ae2d3ec..2564e81 100644 --- a/src/Curry/LanguageServer/Reactor.hs +++ b/src/Curry/LanguageServer/Reactor.hs @@ -16,7 +16,7 @@ import Curry.LanguageServer.Features.DocumentSymbols import Curry.LanguageServer.Features.Hover import Curry.LanguageServer.Features.WorkspaceSymbols import Curry.LanguageServer.Logging -import Curry.LanguageServer.Utils.General (liftMaybe, slipr3, wordAtPos) +import Curry.LanguageServer.Utils.General (liftMaybe, slipr3) import Curry.LanguageServer.Utils.Uri (filePathToNormalizedUri, normalizeUriWithPath) import Data.Default import qualified Data.Map as M @@ -110,8 +110,7 @@ reactor lf rin = do completions <- fmap (join . maybeToList) $ runMaybeT $ do entry <- I.getModule normUri vfile <- liftMaybe =<< (liftIO $ Core.getVirtualFileFunc lf normUri) - query <- liftMaybe $ wordAtPos pos $ VFS.virtualFileText vfile - liftIO $ fetchCompletions store entry query pos + liftIO $ fetchCompletions store entry (VFS.virtualFileText vfile) pos let maxCompletions = 25 items = take maxCompletions completions incomplete = length completions > maxCompletions From da1b280ea7de136dcbe9c570f1f87fd949fb37be Mon Sep 17 00:00:00 2001 From: fwcd Date: Sat, 13 Jun 2020 16:16:23 +0200 Subject: [PATCH 06/11] Provide import completions --- src/Curry/LanguageServer/Features/Completion.hs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/Curry/LanguageServer/Features/Completion.hs b/src/Curry/LanguageServer/Features/Completion.hs index 6b0df7d..10ca68b 100644 --- a/src/Curry/LanguageServer/Features/Completion.hs +++ b/src/Curry/LanguageServer/Features/Completion.hs @@ -43,6 +43,8 @@ fetchCompletions store entry content pos = do Right (_, m) -> Just m _ -> Nothing _ -> return Nothing + + logs INFO $ "decl: " ++ show (elementAt pos =<< (\(CS.Module _ _ _ _ _ is _) -> is) <$> ast') let completions = expressionCompletions ast' pos <|> declarationCompletions ast' pos @@ -69,11 +71,8 @@ declarationCompletions ast pos = do importCompletions :: IndexStore -> Maybe (CS.Module a) -> J.Position -> [J.CompletionItem] importCompletions store ast pos = do CS.Module _ _ _ _ _ is _ <- maybeToList ast - CS.ImportDecl _ _ _ mid spec <- maybeToList $ elementAt pos is - case spec of - Just (CS.Importing _ is) | not (null is) -> case last is of - CS.Import _ ident -> moduleCompletions store - _ -> [] + CS.ImportDecl _ mid _ _ _ <- maybeToList $ elementAt pos is + moduleCompletions store moduleCompletions :: IndexStore -> [J.CompletionItem] moduleCompletions store = moduleToCompletion <$> ((maybeToList . moduleAST) =<< snd <$> storedModules store) From cff4be43c0887d1602c73186853c6a7592662c81 Mon Sep 17 00:00:00 2001 From: fwcd Date: Sat, 13 Jun 2020 16:25:38 +0200 Subject: [PATCH 07/11] Filter import completions by query prefix --- src/Curry/LanguageServer/Features/Completion.hs | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/src/Curry/LanguageServer/Features/Completion.hs b/src/Curry/LanguageServer/Features/Completion.hs index 10ca68b..994f838 100644 --- a/src/Curry/LanguageServer/Features/Completion.hs +++ b/src/Curry/LanguageServer/Features/Completion.hs @@ -20,7 +20,7 @@ import Curry.LanguageServer.Compiler (parseCurryModule) import Curry.LanguageServer.IndexStore (storedModules, IndexStore, ModuleStoreEntry (..)) import Curry.LanguageServer.Logging import Curry.LanguageServer.Utils.Conversions (ppToText) -import Curry.LanguageServer.Utils.General (rmDupsOn, wordAtPos) +import Curry.LanguageServer.Utils.General (rmDupsOn, wordAtPos, guarded) import Curry.LanguageServer.Utils.Env (valueInfoType, typeInfoKind) import Curry.LanguageServer.Utils.Syntax (elementAt, elementContains, HasExpressions (..), HasDeclarations (..)) import qualified Data.Map as M @@ -44,15 +44,15 @@ fetchCompletions store entry content pos = do _ -> Nothing _ -> return Nothing - logs INFO $ "decl: " ++ show (elementAt pos =<< (\(CS.Module _ _ _ _ _ is _) -> is) <$> ast') - - let completions = expressionCompletions ast' pos - <|> declarationCompletions ast' pos - <|> importCompletions store ast' pos - <|> generalCompletions env query + let completions = maybe [] id + $ (takeIfNonEmpty $ expressionCompletions ast' pos) + <|> (takeIfNonEmpty $ declarationCompletions ast' pos) + <|> (takeIfNonEmpty $ importCompletions store ast' pos) + <|> (takeIfNonEmpty $ generalCompletions env query) logs INFO $ "fetchCompletions: Found " ++ show (length completions) ++ " completions with query '" ++ show query return completions + where takeIfNonEmpty = guarded (not . null) expressionCompletions :: Maybe (CS.Module a) -> J.Position -> [J.CompletionItem] expressionCompletions ast pos = do @@ -72,7 +72,8 @@ importCompletions :: IndexStore -> Maybe (CS.Module a) -> J.Position -> [J.Compl importCompletions store ast pos = do CS.Module _ _ _ _ _ is _ <- maybeToList ast CS.ImportDecl _ mid _ _ _ <- maybeToList $ elementAt pos is - moduleCompletions store + let mName = T.pack $ CI.moduleName mid + filter (\c -> mName `T.isPrefixOf` (c ^. J.label)) $ moduleCompletions store moduleCompletions :: IndexStore -> [J.CompletionItem] moduleCompletions store = moduleToCompletion <$> ((maybeToList . moduleAST) =<< snd <$> storedModules store) From 1198e53a65cb3be0253d771cb425c8a6122322ec Mon Sep 17 00:00:00 2001 From: fwcd Date: Sat, 13 Jun 2020 17:26:33 +0200 Subject: [PATCH 08/11] Improve HasDeclarations implementation --- src/Curry/LanguageServer/Utils/Syntax.hs | 23 ++++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/src/Curry/LanguageServer/Utils/Syntax.hs b/src/Curry/LanguageServer/Utils/Syntax.hs index c72d223..f1733bc 100644 --- a/src/Curry/LanguageServer/Utils/Syntax.hs +++ b/src/Curry/LanguageServer/Utils/Syntax.hs @@ -1,3 +1,5 @@ +{-# LANGUAGE LambdaCase #-} + -- | AST utilities and typeclasses. module Curry.LanguageServer.Utils.Syntax ( HasExpressions (..), @@ -101,9 +103,24 @@ instance HasDeclarations CS.Module where instance HasDeclarations CS.Decl where declarations decl = decl : case decl of -- TODO: Fetch declarations inside equations/expressions/... - CS.ClassDecl _ _ _ _ _ ds -> ds - CS.InstanceDecl _ _ _ _ _ ds -> ds - _ -> [] + CS.ClassDecl _ _ _ _ _ ds -> declarations =<< ds + CS.InstanceDecl _ _ _ _ _ ds -> declarations =<< ds + CS.FunctionDecl _ _ _ eqs -> declarations =<< eqs + _ -> [] + +instance HasDeclarations CS.Equation where + declarations eqn = declarations =<< expressions eqn + +instance HasDeclarations CS.Rhs where + declarations rhs = declarations =<< expressions rhs + +instance HasDeclarations CS.CondExpr where + declarations ce = declarations =<< expressions ce + +instance HasDeclarations CS.Expression where + declarations e = expressions e >>= \case + CS.Let _ _ ds e -> declarations =<< ds + _ -> [] class HasQualIdentifier e where qualIdentifier :: e -> Maybe CI.QualIdent From 4538ac1f2d3a9f73560b76a2f72009b5d38f0891 Mon Sep 17 00:00:00 2001 From: fwcd Date: Sat, 13 Jun 2020 18:05:14 +0200 Subject: [PATCH 09/11] WIP: Generate completions from local declarations --- .../LanguageServer/Features/Completion.hs | 23 +++++++++++++++---- src/Curry/LanguageServer/Utils/Syntax.hs | 9 ++++++-- test/resources/Test.curry | 2 +- 3 files changed, 27 insertions(+), 7 deletions(-) diff --git a/src/Curry/LanguageServer/Features/Completion.hs b/src/Curry/LanguageServer/Features/Completion.hs index 994f838..09c1afa 100644 --- a/src/Curry/LanguageServer/Features/Completion.hs +++ b/src/Curry/LanguageServer/Features/Completion.hs @@ -22,7 +22,7 @@ import Curry.LanguageServer.Logging import Curry.LanguageServer.Utils.Conversions (ppToText) import Curry.LanguageServer.Utils.General (rmDupsOn, wordAtPos, guarded) import Curry.LanguageServer.Utils.Env (valueInfoType, typeInfoKind) -import Curry.LanguageServer.Utils.Syntax (elementAt, elementContains, HasExpressions (..), HasDeclarations (..)) +import Curry.LanguageServer.Utils.Syntax (elementAt, elementsAt, elementContains, HasExpressions (..), HasDeclarations (..)) import qualified Data.Map as M import Data.Maybe (maybeToList, isJust) import qualified Data.Text as T @@ -48,7 +48,7 @@ fetchCompletions store entry content pos = do $ (takeIfNonEmpty $ expressionCompletions ast' pos) <|> (takeIfNonEmpty $ declarationCompletions ast' pos) <|> (takeIfNonEmpty $ importCompletions store ast' pos) - <|> (takeIfNonEmpty $ generalCompletions env query) + <|> (takeIfNonEmpty $ generalCompletions env ast' pos query) logs INFO $ "fetchCompletions: Found " ++ show (length completions) ++ " completions with query '" ++ show query return completions @@ -78,8 +78,14 @@ importCompletions store ast pos = do moduleCompletions :: IndexStore -> [J.CompletionItem] moduleCompletions store = moduleToCompletion <$> ((maybeToList . moduleAST) =<< snd <$> storedModules store) -generalCompletions :: Maybe CE.CompilerEnv -> T.Text -> [J.CompletionItem] -generalCompletions env query = rmDupsOn (^. J.label) $ filter (matchesQuery query) $ valueCompletions env ++ typeCompletions env ++ keywordCompletions +generalCompletions :: Maybe CE.CompilerEnv -> Maybe (CS.Module a) -> J.Position -> T.Text -> [J.CompletionItem] +generalCompletions env ast pos query = rmDupsOn (^. J.label) $ filter (matchesQuery query) + $ valueCompletions env ++ localCompletions ast pos + ++ typeCompletions env + ++ keywordCompletions + +localCompletions :: Maybe (CS.Module a) -> J.Position -> [J.CompletionItem] +localCompletions ast pos = declarationToCompletions =<< (elementsAt pos $ declarations =<< maybeToList ast) valueCompletions :: Maybe CE.CompilerEnv -> [J.CompletionItem] valueCompletions env = valueBindingToCompletion <$> ((CT.allBindings . CE.valueEnv) =<< maybeToList env) @@ -108,6 +114,15 @@ moduleToCompletion (CS.Module _ _ _ mid _ _ _) = item doc = Nothing item = completionFrom name ciKind detail doc +-- | Converts a declaration to a completion item. +declarationToCompletions :: CS.Decl a -> [J.CompletionItem] +declarationToCompletions decl = (\n -> completionFrom n J.CiVariable Nothing Nothing) <$> names + where names = T.pack <$> CI.idName <$> case decl of + CS.FunctionDecl _ _ ident _ -> [ident] + CS.ExternalDecl _ vars -> (\(CS.Var _ ident) -> ident) <$> vars + CS.FreeDecl _ vars -> (\(CS.Var _ ident) -> ident) <$> vars + _ -> [] + -- TODO: Reimplement the following functions in terms of bindingToQualSymbols and a conversion from SymbolInformation to CompletionItem -- | Converts a Curry value binding to a completion item. diff --git a/src/Curry/LanguageServer/Utils/Syntax.hs b/src/Curry/LanguageServer/Utils/Syntax.hs index f1733bc..5a1b480 100644 --- a/src/Curry/LanguageServer/Utils/Syntax.hs +++ b/src/Curry/LanguageServer/Utils/Syntax.hs @@ -8,6 +8,7 @@ module Curry.LanguageServer.Utils.Syntax ( HasIdentifier (..), ModuleAST, elementAt, + elementsAt, elementContains, moduleIdentifier ) where @@ -24,9 +25,13 @@ import qualified Language.Haskell.LSP.Types as J type ModuleAST = CS.Module CT.PredType --- | Fetches the element at the given position. +-- | Fetches the innermost element at the given position. elementAt :: CSPI.HasSpanInfo e => J.Position -> [e] -> Maybe e -elementAt pos = lastSafe . filter (elementContains pos) +elementAt pos = lastSafe . elementsAt pos + +-- | Fetches the elements at the given position. +elementsAt :: CSPI.HasSpanInfo e => J.Position -> [e] -> [e] +elementsAt pos = filter (elementContains pos) -- | Tests whether the given element in the AST contains the given position. elementContains :: CSPI.HasSpanInfo e => J.Position -> e -> Bool diff --git a/test/resources/Test.curry b/test/resources/Test.curry index 9c4ef5e..6463dac 100644 --- a/test/resources/Test.curry +++ b/test/resources/Test.curry @@ -1,5 +1,5 @@ module Test where f x = case x of - _ -> 3 + _ -> let y = 4 in f y _ -> 4 From 36a60b0635a83fbe497e024ed561f2d4fa2f12f6 Mon Sep 17 00:00:00 2001 From: fwcd Date: Sat, 13 Jun 2020 18:10:03 +0200 Subject: [PATCH 10/11] Refine HasDeclarations implementations --- src/Curry/LanguageServer/Utils/Syntax.hs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/Curry/LanguageServer/Utils/Syntax.hs b/src/Curry/LanguageServer/Utils/Syntax.hs index 5a1b480..f4d5e17 100644 --- a/src/Curry/LanguageServer/Utils/Syntax.hs +++ b/src/Curry/LanguageServer/Utils/Syntax.hs @@ -114,16 +114,19 @@ instance HasDeclarations CS.Decl where _ -> [] instance HasDeclarations CS.Equation where - declarations eqn = declarations =<< expressions eqn + declarations (CS.Equation _ _ rhs) = declarations rhs instance HasDeclarations CS.Rhs where - declarations rhs = declarations =<< expressions rhs + declarations rhs = case rhs of + CS.SimpleRhs _ _ e decls -> (declarations e) ++ (declarations =<< decls) + CS.GuardedRhs _ _ conds decls -> (declarations =<< conds) ++ (declarations =<< decls) instance HasDeclarations CS.CondExpr where declarations ce = declarations =<< expressions ce instance HasDeclarations CS.Expression where declarations e = expressions e >>= \case + -- TODO: Declarations in do-statements etc. CS.Let _ _ ds e -> declarations =<< ds _ -> [] From dce200ef3f94dd894acb6bdb9dc95a73bf334160 Mon Sep 17 00:00:00 2001 From: fwcd Date: Sat, 13 Jun 2020 18:21:14 +0200 Subject: [PATCH 11/11] Remove unimplemented declarationCompletions --- src/Curry/LanguageServer/Features/Completion.hs | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/src/Curry/LanguageServer/Features/Completion.hs b/src/Curry/LanguageServer/Features/Completion.hs index 09c1afa..324392d 100644 --- a/src/Curry/LanguageServer/Features/Completion.hs +++ b/src/Curry/LanguageServer/Features/Completion.hs @@ -46,7 +46,6 @@ fetchCompletions store entry content pos = do let completions = maybe [] id $ (takeIfNonEmpty $ expressionCompletions ast' pos) - <|> (takeIfNonEmpty $ declarationCompletions ast' pos) <|> (takeIfNonEmpty $ importCompletions store ast' pos) <|> (takeIfNonEmpty $ generalCompletions env ast' pos query) @@ -58,14 +57,7 @@ expressionCompletions :: Maybe (CS.Module a) -> J.Position -> [J.CompletionItem] expressionCompletions ast pos = do expr <- maybeToList $ elementAt pos =<< (expressions <$> ast) case expr of - -- TODO: Implement expression completions - _ -> [] - -declarationCompletions :: Maybe (CS.Module a) -> J.Position -> [J.CompletionItem] -declarationCompletions ast pos = do - expr <- maybeToList $ elementAt pos =<< (declarations <$> ast) - case expr of - -- TODO: Implement declaration completions + -- TODO: Implement expression-specific completions _ -> [] importCompletions :: IndexStore -> Maybe (CS.Module a) -> J.Position -> [J.CompletionItem] @@ -84,6 +76,8 @@ generalCompletions env ast pos query = rmDupsOn (^. J.label) $ filter (matchesQu ++ typeCompletions env ++ keywordCompletions +-- TODO: Re-implement localCompletions using NestEnvs and bindings + localCompletions :: Maybe (CS.Module a) -> J.Position -> [J.CompletionItem] localCompletions ast pos = declarationToCompletions =<< (elementsAt pos $ declarations =<< maybeToList ast)