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