Skip to content

Commit 09af6a4

Browse files
committed
very basic constant completions with formatting
1 parent 8dc4a54 commit 09af6a4

File tree

8 files changed

+377
-39
lines changed

8 files changed

+377
-39
lines changed

haskell-language-server.cabal

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -340,6 +340,12 @@ library hls-cabal-project-plugin
340340
Ide.Plugin.CabalProject.Parse
341341
Ide.Plugin.CabalProject.Diagnostics
342342
Ide.Plugin.CabalProject.Types
343+
Ide.Plugin.CabalProject.Completion.Completions
344+
Ide.Plugin.CabalProject.Completion.Completer.Simple
345+
Ide.Plugin.CabalProject.Completion.Completer.Types
346+
Ide.Plugin.CabalProject.Completion.CabalProjectFields
347+
Ide.Plugin.CabalProject.Completion.Data
348+
343349

344350
build-depends:
345351
, bytestring

plugins/hls-cabal-project-plugin/src/Ide/Plugin/CabalProject.hs

Lines changed: 96 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -9,56 +9,63 @@ module Ide.Plugin.CabalProject where
99

1010
import Control.Concurrent.Strict
1111
import Control.DeepSeq
12-
import Control.Lens ((^.))
12+
import Control.Lens ((^.))
1313
import Control.Monad.Extra
1414
import 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
1818
import 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
2425
import 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)
3132
import 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)
4647
import Distribution.Parsec.Error
47-
import qualified Distribution.Parsec.Position as Syntax
48+
import qualified Distribution.Parsec.Position as Syntax
4849
import 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
5359
import Ide.Plugin.Error
5460
import 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
5763
import 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)
6066
import Text.Regex.TDFA
6167

68+
6269
data 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

7281
instance 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
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
module Ide.Plugin.CabalProject.Completion.CabalProjectFields where
2+
3+
import Ide.Plugin.Cabal.Completion.CabalFields (findFieldSection,
4+
findStanzaForColumn,
5+
getAnnotation,
6+
getFieldName,
7+
getOptionalSectionName)
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
module Ide.Plugin.CabalProject.Completion.Completer.Simple where
2+
3+
import Ide.Plugin.Cabal.Completion.Completer.Simple
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{-# LANGUAGE TypeFamilies #-}
2+
3+
module Ide.Plugin.CabalProject.Completion.Completer.Types where
4+
5+
import Ide.Plugin.Cabal.Completion.Completer.Types

0 commit comments

Comments
 (0)