{-# LANGUAGE DeriveAnyClass #-} {-# LANGUAGE DeriveGeneric #-} {-# LANGUAGE DuplicateRecordFields #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE FlexibleContexts #-} module Haskell.Ide.Engine.CodeActions where import Control.Lens import Control.Monad.Reader import qualified Data.Aeson as J import Data.Maybe import Data.Foldable import qualified GHC.Generics as G import Haskell.Ide.Engine.Reactor import Haskell.Ide.Engine.Types import qualified Language.Haskell.LSP.Core as Core import qualified Language.Haskell.LSP.Types as J import qualified Language.Haskell.LSP.Types.Lens as J import qualified Language.Haskell.LSP.Types.Capabilities as C import Language.Haskell.LSP.VFS import Language.Haskell.LSP.Messages import Haskell.Ide.Engine.PluginsIdeMonads data FallbackCodeActionParams = FallbackCodeActionParams { fallbackWorkspaceEdit :: Maybe J.WorkspaceEdit , fallbackCommand :: Maybe J.Command } deriving (G.Generic, J.ToJSON, J.FromJSON) handleCodeActionReq :: TrackingNumber -> J.CodeActionRequest -> R () handleCodeActionReq tn req = do vfsFunc <- asksLspFuncs Core.getVirtualFileFunc docVersion <- fmap virtualFileVersion <$> liftIO (vfsFunc (J.toNormalizedUri docUri)) let docId = J.VersionedTextDocumentIdentifier docUri docVersion let getProvider p = pluginCodeActionProvider p <*> return (pluginId p) getProviders = do IdePlugins m <- getPlugins return $ IdeResultOk $ mapMaybe getProvider $ toList m providersCb providers = let reqs = map (\f -> lift (f docId range context)) providers in makeRequests reqs "code-actions" tn (req ^. J.id) (send . filter wasRequested . concat) makeRequest (IReq tn "code-actions" (req ^. J.id) providersCb getProviders) where params = req ^. J.params docUri = params ^. J.textDocument . J.uri range = params ^. J.range context = params ^. J.context wasRequested :: J.CodeAction -> Bool wasRequested ca | Nothing <- J.only context = True | Just (J.List allowed) <- J.only context , Just caKind <- ca ^. J.kind = caKind `elem` allowed | otherwise = False wrapCodeAction :: J.CodeAction -> R (Maybe J.CAResult) wrapCodeAction action = do (C.ClientCapabilities _ textDocCaps _ _) <- asksLspFuncs Core.clientCapabilities let literalSupport = textDocCaps >>= C._codeAction >>= C._codeActionLiteralSupport case literalSupport of Nothing -> do let cmdParams = [J.toJSON (FallbackCodeActionParams (action ^. J.edit) (action ^. J.command))] cmd <- mkLspCommand "hie" "fallbackCodeAction" (action ^. J.title) (Just cmdParams) return $ Just (J.CACommand cmd) Just _ -> return $ Just (J.CACodeAction action) send :: [J.CodeAction] -> R () send codeActions = do body <- J.List . catMaybes <$> mapM wrapCodeAction codeActions reactorSend $ RspCodeAction $ Core.makeResponseMessage req body -- TODO: make context specific commands for all sorts of things, such as refactorings