{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE MultiWayIf #-}
{-# LANGUAGE BinaryLiterals #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE ScopedTypeVariables #-}
module Language.Haskell.LSP.Core (
handleMessage
, LanguageContextData(..)
, Handler
, InitializeCallbacks(..)
, LspFuncs(..)
, Progress(..)
, ProgressCancellable(..)
, ProgressCancelledException
, SendFunc
, Handlers(..)
, Options(..)
, defaultLanguageContextData
, makeResponseMessage
, makeResponseError
, setupLogger
, sendErrorResponseS
, sendErrorLogS
, sendErrorShowS
, reverseSortEdit
) where
import Control.Concurrent.STM
import Control.Concurrent.Async
import qualified Control.Exception as E
import Control.Monad
import Control.Monad.IO.Class
import Control.Lens ( (<&>), (^.) )
import qualified Data.Aeson as J
import qualified Data.ByteString.Lazy as BSL
import qualified Data.ByteString.Lazy.Char8 as B
import Data.Default
import qualified Data.HashMap.Strict as HM
import qualified Data.List as L
import qualified Data.Map as Map
import Data.Maybe
import Data.Monoid
import qualified Data.Text as T
import Data.Text ( Text )
import Language.Haskell.LSP.Capture
import Language.Haskell.LSP.Constant
import Language.Haskell.LSP.Messages
import qualified Language.Haskell.LSP.Types.Capabilities as C
import qualified Language.Haskell.LSP.Types as J
import qualified Language.Haskell.LSP.Types.Lens as J
import Language.Haskell.LSP.Utility
import Language.Haskell.LSP.VFS
import Language.Haskell.LSP.Diagnostics
import System.Directory
import System.Exit
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
{-# ANN module ("HLint: ignore Eta reduce" :: String) #-}
{-# ANN module ("HLint: ignore Redundant do" :: String) #-}
{-# ANN module ("HLint: ignore Reduce duplication" :: String) #-}
type SendFunc = FromServerMessage -> IO ()
data LanguageContextData config =
LanguageContextData {
resSeqDebugContextData :: !Int
, resHandlers :: !Handlers
, resOptions :: !Options
, resSendResponse :: !SendFunc
, resVFS :: !VFS
, reverseMap :: !(Map.Map FilePath FilePath)
, resDiagnostics :: !DiagnosticStore
, resConfig :: !(Maybe config)
, resLspId :: !(TVar Int)
, resLspFuncs :: LspFuncs config
, resCaptureFile :: !(Maybe FilePath)
, resWorkspaceFolders :: ![J.WorkspaceFolder]
, resProgressData :: !ProgressData
}
data ProgressData = ProgressData { progressNextId :: !Int
, progressCancel :: !(Map.Map J.ProgressToken (IO ())) }
data Options =
Options
{ textDocumentSync :: Maybe J.TextDocumentSyncOptions
, completionProvider :: Maybe J.CompletionOptions
, signatureHelpProvider :: Maybe J.SignatureHelpOptions
, typeDefinitionProvider :: Maybe J.GotoOptions
, implementationProvider :: Maybe J.GotoOptions
, codeActionProvider :: Maybe J.CodeActionOptions
, codeLensProvider :: Maybe J.CodeLensOptions
, documentOnTypeFormattingProvider :: Maybe J.DocumentOnTypeFormattingOptions
, renameProvider :: Maybe J.RenameOptions
, documentLinkProvider :: Maybe J.DocumentLinkOptions
, colorProvider :: Maybe J.ColorOptions
, foldingRangeProvider :: Maybe J.FoldingRangeOptions
, executeCommandProvider :: Maybe J.ExecuteCommandOptions
}
instance Default Options where
def = Options Nothing Nothing Nothing Nothing Nothing
Nothing Nothing Nothing Nothing Nothing
Nothing Nothing Nothing
type PublishDiagnosticsFunc = Int
-> J.NormalizedUri -> J.TextDocumentVersion -> DiagnosticsBySource -> IO ()
type FlushDiagnosticsBySourceFunc = Int
-> Maybe J.DiagnosticSource -> IO ()
data Progress = Progress (Maybe Double) (Maybe Text)
data ProgressCancelledException = ProgressCancelledException
deriving Show
instance E.Exception ProgressCancelledException
data ProgressCancellable = Cancellable | NotCancellable
data LspFuncs c =
LspFuncs
{ clientCapabilities :: !C.ClientCapabilities
, config :: !(IO (Maybe c))
, sendFunc :: !SendFunc
, getVirtualFileFunc :: !(J.NormalizedUri -> IO (Maybe VirtualFile))
, persistVirtualFileFunc :: !(J.NormalizedUri -> IO FilePath)
, reverseFileMapFunc :: !(IO (FilePath -> FilePath))
, publishDiagnosticsFunc :: !PublishDiagnosticsFunc
, flushDiagnosticsBySourceFunc :: !FlushDiagnosticsBySourceFunc
, getNextReqId :: !(IO J.LspId)
, rootPath :: !(Maybe FilePath)
, getWorkspaceFolders :: !(IO (Maybe [J.WorkspaceFolder]))
, withProgress :: !(forall a . Text -> ProgressCancellable
-> ((Progress -> IO ()) -> IO a) -> IO a)
, withIndefiniteProgress :: !(forall a . Text -> ProgressCancellable
-> IO a -> IO a)
}
data InitializeCallbacks config =
InitializeCallbacks
{ onInitialConfiguration :: J.InitializeRequest -> Either T.Text config
, onConfigurationChange :: J.DidChangeConfigurationNotification-> Either T.Text config
, onStartup :: LspFuncs config -> IO (Maybe J.ResponseError)
}
type Handler b = b -> IO ()
data Handlers =
Handlers
{
hoverHandler :: !(Maybe (Handler J.HoverRequest))
, completionHandler :: !(Maybe (Handler J.CompletionRequest))
, completionResolveHandler :: !(Maybe (Handler J.CompletionItemResolveRequest))
, signatureHelpHandler :: !(Maybe (Handler J.SignatureHelpRequest))
, definitionHandler :: !(Maybe (Handler J.DefinitionRequest))
, typeDefinitionHandler :: !(Maybe (Handler J.TypeDefinitionRequest))
, implementationHandler :: !(Maybe (Handler J.ImplementationRequest))
, referencesHandler :: !(Maybe (Handler J.ReferencesRequest))
, documentHighlightHandler :: !(Maybe (Handler J.DocumentHighlightRequest))
, documentSymbolHandler :: !(Maybe (Handler J.DocumentSymbolRequest))
, workspaceSymbolHandler :: !(Maybe (Handler J.WorkspaceSymbolRequest))
, codeActionHandler :: !(Maybe (Handler J.CodeActionRequest))
, codeLensHandler :: !(Maybe (Handler J.CodeLensRequest))
, codeLensResolveHandler :: !(Maybe (Handler J.CodeLensResolveRequest))
, documentColorHandler :: !(Maybe (Handler J.DocumentColorRequest))
, colorPresentationHandler :: !(Maybe (Handler J.ColorPresentationRequest))
, documentFormattingHandler :: !(Maybe (Handler J.DocumentFormattingRequest))
, documentRangeFormattingHandler :: !(Maybe (Handler J.DocumentRangeFormattingRequest))
, documentTypeFormattingHandler :: !(Maybe (Handler J.DocumentOnTypeFormattingRequest))
, renameHandler :: !(Maybe (Handler J.RenameRequest))
, prepareRenameHandler :: !(Maybe (Handler J.PrepareRenameRequest))
, foldingRangeHandler :: !(Maybe (Handler J.FoldingRangeRequest))
, documentLinkHandler :: !(Maybe (Handler J.DocumentLinkRequest))
, documentLinkResolveHandler :: !(Maybe (Handler J.DocumentLinkResolveRequest))
, executeCommandHandler :: !(Maybe (Handler J.ExecuteCommandRequest))
, willSaveWaitUntilTextDocHandler:: !(Maybe (Handler J.WillSaveWaitUntilTextDocumentRequest))
, didChangeConfigurationParamsHandler :: !(Maybe (Handler J.DidChangeConfigurationNotification))
, didOpenTextDocumentNotificationHandler :: !(Maybe (Handler J.DidOpenTextDocumentNotification))
, didChangeTextDocumentNotificationHandler :: !(Maybe (Handler J.DidChangeTextDocumentNotification))
, didCloseTextDocumentNotificationHandler :: !(Maybe (Handler J.DidCloseTextDocumentNotification))
, didSaveTextDocumentNotificationHandler :: !(Maybe (Handler J.DidSaveTextDocumentNotification))
, didChangeWatchedFilesNotificationHandler :: !(Maybe (Handler J.DidChangeWatchedFilesNotification))
, didChangeWorkspaceFoldersNotificationHandler :: !(Maybe (Handler J.DidChangeWorkspaceFoldersNotification))
, initializedHandler :: !(Maybe (Handler J.InitializedNotification))
, willSaveTextDocumentNotificationHandler :: !(Maybe (Handler J.WillSaveTextDocumentNotification))
, cancelNotificationHandler :: !(Maybe (Handler J.CancelNotification))
, responseHandler :: !(Maybe (Handler J.BareResponseMessage))
, initializeRequestHandler :: !(Maybe (Handler J.InitializeRequest))
, exitNotificationHandler :: !(Maybe (Handler J.ExitNotification))
, customRequestHandler :: !(Maybe (Handler J.CustomClientRequest))
, customNotificationHandler :: !(Maybe (Handler J.CustomClientNotification))
}
instance Default Handlers where
def = nothings { didChangeTextDocumentNotificationHandler = Just ignore
, didOpenTextDocumentNotificationHandler = Just ignore
, didCloseTextDocumentNotificationHandler = Just ignore
}
where ignore = const (pure ())
nothings = Handlers Nothing Nothing Nothing Nothing Nothing Nothing
Nothing Nothing Nothing Nothing Nothing Nothing
Nothing Nothing Nothing Nothing Nothing Nothing
Nothing Nothing Nothing Nothing Nothing Nothing
Nothing Nothing Nothing Nothing Nothing Nothing
Nothing Nothing Nothing Nothing Nothing Nothing
Nothing Nothing Nothing Nothing Nothing
nop :: a -> b -> IO a
nop = const . return
helper :: J.FromJSON a => (TVar (LanguageContextData config) -> a -> IO ()) -> (TVar (LanguageContextData config) -> J.Value -> IO ())
helper requestHandler tvarDat json =
case J.fromJSON json of
J.Success req -> requestHandler tvarDat req
J.Error err -> do
let msg = T.pack . unwords $ ["haskell-lsp:parse error.", show json, show err] ++ _ERR_MSG_URL
failLog = sendErrorLog tvarDat msg
case json of
(J.Object o) -> case HM.lookup "id" o of
Just olid -> case J.fromJSON olid of
J.Success lid -> sendErrorResponse tvarDat lid msg
_ -> failLog
_ -> failLog
_ -> failLog
handlerMap :: (Show config) => InitializeCallbacks config
-> Handlers -> J.ClientMethod -> (TVar (LanguageContextData config) -> J.Value -> IO ())
handlerMap i h J.Initialize = handleInitialConfig i (initializeRequestHandler h)
handlerMap _ h J.Initialized = hh nop NotInitialized $ initializedHandler h
handlerMap _ _ J.Shutdown = helper shutdownRequestHandler
handlerMap _ h J.Exit =
case exitNotificationHandler h of
Just _ -> hh nop NotExit $ exitNotificationHandler h
Nothing -> \ctxVar v -> do
ctx <- readTVarIO ctxVar
case J.fromJSON v :: J.Result J.ExitNotification of
J.Success n -> captureFromClient (NotExit n) (resCaptureFile ctx)
J.Error _ -> return ()
logm $ B.pack "haskell-lsp:Got exit, exiting"
exitSuccess
handlerMap _ h J.CancelRequest = hh nop NotCancelRequestFromClient $ cancelNotificationHandler h
handlerMap _ h J.WorkspaceDidChangeWorkspaceFolders = hwf $ didChangeWorkspaceFoldersNotificationHandler h
handlerMap i h J.WorkspaceDidChangeConfiguration = hc i $ didChangeConfigurationParamsHandler h
handlerMap _ h J.WorkspaceDidChangeWatchedFiles = hh nop NotDidChangeWatchedFiles $ didChangeWatchedFilesNotificationHandler h
handlerMap _ h J.WorkspaceSymbol = hh nop ReqWorkspaceSymbols $ workspaceSymbolHandler h
handlerMap _ h J.WorkspaceExecuteCommand = hh nop ReqExecuteCommand $ executeCommandHandler h
handlerMap _ h J.TextDocumentDidOpen = hh openVFS NotDidOpenTextDocument $ didOpenTextDocumentNotificationHandler h
handlerMap _ h J.TextDocumentDidChange = hh changeFromClientVFS NotDidChangeTextDocument $ didChangeTextDocumentNotificationHandler h
handlerMap _ h J.TextDocumentWillSave = hh nop NotWillSaveTextDocument $ willSaveTextDocumentNotificationHandler h
handlerMap _ h J.TextDocumentWillSaveWaitUntil = hh nop ReqWillSaveWaitUntil $ willSaveWaitUntilTextDocHandler h
handlerMap _ h J.TextDocumentDidSave = hh nop NotDidSaveTextDocument $ didSaveTextDocumentNotificationHandler h
handlerMap _ h J.TextDocumentDidClose = hh closeVFS NotDidCloseTextDocument $ didCloseTextDocumentNotificationHandler h
handlerMap _ h J.TextDocumentCompletion = hh nop ReqCompletion $ completionHandler h
handlerMap _ h J.CompletionItemResolve = hh nop ReqCompletionItemResolve $ completionResolveHandler h
handlerMap _ h J.TextDocumentHover = hh nop ReqHover $ hoverHandler h
handlerMap _ h J.TextDocumentSignatureHelp = hh nop ReqSignatureHelp $ signatureHelpHandler h
handlerMap _ h J.TextDocumentDefinition = hh nop ReqDefinition $ definitionHandler h
handlerMap _ h J.TextDocumentTypeDefinition = hh nop ReqTypeDefinition $ typeDefinitionHandler h
handlerMap _ h J.TextDocumentImplementation = hh nop ReqImplementation $ implementationHandler h
handlerMap _ h J.TextDocumentReferences = hh nop ReqFindReferences $ referencesHandler h
handlerMap _ h J.TextDocumentDocumentHighlight = hh nop ReqDocumentHighlights $ documentHighlightHandler h
handlerMap _ h J.TextDocumentDocumentSymbol = hh nop ReqDocumentSymbols $ documentSymbolHandler h
handlerMap _ h J.TextDocumentFormatting = hh nop ReqDocumentFormatting $ documentFormattingHandler h
handlerMap _ h J.TextDocumentRangeFormatting = hh nop ReqDocumentRangeFormatting $ documentRangeFormattingHandler h
handlerMap _ h J.TextDocumentOnTypeFormatting = hh nop ReqDocumentOnTypeFormatting $ documentTypeFormattingHandler h
handlerMap _ h J.TextDocumentCodeAction = hh nop ReqCodeAction $ codeActionHandler h
handlerMap _ h J.TextDocumentCodeLens = hh nop ReqCodeLens $ codeLensHandler h
handlerMap _ h J.CodeLensResolve = hh nop ReqCodeLensResolve $ codeLensResolveHandler h
handlerMap _ h J.TextDocumentDocumentColor = hh nop ReqDocumentColor $ documentColorHandler h
handlerMap _ h J.TextDocumentColorPresentation = hh nop ReqColorPresentation $ colorPresentationHandler h
handlerMap _ h J.TextDocumentDocumentLink = hh nop ReqDocumentLink $ documentLinkHandler h
handlerMap _ h J.DocumentLinkResolve = hh nop ReqDocumentLinkResolve $ documentLinkResolveHandler h
handlerMap _ h J.TextDocumentRename = hh nop ReqRename $ renameHandler h
handlerMap _ h J.TextDocumentPrepareRename = hh nop ReqPrepareRename $ prepareRenameHandler h
handlerMap _ h J.TextDocumentFoldingRange = hh nop ReqFoldingRange $ foldingRangeHandler h
handlerMap _ _ J.WorkDoneProgressCancel = helper progressCancelHandler
handlerMap _ h (J.CustomClientMethod _) = \ctxData val ->
case val of
J.Object o | "id" `HM.member` o ->
hh nop ReqCustomClient (customRequestHandler h) ctxData val
_ ->
hh nop NotCustomClient (customNotificationHandler h) ctxData val
hh :: forall b config. (J.FromJSON b)
=> (VFS -> b -> IO VFS) -> (b -> FromClientMessage) -> Maybe (Handler b) -> TVar (LanguageContextData config) -> J.Value -> IO ()
hh getVfs wrapper mh tvarDat json = do
case J.fromJSON json of
J.Success req -> do
ctx <- readTVarIO tvarDat
vfs' <- getVfs (resVFS ctx) req
atomically $ modifyTVar' tvarDat (\c -> c {resVFS = vfs'})
captureFromClient (wrapper req) (resCaptureFile ctx)
case mh of
Just h -> h req
Nothing -> do
let msg = T.pack $ unwords ["haskell-lsp:no handler for.", show json]
sendErrorLog tvarDat msg
J.Error err -> do
let msg = T.pack $ unwords $ ["haskell-lsp:parse error.", show json, show err] ++ _ERR_MSG_URL
sendErrorLog tvarDat msg
handleInitialConfig
:: (Show config)
=> InitializeCallbacks config
-> Maybe (Handler J.InitializeRequest)
-> TVar (LanguageContextData config)
-> J.Value
-> IO ()
handleInitialConfig (InitializeCallbacks { onInitialConfiguration, onStartup }) mh tvarDat json
= handleMessageWithConfigChange ReqInitialize
onInitialConfiguration
(Just $ initializeRequestHandler' onStartup mh tvarDat)
tvarDat
json
hc
:: (Show config)
=> InitializeCallbacks config
-> Maybe (Handler J.DidChangeConfigurationNotification)
-> TVar (LanguageContextData config)
-> J.Value
-> IO ()
hc (InitializeCallbacks { onConfigurationChange }) mh tvarDat json =
handleMessageWithConfigChange NotDidChangeConfiguration
onConfigurationChange
mh
tvarDat
json
handleMessageWithConfigChange
:: (J.FromJSON reqParams, Show reqParams, Show err)
=> (reqParams -> FromClientMessage)
-> (reqParams -> Either err config)
-> Maybe (reqParams -> IO ())
-> TVar (LanguageContextData config)
-> J.Value
-> IO ()
handleMessageWithConfigChange notification parseConfig mh tvarDat json =
case J.fromJSON json of
J.Success req -> do
ctx <- readTVarIO tvarDat
captureFromClient (notification req) (resCaptureFile ctx)
case parseConfig req of
Left err -> do
let
msg =
T.pack $ unwords
["haskell-lsp:configuration parse error.", show req, show err]
sendErrorLog tvarDat msg
Right newConfig -> do
let ctx' = ctx { resConfig = Just newConfig }
atomically $ modifyTVar' tvarDat (const ctx')
case mh of
Just h -> h req
Nothing -> return ()
J.Error err -> do
let msg =
T.pack
$ unwords
$ ["haskell-lsp:parse error.", show json, show err]
++ _ERR_MSG_URL
sendErrorLog tvarDat msg
hwf :: Maybe (Handler J.DidChangeWorkspaceFoldersNotification) -> TVar (LanguageContextData config) -> J.Value -> IO ()
hwf h tvarDat json = do
case J.fromJSON json :: J.Result J.DidChangeWorkspaceFoldersNotification of
J.Success (J.NotificationMessage _ _ params) -> atomically $ do
oldWfs <- resWorkspaceFolders <$> readTVar tvarDat
let J.List toRemove = params ^. J.event . J.removed
wfs0 = foldr L.delete oldWfs toRemove
J.List toAdd = params ^. J.event . J.added
wfs1 = wfs0 <> toAdd
modifyTVar' tvarDat (\c -> c {resWorkspaceFolders = wfs1})
_ -> return ()
hh nop NotDidChangeWorkspaceFolders h tvarDat json
getVirtualFile :: TVar (LanguageContextData config) -> J.NormalizedUri -> IO (Maybe VirtualFile)
getVirtualFile tvarDat uri = Map.lookup uri . resVFS <$> readTVarIO tvarDat
persistVirtualFile :: TVar (LanguageContextData config) -> J.NormalizedUri -> IO FilePath
persistVirtualFile tvarDat uri = do
st <- readTVarIO tvarDat
let vfs = resVFS st
revMap = reverseMap st
(fn, new_vfs) <- persistFileVFS vfs uri
let revMap' =
case J.uriToFilePath (J.fromNormalizedUri uri) of
Just uri_fp -> Map.insert fn uri_fp revMap
Nothing -> revMap
atomically $ modifyTVar' tvarDat (\d -> d { resVFS = new_vfs
, reverseMap = revMap' })
return fn
reverseFileMap :: TVar (LanguageContextData config)
-> IO (FilePath -> FilePath)
reverseFileMap tvarDat = do
revMap <- reverseMap <$> readTVarIO tvarDat
let f fp = fromMaybe fp $ Map.lookup fp revMap
return f
getConfig :: TVar (LanguageContextData config) -> IO (Maybe config)
getConfig tvar = resConfig <$> readTVarIO tvar
_INITIAL_RESPONSE_SEQUENCE :: Int
_INITIAL_RESPONSE_SEQUENCE = 0
_SEP_WIN :: Char
_SEP_WIN = '\\'
_SEP_UNIX :: Char
_SEP_UNIX = '/'
_ERR_MSG_URL :: [String]
_ERR_MSG_URL = [ "`stack update` and install new haskell-lsp."
, "Or check information on https://marketplace.visualstudio.com/items?itemName=xxxxxxxxxxxxxxx"
]
defaultLanguageContextData :: Handlers -> Options -> LspFuncs config -> TVar Int -> SendFunc -> Maybe FilePath -> LanguageContextData config
defaultLanguageContextData h o lf tv sf cf =
LanguageContextData _INITIAL_RESPONSE_SEQUENCE h o sf mempty mempty mempty
Nothing tv lf cf mempty defaultProgressData
defaultProgressData :: ProgressData
defaultProgressData = ProgressData 0 Map.empty
handleMessage :: (Show config) => InitializeCallbacks config
-> TVar (LanguageContextData config) -> BSL.ByteString -> IO ()
handleMessage dispatcherProc tvarDat jsonStr = do
case J.eitherDecode jsonStr :: Either String J.Object of
Left err -> do
let msg = T.pack $ unwords [ "haskell-lsp:incoming message parse error.", lbs2str jsonStr, show err]
++ L.intercalate "\n" ("" : "" : _ERR_MSG_URL)
++ "\n"
sendErrorLog tvarDat msg
Right o -> do
case HM.lookup "method" o of
Just cmd@(J.String s) -> case J.fromJSON cmd of
J.Success m -> handle (J.Object o) m
J.Error _ -> do
let msg = T.pack $ unwords ["haskell-lsp:unknown message received:method='"
++ T.unpack s ++ "',", lbs2str jsonStr]
sendErrorLog tvarDat msg
Just oops -> logs $ "haskell-lsp:got strange method param, ignoring:" ++ show oops
Nothing -> do
logs $ "haskell-lsp:Got reply message:" ++ show jsonStr
handleResponse (J.Object o)
where
handleResponse json = do
ctx <- readTVarIO tvarDat
case responseHandler $ resHandlers ctx of
Nothing -> sendErrorLog tvarDat $ T.pack $ "haskell-lsp: responseHandler is not defined, ignoring response " ++ lbs2str jsonStr
Just h -> case J.fromJSON json of
J.Success res -> h res
J.Error err -> let msg = T.pack $ unwords $ ["haskell-lsp:response parse error.", lbs2str jsonStr, show err] ++ _ERR_MSG_URL
in sendErrorLog tvarDat msg
handle json cmd = do
ctx <- readTVarIO tvarDat
let h = resHandlers ctx
handlerMap dispatcherProc h cmd tvarDat json
makeResponseMessage :: J.RequestMessage J.ClientMethod req resp -> resp -> J.ResponseMessage resp
makeResponseMessage req result = J.ResponseMessage "2.0" (J.responseId $ req ^. J.id) (Just result) Nothing
makeResponseError :: J.LspIdRsp -> J.ResponseError -> J.ResponseMessage ()
makeResponseError origId err = J.ResponseMessage "2.0" origId Nothing (Just err)
sendEvent :: TVar (LanguageContextData config) -> FromServerMessage -> IO ()
sendEvent tvarCtx msg = sendResponse tvarCtx msg
sendResponse :: TVar (LanguageContextData config) -> FromServerMessage -> IO ()
sendResponse tvarCtx msg = do
ctx <- readTVarIO tvarCtx
resSendResponse ctx msg
sendErrorResponse :: TVar (LanguageContextData config) -> J.LspIdRsp -> Text -> IO ()
sendErrorResponse tv origId msg = sendErrorResponseS (sendEvent tv) origId J.InternalError msg
sendErrorResponseS :: SendFunc -> J.LspIdRsp -> J.ErrorCode -> Text -> IO ()
sendErrorResponseS sf origId err msg = do
sf $ RspError (J.ResponseMessage "2.0" origId Nothing
(Just $ J.ResponseError err msg Nothing) :: J.ErrorResponse)
sendErrorLog :: TVar (LanguageContextData config) -> Text -> IO ()
sendErrorLog tv msg = sendErrorLogS (sendEvent tv) msg
sendErrorLogS :: SendFunc -> Text -> IO ()
sendErrorLogS sf msg =
sf $ NotLogMessage $ fmServerLogMessageNotification J.MtError msg
sendErrorShowS :: SendFunc -> Text -> IO ()
sendErrorShowS sf msg =
sf $ NotShowMessage $ fmServerShowMessageNotification J.MtError msg
defaultErrorHandlers :: (Show a) => TVar (LanguageContextData config) -> J.LspIdRsp -> a -> [E.Handler ()]
defaultErrorHandlers tvarDat origId req = [ E.Handler someExcept ]
where
someExcept (e :: E.SomeException) = do
let msg = T.pack $ unwords ["request error.", show req, show e]
sendErrorResponse tvarDat origId msg
sendErrorLog tvarDat msg
initializeRequestHandler'
:: (Show config)
=> (LspFuncs config -> IO (Maybe J.ResponseError))
-> Maybe (Handler J.InitializeRequest)
-> TVar (LanguageContextData config)
-> J.InitializeRequest
-> IO ()
initializeRequestHandler' onStartup mHandler tvarCtx req@(J.RequestMessage _ origId _ params) =
flip E.catches (defaultErrorHandlers tvarCtx (J.responseId origId) req) $ do
case mHandler of
Just handler -> handler req
Nothing -> return ()
let wfs = case params ^. J.workspaceFolders of
Just (J.List xs) -> xs
Nothing -> []
atomically $ modifyTVar' tvarCtx (\c -> c { resWorkspaceFolders = wfs })
ctx0 <- readTVarIO tvarCtx
let rootDir = getFirst $ foldMap First [ params ^. J.rootUri >>= J.uriToFilePath
, params ^. J.rootPath <&> T.unpack ]
case rootDir of
Nothing -> return ()
Just dir -> do
logs $ "haskell-lsp:initializeRequestHandler: setting current dir to project root:" ++ dir
unless (null dir) $ setCurrentDirectory dir
let
getCapabilities :: J.InitializeParams -> C.ClientCapabilities
getCapabilities (J.InitializeParams _ _ _ _ c _ _) = c
getLspId tvId = atomically $ do
cid <- readTVar tvId
modifyTVar' tvId (+1)
return $ J.IdInt cid
clientSupportsWfs = fromMaybe False $ do
let (C.ClientCapabilities mw _ _ _) = params ^. J.capabilities
(C.WorkspaceClientCapabilities _ _ _ _ _ _ mwf _) <- mw
mwf
getWfs tvc
| clientSupportsWfs = atomically $ Just . resWorkspaceFolders <$> readTVar tvc
| otherwise = return Nothing
clientSupportsProgress = fromMaybe False $ do
let (C.ClientCapabilities _ _ wc _) = params ^. J.capabilities
(C.WindowClientCapabilities mProgress) <- wc
mProgress
storeProgress :: J.ProgressToken -> Async a -> IO ()
storeProgress n a = atomically $ do
pd <- resProgressData <$> readTVar tvarCtx
let pc = progressCancel pd
pc' = Map.insert n (cancelWith a ProgressCancelledException) pc
modifyTVar tvarCtx (\ctx -> ctx { resProgressData = pd { progressCancel = pc' }})
deleteProgress :: J.ProgressToken -> IO ()
deleteProgress n = atomically $ do
pd <- resProgressData <$> readTVar tvarCtx
let x = progressCancel pd
x' = Map.delete n x
modifyTVar tvarCtx (\ctx -> ctx { resProgressData = pd { progressCancel = x' }})
getNewProgressId :: IO J.ProgressToken
getNewProgressId = liftIO $ atomically $ do
pd <- resProgressData <$> readTVar tvarCtx
let x = progressNextId pd
modifyTVar tvarCtx (\ctx -> ctx { resProgressData = pd { progressNextId = x + 1 }})
return $ J.ProgressNumericToken x
withProgressBase :: Bool -> (Text -> ProgressCancellable
-> ((Progress -> IO ()) -> IO a) -> IO a)
withProgressBase indefinite title cancellable f
| clientSupportsProgress = do
sf <- liftIO $ resSendResponse <$> readTVarIO tvarCtx
progId <- getNewProgressId
let initialPercentage
| indefinite = Nothing
| otherwise = Just 0
cancellable' = case cancellable of
Cancellable -> True
NotCancellable -> False
rId <- getLspId $ resLspId ctx0
liftIO $ sf $ ReqWorkDoneProgressCreate $
fmServerWorkDoneProgressCreateRequest rId $ J.WorkDoneProgressCreateParams progId
liftIO $ sf $ NotWorkDoneProgressBegin $ fmServerWorkDoneProgressBeginNotification $
J.ProgressParams progId $
J.WorkDoneProgressBeginParams title (Just cancellable') Nothing initialPercentage
aid <- async $ f (updater progId sf)
storeProgress progId aid
res <- wait aid
liftIO $ sf $ NotWorkDoneProgressEnd $ fmServerWorkDoneProgressEndNotification $
J.ProgressParams progId $
J.WorkDoneProgressEndParams Nothing
deleteProgress progId
return res
| otherwise = f (const $ return ())
where updater progId sf (Progress percentage msg) =
sf $ NotWorkDoneProgressReport $ fmServerWorkDoneProgressReportNotification $
J.ProgressParams progId $
J.WorkDoneProgressReportParams Nothing msg percentage
withProgress' :: Text -> ProgressCancellable -> ((Progress -> IO ()) -> IO a) -> IO a
withProgress' = withProgressBase False
withIndefiniteProgress' :: Text -> ProgressCancellable -> IO a -> IO a
withIndefiniteProgress' title cancellable f =
withProgressBase True title cancellable (const f)
let lspFuncs = LspFuncs (getCapabilities params)
(getConfig tvarCtx)
(resSendResponse ctx0)
(getVirtualFile tvarCtx)
(persistVirtualFile tvarCtx)
(reverseFileMap tvarCtx)
(publishDiagnostics tvarCtx)
(flushDiagnosticsBySource tvarCtx)
(getLspId $ resLspId ctx0)
rootDir
(getWfs tvarCtx)
withProgress'
withIndefiniteProgress'
let ctx = ctx0 { resLspFuncs = lspFuncs }
atomically $ writeTVar tvarCtx ctx
initializationResult <- onStartup lspFuncs
case initializationResult of
Just errResp -> do
sendResponse tvarCtx $ RspError $ makeResponseError (J.responseId origId) errResp
Nothing -> do
let
h = resHandlers ctx
o = resOptions ctx
supported (Just _) = Just True
supported Nothing = Nothing
static (Just d) _ = Just d
static _ (Just _) = Just (J.GotoOptionsStatic True)
static _ Nothing = Nothing
static' (Just d) (Just _) = Just d
static' _ (Just _) = Just (J.CodeActionOptionsStatic True)
static' _ _ = Nothing
sync = case textDocumentSync o of
Just x -> Just (J.TDSOptions x)
Nothing -> Nothing
workspace = J.WorkspaceOptions workspaceFolder
workspaceFolder = case didChangeWorkspaceFoldersNotificationHandler h of
Just _ -> Just $
J.WorkspaceFolderOptions (Just True) (Just (J.WorkspaceFolderChangeNotificationsBool True))
Nothing -> Nothing
capa =
J.InitializeResponseCapabilitiesInner
{ J._textDocumentSync = sync
, J._hoverProvider = supported (hoverHandler h)
, J._completionProvider = completionProvider o
, J._signatureHelpProvider = signatureHelpProvider o
, J._definitionProvider = supported (definitionHandler h)
, J._typeDefinitionProvider = static (typeDefinitionProvider o) (typeDefinitionHandler h)
, J._implementationProvider = implementationProvider o
, J._referencesProvider = supported (referencesHandler h)
, J._documentHighlightProvider = supported (documentHighlightHandler h)
, J._documentSymbolProvider = supported (documentSymbolHandler h)
, J._workspaceSymbolProvider = supported (workspaceSymbolHandler h)
, J._codeActionProvider = static' (codeActionProvider o) (codeActionHandler h)
, J._codeLensProvider = codeLensProvider o
, J._documentFormattingProvider = supported (documentFormattingHandler h)
, J._documentRangeFormattingProvider = supported (documentRangeFormattingHandler h)
, J._documentOnTypeFormattingProvider = documentOnTypeFormattingProvider o
, J._renameProvider = renameProvider o
, J._documentLinkProvider = documentLinkProvider o
, J._colorProvider = colorProvider o
, J._foldingRangeProvider = foldingRangeProvider o
, J._executeCommandProvider = executeCommandProvider o
, J._workspace = Just workspace
, J._experimental = Nothing :: Maybe J.Value
}
res = J.ResponseMessage "2.0" (J.responseId origId) (Just $ J.InitializeResponseCapabilities capa) Nothing
sendResponse tvarCtx $ RspInitialize res
progressCancelHandler :: TVar (LanguageContextData config) -> J.WorkDoneProgressCancelNotification -> IO ()
progressCancelHandler tvarCtx (J.NotificationMessage _ _ (J.WorkDoneProgressCancelParams tid)) = do
mact <- Map.lookup tid . progressCancel . resProgressData <$> readTVarIO tvarCtx
case mact of
Nothing -> return ()
Just cancelAction -> cancelAction
shutdownRequestHandler :: TVar (LanguageContextData config) -> J.ShutdownRequest -> IO ()
shutdownRequestHandler tvarCtx req@(J.RequestMessage _ origId _ _) =
flip E.catches (defaultErrorHandlers tvarCtx (J.responseId origId) req) $ do
let res = makeResponseMessage req Nothing
sendResponse tvarCtx $ RspShutdown res
publishDiagnostics :: TVar (LanguageContextData config) -> PublishDiagnosticsFunc
publishDiagnostics tvarDat maxDiagnosticCount uri version diags = do
ctx <- readTVarIO tvarDat
let ds = updateDiagnostics (resDiagnostics ctx) uri version diags
atomically $ writeTVar tvarDat $ ctx{resDiagnostics = ds}
let mdp = getDiagnosticParamsFor maxDiagnosticCount ds uri
case mdp of
Nothing -> return ()
Just params -> do
resSendResponse ctx $ NotPublishDiagnostics
$ J.NotificationMessage "2.0" J.TextDocumentPublishDiagnostics params
flushDiagnosticsBySource :: TVar (LanguageContextData config) -> FlushDiagnosticsBySourceFunc
flushDiagnosticsBySource tvarDat maxDiagnosticCount msource = do
ctx <- readTVarIO tvarDat
let ds = flushBySource (resDiagnostics ctx) msource
atomically $ writeTVar tvarDat $ ctx {resDiagnostics = ds}
forM_ (Map.keys ds) $ \uri -> do
let mdp = getDiagnosticParamsFor maxDiagnosticCount ds uri
case mdp of
Nothing -> return ()
Just params -> do
resSendResponse ctx $ NotPublishDiagnostics
$ J.NotificationMessage "2.0" J.TextDocumentPublishDiagnostics params
setupLogger :: Maybe FilePath -> [String] -> Priority -> IO ()
setupLogger mLogFile extraLogNames level = do
logStream <- case mLogFile of
Just logFile -> openFile logFile AppendMode
Nothing -> return stderr
hSetEncoding logStream utf8
logH <- LHS.streamHandler logStream level
let logHandle = logH {LHS.closeFunc = hClose}
logFormat = L.tfLogFormatter _LOG_FORMAT_DATE _LOG_FORMAT
logHandler = LH.setFormatter logHandle logFormat
L.updateGlobalLogger L.rootLoggerName $ L.setHandlers ([] :: [LHS.GenericHandler Handle])
L.updateGlobalLogger _LOG_NAME $ L.setHandlers [logHandler]
L.updateGlobalLogger _LOG_NAME $ L.setLevel level
forM_ extraLogNames $ \logName -> do
L.updateGlobalLogger logName $ L.setHandlers [logHandler]
L.updateGlobalLogger logName $ L.setLevel level
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