mirror of
https://github.com/haskell/ghcide.git
synced 2024-12-02 08:53:07 +03:00
[WIP] Completion support (#227)
* Initial implementation of completion support
* Add fuzzy to set of additional dependencies in 8.8
* Fix test
* Work a bit more on completion
* Attempt at getting completions from last good tckd module
* Revert "Attempt at getting completions from last good tckd module"
This reverts commit 04ca13b9d8
.
* "useWithStale" everywhere
* Some suggestions by @cocreature
* Adjust positions in the document
* Start working on tests
* Fix compilation problem
* Fix tests
* Better type tests
This commit is contained in:
parent
70cb92cc01
commit
b52ee607f9
@ -39,6 +39,7 @@ library
|
|||||||
deepseq,
|
deepseq,
|
||||||
directory,
|
directory,
|
||||||
extra,
|
extra,
|
||||||
|
fuzzy,
|
||||||
filepath,
|
filepath,
|
||||||
hashable,
|
hashable,
|
||||||
haskell-lsp-types == 0.19.*,
|
haskell-lsp-types == 0.19.*,
|
||||||
@ -96,6 +97,8 @@ library
|
|||||||
include-dirs:
|
include-dirs:
|
||||||
include
|
include
|
||||||
exposed-modules:
|
exposed-modules:
|
||||||
|
Development.IDE.Core.Completions
|
||||||
|
Development.IDE.Core.CompletionsTypes
|
||||||
Development.IDE.Core.FileStore
|
Development.IDE.Core.FileStore
|
||||||
Development.IDE.Core.OfInterest
|
Development.IDE.Core.OfInterest
|
||||||
Development.IDE.Core.PositionMapping
|
Development.IDE.Core.PositionMapping
|
||||||
@ -123,6 +126,7 @@ library
|
|||||||
Development.IDE.GHC.Warnings
|
Development.IDE.GHC.Warnings
|
||||||
Development.IDE.Import.FindImports
|
Development.IDE.Import.FindImports
|
||||||
Development.IDE.LSP.CodeAction
|
Development.IDE.LSP.CodeAction
|
||||||
|
Development.IDE.LSP.Completions
|
||||||
Development.IDE.LSP.HoverDefinition
|
Development.IDE.LSP.HoverDefinition
|
||||||
Development.IDE.LSP.Notifications
|
Development.IDE.LSP.Notifications
|
||||||
Development.IDE.Spans.AtPoint
|
Development.IDE.Spans.AtPoint
|
||||||
@ -180,6 +184,7 @@ test-suite ghcide-tests
|
|||||||
ghcide:ghcide,
|
ghcide:ghcide,
|
||||||
ghcide:ghcide-test-preprocessor
|
ghcide:ghcide-test-preprocessor
|
||||||
build-depends:
|
build-depends:
|
||||||
|
aeson,
|
||||||
base,
|
base,
|
||||||
bytestring,
|
bytestring,
|
||||||
containers,
|
containers,
|
||||||
|
565
src/Development/IDE/Core/Completions.hs
Normal file
565
src/Development/IDE/Core/Completions.hs
Normal file
@ -0,0 +1,565 @@
|
|||||||
|
-- Mostly taken from "haskell-ide-engine"
|
||||||
|
module Development.IDE.Core.Completions (
|
||||||
|
CachedCompletions
|
||||||
|
, cacheDataProducer
|
||||||
|
, WithSnippets(..)
|
||||||
|
,getCompletions
|
||||||
|
) where
|
||||||
|
|
||||||
|
import Control.Applicative
|
||||||
|
import Data.Aeson
|
||||||
|
import Data.Aeson.Types
|
||||||
|
import Data.Char (isSpace)
|
||||||
|
import Data.Generics
|
||||||
|
import Data.List as List hiding (stripPrefix)
|
||||||
|
import qualified Data.Map as Map
|
||||||
|
import Data.Maybe (fromMaybe, mapMaybe)
|
||||||
|
import qualified Data.Text as T
|
||||||
|
import qualified Text.Fuzzy as Fuzzy
|
||||||
|
|
||||||
|
import GHC
|
||||||
|
import Module
|
||||||
|
import HscTypes
|
||||||
|
import Name
|
||||||
|
import RdrName
|
||||||
|
import TcRnTypes
|
||||||
|
import Type
|
||||||
|
import Var
|
||||||
|
import Packages
|
||||||
|
import DynFlags
|
||||||
|
import ConLike
|
||||||
|
import DataCon
|
||||||
|
import SrcLoc as GHC
|
||||||
|
|
||||||
|
import Language.Haskell.LSP.Types
|
||||||
|
import Language.Haskell.LSP.Types.Capabilities
|
||||||
|
import qualified Language.Haskell.LSP.VFS as VFS
|
||||||
|
import Development.IDE.Core.CompletionsTypes
|
||||||
|
import Development.IDE.Spans.Documentation
|
||||||
|
|
||||||
|
-- From haskell-ide-engine/src/Haskell/Ide/Engine/Support/HieExtras.hs
|
||||||
|
|
||||||
|
data NameDetails
|
||||||
|
= NameDetails Module OccName
|
||||||
|
deriving (Eq)
|
||||||
|
|
||||||
|
nsJSON :: NameSpace -> Value
|
||||||
|
nsJSON ns
|
||||||
|
| isVarNameSpace ns = String "v"
|
||||||
|
| isDataConNameSpace ns = String "c"
|
||||||
|
| isTcClsNameSpace ns = String "t"
|
||||||
|
| isTvNameSpace ns = String "z"
|
||||||
|
| otherwise = error "namespace not recognized"
|
||||||
|
|
||||||
|
parseNs :: Value -> Parser NameSpace
|
||||||
|
parseNs (String "v") = pure Name.varName
|
||||||
|
parseNs (String "c") = pure dataName
|
||||||
|
parseNs (String "t") = pure tcClsName
|
||||||
|
parseNs (String "z") = pure tvName
|
||||||
|
parseNs _ = mempty
|
||||||
|
|
||||||
|
instance FromJSON NameDetails where
|
||||||
|
parseJSON v@(Array _)
|
||||||
|
= do
|
||||||
|
[modname,modid,namesp,occname] <- parseJSON v
|
||||||
|
mn <- parseJSON modname
|
||||||
|
mid <- parseJSON modid
|
||||||
|
ns <- parseNs namesp
|
||||||
|
occn <- parseJSON occname
|
||||||
|
pure $ NameDetails (mkModule (stringToUnitId mid) (mkModuleName mn)) (mkOccName ns occn)
|
||||||
|
parseJSON _ = mempty
|
||||||
|
instance ToJSON NameDetails where
|
||||||
|
toJSON (NameDetails mdl occ) = toJSON [toJSON mname,toJSON mid,nsJSON ns,toJSON occs]
|
||||||
|
where
|
||||||
|
mname = moduleNameString $ moduleName mdl
|
||||||
|
mid = unitIdString $ moduleUnitId mdl
|
||||||
|
ns = occNameSpace occ
|
||||||
|
occs = occNameString occ
|
||||||
|
|
||||||
|
safeTyThingId :: TyThing -> Maybe Id
|
||||||
|
safeTyThingId (AnId i) = Just i
|
||||||
|
safeTyThingId (AConLike (RealDataCon dc)) = Just $ dataConWrapId dc
|
||||||
|
safeTyThingId _ = Nothing
|
||||||
|
|
||||||
|
-- From haskell-ide-engine/hie-plugin-api/Haskell/Ide/Engine/Context.hs
|
||||||
|
|
||||||
|
-- | A context of a declaration in the program
|
||||||
|
-- e.g. is the declaration a type declaration or a value declaration
|
||||||
|
-- Used for determining which code completions to show
|
||||||
|
-- TODO: expand this with more contexts like classes or instances for
|
||||||
|
-- smarter code completion
|
||||||
|
data Context = TypeContext
|
||||||
|
| ValueContext
|
||||||
|
| ModuleContext String -- ^ module context with module name
|
||||||
|
| ImportContext String -- ^ import context with module name
|
||||||
|
| ImportListContext String -- ^ import list context with module name
|
||||||
|
| ImportHidingContext String -- ^ import hiding context with module name
|
||||||
|
| ExportContext -- ^ List of exported identifiers from the current module
|
||||||
|
deriving (Show, Eq)
|
||||||
|
|
||||||
|
-- | Generates a map of where the context is a type and where the context is a value
|
||||||
|
-- i.e. where are the value decls and the type decls
|
||||||
|
getCContext :: Position -> ParsedModule -> Maybe Context
|
||||||
|
getCContext pos pm
|
||||||
|
| Just (L (RealSrcSpan r) modName) <- moduleHeader
|
||||||
|
, pos `isInsideRange` r
|
||||||
|
= Just (ModuleContext (moduleNameString modName))
|
||||||
|
|
||||||
|
| Just (L (RealSrcSpan r) _) <- exportList
|
||||||
|
, pos `isInsideRange` r
|
||||||
|
= Just ExportContext
|
||||||
|
|
||||||
|
| Just ctx <- something (Nothing `mkQ` go `extQ` goInline) decl
|
||||||
|
= Just ctx
|
||||||
|
|
||||||
|
| Just ctx <- something (Nothing `mkQ` importGo) imports
|
||||||
|
= Just ctx
|
||||||
|
|
||||||
|
| otherwise
|
||||||
|
= Nothing
|
||||||
|
|
||||||
|
where decl = hsmodDecls $ unLoc $ pm_parsed_source pm
|
||||||
|
moduleHeader = hsmodName $ unLoc $ pm_parsed_source pm
|
||||||
|
exportList = hsmodExports $ unLoc $ pm_parsed_source pm
|
||||||
|
imports = hsmodImports $ unLoc $ pm_parsed_source pm
|
||||||
|
|
||||||
|
go :: LHsDecl GhcPs -> Maybe Context
|
||||||
|
go (L (RealSrcSpan r) SigD {})
|
||||||
|
| pos `isInsideRange` r = Just TypeContext
|
||||||
|
| otherwise = Nothing
|
||||||
|
go (L (GHC.RealSrcSpan r) GHC.ValD {})
|
||||||
|
| pos `isInsideRange` r = Just ValueContext
|
||||||
|
| otherwise = Nothing
|
||||||
|
go _ = Nothing
|
||||||
|
|
||||||
|
goInline :: GHC.LHsType GhcPs -> Maybe Context
|
||||||
|
goInline (GHC.L (GHC.RealSrcSpan r) _)
|
||||||
|
| pos `isInsideRange` r = Just TypeContext
|
||||||
|
| otherwise = Nothing
|
||||||
|
goInline _ = Nothing
|
||||||
|
|
||||||
|
p `isInsideRange` r = sp <= p && p <= ep
|
||||||
|
where (sp, ep) = unpackRealSrcSpan r
|
||||||
|
|
||||||
|
-- | Converts from one based tuple
|
||||||
|
toPos :: (Int,Int) -> Position
|
||||||
|
toPos (l,c) = Position (l-1) (c-1)
|
||||||
|
|
||||||
|
unpackRealSrcSpan :: GHC.RealSrcSpan -> (Position, Position)
|
||||||
|
unpackRealSrcSpan rspan =
|
||||||
|
(toPos (l1,c1),toPos (l2,c2))
|
||||||
|
where s = GHC.realSrcSpanStart rspan
|
||||||
|
l1 = GHC.srcLocLine s
|
||||||
|
c1 = GHC.srcLocCol s
|
||||||
|
e = GHC.realSrcSpanEnd rspan
|
||||||
|
l2 = GHC.srcLocLine e
|
||||||
|
c2 = GHC.srcLocCol e
|
||||||
|
|
||||||
|
importGo :: GHC.LImportDecl GhcPs -> Maybe Context
|
||||||
|
importGo (L (RealSrcSpan r) impDecl)
|
||||||
|
| pos `isInsideRange` r
|
||||||
|
= importInline importModuleName (ideclHiding impDecl)
|
||||||
|
<|> Just (ImportContext importModuleName)
|
||||||
|
|
||||||
|
| otherwise = Nothing
|
||||||
|
where importModuleName = moduleNameString $ unLoc $ ideclName impDecl
|
||||||
|
|
||||||
|
importGo _ = Nothing
|
||||||
|
|
||||||
|
importInline :: String -> Maybe (Bool, GHC.Located [LIE GhcPs]) -> Maybe Context
|
||||||
|
importInline modName (Just (True, L (RealSrcSpan r) _))
|
||||||
|
| pos `isInsideRange` r = Just $ ImportHidingContext modName
|
||||||
|
| otherwise = Nothing
|
||||||
|
importInline modName (Just (False, L (RealSrcSpan r) _))
|
||||||
|
| pos `isInsideRange` r = Just $ ImportListContext modName
|
||||||
|
| otherwise = Nothing
|
||||||
|
importInline _ _ = Nothing
|
||||||
|
|
||||||
|
type CompItemResolveData
|
||||||
|
= Maybe NameDetails
|
||||||
|
|
||||||
|
occNameToComKind :: OccName -> CompletionItemKind
|
||||||
|
occNameToComKind oc
|
||||||
|
| isVarOcc oc = CiFunction
|
||||||
|
| isTcOcc oc = CiClass
|
||||||
|
| isDataOcc oc = CiConstructor
|
||||||
|
| otherwise = CiVariable
|
||||||
|
|
||||||
|
mkCompl :: CompItem -> CompletionItem
|
||||||
|
mkCompl CI{origName,importedFrom,thingType,label,isInfix,docs} =
|
||||||
|
CompletionItem label kind (Just $ maybe "" (<>"\n") typeText <> importedFrom)
|
||||||
|
(Just $ CompletionDocMarkup $ MarkupContent MkMarkdown $ T.intercalate sectionSeparator docs)
|
||||||
|
Nothing Nothing Nothing Nothing (Just insertText) (Just Snippet)
|
||||||
|
Nothing Nothing Nothing Nothing resolveData
|
||||||
|
where kind = Just $ occNameToComKind $ occName origName
|
||||||
|
resolveData = Just (toJSON nameDets)
|
||||||
|
insertText = case isInfix of
|
||||||
|
Nothing -> case getArgText <$> thingType of
|
||||||
|
Nothing -> label
|
||||||
|
Just argText -> label <> " " <> argText
|
||||||
|
Just LeftSide -> label <> "`"
|
||||||
|
|
||||||
|
Just Surrounded -> label
|
||||||
|
typeText
|
||||||
|
| Just t <- thingType = Just . stripForall $ T.pack (showGhc t)
|
||||||
|
| otherwise = Nothing
|
||||||
|
nameDets =
|
||||||
|
case (thingType, nameModule_maybe origName) of
|
||||||
|
(Just _,_) -> Nothing
|
||||||
|
(Nothing, Nothing) -> Nothing
|
||||||
|
(Nothing, Just mdl) -> Just (NameDetails mdl (nameOccName origName))
|
||||||
|
|
||||||
|
stripForall :: T.Text -> T.Text
|
||||||
|
stripForall t
|
||||||
|
| T.isPrefixOf "forall" t =
|
||||||
|
-- We drop 2 to remove the '.' and the space after it
|
||||||
|
T.drop 2 (T.dropWhile (/= '.') t)
|
||||||
|
| otherwise = t
|
||||||
|
|
||||||
|
getArgText :: Type -> T.Text
|
||||||
|
getArgText typ = argText
|
||||||
|
where
|
||||||
|
argTypes = getArgs typ
|
||||||
|
argText :: T.Text
|
||||||
|
argText = mconcat $ List.intersperse " " $ zipWith snippet [1..] argTypes
|
||||||
|
snippet :: Int -> Type -> T.Text
|
||||||
|
snippet i t = T.pack $ "${" <> show i <> ":" <> showGhc t <> "}"
|
||||||
|
getArgs :: Type -> [Type]
|
||||||
|
getArgs t
|
||||||
|
| isPredTy t = []
|
||||||
|
| isDictTy t = []
|
||||||
|
| isForAllTy t = getArgs $ snd (splitForAllTys t)
|
||||||
|
| isFunTy t =
|
||||||
|
let (args, ret) = splitFunTys t
|
||||||
|
in if isForAllTy ret
|
||||||
|
then getArgs ret
|
||||||
|
else Prelude.filter (not . isDictTy) args
|
||||||
|
| isPiTy t = getArgs $ snd (splitPiTys t)
|
||||||
|
| isCoercionTy t = maybe [] (getArgs . snd) (splitCoercionType_maybe t)
|
||||||
|
| otherwise = []
|
||||||
|
|
||||||
|
mkModCompl :: T.Text -> CompletionItem
|
||||||
|
mkModCompl label =
|
||||||
|
CompletionItem label (Just CiModule) Nothing
|
||||||
|
Nothing Nothing Nothing Nothing Nothing Nothing Nothing
|
||||||
|
Nothing Nothing Nothing Nothing (Just $ toJSON resolveData)
|
||||||
|
where resolveData :: CompItemResolveData
|
||||||
|
resolveData = Nothing
|
||||||
|
|
||||||
|
mkImportCompl :: T.Text -> T.Text -> CompletionItem
|
||||||
|
mkImportCompl enteredQual label =
|
||||||
|
CompletionItem m (Just CiModule) (Just label)
|
||||||
|
Nothing Nothing Nothing Nothing Nothing Nothing Nothing
|
||||||
|
Nothing Nothing Nothing Nothing Nothing
|
||||||
|
where
|
||||||
|
m = fromMaybe "" (T.stripPrefix enteredQual label)
|
||||||
|
|
||||||
|
mkExtCompl :: T.Text -> CompletionItem
|
||||||
|
mkExtCompl label =
|
||||||
|
CompletionItem label (Just CiKeyword) Nothing
|
||||||
|
Nothing Nothing Nothing Nothing Nothing Nothing Nothing
|
||||||
|
Nothing Nothing Nothing Nothing Nothing
|
||||||
|
|
||||||
|
mkPragmaCompl :: T.Text -> T.Text -> CompletionItem
|
||||||
|
mkPragmaCompl label insertText =
|
||||||
|
CompletionItem label (Just CiKeyword) Nothing
|
||||||
|
Nothing Nothing Nothing Nothing Nothing (Just insertText) (Just Snippet)
|
||||||
|
Nothing Nothing Nothing Nothing Nothing
|
||||||
|
|
||||||
|
cacheDataProducer :: DynFlags -> TypecheckedModule -> [TypecheckedModule] -> IO CachedCompletions
|
||||||
|
cacheDataProducer dflags tm tcs = do
|
||||||
|
let parsedMod = tm_parsed_module tm
|
||||||
|
curMod = moduleName $ ms_mod $ pm_mod_summary parsedMod
|
||||||
|
Just (_,limports,_,_) = tm_renamed_source tm
|
||||||
|
|
||||||
|
iDeclToModName :: ImportDecl name -> ModuleName
|
||||||
|
iDeclToModName = unLoc . ideclName
|
||||||
|
|
||||||
|
showModName :: ModuleName -> T.Text
|
||||||
|
showModName = T.pack . moduleNameString
|
||||||
|
|
||||||
|
asNamespace :: ImportDecl name -> ModuleName
|
||||||
|
asNamespace imp = maybe (iDeclToModName imp) GHC.unLoc (ideclAs imp)
|
||||||
|
-- Full canonical names of imported modules
|
||||||
|
importDeclerations = map unLoc limports
|
||||||
|
|
||||||
|
-- The list of all importable Modules from all packages
|
||||||
|
moduleNames = map showModName (listVisibleModuleNames dflags)
|
||||||
|
|
||||||
|
-- The given namespaces for the imported modules (ie. full name, or alias if used)
|
||||||
|
allModNamesAsNS = map (showModName . asNamespace) importDeclerations
|
||||||
|
|
||||||
|
typeEnv = tcg_type_env $ fst $ tm_internals_ tm
|
||||||
|
rdrEnv = tcg_rdr_env $ fst $ tm_internals_ tm
|
||||||
|
rdrElts = globalRdrEnvElts rdrEnv
|
||||||
|
|
||||||
|
getCompls :: [GlobalRdrElt] -> ([CompItem],QualCompls)
|
||||||
|
getCompls = foldMap getComplsForOne
|
||||||
|
|
||||||
|
getComplsForOne :: GlobalRdrElt -> ([CompItem],QualCompls)
|
||||||
|
getComplsForOne (GRE n _ True _) =
|
||||||
|
case lookupTypeEnv typeEnv n of
|
||||||
|
Just tt -> case safeTyThingId tt of
|
||||||
|
Just var -> ([varToCompl var],mempty)
|
||||||
|
Nothing -> ([toCompItem curMod n],mempty)
|
||||||
|
Nothing -> ([toCompItem curMod n],mempty)
|
||||||
|
getComplsForOne (GRE n _ False prov) =
|
||||||
|
flip foldMap (map is_decl prov) $ \spec ->
|
||||||
|
let unqual
|
||||||
|
| is_qual spec = []
|
||||||
|
| otherwise = compItem
|
||||||
|
qual
|
||||||
|
| is_qual spec = Map.singleton asMod compItem
|
||||||
|
| otherwise = Map.fromList [(asMod,compItem),(origMod,compItem)]
|
||||||
|
compItem = [toCompItem (is_mod spec) n]
|
||||||
|
asMod = showModName (is_as spec)
|
||||||
|
origMod = showModName (is_mod spec)
|
||||||
|
in (unqual,QualCompls qual)
|
||||||
|
|
||||||
|
varToCompl :: Var -> CompItem
|
||||||
|
varToCompl var = CI name (showModName curMod) typ label Nothing docs
|
||||||
|
where
|
||||||
|
typ = Just $ varType var
|
||||||
|
name = Var.varName var
|
||||||
|
label = T.pack $ showGhc name
|
||||||
|
docs = getDocumentation tcs name
|
||||||
|
|
||||||
|
toCompItem :: ModuleName -> Name -> CompItem
|
||||||
|
toCompItem mn n =
|
||||||
|
CI n (showModName mn) Nothing (T.pack $ showGhc n) Nothing (getDocumentation tcs n)
|
||||||
|
|
||||||
|
(unquals,quals) = getCompls rdrElts
|
||||||
|
|
||||||
|
return $ CC
|
||||||
|
{ allModNamesAsNS = allModNamesAsNS
|
||||||
|
, unqualCompls = unquals
|
||||||
|
, qualCompls = quals
|
||||||
|
, importableModules = moduleNames
|
||||||
|
}
|
||||||
|
|
||||||
|
newtype WithSnippets = WithSnippets Bool
|
||||||
|
|
||||||
|
toggleSnippets :: ClientCapabilities -> WithSnippets -> CompletionItem -> CompletionItem
|
||||||
|
toggleSnippets ClientCapabilities { _textDocument } (WithSnippets with) x
|
||||||
|
| with && supported = x
|
||||||
|
| otherwise = x { _insertTextFormat = Just PlainText
|
||||||
|
, _insertText = Nothing
|
||||||
|
}
|
||||||
|
where supported = fromMaybe False (_textDocument >>= _completion >>= _completionItem >>= _snippetSupport)
|
||||||
|
|
||||||
|
-- | Returns the cached completions for the given module and position.
|
||||||
|
getCompletions :: CachedCompletions -> TypecheckedModule -> VFS.PosPrefixInfo -> ClientCapabilities -> WithSnippets -> IO [CompletionItem]
|
||||||
|
getCompletions CC { allModNamesAsNS, unqualCompls, qualCompls, importableModules }
|
||||||
|
tm prefixInfo caps withSnippets = do
|
||||||
|
let VFS.PosPrefixInfo { VFS.fullLine, VFS.prefixModule, VFS.prefixText } = prefixInfo
|
||||||
|
enteredQual = if T.null prefixModule then "" else prefixModule <> "."
|
||||||
|
fullPrefix = enteredQual <> prefixText
|
||||||
|
|
||||||
|
-- default to value context if no explicit context
|
||||||
|
context = fromMaybe ValueContext $ getCContext pos (tm_parsed_module tm)
|
||||||
|
|
||||||
|
{- correct the position by moving 'foo :: Int -> String -> '
|
||||||
|
^
|
||||||
|
to 'foo :: Int -> String -> '
|
||||||
|
^
|
||||||
|
-}
|
||||||
|
pos =
|
||||||
|
let Position l c = VFS.cursorPos prefixInfo
|
||||||
|
typeStuff = [isSpace, (`elem` (">-." :: String))]
|
||||||
|
stripTypeStuff = T.dropWhileEnd (\x -> any (\f -> f x) typeStuff)
|
||||||
|
-- if oldPos points to
|
||||||
|
-- foo -> bar -> baz
|
||||||
|
-- ^
|
||||||
|
-- Then only take the line up to there, discard '-> bar -> baz'
|
||||||
|
partialLine = T.take c fullLine
|
||||||
|
-- drop characters used when writing incomplete type sigs
|
||||||
|
-- like '-> '
|
||||||
|
d = T.length fullLine - T.length (stripTypeStuff partialLine)
|
||||||
|
in Position l (c - d)
|
||||||
|
|
||||||
|
filtModNameCompls =
|
||||||
|
map mkModCompl
|
||||||
|
$ mapMaybe (T.stripPrefix enteredQual)
|
||||||
|
$ Fuzzy.simpleFilter fullPrefix allModNamesAsNS
|
||||||
|
|
||||||
|
filtCompls = map Fuzzy.original $ Fuzzy.filter prefixText ctxCompls "" "" label False
|
||||||
|
where
|
||||||
|
isTypeCompl = isTcOcc . occName . origName
|
||||||
|
-- completions specific to the current context
|
||||||
|
ctxCompls' = case context of
|
||||||
|
TypeContext -> filter isTypeCompl compls
|
||||||
|
ValueContext -> filter (not . isTypeCompl) compls
|
||||||
|
_ -> filter (not . isTypeCompl) compls
|
||||||
|
-- Add whether the text to insert has backticks
|
||||||
|
ctxCompls = map (\comp -> comp { isInfix = infixCompls }) ctxCompls'
|
||||||
|
|
||||||
|
infixCompls :: Maybe Backtick
|
||||||
|
infixCompls = isUsedAsInfix fullLine prefixModule prefixText (VFS.cursorPos prefixInfo)
|
||||||
|
|
||||||
|
compls = if T.null prefixModule
|
||||||
|
then unqualCompls
|
||||||
|
else Map.findWithDefault [] prefixModule $ getQualCompls qualCompls
|
||||||
|
|
||||||
|
filtListWith f list =
|
||||||
|
[ f label
|
||||||
|
| label <- Fuzzy.simpleFilter fullPrefix list
|
||||||
|
, enteredQual `T.isPrefixOf` label
|
||||||
|
]
|
||||||
|
|
||||||
|
filtListWithSnippet f list suffix =
|
||||||
|
[ toggleSnippets caps withSnippets (f label (snippet <> suffix))
|
||||||
|
| (snippet, label) <- list
|
||||||
|
, Fuzzy.test fullPrefix label
|
||||||
|
]
|
||||||
|
|
||||||
|
filtImportCompls = filtListWith (mkImportCompl enteredQual) importableModules
|
||||||
|
filtPragmaCompls = filtListWithSnippet mkPragmaCompl validPragmas
|
||||||
|
filtOptsCompls = filtListWith mkExtCompl
|
||||||
|
|
||||||
|
stripLeading :: Char -> String -> String
|
||||||
|
stripLeading _ [] = []
|
||||||
|
stripLeading c (s:ss)
|
||||||
|
| s == c = ss
|
||||||
|
| otherwise = s:ss
|
||||||
|
|
||||||
|
result
|
||||||
|
| "import " `T.isPrefixOf` fullLine
|
||||||
|
= filtImportCompls
|
||||||
|
| "{-# language" `T.isPrefixOf` T.toLower fullLine
|
||||||
|
= filtOptsCompls languagesAndExts
|
||||||
|
| "{-# options_ghc" `T.isPrefixOf` T.toLower fullLine
|
||||||
|
= filtOptsCompls (map (T.pack . stripLeading '-') $ flagsForCompletion False)
|
||||||
|
| "{-# " `T.isPrefixOf` fullLine
|
||||||
|
= filtPragmaCompls (pragmaSuffix fullLine)
|
||||||
|
| otherwise
|
||||||
|
= filtModNameCompls ++ map (toggleSnippets caps withSnippets
|
||||||
|
. mkCompl . stripAutoGenerated) filtCompls
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
-- The supported languages and extensions
|
||||||
|
languagesAndExts :: [T.Text]
|
||||||
|
languagesAndExts = map T.pack DynFlags.supportedLanguagesAndExtensions
|
||||||
|
|
||||||
|
-- ---------------------------------------------------------------------
|
||||||
|
-- helper functions for pragmas
|
||||||
|
-- ---------------------------------------------------------------------
|
||||||
|
|
||||||
|
validPragmas :: [(T.Text, T.Text)]
|
||||||
|
validPragmas =
|
||||||
|
[ ("LANGUAGE ${1:extension}" , "LANGUAGE")
|
||||||
|
, ("OPTIONS_GHC -${1:option}" , "OPTIONS_GHC")
|
||||||
|
, ("INLINE ${1:function}" , "INLINE")
|
||||||
|
, ("NOINLINE ${1:function}" , "NOINLINE")
|
||||||
|
, ("INLINABLE ${1:function}" , "INLINABLE")
|
||||||
|
, ("WARNING ${1:message}" , "WARNING")
|
||||||
|
, ("DEPRECATED ${1:message}" , "DEPRECATED")
|
||||||
|
, ("ANN ${1:annotation}" , "ANN")
|
||||||
|
, ("RULES" , "RULES")
|
||||||
|
, ("SPECIALIZE ${1:function}" , "SPECIALIZE")
|
||||||
|
, ("SPECIALIZE INLINE ${1:function}", "SPECIALIZE INLINE")
|
||||||
|
]
|
||||||
|
|
||||||
|
pragmaSuffix :: T.Text -> T.Text
|
||||||
|
pragmaSuffix fullLine
|
||||||
|
| "}" `T.isSuffixOf` fullLine = mempty
|
||||||
|
| otherwise = " #-}"
|
||||||
|
|
||||||
|
-- ---------------------------------------------------------------------
|
||||||
|
-- helper functions for infix backticks
|
||||||
|
-- ---------------------------------------------------------------------
|
||||||
|
|
||||||
|
hasTrailingBacktick :: T.Text -> Position -> Bool
|
||||||
|
hasTrailingBacktick line Position { _character }
|
||||||
|
| T.length line > _character = (line `T.index` _character) == '`'
|
||||||
|
| otherwise = False
|
||||||
|
|
||||||
|
isUsedAsInfix :: T.Text -> T.Text -> T.Text -> Position -> Maybe Backtick
|
||||||
|
isUsedAsInfix line prefixMod prefixText pos
|
||||||
|
| hasClosingBacktick && hasOpeningBacktick = Just Surrounded
|
||||||
|
| hasOpeningBacktick = Just LeftSide
|
||||||
|
| otherwise = Nothing
|
||||||
|
where
|
||||||
|
hasOpeningBacktick = openingBacktick line prefixMod prefixText pos
|
||||||
|
hasClosingBacktick = hasTrailingBacktick line pos
|
||||||
|
|
||||||
|
openingBacktick :: T.Text -> T.Text -> T.Text -> Position -> Bool
|
||||||
|
openingBacktick line prefixModule prefixText Position { _character }
|
||||||
|
| backtickIndex < 0 = False
|
||||||
|
| otherwise = (line `T.index` backtickIndex) == '`'
|
||||||
|
where
|
||||||
|
backtickIndex :: Int
|
||||||
|
backtickIndex =
|
||||||
|
let
|
||||||
|
prefixLength = T.length prefixText
|
||||||
|
moduleLength = if prefixModule == ""
|
||||||
|
then 0
|
||||||
|
else T.length prefixModule + 1 {- Because of "." -}
|
||||||
|
in
|
||||||
|
-- Points to the first letter of either the module or prefix text
|
||||||
|
_character - (prefixLength + moduleLength) - 1
|
||||||
|
|
||||||
|
|
||||||
|
-- ---------------------------------------------------------------------
|
||||||
|
|
||||||
|
-- | Under certain circumstance GHC generates some extra stuff that we
|
||||||
|
-- don't want in the autocompleted symbols
|
||||||
|
stripAutoGenerated :: CompItem -> CompItem
|
||||||
|
stripAutoGenerated ci =
|
||||||
|
ci {label = stripPrefix (label ci)}
|
||||||
|
{- When e.g. DuplicateRecordFields is enabled, compiler generates
|
||||||
|
names like "$sel:accessor:One" and "$sel:accessor:Two" to disambiguate record selectors
|
||||||
|
https://ghc.haskell.org/trac/ghc/wiki/Records/OverloadedRecordFields/DuplicateRecordFields#Implementation
|
||||||
|
-}
|
||||||
|
|
||||||
|
-- TODO: Turn this into an alex lexer that discards prefixes as if they were whitespace.
|
||||||
|
|
||||||
|
stripPrefix :: T.Text -> T.Text
|
||||||
|
stripPrefix name = T.takeWhile (/=':') $ go prefixes
|
||||||
|
where
|
||||||
|
go [] = name
|
||||||
|
go (p:ps)
|
||||||
|
| T.isPrefixOf p name = T.drop (T.length p) name
|
||||||
|
| otherwise = go ps
|
||||||
|
|
||||||
|
-- | Prefixes that can occur in a GHC OccName
|
||||||
|
prefixes :: [T.Text]
|
||||||
|
prefixes =
|
||||||
|
[
|
||||||
|
-- long ones
|
||||||
|
"$con2tag_"
|
||||||
|
, "$tag2con_"
|
||||||
|
, "$maxtag_"
|
||||||
|
|
||||||
|
-- four chars
|
||||||
|
, "$sel:"
|
||||||
|
, "$tc'"
|
||||||
|
|
||||||
|
-- three chars
|
||||||
|
, "$dm"
|
||||||
|
, "$co"
|
||||||
|
, "$tc"
|
||||||
|
, "$cp"
|
||||||
|
, "$fx"
|
||||||
|
|
||||||
|
-- two chars
|
||||||
|
, "$W"
|
||||||
|
, "$w"
|
||||||
|
, "$m"
|
||||||
|
, "$b"
|
||||||
|
, "$c"
|
||||||
|
, "$d"
|
||||||
|
, "$i"
|
||||||
|
, "$s"
|
||||||
|
, "$f"
|
||||||
|
, "$r"
|
||||||
|
, "C:"
|
||||||
|
, "N:"
|
||||||
|
, "D:"
|
||||||
|
, "$p"
|
||||||
|
, "$L"
|
||||||
|
, "$f"
|
||||||
|
, "$t"
|
||||||
|
, "$c"
|
||||||
|
, "$m"
|
||||||
|
]
|
62
src/Development/IDE/Core/CompletionsTypes.hs
Normal file
62
src/Development/IDE/Core/CompletionsTypes.hs
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
module Development.IDE.Core.CompletionsTypes (
|
||||||
|
module Development.IDE.Core.CompletionsTypes
|
||||||
|
) where
|
||||||
|
|
||||||
|
import Control.DeepSeq
|
||||||
|
import qualified Data.Map as Map
|
||||||
|
import qualified Data.Text as T
|
||||||
|
|
||||||
|
import GHC
|
||||||
|
import Outputable
|
||||||
|
import DynFlags
|
||||||
|
|
||||||
|
-- From haskell-ide-engine/src/Haskell/Ide/Engine/LSP/Completions.hs
|
||||||
|
|
||||||
|
showGhc :: Outputable a => a -> String
|
||||||
|
showGhc = showPpr unsafeGlobalDynFlags
|
||||||
|
|
||||||
|
data Backtick = Surrounded | LeftSide deriving Show
|
||||||
|
data CompItem = CI
|
||||||
|
{ origName :: Name -- ^ Original name, such as Maybe, //, or find.
|
||||||
|
, importedFrom :: T.Text -- ^ From where this item is imported from.
|
||||||
|
, thingType :: Maybe Type -- ^ Available type information.
|
||||||
|
, label :: T.Text -- ^ Label to display to the user.
|
||||||
|
, isInfix :: Maybe Backtick -- ^ Did the completion happen
|
||||||
|
-- in the context of an infix notation.
|
||||||
|
, docs :: [T.Text] -- ^ Available documentation.
|
||||||
|
}
|
||||||
|
instance Show CompItem where
|
||||||
|
show CI { .. } = "CompItem { origName = \"" ++ showGhc origName ++ "\""
|
||||||
|
++ ", importedFrom = " ++ show importedFrom
|
||||||
|
++ ", thingType = " ++ show (fmap showGhc thingType)
|
||||||
|
++ ", label = " ++ show label
|
||||||
|
++ ", isInfix = " ++ show isInfix
|
||||||
|
++ ", docs = " ++ show docs
|
||||||
|
++ " } "
|
||||||
|
instance Eq CompItem where
|
||||||
|
ci1 == ci2 = origName ci1 == origName ci2
|
||||||
|
instance Ord CompItem where
|
||||||
|
compare ci1 ci2 = origName ci1 `compare` origName ci2
|
||||||
|
|
||||||
|
-- Associates a module's qualifier with its members
|
||||||
|
newtype QualCompls
|
||||||
|
= QualCompls { getQualCompls :: Map.Map T.Text [CompItem] }
|
||||||
|
deriving Show
|
||||||
|
instance Semigroup QualCompls where
|
||||||
|
(QualCompls a) <> (QualCompls b) = QualCompls $ Map.unionWith (++) a b
|
||||||
|
instance Monoid QualCompls where
|
||||||
|
mempty = QualCompls Map.empty
|
||||||
|
mappend = (Prelude.<>)
|
||||||
|
|
||||||
|
-- | End result of the completions
|
||||||
|
data CachedCompletions = CC
|
||||||
|
{ allModNamesAsNS :: [T.Text] -- ^ All module names in scope.
|
||||||
|
-- Prelude is a single module
|
||||||
|
, unqualCompls :: [CompItem] -- ^ All Possible completion items
|
||||||
|
, qualCompls :: QualCompls -- ^ Completion items associated to
|
||||||
|
-- to a specific module name.
|
||||||
|
, importableModules :: [T.Text] -- ^ All modules that may be imported.
|
||||||
|
} deriving Show
|
||||||
|
|
||||||
|
instance NFData CachedCompletions where
|
||||||
|
rnf = rwhnf
|
@ -27,6 +27,7 @@ import Module (InstalledUnitId)
|
|||||||
import HscTypes (CgGuts, Linkable, HomeModInfo, ModDetails)
|
import HscTypes (CgGuts, Linkable, HomeModInfo, ModDetails)
|
||||||
import Development.IDE.GHC.Compat
|
import Development.IDE.GHC.Compat
|
||||||
|
|
||||||
|
import Development.IDE.Core.CompletionsTypes
|
||||||
import Development.IDE.Spans.Type
|
import Development.IDE.Spans.Type
|
||||||
|
|
||||||
|
|
||||||
@ -85,6 +86,9 @@ type instance RuleResult ReportImportCycles = ()
|
|||||||
-- | Read the given HIE file.
|
-- | Read the given HIE file.
|
||||||
type instance RuleResult GetHieFile = HieFile
|
type instance RuleResult GetHieFile = HieFile
|
||||||
|
|
||||||
|
-- | Produce completions info for a file
|
||||||
|
type instance RuleResult ProduceCompletions = (CachedCompletions, TcModuleResult)
|
||||||
|
|
||||||
|
|
||||||
data GetParsedModule = GetParsedModule
|
data GetParsedModule = GetParsedModule
|
||||||
deriving (Eq, Show, Typeable, Generic)
|
deriving (Eq, Show, Typeable, Generic)
|
||||||
@ -153,3 +157,9 @@ data GetHieFile = GetHieFile FilePath
|
|||||||
instance Hashable GetHieFile
|
instance Hashable GetHieFile
|
||||||
instance NFData GetHieFile
|
instance NFData GetHieFile
|
||||||
instance Binary GetHieFile
|
instance Binary GetHieFile
|
||||||
|
|
||||||
|
data ProduceCompletions = ProduceCompletions
|
||||||
|
deriving (Eq, Show, Typeable, Generic)
|
||||||
|
instance Hashable ProduceCompletions
|
||||||
|
instance NFData ProduceCompletions
|
||||||
|
instance Binary ProduceCompletions
|
@ -32,6 +32,7 @@ import Control.Monad
|
|||||||
import Control.Monad.Trans.Class
|
import Control.Monad.Trans.Class
|
||||||
import Control.Monad.Trans.Maybe
|
import Control.Monad.Trans.Maybe
|
||||||
import Development.IDE.Core.Compile
|
import Development.IDE.Core.Compile
|
||||||
|
import Development.IDE.Core.Completions
|
||||||
import Development.IDE.Types.Options
|
import Development.IDE.Types.Options
|
||||||
import Development.IDE.Spans.Calculate
|
import Development.IDE.Spans.Calculate
|
||||||
import Development.IDE.Import.DependencyInformation
|
import Development.IDE.Import.DependencyInformation
|
||||||
@ -304,6 +305,19 @@ generateCoreRule :: Rules ()
|
|||||||
generateCoreRule =
|
generateCoreRule =
|
||||||
define $ \GenerateCore -> generateCore
|
define $ \GenerateCore -> generateCore
|
||||||
|
|
||||||
|
produceCompletions :: Rules ()
|
||||||
|
produceCompletions =
|
||||||
|
define $ \ProduceCompletions file -> do
|
||||||
|
deps <- maybe (TransitiveDependencies [] []) fst <$> useWithStale GetDependencies file
|
||||||
|
tms <- mapMaybe (fmap fst) <$> usesWithStale TypeCheck (transitiveModuleDeps deps)
|
||||||
|
tm <- fmap fst <$> useWithStale TypeCheck file
|
||||||
|
dflags <- fmap (hsc_dflags . hscEnv . fst) <$> useWithStale GhcSession file
|
||||||
|
case (tm, dflags) of
|
||||||
|
(Just tm', Just dflags') -> do
|
||||||
|
cdata <- liftIO $ cacheDataProducer dflags' (tmrModule tm') (map tmrModule tms)
|
||||||
|
return ([], Just (cdata, tm'))
|
||||||
|
_ -> return ([], Nothing)
|
||||||
|
|
||||||
generateByteCodeRule :: Rules ()
|
generateByteCodeRule :: Rules ()
|
||||||
generateByteCodeRule =
|
generateByteCodeRule =
|
||||||
define $ \GenerateByteCode file -> do
|
define $ \GenerateByteCode file -> do
|
||||||
@ -361,6 +375,7 @@ mainRule = do
|
|||||||
generateByteCodeRule
|
generateByteCodeRule
|
||||||
loadGhcSession
|
loadGhcSession
|
||||||
getHieFileRule
|
getHieFileRule
|
||||||
|
produceCompletions
|
||||||
|
|
||||||
------------------------------------------------------------
|
------------------------------------------------------------
|
||||||
|
|
||||||
|
46
src/Development/IDE/LSP/Completions.hs
Normal file
46
src/Development/IDE/LSP/Completions.hs
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
module Development.IDE.LSP.Completions (
|
||||||
|
setHandlersCompletion
|
||||||
|
) where
|
||||||
|
|
||||||
|
import Language.Haskell.LSP.Messages
|
||||||
|
import Language.Haskell.LSP.Types
|
||||||
|
import qualified Language.Haskell.LSP.Core as LSP
|
||||||
|
import qualified Language.Haskell.LSP.VFS as VFS
|
||||||
|
import Language.Haskell.LSP.Types.Capabilities
|
||||||
|
|
||||||
|
import Development.IDE.Core.Service
|
||||||
|
import Development.IDE.Core.Completions
|
||||||
|
import Development.IDE.Types.Location
|
||||||
|
import Development.IDE.Core.PositionMapping
|
||||||
|
import Development.IDE.Core.RuleTypes
|
||||||
|
import Development.IDE.Core.Shake
|
||||||
|
import Development.IDE.LSP.Server
|
||||||
|
|
||||||
|
-- | Generate code actions.
|
||||||
|
getCompletionsLSP
|
||||||
|
:: LSP.LspFuncs ()
|
||||||
|
-> IdeState
|
||||||
|
-> CompletionParams
|
||||||
|
-> IO CompletionResponseResult
|
||||||
|
getCompletionsLSP lsp ide CompletionParams{_textDocument=TextDocumentIdentifier uri,_position=position} = do
|
||||||
|
contents <- LSP.getVirtualFileFunc lsp $ toNormalizedUri uri
|
||||||
|
case (contents, uriToFilePath' uri) of
|
||||||
|
(Just cnts, Just path) -> do
|
||||||
|
let npath = toNormalizedFilePath path
|
||||||
|
compls <- runAction ide (useWithStale ProduceCompletions npath)
|
||||||
|
case compls of
|
||||||
|
Just ((cci', tm'), mapping) -> do
|
||||||
|
let position' = fromCurrentPosition mapping position
|
||||||
|
pfix <- maybe (return Nothing) (flip VFS.getCompletionPrefix cnts) position'
|
||||||
|
case pfix of
|
||||||
|
Just pfix' -> do
|
||||||
|
let fakeClientCapabilities = ClientCapabilities Nothing Nothing Nothing Nothing
|
||||||
|
Completions . List <$> getCompletions cci' (tmrModule tm') pfix' fakeClientCapabilities (WithSnippets True)
|
||||||
|
_ -> return (Completions $ List [])
|
||||||
|
_ -> return (Completions $ List [])
|
||||||
|
_ -> return (Completions $ List [])
|
||||||
|
|
||||||
|
setHandlersCompletion :: PartialHandlers
|
||||||
|
setHandlersCompletion = PartialHandlers $ \WithMessage{..} x -> return x{
|
||||||
|
LSP.completionHandler = withResponse RspCompletion getCompletionsLSP
|
||||||
|
}
|
@ -30,6 +30,7 @@ import Control.Monad.Extra
|
|||||||
|
|
||||||
import Development.IDE.LSP.HoverDefinition
|
import Development.IDE.LSP.HoverDefinition
|
||||||
import Development.IDE.LSP.CodeAction
|
import Development.IDE.LSP.CodeAction
|
||||||
|
import Development.IDE.LSP.Completions
|
||||||
import Development.IDE.LSP.Notifications
|
import Development.IDE.LSP.Notifications
|
||||||
import Development.IDE.Core.Service
|
import Development.IDE.Core.Service
|
||||||
import Development.IDE.Types.Logger
|
import Development.IDE.Types.Logger
|
||||||
@ -97,6 +98,7 @@ runLanguageServer options userHandlers getIdeState = do
|
|||||||
setHandlersIgnore <> -- least important
|
setHandlersIgnore <> -- least important
|
||||||
setHandlersDefinition <> setHandlersHover <>
|
setHandlersDefinition <> setHandlersHover <>
|
||||||
setHandlersCodeAction <> setHandlersCodeLens <> -- useful features someone may override
|
setHandlersCodeAction <> setHandlersCodeLens <> -- useful features someone may override
|
||||||
|
setHandlersCompletion <>
|
||||||
userHandlers <>
|
userHandlers <>
|
||||||
setHandlersNotifications <> -- absolutely critical, join them with user notifications
|
setHandlersNotifications <> -- absolutely critical, join them with user notifications
|
||||||
cancelHandler cancelRequest
|
cancelHandler cancelRequest
|
||||||
|
@ -8,6 +8,7 @@ extra-deps:
|
|||||||
- hie-bios-0.3.0
|
- hie-bios-0.3.0
|
||||||
- ghc-lib-parser-8.8.1
|
- ghc-lib-parser-8.8.1
|
||||||
- ghc-lib-8.8.1
|
- ghc-lib-8.8.1
|
||||||
|
- fuzzy-0.1.0.0
|
||||||
nix:
|
nix:
|
||||||
packages: [zlib]
|
packages: [zlib]
|
||||||
flags:
|
flags:
|
||||||
|
@ -6,5 +6,6 @@ extra-deps:
|
|||||||
- haskell-lsp-types-0.19.0.0
|
- haskell-lsp-types-0.19.0.0
|
||||||
- lsp-test-0.9.0.0
|
- lsp-test-0.9.0.0
|
||||||
- hie-bios-0.3.0
|
- hie-bios-0.3.0
|
||||||
|
- fuzzy-0.1.0.0
|
||||||
nix:
|
nix:
|
||||||
packages: [zlib]
|
packages: [zlib]
|
||||||
|
@ -11,6 +11,7 @@ extra-deps:
|
|||||||
- filepattern-0.1.1
|
- filepattern-0.1.1
|
||||||
- js-dgtable-0.5.2
|
- js-dgtable-0.5.2
|
||||||
- hie-bios-0.3.0
|
- hie-bios-0.3.0
|
||||||
|
- fuzzy-0.1.0.0
|
||||||
nix:
|
nix:
|
||||||
packages: [zlib]
|
packages: [zlib]
|
||||||
allow-newer: true
|
allow-newer: true
|
||||||
|
@ -6,6 +6,7 @@ extra-deps:
|
|||||||
- haskell-lsp-types-0.19.0.0
|
- haskell-lsp-types-0.19.0.0
|
||||||
- lsp-test-0.9.0.0
|
- lsp-test-0.9.0.0
|
||||||
- hie-bios-0.3.0
|
- hie-bios-0.3.0
|
||||||
|
- fuzzy-0.1.0.0
|
||||||
allow-newer: true
|
allow-newer: true
|
||||||
nix:
|
nix:
|
||||||
packages: [zlib]
|
packages: [zlib]
|
||||||
|
@ -10,6 +10,7 @@ module Main (main) where
|
|||||||
import Control.Applicative.Combinators
|
import Control.Applicative.Combinators
|
||||||
import Control.Monad
|
import Control.Monad
|
||||||
import Control.Monad.IO.Class (liftIO)
|
import Control.Monad.IO.Class (liftIO)
|
||||||
|
import qualified Data.Aeson as Aeson
|
||||||
import Data.Char (toLower)
|
import Data.Char (toLower)
|
||||||
import Data.Foldable
|
import Data.Foldable
|
||||||
import Development.IDE.GHC.Util
|
import Development.IDE.GHC.Util
|
||||||
@ -37,6 +38,7 @@ main = defaultMain $ testGroup "HIE"
|
|||||||
closeDoc doc
|
closeDoc doc
|
||||||
void (message :: Session WorkDoneProgressEndNotification)
|
void (message :: Session WorkDoneProgressEndNotification)
|
||||||
, initializeResponseTests
|
, initializeResponseTests
|
||||||
|
, completionTests
|
||||||
, diagnosticTests
|
, diagnosticTests
|
||||||
, codeActionTests
|
, codeActionTests
|
||||||
, codeLensesTests
|
, codeLensesTests
|
||||||
@ -59,7 +61,7 @@ initializeResponseTests = withResource acquire release tests where
|
|||||||
testGroup "initialize response capabilities"
|
testGroup "initialize response capabilities"
|
||||||
[ chk " text doc sync" _textDocumentSync tds
|
[ chk " text doc sync" _textDocumentSync tds
|
||||||
, chk " hover" _hoverProvider (Just True)
|
, chk " hover" _hoverProvider (Just True)
|
||||||
, chk "NO completion" _completionProvider Nothing
|
, chk " completion" _completionProvider (Just $ CompletionOptions (Just False) Nothing Nothing)
|
||||||
, chk "NO signature help" _signatureHelpProvider Nothing
|
, chk "NO signature help" _signatureHelpProvider Nothing
|
||||||
, chk " goto definition" _definitionProvider (Just True)
|
, chk " goto definition" _definitionProvider (Just True)
|
||||||
, chk "NO goto type definition" _typeDefinitionProvider (Just $ GotoOptionsStatic False)
|
, chk "NO goto type definition" _typeDefinitionProvider (Just $ GotoOptionsStatic False)
|
||||||
@ -960,6 +962,51 @@ thTests =
|
|||||||
expectDiagnostics [ ( "B.hs", [(DsError, (6, 29), "Variable not in scope: n")] ) ]
|
expectDiagnostics [ ( "B.hs", [(DsError, (6, 29), "Variable not in scope: n")] ) ]
|
||||||
]
|
]
|
||||||
|
|
||||||
|
completionTests :: TestTree
|
||||||
|
completionTests
|
||||||
|
= testGroup "completion"
|
||||||
|
[ testSessionWait "variable" $ do
|
||||||
|
let source = T.unlines ["module A where", "f = hea"]
|
||||||
|
docId <- openDoc' "A.hs" "haskell" source
|
||||||
|
compls <- getCompletions docId (Position 1 7)
|
||||||
|
liftIO $ compls @?= [complItem "head" ["GHC.List", "base", "v", "head"] (Just CiFunction)]
|
||||||
|
, testSessionWait "type" $ do
|
||||||
|
let source = T.unlines ["{-# OPTIONS_GHC -Wall #-}", "module A () where", "f :: ()", "f = ()"]
|
||||||
|
docId <- openDoc' "A.hs" "haskell" source
|
||||||
|
expectDiagnostics [ ("A.hs", [(DsWarning, (3,0), "not used")]) ]
|
||||||
|
changeDoc docId [TextDocumentContentChangeEvent Nothing Nothing $ T.unlines ["{-# OPTIONS_GHC -Wall #-}", "module A () where", "f :: Bo", "f = True"]]
|
||||||
|
compls <- getCompletions docId (Position 2 7)
|
||||||
|
liftIO $ compls @?=
|
||||||
|
[ complItem "Bounded" ["GHC.Enum", "base", "t", "Bounded"] (Just CiClass)
|
||||||
|
, complItem "Bool" ["GHC.Types", "ghc-prim", "t", "Bool"] (Just CiClass)
|
||||||
|
]
|
||||||
|
, testSessionWait "qualified" $ do
|
||||||
|
let source = T.unlines ["{-# OPTIONS_GHC -Wunused-binds #-}", "module A () where", "f = ()"]
|
||||||
|
docId <- openDoc' "A.hs" "haskell" source
|
||||||
|
expectDiagnostics [ ("A.hs", [(DsWarning, (2, 0), "not used")]) ]
|
||||||
|
changeDoc docId [TextDocumentContentChangeEvent Nothing Nothing $ T.unlines ["{-# OPTIONS_GHC -Wunused-binds #-}", "module A () where", "f = Prelude.hea"]]
|
||||||
|
compls <- getCompletions docId (Position 2 15)
|
||||||
|
liftIO $ compls @?= [complItem "head" ["GHC.List", "base", "v", "head"] (Just CiFunction)]
|
||||||
|
]
|
||||||
|
where
|
||||||
|
complItem label xdata kind = CompletionItem
|
||||||
|
{ _label = label
|
||||||
|
, _kind = kind
|
||||||
|
, _detail = Just "Prelude"
|
||||||
|
, _documentation = Just (CompletionDocMarkup (MarkupContent {_kind = MkMarkdown, _value = ""}))
|
||||||
|
, _deprecated = Nothing
|
||||||
|
, _preselect = Nothing
|
||||||
|
, _sortText = Nothing
|
||||||
|
, _filterText = Nothing
|
||||||
|
, _insertText = Nothing
|
||||||
|
, _insertTextFormat = Just PlainText
|
||||||
|
, _textEdit = Nothing
|
||||||
|
, _additionalTextEdits = Nothing
|
||||||
|
, _commitCharacters = Nothing
|
||||||
|
, _command = Nothing
|
||||||
|
, _xdata = Just (Aeson.toJSON (xdata :: [T.Text]))
|
||||||
|
}
|
||||||
|
|
||||||
xfail :: TestTree -> String -> TestTree
|
xfail :: TestTree -> String -> TestTree
|
||||||
xfail = flip expectFailBecause
|
xfail = flip expectFailBecause
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user