From 047d83284e80f2a6a0c41b149a18a31a6c6bfe73 Mon Sep 17 00:00:00 2001 From: Pepe Iborra <pepeiborra@gmail.com> Date: Tue, 26 May 2020 13:15:51 +0100 Subject: [PATCH 01/13] catch exceptions in plugin Commands --- src/Ide/Plugin.hs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/Ide/Plugin.hs b/src/Ide/Plugin.hs index 0fd249745c..6077c0e91f 100644 --- a/src/Ide/Plugin.hs +++ b/src/Ide/Plugin.hs @@ -18,6 +18,7 @@ module Ide.Plugin , responseError ) where +import Control.Exception(SomeException, catch) import Control.Lens ( (^.) ) import Control.Monad import qualified Data.Aeson as J @@ -206,7 +207,7 @@ executeCommandHandlers ecs = PartialHandlers $ \WithMessage{..} x -> return x{ -- -> ExecuteCommandParams -- -> IO (Either ResponseError Value, Maybe (ServerMethod, ApplyWorkspaceEditParams)) makeExecuteCommands :: [(PluginId, [PluginCommand])] -> LSP.LspFuncs Config -> ExecuteCommandProvider -makeExecuteCommands ecs lf ide = do +makeExecuteCommands ecs lf ide = wrapUnhandledExceptions $ do let pluginMap = Map.fromList ecs parseCmdId :: T.Text -> Maybe (PluginId, CommandId) @@ -334,6 +335,14 @@ makeExecuteCommands ecs lf ide = do -- | Runs a plugin command given a PluginId, CommandId and -- arguments in the form of a JSON object. +wrapUnhandledExceptions :: + (a -> IO (Either ResponseError J.Value, Maybe b)) -> + a -> IO (Either ResponseError J.Value, Maybe b) +wrapUnhandledExceptions action input = + catch (action input) $ \(e::SomeException) -> do + let resp = ResponseError InternalError (T.pack $ show e) Nothing + return (Left resp, Nothing) + runPluginCommand :: Map.Map PluginId [PluginCommand] -> LSP.LspFuncs Config -> IdeState From e70d7e8255e2cd26595be0e394affa9b0ea1529f Mon Sep 17 00:00:00 2001 From: Pepe Iborra <pepeiborra@gmail.com> Date: Sat, 1 Aug 2020 09:05:15 +0100 Subject: [PATCH 02/13] add retrie to shell.nix --- shell.nix | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/shell.nix b/shell.nix index c485fe801b..7f0ffcc9be 100644 --- a/shell.nix +++ b/shell.nix @@ -17,7 +17,8 @@ let defaultCompiler = "ghc" + lib.replaceStrings ["."] [""] haskellPackages.ghc. # for all other compilers there is no Nix cache so dont bother building deps with NIx else haskell.packages.${compiler}.ghcWithPackages (_: []); - compilerWithPackages = haskellPackagesForProject(p: + retrie = with haskell.lib; dontCheck(disableLibraryProfiling(haskellPackages.retrie)); + compilerWithPackages = haskellPackagesForProject(p: with p; [ Diff @@ -66,6 +67,7 @@ let defaultCompiler = "ghc" + lib.replaceStrings ["."] [""] haskellPackages.ghc. primes psqueues regex-tdfa + retrie rope-utf16-splay safe-exceptions shake From f8f58c90256bb0035b8513e84bbc4dd13d044712 Mon Sep 17 00:00:00 2001 From: Pepe Iborra <pepeiborra@gmail.com> Date: Wed, 20 May 2020 19:32:35 +0100 Subject: [PATCH 03/13] Initial Retrie plugin Supports RULES, functions and type synonyms Future work: - Handling names properly (when retrie/#10 is fixed) - Suggestions for pattern synonyms (when retrie/#15 is released) - Refactorings: rename, extract, move, etc.. - Automatically add imports when unfolding - Proper support for workspace folders --- exe/Main.hs | 2 + haskell-language-server.cabal | 3 + src/Ide/Plugin/Retrie.hs | 532 ++++++++++++++++++++++++++++++++++ 3 files changed, 537 insertions(+) create mode 100644 src/Ide/Plugin/Retrie.hs diff --git a/exe/Main.hs b/exe/Main.hs index 081f261cac..67f35c2a64 100644 --- a/exe/Main.hs +++ b/exe/Main.hs @@ -69,6 +69,7 @@ import Ide.Plugin.GhcIde as GhcIde import Ide.Plugin.Floskell as Floskell import Ide.Plugin.Ormolu as Ormolu import Ide.Plugin.StylishHaskell as StylishHaskell +import Ide.Plugin.Retrie as Retrie #if AGPL import Ide.Plugin.Brittany as Brittany #endif @@ -105,6 +106,7 @@ idePlugins includeExamples = pluginDescToIdePlugins allPlugins -- , ghcmodDescriptor "ghcmod" , Ormolu.descriptor "ormolu" , StylishHaskell.descriptor "stylish-haskell" + , Retrie.descriptor "retrie" #if AGPL , Brittany.descriptor "brittany" #endif diff --git a/haskell-language-server.cabal b/haskell-language-server.cabal index 120f1dcce2..30a2495db4 100644 --- a/haskell-language-server.cabal +++ b/haskell-language-server.cabal @@ -47,6 +47,7 @@ library Ide.Plugin.GhcIde Ide.Plugin.Ormolu Ide.Plugin.Pragmas + Ide.Plugin.Retrie Ide.Plugin.Floskell Ide.Plugin.Formatter Ide.Plugin.StylishHaskell @@ -83,6 +84,8 @@ library , optparse-simple , process , regex-tdfa >= 1.3.1.0 + , retrie >= 0.1.1.0 + , safe-exceptions , shake >= 0.17.5 , stylish-haskell == 0.11.* , temporary diff --git a/src/Ide/Plugin/Retrie.hs b/src/Ide/Plugin/Retrie.hs new file mode 100644 index 0000000000..3ddc981d66 --- /dev/null +++ b/src/Ide/Plugin/Retrie.hs @@ -0,0 +1,532 @@ +{-# LANGUAGE CPP #-} +{-# LANGUAGE DeriveAnyClass #-} +{-# LANGUAGE DeriveGeneric #-} +{-# LANGUAGE FlexibleContexts #-} +{-# LANGUAGE LambdaCase #-} +{-# LANGUAGE NamedFieldPuns #-} +{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE RecordWildCards #-} +{-# LANGUAGE ScopedTypeVariables #-} +{-# LANGUAGE StandaloneDeriving #-} +{-# LANGUAGE TupleSections #-} +{-# LANGUAGE TypeApplications #-} +{-# LANGUAGE TypeFamilies #-} + +{-# OPTIONS -Wno-orphans #-} +#include "ghc-api-version.h" + +module Ide.Plugin.Retrie (descriptor) where + +import Control.Exception.Safe (Exception (..), SomeException, + catch, throwIO, try) +import Control.Monad (forM, unless) +import Control.Monad.IO.Class (MonadIO (liftIO)) +import Control.Monad.Trans.Class (MonadTrans (lift)) +import Control.Monad.Trans.Except (ExceptT (..), runExceptT, + throwE) +import Data.Aeson (ToJSON (toJSON), Value (Null)) +import Data.Aeson.Types (FromJSON) +import Data.Bifunctor (Bifunctor (first), second) +import Data.Coerce +import Data.Either (partitionEithers) +import qualified Data.HashMap.Strict as HM +import Data.IORef.Extra (atomicModifyIORef'_, newIORef, + readIORef) +import Data.List.Extra (nubOrdOn) +import Data.String (IsString (fromString)) +import qualified Data.Text as T +import qualified Data.Text.IO as T +import Data.Typeable (Typeable) +import Development.IDE.Core.FileStore (getFileContents) +import Development.IDE.Core.Rules +import Development.IDE.Core.RuleTypes as Ghcide (GetModIface (..), + GetModSummary (..), + GhcSessionDeps (..), + HiFileResult (..), + TypeCheck (..), + tmrModule) +import Development.IDE.Core.Shake (IdeRule, + IdeState (shakeExtras), + runIdeAction, use, + useWithStaleFast, use_) +import Development.IDE.GHC.Error (isInsideSrcSpan, + srcSpanToRange) +import Development.IDE.GHC.Util (hscEnv, prettyPrint, runGhcEnv) +import Development.IDE.Types.Location +import Development.Shake (RuleResult) +import GHC (GenLocated (L), GhcRn, + HsBindLR (FunBind), + HsGroup (..), + HsValBindsLR (..), HscEnv, IdP, + LRuleDecls, + ModIface (mi_fixities), + ModSummary (ModSummary, ms_hspp_buf, ms_mod), + NHsValBindsLR (..), + ParsedModule (..), + RuleDecl (HsRule), + RuleDecls (HsRules), + SrcSpan (..), + TyClDecl (SynDecl), + TyClGroup (..), + TypecheckedModule (..), fun_id, + moduleNameString, parseModule, + rds_rules, srcSpanFile) +import GHC.Generics (Generic) +import GhcPlugins (Outputable, + SourceText (NoSourceText), + isQual, isQual_maybe, + nameModule_maybe, nameRdrName, + occNameFS, occNameString, + rdrNameOcc, unpackFS) +import Ide.Plugin +import Ide.Types +import Language.Haskell.LSP.Core (LspFuncs (..), ProgressCancellable (Cancellable)) +import Language.Haskell.LSP.Messages (FromServerMessage (NotShowMessage)) +import Language.Haskell.LSP.Types as J +import Retrie.CPP (CPP (NoCPP), parseCPP, + printCPP) +import Retrie.ExactPrint (fix, relativiseApiAnns, + transformA, unsafeMkA) +import Retrie.Fixity (mkFixityEnv) +import qualified Retrie.GHC as GHC +import Retrie.Monad (addImports, apply, + getGroundTerms, runRetrie) +import Retrie.Options (defaultOptions, getTargetFiles) +import qualified Retrie.Options as Retrie +import Retrie.Replace (Change (..), Replacement (..)) +import Retrie.Rewrites +import Retrie.SYB (listify) +import Retrie.Util (Verbosity (Loud)) +import StringBuffer (stringToStringBuffer) +import System.Directory (makeAbsolute) + +descriptor :: PluginId -> PluginDescriptor +descriptor plId = + (defaultPluginDescriptor plId) + { pluginCodeActionProvider = Just provider, + pluginCommands = [retrieCommand] + } + +retrieCommandName :: T.Text +retrieCommandName = "retrieCommand" + +retrieCommand :: PluginCommand +retrieCommand = + PluginCommand (coerce retrieCommandName) "run the refactoring" runRetrieCmd + +-- | Parameters for the runRetrie PluginCommand. +data RunRetrieParams = RunRetrieParams + { description :: T.Text, + -- | rewrites for Retrie + rewrites :: [Either ImportSpec RewriteSpec], + -- | Originating file + originatingFile :: String -- NormalizedFilePath + } + deriving (Eq, Show, Generic, FromJSON, ToJSON) + +runRetrieCmd :: + LspFuncs a -> + IdeState -> + RunRetrieParams -> + IO (Either ResponseError Value, Maybe (ServerMethod, ApplyWorkspaceEditParams)) +runRetrieCmd lsp state RunRetrieParams {..} = + withIndefiniteProgress lsp description Cancellable $ do + session <- + runAction "Retrie.GhcSessionDeps" state $ + use_ GhcSessionDeps $ + toNormalizedFilePath originatingFile + (errors, edits) <- + callRetrie + state + (hscEnv session) + rewrites + (toNormalizedFilePath originatingFile) + unless (null errors) $ + sendFunc lsp $ + NotShowMessage $ + NotificationMessage "2.0" WindowShowMessage $ + ShowMessageParams MtWarning $ + T.unlines $ + "## Found errors during rewrite:" : + ["-" <> T.pack (show e) | e <- errors] + return + (Right Null, Just (WorkspaceApplyEdit, ApplyWorkspaceEditParams edits)) + +------------------------------------------------------------------------------- + +provider :: CodeActionProvider +provider _a state plId (TextDocumentIdentifier uri) range ca = response $ do + let (J.CodeActionContext _diags _monly) = ca + fp <- handleMaybe "uri" $ uriToFilePath' uri + let nfp = toNormalizedFilePath' fp + + tm <- + handleMaybeM "no typechecked module" $ + useRule "retrie.typecheckModule" state TypeCheck nfp + + ModSummary {ms_mod} <- + handleMaybeM "no mod summary" $ + useRule "retrie.typecheckModule" state GetModSummary nfp + + -- we use the typechecked source instead of the parsed source + -- to be able to extract module names from the Ids, + -- so that we can include adding the required imports in the retrie command + let TypecheckedModule {tm_renamed_source = Just rn} = tmrModule tm + ( HsGroup + { hs_valds = + XValBindsLR + (NValBinds binds _sigs :: NHsValBindsLR GHC.GhcRn), + hs_ruleds, + hs_tyclds + }, + _, + _, + _ + ) = rn + + pos = _start range + topLevelBinds = + [ decl + | (_, bagBinds) <- binds, + L _ decl <- GHC.bagToList bagBinds + ] + + rewrites = + concatMap (suggestBindRewrites fp pos ms_mod) topLevelBinds + ++ concatMap (suggestRuleRewrites fp pos ms_mod) hs_ruleds + ++ [ r + | TyClGroup {group_tyclds} <- hs_tyclds, + L _ g <- group_tyclds, + r <- suggestTypeRewrites fp pos ms_mod g + ] + + commands <- lift $ + forM rewrites $ \(title, kind, params) -> do + c <- mkLspCommand plId (coerce retrieCommandName) title (Just [toJSON params]) + return $ CodeAction title (Just kind) Nothing Nothing (Just c) + + return $ J.List [CACodeAction c | c <- commands] + +suggestBindRewrites :: + String -> + Position -> + GHC.Module -> + HsBindLR GhcRn GhcRn -> + [(T.Text, CodeActionKind, RunRetrieParams)] +suggestBindRewrites originatingFile pos ms_mod (FunBind {fun_id = L l' rdrName, fun_matches}) + | pos `isInsideSrcSpan` l' = + let pprName = prettyPrint rdrName + pprNameText = T.pack pprName + names = listify p fun_matches + p name = nameModule_maybe name /= Just ms_mod + imports = + [ AddImport {..} + | name <- names, + Just ideclNameString <- + [moduleNameString . GHC.moduleName <$> nameModule_maybe name], + let ideclSource = False, + let r = nameRdrName name, + let ideclQualifiedBool = isQual r, + let ideclAsString = moduleNameString . fst <$> isQual_maybe r, + let ideclThing = Just (IEVar $ occNameString $ rdrNameOcc r) + ] + in [ let rewrites = + [Right $ Unfold (qualify ms_mod pprName)] + ++ map Left imports + description = "Unfold " <> pprNameText + in (description, CodeActionRefactorInline, RunRetrieParams {..}), + let rewrites = [Right $ Fold (qualify ms_mod pprName)] + description = "Fold " <> pprNameText + in (description, CodeActionRefactorExtract, RunRetrieParams {..}) + ] +suggestBindRewrites _ _ _ _ = [] + +-- TODO add imports to the rewrite +suggestTypeRewrites :: + (Outputable (IdP pass)) => + String -> + Position -> + GHC.Module -> + TyClDecl pass -> + [(T.Text, CodeActionKind, RunRetrieParams)] +suggestTypeRewrites originatingFile pos ms_mod (SynDecl {tcdLName = L l rdrName}) + | pos `isInsideSrcSpan` l = + let pprName = prettyPrint rdrName + pprNameText = T.pack pprName + in [ let rewrites = [Right $ TypeForward (qualify ms_mod pprName)] + description = "Unfold " <> pprNameText + in (description, CodeActionRefactorInline, RunRetrieParams {..}), + let rewrites = [Right $ TypeBackward (qualify ms_mod pprName)] + description = "Fold " <> pprNameText + in (description, CodeActionRefactorExtract, RunRetrieParams {..}) + ] +suggestTypeRewrites _ _ _ _ = [] + +-- TODO add imports to the rewrite +suggestRuleRewrites :: + FilePath -> + Position -> + GHC.Module -> + LRuleDecls pass -> + [(T.Text, CodeActionKind, RunRetrieParams)] +suggestRuleRewrites originatingFile pos ms_mod (L l (HsRules {rds_rules})) + | pos `isInsideSrcSpan` l = + concat + [ [ let rewrites = + [Right $ RuleForward (qualify ms_mod ruleName)] + description = "Apply rule " <> T.pack ruleName <> " forward" + in ( description, + CodeActionRefactor, + RunRetrieParams {..} + ), + let rewrites = + [Right $ RuleBackward (qualify ms_mod ruleName)] + description = "Apply rule " <> T.pack ruleName <> " backwards" + in ( description, + CodeActionRefactor, + RunRetrieParams {..} + ) + ] + | L _ (HsRule _ (L _ (_, rn)) _ _ _ _ _) <- rds_rules, + let ruleName = unpackFS rn + ] +suggestRuleRewrites _ _ _ _ = [] + +qualify :: GHC.Module -> String -> String +qualify ms_mod x = prettyPrint ms_mod <> "." <> x + +------------------------------------------------------------------------------- +-- Retrie driving code + +data CallRetrieError + = CallRetrieInternalError String NormalizedFilePath + | NoParse NormalizedFilePath + | GHCParseError NormalizedFilePath String + | NoTypeCheck NormalizedFilePath + deriving (Eq, Typeable) + +instance Show CallRetrieError where + show (CallRetrieInternalError msg f) = msg <> " - " <> fromNormalizedFilePath f + show (NoParse f) = "Cannot parse: " <> fromNormalizedFilePath f + show (GHCParseError f m) = "Cannot parse " <> fromNormalizedFilePath f <> " : " <> m + show (NoTypeCheck f) = "File does not typecheck: " <> fromNormalizedFilePath f + +instance Exception CallRetrieError + +callRetrie :: + IdeState -> + HscEnv -> + [Either ImportSpec RewriteSpec] -> + NormalizedFilePath -> + IO ([CallRetrieError], WorkspaceEdit) +callRetrie state session rewrites origin = do + let reuseParsedModule f = do + pm <- + useOrFail "GetParsedModule" NoParse GetParsedModule f + (fixities, pm) <- fixFixities f (fixAnns pm) + return (fixities, pm) + getCPPmodule t = do + nt <- toNormalizedFilePath' <$> makeAbsolute t + let getParsedModule f contents = do + modSummary <- + useOrFail "GetModSummary" (CallRetrieInternalError "file not found") GetModSummary nt + let ms' = + modSummary + { ms_hspp_buf = + Just (stringToStringBuffer contents) + } + (_, parsed) <- + runGhcEnv session (parseModule ms') + `catch` \e -> throwIO (GHCParseError nt (show @SomeException e)) + (fixities, parsed) <- fixFixities f (fixAnns parsed) + return (fixities, parsed) + + contents <- do + (_, mbContentsVFS) <- + runAction "Retrie.GetFileContents" state $ getFileContents nt + case mbContentsVFS of + Just contents -> return contents + Nothing -> T.readFile (fromNormalizedFilePath nt) + if any (T.isPrefixOf "#if" . T.toLower) (T.lines contents) + then do + fixitiesRef <- newIORef mempty + let parseModule x = do + (fix, res) <- getParsedModule nt x + atomicModifyIORef'_ fixitiesRef (fix <>) + return res + res <- parseCPP parseModule contents + fixities <- readIORef fixitiesRef + return (fixities, res) + else do + (fixities, pm) <- reuseParsedModule nt + return (fixities, NoCPP pm) + + -- TODO cover all workspaceFolders + target = "." + + retrieOptions :: Retrie.Options + retrieOptions = (defaultOptions target) {Retrie.verbosity = Loud} + + (theImports, theRewrites) = partitionEithers rewrites + + annotatedImports = + unsafeMkA (map (GHC.noLoc . toImportDecl) theImports) mempty 0 + + (originFixities, originParsedModule) <- reuseParsedModule origin + retrie <- + (\specs -> apply specs >> addImports annotatedImports) + <$> parseRewriteSpecs + (\_f -> return $ NoCPP originParsedModule) + originFixities + theRewrites + + targets <- getTargetFiles retrieOptions (getGroundTerms retrie) + + results <- forM targets $ \t -> runExceptT $ do + (fixityEnv, cpp) <- ExceptT $ try $ getCPPmodule t + (_user, ast, change@(Change replacements _imports)) <- + lift $ runRetrie fixityEnv retrie cpp + case ast of + _ -> + -- NoCPP {} -> + return $ asTextEdits change + _ -> do + -- DEBUG CODE bypass replacements and use the rewritten ast instead + -- we would want to do this to capture import edits + let new = T.pack $ printCPP replacements ast + uri = Uri $ T.pack t + change' = [(uri, TextEdit wholeDocument new)] + return change' + + let (errors :: [CallRetrieError], replacements) = partitionEithers results + editParams :: WorkspaceEdit + editParams = + WorkspaceEdit (Just $ asEditMap replacements) Nothing + + return (errors, editParams) + where + useOrFail :: + IdeRule r v => + String -> + (NormalizedFilePath -> CallRetrieError) -> + r -> + NormalizedFilePath -> + IO (RuleResult r) + useOrFail lbl mkException rule f = + useRule lbl state rule f >>= maybe (liftIO $ throwIO $ mkException f) return + fixityEnvFromModIface modIface = + mkFixityEnv + [ (fs, (fs, fixity)) + | (n, fixity) <- mi_fixities modIface, + let fs = occNameFS n + ] + fixFixities f pm = do + HiFileResult {hirModIface} <- + useOrFail "GetModIface" NoTypeCheck GetModIface f + let fixities = fixityEnvFromModIface hirModIface + res <- transformA pm (fix fixities) + return (fixities, res) + fixAnns ParsedModule {..} = + let ranns = relativiseApiAnns pm_parsed_source pm_annotations + in unsafeMkA pm_parsed_source ranns 0 + +wholeDocument :: Range +wholeDocument = Range (Position 0 0) (Position maxBound 0) + +asEditMap :: [[(Uri, TextEdit)]] -> WorkspaceEditMap +asEditMap = coerce . HM.fromListWith (++) . concatMap (map (second pure)) + +asTextEdits :: Change -> [(Uri, TextEdit)] +asTextEdits NoChange = [] +asTextEdits (Change reps _imports) = + -- TODO retrie does not include import edits in the 'reps' list + -- fix this in retrie or work around it here + [ (Uri spanLoc, edit) + | Replacement {..} <- nubOrdOn replLocation reps, + s@(RealSrcSpan rspan) <- [replLocation], + let spanLoc = T.pack $ unpackFS $ srcSpanFile rspan, + let edit = TextEdit (srcSpanToRange s) (T.pack replReplacement) + ] + +------------------------------------------------------------------------------- +-- Rule wrappers + +_useRuleBlocking, + _useRuleStale, + useRule :: + (IdeRule k v) => + String -> + IdeState -> + k -> + NormalizedFilePath -> + IO (Maybe (RuleResult k)) +_useRuleBlocking label state rule f = runAction label state (use rule f) +_useRuleStale label state rule f = + fmap fst + <$> runIdeAction label (shakeExtras state) (useWithStaleFast rule f) + +-- | Chosen approach for calling ghcide Shake rules +useRule label = _useRuleStale ("Retrie." <> label) + +------------------------------------------------------------------------------- +-- Error handling combinators + +handleMaybe :: Monad m => e -> Maybe b -> ExceptT e m b +handleMaybe msg = maybe (throwE msg) return + +handleMaybeM :: Monad m => e -> m (Maybe b) -> ExceptT e m b +handleMaybeM msg act = maybe (throwE msg) return =<< lift act + +response :: ExceptT String IO a -> IO (Either ResponseError a) +response = + fmap (first (\msg -> ResponseError InternalError (fromString msg) Nothing)) + . runExceptT + +------------------------------------------------------------------------------- +-- Serialization wrappers and instances + +deriving instance Eq RewriteSpec + +deriving instance Show RewriteSpec + +deriving instance Generic RewriteSpec + +deriving instance FromJSON RewriteSpec + +deriving instance ToJSON RewriteSpec + +data QualName = QualName {qual, name :: String} + deriving (Eq, Show, Generic, FromJSON, ToJSON) + +data IE name + = IEVar name + deriving (Eq, Show, Generic, FromJSON, ToJSON) + +data ImportSpec = AddImport + { ideclNameString :: String, + ideclSource :: Bool, + ideclQualifiedBool :: Bool, + ideclAsString :: Maybe String, + ideclThing :: Maybe (IE String) + } + deriving (Eq, Show, Generic, FromJSON, ToJSON) + +toImportDecl :: ImportSpec -> GHC.ImportDecl GHC.GhcPs +toImportDecl AddImport {..} = GHC.ImportDecl {..} + where + toMod = GHC.noLoc . GHC.mkModuleName + ideclName = toMod ideclNameString + ideclPkgQual = Nothing + ideclSafe = False + ideclImplicit = False + ideclHiding = Nothing + ideclSourceSrc = NoSourceText + ideclExt = GHC.noExtField + ideclAs = toMod <$> ideclAsString + +#if MIN_GHC_API_VERSION(8,10,0) + + ideclQualified = if ideclQualifiedBool then GHC.QualifiedPre else GHC.NotQualified +#else + ideclQualified = ideclQualifiedBool +#endif From 0722bfd90d7c2777fc8a2897c3c0d3e492fb2b3e Mon Sep 17 00:00:00 2001 From: Pepe Iborra <pepeiborra@gmail.com> Date: Sat, 1 Aug 2020 13:58:05 +0100 Subject: [PATCH 04/13] Add README entry --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index cf4624f688..fca6598591 100644 --- a/README.md +++ b/README.md @@ -71,6 +71,10 @@ This is *very* early stage software. This will cause compilation errors if a dependency contains invalid Haddock markup, though in a future version of GHC (hopefully 8.12), [these will be demoted to warnings](https://gitlab.haskell.org/ghc/ghc/-/merge_requests/2377). + - Integration with [retrie](https://hackage.haskell.org/package/retrie) + +  + - Many more (TBD) ## Installation From 5e0e798c2dc4ec10b6b57cec3deb269a90a11a07 Mon Sep 17 00:00:00 2001 From: Pepe Iborra <pepeiborra@gmail.com> Date: Sat, 1 Aug 2020 15:37:14 +0100 Subject: [PATCH 05/13] Fix comment out of place --- src/Ide/Plugin.hs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Ide/Plugin.hs b/src/Ide/Plugin.hs index 6077c0e91f..a9745345a3 100644 --- a/src/Ide/Plugin.hs +++ b/src/Ide/Plugin.hs @@ -332,9 +332,6 @@ makeExecuteCommands ecs lf ide = wrapUnhandledExceptions $ do -} -- ----------------------------------------------------------- - --- | Runs a plugin command given a PluginId, CommandId and --- arguments in the form of a JSON object. wrapUnhandledExceptions :: (a -> IO (Either ResponseError J.Value, Maybe b)) -> a -> IO (Either ResponseError J.Value, Maybe b) @@ -343,6 +340,9 @@ wrapUnhandledExceptions action input = let resp = ResponseError InternalError (T.pack $ show e) Nothing return (Left resp, Nothing) + +-- | Runs a plugin command given a PluginId, CommandId and +-- arguments in the form of a JSON object. runPluginCommand :: Map.Map PluginId [PluginCommand] -> LSP.LspFuncs Config -> IdeState From 6ff62c4a811e36ab1173dde814bc024e7b6ef9a3 Mon Sep 17 00:00:00 2001 From: Pepe Iborra <pepeiborra@gmail.com> Date: Sat, 1 Aug 2020 15:46:56 +0100 Subject: [PATCH 06/13] Compat with 8.10 --- src/Ide/Plugin/Retrie.hs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Ide/Plugin/Retrie.hs b/src/Ide/Plugin/Retrie.hs index 3ddc981d66..741a615910 100644 --- a/src/Ide/Plugin/Retrie.hs +++ b/src/Ide/Plugin/Retrie.hs @@ -59,7 +59,7 @@ import GHC (GenLocated (L), GhcRn, HsGroup (..), HsValBindsLR (..), HscEnv, IdP, LRuleDecls, - ModIface (mi_fixities), + mi_fixities, ModSummary (ModSummary, ms_hspp_buf, ms_mod), NHsValBindsLR (..), ParsedModule (..), From b8627c2254a67fe21905b449e1ba538578652899 Mon Sep 17 00:00:00 2001 From: Pepe Iborra <pepeiborra@gmail.com> Date: Sat, 1 Aug 2020 15:53:04 +0100 Subject: [PATCH 07/13] Add retrie to all? the stack.yaml descriptors --- stack-8.10.1.yaml | 3 +++ stack-8.6.4.yaml | 7 +++++++ stack-8.6.5.yaml | 6 +++++- stack-8.8.2.yaml | 3 +++ stack-8.8.3.yaml | 3 +++ stack-8.8.4.yaml | 3 +++ stack.yaml | 6 +++++- 7 files changed, 29 insertions(+), 2 deletions(-) diff --git a/stack-8.10.1.yaml b/stack-8.10.1.yaml index bc51cd64b9..8d62e3c008 100644 --- a/stack-8.10.1.yaml +++ b/stack-8.10.1.yaml @@ -19,6 +19,7 @@ extra-deps: - monad-dijkstra-0.1.1.2 - optics-core-0.3 - ormolu-0.1.2.0 +- retrie-0.1.1.1 - stylish-haskell-0.11.0.0 - semigroups-0.18.5 - temporary-1.2.1.1 @@ -31,6 +32,8 @@ flags: # but brittany is not usable with ghc-8.10.1 # see https://github.com/lspitzner/brittany/issues/269 agpl: false + retrie: + BuildExecutable: false # allow-newer: true diff --git a/stack-8.6.4.yaml b/stack-8.6.4.yaml index 082611ddb4..cbb071ca8f 100644 --- a/stack-8.6.4.yaml +++ b/stack-8.6.4.yaml @@ -9,6 +9,8 @@ ghc-options: extra-deps: - aeson-1.4.3.0 +- ansi-terminal-0.10.3 +- async-2.2.2 - brittany-0.12.1.1 - butcher-1.3.3.1 - bytestring-trie-0.2.5.0 @@ -46,13 +48,16 @@ extra-deps: - multistate-0.8.0.1 - ormolu-0.1.2.0 - opentelemetry-0.4.2 +- optparse-applicative-0.15.1.0 - parser-combinators-1.2.1 - regex-base-0.94.0.0 - regex-tdfa-1.3.1.0 +- retrie-0.1.1.1 - rope-utf16-splay-0.3.1.0 - shake-0.19.1 - strict-list-0.1.5 - stylish-haskell-0.11.0.0 +- syb-0.7.1 - syz-0.2.0.0 - tasty-rerun-1.1.17 - temporary-1.2.1.1 @@ -68,6 +73,8 @@ extra-deps: flags: haskell-language-server: pedantic: true + retrie: + BuildExecutable: false # allow-newer: true diff --git a/stack-8.6.5.yaml b/stack-8.6.5.yaml index 771d6f3882..fe45cb734a 100644 --- a/stack-8.6.5.yaml +++ b/stack-8.6.5.yaml @@ -8,7 +8,7 @@ ghc-options: "$everything": -haddock extra-deps: -- ansi-terminal-0.10.2 +- ansi-terminal-0.10.3 - base-compat-0.11.0 - brittany-0.12.1.1@rev:2 - butcher-1.3.3.1 @@ -20,6 +20,7 @@ extra-deps: - fuzzy-0.1.0.0 # - ghcide-0.1.0 - ghc-check-0.5.0.1 +- ghc-exactprint-0.6.2 - ghc-lib-parser-8.10.1.20200523 - ghc-lib-parser-ex-8.10.0.4 - haddock-api-2.22.0@rev:1 @@ -41,6 +42,7 @@ extra-deps: - regex-base-0.94.0.0 - regex-pcre-builtin-0.95.1.1.8.43 - regex-tdfa-1.3.1.0 +- retrie-0.1.1.1 - semialign-1.1 # - github: wz1000/shake # commit: fb3859dca2e54d1bbb2c873e68ed225fa179fbef @@ -53,6 +55,8 @@ extra-deps: flags: haskell-language-server: pedantic: true + retrie: + BuildExecutable: false # allow-newer: true diff --git a/stack-8.8.2.yaml b/stack-8.8.2.yaml index 99e5c20f3b..0d3b2d5ca3 100644 --- a/stack-8.8.2.yaml +++ b/stack-8.8.2.yaml @@ -35,6 +35,7 @@ extra-deps: - monad-dijkstra-0.1.1.2 - opentelemetry-0.4.2 - ormolu-0.1.2.0 +- retrie-0.1.1.1 - semigroups-0.18.5 # - github: wz1000/shake # commit: fb3859dca2e54d1bbb2c873e68ed225fa179fbef @@ -44,6 +45,8 @@ extra-deps: flags: haskell-language-server: pedantic: true + retrie: + BuildExecutable: false # allow-newer: true diff --git a/stack-8.8.3.yaml b/stack-8.8.3.yaml index b972a17e4e..71a81374c1 100644 --- a/stack-8.8.3.yaml +++ b/stack-8.8.3.yaml @@ -24,6 +24,7 @@ extra-deps: - ilist-0.3.1.0 - lsp-test-0.11.0.3 - monad-dijkstra-0.1.1.2 +- retrie-0.1.1.1 - semigroups-0.18.5 # - github: wz1000/shake # commit: fb3859dca2e54d1bbb2c873e68ed225fa179fbef @@ -32,6 +33,8 @@ extra-deps: flags: haskell-language-server: pedantic: true + retrie: + BuildExecutable: false # allow-newer: true diff --git a/stack-8.8.4.yaml b/stack-8.8.4.yaml index 59d3327346..d2f1d8449a 100644 --- a/stack-8.8.4.yaml +++ b/stack-8.8.4.yaml @@ -26,6 +26,7 @@ extra-deps: - ilist-0.3.1.0 - lsp-test-0.11.0.3 - monad-dijkstra-0.1.1.2 +- retrie-0.1.1.1 - semigroups-0.18.5 # - github: wz1000/shake # commit: fb3859dca2e54d1bbb2c873e68ed225fa179fbef @@ -34,6 +35,8 @@ extra-deps: flags: haskell-language-server: pedantic: true + retrie: + BuildExecutable: false # allow-newer: true diff --git a/stack.yaml b/stack.yaml index 71c2bd1d51..a3fb790819 100644 --- a/stack.yaml +++ b/stack.yaml @@ -8,7 +8,7 @@ ghc-options: "$everything": -haddock extra-deps: -- ansi-terminal-0.10.2 +- ansi-terminal-0.10.3 - base-compat-0.11.0 - brittany-0.12.1.1@rev:2 - butcher-1.3.3.1 @@ -20,6 +20,7 @@ extra-deps: - fuzzy-0.1.0.0 # - ghcide-0.1.0 - ghc-check-0.5.0.1 +- ghc-exactprint-0.6.2 - ghc-lib-parser-8.10.1.20200523 - ghc-lib-parser-ex-8.10.0.4 - haddock-api-2.22.0@rev:1 @@ -41,6 +42,7 @@ extra-deps: - regex-base-0.94.0.0 - regex-pcre-builtin-0.95.1.1.8.43 - regex-tdfa-1.3.1.0 +- retrie-0.1.1.1 - semialign-1.1 - stylish-haskell-0.11.0.0 - tasty-rerun-1.1.17 @@ -51,6 +53,8 @@ extra-deps: flags: haskell-language-server: pedantic: true + retrie: + BuildExecutable: false # allow-newer: true From 7eb593fa58c7d6622b6a66b420fb6ddb8c1601ea Mon Sep 17 00:00:00 2001 From: Pepe Iborra <pepeiborra@gmail.com> Date: Sat, 1 Aug 2020 16:18:53 +0100 Subject: [PATCH 08/13] Add ghc-api-version.h from ghcide --- haskell-language-server.cabal | 3 +++ include/ghc-api-version.h | 10 ++++++++++ 2 files changed, 13 insertions(+) create mode 100644 include/ghc-api-version.h diff --git a/haskell-language-server.cabal b/haskell-language-server.cabal index 30a2495db4..7d598bb0f9 100644 --- a/haskell-language-server.cabal +++ b/haskell-language-server.cabal @@ -15,6 +15,7 @@ build-type: Simple extra-source-files: README.md ChangeLog.md + include/ghc-api-version.h flag agpl Description: Enable AGPL dependencies @@ -93,6 +94,8 @@ library , time , transformers , unordered-containers + include-dirs: + include if os(windows) build-depends: Win32 else diff --git a/include/ghc-api-version.h b/include/ghc-api-version.h new file mode 100644 index 0000000000..11cabb3dc9 --- /dev/null +++ b/include/ghc-api-version.h @@ -0,0 +1,10 @@ +#ifndef GHC_API_VERSION_H +#define GHC_API_VERSION_H + +#ifdef GHC_LIB +#define MIN_GHC_API_VERSION(x,y,z) MIN_VERSION_ghc_lib(x,y,z) +#else +#define MIN_GHC_API_VERSION(x,y,z) MIN_VERSION_ghc(x,y,z) +#endif + +#endif From a25b1e3183086496010c534be135ba91c2e297c1 Mon Sep 17 00:00:00 2001 From: Pepe Iborra <pepeiborra@gmail.com> Date: Sat, 1 Aug 2020 16:32:50 +0100 Subject: [PATCH 09/13] drop unused debug code and add TODO for import replacements --- src/Ide/Plugin/Retrie.hs | 19 +++---------------- 1 file changed, 3 insertions(+), 16 deletions(-) diff --git a/src/Ide/Plugin/Retrie.hs b/src/Ide/Plugin/Retrie.hs index 741a615910..32241f5cb1 100644 --- a/src/Ide/Plugin/Retrie.hs +++ b/src/Ide/Plugin/Retrie.hs @@ -83,8 +83,7 @@ import Ide.Types import Language.Haskell.LSP.Core (LspFuncs (..), ProgressCancellable (Cancellable)) import Language.Haskell.LSP.Messages (FromServerMessage (NotShowMessage)) import Language.Haskell.LSP.Types as J -import Retrie.CPP (CPP (NoCPP), parseCPP, - printCPP) +import Retrie.CPP (CPP (NoCPP), parseCPP) import Retrie.ExactPrint (fix, relativiseApiAnns, transformA, unsafeMkA) import Retrie.Fixity (mkFixityEnv) @@ -384,19 +383,12 @@ callRetrie state session rewrites origin = do results <- forM targets $ \t -> runExceptT $ do (fixityEnv, cpp) <- ExceptT $ try $ getCPPmodule t - (_user, ast, change@(Change replacements _imports)) <- + -- TODO add the imports to the resulting edits + (_user, ast, change@(Change _replacements _imports)) <- lift $ runRetrie fixityEnv retrie cpp case ast of _ -> - -- NoCPP {} -> return $ asTextEdits change - _ -> do - -- DEBUG CODE bypass replacements and use the rewritten ast instead - -- we would want to do this to capture import edits - let new = T.pack $ printCPP replacements ast - uri = Uri $ T.pack t - change' = [(uri, TextEdit wholeDocument new)] - return change' let (errors :: [CallRetrieError], replacements) = partitionEithers results editParams :: WorkspaceEdit @@ -430,17 +422,12 @@ callRetrie state session rewrites origin = do let ranns = relativiseApiAnns pm_parsed_source pm_annotations in unsafeMkA pm_parsed_source ranns 0 -wholeDocument :: Range -wholeDocument = Range (Position 0 0) (Position maxBound 0) - asEditMap :: [[(Uri, TextEdit)]] -> WorkspaceEditMap asEditMap = coerce . HM.fromListWith (++) . concatMap (map (second pure)) asTextEdits :: Change -> [(Uri, TextEdit)] asTextEdits NoChange = [] asTextEdits (Change reps _imports) = - -- TODO retrie does not include import edits in the 'reps' list - -- fix this in retrie or work around it here [ (Uri spanLoc, edit) | Replacement {..} <- nubOrdOn replLocation reps, s@(RealSrcSpan rspan) <- [replLocation], From 231bf0c98ac83f099c299954ed0ce6e34abf31e5 Mon Sep 17 00:00:00 2001 From: Pepe Iborra <pepeiborra@gmail.com> Date: Sat, 1 Aug 2020 16:59:18 +0100 Subject: [PATCH 10/13] Compat with 8.6 --- src/Ide/Plugin/Retrie.hs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Ide/Plugin/Retrie.hs b/src/Ide/Plugin/Retrie.hs index 32241f5cb1..57ecfdd8de 100644 --- a/src/Ide/Plugin/Retrie.hs +++ b/src/Ide/Plugin/Retrie.hs @@ -286,7 +286,7 @@ suggestRuleRewrites originatingFile pos ms_mod (L l (HsRules {rds_rules})) RunRetrieParams {..} ) ] - | L _ (HsRule _ (L _ (_, rn)) _ _ _ _ _) <- rds_rules, + | L _ (HsRule {rd_name = (L _ (_, rn))}) <- rds_rules, let ruleName = unpackFS rn ] suggestRuleRewrites _ _ _ _ = [] From 4baf2949246c049c70d792a5437bb5c5ecf4e737 Mon Sep 17 00:00:00 2001 From: Pepe Iborra <pepeiborra@gmail.com> Date: Sat, 1 Aug 2020 17:03:46 +0100 Subject: [PATCH 11/13] Rewrite the 8.6.4 stack descriptor on top of the 8.6.5 one GHC minor versions do not change any APIs, do they? --- stack-8.6.4.yaml | 50 ++++++++++++++++-------------------------------- 1 file changed, 16 insertions(+), 34 deletions(-) diff --git a/stack-8.6.4.yaml b/stack-8.6.4.yaml index cbb071ca8f..307f1a86b9 100644 --- a/stack-8.6.4.yaml +++ b/stack-8.6.4.yaml @@ -1,4 +1,5 @@ -resolver: lts-13.19 # Last 8.6.4 +resolver: lts-14.27 # Last 8.6.5 +compiler: ghc-8.6.4 packages: - . @@ -8,67 +9,49 @@ ghc-options: "$everything": -haddock extra-deps: -- aeson-1.4.3.0 - ansi-terminal-0.10.3 -- async-2.2.2 -- brittany-0.12.1.1 +- base-compat-0.11.0 +- brittany-0.12.1.1@rev:2 - butcher-1.3.3.1 -- bytestring-trie-0.2.5.0 - Cabal-3.0.2.0 -- cabal-doctest-1.0.8 -- cabal-plan-0.5.0.0 -- constrained-dynamic-0.1.0.0 -- deque-0.4.3 -# - ghcide-0.1.0 +- cabal-plan-0.6.2.0 +- clock-0.7.2 - extra-1.7.3 - floskell-0.10.3 - fuzzy-0.1.0.0 +# - ghcide-0.1.0 - ghc-check-0.5.0.1 -- ghc-exactprint-0.6.2 # for HaRe +- ghc-exactprint-0.6.2 - ghc-lib-parser-8.10.1.20200523 - ghc-lib-parser-ex-8.10.0.4 -- ghc-paths-0.1.0.12 - haddock-api-2.22.0@rev:1 - haddock-library-1.8.0 -- happy-1.19.12 - haskell-lsp-0.22.0.0 - haskell-lsp-types-0.22.0.0 -- haskell-src-exts-1.21.1 - hie-bios-0.6.1 -- hlint-2.2.8 -- hoogle-5.0.17.11 -- hsimport-0.11.0@rev:2 - HsYAML-0.2.1.0@rev:1 - HsYAML-aeson-0.2.0.0@rev:1 +- indexed-profunctors-0.1 - lens-4.18 - lsp-test-0.11.0.3 -- microlens-th-0.4.2.3@rev:1 - monad-dijkstra-0.1.1.2 -- monad-memo-0.4.1 -- multistate-0.8.0.1 -- ormolu-0.1.2.0 - opentelemetry-0.4.2 +- optics-core-0.2 - optparse-applicative-0.15.1.0 +- ormolu-0.1.2.0 - parser-combinators-1.2.1 - regex-base-0.94.0.0 +- regex-pcre-builtin-0.95.1.1.8.43 - regex-tdfa-1.3.1.0 - retrie-0.1.1.1 -- rope-utf16-splay-0.3.1.0 -- shake-0.19.1 -- strict-list-0.1.5 +- semialign-1.1 +# - github: wz1000/shake +# commit: fb3859dca2e54d1bbb2c873e68ed225fa179fbef - stylish-haskell-0.11.0.0 -- syb-0.7.1 -- syz-0.2.0.0 - tasty-rerun-1.1.17 - temporary-1.2.1.1 -- th-abstraction-0.3.1.0 - type-equality-1 -- unix-compat-0.5.2 -- unordered-containers-0.2.10.0 -- yaml-0.11.1.2 -# To make build work in windows 7 -- unix-time-0.4.7 - +- topograph-1 flags: haskell-language-server: @@ -76,7 +59,6 @@ flags: retrie: BuildExecutable: false - # allow-newer: true nix: From c00cca01c221f90d663c69742826aaa092a91d75 Mon Sep 17 00:00:00 2001 From: Pepe Iborra <pepeiborra@gmail.com> Date: Sat, 1 Aug 2020 18:12:57 +0100 Subject: [PATCH 12/13] Compat with 8.6.5 (and tighten spans for rule suggestions) --- src/Ide/Plugin/Retrie.hs | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/Ide/Plugin/Retrie.hs b/src/Ide/Plugin/Retrie.hs index 57ecfdd8de..9d50aa2f19 100644 --- a/src/Ide/Plugin/Retrie.hs +++ b/src/Ide/Plugin/Retrie.hs @@ -268,8 +268,7 @@ suggestRuleRewrites :: GHC.Module -> LRuleDecls pass -> [(T.Text, CodeActionKind, RunRetrieParams)] -suggestRuleRewrites originatingFile pos ms_mod (L l (HsRules {rds_rules})) - | pos `isInsideSrcSpan` l = +suggestRuleRewrites originatingFile pos ms_mod (L _ (HsRules {rds_rules})) = concat [ [ let rewrites = [Right $ RuleForward (qualify ms_mod ruleName)] @@ -286,7 +285,13 @@ suggestRuleRewrites originatingFile pos ms_mod (L l (HsRules {rds_rules})) RunRetrieParams {..} ) ] - | L _ (HsRule {rd_name = (L _ (_, rn))}) <- rds_rules, + | L l r <- rds_rules, + pos `isInsideSrcSpan` l, +#if MIN_GHC_API_VERSION(8,8,0) + let HsRule {rd_name = L _ (_, rn)} = r, +#else + let HsRule _ (L _ (_,rn)) _ _ _ _ = r, +#endif let ruleName = unpackFS rn ] suggestRuleRewrites _ _ _ _ = [] @@ -510,9 +515,7 @@ toImportDecl AddImport {..} = GHC.ImportDecl {..} ideclSourceSrc = NoSourceText ideclExt = GHC.noExtField ideclAs = toMod <$> ideclAsString - #if MIN_GHC_API_VERSION(8,10,0) - ideclQualified = if ideclQualifiedBool then GHC.QualifiedPre else GHC.NotQualified #else ideclQualified = ideclQualifiedBool From 366e8b65d583eefb5e84528b73c0a067a4a3862b Mon Sep 17 00:00:00 2001 From: Pepe Iborra <pepeiborra@gmail.com> Date: Sun, 2 Aug 2020 08:13:19 +0100 Subject: [PATCH 13/13] Fix imgur link in README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index fca6598591..6f97385d18 100644 --- a/README.md +++ b/README.md @@ -73,7 +73,7 @@ This is *very* early stage software. - Integration with [retrie](https://hackage.haskell.org/package/retrie) -  +  - Many more (TBD)