packages feed

descript-lang-0.2.0.0: app/Server/Run.hs

{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE OverloadedStrings #-}

module Server.Run
  ( runServe
  ) where

import           Control.Applicative
import           Control.Concurrent
import           Control.Concurrent.STM.TChan
import           Control.Lens
import           Control.Monad
import qualified Control.Monad.Catch                   as E
import           Control.Monad.IO.Class
import           Control.Monad.Reader
import           Control.Monad.STM
import qualified Data.Aeson                            as J
import           Data.Default
import           Data.Monoid
import qualified Data.Text                             as T
import           Descript.Build
import qualified Descript.BasicInj                     as BasicInj
import           Descript.Misc
import qualified Language.Haskell.LSP.Control          as CTRL
import qualified Language.Haskell.LSP.Core             as Core
import           Language.Haskell.LSP.Diagnostics
import           Language.Haskell.LSP.Messages
import qualified Language.Haskell.LSP.Types            as J
import qualified Language.Haskell.LSP.Utility          as U
import           Server.Decode
import           Server.Encode
import           System.Exit
import qualified System.Log.Logger                     as L

runServe :: IO ()
runServe = do
  -- putStrLn "Starting server..."
  serve handleServerStart >>= \case
    0 -> do
      putStrLn "Server stopped"
      exitSuccess
    c -> do
      putStrLn $ "Server stopped with error or interrupt"
      exitWith . ExitFailure $ c

handleServerStart :: IO ()
handleServerStart = do
  -- putStrLn "Server started. Ctrl-C to stop"
  pure ()

-- Derived from Haskell LSP example
-- (https://github.com/alanz/haskell-lsp/blob/master/example/Main.hs)
-- and Haskell IDE Engine implementation
-- (https://github.com/alanz/haskell-ide-engine/blob/master/src/Haskell/Ide/Engine/Transport/LspStdio.hs)
-- ---------------------------------------------------------------------

serve :: IO () -> IO Int
serve dispatcherProc = E.handle finishErr $ do
  rin  <- atomically newTChan :: IO (TChan ReactorInput)
  let dp lf = do
        liftIO $ U.logs $ "main.run:dp entered"
        dispatcherProc
        liftIO $ U.logs $ "main.run:dp after dispatcher"
        _  <- forkIO $ reactor lf rin
        pure Nothing
  Core.setupLogger (Just "/tmp/descript-server.log") [] L.DEBUG
  res <- CTRL.run (getConfig, dp) (lspHandlers rin) lspOptions
  U.logs $ "****** FINISHED WITH CODE: " ++ show res
  finish
  pure res
  where finishErr (e :: E.SomeException) = do
          U.logs $ "****** FINISHED WITH ERROR: " ++ show e
          finish
          pure 1
        finish = L.removeAllHandlers

-- ---------------------------------------------------------------------

-- | Callback from haskell-lsp core to convert the generic message to the
-- specific one for hie
getConfig :: J.DidChangeConfigurationNotification -> Either T.Text Config
getConfig (J.NotificationMessage _ _ (J.DidChangeConfigurationParams p)) =
  case J.fromJSON p of
    J.Success c -> Right c
    J.Error err -> Left $ T.pack err

newtype Config =
  Config
    { maxNumberOfProblems :: Int
    } deriving (Show)

instance J.FromJSON Config where
  parseJSON = J.withObject "Config" $ \v -> do
    s <- v J..: "descript"
    flip (J.withObject "Config.settings") s $ \o -> Config
      <$> o J..: "maxNumberOfProblems"

-- ---------------------------------------------------------------------

-- The reactor is a process that serialises and buffers all requests from the
-- LSP client, so they can be sent to the backend compiler one at a time, and a
-- reply sent.

data ReactorInput
  = HandlerRequest Core.OutMessage
      -- ^ injected into the reactor input by each of the individual callback handlers

-- ---------------------------------------------------------------------

-- | The monad used in the reactor
type R a = ReaderT (Core.LspFuncs Config) IO a

-- ---------------------------------------------------------------------
-- reactor monad functions
-- ---------------------------------------------------------------------

-- Unlike makeResponseMessage, allows for a response for an arbitrary
-- request (without request type).
makeResponseMessage' :: J.LspIdRsp -> resp -> J.ResponseMessage resp
makeResponseMessage' rid result
  = J.ResponseMessage "2.0" rid (Just result) Nothing

-- ---------------------------------------------------------------------

reactorSend :: (J.ToJSON a) => a -> R ()
reactorSend msg = do
  lf <- ask
  liftIO $ Core.sendFunc lf msg

-- ---------------------------------------------------------------------

publishDiagnostics :: J.Uri -> Maybe J.TextDocumentVersion -> DiagnosticsBySource -> R ()
publishDiagnostics uri mv diags = do
  lf <- ask
  config <- liftIO $ Core.config lf
  let maxNumProbs
        = case maxNumberOfProblems <$> config of
               Nothing -> maxBound
               Just (-1) -> maxBound
               Just x -> x
  liftIO $ (Core.publishDiagnosticsFunc lf) maxNumProbs uri mv diags

-- ---------------------------------------------------------------------

nextLspReqId :: R J.LspId
nextLspReqId = do
  f <- asks Core.getNextReqId
  liftIO $ f

-- ---------------------------------------------------------------------

-- | The single point that all events flow through, allowing management of state
-- to stitch replies and requests together from the two asynchronous sides: lsp
-- server and backend compiler
reactor :: Core.LspFuncs Config -> TChan ReactorInput -> IO ()
reactor lf inp = flip runReaderT lf $ do
  liftIO $ U.logs $ "reactor:entered"
  cache <- newGlobalCache
  let handlers = [ E.Handler onSomeExcept
                 ]
      onSomeExcept (e :: E.SomeException) = do
        liftIO $ U.logm $ "****** EXCEPTION ******"
        liftIO $ U.logs $ show e
  forever $ (`E.catches` handlers) $ do
    HandlerRequest inval <- liftIO $ atomically $ readTChan inp
    case inval of

      -- Handle any response from a message originating at the server, such as
      -- "workspace/applyEdit"
      Core.RspFromClient rm -> do
        liftIO $ U.logs $ "reactor:got RspFromClient:" ++ show rm

      -- -------------------------------

      Core.NotInitialized _notification -> do
        liftIO $ U.logm $ "****** reactor: processing Initialized Notification"
        -- Server is ready, register any specific capabilities we need

         {-
         Example:
         {
                 "method": "client/registerCapability",
                 "params": {
                         "registrations": [
                                 {
                                         "id": "79eee87c-c409-4664-8102-e03263673f6f",
                                         "method": "textDocument/willSaveWaitUntil",
                                         "registerOptions": {
                                                 "documentSelector": [
                                                         { "language": "javascript" }
                                                 ]
                                         }
                                 }
                         ]
                 }
         }
        -}
        let registration = J.Registration "descript-server-registered" J.WorkspaceExecuteCommand Nothing
            registrations = J.RegistrationParams $ J.List [registration]
        rid <- nextLspReqId

        reactorSend $ fmServerRegisterCapabilityRequest rid registrations

      -- -------------------------------

      Core.NotDidOpenTextDocument notification -> do
        liftIO $ U.logm $ "****** reactor: processing NotDidOpenTextDocument"
        let doc = notification ^. J.params . J.textDocument
            uri = decodeUri $ doc ^. J.uri
            version = doc ^. J.version
            text = doc ^. J.text
        liftIO $ U.logs $ "********* uri = " ++ show uri
        addFile (inDelegate uri) uri version text cache

      -- -------------------------------

      Core.NotDidCloseTextDocument notification -> do
        liftIO $ U.logm $ "****** reactor: processing NotDidCloseTextDocument"
        let uri = decodeUri $ notification ^. J.params . J.textDocument . J.uri
        liftIO $ U.logs $ "********* uri = " ++ show uri
        delFile uri cache

      -- -------------------------------

      Core.NotDidChangeTextDocument notification -> do
        liftIO $ U.logm $ "****** reactor: processing NotDidChangeTextDocument"
        let params = notification ^. J.params
            doc = params ^. J.textDocument
            uri = decodeUri $ doc ^. J.uri
            version = doc ^. J.version
            change = decodeChange $ params ^. J.contentChanges
        liftIO $ U.logs $ "********* uri = " ++ show uri
        changeFile (inDelegate uri) uri version change cache

      -- -------------------------------

      Core.NotDidSaveTextDocument notification -> do
        liftIO $ U.logm "****** reactor: processing NotDidSaveTextDocument"
        let uri = decodeUri $ notification ^. J.params . J.textDocument . J.uri
        liftIO $ U.logs $ "********* uri = " ++ show uri

      -- -------------------------------

      Core.ReqRename req -> do
        liftIO $ U.logs $ "reactor:got RenameRequest:" ++ show req
        let rid = J.responseId $ req ^. J.id
            params = req ^. J.params
            doc = params ^. J.textDocument
            uri = decodeUri $ doc ^. J.uri
            loc = decodeLoc $ params ^. J.position
            newName = T.unpack $ params ^. J.newName
        refactorFile (outDelegate rid uri) uri (BasicInj.renameSymbolAt loc newName) cache

      -- -------------------------------

      Core.ReqHover req -> do
        liftIO $ U.logs $ "reactor:got HoverRequest:" ++ show req
        {- let J.TextDocumentPositionParams _doc pos = req ^. J.params

        let ht = J.Hover ms (Just range)
            ms = J.List [J.CodeString $ J.LanguageString "descript" "TYPE INFO" ]
            range = J.Range pos pos
        reactorSend $ Core.makeResponseMessage req ht -}

      -- -------------------------------

      Core.ReqCodeAction req -> do
        liftIO $ U.logs $ "reactor:got CodeActionRequest:" ++ show req
        {- let params = req ^. J.params
            doc = params ^. J.textDocument
            -- fileName = drop (length ("file://"::String)) doc
            -- J.Range from to = J._range (params :: J.CodeActionParams)
            (J.List diags) = params ^. J.context . J.diagnostics

        let
          -- makeCommand only generates commands for diagnostics whose source is us
          makeCommand (J.Diagnostic (J.Range start _) _s _c (Just "descript") _m _l) = [J.Command title cmd cmdparams]
            where
              title = "Apply Descript command:" <> head (T.lines _m)
              -- NOTE: the cmd needs to be registered via the InitializeResponse message. See lspOptions above
              cmd = "rename"
              -- need 'file' and 'start_pos'
              args = J.Array$ V.fromList
                      [ J.Object $ H.fromList [("file",     J.Object $ H.fromList [("textDocument",J.toJSON doc)])]
                      , J.Object $ H.fromList [("start_pos",J.Object $ H.fromList [("position",    J.toJSON start)])]
                      ]
              cmdparams = Just args
          makeCommand (J.Diagnostic _r _s _c _source _m _l) = []
        let body = J.List $ concatMap makeCommand diags
        reactorSend $ Core.makeResponseMessage req body -}

      -- -------------------------------

      Core.ReqExecuteCommand req -> do
        liftIO $ U.logs $ "reactor:got ExecuteCommandRequest:" ++ show req
        {- let params = req ^. J.params
            margs = params ^. J.arguments

        liftIO $ U.logs $ "reactor:ExecuteCommandRequest:margs=" ++ show margs

        let
          reply v = reactorSend $ Core.makeResponseMessage req v
        -- When we get a RefactorResult or HieDiff, we need to send a
        -- separate WorkspaceEdit Notification
          r = J.List [] :: J.List Int
        liftIO $ U.logs $ "ExecuteCommand response got:r=" ++ show r
        case toWorkspaceEdit r of
          Just we -> do
            reply (J.Object mempty)
            lid <- nextLspReqId
            -- reactorSend $ J.RequestMessage "2.0" lid "workspace/applyEdit" (Just we)
            reactorSend $ fmServerApplyWorkspaceEditRequest lid we
          Nothing ->
            reply (J.Object mempty) -}

      -- -------------------------------

      Core.ReqCompletion req -> do
        liftIO $ U.logs $ "reactor:got CompletionRequest:" ++ show req
        {- let params = req ^. J.params
            filePath = J.uriToFilePath $ params ^. J.textDocument ^. J.uri
            pos = params ^. J.position

        mprefix <- getPrefixAtPos doc pos

        callback <- hieResponseHelper (req ^. J.id) $ \compls -> do
          let rspMsg = Core.makeResponseMessage req
                         $ J.Completions $ J.List compls
          reactorSend rspMsg
        case mprefix of
          Nothing -> liftIO $ callback $ IdeResponseOk []
          Just prefix -> do
            let hreq = IReq (req ^. J.id) callback
                         $ Hie.getCompletions doc prefix
            makeRequest hreq -}

      Core.ReqCompletionItemResolve req -> do
        liftIO $ U.logs $ "reactor:got CompletionItemResolveRequest:" ++ show req
        {- let origCompl = req ^. J.params
            mquery = case J.fromJSON <$> origCompl ^. J.xdata of
                       Just (J.Success q) -> Just q
                       _ -> Nothing
        callback <- hieResponseHelper (req ^. J.id) $ \docs -> do
          let rspMsg = Core.makeResponseMessage req $
                         origCompl & J.documentation .~ docs
          reactorSend rspMsg
        let hreq = GReq Nothing Nothing (Just $ req ^. J.id) callback $ runExceptT $ do
              case mquery of
                Nothing -> return Nothing
                Just query -> do
                  res <- lift $ liftToGhc $ Hoogle.infoCmd' query
                  case res of
                    Right x -> return $ Just x
                    _ -> return Nothing
        makeRequest hreq -}

      -- -------------------------------

      Core.ReqDocumentHighlights req -> do
        liftIO $ U.logs $ "reactor:got DocumentHighlightsRequest:" ++ show req
        {- let rid = J.responseId $ req ^. J.id
            params = req ^. J.params
            doc = params ^. J.textDocument
            uri = decodeUri $ doc ^. J.uri
            loc = decodeLoc $ params ^. J.position
        refsRes <- inspectFile uri (BasicInj.refsToSymbolAt loc) cache
        case refsRes of
          Failure err -> do
            let msg = Text.pack $ summary err
                rerr = J.ResponseError J.InvalidRequest msg Nothing
            Core.makeResponseError req rerr
          Just refs -> do
            let erefs = encodeReferences refs
                rsp = Core.makeResponseMessage req erefs
            reactorSend rsp -}

        {- let params = req ^. J.params
            doc = params ^. J.textDocument ^. J.uri
            pos = params ^. J.position
        callback <- hieResponseHelper (req ^. J.id) $ \highlights -> do
          let rspMsg = Core.makeResponseMessage req $ J.List highlights
          reactorSend rspMsg
        let hreq = IReq (req ^. J.id) callback
                 $ Hie.getReferencesInDoc doc pos
        makeRequest hreq -}

      -- -------------------------------

      Core.ReqDefinition req -> do
        liftIO $ U.logs $ "reactor:got DefinitionRequest:" ++ show req
        {- let params = req ^. J.params
            doc = params ^. J.textDocument . J.uri
            pos = params ^. J.position
        callback <- hieResponseHelper (req ^. J.id) $ \loc -> do
            let rspMsg = Core.makeResponseMessage req loc
            reactorSend rspMsg
        let hreq = GReq (Just doc) Nothing (Just $ req ^. J.id) callback
                     $ fmap J.MultiLoc <$> Hie.findDef doc pos
        makeRequest hreq -}

      Core.ReqFindReferences req -> do
        liftIO $ U.logs $ "reactor:got FindReferences:" ++ show req
        {- let params = req ^. J.params
            doc = params ^. J.textDocument ^. J.uri
            pos = params ^. J.position
        callback <- hieResponseHelper (req ^. J.id) $ \highlights -> do
          let rspMsg = Core.makeResponseMessage req $ J.List highlights
          reactorSend rspMsg
        let hreq = IReq (req ^. J.id) callback
                 $ fmap (map (J.Location doc . (^. J.range)))
                 <$> Hie.getReferencesInDoc doc pos
        makeRequest hreq -}

      -- -------------------------------

      Core.ReqDocumentFormatting req -> do
        liftIO $ U.logs $ "reactor:got FormatRequest:" ++ show req
        {- let params = req ^. J.params
            doc = params ^. J.textDocument . J.uri
            tabSize = params ^. J.options . J.tabSize
        callback <- hieResponseHelper (req ^. J.id) $ \textEdit -> do
            let rspMsg = Core.makeResponseMessage req $ J.List textEdit
            reactorSend rspMsg
        let hreq = GReq (Just doc) Nothing (Just $ req ^. J.id) callback
                     $ Brittany.brittanyCmd tabSize doc Nothing
        makeRequest hreq -}

      -- -------------------------------

      Core.ReqDocumentRangeFormatting req -> do
        liftIO $ U.logs $ "reactor:got FormatRequest:" ++ show req
        {- let params = req ^. J.params
            doc = params ^. J.textDocument . J.uri
            range = params ^. J.range
            tabSize = params ^. J.options . J.tabSize
        callback <- hieResponseHelper (req ^. J.id) $ \textEdit -> do
            let rspMsg = Core.makeResponseMessage req $ J.List textEdit
            reactorSend rspMsg
        let hreq = GReq (Just doc) Nothing (Just $ req ^. J.id) callback
                     $ Brittany.brittanyCmd tabSize doc (Just range)
        makeRequest hreq -}

      -- -------------------------------

      Core.ReqDocumentSymbols req -> do
        liftIO $ U.logs $ "reactor:got Document symbol request:" ++ show req
        {- let uri = req ^. J.params . J.textDocument . J.uri
        callback <- hieResponseHelper (req ^. J.id) $ \docSymbols -> do
            let rspMsg = Core.makeResponseMessage req $ J.List docSymbols
            reactorSend rspMsg
        let hreq = IReq (req ^. J.id) callback
                 $ Hie.getSymbols uri
        makeRequest hreq -}

      -- -------------------------------

      Core.NotCancelRequest notif -> do
        liftIO $ U.logs $ "reactor:got CancelRequest:" ++ show notif
        {- let lid = notif ^. J.params . J.id
        liftIO $ atomically $ do
          wip <- readTVar wipTVar
          when (S.member lid wip) $ do
            modifyTVar' cancelReqTVar (S.insert lid) -}

      -- -------------------------------

      Core.NotDidChangeConfigurationParams notif -> do
        liftIO $ U.logs $ "reactor:didChangeConfiguration notification:" ++ show notif
        {- -- if hlint has been turned off, flush the disgnostics
        diagsOn              <- configVal True hlintOn
        maxDiagnosticsToSend <- configVal 50 maxNumberOfProblems
        liftIO $ U.logs $ "reactor:didChangeConfiguration diagsOn:" ++ show diagsOn
        -- If hlint is off, remove the diags. But make sure they get sent, in
        -- case maxDiagnosticsToSend has changed.
        if diagsOn
          then flushDiagnosticsBySource maxDiagnosticsToSend Nothing
          else flushDiagnosticsBySource maxDiagnosticsToSend (Just "hlint") -}

      -- -------------------------------

      om -> do
        liftIO $ U.logs $ "reactor:got HandlerRequest:" ++ show om

-- ---------------------------------------------------------------------

inDelegate :: FilePath
           -> CacheDelegate (ReaderT (Core.LspFuncs Config) IO)
inDelegate uri
  = CacheDelegate
  { onUpdateFile = onInUpdateFile uri
  , onWarning = onInWarning
  , onError = onInError uri
  }

outDelegate :: J.LspIdRsp
            -> FilePath
            -> CacheDelegate (ReaderT (Core.LspFuncs Config) IO)
outDelegate rid uri
  = CacheDelegate
  { onUpdateFile = onOutUpdateFile rid uri
  , onWarning = onOutWarning
  , onError = onOutError rid uri
  }

onInUpdateFile :: FilePath
               -> FileUpdate
               -> FileCache
               -> R ()
onInUpdateFile uri (UpdateBasicInj _) x = do
  liftIO $ U.logs $ "********* updating basic"
  let uri' = encodeUri uri
      version = fileVersion x
  let diags = BasicInj.diagnose $ forceGetPhaseCache $ srcBasicInj x
      ediags = partitionBySource $ map encodeDiagnostic diags
  publishDiagnostics uri' (Just version) ediags
onInUpdateFile _ _ _ = pure ()

onOutUpdateFile :: J.LspIdRsp
                -> FilePath
                -> FileUpdate
                -> FileCache
                -> R ()
onOutUpdateFile rid uri (UpdateText new patchOpt) x = do
  liftIO $ U.logs "********* updating text"
  let old = phaseCached $ srcText x
      patchOpt' = patchOpt <|> fmap (`replacePatch` new) old
  case patchOpt' of
    Nothing -> onOutError rid uri CachePatchBeforeText
    Just patch -> do
      let uri' = encodeUri uri
          doc = J.VersionedTextDocumentIdentifier uri' $ fileVersion x
          de = J.TextDocumentEdit doc $ encodePatch patch
          we = J.WorkspaceEdit Nothing $ Just $ J.List [de]
          rspMsg = makeResponseMessage' rid we
      reactorSend rspMsg
onOutUpdateFile _ _ _ _ = pure ()

onInWarning :: CacheWarning
            -> R ()
            -> R ()
onInWarning _ continue = continue

onOutWarning :: CacheWarning
             -> R ()
             -> R ()
onOutWarning (CacheRefactorWarning _ _) continue = continue
  {- do
  let params = J.ShowMessageRequestParams J.MtWarning ...
                     (Just [J.MessageActionItem "Continue", J.MessageActionItem "Cancel"])
  rid <- nextLspReqId

  reactorSend $ fmServerShowMessageRequest rid params -}

onInError :: FilePath
          -> CacheError
          -> R ()
onInError uri (CacheParseError x err) = do
  liftIO $ U.logs "********* got error: CacheParseError"
  let uri' = encodeUri uri
      version = fileVersion x
      eerr = encodeParseError err
      diags = partitionBySource eerr
  publishDiagnostics uri' (Just version) diags
onInError _ err = do
  liftIO $ U.logs $ "******** got unexpected in error: " ++ show err

onOutError :: J.LspIdRsp
           -> FilePath
           -> CacheError
           -> R ()
onOutError rid _ CacheFileNotFound = do
  liftIO $ U.logs "********* got error: CacheFileNotFound"
  let err = J.ResponseError J.InternalError fileNotFoundErrMsg Nothing
      rspMsg = Core.makeResponseError rid err
  reactorSend rspMsg
onOutError rid _ CachePatchBeforeText = do
  liftIO $ U.logs "********* got error: CachePathBeforeText"
  let err = J.ResponseError J.InternalError patchBeforeTextErrMsg Nothing
      rspMsg = Core.makeResponseError rid err
  reactorSend rspMsg
onOutError rid _ CacheRefactorBeforeParse = do
  liftIO $ U.logs "********* got error: CacheRefactorBeforeParse"
  let err = J.ResponseError J.InternalError refactorBeforeParseErrMsg Nothing
      rspMsg = Core.makeResponseError rid err
  reactorSend rspMsg
onOutError rid uri (CacheRefactorError x err) = do
  let file = mkSFile uri $ forceGetPhaseCache $ srcText x
      msg = "Refactor error: " <> T.pack (summaryF file err)
      rerr = J.ResponseError J.InvalidRequest msg Nothing
      rspMsg = Core.makeResponseError rid rerr
  reactorSend rspMsg
onOutError _ _ err = do
  liftIO $ U.logs $ "********* got unexpected out error: " ++ show err

fileNotFoundErrMsg :: T.Text
fileNotFoundErrMsg = "No file cached at this path. Reopen the file to fix."

patchBeforeTextErrMsg :: T.Text
patchBeforeTextErrMsg = "No text cached at this path. Reopen the file to fix."

refactorBeforeParseErrMsg :: T.Text
refactorBeforeParseErrMsg = "Can't refactor because the source isn't parsed."

-- ---------------------------------------------------------------------

-- toWorkspaceEdit :: t -> Maybe J.ApplyWorkspaceEditParams
-- toWorkspaceEdit _ = Nothing

-- ---------------------------------------------------------------------

{- -- | Analyze the file and send any diagnostics to the client in a
-- "textDocument/publishDiagnostics" notification
sendDiagnostics :: J.Uri -> Maybe Int -> R ()
sendDiagnostics fileUri mversion = do
  let
    diags = [J.Diagnostic
              (J.Range (J.Position 0 1) (J.Position 0 5))
              (Just J.DsWarning)  -- severity
              Nothing  -- code
              (Just "descript-server") -- source
              "Example diagnostic message"
              (Just (J.List []))
            ]
  -- reactorSend $ J.NotificationMessage "2.0" "textDocument/publishDiagnostics" (Just r)
  publishDiagnostics 100 fileUri mversion (partitionBySource diags) -}

-- ---------------------------------------------------------------------

syncOptions :: J.TextDocumentSyncOptions
syncOptions = J.TextDocumentSyncOptions
  { J._openClose         = Just True
  , J._change            = Just J.TdSyncIncremental
  , J._willSave          = Just False
  , J._willSaveWaitUntil = Just False
  , J._save              = Just $ J.SaveOptions $ Just False
  }

lspOptions :: Core.Options
lspOptions = def { Core.textDocumentSync = Just syncOptions
                 , Core.completionProvider = Just (J.CompletionOptions (Just True) (Just ["<", ">"]))
                 , Core.executeCommandProvider = Just (J.ExecuteCommandOptions (J.List ["rename"]))
                 }

lspHandlers :: TChan ReactorInput -> Core.Handlers
lspHandlers rin
  = def { Core.initializedHandler                       = Just $ passHandler rin Core.NotInitialized
        , Core.renameHandler                            = Just $ passHandler rin Core.ReqRename
        , Core.hoverHandler                             = Just $ passHandler rin Core.ReqHover
        , Core.didOpenTextDocumentNotificationHandler   = Just $ passHandler rin Core.NotDidOpenTextDocument
        , Core.didSaveTextDocumentNotificationHandler   = Just $ passHandler rin Core.NotDidSaveTextDocument
        , Core.didChangeTextDocumentNotificationHandler = Just $ passHandler rin Core.NotDidChangeTextDocument
        , Core.didCloseTextDocumentNotificationHandler  = Just $ passHandler rin Core.NotDidCloseTextDocument
        , Core.cancelNotificationHandler                = Just $ passHandler rin Core.NotCancelRequest
        , Core.responseHandler                          = Just $ responseHandlerCb rin
        , Core.codeActionHandler                        = Just $ passHandler rin Core.ReqCodeAction
        , Core.executeCommandHandler                    = Just $ passHandler rin Core.ReqExecuteCommand
        , Core.completionHandler                        = Just $ passHandler rin Core.ReqCompletion
        , Core.completionResolveHandler                 = Just $ passHandler rin Core.ReqCompletionItemResolve
        , Core.documentHighlightHandler                 = Just $ passHandler rin Core.ReqDocumentHighlights
        , Core.documentFormattingHandler                = Just $ passHandler rin Core.ReqDocumentFormatting
        , Core.documentRangeFormattingHandler           = Just $ passHandler rin Core.ReqDocumentRangeFormatting
        , Core.documentSymbolHandler                    = Just $ passHandler rin Core.ReqDocumentSymbols
        }

-- ---------------------------------------------------------------------

passHandler :: TChan ReactorInput -> (a -> Core.OutMessage) -> Core.Handler a
passHandler rin c notification = do
  atomically $ writeTChan rin (HandlerRequest (c notification))

-- ---------------------------------------------------------------------

responseHandlerCb :: TChan ReactorInput -> Core.Handler J.BareResponseMessage
responseHandlerCb _rin resp = do

  U.logs $ "******** got ResponseMessage, ignoring:" ++ show resp

-- ---------------------------------------------------------------------