@@ -40,7 +40,7 @@ import Control.Arrow ((&&&))
40
40
import Control.Concurrent.STM
41
41
import Control.DeepSeq
42
42
import Control.Exception
43
- import Control.Lens ((^.) )
43
+ import Control.Lens ((?~) , ( ^.) )
44
44
import Control.Monad
45
45
import Control.Monad.IO.Class
46
46
import Control.Monad.Trans.Except
@@ -127,10 +127,7 @@ import Language.LSP.Protocol.Message
127
127
import Language.LSP.Protocol.Types hiding
128
128
(Null )
129
129
import 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 )
134
131
135
132
import qualified Development.IDE.Core.Shake as Shake
136
133
import Development.IDE.Spans.Pragmas (LineSplitTextEdits (LineSplitTextEdits ),
@@ -146,6 +143,8 @@ import GHC.Generics (Generic)
146
143
import System.Environment (setEnv ,
147
144
unsetEnv )
148
145
#endif
146
+ import Data.Aeson (Result (Error , Success ),
147
+ fromJSON )
149
148
import Text.Regex.TDFA.Text ()
150
149
-- ---------------------------------------------------------------------
151
150
@@ -188,13 +187,12 @@ fromStrictMaybe Strict.Nothing = Nothing
188
187
#endif
189
188
190
189
descriptor :: 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)
192
193
{ 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
198
196
, pluginConfigDescriptor = defaultConfigDescriptor
199
197
{ configHasDiagnostics = True
200
198
, configCustomConfig = mkCustomConfig properties
@@ -396,21 +394,9 @@ getHlintConfig pId =
396
394
Config
397
395
<$> usePropertyAction # flags pId properties
398
396
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
-
411
397
-- ---------------------------------------------------------------------
412
398
codeActionProvider :: PluginMethodHandler IdeState Method_TextDocumentCodeAction
413
- codeActionProvider ideState pluginId (CodeActionParams _ _ documentId _ context)
399
+ codeActionProvider ideState _pluginId (CodeActionParams _ _ documentId _ context)
414
400
| let TextDocumentIdentifier uri = documentId
415
401
, Just docNormalizedFilePath <- uriToNormalizedFilePath (toNormalizedUri uri)
416
402
= do
@@ -427,16 +413,7 @@ codeActionProvider ideState pluginId (CodeActionParams _ _ documentId _ context)
427
413
[diagnostic | diagnostic <- diags
428
414
, validCommand diagnostic
429
415
]
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
440
417
if numHintsInDoc > 1 && numHintsInContext > 0 then do
441
418
pure $ singleHintCodeActions ++ [applyAllAction verTxtDocId]
442
419
else
@@ -446,9 +423,8 @@ codeActionProvider ideState pluginId (CodeActionParams _ _ documentId _ context)
446
423
447
424
where
448
425
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
452
428
453
429
-- | Some hints do not have an associated refactoring
454
430
validCommand (LSP. Diagnostic _ _ (Just (InR code)) _ (Just " hlint" ) _ _ _ _) =
@@ -458,44 +434,57 @@ codeActionProvider ideState pluginId (CodeActionParams _ _ documentId _ context)
458
434
459
435
diags = context ^. LSP. diagnostics
460
436
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
+
461
456
-- | Convert a hlint diagnostic into an apply and an ignore code action
462
457
-- 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
465
460
| LSP. Diagnostic { _source = Just " hlint" , _code = Just (InR code), _range = LSP. Range start _ } <- diagnostic
466
461
, let isHintApplicable = " refact:" `T.isPrefixOf` code
467
462
, let hint = T. replace " refact:" " " code
468
463
, 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
475
465
= catMaybes
476
466
-- Applying the hint is marked preferred because it addresses the underlying error.
477
467
-- Disabling the rule isn't, because less often used and configuration can be adapted.
478
468
[ if | isHintApplicable
479
469
, 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 )
483
472
| otherwise -> Nothing
484
- , Just (mkCodeAction suppressHintTitle diagnostic (Just suppressHintWorkspaceEdit) Nothing False )
473
+ , Just (mkCodeAction suppressHintTitle diagnostic (Just (toJSON suppressHintArguments)) False )
485
474
]
486
475
| otherwise = []
487
476
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 =
490
479
LSP. CodeAction
491
480
{ _title = title
492
481
, _kind = Just LSP. CodeActionKind_QuickFix
493
482
, _diagnostics = Just [diagnostic]
494
483
, _isPreferred = Just isPreferred
495
484
, _disabled = Nothing
496
- , _edit = workspaceEdit
497
- , _command = command
498
- , _data_ = Nothing
485
+ , _edit = Nothing
486
+ , _command = Nothing
487
+ , _data_ = data_
499
488
}
500
489
501
490
mkSuppressHintTextEdits :: DynFlags -> T. Text -> T. Text -> [LSP. TextEdit ]
@@ -519,28 +508,32 @@ mkSuppressHintTextEdits dynFlags fileContents hint =
519
508
combinedTextEdit : lineSplitTextEditList
520
509
-- ---------------------------------------------------------------------
521
510
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"
535
526
536
527
-- ---------------------------------------------------------------------
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 )
544
537
545
538
type HintTitle = T. Text
546
539
@@ -549,21 +542,6 @@ data OneHint = OneHint
549
542
, oneHintTitle :: HintTitle
550
543
} deriving (Eq , Show )
551
544
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
-
567
545
applyHint :: Recorder (WithPriority Log ) -> IdeState -> NormalizedFilePath -> Maybe OneHint -> VersionedTextDocumentIdentifier -> IO (Either String WorkspaceEdit )
568
546
applyHint recorder ide nfp mhint verTxtDocId =
569
547
runExceptT $ do
0 commit comments