haskell-language-server-1.0.0.0: plugins/default/src/Ide/Plugin/Fourmolu.hs
{-# LANGUAGE DisambiguateRecordFields #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE PackageImports #-}
{-# LANGUAGE TypeApplications #-}
module Ide.Plugin.Fourmolu (
descriptor,
provider,
) where
import Control.Exception
import Data.Either.Extra
import System.FilePath
import Control.Lens ((^.))
import qualified Data.Text as T
import Development.IDE as D hiding (pluginHandlers)
import qualified DynFlags as D
import qualified EnumSet as S
import GHC (DynFlags, moduleNameString)
import GHC.LanguageExtensions.Type (Extension (Cpp))
import GhcPlugins (HscEnv (hsc_dflags))
import Ide.PluginUtils (makeDiffTextEdit)
import Ide.Types
import Language.LSP.Server
import Language.LSP.Types
import Language.LSP.Types.Lens
import "fourmolu" Ormolu
import Control.Monad.IO.Class
-- ---------------------------------------------------------------------
descriptor :: PluginId -> PluginDescriptor IdeState
descriptor plId =
(defaultPluginDescriptor plId)
{ pluginHandlers = mkFormattingHandlers provider
}
-- ---------------------------------------------------------------------
provider :: FormattingHandler IdeState
provider ideState typ contents fp fo = withIndefiniteProgress title Cancellable $ do
ghc <- liftIO $ runAction "Fourmolu" ideState $ use GhcSession fp
fileOpts <- case hsc_dflags . hscEnv <$> ghc of
Nothing -> return []
Just df -> liftIO $ convertDynFlags df
let format printerOpts =
mapLeft (responseError . ("Fourmolu: " <>) . T.pack . show)
<$> try @OrmoluException (makeDiffTextEdit contents <$> ormolu config fp' (T.unpack contents))
where
config =
defaultConfig
{ cfgDynOptions = fileOpts
, cfgRegion = region
, cfgDebug = True
, cfgPrinterOpts =
fillMissingPrinterOpts
(printerOpts <> lspPrinterOpts)
defaultPrinterOpts
}
liftIO (loadConfigFile fp') >>= \case
ConfigLoaded file opts -> liftIO $ do
putStrLn $ "Loaded Fourmolu config from: " <> file
format opts
ConfigNotFound searchDirs -> liftIO $ do
putStrLn
. unlines
$ ("No " ++ show configFileName ++ " found in any of:") :
map (" " ++) searchDirs
format mempty
ConfigParseError f (_, err) -> do
sendNotification SWindowShowMessage $
ShowMessageParams
{ _xtype = MtError
, _message = errorMessage
}
return . Left $ responseError errorMessage
where
errorMessage = "Failed to load " <> T.pack f <> ": " <> T.pack err
where
fp' = fromNormalizedFilePath fp
title = "Formatting " <> T.pack (takeFileName fp')
lspPrinterOpts = mempty{poIndentation = Just $ fo ^. tabSize}
region = case typ of
FormatText ->
RegionIndices Nothing Nothing
FormatRange (Range (Position sl _) (Position el _)) ->
RegionIndices (Just $ sl + 1) (Just $ el + 1)
convertDynFlags :: DynFlags -> IO [DynOption]
convertDynFlags df =
let pp = ["-pgmF=" <> p | not (null p)]
p = D.sPgm_F $ D.settings df
pm = map (("-fplugin=" <>) . moduleNameString) $ D.pluginModNames df
ex = map showExtension $ S.toList $ D.extensionFlags df
showExtension = \case
Cpp -> "-XCPP"
x -> "-X" ++ show x
in return $ map DynOption $ pp <> pm <> ex