Skip to content

Commit e2201ec

Browse files
dyniecfendor
authored andcommitted
Use context in code actions for cabal files
1 parent 6932df5 commit e2201ec

File tree

2 files changed

+91
-71
lines changed

2 files changed

+91
-71
lines changed

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

Lines changed: 54 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -19,21 +19,23 @@ import Data.Hashable
1919
import Data.HashMap.Strict (HashMap)
2020
import qualified Data.HashMap.Strict as HashMap
2121
import qualified Data.List.NonEmpty as NE
22+
import Data.Maybe (mapMaybe)
23+
import qualified Data.Text as T
2224
import qualified Data.Text.Encoding as Encoding
25+
import Data.Text.Utf16.Rope.Mixed (Rope)
2326
import Data.Typeable
2427
import Development.IDE as D
2528
import Development.IDE.Core.Shake (restartShakeSession)
2629
import qualified Development.IDE.Core.Shake as Shake
2730
import Development.IDE.Graph (alwaysRerun)
2831
import qualified Development.IDE.Plugin.Completions.Logic as Ghcide
29-
import qualified Development.IDE.Plugin.Completions.Types as Ghcide
3032
import GHC.Generics
3133
import qualified Ide.Plugin.Cabal.Completion.Completer.Types as CompleterTypes
3234
import qualified Ide.Plugin.Cabal.Completion.Completions as Completions
3335
import qualified Ide.Plugin.Cabal.Completion.Types as Types
3436
import qualified Ide.Plugin.Cabal.Diagnostics as Diagnostics
35-
import qualified Ide.Plugin.Cabal.LicenseSuggest as LicenseSuggest
3637
import qualified Ide.Plugin.Cabal.FieldSuggest as FieldSuggest
38+
import qualified Ide.Plugin.Cabal.LicenseSuggest as LicenseSuggest
3739
import qualified Ide.Plugin.Cabal.Parse as Parse
3840
import Ide.Types
3941
import qualified Language.LSP.Protocol.Lens as JL
@@ -84,7 +86,7 @@ descriptor recorder plId =
8486
mconcat
8587
[ mkPluginHandler LSP.SMethod_TextDocumentCodeAction licenseSuggestCodeAction
8688
, mkPluginHandler LSP.SMethod_TextDocumentCompletion $ completion recorder
87-
, mkPluginHandler LSP.SMethod_TextDocumentCodeAction fieldSuggestCodeAction
89+
, mkPluginHandler LSP.SMethod_TextDocumentCodeAction $ fieldSuggestCodeAction recorder
8890
]
8991
, pluginNotificationHandlers =
9092
mconcat
@@ -199,9 +201,27 @@ licenseSuggestCodeAction :: PluginMethodHandler IdeState 'LSP.Method_TextDocumen
199201
licenseSuggestCodeAction _ _ (CodeActionParams _ _ (TextDocumentIdentifier uri) _range CodeActionContext{_diagnostics=diags}) =
200202
pure $ InL $ diags >>= (fmap InR . LicenseSuggest.licenseErrorAction uri)
201203

202-
fieldSuggestCodeAction :: PluginMethodHandler IdeState 'LSP.Method_TextDocumentCodeAction
203-
fieldSuggestCodeAction _ _ (CodeActionParams _ _ (TextDocumentIdentifier uri) _range CodeActionContext{_diagnostics=diags}) =
204-
pure $ InL $ diags >>= (fmap InR . FieldSuggest.fieldErrorAction uri)
204+
-- | CodeActions for misspelled fields in cabal files both for toplevel fields, and fields in stanzas.
205+
-- Uses same logic as completions but reacts on diagnostics from cabal.
206+
fieldSuggestCodeAction :: Recorder (WithPriority Log) -> PluginMethodHandler IdeState 'LSP.Method_TextDocumentCodeAction
207+
fieldSuggestCodeAction recorder ide _ (CodeActionParams _ _ (TextDocumentIdentifier uri) _ CodeActionContext{_diagnostics=diags}) = do
208+
vfileM <- lift (getVirtualFile $ toNormalizedUri uri)
209+
case liftA2 (,) vfileM (uriToFilePath' uri) of
210+
Nothing -> pure $ InL []
211+
Just (vfile, path) -> do
212+
let fields = mapMaybe FieldSuggest.fieldErrorName diags
213+
results <- forM fields (getSuggestion vfile path)
214+
pure $ InL $ map InR $ concat results
215+
where
216+
getSuggestion vfile fp (field,Diagnostic{ _range=_range@(Range (Position lineNr col) _) })= do
217+
let -- compute where we would anticipate the cursor to be.
218+
-- This is an heuristic and could be incorrect.
219+
fakeLspCursorPosition = Position lineNr (col + fromIntegral (T.length field))
220+
lspPrefixInfo = Ghcide.getCompletionPrefix fakeLspCursorPosition vfile
221+
cabalPrefixInfo = Completions.getCabalPrefixInfo fp lspPrefixInfo
222+
completions <- liftIO $ computeCompletionsAt recorder cabalPrefixInfo fp (vfile ^. VFS.file_text) (shakeExtras ide)
223+
let completionTexts = (fmap (^. JL.label) completions)
224+
pure $ FieldSuggest.fieldErrorAction uri field completionTexts _range
205225

206226
-- ----------------------------------------------------------------
207227
-- Cabal file of Interest rules and global variable
@@ -287,32 +307,32 @@ completion recorder ide _ complParams = do
287307
contents <- lift $ getVirtualFile $ toNormalizedUri uri
288308
case (contents, uriToFilePath' uri) of
289309
(Just cnts, Just path) -> do
290-
let pref = Ghcide.getCompletionPrefix position cnts
291-
let res = result pref path cnts
292-
liftIO $ fmap InL res
310+
let lspPrefixInfo = Ghcide.getCompletionPrefix position cnts
311+
cabalPrefixInfo = Completions.getCabalPrefixInfo path lspPrefixInfo
312+
let compls = computeCompletionsAt recorder cabalPrefixInfo path (cnts ^. VFS.file_text) (shakeExtras ide)
313+
liftIO $ fmap InL compls
293314
_ -> pure . InR $ InR Null
294-
where
295-
result :: Ghcide.PosPrefixInfo -> FilePath -> VFS.VirtualFile -> IO [CompletionItem]
296-
result prefix fp cnts = do
297-
runMaybeT context >>= \case
298-
Nothing -> pure []
299-
Just ctx -> do
300-
logWith recorder Debug $ LogCompletionContext ctx pos
301-
let completer = Completions.contextToCompleter ctx
302-
let completerData = CompleterTypes.CompleterData
303-
{ getLatestGPD = do
304-
mGPD <- runIdeAction "cabal-plugin.modulesCompleter.gpd" (shakeExtras ide) $ useWithStaleFast Types.GetCabalDiagnostics $ toNormalizedFilePath fp
305-
pure $ fmap fst mGPD
306-
, cabalPrefixInfo = prefInfo
307-
, stanzaName =
308-
case fst ctx of
309-
Types.Stanza _ name -> name
310-
_ -> Nothing
311-
}
312-
completions <- completer completerRecorder completerData
313-
pure completions
314-
where
315-
completerRecorder = cmapWithPrio LogCompletions recorder
316-
pos = Ghcide.cursorPos prefix
317-
context = Completions.getContext completerRecorder prefInfo (cnts ^. VFS.file_text)
318-
prefInfo = Completions.getCabalPrefixInfo fp prefix
315+
316+
computeCompletionsAt :: Recorder (WithPriority Log) -> Types.CabalPrefixInfo -> FilePath -> Rope -> ShakeExtras -> IO [CompletionItem]
317+
computeCompletionsAt recorder cabalPrefixInfo fp fileRope extras = do
318+
runMaybeT context >>= \case
319+
Nothing -> pure []
320+
Just ctx -> do
321+
logWith recorder Debug $ LogCompletionContext ctx pos
322+
let completer = Completions.contextToCompleter ctx
323+
let completerData = CompleterTypes.CompleterData
324+
{ getLatestGPD = do
325+
mGPD <- runIdeAction "computeCompletionsAt.gpd" extras $ useWithStaleFast Types.GetCabalDiagnostics $ toNormalizedFilePath fp
326+
pure $ fmap fst mGPD
327+
, cabalPrefixInfo = cabalPrefixInfo
328+
, stanzaName =
329+
case fst ctx of
330+
Types.Stanza _ name -> name
331+
_ -> Nothing
332+
}
333+
completions <- completer completerRecorder completerData
334+
pure completions
335+
where
336+
completerRecorder = cmapWithPrio LogCompletions recorder
337+
pos = Types.completionCursorPosition cabalPrefixInfo
338+
context = Completions.getContext completerRecorder cabalPrefixInfo fileRope

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

Lines changed: 37 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -3,65 +3,65 @@
33
{-# LANGUAGE FlexibleContexts #-}
44
{-# LANGUAGE LambdaCase #-}
55
{-# LANGUAGE OverloadedStrings #-}
6+
67
module Ide.Plugin.Cabal.FieldSuggest
7-
( fieldErrorSuggestion
8-
, fieldErrorAction
9-
-- * Re-exports
10-
, T.Text
11-
, Diagnostic(..)
12-
)
8+
( fieldErrorName,
9+
fieldErrorAction,
10+
-- * Re-exports
11+
T.Text,
12+
Diagnostic (..),
13+
)
1314
where
1415

1516
import qualified Data.Map.Strict as Map
1617
import qualified Data.Text as T
17-
import Language.LSP.Protocol.Types (CodeAction (CodeAction),
18+
import Language.LSP.Protocol.Types (CodeAction (..),
1819
CodeActionKind (..),
19-
Diagnostic (..),
20-
Position (Position),
21-
Range (Range),
22-
TextEdit (TextEdit), Uri,
23-
WorkspaceEdit (WorkspaceEdit))
20+
Diagnostic (..), Position (..),
21+
Range (..), TextEdit (..), Uri,
22+
WorkspaceEdit (..))
2423
import Text.Regex.TDFA
2524

26-
-- | Given a diagnostic returned by 'Ide.Plugin.Cabal.Diag.errorDiagnostic',
27-
-- if it represents an "Unknown field"-error along
28-
-- with a incorrect field, then return a 'CodeAction' for replacing the
29-
-- the incorrect field with the suggestion.
30-
-- It should be context sensitive, but for now it isn't
25+
-- | Generate all code action for given file, error field in position and suggestions
3126
fieldErrorAction
3227
:: Uri
3328
-- ^ File for which the diagnostic was generated
34-
-> Diagnostic
35-
-- ^ Output of 'Ide.Plugin.Cabal.Diag.errorDiagnostic'
29+
-> T.Text
30+
-- ^ Original field
31+
-> [T.Text]
32+
-- ^ Suggestions
33+
-> Range
34+
-- ^ location of diagnostic
3635
-> [CodeAction]
37-
fieldErrorAction uri diag =
38-
mkCodeAction <$> fieldErrorSuggestion diag
36+
fieldErrorAction uri original suggestions range =
37+
fmap mkCodeAction suggestions
3938
where
40-
mkCodeAction (original, suggestion) =
39+
mkCodeAction suggestion =
4140
let
4241
-- Range returned by cabal here represents fragment from start of
4342
-- offending identifier to end of line, we modify it to the end of identifier
44-
adjustRange (Range rangeFrom@(Position line col) _) =
45-
Range rangeFrom (Position line (col + fromIntegral (T.length original)))
46-
title = "Replace with " <> suggestion
47-
tedit = [TextEdit (adjustRange $ _range diag) suggestion]
43+
adjustRange (Range rangeFrom@(Position lineNr col) _) =
44+
Range rangeFrom (Position lineNr (col + fromIntegral (T.length original)))
45+
title = "Replace with " <> suggestion'
46+
tedit = [TextEdit (adjustRange range ) suggestion']
4847
edit = WorkspaceEdit (Just $ Map.singleton uri tedit) Nothing Nothing
4948
in CodeAction title (Just CodeActionKind_QuickFix) (Just []) Nothing Nothing (Just edit) Nothing Nothing
49+
where
50+
-- dropping colon from the end of suggestion
51+
suggestion' = T.dropEnd 1 suggestion
5052

5153
-- | Given a diagnostic returned by 'Ide.Plugin.Cabal.Diag.errorDiagnostic',
5254
-- if it represents an "Unknown field"- error with incorrect identifier
53-
-- then return the suggestion (for now placeholder "name")
54-
-- along with the incorrect identifier.
55-
--
56-
fieldErrorSuggestion
57-
:: Diagnostic
58-
-- ^ Output of 'Ide.Plugin.Cabal.Diag.errorDiagnostic'
59-
-> [(T.Text, T.Text)]
60-
-- ^ (Original (incorrect) license identifier, suggested replacement)
61-
fieldErrorSuggestion diag =
55+
-- then return the incorrect identifier together with original diagnostics.
56+
fieldErrorName ::
57+
-- | Output of 'Ide.Plugin.Cabal.Diag.errorDiagnostic'
58+
Diagnostic ->
59+
-- | (Original (incorrect) license identifier, suggested replacement)
60+
Maybe (T.Text, Diagnostic)
61+
fieldErrorName diag =
6262
mSuggestion (_message diag) >>= \case
63-
[original] -> [(original, "name")]
64-
_ -> []
63+
[original] -> Just (original, diag)
64+
_ -> Nothing
6565
where
6666
regex :: T.Text
6767
regex = "Unknown field: \"(.*)\""

0 commit comments

Comments
 (0)