This repository was archived by the owner on Oct 7, 2020. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 206
HaRe hie plugin api #1215
Merged
Merged
HaRe hie plugin api #1215
Changes from all commits
Commits
Show all changes
23 commits
Select commit
Hold shift + click to select a range
82ae176
Add Haskell.Ide.Engine.PluginApi.
alanz d71de15
Expose ghc-mod features currently used in HaRe tests
alanz d645924
Add the HIE Monads
alanz 8cb0bca
Merge branch 'master' into hare-hie-plugin-api
alanz ecced4f
Suport HaRe running in IdeGhcM
alanz f5ea4b3
Move setTypecheckedModule into hie-plugin-api
alanz 0455f9e
Tweaks
alanz f9137a3
Add missing module
alanz e47d1dc
WIP, includes list of things actually used by HaRe currently
alanz e1da17d
More narrowing of the plugin api
alanz 416b2c8
Merge remote-tracking branch 'haskell/master' into hare-hie-plugin-api
alanz e113322
Start using ghc-project-types for the plugin API
alanz fbd6860
Only ghc-mod Options and defaultOptions left to decouple
alanz 1d4a52c
Use hie data type BiosOptions instead of ghc-mod Options
alanz 43c3a3b
Update submodules for all GHC versions
alanz 5dcc7f2
Sort out some compiler warnings
alanz 9c51e77
Use hie-bios version of HaRe
alanz c16e343
Bump HaRe submodule to work with GHC 8.2.x
alanz 54a8b61
Update comment.
alanz eb77dc1
Merge branch 'master' into hare-hie-plugin-api
alanz d50a1b0
Apply progress reporting to setTypecheckedModule in its new home
alanz 84b0405
Fix build for GHC 8.2
alanz bf612fc
Fix GHC 8.6.5 build too
alanz File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,238 @@ | ||
{-# LANGUAGE CPP #-} | ||
{-# LANGUAGE DeriveGeneric #-} | ||
{-# LANGUAGE FlexibleContexts #-} | ||
{-# LANGUAGE OverloadedStrings #-} | ||
{-# LANGUAGE ScopedTypeVariables #-} | ||
{-# LANGUAGE TupleSections #-} | ||
{-# LANGUAGE TypeFamilies #-} | ||
-- | This module provides the interface to GHC, mainly for loading | ||
-- modules while updating the module cache. | ||
|
||
module Haskell.Ide.Engine.Ghc | ||
( | ||
setTypecheckedModule | ||
, Diagnostics | ||
, AdditionalErrs | ||
, cabalModuleGraphs | ||
, makeRevRedirMapFunc | ||
) where | ||
|
||
import Bag | ||
import Control.Monad.IO.Class | ||
import Data.IORef | ||
import qualified Data.Map.Strict as Map | ||
import qualified Data.Set as Set | ||
import qualified Data.Text as T | ||
import ErrUtils | ||
import qualified GhcMod.DynFlags as GM | ||
import qualified GhcMod.Error as GM | ||
import qualified GhcMod.Gap as GM | ||
import qualified GhcMod.ModuleLoader as GM | ||
import qualified GhcMod.Monad as GM | ||
import Data.Monoid ((<>)) | ||
import qualified GhcMod.Target as GM | ||
import qualified GhcMod.Types as GM | ||
import qualified GhcMod.Utils as GM | ||
import Haskell.Ide.Engine.MonadFunctions | ||
import Haskell.Ide.Engine.MonadTypes | ||
import Haskell.Ide.Engine.PluginUtils | ||
import System.FilePath | ||
|
||
import DynFlags | ||
import GHC | ||
import IOEnv as G | ||
import HscTypes | ||
import Outputable (renderWithStyle) | ||
|
||
-- --------------------------------------------------------------------- | ||
|
||
type Diagnostics = Map.Map Uri (Set.Set Diagnostic) | ||
type AdditionalErrs = [T.Text] | ||
|
||
-- --------------------------------------------------------------------- | ||
|
||
lspSev :: Severity -> DiagnosticSeverity | ||
lspSev SevWarning = DsWarning | ||
lspSev SevError = DsError | ||
lspSev SevFatal = DsError | ||
lspSev SevInfo = DsInfo | ||
lspSev _ = DsInfo | ||
|
||
-- --------------------------------------------------------------------- | ||
-- type LogAction = DynFlags -> WarnReason -> Severity -> SrcSpan -> PprStyle -> MsgDoc -> IO () | ||
logDiag :: (FilePath -> FilePath) -> IORef AdditionalErrs -> IORef Diagnostics -> LogAction | ||
logDiag rfm eref dref df _reason sev spn style msg = do | ||
eloc <- srcSpan2Loc rfm spn | ||
let msgTxt = T.pack $ renderWithStyle df msg style | ||
case eloc of | ||
Right (Location uri range) -> do | ||
let update = Map.insertWith Set.union uri l | ||
where l = Set.singleton diag | ||
diag = Diagnostic range (Just $ lspSev sev) Nothing (Just "ghcmod") msgTxt Nothing | ||
modifyIORef' dref update | ||
Left _ -> do | ||
modifyIORef' eref (msgTxt:) | ||
return () | ||
|
||
-- --------------------------------------------------------------------- | ||
|
||
-- unhelpfulSrcSpanErr :: T.Text -> IdeError | ||
-- unhelpfulSrcSpanErr err = | ||
-- IdeError PluginError | ||
-- ("Unhelpful SrcSpan" <> ": \"" <> err <> "\"") | ||
-- Null | ||
|
||
-- --------------------------------------------------------------------- | ||
|
||
srcErrToDiag :: MonadIO m | ||
=> DynFlags | ||
-> (FilePath -> FilePath) | ||
-> SourceError -> m (Diagnostics, AdditionalErrs) | ||
srcErrToDiag df rfm se = do | ||
debugm "in srcErrToDiag" | ||
let errMsgs = bagToList $ srcErrorMessages se | ||
processMsg err = do | ||
let sev = Just DsError | ||
unqual = errMsgContext err | ||
st = GM.mkErrStyle' df unqual | ||
msgTxt = T.pack $ renderWithStyle df (pprLocErrMsg err) st | ||
eloc <- srcSpan2Loc rfm $ errMsgSpan err | ||
case eloc of | ||
Right (Location uri range) -> | ||
return $ Right (uri, Diagnostic range sev Nothing (Just "ghcmod") msgTxt Nothing) | ||
Left _ -> return $ Left msgTxt | ||
processMsgs [] = return (Map.empty,[]) | ||
processMsgs (x:xs) = do | ||
res <- processMsg x | ||
(m,es) <- processMsgs xs | ||
case res of | ||
Right (uri, diag) -> | ||
return (Map.insertWith Set.union uri (Set.singleton diag) m, es) | ||
Left e -> return (m, e:es) | ||
processMsgs errMsgs | ||
|
||
-- --------------------------------------------------------------------- | ||
|
||
myWrapper :: GM.IOish m | ||
=> (FilePath -> FilePath) | ||
-> GM.GmlT m () | ||
-> GM.GmlT m (Diagnostics, AdditionalErrs) | ||
myWrapper rfm action = do | ||
env <- getSession | ||
diagRef <- liftIO $ newIORef Map.empty | ||
errRef <- liftIO $ newIORef [] | ||
let setLogger df = df { log_action = logDiag rfm errRef diagRef } | ||
setDeferTypedHoles = setGeneralFlag' Opt_DeferTypedHoles | ||
ghcErrRes msg = (Map.empty, [T.pack msg]) | ||
handlers = errorHandlers ghcErrRes (srcErrToDiag (hsc_dflags env) rfm ) | ||
action' = do | ||
GM.withDynFlags (setLogger . setDeferTypedHoles) action | ||
diags <- liftIO $ readIORef diagRef | ||
errs <- liftIO $ readIORef errRef | ||
return (diags,errs) | ||
GM.gcatches action' handlers | ||
|
||
-- --------------------------------------------------------------------- | ||
|
||
errorHandlers :: (Monad m) => (String -> a) -> (SourceError -> m a) -> [GM.GHandler m a] | ||
errorHandlers ghcErrRes renderSourceError = handlers | ||
where | ||
-- ghc throws GhcException, SourceError, GhcApiError and | ||
-- IOEnvFailure. ghc-mod-core throws GhcModError. | ||
handlers = | ||
[ GM.GHandler $ \(ex :: GM.GhcModError) -> | ||
return $ ghcErrRes (show ex) | ||
, GM.GHandler $ \(ex :: IOEnvFailure) -> | ||
return $ ghcErrRes (show ex) | ||
, GM.GHandler $ \(ex :: GhcApiError) -> | ||
return $ ghcErrRes (show ex) | ||
, GM.GHandler $ \(ex :: SourceError) -> | ||
renderSourceError ex | ||
, GM.GHandler $ \(ex :: GhcException) -> | ||
return $ ghcErrRes $ GM.renderGm $ GM.ghcExceptionDoc ex | ||
, GM.GHandler $ \(ex :: IOError) -> | ||
return $ ghcErrRes (show ex) | ||
-- , GM.GHandler $ \(ex :: GM.SomeException) -> | ||
-- return $ ghcErrRes (show ex) | ||
] | ||
|
||
-- --------------------------------------------------------------------- | ||
|
||
setTypecheckedModule :: Uri -> IdeGhcM (IdeResult (Diagnostics, AdditionalErrs)) | ||
setTypecheckedModule uri = | ||
pluginGetFile "setTypecheckedModule: " uri $ \fp -> do | ||
fileMap <- GM.getMMappedFiles | ||
debugm $ "setTypecheckedModule: file mapping state is: " ++ show fileMap | ||
rfm <- GM.mkRevRedirMapFunc | ||
let | ||
ghcErrRes msg = ((Map.empty, [T.pack msg]),Nothing,Nothing) | ||
progTitle = "Typechecking " <> T.pack (takeFileName fp) | ||
debugm "setTypecheckedModule: before ghc-mod" | ||
-- TODO:AZ: loading this one module may/should trigger loads of any | ||
-- other modules which currently have a VFS entry. Need to make | ||
-- sure that their diagnostics are reported, and their module | ||
-- cache entries are updated. | ||
-- TODO: Are there any hooks we can use to report back on the progress? | ||
((diags', errs), mtm, mpm) <- withIndefiniteProgress progTitle NotCancellable $ GM.gcatches | ||
(GM.getModulesGhc' (myWrapper rfm) fp) | ||
(errorHandlers ghcErrRes (return . ghcErrRes . show)) | ||
debugm "setTypecheckedModule: after ghc-mod" | ||
|
||
canonUri <- canonicalizeUri uri | ||
let diags = Map.insertWith Set.union canonUri Set.empty diags' | ||
diags2 <- case (mpm,mtm) of | ||
(Just pm, Nothing) -> do | ||
debugm $ "setTypecheckedModule: Did get parsed module for: " ++ show fp | ||
cacheModule fp (Left pm) | ||
debugm "setTypecheckedModule: done" | ||
return diags | ||
|
||
(_, Just tm) -> do | ||
debugm $ "setTypecheckedModule: Did get typechecked module for: " ++ show fp | ||
sess <- fmap GM.gmgsSession . GM.gmGhcSession <$> GM.gmsGet | ||
|
||
-- set the session before we cache the module, so that deferred | ||
-- responses triggered by cacheModule can access it | ||
modifyMTS (\s -> s {ghcSession = sess}) | ||
cacheModule fp (Right tm) | ||
debugm "setTypecheckedModule: done" | ||
return diags | ||
|
||
_ -> do | ||
debugm $ "setTypecheckedModule: Didn't get typechecked or parsed module for: " ++ show fp | ||
debugm $ "setTypecheckedModule: errs: " ++ show errs | ||
|
||
failModule fp | ||
|
||
let sev = Just DsError | ||
range = Range (Position 0 0) (Position 1 0) | ||
msgTxt = T.unlines errs | ||
let d = Diagnostic range sev Nothing (Just "ghcmod") msgTxt Nothing | ||
return $ Map.insertWith Set.union canonUri (Set.singleton d) diags | ||
|
||
return $ IdeResultOk (diags2,errs) | ||
|
||
-- --------------------------------------------------------------------- | ||
|
||
cabalModuleGraphs :: IdeGhcM [GM.GmModuleGraph] | ||
cabalModuleGraphs = doCabalModuleGraphs | ||
where | ||
doCabalModuleGraphs :: (GM.IOish m) => GM.GhcModT m [GM.GmModuleGraph] | ||
doCabalModuleGraphs = do | ||
crdl <- GM.cradle | ||
case GM.cradleCabalFile crdl of | ||
Just _ -> do | ||
mcs <- GM.cabalResolvedComponents | ||
let graph = map GM.gmcHomeModuleGraph $ Map.elems mcs | ||
return graph | ||
Nothing -> return [] | ||
|
||
-- --------------------------------------------------------------------- | ||
|
||
makeRevRedirMapFunc :: IdeGhcM (FilePath -> FilePath) | ||
makeRevRedirMapFunc = make | ||
where | ||
make :: (GM.IOish m) => GM.GhcModT m (FilePath -> FilePath) | ||
make = GM.mkRevRedirMapFunc | ||
|
||
-- --------------------------------------------------------------------- |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,71 @@ | ||
-- | This module provides an API that software intented to be | ||
-- integrated into HIE can use, so that they can make use of the | ||
-- shared BIOS features. | ||
|
||
{- | ||
-- Stuff used in HaRe currently | ||
Options(..) | ||
defaultOptions | ||
GmModuleGraph(..) | ||
ModulePath(..) | ||
GmComponent(..) | ||
GmComponentType(..) | ||
|
||
CachedInfo(..) | ||
HasGhcModuleCache(..) | ||
IdeGhcM | ||
|
||
cabalModuleGraphs | ||
filePathToUri | ||
makeRevRedirMapFunc | ||
|
||
MonadIO(..) | ||
ifCachedModule | ||
runIdeGhcMBare | ||
setTypecheckedModule | ||
-} | ||
|
||
|
||
module Haskell.Ide.Engine.PluginApi | ||
( | ||
-- ** Re-exported from ghc-mod via ghc-project-types | ||
GP.GmModuleGraph(..) | ||
, GP.ModulePath(..) | ||
, GP.GmComponent(..) | ||
, GP.GmComponentType(..) | ||
|
||
-- * IDE monads | ||
, HIE.IdeState(..) | ||
, HIE.IdeGhcM | ||
, HIE.runIdeGhcM | ||
, HIE.runIdeGhcMBare | ||
, HIE.IdeM | ||
, HIE.runIdeM | ||
, HIE.IdeDeferM | ||
, HIE.MonadIde | ||
, HIE.iterT | ||
, HIE.LiftsToGhc(..) | ||
, HIE.HasGhcModuleCache(..) | ||
, HIE.cabalModuleGraphs | ||
, HIE.makeRevRedirMapFunc | ||
|
||
-- * Using the HIE module cache etc | ||
, HIE.setTypecheckedModule | ||
, HIE.Diagnostics | ||
, HIE.AdditionalErrs | ||
, LSP.filePathToUri | ||
, HIE.ifCachedModule | ||
, HIE.CachedInfo(..) | ||
|
||
-- * used for tests in HaRe | ||
, HIE.BiosLogLevel(..) | ||
, HIE.BiosOptions(..) | ||
, HIE.defaultOptions | ||
) where | ||
|
||
import qualified GhcProject.Types as GP | ||
import qualified Haskell.Ide.Engine.Ghc as HIE | ||
import qualified Haskell.Ide.Engine.GhcModuleCache as HIE (CachedInfo(..),HasGhcModuleCache(..)) | ||
import qualified Haskell.Ide.Engine.ModuleCache as HIE (ifCachedModule) | ||
import qualified Haskell.Ide.Engine.PluginsIdeMonads as HIE | ||
import qualified Language.Haskell.LSP.Types as LSP ( filePathToUri ) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this meant to be a one-stop import for all plugins? If so should this also export the
withCachedModule
etc. functions and other helpers?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@bubba the initial intention is to create as narrow as possible an API to support what HaRe needs, as a way to end up with a real world requirement on hie-bios.
And I think there possibly still needs to be a discussion about what the layers are for a plugin.
My mental model for an external tool XXX currently has it something like
So this file is currently aiming at the last of those, which is effectively the hie-bios. I think.
But this whole layering is something that needs discussion, I think.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Perhaps we could view HIE as having two parts:
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@michaelpj Do you mean LSP server in your comment above? The client is the IDE that talks to hie.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Er yes, sorry 🤦♂️
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
And the intended breakdown you describe is the driver behind having
hie-plugin-api
which should play the role described in your first bullet point, and does so for HaRe using this branch and https://github.com/alanz/HaRe/tree/hie-plugin-api.But I can also envisage another layer, where a third party could package something like HaRe for hie, which can then be simply included in the plugin list in
app/MainHie.hs
and be usable.So my original discussion point is whether we need to distinguish between the 'upper' and 'lower' APIs. Especially as I think there is likely to be some level of crossover in it.