1{-# LANGUAGE GeneralizedNewtypeDeriving #-} 2{-# LANGUAGE TypeFamilyDependencies #-} 3{-# LANGUAGE UndecidableInstances #-} 4{-# LANGUAGE BangPatterns #-} 5{-# LANGUAGE LambdaCase #-} 6{-# LANGUAGE GADTs #-} 7{-# LANGUAGE MultiWayIf #-} 8{-# LANGUAGE BinaryLiterals #-} 9{-# LANGUAGE OverloadedStrings #-} 10{-# LANGUAGE RankNTypes #-} 11{-# LANGUAGE ScopedTypeVariables #-} 12{-# LANGUAGE TypeFamilies #-} 13{-# LANGUAGE FlexibleContexts #-} 14{-# LANGUAGE ViewPatterns #-} 15{-# LANGUAGE TypeInType #-} 16{-# LANGUAGE TypeApplications #-} 17{-# LANGUAGE RecordWildCards #-} 18{-# LANGUAGE NamedFieldPuns #-} 19{-# LANGUAGE FlexibleInstances #-} 20{-# LANGUAGE MultiParamTypeClasses #-} 21{-# LANGUAGE FunctionalDependencies #-} 22{-# LANGUAGE TypeOperators #-} 23{-# LANGUAGE RecursiveDo #-} 24{-# LANGUAGE RoleAnnotations #-} 25{-# OPTIONS_GHC -Wno-unticked-promoted-constructors #-} 26{-# OPTIONS_GHC -fprint-explicit-kinds #-} 27 28 29module Language.LSP.Server.Core where 30 31import Control.Concurrent.Async 32import Control.Concurrent.STM 33import qualified Control.Exception as E 34import Control.Monad 35import Control.Monad.Fix 36import Control.Monad.IO.Class 37import Control.Monad.Trans.Reader 38import Control.Monad.Trans.Class 39import Control.Monad.IO.Unlift 40import Control.Lens ( (^.), (^?), _Just ) 41import qualified Data.Aeson as J 42import Data.Default 43import Data.Functor.Product 44import Data.IxMap 45import qualified Data.Dependent.Map as DMap 46import Data.Dependent.Map (DMap) 47import qualified Data.HashMap.Strict as HM 48import Data.Kind 49import qualified Data.List as L 50import Data.List.NonEmpty (NonEmpty(..)) 51import qualified Data.Map.Strict as Map 52import Data.Maybe 53import Data.Ord (Down (Down)) 54import qualified Data.Text as T 55import Data.Text ( Text ) 56import qualified Data.UUID as UUID 57import qualified Language.LSP.Types.Capabilities as J 58import Language.LSP.Types as J 59import qualified Language.LSP.Types.Lens as J 60import Language.LSP.VFS 61import Language.LSP.Diagnostics 62import System.IO 63import qualified System.Log.Formatter as L 64import qualified System.Log.Handler as LH 65import qualified System.Log.Handler.Simple as LHS 66import System.Log.Logger 67import qualified System.Log.Logger as L 68import System.Random hiding (next) 69import Control.Monad.Trans.Identity 70import Control.Monad.Catch (MonadMask, MonadCatch, MonadThrow) 71 72-- --------------------------------------------------------------------- 73{-# ANN module ("HLint: ignore Eta reduce" :: String) #-} 74{-# ANN module ("HLint: ignore Redundant do" :: String) #-} 75{-# ANN module ("HLint: ignore Reduce duplication" :: String) #-} 76-- --------------------------------------------------------------------- 77 78newtype LspT config m a = LspT { unLspT :: ReaderT (LanguageContextEnv config) m a } 79 deriving (Functor, Applicative, Monad, MonadCatch, MonadIO, MonadMask, MonadThrow, MonadTrans, MonadUnliftIO, MonadFix) 80 81-- for deriving the instance of MonadUnliftIO 82type role LspT representational representational nominal 83 84runLspT :: LanguageContextEnv config -> LspT config m a -> m a 85runLspT env = flip runReaderT env . unLspT 86 87{-# INLINE runLspT #-} 88 89type LspM config = LspT config IO 90 91class MonadUnliftIO m => MonadLsp config m | m -> config where 92 getLspEnv :: m (LanguageContextEnv config) 93 94instance MonadUnliftIO m => MonadLsp config (LspT config m) where 95 {-# SPECIALIZE instance MonadLsp config (LspT config IO) #-} 96 {-# INLINE getLspEnv #-} 97 getLspEnv = LspT ask 98 99instance MonadLsp c m => MonadLsp c (ReaderT r m) where 100 {-# SPECIALIZE instance MonadLsp config (ReaderT r (LspT config IO)) #-} 101 {-# INLINE getLspEnv #-} 102 getLspEnv = lift getLspEnv 103 104instance MonadLsp c m => MonadLsp c (IdentityT m) where 105 getLspEnv = lift getLspEnv 106 107data LanguageContextEnv config = 108 LanguageContextEnv 109 { resHandlers :: !(Handlers IO) 110 , resParseConfig :: !(config -> J.Value -> (Either T.Text config)) 111 , resSendMessage :: !(FromServerMessage -> IO ()) 112 -- We keep the state in a TVar to be thread safe 113 , resState :: !(LanguageContextState config) 114 , resClientCapabilities :: !J.ClientCapabilities 115 , resRootPath :: !(Maybe FilePath) 116 } 117 118-- --------------------------------------------------------------------- 119-- Handlers 120-- --------------------------------------------------------------------- 121 122-- | A mapping from methods to the static 'Handler's that should be used to 123-- handle responses when they come in from the client. To build up a 'Handlers', 124-- you should 'mconcat' a list of 'notificationHandler' and 'requestHandler's: 125-- 126-- @ 127-- mconcat [ 128-- notificationHandler SInitialized $ \notif -> pure () 129-- , requestHandler STextDocumentHover $ \req responder -> pure () 130-- ] 131-- @ 132data Handlers m 133 = Handlers 134 { reqHandlers :: !(DMap SMethod (ClientMessageHandler m Request)) 135 , notHandlers :: !(DMap SMethod (ClientMessageHandler m Notification)) 136 } 137instance Semigroup (Handlers config) where 138 Handlers r1 n1 <> Handlers r2 n2 = Handlers (r1 <> r2) (n1 <> n2) 139instance Monoid (Handlers config) where 140 mempty = Handlers mempty mempty 141 142notificationHandler :: forall (m :: Method FromClient Notification) f. SMethod m -> Handler f m -> Handlers f 143notificationHandler m h = Handlers mempty (DMap.singleton m (ClientMessageHandler h)) 144 145requestHandler :: forall (m :: Method FromClient Request) f. SMethod m -> Handler f m -> Handlers f 146requestHandler m h = Handlers (DMap.singleton m (ClientMessageHandler h)) mempty 147 148-- | Wrapper to restrict 'Handler's to 'FromClient' 'Method's 149newtype ClientMessageHandler f (t :: MethodType) (m :: Method FromClient t) = ClientMessageHandler (Handler f m) 150 151-- | The type of a handler that handles requests and notifications coming in 152-- from the server or client 153type family Handler (f :: Type -> Type) (m :: Method from t) = (result :: Type) | result -> f t m where 154 Handler f (m :: Method _from Request) = RequestMessage m -> (Either ResponseError (ResponseResult m) -> f ()) -> f () 155 Handler f (m :: Method _from Notification) = NotificationMessage m -> f () 156 157-- | How to convert two isomorphic data structures between each other. 158data m <~> n 159 = Iso 160 { forward :: forall a. m a -> n a 161 , backward :: forall a. n a -> m a 162 } 163 164transmuteHandlers :: (m <~> n) -> Handlers m -> Handlers n 165transmuteHandlers nat = mapHandlers (\i m k -> forward nat (i m (backward nat . k))) (\i m -> forward nat (i m)) 166 167mapHandlers 168 :: (forall (a :: Method FromClient Request). Handler m a -> Handler n a) 169 -> (forall (a :: Method FromClient Notification). Handler m a -> Handler n a) 170 -> Handlers m -> Handlers n 171mapHandlers mapReq mapNot (Handlers reqs nots) = Handlers reqs' nots' 172 where 173 reqs' = DMap.map (\(ClientMessageHandler i) -> ClientMessageHandler $ mapReq i) reqs 174 nots' = DMap.map (\(ClientMessageHandler i) -> ClientMessageHandler $ mapNot i) nots 175 176-- | state used by the LSP dispatcher to manage the message loop 177data LanguageContextState config = 178 LanguageContextState 179 { resVFS :: !(TVar VFSData) 180 , resDiagnostics :: !(TVar DiagnosticStore) 181 , resConfig :: !(TVar config) 182 , resWorkspaceFolders :: !(TVar [WorkspaceFolder]) 183 , resProgressData :: !ProgressData 184 , resPendingResponses :: !(TVar ResponseMap) 185 , resRegistrationsNot :: !(TVar (RegistrationMap Notification)) 186 , resRegistrationsReq :: !(TVar (RegistrationMap Request)) 187 , resLspId :: !(TVar Int) 188 } 189 190type ResponseMap = IxMap LspId (Product SMethod ServerResponseCallback) 191 192type RegistrationMap (t :: MethodType) = DMap SMethod (Product RegistrationId (ClientMessageHandler IO t)) 193 194data RegistrationToken (m :: Method FromClient t) = RegistrationToken (SMethod m) (RegistrationId m) 195newtype RegistrationId (m :: Method FromClient t) = RegistrationId Text 196 deriving Eq 197 198data ProgressData = ProgressData { progressNextId :: !(TVar Int) 199 , progressCancel :: !(TVar (Map.Map ProgressToken (IO ()))) } 200 201data VFSData = 202 VFSData 203 { vfsData :: !VFS 204 , reverseMap :: !(Map.Map FilePath FilePath) 205 } 206 207{-# INLINE modifyState #-} 208modifyState :: MonadLsp config m => (LanguageContextState config -> TVar a) -> (a -> a) -> m () 209modifyState sel f = do 210 tvarDat <- sel . resState <$> getLspEnv 211 liftIO $ atomically $ modifyTVar' tvarDat f 212 213{-# INLINE stateState #-} 214stateState :: MonadLsp config m => (LanguageContextState config -> TVar s) -> (s -> (a,s)) -> m a 215stateState sel f = do 216 tvarDat <- sel . resState <$> getLspEnv 217 liftIO $ atomically $ stateTVar tvarDat f 218 219{-# INLINE getsState #-} 220getsState :: MonadLsp config m => (LanguageContextState config -> TVar a) -> m a 221getsState f = do 222 tvarDat <- f . resState <$> getLspEnv 223 liftIO $ readTVarIO tvarDat 224 225-- --------------------------------------------------------------------- 226 227-- | Language Server Protocol options that the server may configure. 228-- If you set handlers for some requests, you may need to set some of these options. 229data Options = 230 Options 231 { textDocumentSync :: Maybe J.TextDocumentSyncOptions 232 -- | The characters that trigger completion automatically. 233 , completionTriggerCharacters :: Maybe [Char] 234 -- | The list of all possible characters that commit a completion. This field can be used 235 -- if clients don't support individual commmit characters per completion item. See 236 -- `_commitCharactersSupport`. 237 , completionAllCommitCharacters :: Maybe [Char] 238 -- | The characters that trigger signature help automatically. 239 , signatureHelpTriggerCharacters :: Maybe [Char] 240 -- | List of characters that re-trigger signature help. 241 -- These trigger characters are only active when signature help is already showing. All trigger characters 242 -- are also counted as re-trigger characters. 243 , signatureHelpRetriggerCharacters :: Maybe [Char] 244 -- | CodeActionKinds that this server may return. 245 -- The list of kinds may be generic, such as `CodeActionKind.Refactor`, or the server 246 -- may list out every specific kind they provide. 247 , codeActionKinds :: Maybe [CodeActionKind] 248 -- | The list of characters that triggers on type formatting. 249 -- If you set `documentOnTypeFormattingHandler`, you **must** set this. 250 -- The first character is mandatory, so a 'NonEmpty' should be passed. 251 , documentOnTypeFormattingTriggerCharacters :: Maybe (NonEmpty Char) 252 -- | The commands to be executed on the server. 253 -- If you set `executeCommandHandler`, you **must** set this. 254 , executeCommandCommands :: Maybe [Text] 255 -- | Information about the server that can be advertised to the client. 256 , serverInfo :: Maybe J.ServerInfo 257 } 258 259instance Default Options where 260 def = Options Nothing Nothing Nothing Nothing Nothing 261 Nothing Nothing Nothing Nothing 262 263defaultOptions :: Options 264defaultOptions = def 265 266-- | A package indicating the perecentage of progress complete and a 267-- an optional message to go with it during a 'withProgress' 268-- 269-- @since 0.10.0.0 270data ProgressAmount = ProgressAmount (Maybe Double) (Maybe Text) 271 272-- | Thrown if the user cancels a 'Cancellable' 'withProgress'/'withIndefiniteProgress'/ session 273-- 274-- @since 0.11.0.0 275data ProgressCancelledException = ProgressCancelledException 276 deriving Show 277instance E.Exception ProgressCancelledException 278 279-- | Whether or not the user should be able to cancel a 'withProgress'/'withIndefiniteProgress' 280-- session 281-- 282-- @since 0.11.0.0 283data ProgressCancellable = Cancellable | NotCancellable 284 285-- | Contains all the callbacks to use for initialized the language server. 286-- it is parameterized over a config type variable representing the type for the 287-- specific configuration data the language server needs to use. 288data ServerDefinition config = forall m a. 289 ServerDefinition 290 { defaultConfig :: config 291 -- ^ The default value we initialize the config variable to. 292 , onConfigurationChange :: config -> J.Value -> Either T.Text config 293 -- ^ @onConfigurationChange oldConfig newConfig@ is called whenever the 294 -- clients sends a message with a changed client configuration. This 295 -- callback should return either the parsed configuration data or an error 296 -- indicating what went wrong. The parsed configuration object will be 297 -- stored internally and can be accessed via 'config'. 298 -- It is also called on the `initializationOptions` field of the InitializeParams 299 , doInitialize :: LanguageContextEnv config -> Message Initialize -> IO (Either ResponseError a) 300 -- ^ Called *after* receiving the @initialize@ request and *before* 301 -- returning the response. This callback will be invoked to offer the 302 -- language server implementation the chance to create any processes or 303 -- start new threads that may be necesary for the server lifecycle. It can 304 -- also return an error in the initialization if necessary. 305 , staticHandlers :: Handlers m 306 -- ^ Handlers for any methods you want to statically support. 307 -- The handlers here cannot be unregistered during the server's lifetime 308 -- and will be regsitered statically in the initialize request. 309 , interpretHandler :: a -> (m <~> IO) 310 -- ^ How to run the handlers in your own monad of choice, @m@. 311 -- It is passed the result of 'doInitialize', so typically you will want 312 -- to thread along the 'LanguageContextEnv' as well as any other state you 313 -- need to run your monad. @m@ should most likely be built on top of 314 -- 'LspT'. 315 -- 316 -- @ 317 -- ServerDefinition { ... 318 -- , doInitialize = \env _req -> pure $ Right env 319 -- , interpretHandler = \env -> Iso 320 -- (runLspT env) -- how to convert from IO ~> m 321 -- liftIO -- how to convert from m ~> IO 322 -- } 323 -- @ 324 , options :: Options 325 -- ^ Configurable options for the server's capabilities. 326 } 327 328-- | A function that a 'Handler' is passed that can be used to respond to a 329-- request with either an error, or the response params. 330newtype ServerResponseCallback (m :: Method FromServer Request) 331 = ServerResponseCallback (Either ResponseError (ResponseResult m) -> IO ()) 332 333-- | Return value signals if response handler was inserted succesfully 334-- Might fail if the id was already in the map 335addResponseHandler :: MonadLsp config f => LspId m -> (Product SMethod ServerResponseCallback) m -> f Bool 336addResponseHandler lid h = do 337 stateState resPendingResponses $ \pending -> 338 case insertIxMap lid h pending of 339 Just !m -> (True, m) 340 Nothing -> (False, pending) 341 342sendNotification 343 :: forall (m :: Method FromServer Notification) f config. MonadLsp config f 344 => SServerMethod m 345 -> MessageParams m 346 -> f () 347sendNotification m params = 348 let msg = NotificationMessage "2.0" m params 349 in case splitServerMethod m of 350 IsServerNot -> sendToClient $ fromServerNot msg 351 IsServerEither -> sendToClient $ FromServerMess m $ NotMess msg 352 353sendRequest :: forall (m :: Method FromServer Request) f config. MonadLsp config f 354 => SServerMethod m 355 -> MessageParams m 356 -> (Either ResponseError (ResponseResult m) -> f ()) 357 -> f (LspId m) 358sendRequest m params resHandler = do 359 reqId <- IdInt <$> freshLspId 360 rio <- askRunInIO 361 success <- addResponseHandler reqId (Pair m (ServerResponseCallback (rio . resHandler))) 362 unless success $ error "haskell-lsp: could not send FromServer request as id is reused" 363 364 let msg = RequestMessage "2.0" reqId m params 365 ~() <- case splitServerMethod m of 366 IsServerReq -> sendToClient $ fromServerReq msg 367 IsServerEither -> sendToClient $ FromServerMess m $ ReqMess msg 368 return reqId 369 370-- --------------------------------------------------------------------- 371 372-- | Return the 'VirtualFile' associated with a given 'NormalizedUri', if there is one. 373getVirtualFile :: MonadLsp config m => NormalizedUri -> m (Maybe VirtualFile) 374getVirtualFile uri = Map.lookup uri . vfsMap . vfsData <$> getsState resVFS 375 376{-# INLINE getVirtualFile #-} 377 378getVirtualFiles :: MonadLsp config m => m VFS 379getVirtualFiles = vfsData <$> getsState resVFS 380 381{-# INLINE getVirtualFiles #-} 382 383-- | Dump the current text for a given VFS file to a temporary file, 384-- and return the path to the file. 385persistVirtualFile :: MonadLsp config m => NormalizedUri -> m (Maybe FilePath) 386persistVirtualFile uri = do 387 join $ stateState resVFS $ \vfs -> 388 case persistFileVFS (vfsData vfs) uri of 389 Nothing -> (return Nothing, vfs) 390 Just (fn, write) -> 391 let !revMap = case uriToFilePath (fromNormalizedUri uri) of 392 Just uri_fp -> Map.insert fn uri_fp $ reverseMap vfs 393 -- TODO: Does the VFS make sense for URIs which are not files? 394 -- The reverse map should perhaps be (FilePath -> URI) 395 Nothing -> reverseMap vfs 396 !vfs' = vfs {reverseMap = revMap} 397 act = do 398 liftIO write 399 pure (Just fn) 400 in (act, vfs') 401 402-- | Given a text document identifier, annotate it with the latest version. 403getVersionedTextDoc :: MonadLsp config m => TextDocumentIdentifier -> m VersionedTextDocumentIdentifier 404getVersionedTextDoc doc = do 405 let uri = doc ^. J.uri 406 mvf <- getVirtualFile (toNormalizedUri uri) 407 let ver = case mvf of 408 Just (VirtualFile lspver _ _) -> Just lspver 409 Nothing -> Nothing 410 return (VersionedTextDocumentIdentifier uri ver) 411 412{-# INLINE getVersionedTextDoc #-} 413 414-- TODO: should this function return a URI? 415-- | If the contents of a VFS has been dumped to a temporary file, map 416-- the temporary file name back to the original one. 417reverseFileMap :: MonadLsp config m => m (FilePath -> FilePath) 418reverseFileMap = do 419 vfs <- getsState resVFS 420 let f fp = fromMaybe fp . Map.lookup fp . reverseMap $ vfs 421 return f 422 423{-# INLINE reverseFileMap #-} 424 425-- --------------------------------------------------------------------- 426 427sendToClient :: MonadLsp config m => FromServerMessage -> m () 428sendToClient msg = do 429 f <- resSendMessage <$> getLspEnv 430 liftIO $ f msg 431 432{-# INLINE sendToClient #-} 433 434-- --------------------------------------------------------------------- 435 436sendErrorLog :: MonadLsp config m => Text -> m () 437sendErrorLog msg = 438 sendToClient $ fromServerNot $ 439 NotificationMessage "2.0" SWindowLogMessage (LogMessageParams MtError msg) 440 441{-# INLINE sendErrorLog #-} 442 443-- --------------------------------------------------------------------- 444 445freshLspId :: MonadLsp config m => m Int 446freshLspId = do 447 stateState resLspId $ \cur -> 448 let !next = cur+1 in (cur, next) 449 450{-# INLINE freshLspId #-} 451 452-- --------------------------------------------------------------------- 453 454-- | The current configuration from the client as set via the @initialize@ and 455-- @workspace/didChangeConfiguration@ requests. 456getConfig :: MonadLsp config m => m config 457getConfig = getsState resConfig 458 459{-# INLINE getConfig #-} 460 461getClientCapabilities :: MonadLsp config m => m J.ClientCapabilities 462getClientCapabilities = resClientCapabilities <$> getLspEnv 463 464{-# INLINE getClientCapabilities #-} 465 466getRootPath :: MonadLsp config m => m (Maybe FilePath) 467getRootPath = resRootPath <$> getLspEnv 468 469{-# INLINE getRootPath #-} 470 471-- | The current workspace folders, if the client supports workspace folders. 472getWorkspaceFolders :: MonadLsp config m => m (Maybe [WorkspaceFolder]) 473getWorkspaceFolders = do 474 clientCaps <- getClientCapabilities 475 let clientSupportsWfs = fromMaybe False $ do 476 let (J.ClientCapabilities mw _ _ _) = clientCaps 477 (J.WorkspaceClientCapabilities _ _ _ _ _ _ mwf _ _) <- mw 478 mwf 479 if clientSupportsWfs 480 then Just <$> getsState resWorkspaceFolders 481 else pure Nothing 482 483{-# INLINE getWorkspaceFolders #-} 484 485-- | Sends a @client/registerCapability@ request and dynamically registers 486-- a 'Method' with a 'Handler'. Returns 'Nothing' if the client does not 487-- support dynamic registration for the specified method, otherwise a 488-- 'RegistrationToken' which can be used to unregister it later. 489registerCapability :: forall f t (m :: Method FromClient t) config. 490 MonadLsp config f 491 => SClientMethod m 492 -> RegistrationOptions m 493 -> Handler f m 494 -> f (Maybe (RegistrationToken m)) 495registerCapability method regOpts f = do 496 clientCaps <- resClientCapabilities <$> getLspEnv 497 handlers <- resHandlers <$> getLspEnv 498 let alreadyStaticallyRegistered = case splitClientMethod method of 499 IsClientNot -> DMap.member method $ notHandlers handlers 500 IsClientReq -> DMap.member method $ reqHandlers handlers 501 IsClientEither -> error "Cannot register capability for custom methods" 502 go clientCaps alreadyStaticallyRegistered 503 where 504 -- If the server has already registered statically, don't dynamically register 505 -- as per the spec 506 go _clientCaps True = pure Nothing 507 go clientCaps False 508 -- First, check to see if the client supports dynamic registration on this method 509 | dynamicSupported clientCaps = do 510 uuid <- liftIO $ UUID.toText <$> getStdRandom random 511 let registration = J.Registration uuid method regOpts 512 params = J.RegistrationParams (J.List [J.SomeRegistration registration]) 513 regId = RegistrationId uuid 514 rio <- askUnliftIO 515 ~() <- case splitClientMethod method of 516 IsClientNot -> modifyState resRegistrationsNot $ \oldRegs -> 517 let pair = Pair regId (ClientMessageHandler (unliftIO rio . f)) 518 in DMap.insert method pair oldRegs 519 IsClientReq -> modifyState resRegistrationsReq $ \oldRegs -> 520 let pair = Pair regId (ClientMessageHandler (\msg k -> unliftIO rio $ f msg (liftIO . k))) 521 in DMap.insert method pair oldRegs 522 IsClientEither -> error "Cannot register capability for custom methods" 523 524 -- TODO: handle the scenario where this returns an error 525 _ <- sendRequest SClientRegisterCapability params $ \_res -> pure () 526 527 pure (Just (RegistrationToken method regId)) 528 | otherwise = pure Nothing 529 530 -- Also I'm thinking we should move this function to somewhere in messages.hs so 531 -- we don't forget to update it when adding new methods... 532 capDyn :: J.HasDynamicRegistration a (Maybe Bool) => Maybe a -> Bool 533 capDyn (Just x) = fromMaybe False $ x ^. J.dynamicRegistration 534 capDyn Nothing = False 535 536 -- | Checks if client capabilities declares that the method supports dynamic registration 537 dynamicSupported clientCaps = case method of 538 SWorkspaceDidChangeConfiguration -> capDyn $ clientCaps ^? J.workspace . _Just . J.didChangeConfiguration . _Just 539 SWorkspaceDidChangeWatchedFiles -> capDyn $ clientCaps ^? J.workspace . _Just . J.didChangeWatchedFiles . _Just 540 SWorkspaceSymbol -> capDyn $ clientCaps ^? J.workspace . _Just . J.symbol . _Just 541 SWorkspaceExecuteCommand -> capDyn $ clientCaps ^? J.workspace . _Just . J.executeCommand . _Just 542 STextDocumentDidOpen -> capDyn $ clientCaps ^? J.textDocument . _Just . J.synchronization . _Just 543 STextDocumentDidChange -> capDyn $ clientCaps ^? J.textDocument . _Just . J.synchronization . _Just 544 STextDocumentDidClose -> capDyn $ clientCaps ^? J.textDocument . _Just . J.synchronization . _Just 545 STextDocumentCompletion -> capDyn $ clientCaps ^? J.textDocument . _Just . J.completion . _Just 546 STextDocumentHover -> capDyn $ clientCaps ^? J.textDocument . _Just . J.hover . _Just 547 STextDocumentSignatureHelp -> capDyn $ clientCaps ^? J.textDocument . _Just . J.signatureHelp . _Just 548 STextDocumentDeclaration -> capDyn $ clientCaps ^? J.textDocument . _Just . J.declaration . _Just 549 STextDocumentDefinition -> capDyn $ clientCaps ^? J.textDocument . _Just . J.definition . _Just 550 STextDocumentTypeDefinition -> capDyn $ clientCaps ^? J.textDocument . _Just . J.typeDefinition . _Just 551 STextDocumentImplementation -> capDyn $ clientCaps ^? J.textDocument . _Just . J.implementation . _Just 552 STextDocumentReferences -> capDyn $ clientCaps ^? J.textDocument . _Just . J.references . _Just 553 STextDocumentDocumentHighlight -> capDyn $ clientCaps ^? J.textDocument . _Just . J.documentHighlight . _Just 554 STextDocumentDocumentSymbol -> capDyn $ clientCaps ^? J.textDocument . _Just . J.documentSymbol . _Just 555 STextDocumentCodeAction -> capDyn $ clientCaps ^? J.textDocument . _Just . J.codeAction . _Just 556 STextDocumentCodeLens -> capDyn $ clientCaps ^? J.textDocument . _Just . J.codeLens . _Just 557 STextDocumentDocumentLink -> capDyn $ clientCaps ^? J.textDocument . _Just . J.documentLink . _Just 558 STextDocumentDocumentColor -> capDyn $ clientCaps ^? J.textDocument . _Just . J.colorProvider . _Just 559 STextDocumentColorPresentation -> capDyn $ clientCaps ^? J.textDocument . _Just . J.colorProvider . _Just 560 STextDocumentFormatting -> capDyn $ clientCaps ^? J.textDocument . _Just . J.formatting . _Just 561 STextDocumentRangeFormatting -> capDyn $ clientCaps ^? J.textDocument . _Just . J.rangeFormatting . _Just 562 STextDocumentOnTypeFormatting -> capDyn $ clientCaps ^? J.textDocument . _Just . J.onTypeFormatting . _Just 563 STextDocumentRename -> capDyn $ clientCaps ^? J.textDocument . _Just . J.rename . _Just 564 STextDocumentFoldingRange -> capDyn $ clientCaps ^? J.textDocument . _Just . J.foldingRange . _Just 565 STextDocumentSelectionRange -> capDyn $ clientCaps ^? J.textDocument . _Just . J.selectionRange . _Just 566 STextDocumentPrepareCallHierarchy -> capDyn $ clientCaps ^? J.textDocument . _Just . J.callHierarchy . _Just 567 STextDocumentSemanticTokens -> capDyn $ clientCaps ^? J.textDocument . _Just . J.semanticTokens . _Just 568 _ -> False 569 570-- | Sends a @client/unregisterCapability@ request and removes the handler 571-- for that associated registration. 572unregisterCapability :: MonadLsp config f => RegistrationToken m -> f () 573unregisterCapability (RegistrationToken m (RegistrationId uuid)) = do 574 ~() <- case splitClientMethod m of 575 IsClientReq -> modifyState resRegistrationsReq $ DMap.delete m 576 IsClientNot -> modifyState resRegistrationsNot $ DMap.delete m 577 IsClientEither -> error "Cannot unregister capability for custom methods" 578 579 let unregistration = J.Unregistration uuid (J.SomeClientMethod m) 580 params = J.UnregistrationParams (J.List [unregistration]) 581 void $ sendRequest SClientUnregisterCapability params $ \_res -> pure () 582 583-------------------------------------------------------------------------------- 584-- PROGRESS 585-------------------------------------------------------------------------------- 586 587storeProgress :: MonadLsp config m => ProgressToken -> Async a -> m () 588storeProgress n a = modifyState (progressCancel . resProgressData) $ Map.insert n (cancelWith a ProgressCancelledException) 589 590{-# INLINE storeProgress #-} 591 592deleteProgress :: MonadLsp config m => ProgressToken -> m () 593deleteProgress n = modifyState (progressCancel . resProgressData) $ Map.delete n 594 595{-# INLINE deleteProgress #-} 596 597-- Get a new id for the progress session and make a new one 598getNewProgressId :: MonadLsp config m => m ProgressToken 599getNewProgressId = do 600 stateState (progressNextId . resProgressData) $ \cur -> 601 let !next = cur+1 602 in (ProgressNumericToken cur, next) 603 604{-# INLINE getNewProgressId #-} 605 606withProgressBase :: MonadLsp c m => Bool -> Text -> ProgressCancellable -> ((ProgressAmount -> m ()) -> m a) -> m a 607withProgressBase indefinite title cancellable f = do 608 609 progId <- getNewProgressId 610 611 let initialPercentage 612 | indefinite = Nothing 613 | otherwise = Just 0 614 cancellable' = case cancellable of 615 Cancellable -> True 616 NotCancellable -> False 617 618 -- Create progress token 619 -- FIXME : This needs to wait until the request returns before 620 -- continuing!!! 621 _ <- sendRequest SWindowWorkDoneProgressCreate 622 (WorkDoneProgressCreateParams progId) $ \res -> do 623 case res of 624 -- An error ocurred when the client was setting it up 625 -- No need to do anything then, as per the spec 626 Left _err -> pure () 627 Right Empty -> pure () 628 629 -- Send the begin and done notifications via 'bracket_' so that they are always fired 630 res <- withRunInIO $ \runInBase -> 631 E.bracket_ 632 -- Send begin notification 633 (runInBase $ sendNotification SProgress $ 634 fmap Begin $ ProgressParams progId $ 635 WorkDoneProgressBeginParams title (Just cancellable') Nothing initialPercentage) 636 637 -- Send end notification 638 (runInBase $ sendNotification SProgress $ 639 End <$> ProgressParams progId (WorkDoneProgressEndParams Nothing)) $ do 640 641 -- Run f asynchronously 642 aid <- async $ runInBase $ f (updater progId) 643 runInBase $ storeProgress progId aid 644 wait aid 645 646 -- Delete the progress cancellation from the map 647 -- If we don't do this then it's easy to leak things as the map contains any IO action. 648 deleteProgress progId 649 650 return res 651 where updater progId (ProgressAmount percentage msg) = do 652 liftIO $ putStrLn "asdf" 653 sendNotification SProgress $ fmap Report $ ProgressParams progId $ 654 WorkDoneProgressReportParams Nothing msg percentage 655 656clientSupportsProgress :: J.ClientCapabilities -> Bool 657clientSupportsProgress (J.ClientCapabilities _ _ wc _) = fromMaybe False $ do 658 (J.WindowClientCapabilities mProgress) <- wc 659 mProgress 660 661{-# INLINE clientSupportsProgress #-} 662 663-- | Wrapper for reporting progress to the client during a long running 664-- task. 665-- 'withProgress' @title cancellable f@ starts a new progress reporting 666-- session, and finishes it once f is completed. 667-- f is provided with an update function that allows it to report on 668-- the progress during the session. 669-- If @cancellable@ is 'Cancellable', @f@ will be thrown a 670-- 'ProgressCancelledException' if the user cancels the action in 671-- progress. 672withProgress :: MonadLsp c m => Text -> ProgressCancellable -> ((ProgressAmount -> m ()) -> m a) -> m a 673withProgress title cancellable f = do 674 clientCaps <- getClientCapabilities 675 if clientSupportsProgress clientCaps 676 then withProgressBase False title cancellable f 677 else f (const $ return ()) 678 679-- | Same as 'withProgress', but for processes that do not report the 680-- precentage complete. 681-- 682-- @since 0.10.0.0 683withIndefiniteProgress :: MonadLsp c m => Text -> ProgressCancellable -> m a -> m a 684withIndefiniteProgress title cancellable f = do 685 clientCaps <- getClientCapabilities 686 if clientSupportsProgress clientCaps 687 then withProgressBase True title cancellable (const f) 688 else f 689 690-- --------------------------------------------------------------------- 691 692-- | Aggregate all diagnostics pertaining to a particular version of a document, 693-- by source, and sends a @textDocument/publishDiagnostics@ notification with 694-- the total (limited by the first parameter) whenever it is updated. 695publishDiagnostics :: MonadLsp config m => Int -> NormalizedUri -> TextDocumentVersion -> DiagnosticsBySource -> m () 696publishDiagnostics maxDiagnosticCount uri version diags = join $ stateState resDiagnostics $ \oldDiags-> 697 let !newDiags = updateDiagnostics oldDiags uri version diags 698 mdp = getDiagnosticParamsFor maxDiagnosticCount newDiags uri 699 act = case mdp of 700 Nothing -> return () 701 Just params -> 702 sendToClient $ J.fromServerNot $ J.NotificationMessage "2.0" J.STextDocumentPublishDiagnostics params 703 in (act,newDiags) 704 705-- --------------------------------------------------------------------- 706 707-- | Remove all diagnostics from a particular source, and send the updates to 708-- the client. 709flushDiagnosticsBySource :: MonadLsp config m => Int -- ^ Max number of diagnostics to send 710 -> Maybe DiagnosticSource -> m () 711flushDiagnosticsBySource maxDiagnosticCount msource = join $ stateState resDiagnostics $ \oldDiags -> 712 let !newDiags = flushBySource oldDiags msource 713 -- Send the updated diagnostics to the client 714 act = forM_ (HM.keys newDiags) $ \uri -> do 715 let mdp = getDiagnosticParamsFor maxDiagnosticCount newDiags uri 716 case mdp of 717 Nothing -> return () 718 Just params -> do 719 sendToClient $ J.fromServerNot $ J.NotificationMessage "2.0" J.STextDocumentPublishDiagnostics params 720 in (act,newDiags) 721 722-- ===================================================================== 723-- 724-- utility 725 726 727-- 728-- Logger 729-- 730setupLogger :: Maybe FilePath -> [String] -> Priority -> IO () 731setupLogger mLogFile extraLogNames level = do 732 733 logStream <- case mLogFile of 734 Just logFile -> openFile logFile AppendMode `E.catch` handleIOException logFile 735 Nothing -> return stderr 736 hSetEncoding logStream utf8 737 738 logH <- LHS.streamHandler logStream level 739 740 let logHandle = logH {LHS.closeFunc = hClose} 741 logFormatter = L.tfLogFormatter logDateFormat logFormat 742 logHandler = LH.setFormatter logHandle logFormatter 743 744 L.updateGlobalLogger L.rootLoggerName $ L.setHandlers ([] :: [LHS.GenericHandler Handle]) 745 L.updateGlobalLogger "haskell-lsp" $ L.setHandlers [logHandler] 746 L.updateGlobalLogger "haskell-lsp" $ L.setLevel level 747 748 -- Also route the additional log names to the same log 749 forM_ extraLogNames $ \logName -> do 750 L.updateGlobalLogger logName $ L.setHandlers [logHandler] 751 L.updateGlobalLogger logName $ L.setLevel level 752 where 753 logFormat = "$time [$tid] $prio $loggername:\t$msg" 754 logDateFormat = "%Y-%m-%d %H:%M:%S%Q" 755 756handleIOException :: FilePath -> E.IOException -> IO Handle 757handleIOException logFile _ = do 758 hPutStr stderr $ "Couldn't open log file " ++ logFile ++ "; falling back to stderr logging" 759 return stderr 760 761-- --------------------------------------------------------------------- 762 763-- | The changes in a workspace edit should be applied from the end of the file 764-- toward the start. Sort them into this order. 765reverseSortEdit :: J.WorkspaceEdit -> J.WorkspaceEdit 766reverseSortEdit (J.WorkspaceEdit cs dcs anns) = J.WorkspaceEdit cs' dcs' anns 767 where 768 cs' :: Maybe J.WorkspaceEditMap 769 cs' = (fmap . fmap ) sortTextEdits cs 770 771 dcs' :: Maybe (J.List J.DocumentChange) 772 dcs' = (fmap . fmap) sortOnlyTextDocumentEdits dcs 773 774 sortTextEdits :: J.List J.TextEdit -> J.List J.TextEdit 775 sortTextEdits (J.List edits) = J.List (L.sortOn (Down . (^. J.range)) edits) 776 777 sortOnlyTextDocumentEdits :: J.DocumentChange -> J.DocumentChange 778 sortOnlyTextDocumentEdits (J.InL (J.TextDocumentEdit td (J.List edits))) = J.InL $ J.TextDocumentEdit td (J.List edits') 779 where 780 edits' = L.sortOn (Down . editRange) edits 781 sortOnlyTextDocumentEdits (J.InR others) = J.InR others 782 783 editRange :: J.TextEdit J.|? J.AnnotatedTextEdit -> J.Range 784 editRange (J.InR e) = e ^. J.range 785 editRange (J.InL e) = e ^. J.range 786