Skip to content

Commit 94f8009

Browse files
committed
Break out HoverProvider into separate handler config
1 parent 3088e6d commit 94f8009

File tree

8 files changed

+177
-69
lines changed

8 files changed

+177
-69
lines changed

exe/Main.hs

Lines changed: 82 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,26 @@
11
-- Copyright (c) 2019 The DAML Authors. All rights reserved.
22
-- SPDX-License-Identifier: Apache-2.0
33
{-# OPTIONS_GHC -Wno-dodgy-imports #-} -- GHC no longer exports def in GHC 8.6 and above
4+
{-# LANGUAGE DeriveGeneric #-}
5+
{-# LANGUAGE OverloadedStrings #-}
46
{-# LANGUAGE RecordWildCards #-}
5-
{-# LANGUAGE ViewPatterns #-}
67
{-# LANGUAGE TupleSections #-}
7-
{-# LANGUAGE OverloadedStrings #-}
8+
{-# LANGUAGE TypeFamilies #-}
9+
{-# LANGUAGE ViewPatterns #-}
810

911
module Main(main) where
1012

1113
import Arguments
1214
import Control.Concurrent.Extra
15+
import Control.DeepSeq (NFData)
1316
import Control.Exception
1417
import Control.Monad.Extra
1518
import Control.Monad.IO.Class
19+
import Data.Binary (Binary)
1620
import Data.Default
21+
import Data.Dynamic (Typeable)
22+
import qualified Data.HashSet as HashSet
23+
import Data.Hashable (Hashable)
1724
import Data.List.Extra
1825
import qualified Data.Map.Strict as Map
1926
import Data.Maybe
@@ -34,16 +41,21 @@ import Development.IDE.Types.Diagnostics
3441
import Development.IDE.Types.Location
3542
import Development.IDE.Types.Logger
3643
import Development.IDE.Types.Options
37-
import Development.Shake (Action, action)
44+
import Development.Shake (Action, RuleResult, Rules, action, doesFileExist, need)
3845
import GHC hiding (def)
46+
import GHC.Generics (Generic)
47+
-- import qualified GHC.Paths
3948
import HIE.Bios
40-
import Ide.Plugin.Formatter
49+
import HIE.Bios.Cradle
50+
import HIE.Bios.Types
51+
import Ide.Plugin
4152
import Ide.Plugin.Config
53+
-- import Ide.Plugin.Formatter
4254
import Language.Haskell.LSP.Messages
4355
import Language.Haskell.LSP.Types (LspId(IdInt))
4456
import Linker
45-
import qualified Data.HashSet as HashSet
46-
import System.Directory.Extra as IO
57+
import qualified System.Directory.Extra as IO
58+
-- import System.Environment
4759
import System.Exit
4860
import System.FilePath
4961
import System.IO
@@ -70,6 +82,7 @@ idePlugins includeExample
7082
CodeAction.plugin <>
7183
formatterPlugins [("ormolu", Ormolu.provider)
7284
,("floskell", Floskell.provider)] <>
85+
hoverPlugins [Example.hover, Example2.hover] <>
7386
if includeExample then Example.plugin <> Example2.plugin
7487
else mempty
7588

@@ -89,9 +102,9 @@ main = do
89102
let logger p = Logger $ \pri msg -> when (pri >= p) $ withLock lock $
90103
T.putStrLn $ T.pack ("[" ++ upper (show pri) ++ "] ") <> msg
91104

92-
whenJust argsCwd setCurrentDirectory
105+
whenJust argsCwd IO.setCurrentDirectory
93106

94-
dir <- getCurrentDirectory
107+
dir <- IO.getCurrentDirectory
95108

96109
let plugins = idePlugins argsExamplePlugin
97110

@@ -102,22 +115,21 @@ main = do
102115
runLanguageServer def (pluginHandler plugins) getInitialConfig getConfigFromNotification $ \getLspId event vfs caps -> do
103116
t <- t
104117
hPutStrLn stderr $ "Started LSP server in " ++ showDuration t
105-
-- very important we only call loadSession once, and it's fast, so just do it before starting
106-
session <- loadSession dir
107-
let options = (defaultIdeOptions $ return session)
118+
let options = (defaultIdeOptions $ loadSession dir)
108119
{ optReportProgress = clientSupportsProgress caps
109120
, optShakeProfiling = argsShakeProfiling
110121
}
111122
debouncer <- newAsyncDebouncer
112-
initialise caps (mainRule >> pluginRules plugins >> action kick) getLspId event (logger minBound) debouncer options vfs
123+
initialise caps (loadGhcSessionIO >> mainRule >> pluginRules plugins >> action kick)
124+
getLspId event (logger minBound) debouncer options vfs
113125
else do
114126
putStrLn $ "(haskell-language-server)Ghcide setup tester in " ++ dir ++ "."
115127
putStrLn "Report bugs at https://github.com/haskell/haskell-language-server/issues"
116128

117129
putStrLn $ "\nStep 1/6: Finding files to test in " ++ dir
118130
files <- expandFiles (argFiles ++ ["." | null argFiles])
119131
-- LSP works with absolute file paths, so try and behave similarly
120-
files <- nubOrd <$> mapM canonicalizePath files
132+
files <- nubOrd <$> mapM IO.canonicalizePath files
121133
putStrLn $ "Found " ++ show (length files) ++ " files"
122134

123135
putStrLn "\nStep 2/6: Looking for hie.yaml files that control setup"
@@ -131,7 +143,8 @@ main = do
131143
cradle <- maybe (loadImplicitCradle $ addTrailingPathSeparator dir) loadCradle x
132144
when (isNothing x) $ print cradle
133145
putStrLn $ "\nStep 4/6, Cradle " ++ show i ++ "/" ++ show n ++ ": Loading GHC Session"
134-
cradleToSession cradle
146+
opts <- getComponentOptions cradle
147+
createSession opts
135148

136149
putStrLn "\nStep 5/6: Initializing the IDE"
137150
vfs <- makeVFSHandle
@@ -144,7 +157,7 @@ main = do
144157
let options =
145158
(defaultIdeOptions $ return $ return . grab)
146159
{ optShakeProfiling = argsShakeProfiling }
147-
ide <- initialise def mainRule (pure $ IdInt 0) (showEvent lock) (logger Info) noopDebouncer options vfs
160+
ide <- initialise def (loadGhcSessionIO >> mainRule) (pure $ IdInt 0) (showEvent lock) (logger Info) noopDebouncer options vfs
148161

149162
putStrLn "\nStep 6/6: Type checking the files"
150163
setFilesOfInterest ide $ HashSet.fromList $ map toNormalizedFilePath files
@@ -166,7 +179,7 @@ expandFiles = concatMapM $ \x -> do
166179
let recurse "." = True
167180
recurse x | "." `isPrefixOf` takeFileName x = False -- skip .git etc
168181
recurse x = takeFileName x `notElem` ["dist","dist-newstyle"] -- cabal directories
169-
files <- filter (\x -> takeExtension x `elem` [".hs",".lhs"]) <$> listFilesInside (return . recurse) x
182+
files <- filter (\x -> takeExtension x `elem` [".hs",".lhs"]) <$> IO.listFilesInside (return . recurse) x
170183
when (null files) $
171184
fail $ "Couldn't find any .hs/.lhs files inside directory: " ++ x
172185
return files
@@ -185,15 +198,42 @@ showEvent lock (EventFileDiagnostics (toNormalizedFilePath -> file) diags) =
185198
showEvent lock e = withLock lock $ print e
186199

187200

188-
cradleToSession :: Cradle a -> IO HscEnvEq
189-
cradleToSession cradle = do
190-
cradleRes <- getCompilerOptions "" cradle
191-
opts <- case cradleRes of
201+
-- Rule type for caching GHC sessions.
202+
type instance RuleResult GetHscEnv = HscEnvEq
203+
204+
data GetHscEnv = GetHscEnv
205+
{ hscenvOptions :: [String] -- componentOptions from hie-bios
206+
, hscenvDependencies :: [FilePath] -- componentDependencies from hie-bios
207+
}
208+
deriving (Eq, Show, Typeable, Generic)
209+
instance Hashable GetHscEnv
210+
instance NFData GetHscEnv
211+
instance Binary GetHscEnv
212+
213+
214+
loadGhcSessionIO :: Rules ()
215+
loadGhcSessionIO =
216+
-- This rule is for caching the GHC session. E.g., even when the cabal file
217+
-- changed, if the resulting flags did not change, we would continue to use
218+
-- the existing session.
219+
defineNoFile $ \(GetHscEnv opts deps) ->
220+
liftIO $ createSession $ ComponentOptions opts deps
221+
222+
223+
getComponentOptions :: Cradle a -> IO ComponentOptions
224+
getComponentOptions cradle = do
225+
let showLine s = putStrLn ("> " ++ s)
226+
cradleRes <- runCradle (cradleOptsProg cradle) showLine ""
227+
case cradleRes of
192228
CradleSuccess r -> pure r
193229
CradleFail err -> throwIO err
194230
-- TODO Rather than failing here, we should ignore any files that use this cradle.
195231
-- That will require some more changes.
196232
CradleNone -> fail "'none' cradle is not yet supported"
233+
234+
235+
createSession :: ComponentOptions -> IO HscEnvEq
236+
createSession opts = do
197237
libdir <- getLibdir
198238
env <- runGhc (Just libdir) $ do
199239
_targets <- initSession opts
@@ -202,19 +242,34 @@ cradleToSession cradle = do
202242
newHscEnvEq env
203243

204244

205-
loadSession :: FilePath -> IO (FilePath -> Action HscEnvEq)
206-
loadSession dir = do
245+
cradleToSession :: Maybe FilePath -> Cradle a -> Action HscEnvEq
246+
cradleToSession mbYaml cradle = do
247+
cmpOpts <- liftIO $ getComponentOptions cradle
248+
let opts = componentOptions cmpOpts
249+
deps = componentDependencies cmpOpts
250+
deps' = case mbYaml of
251+
-- For direct cradles, the hie.yaml file itself must be watched.
252+
Just yaml | isDirectCradle cradle -> yaml : deps
253+
_ -> deps
254+
existingDeps <- filterM doesFileExist deps'
255+
need existingDeps
256+
useNoFile_ $ GetHscEnv opts deps
257+
258+
259+
loadSession :: FilePath -> Action (FilePath -> Action HscEnvEq)
260+
loadSession dir = liftIO $ do
207261
cradleLoc <- memoIO $ \v -> do
208262
res <- findCradle v
209263
-- Sometimes we get C:, sometimes we get c:, and sometimes we get a relative path
210264
-- try and normalise that
211265
-- e.g. see https://github.com/digital-asset/ghcide/issues/126
212-
res' <- traverse makeAbsolute res
266+
res' <- traverse IO.makeAbsolute res
213267
return $ normalise <$> res'
214-
session <- memoIO $ \file -> do
215-
c <- maybe (loadImplicitCradle $ addTrailingPathSeparator dir) loadCradle file
216-
cradleToSession c
217-
return $ \file -> liftIO $ session =<< cradleLoc file
268+
let session :: Maybe FilePath -> Action HscEnvEq
269+
session file = do
270+
c <- liftIO $ maybe (loadImplicitCradle $ addTrailingPathSeparator dir) loadCradle file
271+
cradleToSession file c
272+
return $ \file -> session =<< liftIO (cradleLoc file)
218273

219274

220275
-- | Memoize an IO function, with the characteristics:

ghcide

haskell-language-server.cabal

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ library
6262
, haskell-lsp == 0.20.*
6363
, hie-bios >= 0.4
6464
, hslogger
65+
, lens
6566
, optparse-simple
6667
, process
6768
, regex-tdfa >= 1.3.1.0
@@ -106,8 +107,10 @@ executable haskell-language-server
106107

107108
build-depends:
108109
base >=4.7 && <5
110+
, binary
109111
, containers
110112
, data-default
113+
, deepseq
111114
, extra
112115
, filepath
113116
--------------------------------------------------------------
@@ -121,6 +124,7 @@ executable haskell-language-server
121124
, ghc-paths
122125
, ghcide
123126
, gitrev
127+
, hashable
124128
, haskell-lsp
125129
, hie-bios >= 0.4
126130
, haskell-language-server

src/Ide/Plugin.hs

Lines changed: 70 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,38 @@
11
{-# LANGUAGE GADTs #-}
2+
{-# LANGUAGE OverloadedStrings #-}
3+
{-# LANGUAGE RecordWildCards #-}
24
{-# LANGUAGE ScopedTypeVariables #-}
35

46
module Ide.Plugin
57
(
68
asGhcIdePlugin
9+
, formatterPlugins
10+
, hoverPlugins
711
) where
812

9-
import Data.Aeson hiding (defaultOptions)
13+
import Control.Lens ( (^.) )
14+
import Data.Either
15+
import Data.Maybe
1016
import qualified Data.Map as Map
11-
import qualified Data.Set as S
12-
import Data.String
1317
import qualified Data.Text as T
14-
import Data.Typeable
18+
import Development.IDE.Core.FileStore
1519
import Development.IDE.Core.Rules
20+
import Development.IDE.LSP.Server
21+
import Development.IDE.Plugin
1622
import Development.IDE.Types.Diagnostics as D
1723
import Development.IDE.Types.Location
18-
import Language.Haskell.LSP.Types
19-
import Text.Regex.TDFA.Text()
20-
import Development.IDE.Plugin
24+
import Development.Shake hiding ( Diagnostic )
2125
import Ide.Plugin.Config
26+
import Ide.Plugin.Formatter
2227
import Ide.Types
28+
import qualified Language.Haskell.LSP.Core as LSP
29+
import Language.Haskell.LSP.Messages
30+
import Text.Regex.TDFA.Text()
31+
32+
import qualified Language.Haskell.LSP.Core as LSP
33+
import Language.Haskell.LSP.Messages
34+
import Language.Haskell.LSP.Types
35+
import Language.Haskell.LSP.Types.Lens as L hiding (formatting, rangeFormatting)
2336

2437
-- ---------------------------------------------------------------------
2538

@@ -32,3 +45,53 @@ asGhcIdePlugin _ = Plugin mempty mempty
3245
-- First strp will be to bring the machinery from Ide.Plugin.Formatter over.
3346

3447
-- ---------------------------------------------------------------------
48+
49+
hoverPlugins :: [HoverProvider] -> Plugin Config
50+
hoverPlugins hs = Plugin hoverRules (hoverHandlers hs)
51+
52+
hoverRules :: Rules ()
53+
hoverRules = mempty
54+
55+
hoverHandlers :: [HoverProvider] -> PartialHandlers Config
56+
hoverHandlers hps = PartialHandlers $ \WithMessage{..} x ->
57+
return x{LSP.hoverHandler = withResponse RspHover (makeHover hps)}
58+
59+
makeHover :: [HoverProvider]
60+
-> LSP.LspFuncs Config -> IdeState
61+
-> TextDocumentPositionParams
62+
-> IO (Either ResponseError (Maybe Hover))
63+
makeHover hps lf ideState params
64+
= do
65+
mhs <- mapM (\p -> p ideState params) hps
66+
-- TODO: We should support ServerCapabilities and declare that
67+
-- we don't support hover requests during initialization if we
68+
-- don't have any hover providers
69+
-- TODO: maybe only have provider give MarkedString and
70+
-- work out range here?
71+
let hs = catMaybes (rights mhs)
72+
r = listToMaybe $ mapMaybe (^. range) hs
73+
h = case mconcat ((map (^. contents) hs) :: [HoverContents]) of
74+
HoverContentsMS (List []) -> Nothing
75+
hh -> Just $ Hover hh r
76+
return $ Right h
77+
78+
-- ---------------------------------------------------------------------
79+
-- ---------------------------------------------------------------------
80+
81+
formatterPlugins :: [(T.Text, FormattingProvider IO)] -> Plugin Config
82+
formatterPlugins providers
83+
= Plugin formatterRules
84+
(formatterHandlers (Map.fromList (("none",noneProvider):providers)))
85+
86+
formatterRules :: Rules ()
87+
formatterRules = mempty
88+
89+
formatterHandlers :: Map.Map T.Text (FormattingProvider IO) -> PartialHandlers Config
90+
formatterHandlers providers = PartialHandlers $ \WithMessage{..} x -> return x
91+
{ LSP.documentFormattingHandler
92+
= withResponse RspDocumentFormatting (formatting providers)
93+
, LSP.documentRangeFormattingHandler
94+
= withResponse RspDocumentRangeFormatting (rangeFormatting providers)
95+
}
96+
97+
-- ---------------------------------------------------------------------

src/Ide/Plugin/Example.hs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
module Ide.Plugin.Example
1212
(
1313
plugin
14+
, hover
1415
) where
1516

1617
import Control.DeepSeq ( NFData )
@@ -52,12 +53,12 @@ hover = request "Hover" blah (Right Nothing) foundHover
5253

5354
blah :: NormalizedFilePath -> Position -> Action (Maybe (Maybe Range, [T.Text]))
5455
blah _ (Position line col)
55-
= return $ Just (Just (Range (Position line col) (Position (line+1) 0)), ["example hover"])
56+
= return $ Just (Just (Range (Position line col) (Position (line+1) 0)), ["example hover 1\n"])
5657

5758
handlersExample :: PartialHandlers c
58-
handlersExample = PartialHandlers $ \WithMessage{..} x ->
59-
return x{LSP.hoverHandler = withResponse RspHover $ const hover}
60-
59+
handlersExample = mempty
60+
-- handlersExample = PartialHandlers $ \WithMessage{..} x ->
61+
-- return x{LSP.hoverHandler = withResponse RspHover $ const hover}
6162

6263
-- ---------------------------------------------------------------------
6364

src/Ide/Plugin/Example2.hs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
module Ide.Plugin.Example2
1212
(
1313
plugin
14+
, hover
1415
) where
1516

1617
import Control.DeepSeq ( NFData )
@@ -52,11 +53,12 @@ hover = request "Hover" blah (Right Nothing) foundHover
5253

5354
blah :: NormalizedFilePath -> Position -> Action (Maybe (Maybe Range, [T.Text]))
5455
blah _ (Position line col)
55-
= return $ Just (Just (Range (Position line col) (Position (line+1) 0)), ["example hover"])
56+
= return $ Just (Just (Range (Position line col) (Position (line+1) 0)), ["example hover 2\n"])
5657

5758
handlersExample2 :: PartialHandlers c
58-
handlersExample2 = PartialHandlers $ \WithMessage{..} x ->
59-
return x{LSP.hoverHandler = withResponse RspHover $ const hover}
59+
handlersExample2 = mempty
60+
-- handlersExample2 = PartialHandlers $ \WithMessage{..} x ->
61+
-- return x{LSP.hoverHandler = withResponse RspHover $ const hover}
6062

6163

6264
-- ---------------------------------------------------------------------

0 commit comments

Comments
 (0)