@@ -8,50 +8,64 @@ module Ide.Plugin.Cabal (descriptor, haskellInteractionDescriptor, Log (..)) whe
8
8
9
9
import Control.Concurrent.Strict
10
10
import Control.DeepSeq
11
- import Control.Lens ((^.) )
11
+ import Control.Lens ((^.) )
12
12
import Control.Monad.Extra
13
13
import Control.Monad.IO.Class
14
- import Control.Monad.Trans.Maybe (runMaybeT )
15
- import qualified Data.ByteString as BS
14
+ import Control.Monad.Trans.Class (lift )
15
+ import Control.Monad.Trans.Maybe (runMaybeT )
16
+ import qualified Data.ByteString as BS
16
17
import Data.Hashable
17
- import Data.HashMap.Strict (HashMap )
18
- import qualified Data.HashMap.Strict as HashMap
19
- import qualified Data.List.NonEmpty as NE
20
- import qualified Data.Maybe as Maybe
21
- import qualified Data.Text as T
22
- import qualified Data.Text.Encoding as Encoding
23
- import qualified Data.Text.Utf16.Rope.Mixed as Rope
18
+ import Data.HashMap.Strict (HashMap )
19
+ import qualified Data.HashMap.Strict as HashMap
20
+ import qualified Data.List.NonEmpty as NE
21
+ import qualified Data.Maybe as Maybe
22
+ import qualified Data.Text as T
23
+ import qualified Data.Text.Encoding as Encoding
24
+ import Data.Text.Utf16.Rope.Mixed as Rope
24
25
import Data.Typeable
25
- import Development.IDE as D
26
- import Development.IDE.Core.Shake (restartShakeSession )
27
- import qualified Development.IDE.Core.Shake as Shake
28
- import Development.IDE.Graph (Key , alwaysRerun )
29
- import qualified Development.IDE.Plugin.Completions.Logic as Ghcide
30
- import Development.IDE.Types.Shake (toKey )
31
- import qualified Distribution.Fields as Syntax
32
- import qualified Distribution.Parsec.Position as Syntax
26
+ import Development.IDE as D
27
+ import Development.IDE.Core.FileStore (getVersionedTextDoc )
28
+ import Development.IDE.Core.PluginUtils
29
+ import Development.IDE.Core.Shake (restartShakeSession )
30
+ import qualified Development.IDE.Core.Shake as Shake
31
+ import Development.IDE.Graph (Key ,
32
+ alwaysRerun )
33
+ import Development.IDE.LSP.HoverDefinition (foundHover )
34
+ import qualified Development.IDE.Plugin.Completions.Logic as Ghcide
35
+ import Development.IDE.Types.Shake (toKey )
36
+ import qualified Distribution.Fields as Syntax
37
+ import Distribution.Package (Dependency )
38
+ import Distribution.PackageDescription (allBuildDepends ,
39
+ depPkgName ,
40
+ unPackageName )
41
+ import Distribution.PackageDescription.Configuration (flattenPackageDescription )
42
+ import qualified Distribution.Parsec.Position as Syntax
33
43
import GHC.Generics
34
- import qualified Ide.Plugin.Cabal.Completion.Completer.Types as CompleterTypes
35
- import qualified Ide.Plugin.Cabal.Completion.Completions as Completions
36
- import Ide.Plugin.Cabal.Completion.Types (ParseCabalCommonSections (ParseCabalCommonSections ),
37
- ParseCabalFields (.. ),
38
- ParseCabalFile (.. ))
39
- import qualified Ide.Plugin.Cabal.Completion.Types as Types
40
- import Ide.Plugin.Cabal.Definition (gotoDefinition )
41
- import qualified Ide.Plugin.Cabal.Diagnostics as Diagnostics
42
- import qualified Ide.Plugin.Cabal.FieldSuggest as FieldSuggest
43
- import qualified Ide.Plugin.Cabal.LicenseSuggest as LicenseSuggest
44
- import Ide.Plugin.Cabal.Orphans ()
44
+ import Ide.Plugin.Cabal.Completion.CabalFields as CabalFields
45
+ import qualified Ide.Plugin.Cabal.Completion.Completer.Types as CompleterTypes
46
+ import qualified Ide.Plugin.Cabal.Completion.Completions as Completions
47
+ import Ide.Plugin.Cabal.Completion.Types (ParseCabalCommonSections (ParseCabalCommonSections ),
48
+ ParseCabalFields (.. ),
49
+ ParseCabalFile (.. ))
50
+ import qualified Ide.Plugin.Cabal.Completion.Types as Types
51
+ import Ide.Plugin.Cabal.Definition (gotoDefinition )
52
+ import qualified Ide.Plugin.Cabal.Diagnostics as Diagnostics
53
+ import qualified Ide.Plugin.Cabal.FieldSuggest as FieldSuggest
54
+ import qualified Ide.Plugin.Cabal.LicenseSuggest as LicenseSuggest
55
+ import Ide.Plugin.Cabal.Orphans ()
45
56
import Ide.Plugin.Cabal.Outline
46
- import qualified Ide.Plugin.Cabal.Parse as Parse
57
+ import qualified Ide.Plugin.Cabal.Parse as Parse
58
+ import Ide.Plugin.Error
47
59
import Ide.Types
48
- import qualified Language.LSP.Protocol.Lens as JL
49
- import qualified Language.LSP.Protocol.Message as LSP
60
+ import qualified Language.LSP.Protocol.Lens as JL
61
+ import qualified Language.LSP.Protocol.Message as LSP
50
62
import Language.LSP.Protocol.Types
51
- import qualified Language.LSP.VFS as VFS
63
+ import qualified Language.LSP.VFS as VFS
64
+ import Text.Regex.TDFA
52
65
53
- import qualified Data.Text ()
54
- import qualified Ide.Plugin.Cabal.CabalAdd as CabalAdd
66
+
67
+ import qualified Data.Text ()
68
+ import qualified Ide.Plugin.Cabal.CabalAdd as CabalAdd
55
69
56
70
data Log
57
71
= LogModificationTime NormalizedFilePath FileVersion
@@ -118,6 +132,7 @@ descriptor recorder plId =
118
132
, mkPluginHandler LSP. SMethod_TextDocumentDocumentSymbol moduleOutline
119
133
, mkPluginHandler LSP. SMethod_TextDocumentCodeAction $ fieldSuggestCodeAction recorder
120
134
, mkPluginHandler LSP. SMethod_TextDocumentDefinition gotoDefinition
135
+ , mkPluginHandler LSP. SMethod_TextDocumentHover hover
121
136
]
122
137
, pluginNotificationHandlers =
123
138
mconcat
@@ -302,7 +317,6 @@ fieldSuggestCodeAction recorder ide _ (CodeActionParams _ _ (TextDocumentIdentif
302
317
let completionTexts = fmap (^. JL. label) completions
303
318
pure $ FieldSuggest. fieldErrorAction uri fieldName completionTexts _range
304
319
305
-
306
320
cabalAddCodeAction :: PluginMethodHandler IdeState 'LSP.Method_TextDocumentCodeAction
307
321
cabalAddCodeAction state plId (CodeActionParams _ _ (TextDocumentIdentifier uri) _ CodeActionContext {_diagnostics= diags}) = do
308
322
maxCompls <- fmap maxCompletions . liftIO $ runAction " cabal.cabal-add" state getClientConfigAction
@@ -317,7 +331,8 @@ cabalAddCodeAction state plId (CodeActionParams _ _ (TextDocumentIdentifier uri)
317
331
case mbCabalFile of
318
332
Nothing -> pure $ InL []
319
333
Just cabalFilePath -> do
320
- verTxtDocId <- lift $ pluginGetVersionedTextDoc $ TextDocumentIdentifier (filePathToUri cabalFilePath)
334
+ verTxtDocId <- runActionE " cabalAdd.getVersionedTextDoc" state $
335
+ lift $ getVersionedTextDoc $ TextDocumentIdentifier (filePathToUri cabalFilePath)
321
336
mbGPD <- liftIO $ runAction " cabal.cabal-add" state $ useWithStale ParseCabalFile $ toNormalizedFilePath cabalFilePath
322
337
case mbGPD of
323
338
Nothing -> pure $ InL []
@@ -328,6 +343,55 @@ cabalAddCodeAction state plId (CodeActionParams _ _ (TextDocumentIdentifier uri)
328
343
gpd
329
344
pure $ InL $ fmap InR actions
330
345
346
+ -- | Handler for hover messages.
347
+ --
348
+ -- Provides a Handler for displaying message on hover.
349
+ -- If found that the filtered hover message is a dependency,
350
+ -- adds a Documentation link.
351
+ hover :: PluginMethodHandler IdeState LSP. Method_TextDocumentHover
352
+ hover ide _ msgParam = do
353
+ nfp <- getNormalizedFilePathE uri
354
+ cabalFields <- runActionE " cabal.cabal-hover" ide $ useE ParseCabalFields nfp
355
+ case CabalFields. findTextWord cursor cabalFields of
356
+ Nothing ->
357
+ pure $ InR Null
358
+ Just cursorText -> do
359
+ gpd <- runActionE " cabal.GPD" ide $ useE ParseCabalFile nfp
360
+ let depsNames = map dependencyName $ allBuildDepends $ flattenPackageDescription gpd
361
+ case filterVersion cursorText of
362
+ Nothing -> pure $ InR Null
363
+ Just txt ->
364
+ if txt `elem` depsNames
365
+ then pure $ foundHover (Nothing , [txt <> " \n " , documentationText txt])
366
+ else pure $ InR Null
367
+ where
368
+ cursor = Types. lspPositionToCabalPosition (msgParam ^. JL. position)
369
+ uri = msgParam ^. JL. textDocument . JL. uri
370
+
371
+ dependencyName :: Dependency -> T. Text
372
+ dependencyName dep = T. pack $ unPackageName $ depPkgName dep
373
+
374
+ -- | Removes version requirements like
375
+ -- `==1.0.0.0`, `>= 2.1.1` that could be included in
376
+ -- hover message. Assumes that the dependency consists
377
+ -- of alphanums with dashes in between. Ends with an alphanum.
378
+ --
379
+ -- Examples:
380
+ -- >>> filterVersion "imp-deps>=2.1.1"
381
+ -- "imp-deps"
382
+ filterVersion :: T. Text -> Maybe T. Text
383
+ filterVersion msg = getMatch (msg =~ regex)
384
+ where
385
+ regex :: T. Text
386
+ regex = " ([a-zA-Z0-9-]*[a-zA-Z0-9])"
387
+
388
+ getMatch :: (T. Text , T. Text , T. Text , [T. Text ]) -> Maybe T. Text
389
+ getMatch (_, _, _, [dependency]) = Just dependency
390
+ getMatch (_, _, _, _) = Nothing -- impossible case
391
+
392
+ documentationText :: T. Text -> T. Text
393
+ documentationText package = " [Documentation](https://hackage.haskell.org/package/" <> package <> " )"
394
+
331
395
332
396
-- ----------------------------------------------------------------
333
397
-- Cabal file of Interest rules and global variable
0 commit comments