@@ -18,6 +18,8 @@ import Data.Hashable
18
18
import Data.HashMap.Strict (HashMap )
19
19
import qualified Data.HashMap.Strict as HashMap
20
20
import qualified Data.List.NonEmpty as NE
21
+ import Data.Maybe (mapMaybe )
22
+ import qualified Data.Text as T
21
23
import qualified Data.Text.Encoding as Encoding
22
24
import Data.Typeable
23
25
import Development.IDE as D
@@ -36,9 +38,9 @@ import Ide.Plugin.Cabal.Completion.Types (ParseCabalFields (
36
38
ParseCabalFile (.. ))
37
39
import qualified Ide.Plugin.Cabal.Completion.Types as Types
38
40
import qualified Ide.Plugin.Cabal.Diagnostics as Diagnostics
41
+ import qualified Ide.Plugin.Cabal.FieldSuggest as FieldSuggest
39
42
import qualified Ide.Plugin.Cabal.LicenseSuggest as LicenseSuggest
40
43
import Ide.Plugin.Cabal.Orphans ()
41
- import qualified Ide.Plugin.Cabal.FieldSuggest as FieldSuggest
42
44
import qualified Ide.Plugin.Cabal.Parse as Parse
43
45
import Ide.Types
44
46
import qualified Language.LSP.Protocol.Lens as JL
@@ -89,7 +91,7 @@ descriptor recorder plId =
89
91
mconcat
90
92
[ mkPluginHandler LSP. SMethod_TextDocumentCodeAction licenseSuggestCodeAction
91
93
, mkPluginHandler LSP. SMethod_TextDocumentCompletion $ completion recorder
92
- , mkPluginHandler LSP. SMethod_TextDocumentCodeAction fieldSuggestCodeAction
94
+ , mkPluginHandler LSP. SMethod_TextDocumentCodeAction $ fieldSuggestCodeAction recorder
93
95
]
94
96
, pluginNotificationHandlers =
95
97
mconcat
@@ -230,9 +232,34 @@ licenseSuggestCodeAction :: PluginMethodHandler IdeState 'LSP.Method_TextDocumen
230
232
licenseSuggestCodeAction _ _ (CodeActionParams _ _ (TextDocumentIdentifier uri) _range CodeActionContext {_diagnostics= diags}) =
231
233
pure $ InL $ diags >>= (fmap InR . LicenseSuggest. licenseErrorAction uri)
232
234
233
- fieldSuggestCodeAction :: PluginMethodHandler IdeState 'LSP.Method_TextDocumentCodeAction
234
- fieldSuggestCodeAction _ _ (CodeActionParams _ _ (TextDocumentIdentifier uri) _range CodeActionContext {_diagnostics= diags}) =
235
- pure $ InL $ diags >>= (fmap InR . FieldSuggest. fieldErrorAction uri)
235
+ -- | CodeActions for correcting field names with typos in them.
236
+ --
237
+ -- Provides CodeActions that fix typos in field names, in both stanzas and top-level field names.
238
+ -- The suggestions are computed based on the completion context, where we "move" a fake cursor
239
+ -- to the end of the field name and trigger cabal file completions. The completions are then
240
+ -- suggested to the user.
241
+ fieldSuggestCodeAction :: Recorder (WithPriority Log ) -> PluginMethodHandler IdeState 'LSP.Method_TextDocumentCodeAction
242
+ fieldSuggestCodeAction recorder ide _ (CodeActionParams _ _ (TextDocumentIdentifier uri) _ CodeActionContext {_diagnostics= diags}) = do
243
+ vfileM <- lift (getVirtualFile $ toNormalizedUri uri)
244
+ case (,) <$> vfileM <*> uriToFilePath' uri of
245
+ Nothing -> pure $ InL []
246
+ Just (vfile, path) -> do
247
+ mFields <- liftIO $ runIdeAction " cabal-plugin.fields" (shakeExtras ide) $ useWithStaleFast ParseCabalFields $ toNormalizedFilePath path
248
+ case mFields of
249
+ Nothing ->
250
+ pure $ InL []
251
+ Just (fields, _) -> do
252
+ let errorFields = mapMaybe FieldSuggest. fieldErrorName diags
253
+ results <- forM errorFields (getSuggestion fields vfile path)
254
+ pure $ InL $ map InR $ concat results
255
+ where
256
+ getSuggestion fields vfile fp (field,Diagnostic { _range= _range@ (Range (Position lineNr col) _) })= do
257
+ let -- Compute where we would anticipate the cursor to be.
258
+ fakeLspCursorPosition = Position lineNr (col + fromIntegral (T. length field))
259
+ lspPrefixInfo = Ghcide. getCompletionPrefix fakeLspCursorPosition vfile
260
+ completions <- liftIO $ produceCompletions recorder lspPrefixInfo fp fields (shakeExtras ide)
261
+ let completionTexts = (fmap (^. JL. label) completions)
262
+ pure $ FieldSuggest. fieldErrorAction uri field completionTexts _range
236
263
237
264
-- ----------------------------------------------------------------
238
265
-- Cabal file of Interest rules and global variable
@@ -326,32 +353,31 @@ completion recorder ide _ complParams = do
326
353
pure . InR $ InR Null
327
354
Just (fields, _) -> do
328
355
let pref = Ghcide. getCompletionPrefix position cnts
329
- let res = produceCompletions pref path fields
356
+ let res = produceCompletions recorder pref path fields (shakeExtras ide)
330
357
liftIO $ fmap InL res
331
358
Nothing -> pure . InR $ InR Null
332
- where
333
- completerRecorder = cmapWithPrio LogCompletions recorder
334
359
335
- produceCompletions :: Ghcide. PosPrefixInfo -> FilePath -> [Syntax. Field Syntax. Position ] -> IO [CompletionItem ]
336
- produceCompletions prefix fp fields = do
337
- runMaybeT (context fields) >>= \ case
338
- Nothing -> pure []
339
- Just ctx -> do
340
- logWith recorder Debug $ LogCompletionContext ctx pos
341
- let completer = Completions. contextToCompleter ctx
342
- let completerData = CompleterTypes. CompleterData
343
- { getLatestGPD = do
344
- mGPD <- runIdeAction " cabal-plugin.modulesCompleter.gpd" (shakeExtras ide) $ useWithStaleFast ParseCabalFile $ toNormalizedFilePath fp
345
- pure $ fmap fst mGPD
346
- , cabalPrefixInfo = prefInfo
347
- , stanzaName =
348
- case fst ctx of
349
- Types. Stanza _ name -> name
350
- _ -> Nothing
351
- }
352
- completions <- completer completerRecorder completerData
353
- pure completions
354
- where
355
- pos = Ghcide. cursorPos prefix
356
- context fields = Completions. getContext completerRecorder prefInfo fields
357
- prefInfo = Completions. getCabalPrefixInfo fp prefix
360
+ produceCompletions :: Recorder (WithPriority Log ) -> Ghcide. PosPrefixInfo -> FilePath -> [Syntax. Field Syntax. Position ] -> ShakeExtras -> IO [CompletionItem ]
361
+ produceCompletions recorder prefix fp fields extras = do
362
+ runMaybeT (context fields) >>= \ case
363
+ Nothing -> pure []
364
+ Just ctx -> do
365
+ logWith recorder Debug $ LogCompletionContext ctx pos
366
+ let completer = Completions. contextToCompleter ctx
367
+ let completerData = CompleterTypes. CompleterData
368
+ { getLatestGPD = do
369
+ mGPD <- runIdeAction " cabal-plugin.modulesCompleter.gpd" extras $ useWithStaleFast ParseCabalFile $ toNormalizedFilePath fp
370
+ pure $ fmap fst mGPD
371
+ , cabalPrefixInfo = prefInfo
372
+ , stanzaName =
373
+ case fst ctx of
374
+ Types. Stanza _ name -> name
375
+ _ -> Nothing
376
+ }
377
+ completions <- completer completerRecorder completerData
378
+ pure completions
379
+ where
380
+ pos = Ghcide. cursorPos prefix
381
+ context fields = Completions. getContext completerRecorder prefInfo fields
382
+ prefInfo = Completions. getCabalPrefixInfo fp prefix
383
+ completerRecorder = cmapWithPrio LogCompletions recorder
0 commit comments