@@ -19,14 +19,14 @@ import Data.HashMap.Strict (HashMap)
19
19
import qualified Data.HashMap.Strict as HashMap
20
20
import qualified Data.List.NonEmpty as NE
21
21
import qualified Data.Maybe as Maybe
22
+ import qualified Data.Text as T
22
23
import qualified Data.Text.Encoding as Encoding
23
24
import Data.Typeable
24
25
import Development.IDE as D
25
26
import Development.IDE.Core.Shake (restartShakeSession )
26
27
import qualified Development.IDE.Core.Shake as Shake
27
28
import Development.IDE.Graph (Key , alwaysRerun )
28
29
import qualified Development.IDE.Plugin.Completions.Logic as Ghcide
29
- import qualified Development.IDE.Plugin.Completions.Types as Ghcide
30
30
import Development.IDE.Types.Shake (toKey )
31
31
import qualified Distribution.Fields as Syntax
32
32
import qualified Distribution.Parsec.Position as Syntax
@@ -38,6 +38,7 @@ import Ide.Plugin.Cabal.Completion.Types (ParseCabalCommonSe
38
38
ParseCabalFile (.. ))
39
39
import qualified Ide.Plugin.Cabal.Completion.Types as Types
40
40
import qualified Ide.Plugin.Cabal.Diagnostics as Diagnostics
41
+ import qualified Ide.Plugin.Cabal.FieldSuggest as FieldSuggest
41
42
import qualified Ide.Plugin.Cabal.LicenseSuggest as LicenseSuggest
42
43
import Ide.Plugin.Cabal.Orphans ()
43
44
import qualified Ide.Plugin.Cabal.Parse as Parse
@@ -89,6 +90,7 @@ descriptor recorder plId =
89
90
mconcat
90
91
[ mkPluginHandler LSP. SMethod_TextDocumentCodeAction licenseSuggestCodeAction
91
92
, mkPluginHandler LSP. SMethod_TextDocumentCompletion $ completion recorder
93
+ , mkPluginHandler LSP. SMethod_TextDocumentCodeAction $ fieldSuggestCodeAction recorder
92
94
]
93
95
, pluginNotificationHandlers =
94
96
mconcat
@@ -238,6 +240,41 @@ licenseSuggestCodeAction ideState _ (CodeActionParams _ _ (TextDocumentIdentifie
238
240
maxCompls <- fmap maxCompletions . liftIO $ runAction " cabal-plugin.suggestLicense" ideState getClientConfigAction
239
241
pure $ InL $ diags >>= (fmap InR . LicenseSuggest. licenseErrorAction maxCompls uri)
240
242
243
+ -- | CodeActions for correcting field names with typos in them.
244
+ --
245
+ -- Provides CodeActions that fix typos in both stanzas and top-level field names.
246
+ -- The suggestions are computed based on the completion context, where we "move" a fake cursor
247
+ -- to the end of the field name and trigger cabal file completions. The completions are then
248
+ -- suggested to the user.
249
+ --
250
+ -- TODO: Relying on completions here often does not produce the desired results, we should
251
+ -- use some sort of fuzzy matching in the future, see issue #4357.
252
+ fieldSuggestCodeAction :: Recorder (WithPriority Log ) -> PluginMethodHandler IdeState 'LSP.Method_TextDocumentCodeAction
253
+ fieldSuggestCodeAction recorder ide _ (CodeActionParams _ _ (TextDocumentIdentifier uri) _ CodeActionContext {_diagnostics= diags}) = do
254
+ vfileM <- lift (pluginGetVirtualFile $ toNormalizedUri uri)
255
+ case (,) <$> vfileM <*> uriToFilePath' uri of
256
+ Nothing -> pure $ InL []
257
+ Just (vfile, path) -> do
258
+ -- We decide on `useWithStale` here, since `useWithStaleFast` often leads to the wrong completions being suggested.
259
+ -- In case it fails, we still will get some completion results instead of an error.
260
+ mFields <- liftIO $ runAction " cabal-plugin.fields" ide $ useWithStale ParseCabalFields $ toNormalizedFilePath path
261
+ case mFields of
262
+ Nothing ->
263
+ pure $ InL []
264
+ Just (cabalFields, _) -> do
265
+ let fields = Maybe. mapMaybe FieldSuggest. fieldErrorName diags
266
+ results <- forM fields (getSuggestion vfile path cabalFields)
267
+ pure $ InL $ map InR $ concat results
268
+ where
269
+ getSuggestion vfile fp cabalFields (fieldName,Diagnostic { _range= _range@ (Range (Position lineNr col) _) }) = do
270
+ let -- Compute where we would anticipate the cursor to be.
271
+ fakeLspCursorPosition = Position lineNr (col + fromIntegral (T. length fieldName))
272
+ lspPrefixInfo = Ghcide. getCompletionPrefix fakeLspCursorPosition vfile
273
+ cabalPrefixInfo = Completions. getCabalPrefixInfo fp lspPrefixInfo
274
+ completions <- liftIO $ computeCompletionsAt recorder ide cabalPrefixInfo fp cabalFields
275
+ let completionTexts = fmap (^. JL. label) completions
276
+ pure $ FieldSuggest. fieldErrorAction uri fieldName completionTexts _range
277
+
241
278
-- ----------------------------------------------------------------
242
279
-- Cabal file of Interest rules and global variable
243
280
-- ----------------------------------------------------------------
@@ -319,7 +356,7 @@ deleteFileOfInterest recorder state f = do
319
356
320
357
completion :: Recorder (WithPriority Log ) -> PluginMethodHandler IdeState 'LSP.Method_TextDocumentCompletion
321
358
completion recorder ide _ complParams = do
322
- let ( TextDocumentIdentifier uri) = complParams ^. JL. textDocument
359
+ let TextDocumentIdentifier uri = complParams ^. JL. textDocument
323
360
position = complParams ^. JL. position
324
361
mVf <- lift $ pluginGetVirtualFile $ toNormalizedUri uri
325
362
case (,) <$> mVf <*> uriToFilePath' uri of
@@ -331,39 +368,36 @@ completion recorder ide _ complParams = do
331
368
Nothing ->
332
369
pure . InR $ InR Null
333
370
Just (fields, _) -> do
334
- let pref = Ghcide. getCompletionPrefix position cnts
335
- let res = produceCompletions pref path fields
371
+ let lspPrefInfo = Ghcide. getCompletionPrefix position cnts
372
+ cabalPrefInfo = Completions. getCabalPrefixInfo path lspPrefInfo
373
+ let res = computeCompletionsAt recorder ide cabalPrefInfo path fields
336
374
liftIO $ fmap InL res
337
375
Nothing -> pure . InR $ InR Null
338
- where
339
- completerRecorder = cmapWithPrio LogCompletions recorder
340
-
341
- produceCompletions :: Ghcide. PosPrefixInfo -> FilePath -> [Syntax. Field Syntax. Position ] -> IO [CompletionItem ]
342
- produceCompletions prefix fp fields = do
343
- runMaybeT (context fields) >>= \ case
344
- Nothing -> pure []
345
- Just ctx -> do
346
- logWith recorder Debug $ LogCompletionContext ctx pos
347
- let completer = Completions. contextToCompleter ctx
348
- let completerData = CompleterTypes. CompleterData
349
- { getLatestGPD = do
350
- -- We decide on useWithStaleFast here, since we mostly care about the file's meta information,
351
- -- thus, a quick response gives us the desired result most of the time.
352
- -- The `withStale` option is very important here, since we often call this rule with invalid cabal files.
353
- mGPD <- runIdeAction " cabal-plugin.modulesCompleter.gpd" (shakeExtras ide) $ useWithStaleFast ParseCabalFile $ toNormalizedFilePath fp
354
- pure $ fmap fst mGPD
355
- , getCabalCommonSections = do
356
- mSections <- runIdeAction " cabal-plugin.modulesCompleter.commonsections" (shakeExtras ide) $ useWithStaleFast ParseCabalCommonSections $ toNormalizedFilePath fp
357
- pure $ fmap fst mSections
358
- , cabalPrefixInfo = prefInfo
359
- , stanzaName =
360
- case fst ctx of
361
- Types. Stanza _ name -> name
362
- _ -> Nothing
363
- }
364
- completions <- completer completerRecorder completerData
365
- pure completions
366
- where
367
- pos = Ghcide. cursorPos prefix
376
+
377
+ computeCompletionsAt :: Recorder (WithPriority Log ) -> IdeState -> Types. CabalPrefixInfo -> FilePath -> [Syntax. Field Syntax. Position ] -> IO [CompletionItem ]
378
+ computeCompletionsAt recorder ide prefInfo fp fields = do
379
+ runMaybeT (context fields) >>= \ case
380
+ Nothing -> pure []
381
+ Just ctx -> do
382
+ logWith recorder Debug $ LogCompletionContext ctx pos
383
+ let completer = Completions. contextToCompleter ctx
384
+ let completerData = CompleterTypes. CompleterData
385
+ { getLatestGPD = do
386
+ -- We decide on useWithStaleFast here, since we mostly care about the file's meta information,
387
+ -- thus, a quick response gives us the desired result most of the time.
388
+ -- The `withStale` option is very important here, since we often call this rule with invalid cabal files.
389
+ mGPD <- runAction " cabal-plugin.modulesCompleter.gpd" ide $ useWithStale ParseCabalFile $ toNormalizedFilePath fp
390
+ pure $ fmap fst mGPD
391
+ , getCabalCommonSections = runAction " cabal-plugin.commonSections" ide $ use ParseCabalCommonSections $ toNormalizedFilePath fp
392
+ , cabalPrefixInfo = prefInfo
393
+ , stanzaName =
394
+ case fst ctx of
395
+ Types. Stanza _ name -> name
396
+ _ -> Nothing
397
+ }
398
+ completions <- completer completerRecorder completerData
399
+ pure completions
400
+ where
401
+ pos = Types. completionCursorPosition prefInfo
368
402
context fields = Completions. getContext completerRecorder prefInfo fields
369
- prefInfo = Completions. getCabalPrefixInfo fp prefix
403
+ completerRecorder = cmapWithPrio LogCompletions recorder
0 commit comments