@@ -18,22 +18,24 @@ import Data.Hashable
1818import Data.HashMap.Strict (HashMap )
1919import qualified Data.HashMap.Strict as HashMap
2020import qualified Data.List.NonEmpty as NE
21+ import Data.Maybe (mapMaybe )
22+ import qualified Data.Text as T
2123import qualified Data.Text.Encoding as Encoding
24+ import Data.Text.Utf16.Rope.Mixed (Rope )
2225import Data.Typeable
2326import Development.IDE as D
2427import Development.IDE.Core.Shake (restartShakeSession )
2528import qualified Development.IDE.Core.Shake as Shake
2629import Development.IDE.Graph (Key , alwaysRerun )
2730import qualified Development.IDE.Plugin.Completions.Logic as Ghcide
28- import qualified Development.IDE.Plugin.Completions.Types as Ghcide
2931import Development.IDE.Types.Shake (toKey )
3032import GHC.Generics
3133import qualified Ide.Plugin.Cabal.Completion.Completer.Types as CompleterTypes
3234import qualified Ide.Plugin.Cabal.Completion.Completions as Completions
3335import qualified Ide.Plugin.Cabal.Completion.Types as Types
3436import qualified Ide.Plugin.Cabal.Diagnostics as Diagnostics
35- import qualified Ide.Plugin.Cabal.LicenseSuggest as LicenseSuggest
3637import qualified Ide.Plugin.Cabal.FieldSuggest as FieldSuggest
38+ import qualified Ide.Plugin.Cabal.LicenseSuggest as LicenseSuggest
3739import qualified Ide.Plugin.Cabal.Parse as Parse
3840import Ide.Types
3941import qualified Language.LSP.Protocol.Lens as JL
@@ -84,7 +86,7 @@ descriptor recorder plId =
8486 mconcat
8587 [ mkPluginHandler LSP. SMethod_TextDocumentCodeAction licenseSuggestCodeAction
8688 , mkPluginHandler LSP. SMethod_TextDocumentCompletion $ completion recorder
87- , mkPluginHandler LSP. SMethod_TextDocumentCodeAction fieldSuggestCodeAction
89+ , mkPluginHandler LSP. SMethod_TextDocumentCodeAction $ fieldSuggestCodeAction recorder
8890 ]
8991 , pluginNotificationHandlers =
9092 mconcat
@@ -200,9 +202,30 @@ licenseSuggestCodeAction :: PluginMethodHandler IdeState 'LSP.Method_TextDocumen
200202licenseSuggestCodeAction _ _ (CodeActionParams _ _ (TextDocumentIdentifier uri) _range CodeActionContext {_diagnostics= diags}) =
201203 pure $ InL $ diags >>= (fmap InR . LicenseSuggest. licenseErrorAction uri)
202204
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
206229
207230-- ----------------------------------------------------------------
208231-- Cabal file of Interest rules and global variable
@@ -290,32 +313,32 @@ completion recorder ide _ complParams = do
290313 contents <- lift $ getVirtualFile $ toNormalizedUri uri
291314 case (contents, uriToFilePath' uri) of
292315 (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
296320 _ -> 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