@@ -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
@@ -90,7 +90,7 @@ descriptor recorder plId =
90
90
mconcat
91
91
[ mkPluginHandler LSP. SMethod_TextDocumentCodeAction licenseSuggestCodeAction
92
92
, mkPluginHandler LSP. SMethod_TextDocumentCompletion $ completion recorder
93
- , mkPluginHandler LSP. SMethod_TextDocumentCodeAction fieldSuggestCodeAction
93
+ , mkPluginHandler LSP. SMethod_TextDocumentCodeAction $ fieldSuggestCodeAction recorder
94
94
]
95
95
, pluginNotificationHandlers =
96
96
mconcat
@@ -240,9 +240,37 @@ licenseSuggestCodeAction ideState _ (CodeActionParams _ _ (TextDocumentIdentifie
240
240
maxCompls <- fmap maxCompletions . liftIO $ runAction " cabal-plugin.suggestLicense" ideState getClientConfigAction
241
241
pure $ InL $ diags >>= (fmap InR . LicenseSuggest. licenseErrorAction maxCompls uri)
242
242
243
- fieldSuggestCodeAction :: PluginMethodHandler IdeState 'LSP.Method_TextDocumentCodeAction
244
- fieldSuggestCodeAction _ _ (CodeActionParams _ _ (TextDocumentIdentifier uri) _range CodeActionContext {_diagnostics= diags}) =
245
- pure $ InL $ diags >>= (fmap InR . FieldSuggest. fieldErrorAction uri)
243
+ -- | CodeActions for correcting field names with typos in them.
244
+ --
245
+ -- Provides CodeActions that fix typos in field names, 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
+ fieldSuggestCodeAction :: Recorder (WithPriority Log ) -> PluginMethodHandler IdeState 'LSP.Method_TextDocumentCodeAction
250
+ fieldSuggestCodeAction recorder ide _ (CodeActionParams _ _ (TextDocumentIdentifier uri) _ CodeActionContext {_diagnostics= diags}) = do
251
+ vfileM <- lift (pluginGetVirtualFile $ toNormalizedUri uri)
252
+ case (,) <$> vfileM <*> uriToFilePath' uri of
253
+ Nothing -> pure $ InL []
254
+ Just (vfile, path) -> do
255
+ -- We decide on `useWithStale` here, since `useWithStaleFast` often leads to the wrong completions being suggested.
256
+ -- In case it fails, we still will get some completion results instead of an error.
257
+ mFields <- liftIO $ runAction " cabal-plugin.fields" ide $ useWithStale ParseCabalFields $ toNormalizedFilePath path
258
+ case mFields of
259
+ Nothing ->
260
+ pure $ InL []
261
+ Just (cabalFields, _) -> do
262
+ let fields = Maybe. mapMaybe FieldSuggest. fieldErrorName diags
263
+ results <- forM fields (getSuggestion vfile path cabalFields)
264
+ pure $ InL $ map InR $ concat results
265
+ where
266
+ getSuggestion vfile fp cabalFields (fieldName,Diagnostic { _range= _range@ (Range (Position lineNr col) _) }) = do
267
+ let -- Compute where we would anticipate the cursor to be.
268
+ fakeLspCursorPosition = Position lineNr (col + fromIntegral (T. length fieldName))
269
+ lspPrefixInfo = Ghcide. getCompletionPrefix fakeLspCursorPosition vfile
270
+ cabalPrefixInfo = Completions. getCabalPrefixInfo fp lspPrefixInfo
271
+ completions <- liftIO $ computeCompletionsAt recorder ide cabalPrefixInfo fp cabalFields
272
+ let completionTexts = fmap (^. JL. label) completions
273
+ pure $ FieldSuggest. fieldErrorAction uri fieldName completionTexts _range
246
274
247
275
-- ----------------------------------------------------------------
248
276
-- Cabal file of Interest rules and global variable
@@ -325,7 +353,7 @@ deleteFileOfInterest recorder state f = do
325
353
326
354
completion :: Recorder (WithPriority Log ) -> PluginMethodHandler IdeState 'LSP.Method_TextDocumentCompletion
327
355
completion recorder ide _ complParams = do
328
- let ( TextDocumentIdentifier uri) = complParams ^. JL. textDocument
356
+ let TextDocumentIdentifier uri = complParams ^. JL. textDocument
329
357
position = complParams ^. JL. position
330
358
mVf <- lift $ pluginGetVirtualFile $ toNormalizedUri uri
331
359
case (,) <$> mVf <*> uriToFilePath' uri of
@@ -337,39 +365,35 @@ completion recorder ide _ complParams = do
337
365
Nothing ->
338
366
pure . InR $ InR Null
339
367
Just (fields, _) -> do
340
- let pref = Ghcide. getCompletionPrefix position cnts
341
- let res = produceCompletions pref path fields
368
+ let lspPrefInfo = Ghcide. getCompletionPrefix position cnts
369
+ cabalPrefInfo = Completions. getCabalPrefixInfo path lspPrefInfo
370
+ let res = computeCompletionsAt recorder ide cabalPrefInfo path fields
342
371
liftIO $ fmap InL res
343
372
Nothing -> pure . InR $ InR Null
344
- where
345
- completerRecorder = cmapWithPrio LogCompletions recorder
346
-
347
- produceCompletions :: Ghcide. PosPrefixInfo -> FilePath -> [Syntax. Field Syntax. Position ] -> IO [CompletionItem ]
348
- produceCompletions prefix fp fields = do
349
- runMaybeT (context fields) >>= \ case
350
- Nothing -> pure []
351
- Just ctx -> do
352
- logWith recorder Debug $ LogCompletionContext ctx pos
353
- let completer = Completions. contextToCompleter ctx
354
- let completerData = CompleterTypes. CompleterData
355
- { getLatestGPD = do
356
- -- We decide on useWithStaleFast here, since we mostly care about the file's meta information,
357
- -- thus, a quick response gives us the desired result most of the time.
358
- -- The `withStale` option is very important here, since we often call this rule with invalid cabal files.
359
- mGPD <- runIdeAction " cabal-plugin.modulesCompleter.gpd" (shakeExtras ide) $ useWithStaleFast ParseCabalFile $ toNormalizedFilePath fp
360
- pure $ fmap fst mGPD
361
- , getCabalCommonSections = do
362
- mSections <- runIdeAction " cabal-plugin.modulesCompleter.commonsections" (shakeExtras ide) $ useWithStaleFast ParseCabalCommonSections $ toNormalizedFilePath fp
363
- pure $ fmap fst mSections
364
- , cabalPrefixInfo = prefInfo
365
- , stanzaName =
366
- case fst ctx of
367
- Types. Stanza _ name -> name
368
- _ -> Nothing
369
- }
370
- completions <- completer completerRecorder completerData
371
- pure completions
372
- where
373
- pos = Ghcide. cursorPos prefix
373
+
374
+ computeCompletionsAt :: Recorder (WithPriority Log ) -> IdeState -> Types. CabalPrefixInfo -> FilePath -> [Syntax. Field Syntax. Position ] -> IO [CompletionItem ]
375
+ computeCompletionsAt recorder ide prefInfo fp fields = do
376
+ runMaybeT (context fields) >>= \ case
377
+ Nothing -> pure []
378
+ Just ctx -> do
379
+ logWith recorder Debug $ LogCompletionContext ctx pos
380
+ let completer = Completions. contextToCompleter ctx
381
+ let completerData = CompleterTypes. CompleterData
382
+ { getLatestGPD = do
383
+ -- We decide on useWithStaleFast here, since we mostly care about the file's meta information,
384
+ -- thus, a quick response gives us the desired result most of the time.
385
+ -- The `withStale` option is very important here, since we often call this rule with invalid cabal files.
386
+ mGPD <- runAction " cabal-plugin.modulesCompleter.gpd" ide $ useWithStale ParseCabalFile $ toNormalizedFilePath fp
387
+ pure $ fmap fst mGPD
388
+ , cabalPrefixInfo = prefInfo
389
+ , stanzaName =
390
+ case fst ctx of
391
+ Types. Stanza _ name -> name
392
+ _ -> Nothing
393
+ }
394
+ completions <- completer completerRecorder completerData
395
+ pure completions
396
+ where
397
+ pos = Types. completionCursorPosition prefInfo
374
398
context fields = Completions. getContext completerRecorder prefInfo fields
375
- prefInfo = Completions. getCabalPrefixInfo fp prefix
399
+ completerRecorder = cmapWithPrio LogCompletions recorder
0 commit comments