@@ -18,22 +18,24 @@ 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
24
+ import Data.Text.Utf16.Rope.Mixed (Rope )
22
25
import Data.Typeable
23
26
import Development.IDE as D
24
27
import Development.IDE.Core.Shake (restartShakeSession )
25
28
import qualified Development.IDE.Core.Shake as Shake
26
29
import Development.IDE.Graph (Key , alwaysRerun )
27
30
import qualified Development.IDE.Plugin.Completions.Logic as Ghcide
28
- import qualified Development.IDE.Plugin.Completions.Types as Ghcide
29
31
import Development.IDE.Types.Shake (toKey )
30
32
import GHC.Generics
31
33
import qualified Ide.Plugin.Cabal.Completion.Completer.Types as CompleterTypes
32
34
import qualified Ide.Plugin.Cabal.Completion.Completions as Completions
33
35
import qualified Ide.Plugin.Cabal.Completion.Types as Types
34
36
import qualified Ide.Plugin.Cabal.Diagnostics as Diagnostics
35
- import qualified Ide.Plugin.Cabal.LicenseSuggest as LicenseSuggest
36
37
import qualified Ide.Plugin.Cabal.FieldSuggest as FieldSuggest
38
+ import qualified Ide.Plugin.Cabal.LicenseSuggest as LicenseSuggest
37
39
import qualified Ide.Plugin.Cabal.Parse as Parse
38
40
import Ide.Types
39
41
import qualified Language.LSP.Protocol.Lens as JL
@@ -84,7 +86,7 @@ descriptor recorder plId =
84
86
mconcat
85
87
[ mkPluginHandler LSP. SMethod_TextDocumentCodeAction licenseSuggestCodeAction
86
88
, mkPluginHandler LSP. SMethod_TextDocumentCompletion $ completion recorder
87
- , mkPluginHandler LSP. SMethod_TextDocumentCodeAction fieldSuggestCodeAction
89
+ , mkPluginHandler LSP. SMethod_TextDocumentCodeAction $ fieldSuggestCodeAction recorder
88
90
]
89
91
, pluginNotificationHandlers =
90
92
mconcat
@@ -200,9 +202,30 @@ licenseSuggestCodeAction :: PluginMethodHandler IdeState 'LSP.Method_TextDocumen
200
202
licenseSuggestCodeAction _ _ (CodeActionParams _ _ (TextDocumentIdentifier uri) _range CodeActionContext {_diagnostics= diags}) =
201
203
pure $ InL $ diags >>= (fmap InR . LicenseSuggest. licenseErrorAction uri)
202
204
203
- fieldSuggestCodeAction :: PluginMethodHandler IdeState 'LSP.Method_TextDocumentCodeAction
204
- fieldSuggestCodeAction _ _ (CodeActionParams _ _ (TextDocumentIdentifier uri) _range CodeActionContext {_diagnostics= diags}) =
205
- pure $ InL $ diags >>= (fmap InR . FieldSuggest. fieldErrorAction uri)
205
+ -- | CodeActions for correcting field names with typos in them.
206
+ --
207
+ -- Provides CodeActions that fix typos in field names, in both stanzas and top-level field names.
208
+ -- The suggestions are computed based on the completion context, where we "move" a fake cursor
209
+ -- to the end of the field name and trigger cabal file completions. The completions are then
210
+ -- suggested to the user.
211
+ fieldSuggestCodeAction :: Recorder (WithPriority Log ) -> PluginMethodHandler IdeState 'LSP.Method_TextDocumentCodeAction
212
+ fieldSuggestCodeAction recorder ide _ (CodeActionParams _ _ (TextDocumentIdentifier uri) _ CodeActionContext {_diagnostics= diags}) = do
213
+ vfileM <- lift (getVirtualFile $ toNormalizedUri uri)
214
+ case (,) <$> vfileM <*> uriToFilePath' uri of
215
+ Nothing -> pure $ InL []
216
+ Just (vfile, path) -> do
217
+ let fields = mapMaybe FieldSuggest. fieldErrorName diags
218
+ results <- forM fields (getSuggestion vfile path)
219
+ pure $ InL $ map InR $ concat results
220
+ where
221
+ getSuggestion vfile fp (field,Diagnostic { _range= _range@ (Range (Position lineNr col) _) })= do
222
+ let -- Compute where we would anticipate the cursor to be.
223
+ fakeLspCursorPosition = Position lineNr (col + fromIntegral (T. length field))
224
+ lspPrefixInfo = Ghcide. getCompletionPrefix fakeLspCursorPosition vfile
225
+ cabalPrefixInfo = Completions. getCabalPrefixInfo fp lspPrefixInfo
226
+ completions <- liftIO $ computeCompletionsAt recorder cabalPrefixInfo fp (vfile ^. VFS. file_text) (shakeExtras ide)
227
+ let completionTexts = (fmap (^. JL. label) completions)
228
+ pure $ FieldSuggest. fieldErrorAction uri field completionTexts _range
206
229
207
230
-- ----------------------------------------------------------------
208
231
-- Cabal file of Interest rules and global variable
@@ -290,32 +313,32 @@ completion recorder ide _ complParams = do
290
313
contents <- lift $ getVirtualFile $ toNormalizedUri uri
291
314
case (contents, uriToFilePath' uri) of
292
315
(Just cnts, Just path) -> do
293
- let pref = Ghcide. getCompletionPrefix position cnts
294
- let res = result pref path cnts
295
- liftIO $ fmap InL res
316
+ let lspPrefixInfo = Ghcide. getCompletionPrefix position cnts
317
+ cabalPrefixInfo = Completions. getCabalPrefixInfo path lspPrefixInfo
318
+ let compls = computeCompletionsAt recorder cabalPrefixInfo path (cnts ^. VFS. file_text) (shakeExtras ide)
319
+ liftIO $ fmap InL compls
296
320
_ -> pure . InR $ InR Null
297
- where
298
- result :: Ghcide. PosPrefixInfo -> FilePath -> VFS. VirtualFile -> IO [CompletionItem ]
299
- result prefix fp cnts = do
300
- runMaybeT context >>= \ case
301
- Nothing -> pure []
302
- Just ctx -> do
303
- logWith recorder Debug $ LogCompletionContext ctx pos
304
- let completer = Completions. contextToCompleter ctx
305
- let completerData = CompleterTypes. CompleterData
306
- { getLatestGPD = do
307
- mGPD <- runIdeAction " cabal-plugin.modulesCompleter.gpd" (shakeExtras ide) $ useWithStaleFast Types. GetCabalDiagnostics $ toNormalizedFilePath fp
308
- pure $ fmap fst mGPD
309
- , cabalPrefixInfo = prefInfo
310
- , stanzaName =
311
- case fst ctx of
312
- Types. Stanza _ name -> name
313
- _ -> Nothing
314
- }
315
- completions <- completer completerRecorder completerData
316
- pure completions
317
- where
318
- completerRecorder = cmapWithPrio LogCompletions recorder
319
- pos = Ghcide. cursorPos prefix
320
- context = Completions. getContext completerRecorder prefInfo (cnts ^. VFS. file_text)
321
- prefInfo = Completions. getCabalPrefixInfo fp prefix
321
+
322
+ computeCompletionsAt :: Recorder (WithPriority Log ) -> Types. CabalPrefixInfo -> FilePath -> Rope -> ShakeExtras -> IO [CompletionItem ]
323
+ computeCompletionsAt recorder cabalPrefixInfo fp fileRope extras = do
324
+ runMaybeT context >>= \ case
325
+ Nothing -> pure []
326
+ Just ctx -> do
327
+ logWith recorder Debug $ LogCompletionContext ctx pos
328
+ let completer = Completions. contextToCompleter ctx
329
+ let completerData = CompleterTypes. CompleterData
330
+ { getLatestGPD = do
331
+ mGPD <- runIdeAction " computeCompletionsAt.gpd" extras $ useWithStaleFast Types. GetCabalDiagnostics $ toNormalizedFilePath fp
332
+ pure $ fmap fst mGPD
333
+ , cabalPrefixInfo = cabalPrefixInfo
334
+ , stanzaName =
335
+ case fst ctx of
336
+ Types. Stanza _ name -> name
337
+ _ -> Nothing
338
+ }
339
+ completions <- completer completerRecorder completerData
340
+ pure completions
341
+ where
342
+ completerRecorder = cmapWithPrio LogCompletions recorder
343
+ pos = Types. completionCursorPosition cabalPrefixInfo
344
+ context = Completions. getContext completerRecorder cabalPrefixInfo fileRope
0 commit comments