Skip to content

Commit ab1ef25

Browse files
committed
Choose formatter based on config.
Requires haskell/ghcide#416
1 parent eed4f41 commit ab1ef25

14 files changed

+236
-212
lines changed

.gitmodules

+4
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,7 @@
88
# Commit git commit -m "Removed submodule <name>"
99
# Delete the now untracked submodule files
1010
# rm -rf path_to_submodule
11+
[submodule "ghcide"]
12+
path = ghcide
13+
# url = https://github.com/digital-asset/ghcide.git
14+
url = https://github.com/alanz/ghcide.git

cabal.project

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
packages:
22
./
3-
-- ghcide
3+
ghcide
44

55
tests: true
66

@@ -11,4 +11,4 @@ package ghcide
1111

1212
write-ghc-environment-files: never
1313

14-
index-state: 2020-02-04T19:45:47Z
14+
index-state: 2020-02-09T06:58:05Z

exe/Main.hs

+12-5
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
{-# LANGUAGE RecordWildCards #-}
55
{-# LANGUAGE ViewPatterns #-}
66
{-# LANGUAGE TupleSections #-}
7+
{-# LANGUAGE OverloadedStrings #-}
78

89
module Main(main) where
910

@@ -19,6 +20,7 @@ import Data.Maybe
1920
import qualified Data.Set as Set
2021
import qualified Data.Text as T
2122
import qualified Data.Text.IO as T
23+
import Development.IDE.Core.Debouncer
2224
import Development.IDE.Core.FileStore
2325
import Development.IDE.Core.OfInterest
2426
import Development.IDE.Core.RuleTypes
@@ -36,6 +38,8 @@ import Development.IDE.Types.Options
3638
import Development.Shake (Action, action)
3739
import GHC hiding (def)
3840
import HIE.Bios
41+
import Ide.Plugin.Formatter
42+
import Ide.Plugin.Config
3943
import Language.Haskell.LSP.Messages
4044
import Language.Haskell.LSP.Types (LspId(IdInt))
4145
import Linker
@@ -50,6 +54,7 @@ import System.Time.Extra
5054
import Development.IDE.Plugin.CodeAction as CodeAction
5155
import Development.IDE.Plugin.Completions as Completions
5256
import Ide.Plugin.Example as Example
57+
import Ide.Plugin.Floskell as Floskell
5358
import Ide.Plugin.Ormolu as Ormolu
5459

5560
-- ---------------------------------------------------------------------
@@ -58,11 +63,12 @@ import Ide.Plugin.Ormolu as Ormolu
5863
-- server.
5964
-- These can be freely added or removed to tailor the available
6065
-- features of the server.
61-
idePlugins :: Bool -> Plugin
66+
idePlugins :: Bool -> Plugin Config
6267
idePlugins includeExample
6368
= Completions.plugin <>
6469
CodeAction.plugin <>
65-
Ormolu.plugin <>
70+
formatterPlugins [("ormolu", Ormolu.provider)
71+
,("floskell", Floskell.provider)] <>
6672
if includeExample then Example.plugin else mempty
6773

6874
-- ---------------------------------------------------------------------
@@ -91,7 +97,7 @@ main = do
9197
t <- offsetTime
9298
hPutStrLn stderr "Starting (haskell-language-server)LSP server..."
9399
hPutStrLn stderr "If you are seeing this in a terminal, you probably should have run ghcide WITHOUT the --lsp option!"
94-
runLanguageServer def (pluginHandler plugins) $ \getLspId event vfs caps -> do
100+
runLanguageServer def (pluginHandler plugins) getInitialConfig getConfigFromNotification $ \getLspId event vfs caps -> do
95101
t <- t
96102
hPutStrLn stderr $ "Started LSP server in " ++ showDuration t
97103
-- very important we only call loadSession once, and it's fast, so just do it before starting
@@ -100,7 +106,8 @@ main = do
100106
{ optReportProgress = clientSupportsProgress caps
101107
, optShakeProfiling = argsShakeProfiling
102108
}
103-
initialise caps (mainRule >> pluginRules plugins >> action kick) getLspId event (logger minBound) options vfs
109+
debouncer <- newAsyncDebouncer
110+
initialise caps (mainRule >> pluginRules plugins >> action kick) getLspId event (logger minBound) debouncer options vfs
104111
else do
105112
putStrLn $ "(haskell-language-server)Ghcide setup tester in " ++ dir ++ "."
106113
putStrLn "Report bugs at https://github.com/haskell/haskell-language-server/issues"
@@ -135,7 +142,7 @@ main = do
135142
let options =
136143
(defaultIdeOptions $ return $ return . grab)
137144
{ optShakeProfiling = argsShakeProfiling }
138-
ide <- initialise def mainRule (pure $ IdInt 0) (showEvent lock) (logger Info) options vfs
145+
ide <- initialise def mainRule (pure $ IdInt 0) (showEvent lock) (logger Info) noopDebouncer options vfs
139146

140147
putStrLn "\nStep 6/6: Type checking the files"
141148
setFilesOfInterest ide $ Set.fromList $ map toNormalizedFilePath files

ghcide

Submodule ghcide added at 24116bc

haskell-language-server.cabal

+4-1
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ source-repository head
2828
library
2929
exposed-modules:
3030
Ide.Cradle
31+
Ide.Plugin.Config
3132
Ide.Plugin.Example
3233
Ide.Plugin.Ormolu
3334
Ide.Plugin.Floskell
@@ -45,15 +46,17 @@ library
4546
, Cabal
4647
, cabal-helper >= 1.0
4748
, containers
49+
, data-default
4850
, deepseq
4951
, directory
52+
, extra
5053
, filepath
5154
, floskell == 0.10.*
5255
, ghc
5356
, ghcide >= 0.1
5457
, gitrev
5558
, hashable
56-
, haskell-lsp == 0.19.*
59+
, haskell-lsp == 0.20.*
5760
, hie-bios >= 0.4
5861
, hslogger
5962
, optparse-simple

src/Ide/Plugin/Config.hs

+102
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
{-# LANGUAGE RecordWildCards #-}
2+
{-# LANGUAGE DeriveGeneric #-}
3+
{-# LANGUAGE FlexibleInstances #-}
4+
{-# LANGUAGE OverloadedStrings #-}
5+
{-# LANGUAGE TypeFamilies #-}
6+
module Ide.Plugin.Config
7+
(
8+
getInitialConfig
9+
, getConfigFromNotification
10+
, Config(..)
11+
) where
12+
13+
import qualified Data.Aeson as A
14+
import Data.Aeson hiding ( Error )
15+
import Data.Default
16+
import qualified Data.Text as T
17+
import Language.Haskell.LSP.Types
18+
19+
-- ---------------------------------------------------------------------
20+
21+
-- | Given a DidChangeConfigurationNotification message, this function returns the parsed
22+
-- Config object if possible.
23+
getConfigFromNotification :: DidChangeConfigurationNotification -> Either T.Text Config
24+
getConfigFromNotification (NotificationMessage _ _ (DidChangeConfigurationParams p)) =
25+
case fromJSON p of
26+
A.Success c -> Right c
27+
A.Error err -> Left $ T.pack err
28+
29+
-- | Given an InitializeRequest message, this function returns the parsed
30+
-- Config object if possible. Otherwise, it returns the default configuration
31+
getInitialConfig :: InitializeRequest -> Either T.Text Config
32+
getInitialConfig (RequestMessage _ _ _ InitializeParams{_initializationOptions = Nothing }) = Right def
33+
getInitialConfig (RequestMessage _ _ _ InitializeParams{_initializationOptions = Just opts}) =
34+
case fromJSON opts of
35+
A.Success c -> Right c
36+
A.Error err -> Left $ T.pack err
37+
38+
-- ---------------------------------------------------------------------
39+
40+
-- | We (initially anyway) mirror the hie configuration, so that existing
41+
-- clients can simply switch executable and not have any nasty surprises. There
42+
-- will be surprises relating to config options being ignored, initially though.
43+
data Config =
44+
Config
45+
{ hlintOn :: Bool
46+
, diagnosticsOnChange :: Bool
47+
, maxNumberOfProblems :: Int
48+
, diagnosticsDebounceDuration :: Int
49+
, liquidOn :: Bool
50+
, completionSnippetsOn :: Bool
51+
, formatOnImportOn :: Bool
52+
, formattingProvider :: T.Text
53+
} deriving (Show,Eq)
54+
55+
instance Default Config where
56+
def = Config
57+
{ hlintOn = True
58+
, diagnosticsOnChange = True
59+
, maxNumberOfProblems = 100
60+
, diagnosticsDebounceDuration = 350000
61+
, liquidOn = False
62+
, completionSnippetsOn = True
63+
, formatOnImportOn = True
64+
-- , formattingProvider = "brittany"
65+
, formattingProvider = "ormolu"
66+
}
67+
68+
-- TODO: Add API for plugins to expose their own LSP config options
69+
instance A.FromJSON Config where
70+
parseJSON = A.withObject "Config" $ \v -> do
71+
s <- v .: "languageServerHaskell"
72+
flip (A.withObject "Config.settings") s $ \o -> Config
73+
<$> o .:? "hlintOn" .!= hlintOn def
74+
<*> o .:? "diagnosticsOnChange" .!= diagnosticsOnChange def
75+
<*> o .:? "maxNumberOfProblems" .!= maxNumberOfProblems def
76+
<*> o .:? "diagnosticsDebounceDuration" .!= diagnosticsDebounceDuration def
77+
<*> o .:? "liquidOn" .!= liquidOn def
78+
<*> o .:? "completionSnippetsOn" .!= completionSnippetsOn def
79+
<*> o .:? "formatOnImportOn" .!= formatOnImportOn def
80+
<*> o .:? "formattingProvider" .!= formattingProvider def
81+
82+
-- 2017-10-09 23:22:00.710515298 [ThreadId 11] - ---> {"jsonrpc":"2.0","method":"workspace/didChangeConfiguration","params":{"settings":{"languageServerHaskell":{"maxNumberOfProblems":100,"hlintOn":true}}}}
83+
-- 2017-10-09 23:22:00.710667381 [ThreadId 15] - reactor:got didChangeConfiguration notification:
84+
-- NotificationMessage
85+
-- {_jsonrpc = "2.0"
86+
-- , _method = WorkspaceDidChangeConfiguration
87+
-- , _params = DidChangeConfigurationParams
88+
-- {_settings = Object (fromList [("languageServerHaskell",Object (fromList [("hlintOn",Bool True)
89+
-- ,("maxNumberOfProblems",Number 100.0)]))])}}
90+
91+
instance A.ToJSON Config where
92+
toJSON (Config h diag m d l c f fp) = object [ "languageServerHaskell" .= r ]
93+
where
94+
r = object [ "hlintOn" .= h
95+
, "diagnosticsOnChange" .= diag
96+
, "maxNumberOfProblems" .= m
97+
, "diagnosticsDebounceDuration" .= d
98+
, "liquidOn" .= l
99+
, "completionSnippetsOn" .= c
100+
, "formatOnImportOn" .= f
101+
, "formattingProvider" .= fp
102+
]

src/Ide/Plugin/Example.hs

+6-6
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ import Text.Regex.TDFA.Text()
4242

4343
-- ---------------------------------------------------------------------
4444

45-
plugin :: Plugin
45+
plugin :: Plugin c
4646
plugin = Plugin exampleRules handlersExample
4747
<> codeActionPlugin codeAction
4848
<> Plugin mempty handlersCodeLens
@@ -54,7 +54,7 @@ blah :: NormalizedFilePath -> Position -> Action (Maybe (Maybe Range, [T.Text]))
5454
blah _ (Position line col)
5555
= return $ Just (Just (Range (Position line col) (Position (line+1) 0)), ["example hover"])
5656

57-
handlersExample :: PartialHandlers
57+
handlersExample :: PartialHandlers c
5858
handlersExample = PartialHandlers $ \WithMessage{..} x ->
5959
return x{LSP.hoverHandler = withResponse RspHover $ const hover}
6060

@@ -100,7 +100,7 @@ mkDiag file diagSource sev loc msg = (file, D.ShowDiag,)
100100

101101
-- | Generate code actions.
102102
codeAction
103-
:: LSP.LspFuncs ()
103+
:: LSP.LspFuncs c
104104
-> IdeState
105105
-> TextDocumentIdentifier
106106
-> Range
@@ -118,14 +118,14 @@ codeAction _lsp _state (TextDocumentIdentifier uri) _range CodeActionContext{_di
118118
-- ---------------------------------------------------------------------
119119

120120
-- | Generate code lenses.
121-
handlersCodeLens :: PartialHandlers
121+
handlersCodeLens :: PartialHandlers c
122122
handlersCodeLens = PartialHandlers $ \WithMessage{..} x -> return x{
123123
LSP.codeLensHandler = withResponse RspCodeLens codeLens,
124124
LSP.executeCommandHandler = withResponseAndRequest RspExecuteCommand ReqApplyWorkspaceEdit executeAddSignatureCommand
125125
}
126126

127127
codeLens
128-
:: LSP.LspFuncs ()
128+
:: LSP.LspFuncs c
129129
-> IdeState
130130
-> CodeLensParams
131131
-> IO (Either ResponseError (List CodeLens))
@@ -149,7 +149,7 @@ codeLens _lsp ideState CodeLensParams{_textDocument=TextDocumentIdentifier uri}
149149

150150
-- | Execute the "codelens.todo" command.
151151
executeAddSignatureCommand
152-
:: LSP.LspFuncs ()
152+
:: LSP.LspFuncs c
153153
-> IdeState
154154
-> ExecuteCommandParams
155155
-> IO (Value, Maybe (ServerMethod, ApplyWorkspaceEditParams))

src/Ide/Plugin/Floskell.hs

+3-46
Original file line numberDiff line numberDiff line change
@@ -7,39 +7,20 @@
77

88
module Ide.Plugin.Floskell
99
(
10-
plugin
10+
provider
1111
)
1212
where
1313

14-
#if __GLASGOW_HASKELL__ >= 806
15-
#if __GLASGOW_HASKELL__ >= 808
16-
import Control.Monad.IO.Class ( MonadIO(..) )
17-
#else
18-
import Control.Monad.IO.Class ( liftIO
19-
, MonadIO(..)
20-
)
21-
#endif
22-
import qualified Data.Text as T
23-
#endif
24-
2514
import qualified Data.ByteString.Lazy as BS
15+
import qualified Data.Text as T
2616
import qualified Data.Text.Encoding as T
27-
import Development.IDE.Plugin
2817
import Development.IDE.Types.Diagnostics as D
2918
import Development.IDE.Types.Location
3019
import Floskell
3120
import Ide.Plugin.Formatter
3221
import Language.Haskell.LSP.Types
3322
import Text.Regex.TDFA.Text()
3423

35-
-- ---------------------------------------------------------------------
36-
-- New style plugin
37-
38-
plugin :: Plugin
39-
plugin = formatterPlugin provider
40-
41-
-- ---------------------------------------------------------------------
42-
-- ---------------------------------------------------------------------
4324
-- ---------------------------------------------------------------------
4425

4526
-- | Format provider of Floskell.
@@ -48,7 +29,7 @@ plugin = formatterPlugin provider
4829
provider :: FormattingProvider IO
4930
provider _ideState typ contents fp _ = do
5031
let file = fromNormalizedFilePath fp
51-
config <- liftIO $ findConfigOrDefault file
32+
config <- findConfigOrDefault file
5233
let (range, selectedContents) = case typ of
5334
FormatText -> (fullRange contents, contents)
5435
FormatRange r -> (r, extractRange r contents)
@@ -71,27 +52,3 @@ findConfigOrDefault file = do
7152
in return $ defaultAppConfig { appStyle = gibiansky }
7253

7354
-- ---------------------------------------------------------------------
74-
-- ---------------------------------------------------------------------
75-
-- ---------------------------------------------------------------------
76-
77-
78-
extractRange :: Range -> T.Text -> T.Text
79-
extractRange (Range (Position sl _) (Position el _)) s = newS
80-
where focusLines = take (el-sl+1) $ drop sl $ T.lines s
81-
newS = T.unlines focusLines
82-
83-
-- | Gets the range that covers the entire text
84-
fullRange :: T.Text -> Range
85-
fullRange s = Range startPos endPos
86-
where startPos = Position 0 0
87-
endPos = Position lastLine 0
88-
{-
89-
In order to replace everything including newline characters,
90-
the end range should extend below the last line. From the specification:
91-
"If you want to specify a range that contains a line including
92-
the line ending character(s) then use an end position denoting
93-
the start of the next line"
94-
-}
95-
lastLine = length $ T.lines s
96-
97-
-- ---------------------------------------------------------------------

0 commit comments

Comments
 (0)