Skip to content

Commit 535f89b

Browse files
committed
Merge upstream master
2 parents 97682e7 + 704ed3c commit 535f89b

32 files changed

+547
-235
lines changed

README.md

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,7 @@
1414
[appveyor]: https://ci.appveyor.com/project/Bubba/haskell-ide-engine-74xec
1515

1616

17-
This project aims to be __the universal interface__ to __a growing number of Haskell tools__, providing a __full-featured and easy to query backend__ for editors and IDEs that require Haskell-specific functionality.
18-
19-
__We are currently focusing on using the [Language Server Protocol](https://github.com/Microsoft/language-server-protocol/blob/master/protocol.md) as the interface via which
20-
we talk to clients.__
17+
This project aims to be __the universal interface__ to __a growing number of Haskell tools__, providing a __fully-featured [Language Server Protocol](https://github.com/Microsoft/language-server-protocol/blob/master/protocol.md) server__ for editors and IDEs that require Haskell-specific functionality.
2118

2219
- [Haskell IDE Engine (HIE)](#haskell-ide-engine-hie)
2320
- [Features](#features)
@@ -545,9 +542,10 @@ Then issue `:CocConfig` and add the following to your Coc config file.
545542
"haskell": {
546543
"command": "hie-wrapper",
547544
"rootPatterns": [
548-
".stack.yaml",
549-
"cabal.config",
550-
"package.yaml"
545+
"*.cabal",
546+
"stack.yaml",
547+
"cabal.project",
548+
"package.yaml",
551549
],
552550
"filetypes": [
553551
"hs",

app/MainHie.hs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ import Haskell.Ide.Engine.Plugin.Package
3838
import Haskell.Ide.Engine.Plugin.Pragmas
3939
import Haskell.Ide.Engine.Plugin.Floskell
4040
import Haskell.Ide.Engine.Plugin.Generic
41+
import Haskell.Ide.Engine.Plugin.GhcMod
4142

4243
-- ---------------------------------------------------------------------
4344

@@ -59,6 +60,7 @@ plugins includeExamples = pluginDescToIdePlugins allPlugins
5960
, pragmasDescriptor "pragmas"
6061
, floskellDescriptor "floskell"
6162
, genericDescriptor "generic"
63+
, ghcmodDescriptor "ghcmod"
6264
]
6365
examplePlugins =
6466
[example2Descriptor "eg2"

haskell-ide-engine.cabal

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ library
3737
Haskell.Ide.Engine.Plugin.Package.Compat
3838
Haskell.Ide.Engine.Plugin.Pragmas
3939
Haskell.Ide.Engine.Plugin.Generic
40+
Haskell.Ide.Engine.Plugin.GhcMod
4041
Haskell.Ide.Engine.Scheduler
4142
Haskell.Ide.Engine.Support.FromHaRe
4243
Haskell.Ide.Engine.Support.Hoogle
@@ -180,6 +181,7 @@ test-suite unit-test
180181
DiffSpec
181182
ExtensibleStateSpec
182183
GenericPluginSpec
184+
GhcModPluginSpec
183185
-- HaRePluginSpec
184186
HooglePluginSpec
185187
JsonSpec
@@ -245,6 +247,8 @@ test-suite plugin-dispatcher-test
245247
main-is: Main.hs
246248
build-depends: base
247249
, data-default
250+
, directory
251+
, filepath
248252
, haskell-ide-engine
249253
, haskell-lsp-types
250254
, hie-plugin-api
@@ -287,7 +291,7 @@ test-suite func-test
287291
, data-default
288292
, directory
289293
, filepath
290-
, lsp-test >= 0.9.0.0
294+
, lsp-test >= 0.10.0.0
291295
, haskell-ide-engine
292296
, haskell-lsp-types == 0.19.*
293297
, haskell-lsp == 0.19.*

hie-plugin-api/Haskell/Ide/Engine/Cradle.hs

Lines changed: 96 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,19 +17,20 @@ import Distribution.Helper (Package, projectPackages, pUnits,
1717
import Distribution.Helper.Discover (findProjects, getDefaultDistDir)
1818
import Data.Char (toLower)
1919
import Data.Function ((&))
20-
import Data.List (isPrefixOf, isInfixOf)
20+
import Data.List (isPrefixOf, isInfixOf, sortOn, find)
2121
import qualified Data.List.NonEmpty as NonEmpty
2222
import Data.List.NonEmpty (NonEmpty)
2323
import qualified Data.Map as M
24-
import Data.List (sortOn, find)
2524
import Data.Maybe (listToMaybe, mapMaybe, isJust)
2625
import Data.Ord (Down(..))
2726
import Data.String (IsString(..))
27+
import qualified Data.Text as T
2828
import Data.Foldable (toList)
29-
import Control.Exception (IOException, try)
29+
import Control.Exception
3030
import System.FilePath
3131
import System.Directory (getCurrentDirectory, canonicalizePath, findExecutable)
3232
import System.Exit
33+
import System.Process (readCreateProcessWithExitCode, shell)
3334

3435
-- | Find the cradle that the given File belongs to.
3536
--
@@ -57,6 +58,98 @@ isStackCradle = (`elem` ["stack", "Cabal-Helper-Stack", "Cabal-Helper-Stack-None
5758
. BIOS.actionName
5859
. BIOS.cradleOptsProg
5960

61+
-- | Check if the given cradle is a cabal cradle.
62+
-- This might be used to determine the GHC version to use on the project.
63+
-- If it is a stack-cradle, we have to use `stack path --compiler-exe`
64+
-- otherwise we may ask `ghc` directly what version it is.
65+
isCabalCradle :: Cradle -> Bool
66+
isCabalCradle =
67+
(`elem`
68+
["cabal"
69+
, "Cabal-Helper-Cabal-V1"
70+
, "Cabal-Helper-Cabal-V2"
71+
, "Cabal-Helper-Cabal-V1-Dir"
72+
, "Cabal-Helper-Cabal-V2-Dir"
73+
, "Cabal-Helper-Cabal-None"
74+
]
75+
)
76+
. BIOS.actionName
77+
. BIOS.cradleOptsProg
78+
79+
-- | Execute @ghc@ that is based on the given cradle.
80+
-- Output must be a single line. If an error is raised, e.g. the command
81+
-- failed, a @Nothing@ is returned.
82+
-- The exact error is written to logs.
83+
--
84+
-- E.g. for a stack cradle, we use `stack ghc` and for a cabal cradle
85+
-- we are taking the @ghc@ that is on the path.
86+
execProjectGhc :: Cradle -> [String] -> IO (Maybe String)
87+
execProjectGhc crdl args = do
88+
isStackInstalled <- isJust <$> findExecutable "stack"
89+
-- isCabalInstalled <- isJust <$> findExecutable "cabal"
90+
ghcOutput <- if isStackCradle crdl && isStackInstalled
91+
then do
92+
logm "Use Stack GHC"
93+
catch (Just <$> tryCommand stackCmd) $ \(_ :: IOException) -> do
94+
errorm $ "Command `" ++ stackCmd ++"` failed."
95+
execWithGhc
96+
-- The command `cabal v2-exec -v0 ghc` only works if the project has been
97+
-- built already.
98+
-- This command must work though before the project is build.
99+
-- Therefore, fallback to "ghc" on the path.
100+
--
101+
-- else if isCabalCradle crdl && isCabalInstalled then do
102+
-- let cmd = "cabal v2-exec -v0 ghc -- " ++ unwords args
103+
-- catch (Just <$> tryCommand cmd) $ \(_ ::IOException) -> do
104+
-- errorm $ "Command `" ++ cmd ++ "` failed."
105+
-- return Nothing
106+
else do
107+
logm "Use Plain GHC"
108+
execWithGhc
109+
debugm $ "GHC Output: \"" ++ show ghcOutput ++ "\""
110+
return ghcOutput
111+
where
112+
stackCmd = "stack ghc -- " ++ unwords args
113+
plainCmd = "ghc " ++ unwords args
114+
115+
execWithGhc =
116+
catch (Just <$> tryCommand plainCmd) $ \(_ :: IOException) -> do
117+
errorm $ "Command `" ++ plainCmd ++"` failed."
118+
return Nothing
119+
120+
tryCommand :: String -> IO String
121+
tryCommand cmd = do
122+
(code, sout, serr) <- readCreateProcessWithExitCode (shell cmd) ""
123+
case code of
124+
ExitFailure e -> do
125+
let errmsg = concat
126+
[ "`"
127+
, cmd
128+
, "`: Exit failure: "
129+
, show e
130+
, ", stdout: "
131+
, sout
132+
, ", stderr: "
133+
, serr
134+
]
135+
errorm errmsg
136+
throwIO $ userError errmsg
137+
138+
ExitSuccess -> return $ T.unpack . T.strip . head . T.lines $ T.pack sout
139+
140+
141+
-- | Get the directory of the libdir based on the project ghc.
142+
getProjectGhcLibDir :: Cradle -> IO (Maybe FilePath)
143+
getProjectGhcLibDir crdl =
144+
execProjectGhc crdl ["--print-libdir"] >>= \case
145+
Nothing -> do
146+
logm "Could not obtain the libdir."
147+
return Nothing
148+
mlibdir -> return mlibdir
149+
150+
-- ---------------------------------------------------------------------
151+
152+
60153
{- | Finds a Cabal v2-project, Cabal v1-project or a Stack project
61154
relative to the given FilePath.
62155
Cabal v2-project and Stack have priority over Cabal v1-project.

hie-plugin-api/Haskell/Ide/Engine/MonadFunctions.hs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ warningm :: MonadIO m => String -> m ()
3737
warningm s = liftIO $ warningM "hie" s
3838

3939
errorm :: MonadIO m => String -> m ()
40-
errorm s = liftIO $ warningM "hie" s
40+
errorm s = liftIO $ errorM "hie" s
4141

4242
-- ---------------------------------------------------------------------
4343
-- Extensible state, based on

hie-plugin-api/Haskell/Ide/Engine/PluginsIdeMonads.hs

Lines changed: 21 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -125,9 +125,8 @@ import Data.Typeable ( Typeable )
125125

126126
import System.Directory
127127
import GhcMonad
128-
import qualified HIE.Bios.Ghc.Api as BIOS
129128
import GHC.Generics
130-
import GHC ( HscEnv )
129+
import GHC ( HscEnv, runGhcT )
131130
import Exception
132131

133132
import Haskell.Ide.Engine.Compat
@@ -350,10 +349,10 @@ getDiagnosticProvidersConfig c = Map.fromList [("applyrefact",hlintOn c)
350349
type IdeGhcM = GhcT IdeM
351350

352351
-- | Run an IdeGhcM with Cradle found from the current directory
353-
runIdeGhcM :: IdePlugins -> Maybe (Core.LspFuncs Config) -> TVar IdeState -> IdeGhcM a -> IO a
354-
runIdeGhcM plugins mlf stateVar f = do
355-
env <- IdeEnv <$> pure mlf <*> getProcessID <*> pure plugins
356-
flip runReaderT stateVar $ flip runReaderT env $ BIOS.withGhcT f
352+
runIdeGhcM :: Maybe FilePath -> IdePlugins -> Core.LspFuncs Config -> TVar IdeState -> IdeGhcM a -> IO a
353+
runIdeGhcM mlibdir plugins lf stateVar f = do
354+
env <- IdeEnv <$> pure lf <*> getProcessID <*> pure plugins
355+
flip runReaderT stateVar $ flip runReaderT env $ runGhcT mlibdir f
357356

358357
-- | A computation that is deferred until the module is cached.
359358
-- Note that the module may not typecheck, in which case 'UriCacheFailed' is passed
@@ -363,14 +362,14 @@ type IdeDeferM = FreeT Defer IdeM
363362
type IdeM = ReaderT IdeEnv (MultiThreadState IdeState)
364363

365364
-- | Run an IdeM
366-
runIdeM :: IdePlugins -> Maybe (Core.LspFuncs Config) -> TVar IdeState -> IdeM a -> IO a
367-
runIdeM plugins mlf stateVar f = do
368-
env <- IdeEnv <$> pure mlf <*> getProcessID <*> pure plugins
365+
runIdeM :: IdePlugins -> Core.LspFuncs Config -> TVar IdeState -> IdeM a -> IO a
366+
runIdeM plugins lf stateVar f = do
367+
env <- IdeEnv <$> pure lf <*> getProcessID <*> pure plugins
369368
-- TODO: AZ run a single ReaderT, with a composite R.
370369
flip runReaderT stateVar $ runReaderT f env
371370

372371
data IdeEnv = IdeEnv
373-
{ ideEnvLspFuncs :: Maybe (Core.LspFuncs Config)
372+
{ ideEnvLspFuncs :: Core.LspFuncs Config
374373
-- | The pid of this instance of hie
375374
, ideEnvPidCache :: Int
376375
, idePlugins :: IdePlugins
@@ -390,18 +389,12 @@ instance MonadIde IdeGhcM where
390389
getIdeEnv = lift ask
391390

392391
getRootPath :: MonadIde m => m (Maybe FilePath)
393-
getRootPath = do
394-
mlf <- ideEnvLspFuncs <$> getIdeEnv
395-
case mlf of
396-
Just lf -> return (Core.rootPath lf)
397-
Nothing -> return Nothing
392+
getRootPath = Core.rootPath . ideEnvLspFuncs <$> getIdeEnv
398393

399394
getVirtualFile :: (MonadIde m, MonadIO m) => Uri -> m (Maybe VirtualFile)
400395
getVirtualFile uri = do
401-
mlf <- ideEnvLspFuncs <$> getIdeEnv
402-
case mlf of
403-
Just lf -> liftIO $ Core.getVirtualFileFunc lf (toNormalizedUri uri)
404-
Nothing -> return Nothing
396+
lf <- ideEnvLspFuncs <$> getIdeEnv
397+
liftIO $ Core.getVirtualFileFunc lf (toNormalizedUri uri)
405398

406399
-- | Worker function for persistVirtualFile without monad constraints.
407400
--
@@ -413,19 +406,15 @@ persistVirtualFile' lf uri = Core.persistVirtualFileFunc lf (toNormalizedUri uri
413406

414407
reverseFileMap :: (MonadIde m, MonadIO m) => m (FilePath -> FilePath)
415408
reverseFileMap = do
416-
mlf <- ideEnvLspFuncs <$> getIdeEnv
417-
case mlf of
418-
Just lf -> liftIO $ Core.reverseFileMapFunc lf
419-
Nothing -> return id
409+
lf <- ideEnvLspFuncs <$> getIdeEnv
410+
liftIO $ Core.reverseFileMapFunc lf
420411

421412
-- | Get the location of the virtual file persisted to the file system associated
422413
-- to the given Uri.
423414
getPersistedFile :: (MonadIde m, MonadIO m) => Uri -> m (Maybe FilePath)
424415
getPersistedFile uri = do
425-
mlf <- ideEnvLspFuncs <$> getIdeEnv
426-
case mlf of
427-
Just lf -> liftIO $ persistVirtualFile' lf uri
428-
Nothing -> return $ uriToFilePath uri
416+
lf <- ideEnvLspFuncs <$> getIdeEnv
417+
liftIO $ persistVirtualFile' lf uri
429418

430419
-- | Execute an action on the temporary file associated to the given FilePath.
431420
-- If the file is not in the current Virtual File System, the given action is not executed
@@ -439,17 +428,11 @@ withMappedFile fp m k = do
439428

440429
getConfig :: (MonadIde m, MonadIO m) => m Config
441430
getConfig = do
442-
mlf <- ideEnvLspFuncs <$> getIdeEnv
443-
case mlf of
444-
Just lf -> fromMaybe def <$> liftIO (Core.config lf)
445-
Nothing -> return def
431+
lf <- ideEnvLspFuncs <$> getIdeEnv
432+
fromMaybe def <$> liftIO (Core.config lf)
446433

447434
getClientCapabilities :: MonadIde m => m ClientCapabilities
448-
getClientCapabilities = do
449-
mlf <- ideEnvLspFuncs <$> getIdeEnv
450-
case mlf of
451-
Just lf -> return (Core.clientCapabilities lf)
452-
Nothing -> return def
435+
getClientCapabilities = Core.clientCapabilities . ideEnvLspFuncs <$> getIdeEnv
453436

454437
getPlugins :: MonadIde m => m IdePlugins
455438
getPlugins = idePlugins <$> getIdeEnv
@@ -462,10 +445,7 @@ withProgress :: (MonadIde m , MonadIO m, MonadBaseControl IO m)
462445
-> ((Core.Progress -> IO ()) -> m a) -> m a
463446
withProgress t c f = do
464447
lf <- ideEnvLspFuncs <$> getIdeEnv
465-
let mWp = Core.withProgress <$> lf
466-
case mWp of
467-
Nothing -> f (const $ return ())
468-
Just wp -> control $ \run -> wp t c $ \update -> run (f update)
448+
control $ \run -> Core.withProgress lf t c $ \update -> run (f update)
469449

470450

471451
-- | 'withIndefiniteProgress' @title cancellable f@ is the same as the 'withProgress' but for tasks
@@ -474,10 +454,7 @@ withIndefiniteProgress :: (MonadIde m, MonadBaseControl IO m)
474454
=> T.Text -> Core.ProgressCancellable -> m a -> m a
475455
withIndefiniteProgress t c f = do
476456
lf <- ideEnvLspFuncs <$> getIdeEnv
477-
let mWp = Core.withIndefiniteProgress <$> lf
478-
case mWp of
479-
Nothing -> f
480-
Just wp -> control $ \run -> wp t c (run f)
457+
control $ \run -> Core.withIndefiniteProgress lf t c (run f)
481458

482459
data IdeState = IdeState
483460
{ moduleCache :: !GhcModuleCache

hie-plugin-api/hie-plugin-api.cabal

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ library
5656
, unliftio
5757
, monad-control
5858
, mtl
59+
, process
5960
, stm
6061
, syb
6162
, text

0 commit comments

Comments
 (0)