@@ -18,14 +18,15 @@ 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
24
26
import Development.IDE.Core.Shake (restartShakeSession )
25
27
import qualified Development.IDE.Core.Shake as Shake
26
28
import Development.IDE.Graph (Key , alwaysRerun )
27
29
import qualified Development.IDE.Plugin.Completions.Logic as Ghcide
28
- import qualified Development.IDE.Plugin.Completions.Types as Ghcide
29
30
import Development.IDE.Types.Shake (toKey )
30
31
import qualified Distribution.Fields as Syntax
31
32
import qualified Distribution.Parsec.Position as Syntax
@@ -88,7 +89,7 @@ descriptor recorder plId =
88
89
mconcat
89
90
[ mkPluginHandler LSP. SMethod_TextDocumentCodeAction licenseSuggestCodeAction
90
91
, mkPluginHandler LSP. SMethod_TextDocumentCompletion $ completion recorder
91
- , mkPluginHandler LSP. SMethod_TextDocumentCodeAction fieldSuggestCodeAction
92
+ , mkPluginHandler LSP. SMethod_TextDocumentCodeAction $ fieldSuggestCodeAction recorder
92
93
]
93
94
, pluginNotificationHandlers =
94
95
mconcat
@@ -229,9 +230,37 @@ licenseSuggestCodeAction :: PluginMethodHandler IdeState 'LSP.Method_TextDocumen
229
230
licenseSuggestCodeAction _ _ (CodeActionParams _ _ (TextDocumentIdentifier uri) _range CodeActionContext {_diagnostics= diags}) =
230
231
pure $ InL $ diags >>= (fmap InR . LicenseSuggest. licenseErrorAction uri)
231
232
232
- fieldSuggestCodeAction :: PluginMethodHandler IdeState 'LSP.Method_TextDocumentCodeAction
233
- fieldSuggestCodeAction _ _ (CodeActionParams _ _ (TextDocumentIdentifier uri) _range CodeActionContext {_diagnostics= diags}) =
234
- pure $ InL $ diags >>= (fmap InR . FieldSuggest. fieldErrorAction uri)
233
+ -- | CodeActions for correcting field names with typos in them.
234
+ --
235
+ -- Provides CodeActions that fix typos in field names, in both stanzas and top-level field names.
236
+ -- The suggestions are computed based on the completion context, where we "move" a fake cursor
237
+ -- to the end of the field name and trigger cabal file completions. The completions are then
238
+ -- suggested to the user.
239
+ fieldSuggestCodeAction :: Recorder (WithPriority Log ) -> PluginMethodHandler IdeState 'LSP.Method_TextDocumentCodeAction
240
+ fieldSuggestCodeAction recorder ide _ (CodeActionParams _ _ (TextDocumentIdentifier uri) _ CodeActionContext {_diagnostics= diags}) = do
241
+ vfileM <- lift (pluginGetVirtualFile $ toNormalizedUri uri)
242
+ case (,) <$> vfileM <*> uriToFilePath' uri of
243
+ Nothing -> pure $ InL []
244
+ Just (vfile, path) -> do
245
+ -- We decide on `useWithStale` here, since `useWithStaleFast` often leads to the wrong completions being suggested.
246
+ -- In case it fails, we still will get some completion results instead of an error.
247
+ mFields <- liftIO $ runAction " cabal-plugin.fields" ide $ useWithStale ParseCabalFields $ toNormalizedFilePath path
248
+ case mFields of
249
+ Nothing ->
250
+ pure $ InL []
251
+ Just (cabalFields, _) -> do
252
+ let fields = mapMaybe FieldSuggest. fieldErrorName diags
253
+ results <- forM fields (getSuggestion vfile path cabalFields)
254
+ pure $ InL $ map InR $ concat results
255
+ where
256
+ getSuggestion vfile fp cabalFields (fieldName,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 fieldName))
259
+ lspPrefixInfo = Ghcide. getCompletionPrefix fakeLspCursorPosition vfile
260
+ cabalPrefixInfo = Completions. getCabalPrefixInfo fp lspPrefixInfo
261
+ completions <- liftIO $ computeCompletionsAt recorder ide cabalPrefixInfo fp cabalFields
262
+ let completionTexts = fmap (^. JL. label) completions
263
+ pure $ FieldSuggest. fieldErrorAction uri fieldName completionTexts _range
235
264
236
265
-- ----------------------------------------------------------------
237
266
-- Cabal file of Interest rules and global variable
@@ -314,7 +343,7 @@ deleteFileOfInterest recorder state f = do
314
343
315
344
completion :: Recorder (WithPriority Log ) -> PluginMethodHandler IdeState 'LSP.Method_TextDocumentCompletion
316
345
completion recorder ide _ complParams = do
317
- let ( TextDocumentIdentifier uri) = complParams ^. JL. textDocument
346
+ let TextDocumentIdentifier uri = complParams ^. JL. textDocument
318
347
position = complParams ^. JL. position
319
348
mVf <- lift $ pluginGetVirtualFile $ toNormalizedUri uri
320
349
case (,) <$> mVf <*> uriToFilePath' uri of
@@ -326,36 +355,35 @@ completion recorder ide _ complParams = do
326
355
Nothing ->
327
356
pure . InR $ InR Null
328
357
Just (fields, _) -> do
329
- let pref = Ghcide. getCompletionPrefix position cnts
330
- let res = produceCompletions pref path fields
358
+ let lspPrefInfo = Ghcide. getCompletionPrefix position cnts
359
+ cabalPrefInfo = Completions. getCabalPrefixInfo path lspPrefInfo
360
+ let res = computeCompletionsAt recorder ide cabalPrefInfo path fields
331
361
liftIO $ fmap InL res
332
362
Nothing -> pure . InR $ InR Null
333
- where
334
- completerRecorder = cmapWithPrio LogCompletions recorder
335
-
336
- produceCompletions :: Ghcide. PosPrefixInfo -> FilePath -> [Syntax. Field Syntax. Position ] -> IO [CompletionItem ]
337
- produceCompletions prefix fp fields = do
338
- runMaybeT (context fields) >>= \ case
339
- Nothing -> pure []
340
- Just ctx -> do
341
- logWith recorder Debug $ LogCompletionContext ctx pos
342
- let completer = Completions. contextToCompleter ctx
343
- let completerData = CompleterTypes. CompleterData
344
- { getLatestGPD = do
345
- -- We decide on useWithStaleFast here, since we mostly care about the file's meta information,
346
- -- thus, a quick response gives us the desired result most of the time.
347
- -- The `withStale` option is very important here, since we often call this rule with invalid cabal files.
348
- mGPD <- runIdeAction " cabal-plugin.modulesCompleter.gpd" (shakeExtras ide) $ useWithStaleFast ParseCabalFile $ toNormalizedFilePath fp
349
- pure $ fmap fst mGPD
350
- , cabalPrefixInfo = prefInfo
351
- , stanzaName =
352
- case fst ctx of
353
- Types. Stanza _ name -> name
354
- _ -> Nothing
355
- }
356
- completions <- completer completerRecorder completerData
357
- pure completions
358
- where
359
- pos = Ghcide. cursorPos prefix
363
+
364
+ computeCompletionsAt :: Recorder (WithPriority Log ) -> IdeState -> Types. CabalPrefixInfo -> FilePath -> [Syntax. Field Syntax. Position ] -> IO [CompletionItem ]
365
+ computeCompletionsAt recorder ide prefInfo fp fields = do
366
+ runMaybeT (context fields) >>= \ case
367
+ Nothing -> pure []
368
+ Just ctx -> do
369
+ logWith recorder Debug $ LogCompletionContext ctx pos
370
+ let completer = Completions. contextToCompleter ctx
371
+ let completerData = CompleterTypes. CompleterData
372
+ { getLatestGPD = do
373
+ -- We decide on useWithStaleFast here, since we mostly care about the file's meta information,
374
+ -- thus, a quick response gives us the desired result most of the time.
375
+ -- The `withStale` option is very important here, since we often call this rule with invalid cabal files.
376
+ mGPD <- runAction " cabal-plugin.modulesCompleter.gpd" ide $ useWithStale ParseCabalFile $ toNormalizedFilePath fp
377
+ pure $ fmap fst mGPD
378
+ , cabalPrefixInfo = prefInfo
379
+ , stanzaName =
380
+ case fst ctx of
381
+ Types. Stanza _ name -> name
382
+ _ -> Nothing
383
+ }
384
+ completions <- completer completerRecorder completerData
385
+ pure completions
386
+ where
387
+ pos = Types. completionCursorPosition prefInfo
360
388
context fields = Completions. getContext completerRecorder prefInfo fields
361
- prefInfo = Completions. getCabalPrefixInfo fp prefix
389
+ completerRecorder = cmapWithPrio LogCompletions recorder
0 commit comments