swarm-0.1.1.0: src/Swarm/Language/LSP.hs
{-# LANGUAGE OverloadedStrings #-}
-- |
-- Module : Swarm.Language.LSP
-- Copyright : Brent Yorgey
-- Maintainer : byorgey@gmail.com
--
-- SPDX-License-Identifier: BSD-3-Clause
--
-- Language Server Protocol (LSP) server for the Swarm language.
-- See the docs/EDITORS.md to learn how to use it.
module Swarm.Language.LSP where
import Control.Lens (to, (^.))
import Control.Monad (void)
import Control.Monad.IO.Class
import Data.Foldable (forM_)
import Data.Maybe (fromMaybe)
import Data.Text (Text)
import Data.Text.IO qualified as Text
import Language.LSP.Diagnostics
import Language.LSP.Server
import Language.LSP.Types qualified as J
import Language.LSP.Types.Lens qualified as J
import Language.LSP.VFS
import Swarm.Language.Parse
import Swarm.Language.Pipeline
import System.IO (stderr)
import Witch
lspMain :: IO ()
lspMain =
void $
runServer $
ServerDefinition
{ onConfigurationChange = const $ const $ Right ()
, defaultConfig = ()
, doInitialize = \env _req -> pure $ Right env
, staticHandlers = handlers
, interpretHandler = \env -> Iso (runLspT env) liftIO
, options =
defaultOptions
{ -- set sync options to get DidSave event
textDocumentSync =
Just
( J.TextDocumentSyncOptions
(Just True)
(Just syncKind)
(Just False)
(Just False)
(Just $ J.InR $ J.SaveOptions $ Just True)
)
}
}
where
-- Using SyncFull seems to handle the debounce for us.
-- The alternative is to use SyncIncremental, but then then
-- handler is called for each key-stroke.
syncKind = J.TdSyncFull
debug :: MonadIO m => Text -> m ()
debug msg = liftIO $ Text.hPutStrLn stderr $ "[swarm-lsp] " <> msg
validateSwarmCode :: J.NormalizedUri -> J.TextDocumentVersion -> Text -> LspM () ()
validateSwarmCode doc version content = do
-- debug $ "Validating: " <> from (show doc) <> " ( " <> content <> ")"
flushDiagnosticsBySource 0 (Just "swarm-lsp")
let err = case readTerm' content of
Right Nothing -> Nothing
Right (Just term) -> case processParsedTerm' mempty mempty term of
Right _ -> Nothing
Left e -> Just $ showTypeErrorPos content e
Left e -> Just $ showErrorPos e
-- debug $ "-> " <> from (show err)
forM_ err sendDiagnostic
where
sendDiagnostic :: ((Int, Int), (Int, Int), Text) -> LspM () ()
sendDiagnostic ((startLine, startCol), (endLine, endCol), msg) = do
let diags =
[ J.Diagnostic
( J.Range
(J.Position (fromIntegral startLine) (fromIntegral startCol))
(J.Position (fromIntegral endLine) (fromIntegral endCol))
)
(Just J.DsWarning) -- severity
Nothing -- code
(Just "swarm-lsp") -- source
msg
Nothing -- tags
(Just (J.List []))
]
publishDiagnostics 1 doc version (partitionBySource diags)
handlers :: Handlers (LspM ())
handlers =
mconcat
[ notificationHandler J.SInitialized $ \_not -> do
debug "Initialized"
, notificationHandler J.STextDocumentDidSave $ \msg -> do
let doc = msg ^. J.params . J.textDocument . J.uri
content = fromMaybe "?" $ msg ^. J.params . J.text
validateSwarmCode (J.toNormalizedUri doc) Nothing content
, notificationHandler J.STextDocumentDidOpen $ \msg -> do
let doc = msg ^. J.params . J.textDocument . J.uri
content = msg ^. J.params . J.textDocument . J.text
validateSwarmCode (J.toNormalizedUri doc) Nothing content
, notificationHandler J.STextDocumentDidChange $ \msg -> do
let doc = msg ^. J.params . J.textDocument . J.uri . to J.toNormalizedUri
mdoc <- getVirtualFile doc
case mdoc of
Just vf@(VirtualFile _ version _rope) -> do
validateSwarmCode doc (Just $ fromIntegral version) (virtualFileText vf)
_ -> debug $ "No virtual file found for: " <> from (show msg)
]