{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE TypeFamilyDependencies #-}
{-# LANGUAGE UndecidableInstances #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE MultiWayIf #-}
{-# LANGUAGE BinaryLiterals #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE ViewPatterns #-}
{-# LANGUAGE TypeInType #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE FunctionalDependencies #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE RecursiveDo #-}
{-# OPTIONS_GHC -Wno-unticked-promoted-constructors #-}
{-# OPTIONS_GHC -fprint-explicit-kinds #-}
module Language.LSP.Server.Core where
import Control.Concurrent.Async
import Control.Concurrent.STM
import qualified Control.Exception as E
import Control.Monad
import Control.Monad.Fix
import Control.Monad.IO.Class
import Control.Monad.Trans.Reader
import Control.Monad.Trans.Class
import Control.Monad.IO.Unlift
import Control.Lens ( (^.), (^?), _Just )
import qualified Data.Aeson as J
import Data.Default
import Data.Functor.Product
import Data.IxMap
import qualified Data.Dependent.Map as DMap
import Data.Dependent.Map (DMap)
import qualified Data.HashMap.Strict as HM
import Data.Kind
import qualified Data.List as L
import Data.List.NonEmpty (NonEmpty(..))
import qualified Data.Map as Map
import Data.Maybe
import qualified Data.Text as T
import Data.Text ( Text )
import qualified Data.UUID as UUID
import qualified Language.LSP.Types.Capabilities as J
import Language.LSP.Types as J
import qualified Language.LSP.Types.Lens as J
import Language.LSP.VFS
import Language.LSP.Diagnostics
import System.IO
import qualified System.Log.Formatter as L
import qualified System.Log.Handler as LH
import qualified System.Log.Handler.Simple as LHS
import System.Log.Logger
import qualified System.Log.Logger as L
import System.Random
import Control.Monad.Trans.Identity
{-# ANN module ("HLint: ignore Eta reduce" :: String) #-}
{-# ANN module ("HLint: ignore Redundant do" :: String) #-}
{-# ANN module ("HLint: ignore Reduce duplication" :: String) #-}
newtype LspT config m a = LspT { unLspT :: ReaderT (LanguageContextEnv config) m a }
deriving (Functor, Applicative, Monad, MonadIO, MonadTrans, MonadUnliftIO, MonadFix)
runLspT :: LanguageContextEnv config -> LspT config m a -> m a
runLspT env = flip runReaderT env . unLspT
type LspM config = LspT config IO
class MonadUnliftIO m => MonadLsp config m | m -> config where
getLspEnv :: m (LanguageContextEnv config)
instance MonadUnliftIO m => MonadLsp config (LspT config m) where
getLspEnv = LspT ask
instance MonadLsp c m => MonadLsp c (ReaderT r m) where
getLspEnv = lift getLspEnv
instance MonadLsp c m => MonadLsp c (IdentityT m) where
getLspEnv = lift getLspEnv
data LanguageContextEnv config =
LanguageContextEnv
{ resHandlers :: !(Handlers IO)
, resParseConfig :: !(J.Value -> IO (Either T.Text config))
, resSendMessage :: !(FromServerMessage -> IO ())
, resState :: !(TVar (LanguageContextState config))
, resClientCapabilities :: !J.ClientCapabilities
, resRootPath :: !(Maybe FilePath)
}
data Handlers m
= Handlers
{ reqHandlers :: DMap SMethod (ClientMessageHandler m Request)
, notHandlers :: DMap SMethod (ClientMessageHandler m Notification)
}
instance Semigroup (Handlers config) where
Handlers r1 n1 <> Handlers r2 n2 = Handlers (r1 <> r2) (n1 <> n2)
instance Monoid (Handlers config) where
mempty = Handlers mempty mempty
notificationHandler :: forall (m :: Method FromClient Notification) f. SMethod m -> Handler f m -> Handlers f
notificationHandler m h = Handlers mempty (DMap.singleton m (ClientMessageHandler h))
requestHandler :: forall (m :: Method FromClient Request) f. SMethod m -> Handler f m -> Handlers f
requestHandler m h = Handlers (DMap.singleton m (ClientMessageHandler h)) mempty
newtype ClientMessageHandler f (t :: MethodType) (m :: Method FromClient t) = ClientMessageHandler (Handler f m)
type family Handler (f :: Type -> Type) (m :: Method from t) = (result :: Type) | result -> f t m where
Handler f (m :: Method _from Request) = RequestMessage m -> (Either ResponseError (ResponseResult m) -> f ()) -> f ()
Handler f (m :: Method _from Notification) = NotificationMessage m -> f ()
data m <~> n
= Iso
{ forward :: forall a. m a -> n a
, backward :: forall a. n a -> m a
}
transmuteHandlers :: (m <~> n) -> Handlers m -> Handlers n
transmuteHandlers nat = mapHandlers (\i m k -> forward nat (i m (backward nat . k))) (\i m -> forward nat (i m))
mapHandlers
:: (forall (a :: Method FromClient Request). Handler m a -> Handler n a)
-> (forall (a :: Method FromClient Notification). Handler m a -> Handler n a)
-> Handlers m -> Handlers n
mapHandlers mapReq mapNot (Handlers reqs nots) = Handlers reqs' nots'
where
reqs' = DMap.map (\(ClientMessageHandler i) -> ClientMessageHandler $ mapReq i) reqs
nots' = DMap.map (\(ClientMessageHandler i) -> ClientMessageHandler $ mapNot i) nots
data LanguageContextState config =
LanguageContextState
{ resVFS :: !VFSData
, resDiagnostics :: !DiagnosticStore
, resConfig :: !(Maybe config)
, resWorkspaceFolders :: ![WorkspaceFolder]
, resProgressData :: !ProgressData
, resPendingResponses :: !ResponseMap
, resRegistrationsNot :: !(RegistrationMap Notification)
, resRegistrationsReq :: !(RegistrationMap Request)
, resLspId :: !Int
}
type ResponseMap = IxMap LspId (Product SMethod ServerResponseCallback)
type RegistrationMap (t :: MethodType) = DMap SMethod (Product RegistrationId (ClientMessageHandler IO t))
data RegistrationToken (m :: Method FromClient t) = RegistrationToken (SMethod m) (RegistrationId m)
newtype RegistrationId (m :: Method FromClient t) = RegistrationId Text
deriving Eq
data ProgressData = ProgressData { progressNextId :: !Int
, progressCancel :: !(Map.Map ProgressToken (IO ())) }
data VFSData =
VFSData
{ vfsData :: !VFS
, reverseMap :: !(Map.Map FilePath FilePath)
}
modifyState :: MonadLsp config m => (LanguageContextState config -> LanguageContextState config) -> m ()
modifyState f = do
tvarDat <- resState <$> getLspEnv
liftIO $ atomically $ modifyTVar' tvarDat f
stateState :: MonadLsp config m => (LanguageContextState config -> (a,LanguageContextState config)) -> m a
stateState f = do
tvarDat <- resState <$> getLspEnv
liftIO $ atomically $ stateTVar tvarDat f
getsState :: MonadLsp config m => (LanguageContextState config -> a) -> m a
getsState f = do
tvarDat <- resState <$> getLspEnv
liftIO $ f <$> readTVarIO tvarDat
data Options =
Options
{ textDocumentSync :: Maybe J.TextDocumentSyncOptions
, completionTriggerCharacters :: Maybe [Char]
, completionAllCommitCharacters :: Maybe [Char]
, signatureHelpTriggerCharacters :: Maybe [Char]
, signatureHelpRetriggerCharacters :: Maybe [Char]
, codeActionKinds :: Maybe [CodeActionKind]
, documentOnTypeFormattingTriggerCharacters :: Maybe (NonEmpty Char)
, executeCommandCommands :: Maybe [Text]
, serverInfo :: Maybe J.ServerInfo
}
instance Default Options where
def = Options Nothing Nothing Nothing Nothing Nothing
Nothing Nothing Nothing Nothing
defaultOptions :: Options
defaultOptions = def
data ProgressAmount = ProgressAmount (Maybe Double) (Maybe Text)
data ProgressCancelledException = ProgressCancelledException
deriving Show
instance E.Exception ProgressCancelledException
data ProgressCancellable = Cancellable | NotCancellable
data ServerDefinition config = forall m a.
ServerDefinition
{ onConfigurationChange :: J.Value -> m (Either T.Text config)
, doInitialize :: LanguageContextEnv config -> Message Initialize -> IO (Either ResponseError a)
, staticHandlers :: Handlers m
, interpretHandler :: a -> (m <~> IO)
, options :: Options
}
newtype ServerResponseCallback (m :: Method FromServer Request)
= ServerResponseCallback (Either ResponseError (ResponseResult m) -> IO ())
addResponseHandler :: MonadLsp config f => LspId m -> (Product SMethod ServerResponseCallback) m -> f Bool
addResponseHandler lid h = do
stateState $ \ctx@LanguageContextState{resPendingResponses} ->
case insertIxMap lid h resPendingResponses of
Just m -> (True, ctx { resPendingResponses = m})
Nothing -> (False, ctx)
sendNotification
:: forall (m :: Method FromServer Notification) f config. MonadLsp config f
=> SServerMethod m
-> MessageParams m
-> f ()
sendNotification m params =
let msg = NotificationMessage "2.0" m params
in case splitServerMethod m of
IsServerNot -> sendToClient $ fromServerNot msg
IsServerEither -> sendToClient $ FromServerMess m $ NotMess msg
sendRequest :: forall (m :: Method FromServer Request) f config. MonadLsp config f
=> SServerMethod m
-> MessageParams m
-> (Either ResponseError (ResponseResult m) -> f ())
-> f (LspId m)
sendRequest m params resHandler = do
reqId <- IdInt <$> freshLspId
rio <- askRunInIO
success <- addResponseHandler reqId (Pair m (ServerResponseCallback (rio . resHandler)))
unless success $ error "haskell-lsp: could not send FromServer request as id is reused"
let msg = RequestMessage "2.0" reqId m params
~() <- case splitServerMethod m of
IsServerReq -> sendToClient $ fromServerReq msg
IsServerEither -> sendToClient $ FromServerMess m $ ReqMess msg
return reqId
getVirtualFile :: MonadLsp config m => NormalizedUri -> m (Maybe VirtualFile)
getVirtualFile uri = getsState $ Map.lookup uri . vfsMap . vfsData . resVFS
getVirtualFiles :: MonadLsp config m => m VFS
getVirtualFiles = getsState $ vfsData . resVFS
persistVirtualFile :: MonadLsp config m => NormalizedUri -> m (Maybe FilePath)
persistVirtualFile uri = do
join $ stateState $ \ctx@LanguageContextState{resVFS = vfs} ->
case persistFileVFS (vfsData vfs) uri of
Nothing -> (return Nothing, ctx)
Just (fn, write) ->
let revMap = case uriToFilePath (fromNormalizedUri uri) of
Just uri_fp -> Map.insert fn uri_fp $ reverseMap vfs
Nothing -> reverseMap vfs
act = do
liftIO write
pure (Just fn)
in (act, ctx{resVFS = vfs {reverseMap = revMap} })
getVersionedTextDoc :: MonadLsp config m => TextDocumentIdentifier -> m VersionedTextDocumentIdentifier
getVersionedTextDoc doc = do
let uri = doc ^. J.uri
mvf <- getVirtualFile (toNormalizedUri uri)
let ver = case mvf of
Just (VirtualFile lspver _ _) -> Just lspver
Nothing -> Nothing
return (VersionedTextDocumentIdentifier uri ver)
reverseFileMap :: MonadLsp config m => m (FilePath -> FilePath)
reverseFileMap = do
vfs <- getsState resVFS
let f fp = fromMaybe fp . Map.lookup fp . reverseMap $ vfs
return f
defaultProgressData :: ProgressData
defaultProgressData = ProgressData 0 Map.empty
sendToClient :: MonadLsp config m => FromServerMessage -> m ()
sendToClient msg = do
f <- resSendMessage <$> getLspEnv
liftIO $ f msg
sendErrorLog :: MonadLsp config m => Text -> m ()
sendErrorLog msg =
sendToClient $ fromServerNot $
NotificationMessage "2.0" SWindowLogMessage (LogMessageParams MtError msg)
freshLspId :: MonadLsp config m => m Int
freshLspId = do
stateState $ \c ->
(resLspId c, c{resLspId = resLspId c+1})
getConfig :: MonadLsp config m => m (Maybe config)
getConfig = getsState resConfig
getClientCapabilities :: MonadLsp config m => m J.ClientCapabilities
getClientCapabilities = resClientCapabilities <$> getLspEnv
getRootPath :: MonadLsp config m => m (Maybe FilePath)
getRootPath = resRootPath <$> getLspEnv
getWorkspaceFolders :: MonadLsp config m => m (Maybe [WorkspaceFolder])
getWorkspaceFolders = do
clientCaps <- getClientCapabilities
let clientSupportsWfs = fromMaybe False $ do
let (J.ClientCapabilities mw _ _ _) = clientCaps
(J.WorkspaceClientCapabilities _ _ _ _ _ _ mwf _) <- mw
mwf
if clientSupportsWfs
then Just <$> getsState resWorkspaceFolders
else pure Nothing
registerCapability :: forall f t (m :: Method FromClient t) config.
MonadLsp config f
=> SClientMethod m
-> RegistrationOptions m
-> Handler f m
-> f (Maybe (RegistrationToken m))
registerCapability method regOpts f = do
clientCaps <- resClientCapabilities <$> getLspEnv
handlers <- resHandlers <$> getLspEnv
let alreadyStaticallyRegistered = case splitClientMethod method of
IsClientNot -> DMap.member method $ notHandlers handlers
IsClientReq -> DMap.member method $ reqHandlers handlers
IsClientEither -> error "Cannot register capability for custom methods"
go clientCaps alreadyStaticallyRegistered
where
go _clientCaps True = pure Nothing
go clientCaps False
| dynamicSupported clientCaps = do
uuid <- liftIO $ UUID.toText <$> getStdRandom random
let registration = J.Registration uuid method regOpts
params = J.RegistrationParams (J.List [J.SomeRegistration registration])
regId = RegistrationId uuid
rio <- askUnliftIO
~() <- case splitClientMethod method of
IsClientNot -> modifyState $ \ctx ->
let newRegs = DMap.insert method pair (resRegistrationsNot ctx)
pair = Pair regId (ClientMessageHandler (unliftIO rio . f))
in ctx { resRegistrationsNot = newRegs }
IsClientReq -> modifyState $ \ctx ->
let newRegs = DMap.insert method pair (resRegistrationsReq ctx)
pair = Pair regId (ClientMessageHandler (\msg k -> unliftIO rio $ f msg (liftIO . k)))
in ctx { resRegistrationsReq = newRegs }
IsClientEither -> error "Cannot register capability for custom methods"
_ <- sendRequest SClientRegisterCapability params $ \_res -> pure ()
pure (Just (RegistrationToken method regId))
| otherwise = pure Nothing
capDyn :: J.HasDynamicRegistration a (Maybe Bool) => Maybe a -> Bool
capDyn (Just x) = fromMaybe False $ x ^. J.dynamicRegistration
capDyn Nothing = False
dynamicSupported clientCaps = case method of
SWorkspaceDidChangeConfiguration -> capDyn $ clientCaps ^? J.workspace . _Just . J.didChangeConfiguration . _Just
SWorkspaceDidChangeWatchedFiles -> capDyn $ clientCaps ^? J.workspace . _Just . J.didChangeWatchedFiles . _Just
SWorkspaceSymbol -> capDyn $ clientCaps ^? J.workspace . _Just . J.symbol . _Just
SWorkspaceExecuteCommand -> capDyn $ clientCaps ^? J.workspace . _Just . J.executeCommand . _Just
STextDocumentDidOpen -> capDyn $ clientCaps ^? J.textDocument . _Just . J.synchronization . _Just
STextDocumentDidChange -> capDyn $ clientCaps ^? J.textDocument . _Just . J.synchronization . _Just
STextDocumentDidClose -> capDyn $ clientCaps ^? J.textDocument . _Just . J.synchronization . _Just
STextDocumentCompletion -> capDyn $ clientCaps ^? J.textDocument . _Just . J.completion . _Just
STextDocumentHover -> capDyn $ clientCaps ^? J.textDocument . _Just . J.hover . _Just
STextDocumentSignatureHelp -> capDyn $ clientCaps ^? J.textDocument . _Just . J.signatureHelp . _Just
STextDocumentDeclaration -> capDyn $ clientCaps ^? J.textDocument . _Just . J.declaration . _Just
STextDocumentDefinition -> capDyn $ clientCaps ^? J.textDocument . _Just . J.definition . _Just
STextDocumentTypeDefinition -> capDyn $ clientCaps ^? J.textDocument . _Just . J.typeDefinition . _Just
STextDocumentImplementation -> capDyn $ clientCaps ^? J.textDocument . _Just . J.implementation . _Just
STextDocumentReferences -> capDyn $ clientCaps ^? J.textDocument . _Just . J.references . _Just
STextDocumentDocumentHighlight -> capDyn $ clientCaps ^? J.textDocument . _Just . J.documentHighlight . _Just
STextDocumentDocumentSymbol -> capDyn $ clientCaps ^? J.textDocument . _Just . J.documentSymbol . _Just
STextDocumentCodeAction -> capDyn $ clientCaps ^? J.textDocument . _Just . J.codeAction . _Just
STextDocumentCodeLens -> capDyn $ clientCaps ^? J.textDocument . _Just . J.codeLens . _Just
STextDocumentDocumentLink -> capDyn $ clientCaps ^? J.textDocument . _Just . J.documentLink . _Just
STextDocumentDocumentColor -> capDyn $ clientCaps ^? J.textDocument . _Just . J.colorProvider . _Just
STextDocumentColorPresentation -> capDyn $ clientCaps ^? J.textDocument . _Just . J.colorProvider . _Just
STextDocumentFormatting -> capDyn $ clientCaps ^? J.textDocument . _Just . J.formatting . _Just
STextDocumentRangeFormatting -> capDyn $ clientCaps ^? J.textDocument . _Just . J.rangeFormatting . _Just
STextDocumentOnTypeFormatting -> capDyn $ clientCaps ^? J.textDocument . _Just . J.onTypeFormatting . _Just
STextDocumentRename -> capDyn $ clientCaps ^? J.textDocument . _Just . J.rename . _Just
STextDocumentFoldingRange -> capDyn $ clientCaps ^? J.textDocument . _Just . J.foldingRange . _Just
STextDocumentSelectionRange -> capDyn $ clientCaps ^? J.textDocument . _Just . J.selectionRange . _Just
_ -> False
unregisterCapability :: MonadLsp config f => RegistrationToken m -> f ()
unregisterCapability (RegistrationToken m (RegistrationId uuid)) = do
~() <- case splitClientMethod m of
IsClientReq -> do
reqRegs <- getsState resRegistrationsReq
let newMap = DMap.delete m reqRegs
modifyState (\ctx -> ctx { resRegistrationsReq = newMap })
IsClientNot -> do
notRegs <- getsState resRegistrationsNot
let newMap = DMap.delete m notRegs
modifyState (\ctx -> ctx { resRegistrationsNot = newMap })
IsClientEither -> error "Cannot unregister capability for custom methods"
let unregistration = J.Unregistration uuid (J.SomeClientMethod m)
params = J.UnregistrationParams (J.List [unregistration])
void $ sendRequest SClientUnregisterCapability params $ \_res -> pure ()
storeProgress :: MonadLsp config m => ProgressToken -> Async a -> m ()
storeProgress n a = do
let f = Map.insert n (cancelWith a ProgressCancelledException) . progressCancel
modifyState $ \ctx -> ctx { resProgressData = (resProgressData ctx) { progressCancel = f (resProgressData ctx)}}
deleteProgress :: MonadLsp config m => ProgressToken -> m ()
deleteProgress n = do
let f = Map.delete n . progressCancel
modifyState $ \ctx -> ctx { resProgressData = (resProgressData ctx) { progressCancel = f (resProgressData ctx)}}
getNewProgressId :: MonadLsp config m => m ProgressToken
getNewProgressId = do
stateState $ \ctx@LanguageContextState{resProgressData} ->
let x = progressNextId resProgressData
ctx' = ctx { resProgressData = resProgressData { progressNextId = x + 1 }}
in (ProgressNumericToken x, ctx')
withProgressBase :: MonadLsp c m => Bool -> Text -> ProgressCancellable -> ((ProgressAmount -> m ()) -> m a) -> m a
withProgressBase indefinite title cancellable f = do
progId <- getNewProgressId
let initialPercentage
| indefinite = Nothing
| otherwise = Just 0
cancellable' = case cancellable of
Cancellable -> True
NotCancellable -> False
_ <- sendRequest SWindowWorkDoneProgressCreate
(WorkDoneProgressCreateParams progId) $ \res -> do
case res of
Left _err -> pure ()
Right () -> pure ()
sendNotification SProgress $
fmap Begin $ ProgressParams progId $
WorkDoneProgressBeginParams title (Just cancellable') Nothing initialPercentage
res <- withRunInIO $ \runInBase ->
E.bracket_
(runInBase $ sendNotification SProgress $
fmap Begin $ ProgressParams progId $
WorkDoneProgressBeginParams title (Just cancellable') Nothing initialPercentage)
(runInBase $ sendNotification SProgress $
End <$> ProgressParams progId (WorkDoneProgressEndParams Nothing)) $ do
aid <- async $ runInBase $ f (updater progId)
runInBase $ storeProgress progId aid
wait aid
deleteProgress progId
return res
where updater progId (ProgressAmount percentage msg) = do
liftIO $ putStrLn "asdf"
sendNotification SProgress $ fmap Report $ ProgressParams progId $
WorkDoneProgressReportParams Nothing msg percentage
clientSupportsProgress :: J.ClientCapabilities -> Bool
clientSupportsProgress (J.ClientCapabilities _ _ wc _) = fromMaybe False $ do
(J.WindowClientCapabilities mProgress) <- wc
mProgress
withProgress :: MonadLsp c m => Text -> ProgressCancellable -> ((ProgressAmount -> m ()) -> m a) -> m a
withProgress title cancellable f = do
clientCaps <- getClientCapabilities
if clientSupportsProgress clientCaps
then withProgressBase False title cancellable f
else f (const $ return ())
withIndefiniteProgress :: MonadLsp c m => Text -> ProgressCancellable -> m a -> m a
withIndefiniteProgress title cancellable f = do
clientCaps <- getClientCapabilities
if clientSupportsProgress clientCaps
then withProgressBase True title cancellable (const f)
else f
publishDiagnostics :: MonadLsp config m => Int -> NormalizedUri -> TextDocumentVersion -> DiagnosticsBySource -> m ()
publishDiagnostics maxDiagnosticCount uri version diags = join $ stateState $ \ctx ->
let ds = updateDiagnostics (resDiagnostics ctx) uri version diags
ctx' = ctx{resDiagnostics = ds}
mdp = getDiagnosticParamsFor maxDiagnosticCount ds uri
act = case mdp of
Nothing -> return ()
Just params ->
sendToClient $ J.fromServerNot $ J.NotificationMessage "2.0" J.STextDocumentPublishDiagnostics params
in (act,ctx')
flushDiagnosticsBySource :: MonadLsp config m => Int
-> Maybe DiagnosticSource -> m ()
flushDiagnosticsBySource maxDiagnosticCount msource = join $ stateState $ \ctx ->
let ds = flushBySource (resDiagnostics ctx) msource
ctx' = ctx {resDiagnostics = ds}
act = forM_ (HM.keys ds) $ \uri -> do
let mdp = getDiagnosticParamsFor maxDiagnosticCount ds uri
case mdp of
Nothing -> return ()
Just params -> do
sendToClient $ J.fromServerNot $ J.NotificationMessage "2.0" J.STextDocumentPublishDiagnostics params
in (act,ctx')
setupLogger :: Maybe FilePath -> [String] -> Priority -> IO ()
setupLogger mLogFile extraLogNames level = do
logStream <- case mLogFile of
Just logFile -> openFile logFile AppendMode `E.catch` handleIOException logFile
Nothing -> return stderr
hSetEncoding logStream utf8
logH <- LHS.streamHandler logStream level
let logHandle = logH {LHS.closeFunc = hClose}
logFormatter = L.tfLogFormatter logDateFormat logFormat
logHandler = LH.setFormatter logHandle logFormatter
L.updateGlobalLogger L.rootLoggerName $ L.setHandlers ([] :: [LHS.GenericHandler Handle])
L.updateGlobalLogger "haskell-lsp" $ L.setHandlers [logHandler]
L.updateGlobalLogger "haskell-lsp" $ L.setLevel level
forM_ extraLogNames $ \logName -> do
L.updateGlobalLogger logName $ L.setHandlers [logHandler]
L.updateGlobalLogger logName $ L.setLevel level
where
logFormat = "$time [$tid] $prio $loggername:\t$msg"
logDateFormat = "%Y-%m-%d %H:%M:%S%Q"
handleIOException :: FilePath -> E.IOException -> IO Handle
handleIOException logFile _ = do
hPutStr stderr $ "Couldn't open log file " ++ logFile ++ "; falling back to stderr logging"
return stderr
reverseSortEdit :: J.WorkspaceEdit -> J.WorkspaceEdit
reverseSortEdit (J.WorkspaceEdit cs dcs) = J.WorkspaceEdit cs' dcs'
where
cs' :: Maybe J.WorkspaceEditMap
cs' = (fmap . fmap ) sortTextEdits cs
dcs' :: Maybe (J.List J.TextDocumentEdit)
dcs' = (fmap . fmap ) sortTextDocumentEdits dcs
sortTextEdits :: J.List J.TextEdit -> J.List J.TextEdit
sortTextEdits (J.List edits) = J.List (L.sortBy down edits)
sortTextDocumentEdits :: J.TextDocumentEdit -> J.TextDocumentEdit
sortTextDocumentEdits (J.TextDocumentEdit td (J.List edits)) = J.TextDocumentEdit td (J.List edits')
where
edits' = L.sortBy down edits
down (J.TextEdit r1 _) (J.TextEdit r2 _) = r2 `compare` r1