@@ -40,7 +40,7 @@ import Control.Arrow ((&&&))
4040import Control.Concurrent.STM
4141import Control.DeepSeq
4242import Control.Exception
43- import Control.Lens ((^.) )
43+ import Control.Lens ((?~) , ( ^.) )
4444import Control.Monad
4545import Control.Monad.IO.Class
4646import Control.Monad.Trans.Except
@@ -127,10 +127,7 @@ import Language.LSP.Protocol.Message
127127import Language.LSP.Protocol.Types hiding
128128 (Null )
129129import qualified Language.LSP.Protocol.Types as LSP
130- import Language.LSP.Server (ProgressCancellable (Cancellable ),
131- getVersionedTextDoc ,
132- sendRequest ,
133- withIndefiniteProgress )
130+ import Language.LSP.Server (getVersionedTextDoc )
134131
135132import qualified Development.IDE.Core.Shake as Shake
136133import Development.IDE.Spans.Pragmas (LineSplitTextEdits (LineSplitTextEdits ),
@@ -146,6 +143,8 @@ import GHC.Generics (Generic)
146143import System.Environment (setEnv ,
147144 unsetEnv )
148145#endif
146+ import Data.Aeson (Result (Error , Success ),
147+ fromJSON )
149148import Text.Regex.TDFA.Text ()
150149-- ---------------------------------------------------------------------
151150
@@ -188,13 +187,12 @@ fromStrictMaybe Strict.Nothing = Nothing
188187#endif
189188
190189descriptor :: Recorder (WithPriority Log ) -> PluginId -> PluginDescriptor IdeState
191- descriptor recorder plId = (defaultPluginDescriptor plId)
190+ descriptor recorder plId =
191+ let (pluginCommands, pluginHandlers) = mkCodeActionWithResolveAndCommand plId codeActionProvider (resolveProvider recorder)
192+ in (defaultPluginDescriptor plId)
192193 { pluginRules = rules recorder plId
193- , pluginCommands =
194- [ PluginCommand " applyOne" " Apply a single hint" (applyOneCmd recorder)
195- , PluginCommand " applyAll" " Apply all hints to the file" (applyAllCmd recorder)
196- ]
197- , pluginHandlers = mkPluginHandler SMethod_TextDocumentCodeAction codeActionProvider
194+ , pluginCommands = pluginCommands
195+ , pluginHandlers = pluginHandlers
198196 , pluginConfigDescriptor = defaultConfigDescriptor
199197 { configHasDiagnostics = True
200198 , configCustomConfig = mkCustomConfig properties
@@ -396,21 +394,9 @@ getHlintConfig pId =
396394 Config
397395 <$> usePropertyAction # flags pId properties
398396
399- runHlintAction
400- :: (Eq k , Hashable k , Show k , Show (RuleResult k ), Typeable k , Typeable (RuleResult k ), NFData k , NFData (RuleResult k ))
401- => IdeState
402- -> NormalizedFilePath -> String -> k -> IO (Maybe (RuleResult k ))
403- runHlintAction ideState normalizedFilePath desc rule = runAction desc ideState $ use rule normalizedFilePath
404-
405- runGetFileContentsAction :: IdeState -> NormalizedFilePath -> IO (Maybe (FileVersion , Maybe T. Text ))
406- runGetFileContentsAction ideState normalizedFilePath = runHlintAction ideState normalizedFilePath " Hlint.GetFileContents" GetFileContents
407-
408- runGetModSummaryAction :: IdeState -> NormalizedFilePath -> IO (Maybe ModSummaryResult )
409- runGetModSummaryAction ideState normalizedFilePath = runHlintAction ideState normalizedFilePath " Hlint.GetModSummary" GetModSummary
410-
411397-- ---------------------------------------------------------------------
412398codeActionProvider :: PluginMethodHandler IdeState Method_TextDocumentCodeAction
413- codeActionProvider ideState pluginId (CodeActionParams _ _ documentId _ context)
399+ codeActionProvider ideState _pluginId (CodeActionParams _ _ documentId _ context)
414400 | let TextDocumentIdentifier uri = documentId
415401 , Just docNormalizedFilePath <- uriToNormalizedFilePath (toNormalizedUri uri)
416402 = do
@@ -427,16 +413,7 @@ codeActionProvider ideState pluginId (CodeActionParams _ _ documentId _ context)
427413 [diagnostic | diagnostic <- diags
428414 , validCommand diagnostic
429415 ]
430- file <- runGetFileContentsAction ideState docNormalizedFilePath
431- singleHintCodeActions <-
432- if | Just (_, source) <- file -> do
433- modSummaryResult <- runGetModSummaryAction ideState docNormalizedFilePath
434- pure if | Just modSummaryResult <- modSummaryResult
435- , Just source <- source
436- , let dynFlags = ms_hspp_opts $ msrModSummary modSummaryResult ->
437- diags >>= diagnosticToCodeActions dynFlags source pluginId verTxtDocId
438- | otherwise -> []
439- | otherwise -> pure []
416+ let singleHintCodeActions = diags >>= diagnosticToCodeActions verTxtDocId
440417 if numHintsInDoc > 1 && numHintsInContext > 0 then do
441418 pure $ singleHintCodeActions ++ [applyAllAction verTxtDocId]
442419 else
@@ -446,9 +423,8 @@ codeActionProvider ideState pluginId (CodeActionParams _ _ documentId _ context)
446423
447424 where
448425 applyAllAction verTxtDocId =
449- let args = Just [toJSON verTxtDocId]
450- cmd = mkLspCommand pluginId " applyAll" " Apply all hints" args
451- in LSP. CodeAction " Apply all hints" (Just LSP. CodeActionKind_QuickFix ) Nothing Nothing Nothing Nothing (Just cmd) Nothing
426+ let args = Just $ toJSON (AA verTxtDocId)
427+ in LSP. CodeAction " Apply all hints" (Just LSP. CodeActionKind_QuickFix ) Nothing Nothing Nothing Nothing Nothing args
452428
453429 -- | Some hints do not have an associated refactoring
454430 validCommand (LSP. Diagnostic _ _ (Just (InR code)) _ (Just " hlint" ) _ _ _ _) =
@@ -458,44 +434,57 @@ codeActionProvider ideState pluginId (CodeActionParams _ _ documentId _ context)
458434
459435 diags = context ^. LSP. diagnostics
460436
437+ resolveProvider :: Recorder (WithPriority Log ) -> PluginMethodHandler IdeState Method_CodeActionResolve
438+ resolveProvider recorder ideState _pluginId ca@ CodeAction {_data_ = Just data_} = pluginResponse $ do
439+ case fromJSON data_ of
440+ (Success (AA verTxtDocId@ (VersionedTextDocumentIdentifier uri _))) -> do
441+ file <- getNormalizedFilePath uri
442+ edit <- ExceptT $ liftIO $ applyHint recorder ideState file Nothing verTxtDocId
443+ pure $ ca & LSP. edit ?~ edit
444+ (Success (AO verTxtDocId@ (VersionedTextDocumentIdentifier uri _) pos hintTitle)) -> do
445+ let oneHint = OneHint pos hintTitle
446+ file <- getNormalizedFilePath uri
447+ edit <- ExceptT $ liftIO $ applyHint recorder ideState file (Just oneHint) verTxtDocId
448+ pure $ ca & LSP. edit ?~ edit
449+ (Success (IH verTxtDocId@ (VersionedTextDocumentIdentifier uri _) hintTitle )) -> do
450+ file <- getNormalizedFilePath uri
451+ edit <- ExceptT $ liftIO $ ignoreHint recorder ideState file verTxtDocId hintTitle
452+ pure $ ca & LSP. edit ?~ edit
453+ Error s-> throwE (" JSON decoding error: " <> s)
454+ resolveProvider _ _ _ _ = pluginResponse $ throwE " CodeAction with no data field"
455+
461456-- | Convert a hlint diagnostic into an apply and an ignore code action
462457-- if applicable
463- diagnosticToCodeActions :: DynFlags -> T. Text -> PluginId -> VersionedTextDocumentIdentifier -> LSP. Diagnostic -> [LSP. CodeAction ]
464- diagnosticToCodeActions dynFlags fileContents pluginId verTxtDocId diagnostic
458+ diagnosticToCodeActions :: VersionedTextDocumentIdentifier -> LSP. Diagnostic -> [LSP. CodeAction ]
459+ diagnosticToCodeActions verTxtDocId diagnostic
465460 | LSP. Diagnostic { _source = Just " hlint" , _code = Just (InR code), _range = LSP. Range start _ } <- diagnostic
466461 , let isHintApplicable = " refact:" `T.isPrefixOf` code
467462 , let hint = T. replace " refact:" " " code
468463 , let suppressHintTitle = " Ignore hint \" " <> hint <> " \" in this module"
469- , let suppressHintTextEdits = mkSuppressHintTextEdits dynFlags fileContents hint
470- , let suppressHintWorkspaceEdit =
471- LSP. WorkspaceEdit
472- (Just (M. singleton (verTxtDocId ^. LSP. uri) suppressHintTextEdits))
473- Nothing
474- Nothing
464+ , let suppressHintArguments = IH verTxtDocId hint
475465 = catMaybes
476466 -- Applying the hint is marked preferred because it addresses the underlying error.
477467 -- Disabling the rule isn't, because less often used and configuration can be adapted.
478468 [ if | isHintApplicable
479469 , let applyHintTitle = " Apply hint \" " <> hint <> " \" "
480- applyHintArguments = [toJSON (AOP verTxtDocId start hint)]
481- applyHintCommand = mkLspCommand pluginId " applyOne" applyHintTitle (Just applyHintArguments) ->
482- Just (mkCodeAction applyHintTitle diagnostic Nothing (Just applyHintCommand) True )
470+ applyHintArguments = AO verTxtDocId start hint ->
471+ Just (mkCodeAction applyHintTitle diagnostic (Just (toJSON applyHintArguments)) True )
483472 | otherwise -> Nothing
484- , Just (mkCodeAction suppressHintTitle diagnostic (Just suppressHintWorkspaceEdit) Nothing False )
473+ , Just (mkCodeAction suppressHintTitle diagnostic (Just (toJSON suppressHintArguments)) False )
485474 ]
486475 | otherwise = []
487476
488- mkCodeAction :: T. Text -> LSP. Diagnostic -> Maybe LSP. WorkspaceEdit -> Maybe LSP. Command -> Bool -> LSP. CodeAction
489- mkCodeAction title diagnostic workspaceEdit command isPreferred =
477+ mkCodeAction :: T. Text -> LSP. Diagnostic -> Maybe Value -> Bool -> LSP. CodeAction
478+ mkCodeAction title diagnostic data_ isPreferred =
490479 LSP. CodeAction
491480 { _title = title
492481 , _kind = Just LSP. CodeActionKind_QuickFix
493482 , _diagnostics = Just [diagnostic]
494483 , _isPreferred = Just isPreferred
495484 , _disabled = Nothing
496- , _edit = workspaceEdit
497- , _command = command
498- , _data_ = Nothing
485+ , _edit = Nothing
486+ , _command = Nothing
487+ , _data_ = data_
499488 }
500489
501490mkSuppressHintTextEdits :: DynFlags -> T. Text -> T. Text -> [LSP. TextEdit ]
@@ -519,28 +508,32 @@ mkSuppressHintTextEdits dynFlags fileContents hint =
519508 combinedTextEdit : lineSplitTextEditList
520509-- ---------------------------------------------------------------------
521510
522- applyAllCmd :: Recorder (WithPriority Log ) -> CommandFunction IdeState VersionedTextDocumentIdentifier
523- applyAllCmd recorder ide verTxtDocId = do
524- let file = maybe (error $ show (verTxtDocId ^. LSP. uri) ++ " is not a file." )
525- toNormalizedFilePath'
526- (uriToFilePath' (verTxtDocId ^. LSP. uri))
527- withIndefiniteProgress " Applying all hints" Cancellable $ do
528- res <- liftIO $ applyHint recorder ide file Nothing verTxtDocId
529- logWith recorder Debug $ LogApplying file res
530- case res of
531- Left err -> pure $ Left (responseError (T. pack $ " hlint:applyAll: " ++ show err))
532- Right fs -> do
533- _ <- sendRequest SMethod_WorkspaceApplyEdit (ApplyWorkspaceEditParams Nothing fs) (\ _ -> pure () )
534- pure $ Right Null
511+ ignoreHint :: Recorder (WithPriority Log ) -> IdeState -> NormalizedFilePath -> VersionedTextDocumentIdentifier -> HintTitle -> IO (Either String WorkspaceEdit )
512+ ignoreHint _recorder ideState nfp verTxtDocId ignoreHintTitle = do
513+ (_, fileContents) <- runAction " Hlint.GetFileContents" ideState $ getFileContents nfp
514+ (msr, _) <- runAction " Hlint.GetModSummaryWithoutTimestamps" ideState $ useWithStale_ GetModSummaryWithoutTimestamps nfp
515+ case fileContents of
516+ Just contents -> do
517+ let dynFlags = ms_hspp_opts $ msrModSummary msr
518+ textEdits = mkSuppressHintTextEdits dynFlags contents ignoreHintTitle
519+ workspaceEdit =
520+ LSP. WorkspaceEdit
521+ (Just (M. singleton (verTxtDocId ^. LSP. uri) textEdits))
522+ Nothing
523+ Nothing
524+ pure $ Right workspaceEdit
525+ Nothing -> pure $ Left " Unable to get fileContents"
535526
536527-- ---------------------------------------------------------------------
537-
538- data ApplyOneParams = AOP
539- { verTxtDocId :: VersionedTextDocumentIdentifier
540- , start_pos :: Position
541- -- | There can be more than one hint suggested at the same position, so HintTitle is used to distinguish between them.
542- , hintTitle :: HintTitle
543- } deriving (Eq ,Show ,Generic ,FromJSON ,ToJSON )
528+ data HlintResolveCommands = AA { verTxtDocId :: VersionedTextDocumentIdentifier }
529+ | AO { verTxtDocId :: VersionedTextDocumentIdentifier
530+ , start_pos :: Position
531+ -- | There can be more than one hint suggested at the same position, so HintTitle is used to distinguish between them.
532+ , hintTitle :: HintTitle
533+ }
534+ | IH { verTxtDocId :: VersionedTextDocumentIdentifier
535+ , ignoreHintTitle :: HintTitle
536+ } deriving (Generic , ToJSON , FromJSON )
544537
545538type HintTitle = T. Text
546539
@@ -549,21 +542,6 @@ data OneHint = OneHint
549542 , oneHintTitle :: HintTitle
550543 } deriving (Eq , Show )
551544
552- applyOneCmd :: Recorder (WithPriority Log ) -> CommandFunction IdeState ApplyOneParams
553- applyOneCmd recorder ide (AOP verTxtDocId pos title) = do
554- let oneHint = OneHint pos title
555- let file = maybe (error $ show (verTxtDocId ^. LSP. uri) ++ " is not a file." ) toNormalizedFilePath'
556- (uriToFilePath' (verTxtDocId ^. LSP. uri))
557- let progTitle = " Applying hint: " <> title
558- withIndefiniteProgress progTitle Cancellable $ do
559- res <- liftIO $ applyHint recorder ide file (Just oneHint) verTxtDocId
560- logWith recorder Debug $ LogApplying file res
561- case res of
562- Left err -> pure $ Left (responseError (T. pack $ " hlint:applyOne: " ++ show err))
563- Right fs -> do
564- _ <- sendRequest SMethod_WorkspaceApplyEdit (ApplyWorkspaceEditParams Nothing fs) (\ _ -> pure () )
565- pure $ Right Null
566-
567545applyHint :: Recorder (WithPriority Log ) -> IdeState -> NormalizedFilePath -> Maybe OneHint -> VersionedTextDocumentIdentifier -> IO (Either String WorkspaceEdit )
568546applyHint recorder ide nfp mhint verTxtDocId =
569547 runExceptT $ do
0 commit comments