packages feed

haskell-language-server-2.10.0.0: plugins/hls-cabal-gild-plugin/src/Ide/Plugin/CabalGild.hs

{-# LANGUAGE DataKinds         #-}
{-# LANGUAGE LambdaCase        #-}
{-# LANGUAGE OverloadedLabels  #-}
{-# LANGUAGE OverloadedStrings #-}

module Ide.Plugin.CabalGild where

import           Control.Monad.Except             (throwError)
import           Control.Monad.IO.Class
import qualified Data.Text                        as T
import           Development.IDE                  hiding (pluginHandlers)
import           Development.IDE.Core.PluginUtils (mkFormattingHandlers)
import           Ide.Plugin.Error                 (PluginError (PluginInternalError, PluginInvalidParams))
import           Ide.Plugin.Properties
import           Ide.PluginUtils
import           Ide.Types
import           Language.LSP.Protocol.Types
import           Prelude                          hiding (log)
import           System.Directory
import           System.Exit
import           System.FilePath
import           System.Process.ListLike
import qualified System.Process.Text              as Process

data Log
  = LogProcessInvocationFailure Int T.Text
  | LogReadCreateProcessInfo [String]
  | LogInvalidInvocationInfo
  | LogFormatterBinNotFound FilePath
  deriving (Show)

instance Pretty Log where
  pretty = \case
    LogProcessInvocationFailure exitCode err ->
      vcat
        [ "Invocation of cabal-gild failed with code" <+> pretty exitCode
        , "Stderr:" <+> pretty err
        ]
    LogReadCreateProcessInfo args ->
      "Formatter invocation: cabal-gild " <+> pretty args
    LogInvalidInvocationInfo -> "Invocation of cabal-gild with range was called but is not supported."
    LogFormatterBinNotFound fp -> "Couldn't find formatter executable 'cabal-gild' at:" <+> pretty fp

descriptor :: Recorder (WithPriority Log) -> PluginId -> PluginDescriptor IdeState
descriptor recorder plId =
  (defaultCabalPluginDescriptor plId "Provides formatting of cabal files with cabal-gild")
    { pluginHandlers = mkFormattingHandlers (provider recorder plId)
    , pluginConfigDescriptor = defaultConfigDescriptor{configCustomConfig = mkCustomConfig properties}
    }

properties :: Properties '[ 'PropertyKey "path" 'TString]
properties =
    emptyProperties
        & defineStringProperty
            #path
            "Set path to 'cabal-gild' executable"
            "cabal-gild"

-- | Formatter provider of cabal gild.
-- Formats the given source in either a given Range or the whole Document.
-- If the provider fails an error is returned that can be displayed to the user.
provider :: Recorder (WithPriority Log) -> PluginId -> FormattingHandler IdeState
provider recorder _ _ _ (FormatRange _) _ _ _ = do
  logWith recorder Info LogInvalidInvocationInfo
  throwError $ PluginInvalidParams "You cannot format a text-range using cabal-gild."
provider recorder plId ideState _ FormatText contents nfp _ = do
  let cabalGildArgs = ["--stdin=" <> fp, "--input=-"] -- < Read from stdin

  cabalGildExePath <- fmap T.unpack $ liftIO $ runAction "cabal-gild" ideState $ usePropertyAction #path plId properties
  x <- liftIO $ findExecutable cabalGildExePath
  case x of
    Just _ -> do
      log Debug $ LogReadCreateProcessInfo cabalGildArgs
      (exitCode, out, err) <-
        liftIO $ Process.readCreateProcessWithExitCode
          ( proc cabalGildExePath cabalGildArgs
          )
            { cwd = Just $ takeDirectory fp
            }
          contents
      case exitCode of
        ExitFailure code -> do
          log Error $ LogProcessInvocationFailure code err
          throwError (PluginInternalError "Failed to invoke cabal-gild")
        ExitSuccess -> do
          let fmtDiff = makeDiffTextEdit contents out
          pure $ InL fmtDiff
    Nothing -> do
      log Error $ LogFormatterBinNotFound cabalGildExePath
      throwError (PluginInternalError "No installation of cabal-gild could be found. Please install it globally, or provide the full path to the executable.")
  where
    fp = fromNormalizedFilePath nfp
    log = logWith recorder