@@ -9,56 +9,63 @@ module Ide.Plugin.CabalProject where
99
1010import Control.Concurrent.Strict
1111import Control.DeepSeq
12- import Control.Lens ((^.) )
12+ import Control.Lens ((^.) )
1313import Control.Monad.Extra
1414import Control.Monad.IO.Class
15- import Control.Monad.Trans.Class (lift )
16- import Control.Monad.Trans.Maybe (runMaybeT )
17- import qualified Data.ByteString as BS
15+ import Control.Monad.Trans.Class (lift )
16+ import Control.Monad.Trans.Maybe (runMaybeT )
17+ import qualified Data.ByteString as BS
1818import Data.Hashable
19- import Data.HashMap.Strict (HashMap , toList )
20- import qualified Data.HashMap.Strict as HashMap
21- import qualified Data.List as List
22- import qualified Data.List.NonEmpty as NE
23- import qualified Data.Maybe as Maybe
19+ import Data.HashMap.Strict (HashMap ,
20+ toList )
21+ import qualified Data.HashMap.Strict as HashMap
22+ import qualified Data.List as List
23+ import qualified Data.List.NonEmpty as NE
24+ import qualified Data.Maybe as Maybe
2425import Data.Proxy
25- import qualified Data.Text ()
26- import qualified Data.Text as T
27- import qualified Data.Text.Encoding as Encoding
28- import Data.Text.Utf16.Rope.Mixed as Rope
29- import Development.IDE as D
30- import Development.IDE.Core.FileStore (getVersionedTextDoc )
26+ import qualified Data.Text ()
27+ import qualified Data.Text as T
28+ import qualified Data.Text.Encoding as Encoding
29+ import Data.Text.Utf16.Rope.Mixed as Rope
30+ import Development.IDE as D
31+ import Development.IDE.Core.FileStore (getVersionedTextDoc )
3132import Development.IDE.Core.PluginUtils
32- import Development.IDE.Core.Shake (restartShakeSession )
33- import qualified Development.IDE.Core.Shake as Shake
34- import Development.IDE.Graph (Key ,
35- alwaysRerun )
36- import Development.IDE.LSP.HoverDefinition (foundHover )
37- import qualified Development.IDE.Plugin.Completions.Logic as Ghcide
38- import Development.IDE.Types.Shake (toKey )
39- import qualified Distribution.CabalSpecVersion as Cabal
40- import qualified Distribution.Fields as Syntax
41- import Distribution.Package (Dependency )
42- import Distribution.PackageDescription (allBuildDepends ,
43- depPkgName ,
44- unPackageName )
45- import Distribution.PackageDescription.Configuration (flattenPackageDescription )
33+ import Development.IDE.Core.Shake (restartShakeSession )
34+ import qualified Development.IDE.Core.Shake as Shake
35+ import Development.IDE.Graph (Key ,
36+ alwaysRerun )
37+ import Development.IDE.LSP.HoverDefinition (foundHover )
38+ import qualified Development.IDE.Plugin.Completions.Logic as Ghcide
39+ import Development.IDE.Types.Shake (toKey )
40+ import qualified Distribution.CabalSpecVersion as Cabal
41+ import qualified Distribution.Fields as Syntax
42+ import Distribution.Package (Dependency )
43+ import Distribution.PackageDescription (allBuildDepends ,
44+ depPkgName ,
45+ unPackageName )
46+ import Distribution.PackageDescription.Configuration (flattenPackageDescription )
4647import Distribution.Parsec.Error
47- import qualified Distribution.Parsec.Position as Syntax
48+ import qualified Distribution.Parsec.Position as Syntax
4849import GHC.Generics
49- import Ide.Plugin.Cabal.Orphans ()
50- import Ide.Plugin.CabalProject.Diagnostics as Diagnostics
51- import Ide.Plugin.CabalProject.Parse as Parse
52- import Ide.Plugin.CabalProject.Types as Types
50+ import Ide.Plugin.Cabal.Completion.CabalFields as CabalFields
51+ import qualified Ide.Plugin.Cabal.Completion.Completer.Types as CompleterTypes
52+ import qualified Ide.Plugin.Cabal.Completion.Data as Data
53+ import qualified Ide.Plugin.Cabal.Completion.Types as CTypes
54+ import Ide.Plugin.Cabal.Orphans ()
55+ import qualified Ide.Plugin.CabalProject.Completion.Completions as Completions
56+ import Ide.Plugin.CabalProject.Diagnostics as Diagnostics
57+ import Ide.Plugin.CabalProject.Parse as Parse
58+ import Ide.Plugin.CabalProject.Types as Types
5359import Ide.Plugin.Error
5460import Ide.Types
55- import qualified Language.LSP.Protocol.Lens as JL
56- import qualified Language.LSP.Protocol.Message as LSP
61+ import qualified Language.LSP.Protocol.Lens as JL
62+ import qualified Language.LSP.Protocol.Message as LSP
5763import Language.LSP.Protocol.Types
58- import qualified Language.LSP.VFS as VFS
59- import System.FilePath (takeFileName )
64+ import qualified Language.LSP.VFS as VFS
65+ import System.FilePath (takeFileName )
6066import Text.Regex.TDFA
6167
68+
6269data Log
6370 = LogModificationTime NormalizedFilePath FileVersion
6471 | LogShake Shake. Log
@@ -67,6 +74,8 @@ data Log
6774 | LogDocSaved Uri
6875 | LogDocClosed Uri
6976 | LogFOI (HashMap NormalizedFilePath FileOfInterestStatus )
77+ | LogCompletionContext CTypes. Context Position
78+ | LogCompletions CTypes. Log
7079 deriving (Show )
7180
7281instance Pretty Log where
@@ -91,7 +100,9 @@ descriptor recorder plId =
91100 { pluginRules = cabalRules recorder plId
92101 , pluginHandlers =
93102 mconcat
94- []
103+ [
104+ mkPluginHandler LSP. SMethod_TextDocumentCompletion $ completion recorder
105+ ]
95106 , pluginNotificationHandlers =
96107 mconcat
97108 [ mkPluginNotificationHandler LSP. SMethod_TextDocumentDidOpen $
@@ -294,3 +305,49 @@ deleteFileOfInterest recorder state f = do
294305 return [toKey IsFileOfInterest f]
295306 where
296307 log' = logWith recorder
308+
309+ -- ----------------------------------------------------------------
310+ -- Completion
311+ -- ----------------------------------------------------------------
312+
313+ completion :: Recorder (WithPriority Log ) -> PluginMethodHandler IdeState 'LSP.Method_TextDocumentCompletion
314+ completion recorder ide _ complParams = do
315+ let TextDocumentIdentifier uri = complParams ^. JL. textDocument
316+ position = complParams ^. JL. position
317+ mContents <- liftIO $ runAction " cabal-project-plugin.getUriContents" ide $ getUriContents $ toNormalizedUri uri
318+ case (,) <$> mContents <*> uriToFilePath' uri of
319+ Just (cnts, path) -> do
320+ -- We decide on `useWithStale` here, since `useWithStaleFast` often leads to the wrong completions being suggested.
321+ -- In case it fails, we still will get some completion results instead of an error.
322+ mFields <- liftIO $ runAction " cabal-project-plugin.fields" ide $ useWithStale ParseCabalProjectFields $ toNormalizedFilePath path
323+ case mFields of
324+ Nothing ->
325+ pure . InR $ InR Null
326+ Just (fields, _) -> do
327+ let lspPrefInfo = Ghcide. getCompletionPrefixFromRope position cnts
328+ cabalPrefInfo = Completions. getCabalPrefixInfo path lspPrefInfo
329+ let res = computeCompletionsAt recorder ide cabalPrefInfo path fields
330+ liftIO $ fmap InL res
331+ Nothing -> pure . InR $ InR Null
332+
333+ computeCompletionsAt :: Recorder (WithPriority Log ) -> IdeState -> CTypes. CabalPrefixInfo -> FilePath -> [Syntax. Field Syntax. Position ] -> IO [CompletionItem ]
334+ computeCompletionsAt recorder ide prefInfo fp fields = do
335+ runMaybeT (context fields) >>= \ case
336+ Nothing -> pure []
337+ Just ctx -> do
338+ logWith recorder Debug $ LogCompletionContext ctx pos
339+ let completer = Completions. contextToCompleter ctx
340+ let completerData = CompleterTypes. CompleterData
341+ {
342+ cabalPrefixInfo = prefInfo
343+ , stanzaName =
344+ case fst ctx of
345+ CTypes. Stanza _ name -> name
346+ _ -> Nothing
347+ }
348+ completions <- completer completerRecorder completerData
349+ pure completions
350+ where
351+ pos = CTypes. completionCursorPosition prefInfo
352+ context fields = Completions. getContext completerRecorder prefInfo fields
353+ completerRecorder = cmapWithPrio LogCompletions recorder
0 commit comments