2
2
{-# LANGUAGE LambdaCase #-}
3
3
{-# LANGUAGE OverloadedStrings #-}
4
4
{-# LANGUAGE TypeApplications #-}
5
+ {-# LANGUAGE DataKinds #-}
6
+ {-# LANGUAGE OverloadedLabels #-}
5
7
6
8
module Ide.Plugin.Fourmolu (
7
9
descriptor ,
8
10
provider ,
9
11
) where
10
12
11
- import Control.Exception (try )
13
+ import Control.Exception (IOException , try )
12
14
import Control.Lens ((^.) )
15
+ import Control.Monad
13
16
import Control.Monad.IO.Class
14
17
import Data.Bifunctor (first )
18
+ import Data.Maybe
15
19
import qualified Data.Text as T
20
+ import qualified Data.Text.IO as T
16
21
import Development.IDE hiding (pluginHandlers )
17
22
import Development.IDE.GHC.Compat as Compat hiding (Cpp )
18
23
import qualified Development.IDE.GHC.Compat.Util as S
19
24
import GHC.LanguageExtensions.Type (Extension (Cpp ))
20
- import Ide.PluginUtils (makeDiffTextEdit )
25
+ import Ide.Plugin.Properties
26
+ import Ide.PluginUtils (makeDiffTextEdit , usePropertyLsp )
21
27
import Ide.Types
22
28
import Language.LSP.Server hiding (defaultConfig )
23
29
import Language.LSP.Types
24
30
import Language.LSP.Types.Lens (HasTabSize (tabSize ))
25
31
import Ormolu
32
+ import System.Exit
26
33
import System.FilePath
27
-
28
- -- ---------------------------------------------------------------------
34
+ import System.IO (stderr )
35
+ import System.Process.Run (proc , cwd )
36
+ import System.Process.Text (readCreateProcessWithExitCode )
29
37
30
38
descriptor :: PluginId -> PluginDescriptor IdeState
31
39
descriptor plId =
32
40
(defaultPluginDescriptor plId)
33
- { pluginHandlers = mkFormattingHandlers provider
41
+ { pluginHandlers = mkFormattingHandlers $ provider plId
34
42
}
35
43
36
- -- ---------------------------------------------------------------------
37
-
38
- provider :: FormattingHandler IdeState
39
- provider ideState typ contents fp fo = withIndefiniteProgress title Cancellable $ do
40
- ghc <- liftIO $ runAction " Fourmolu" ideState $ use GhcSession fp
41
- fileOpts <- case hsc_dflags . hscEnv <$> ghc of
42
- Nothing -> return []
43
- Just df -> liftIO $ convertDynFlags df
44
-
45
- let format printerOpts =
46
- first (responseError . (" Fourmolu: " <> ) . T. pack . show )
47
- <$> try @ OrmoluException (makeDiffTextEdit contents <$> ormolu config fp' (T. unpack contents))
48
- where
49
- config =
50
- defaultConfig
51
- { cfgDynOptions = fileOpts
52
- , cfgRegion = region
53
- , cfgDebug = True
54
- , cfgPrinterOpts =
55
- fillMissingPrinterOpts
56
- (printerOpts <> lspPrinterOpts)
57
- defaultPrinterOpts
58
- }
44
+ properties :: Properties '[ 'PropertyKey " external" 'TBoolean]
45
+ properties =
46
+ emptyProperties
47
+ & defineBooleanProperty
48
+ # external
49
+ " Call out to an external \" fourmolu\" executable, rather than using the bundled library"
50
+ False
59
51
60
- liftIO (loadConfigFile fp') >>= \ case
61
- ConfigLoaded file opts -> liftIO $ do
62
- putStrLn $ " Loaded Fourmolu config from: " <> file
63
- format opts
64
- ConfigNotFound searchDirs -> liftIO $ do
65
- putStrLn
66
- . unlines
67
- $ (" No " ++ show configFileName ++ " found in any of:" ) :
68
- map (" " ++ ) searchDirs
69
- format mempty
70
- ConfigParseError f (_, err) -> do
71
- sendNotification SWindowShowMessage $
72
- ShowMessageParams
73
- { _xtype = MtError
74
- , _message = errorMessage
75
- }
76
- return . Left $ responseError errorMessage
77
- where
78
- errorMessage = " Failed to load " <> T. pack f <> " : " <> T. pack err
52
+ provider :: PluginId -> FormattingHandler IdeState
53
+ provider plId ideState typ contents fp fo = withIndefiniteProgress title Cancellable $ do
54
+ fileOpts <-
55
+ maybe [] (convertDynFlags . hsc_dflags . hscEnv)
56
+ <$> liftIO (runAction " Fourmolu" ideState $ use GhcSession fp)
57
+ useCLI <- usePropertyLsp # external plId properties
58
+ if useCLI
59
+ then liftIO
60
+ . fmap (join . first (mkError . show ))
61
+ . try @ IOException
62
+ $ do
63
+ (exitCode, out, err) <-
64
+ readCreateProcessWithExitCode
65
+ ( proc " fourmolu" $
66
+ [" -d" ]
67
+ <> catMaybes
68
+ [ (" --start-line=" <> ) . show <$> regionStartLine region
69
+ , (" --end-line=" <> ) . show <$> regionEndLine region
70
+ ]
71
+ <> map (" -o" <> ) fileOpts
72
+ ){cwd = Just $ takeDirectory fp'}
73
+ contents
74
+ T. hPutStrLn stderr err
75
+ case exitCode of
76
+ ExitSuccess ->
77
+ pure . Right $ makeDiffTextEdit contents out
78
+ ExitFailure n ->
79
+ pure . Left . responseError $ " Fourmolu failed with exit code " <> T. pack (show n)
80
+ else do
81
+ let format printerOpts =
82
+ first (mkError . show )
83
+ <$> try @ OrmoluException (makeDiffTextEdit contents <$> ormolu config fp' (T. unpack contents))
84
+ where
85
+ config =
86
+ defaultConfig
87
+ { cfgDynOptions = map DynOption fileOpts
88
+ , cfgRegion = region
89
+ , cfgDebug = True
90
+ , cfgPrinterOpts =
91
+ fillMissingPrinterOpts
92
+ (printerOpts <> lspPrinterOpts)
93
+ defaultPrinterOpts
94
+ }
95
+ in liftIO (loadConfigFile fp') >>= \ case
96
+ ConfigLoaded file opts -> liftIO $ do
97
+ putStrLn $ " Loaded Fourmolu config from: " <> file
98
+ format opts
99
+ ConfigNotFound searchDirs -> liftIO $ do
100
+ putStrLn
101
+ . unlines
102
+ $ (" No " ++ show configFileName ++ " found in any of:" ) :
103
+ map (" " ++ ) searchDirs
104
+ format mempty
105
+ ConfigParseError f (_, err) -> do
106
+ sendNotification SWindowShowMessage $
107
+ ShowMessageParams
108
+ { _xtype = MtError
109
+ , _message = errorMessage
110
+ }
111
+ return . Left $ responseError errorMessage
112
+ where
113
+ errorMessage = " Failed to load " <> T. pack f <> " : " <> T. pack err
79
114
where
80
115
fp' = fromNormalizedFilePath fp
81
116
title = " Formatting " <> T. pack (takeFileName fp')
117
+ mkError = responseError . (" Fourmolu: " <> ) . T. pack
82
118
lspPrinterOpts = mempty {poIndentation = Just $ fromIntegral $ fo ^. tabSize}
83
119
region = case typ of
84
120
FormatText ->
85
121
RegionIndices Nothing Nothing
86
122
FormatRange (Range (Position sl _) (Position el _)) ->
87
123
RegionIndices (Just $ fromIntegral $ sl + 1 ) (Just $ fromIntegral $ el + 1 )
88
124
89
- convertDynFlags :: DynFlags -> IO [ DynOption ]
125
+ convertDynFlags :: DynFlags -> [ String ]
90
126
convertDynFlags df =
91
127
let pp = [" -pgmF=" <> p | not (null p)]
92
128
p = sPgm_F $ Compat. settings df
@@ -95,4 +131,4 @@ convertDynFlags df =
95
131
showExtension = \ case
96
132
Cpp -> " -XCPP"
97
133
x -> " -X" ++ show x
98
- in return $ map DynOption $ pp <> pm <> ex
134
+ in pp <> pm <> ex
0 commit comments