hls-gadt-plugin-1.0.0.0: src/Ide/Plugin/GADT.hs
{-# LANGUAGE DeriveAnyClass #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE ViewPatterns #-}
module Ide.Plugin.GADT (descriptor) where
import Control.Lens ((^.))
import Control.Monad.Except
import Data.Aeson (FromJSON, ToJSON,
Value (Null), toJSON)
import Data.Either.Extra (maybeToEither)
import qualified Data.HashMap.Lazy as HashMap
import qualified Data.Text as T
import Development.IDE
import Development.IDE.GHC.Compat
import Control.Monad.Trans.Except (throwE)
import Data.Maybe (mapMaybe)
import Development.IDE.GHC.Compat.Util (toList)
import Development.IDE.Spans.Pragmas (NextPragmaInfo,
getNextPragmaInfo,
insertNewPragma)
import GHC.Generics (Generic)
import GHC.LanguageExtensions.Type (Extension (GADTSyntax, GADTs))
import Ide.Plugin.GHC
import Ide.PluginUtils
import Ide.Types
import Language.LSP.Server (sendRequest)
import Language.LSP.Types
import qualified Language.LSP.Types.Lens as L
descriptor :: PluginId -> PluginDescriptor IdeState
descriptor plId = (defaultPluginDescriptor plId)
{ Ide.Types.pluginHandlers =
mkPluginHandler STextDocumentCodeAction codeActionHandler
, pluginCommands =
[PluginCommand toGADTSyntaxCommandId "convert data decl to GADT syntax" (toGADTCommand plId)]
}
-- | Parameter used in the command
data ToGADTParams = ToGADTParams
{ uri :: Uri
, range :: Range
} deriving (Generic, ToJSON, FromJSON)
toGADTSyntaxCommandId :: CommandId
toGADTSyntaxCommandId = "GADT.toGADT"
-- | A command replaces H98 data decl with GADT decl in place
toGADTCommand :: PluginId -> CommandFunction IdeState ToGADTParams
toGADTCommand _ state ToGADTParams{..} = pluginResponse $ do
nfp <- getNormalizedFilePath uri
(decls, exts) <- getInRangeH98DeclsAndExts state range nfp
(L ann decl) <- case decls of
[d] -> pure d
_ -> throwE $ "Expected 1 declaration, but got " <> show (Prelude.length decls)
deps <- liftIO $ runAction "GADT.GhcSessionDeps" state $ use GhcSessionDeps nfp
(hsc_dflags . hscEnv -> df) <- liftEither
$ maybeToEither "Get GhcSessionDeps failed" deps
txt <- liftEither $ T.pack <$> (prettyGADTDecl df . h98ToGADTDecl) decl
range <- liftEither
$ maybeToEither "Unable to get data decl range"
$ srcSpanToRange $ locA ann
pragma <- getNextPragma state nfp
let insertEdit = [insertNewPragma pragma GADTs | all (`notElem` exts) [GADTSyntax, GADTs]]
_ <- lift $ sendRequest
SWorkspaceApplyEdit
(ApplyWorkspaceEditParams Nothing (workSpaceEdit nfp (TextEdit range txt : insertEdit)))
(\_ -> pure ())
pure Null
where
workSpaceEdit nfp edits = WorkspaceEdit
(pure $ HashMap.fromList
[(filePathToUri $ fromNormalizedFilePath nfp,
List edits)])
Nothing Nothing
codeActionHandler :: PluginMethodHandler IdeState TextDocumentCodeAction
codeActionHandler state plId (CodeActionParams _ _ doc range _) = pluginResponse $ do
nfp <- getNormalizedFilePath (doc ^. L.uri)
(inRangeH98Decls, _) <- getInRangeH98DeclsAndExts state range nfp
let actions = map (mkAction . printOutputable . tcdLName . unLoc) inRangeH98Decls
pure $ List actions
where
mkAction :: T.Text -> Command |? CodeAction
mkAction name = InR CodeAction{..}
where
_title = "Convert \"" <> name <> "\" to GADT syntax"
_kind = Just CodeActionRefactorRewrite
_diagnostics = Nothing
_isPreferred = Nothing
_disabled = Nothing
_edit = Nothing
_command = Just
$ mkLspCommand plId toGADTSyntaxCommandId _title (Just [toJSON mkParam])
_xdata = Nothing
mkParam = ToGADTParams (doc ^. L.uri) range
-- | Get all H98 decls in the given range, and enabled extensions
getInRangeH98DeclsAndExts :: (MonadIO m) =>
IdeState
-> Range
-> NormalizedFilePath
-> ExceptT String m ([LTyClDecl GP], [Extension])
getInRangeH98DeclsAndExts state range nfp = do
pm <- handleMaybeM "Unable to get ParsedModuleWithComments"
$ liftIO
$ runAction "GADT.GetParsedModuleWithComments" state
$ use GetParsedModuleWithComments nfp
let (L _ hsDecls) = hsmodDecls <$> pm_parsed_source pm
decls = filter isH98DataDecl
$ mapMaybe getDataDecl
$ filter (inRange range) hsDecls
exts = (toList . extensionFlags . ms_hspp_opts . pm_mod_summary) pm
pure (decls, exts)
-- Copy from hls-alternate-number-format-plugin
getNextPragma :: MonadIO m => IdeState -> NormalizedFilePath -> ExceptT String m NextPragmaInfo
getNextPragma state nfp = handleMaybeM "Error: Could not get NextPragmaInfo" $ do
ghcSession <- liftIO $ runAction "GADT.GhcSession" state $ useWithStale GhcSession nfp
(_, fileContents) <- liftIO $ runAction "GADT.GetFileContents" state $ getFileContents nfp
case ghcSession of
Just (hscEnv -> hsc_dflags -> sessionDynFlags, _) -> pure $ Just $ getNextPragmaInfo sessionDynFlags fileContents
Nothing -> pure Nothing