Skip to content

Commit 56fa0de

Browse files
authored
cabal-add integration as a CodeAction (#4360)
If HLS detects a message like "module `Bla.Bla.Bla` is a member of a hidden package `bla-1.2.3`" it suggests a quick fix, that finds the closest cabal file and adds the dependency there. Solution uses [`Distribution.Client.Add`](https://hackage.haskell.org/package/cabal-add-0.1/candidate/docs/Distribution-Client-Add.html) from the `cabal-add` and automaticly adds version requirement, if it's detected. For now, the `cabal-add` project was linked using [remote package specification](https://cabal.readthedocs.io/en/3.4/cabal-project.html#specifying-packages-from-remote-version-control-locations). Some parts were heavily inspired by the `cabal-add` code in the main module and might be rewritten later. `CodeAction` works by parsing all haskell diagnostics, and is constructed if a suited message was found. Parsed information is passed down to a new command, which itself uses tools provided by `cabal-add`. The command conducts IO actions with found cabal file.
1 parent de36c8e commit 56fa0de

26 files changed

+673
-5
lines changed

cabal.project

+7
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,13 @@ packages:
77
./hls-plugin-api
88
./hls-test-utils
99

10+
-- Only keep this until https://github.com/Bodigrim/cabal-add/issues/7
11+
-- is resolved
12+
source-repository-package
13+
type: git
14+
location: https://github.com/Bodigrim/cabal-add.git
15+
tag: 8c004e2a4329232f9824425f5472b2d6d7958bbd
16+
1017
index-state: 2024-06-29T00:00:00Z
1118

1219
tests: True

haskell-language-server.cabal

+9
Original file line numberDiff line numberDiff line change
@@ -244,6 +244,7 @@ library hls-cabal-plugin
244244
Ide.Plugin.Cabal.Completion.Types
245245
Ide.Plugin.Cabal.FieldSuggest
246246
Ide.Plugin.Cabal.LicenseSuggest
247+
Ide.Plugin.Cabal.CabalAdd
247248
Ide.Plugin.Cabal.Orphans
248249
Ide.Plugin.Cabal.Outline
249250
Ide.Plugin.Cabal.Parse
@@ -270,6 +271,12 @@ library hls-cabal-plugin
270271
, transformers
271272
, unordered-containers >=0.2.10.0
272273
, containers
274+
, cabal-add
275+
, process
276+
, aeson
277+
, Cabal
278+
, pretty
279+
273280
hs-source-dirs: plugins/hls-cabal-plugin/src
274281

275282
test-suite hls-cabal-plugin-tests
@@ -284,6 +291,7 @@ test-suite hls-cabal-plugin-tests
284291
Context
285292
Utils
286293
Outline
294+
CabalAdd
287295
build-depends:
288296
, base
289297
, bytestring
@@ -296,6 +304,7 @@ test-suite hls-cabal-plugin-tests
296304
, lens
297305
, lsp-types
298306
, text
307+
, hls-plugin-api
299308

300309
-----------------------------
301310
-- class plugin

plugins/hls-cabal-plugin/src/Ide/Plugin/Cabal.hs

+50-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
{-# LANGUAGE OverloadedStrings #-}
55
{-# LANGUAGE TypeFamilies #-}
66

7-
module Ide.Plugin.Cabal (descriptor, Log (..)) where
7+
module Ide.Plugin.Cabal (descriptor, haskellInteractionDescriptor, Log (..)) where
88

99
import Control.Concurrent.Strict
1010
import Control.DeepSeq
@@ -53,6 +53,9 @@ import qualified Language.LSP.Protocol.Message as LSP
5353
import Language.LSP.Protocol.Types
5454
import qualified Language.LSP.VFS as VFS
5555

56+
import qualified Data.Text ()
57+
import qualified Ide.Plugin.Cabal.CabalAdd as CabalAdd
58+
5659
data Log
5760
= LogModificationTime NormalizedFilePath FileVersion
5861
| LogShake Shake.Log
@@ -63,6 +66,7 @@ data Log
6366
| LogFOI (HashMap NormalizedFilePath FileOfInterestStatus)
6467
| LogCompletionContext Types.Context Position
6568
| LogCompletions Types.Log
69+
| LogCabalAdd CabalAdd.Log
6670
deriving (Show)
6771

6872
instance Pretty Log where
@@ -86,6 +90,25 @@ instance Pretty Log where
8690
<+> "for cursor position:"
8791
<+> pretty position
8892
LogCompletions logs -> pretty logs
93+
LogCabalAdd logs -> pretty logs
94+
95+
-- | Some actions with cabal files originate from haskell files.
96+
-- This descriptor allows to hook into the diagnostics of haskell source files, and
97+
-- allows us to provide code actions and commands that interact with `.cabal` files.
98+
haskellInteractionDescriptor :: Recorder (WithPriority Log) -> PluginId -> PluginDescriptor IdeState
99+
haskellInteractionDescriptor recorder plId =
100+
(defaultPluginDescriptor plId "Provides the cabal-add code action in haskell files")
101+
{ pluginHandlers =
102+
mconcat
103+
[ mkPluginHandler LSP.SMethod_TextDocumentCodeAction cabalAddCodeAction
104+
]
105+
, pluginCommands = [PluginCommand CabalAdd.cabalAddCommand "add a dependency to a cabal file" (CabalAdd.command cabalAddRecorder)]
106+
, pluginRules = pure ()
107+
, pluginNotificationHandlers = mempty
108+
}
109+
where
110+
cabalAddRecorder = cmapWithPrio LogCabalAdd recorder
111+
89112

90113
descriptor :: Recorder (WithPriority Log) -> PluginId -> PluginDescriptor IdeState
91114
descriptor recorder plId =
@@ -309,6 +332,32 @@ gotoDefinition ideState _ msgParam = do
309332
isSectionArgName name (Syntax.Section _ sectionArgName _) = name == CabalFields.onelineSectionArgs sectionArgName
310333
isSectionArgName _ _ = False
311334

335+
cabalAddCodeAction :: PluginMethodHandler IdeState 'LSP.Method_TextDocumentCodeAction
336+
cabalAddCodeAction state plId (CodeActionParams _ _ (TextDocumentIdentifier uri) _ CodeActionContext{_diagnostics=diags}) = do
337+
maxCompls <- fmap maxCompletions . liftIO $ runAction "cabal.cabal-add" state getClientConfigAction
338+
let suggestions = take maxCompls $ concatMap CabalAdd.hiddenPackageSuggestion diags
339+
case suggestions of
340+
[] -> pure $ InL []
341+
_ ->
342+
case uriToFilePath uri of
343+
Nothing -> pure $ InL []
344+
Just haskellFilePath -> do
345+
mbCabalFile <- liftIO $ CabalAdd.findResponsibleCabalFile haskellFilePath
346+
case mbCabalFile of
347+
Nothing -> pure $ InL []
348+
Just cabalFilePath -> do
349+
verTxtDocId <- lift $ pluginGetVersionedTextDoc $ TextDocumentIdentifier (filePathToUri cabalFilePath)
350+
mbGPD <- liftIO $ runAction "cabal.cabal-add" state $ useWithStale ParseCabalFile $ toNormalizedFilePath cabalFilePath
351+
case mbGPD of
352+
Nothing -> pure $ InL []
353+
Just (gpd, _) -> do
354+
actions <- liftIO $ CabalAdd.addDependencySuggestCodeAction plId verTxtDocId
355+
suggestions
356+
haskellFilePath cabalFilePath
357+
gpd
358+
pure $ InL $ fmap InR actions
359+
360+
312361
-- ----------------------------------------------------------------
313362
-- Cabal file of Interest rules and global variable
314363
-- ----------------------------------------------------------------

0 commit comments

Comments
 (0)