Skip to content

Commit f23fe20

Browse files
committed
hls-cabal-project-plugin: Add completions suggestions for cabal.project files.
1 parent a1e13bf commit f23fe20

File tree

13 files changed

+648
-92
lines changed

13 files changed

+648
-92
lines changed

haskell-language-server.cabal

Lines changed: 4 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -345,6 +345,8 @@ library hls-cabal-project-plugin
345345
Ide.Plugin.CabalProject.Parse
346346
Ide.Plugin.CabalProject.Diagnostics
347347
Ide.Plugin.CabalProject.Types
348+
Ide.Plugin.CabalProject.Completion.Completions
349+
Ide.Plugin.CabalProject.Completion.Data
348350

349351
build-depends:
350352
, bytestring
@@ -387,6 +389,7 @@ test-suite hls-cabal-project-plugin-tests
387389
hs-source-dirs: plugins/hls-cabal-project-plugin/test
388390
main-is: Main.hs
389391
other-modules:
392+
Completer
390393
Utils
391394
build-depends:
392395
, bytestring
@@ -401,77 +404,7 @@ test-suite hls-cabal-project-plugin-tests
401404
, text
402405
, hls-plugin-api
403406
, cabal-install
404-
405-
-----------------------------
406-
-- cabal project plugin
407-
-----------------------------
408-
409-
flag cabalProject
410-
description: Enable cabalProject plugin
411-
default: True
412-
manual: True
413-
414-
common cabalProject
415-
if flag(cabalProject)
416-
build-depends: haskell-language-server:hls-cabal-project-plugin
417-
cpp-options: -Dhls_cabal_project
418-
419-
library hls-cabal-project-plugin
420-
import: defaults, pedantic, warnings
421-
if !flag(cabal)
422-
buildable: False
423-
exposed-modules:
424-
Ide.Plugin.CabalProject
425-
426-
427-
build-depends:
428-
, bytestring
429-
, Cabal-syntax >= 3.7
430-
, containers
431-
, deepseq
432-
, directory
433-
, filepath
434-
, extra >=1.7.4
435-
, ghcide == 2.9.0.1
436-
, hashable
437-
, hls-plugin-api == 2.9.0.1
438-
, hls-graph == 2.9.0.1
439-
, lens
440-
, lsp ^>=2.7
441-
, lsp-types ^>=2.3
442-
, regex-tdfa ^>=1.3.1
443-
, text
444-
, text-rope
445-
, transformers
446-
, unordered-containers >=0.2.10.0
447-
, containers
448-
, process
449-
, aeson
450-
, Cabal
451-
, pretty
452-
453-
hs-source-dirs: plugins/hls-cabal-project-plugin/src
454-
455-
test-suite hls-cabal-project-plugin-tests
456-
import: defaults, pedantic, test-defaults, warnings
457-
if !flag(cabalProject)
458-
buildable: False
459-
type: exitcode-stdio-1.0
460-
hs-source-dirs: plugins/hls-cabal-project-plugin/test
461-
main-is: Main.hs
462-
other-modules:
463-
build-depends:
464-
, bytestring
465-
, Cabal-syntax >= 3.7
466-
, extra
467-
, filepath
468-
, ghcide
469-
, haskell-language-server:hls-cabal-project-plugin
470-
, hls-test-utils == 2.9.0.1
471-
, lens
472-
, lsp-types
473-
, text
474-
, hls-plugin-api
407+
, haskell-language-server:hls-cabal-plugin
475408

476409
-----------------------------
477410
-- class plugin

plugins/hls-cabal-plugin/test/testdata/completer.cabal

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,4 +11,4 @@ be
1111
library
1212
lib
1313

14-
co
14+
co

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

Lines changed: 94 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -9,31 +9,45 @@ module Ide.Plugin.CabalProject where
99

1010
import Control.Concurrent.Strict
1111
import Control.DeepSeq
12+
import Control.Lens ((^.))
1213
import Control.Monad.Extra
1314
import Control.Monad.IO.Class
14-
import qualified Data.ByteString as BS
15+
import Control.Monad.Trans.Maybe (runMaybeT)
16+
import qualified Data.ByteString as BS
1517
import Data.Hashable
16-
import Data.HashMap.Strict (HashMap)
17-
import qualified Data.HashMap.Strict as HashMap
18-
import qualified Data.List.NonEmpty as NE
18+
import Data.HashMap.Strict (HashMap)
19+
-- toList)
20+
import qualified Data.HashMap.Strict as HashMap
21+
import qualified Data.List.NonEmpty as NE
1922
import Data.Proxy
20-
import qualified Data.Text ()
21-
import qualified Data.Text.Encoding as Encoding
22-
import Data.Text.Utf16.Rope.Mixed as Rope
23-
import Development.IDE as D
24-
import Development.IDE.Core.Shake (restartShakeSession)
25-
import qualified Development.IDE.Core.Shake as Shake
26-
import Development.IDE.Graph (Key, alwaysRerun)
27-
import Development.IDE.Types.Shake (toKey)
23+
import qualified Data.Text ()
24+
import qualified Data.Text.Encoding as Encoding
25+
import Data.Text.Utf16.Rope.Mixed as Rope
26+
import Development.IDE as D
27+
import Development.IDE.Core.Shake (restartShakeSession)
28+
import qualified Development.IDE.Core.Shake as Shake
29+
import Development.IDE.Graph (Key,
30+
alwaysRerun)
31+
import qualified Development.IDE.Plugin.Completions.Logic as Ghcide
32+
import Development.IDE.Types.Shake (toKey)
33+
import qualified Distribution.Fields as Syntax
34+
-- import Distribution.PackageDescription (allBuildDepends,
35+
-- depPkgName,
36+
-- unPackageName)
37+
import qualified Distribution.Parsec.Position as Syntax
2838
import GHC.Generics
29-
import Ide.Plugin.Cabal.Orphans ()
30-
import Ide.Plugin.CabalProject.Diagnostics as Diagnostics
31-
import Ide.Plugin.CabalProject.Parse as Parse
32-
import Ide.Plugin.CabalProject.Types as Types
39+
import qualified Ide.Plugin.Cabal.Completion.Completer.Types as CompleterTypes
40+
import qualified Ide.Plugin.Cabal.Completion.Types as CTypes
41+
import Ide.Plugin.Cabal.Orphans ()
42+
import qualified Ide.Plugin.CabalProject.Completion.Completions as Completions
43+
import Ide.Plugin.CabalProject.Diagnostics as Diagnostics
44+
import Ide.Plugin.CabalProject.Parse as Parse
45+
import Ide.Plugin.CabalProject.Types as Types
3346
import Ide.Types
34-
import qualified Language.LSP.Protocol.Message as LSP
47+
import qualified Language.LSP.Protocol.Lens as JL
48+
import qualified Language.LSP.Protocol.Message as LSP
3549
import Language.LSP.Protocol.Types
36-
import qualified Language.LSP.VFS as VFS
50+
import qualified Language.LSP.VFS as VFS
3751

3852
data Log
3953
= LogModificationTime NormalizedFilePath FileVersion
@@ -43,6 +57,8 @@ data Log
4357
| LogDocSaved Uri
4458
| LogDocClosed Uri
4559
| LogFOI (HashMap NormalizedFilePath FileOfInterestStatus)
60+
| LogCompletionContext CTypes.Context Position
61+
| LogCompletions CTypes.Log
4662
deriving (Show)
4763

4864
instance Pretty Log where
@@ -60,11 +76,22 @@ instance Pretty Log where
6076
"Closed text document:" <+> pretty (getUri uri)
6177
LogFOI files ->
6278
"Set files of interest to:" <+> viaShow files
79+
LogCompletionContext context position ->
80+
"Determined completion context:"
81+
<+> pretty context
82+
<+> "for cursor position:"
83+
<+> pretty position
84+
LogCompletions logs -> pretty logs
6385

6486
descriptor :: Recorder (WithPriority Log) -> PluginId -> PluginDescriptor IdeState
6587
descriptor recorder plId =
6688
(defaultCabalProjectPluginDescriptor plId "Provides a variety of IDE features in cabal.project files")
6789
{ pluginRules = cabalProjectRules recorder plId
90+
, pluginHandlers =
91+
mconcat
92+
[
93+
mkPluginHandler LSP.SMethod_TextDocumentCompletion $ completion recorder
94+
]
6895
, pluginNotificationHandlers =
6996
mconcat
7097
[ mkPluginNotificationHandler LSP.SMethod_TextDocumentDidOpen $
@@ -179,8 +206,8 @@ cabalProjectRules recorder plId = do
179206
where
180207
log' = logWith recorder
181208

182-
{- | This is the kick function for the cabal.project plugin.
183-
We run this action, whenever a shake session is run/restarted, which triggers
209+
{- | This is the kick function for the cabal project plugin.
210+
We run this action, whenever we shake session us run/restarted, which triggers
184211
actions to produce diagnostics for cabal.project files.
185212
186213
It is paramount that this kick-function can be run quickly, since it is a blocking
@@ -189,6 +216,7 @@ function invocation.
189216
kick :: Action ()
190217
kick = do
191218
files <- HashMap.keys <$> getCabalProjectFilesOfInterestUntracked
219+
-- let keys = map Types.ParseCabalProjectFile files
192220
Shake.runWithSignal (Proxy @"kick/start/cabal-project") (Proxy @"kick/done/cabal-project") files Types.ParseCabalProjectFile
193221

194222

@@ -266,3 +294,49 @@ deleteFileOfInterest recorder state f = do
266294
return [toKey IsFileOfInterest f]
267295
where
268296
log' = logWith recorder
297+
298+
-- ----------------------------------------------------------------
299+
-- Completion
300+
-- ----------------------------------------------------------------
301+
302+
completion :: Recorder (WithPriority Log) -> PluginMethodHandler IdeState 'LSP.Method_TextDocumentCompletion
303+
completion recorder ide _ complParams = do
304+
let TextDocumentIdentifier uri = complParams ^. JL.textDocument
305+
position = complParams ^. JL.position
306+
mContents <- liftIO $ runAction "cabal-project-plugin.getUriContents" ide $ getUriContents $ toNormalizedUri uri
307+
case (,) <$> mContents <*> uriToFilePath' uri of
308+
Just (cnts, path) -> do
309+
mFields <- liftIO $ runAction "cabal-project-plugin.fields" ide $ useWithStale ParseCabalProjectFields $ toNormalizedFilePath path
310+
case mFields of
311+
Nothing ->
312+
pure . InR $ InR Null
313+
Just (fields, _) -> do
314+
let lspPrefInfo = Ghcide.getCompletionPrefixFromRope position cnts
315+
cabalProjectPrefInfo = Completions.getCabalProjectPrefixInfo path lspPrefInfo
316+
let res = computeCompletionsAt recorder ide cabalProjectPrefInfo path fields
317+
liftIO $ fmap InL res
318+
Nothing -> pure . InR $ InR Null
319+
320+
computeCompletionsAt :: Recorder (WithPriority Log) -> IdeState -> CTypes.CabalPrefixInfo -> FilePath -> [Syntax.Field Syntax.Position] -> IO [CompletionItem]
321+
computeCompletionsAt recorder _ prefInfo _ fields = do
322+
runMaybeT (context fields) >>= \case
323+
Nothing -> pure []
324+
Just ctx -> do
325+
logWith recorder Debug $ LogCompletionContext ctx pos
326+
let completer = Completions.contextToCompleter ctx
327+
let completerData = CompleterTypes.CompleterData
328+
{
329+
getLatestGPD = pure Nothing,
330+
getCabalCommonSections = pure Nothing,
331+
cabalPrefixInfo = prefInfo
332+
, stanzaName =
333+
case fst ctx of
334+
CTypes.Stanza _ name -> name
335+
_ -> Nothing
336+
}
337+
completions <- completer completerRecorder completerData
338+
pure completions
339+
where
340+
pos = CTypes.completionCursorPosition prefInfo
341+
context fields = Completions.getContext completerRecorder prefInfo fields
342+
completerRecorder = cmapWithPrio LogCompletions recorder

0 commit comments

Comments
 (0)