{-# LANGUAGE CPP #-}
{-# LANGUAGE TypeFamilies #-}
#include "ghc-api-version.h"
module Development.IDE.Plugin.Completions(plugin) where
import Control.Applicative
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.Shake.Classes
import Development.Shake
import GHC.Generics
import Development.IDE.Plugin
import Development.IDE.Core.Service
import Development.IDE.Plugin.Completions.Logic
import Development.IDE.Types.Location
import Development.IDE.Types.Options
import Development.IDE.Core.Compile
import Development.IDE.Core.PositionMapping
import Development.IDE.Core.RuleTypes
import Development.IDE.Core.Shake
import Development.IDE.GHC.Compat (hsmodExports, ParsedModule(..), ModSummary (ms_hspp_buf))
import Development.IDE.GHC.Util
import Development.IDE.LSP.Server
import Control.Monad.Trans.Except (runExceptT)
import HscTypes (HscEnv(hsc_dflags))
import Data.Maybe
import Data.Functor ((<&>))
#if !MIN_GHC_API_VERSION(8,6,0) || defined(GHC_LIB)
import Development.IDE.Import.DependencyInformation
#endif
plugin :: Plugin c
plugin = Plugin produceCompletions setHandlersCompletion
produceCompletions :: Rules ()
produceCompletions = do
define $ \ProduceCompletions file -> do
local <- useWithStale LocalCompletions file
nonLocal <- useWithStale NonLocalCompletions file
let extract = fmap fst
return ([], extract local <> extract nonLocal)
define $ \LocalCompletions file -> do
pm <- useWithStale GetParsedModule file
case pm of
Just (pm, _) -> do
let cdata = localCompletionsForParsedModule pm
return ([], Just cdata)
_ -> return ([], Nothing)
define $ \NonLocalCompletions file -> do
ms <- fmap fst <$> useWithStale GetModSummaryWithoutTimestamps file
sess <- fmap fst <$> useWithStale GhcSessionDeps file
#if MIN_GHC_API_VERSION(8,6,0) && !defined(GHC_LIB)
let parsedDeps = []
#else
deps <- maybe (TransitiveDependencies [] [] []) fst <$> useWithStale GetDependencies file
parsedDeps <- mapMaybe (fmap fst) <$> usesWithStale GetParsedModule (transitiveModuleDeps deps)
#endif
case (ms, sess) of
(Just ms, Just sess) -> do
let env = hscEnv sess
buf = fromJust $ ms_hspp_buf ms
f = fromNormalizedFilePath file
dflags = hsc_dflags env
pm <- liftIO $ evalGhcEnv env $ runExceptT $ parseHeader dflags f buf
case pm of
Right (_diags, hsMod) -> do
let hsModNoExports = hsMod <&> \x -> x{hsmodExports = Nothing}
pm = ParsedModule
{ pm_mod_summary = ms
, pm_parsed_source = hsModNoExports
, pm_extra_src_files = []
, pm_annotations = mempty
}
tm <- liftIO $ typecheckModule (IdeDefer True) env pm
case tm of
(_, Just (_,TcModuleResult{..})) -> do
cdata <- liftIO $ cacheDataProducer env tmrModule parsedDeps
return ([], Just cdata)
(_diag, _) ->
return ([], Nothing)
Left _diag ->
return ([], Nothing)
_ -> return ([], Nothing)
type instance RuleResult ProduceCompletions = CachedCompletions
type instance RuleResult LocalCompletions = CachedCompletions
type instance RuleResult NonLocalCompletions = CachedCompletions
data ProduceCompletions = ProduceCompletions
deriving (Eq, Show, Typeable, Generic)
instance Hashable ProduceCompletions
instance NFData ProduceCompletions
instance Binary ProduceCompletions
data LocalCompletions = LocalCompletions
deriving (Eq, Show, Typeable, Generic)
instance Hashable LocalCompletions
instance NFData LocalCompletions
instance Binary LocalCompletions
data NonLocalCompletions = NonLocalCompletions
deriving (Eq, Show, Typeable, Generic)
instance Hashable NonLocalCompletions
instance NFData NonLocalCompletions
instance Binary NonLocalCompletions
getCompletionsLSP
:: LSP.LspFuncs c
-> IdeState
-> CompletionParams
-> IO (Either ResponseError CompletionResponseResult)
getCompletionsLSP lsp ide
CompletionParams{_textDocument=TextDocumentIdentifier uri
,_position=position
,_context=completionContext} = do
contents <- LSP.getVirtualFileFunc lsp $ toNormalizedUri uri
fmap Right $ case (contents, uriToFilePath' uri) of
(Just cnts, Just path) -> do
let npath = toNormalizedFilePath' path
(ideOpts, compls) <- runIdeAction "Completion" (shakeExtras ide) $ do
opts <- liftIO $ getIdeOptionsIO $ shakeExtras ide
compls <- useWithStaleFast ProduceCompletions npath
pm <- useWithStaleFast GetParsedModule npath
pure (opts, liftA2 (,) compls pm)
case compls of
Just ((cci', _), (pm, mapping)) -> do
let !position' = fromCurrentPosition mapping position
pfix <- maybe (return Nothing) (flip VFS.getCompletionPrefix cnts) position'
case (pfix, completionContext) of
(Just (VFS.PosPrefixInfo _ "" _ _), Just CompletionContext { _triggerCharacter = Just "."})
-> return (Completions $ List [])
(Just pfix', _) -> do
let fakeClientCapabilities = ClientCapabilities Nothing Nothing Nothing Nothing
Completions . List <$> getCompletions ideOpts cci' pm pfix' fakeClientCapabilities (WithSnippets True)
_ -> return (Completions $ List [])
_ -> return (Completions $ List [])
_ -> return (Completions $ List [])
setHandlersCompletion :: PartialHandlers c
setHandlersCompletion = PartialHandlers $ \WithMessage{..} x -> return x{
LSP.completionHandler = withResponse RspCompletion getCompletionsLSP
}