Add diagnostics for Megaparsec parsing

This commit is contained in:
Patrick Aldis 2026-03-06 12:11:24 +00:00
parent dedc72789b
commit 7ac8aef519
8 changed files with 91 additions and 51 deletions

View File

@ -6,10 +6,8 @@ import Language.LSP.Protocol.Types
import Language.LSP.Server import Language.LSP.Server
import Datalog.LSP.Hover (hoverHandler) import Datalog.LSP.Hover (hoverHandler)
import Datalog.LSP.Highlight (tokenHandler) import Datalog.LSP.Highlight (tokenHandler)
import Datalog.LSP.Types (DLogLspM, LSPContext (LSPContext)) import Datalog.LSP.Types (DLogLspM, LSPState)
import Datalog.LSP.DocChange (docChangeHandler, docOpenHandler) import Datalog.LSP.DocChange (docChangeHandler, docOpenHandler)
import qualified Data.Map as M
import Control.Concurrent.STM
handlers :: Handlers DLogLspM handlers :: Handlers DLogLspM
handlers = handlers =
@ -24,13 +22,12 @@ handlers =
initHandler :: Handlers DLogLspM initHandler :: Handlers DLogLspM
initHandler = notificationHandler SMethod_Initialized $ \_ -> pure () initHandler = notificationHandler SMethod_Initialized $ \_ -> pure ()
serverDefinition :: IO (ServerDefinition LSPContext) serverDefinition :: LSPState -> ServerDefinition LSPState
serverDefinition = do serverDefinition context =
ref <- newTVarIO M.empty ServerDefinition
pure $ ServerDefinition
{ parseConfig = \c v -> Right c { parseConfig = \c v -> Right c
, onConfigChange = const $ pure () , onConfigChange = const $ pure ()
, defaultConfig = LSPContext ref , defaultConfig = context
, configSection = "demo" , configSection = "demo"
, doInitialize = \env _req -> pure $ Right env , doInitialize = \env _req -> pure $ Right env
, staticHandlers = const handlers , staticHandlers = const handlers

View File

@ -3,15 +3,18 @@
module Datalog.LSP.DocChange (docChangeHandler, docOpenHandler) where module Datalog.LSP.DocChange (docChangeHandler, docOpenHandler) where
import Control.Concurrent.STM import Control.Concurrent.STM
import Control.Lens ((^.))
import Control.Monad.Trans import Control.Monad.Trans
import Data.List.NonEmpty qualified as NE
import Data.Map qualified as M import Data.Map qualified as M
import Data.Text qualified as T
import Datalog.LSP.Types import Datalog.LSP.Types
import Datalog.LSP.Utils (currentBufferText, currentBufferUri) import Datalog.LSP.Utils (currentBufferText, currentBufferUri, currentBufferUriUnNormalized)
import Datalog.Parser (parseProgram) import Datalog.Parser (parseProgram)
import Language.LSP.Protocol.Lens (HasParams, HasTextDocument, HasUri) import Language.LSP.Protocol.Lens (HasParams, HasTextDocument, HasUri)
import Language.LSP.Protocol.Message (SMethod (SMethod_TextDocumentDidChange, SMethod_TextDocumentDidOpen)) import Language.LSP.Protocol.Message (SMethod (SMethod_TextDocumentDidChange, SMethod_TextDocumentDidOpen, SMethod_TextDocumentPublishDiagnostics))
import Language.LSP.Protocol.Types (Uri) import Language.LSP.Protocol.Types (Diagnostic (..), DiagnosticSeverity (DiagnosticSeverity_Error), Position (..), PublishDiagnosticsParams (..), Range (Range), Uri)
import Language.LSP.Server (Handlers, MonadLsp, getConfig, notificationHandler) import Language.LSP.Server (Handlers, MonadLsp, getConfig, notificationHandler, sendNotification)
import Text.Megaparsec import Text.Megaparsec
docOpenHandler :: Handlers DLogLspM docOpenHandler :: Handlers DLogLspM
@ -20,12 +23,45 @@ docOpenHandler = notificationHandler SMethod_TextDocumentDidOpen updateState
docChangeHandler :: Handlers DLogLspM docChangeHandler :: Handlers DLogLspM
docChangeHandler = notificationHandler SMethod_TextDocumentDidChange updateState docChangeHandler = notificationHandler SMethod_TextDocumentDidChange updateState
updateState :: (HasParams s a1, MonadLsp LSPContext (t IO), HasUri a2 Uri, HasTextDocument a1 a2, MonadTrans t) => s -> t IO () updateState :: (HasParams s a1, MonadLsp LSPState (t IO), HasUri a2 Uri, HasTextDocument a1 a2, MonadTrans t) => s -> t IO ()
updateState req = do updateState req = do
let uri = currentBufferUri req let uri = currentBufferUri req
LSPContext parseStateRef <- getConfig
text <- currentBufferText req parseRef <- parseState <$> getConfig
lift . atomically $ do bufferText <- currentBufferText req
v <- readTVar parseStateRef parseResult <- lift . atomically $ do
writeTVar parseStateRef $ v <- readTVar parseRef
M.insert uri (runParser parseProgram (show uri) text) v
let parsedBuffer = runParser parseProgram (show uri) bufferText
writeTVar parseRef $
M.insert uri parsedBuffer v
pure parsedBuffer
sendNotification
SMethod_TextDocumentPublishDiagnostics
PublishDiagnosticsParams
{ _uri = req ^. currentBufferUriUnNormalized
, _version = Nothing
, _diagnostics = case parseResult of
Right _ -> []
Left (ParseErrorBundle errs position) ->
NE.toList . flip fmap (fst $ attachSourcePos errorOffset errs position) $ \(err, pos) ->
Diagnostic
{ _range = let p = sourcePosToPosition pos in Range p p
, _severity = Just DiagnosticSeverity_Error
, _code = Nothing
, _codeDescription = Nothing
, _source = Nothing
, _message = T.pack . parseErrorTextPretty $ err
, _tags = Nothing
, _relatedInformation = Nothing
, _data_ = Nothing
}
}
sourcePosToPosition :: SourcePos -> Position
sourcePosToPosition (SourcePos _ line column) = Position (unPos' line) (unPos' column)
where
unPos' = fromIntegral . (\x -> x - 1) . unPos

View File

@ -5,7 +5,7 @@ import Control.Monad.Trans
import Data.Either (fromRight) import Data.Either (fromRight)
import Data.Map qualified as M import Data.Map qualified as M
import Data.Text qualified as T import Data.Text qualified as T
import Datalog.LSP.Types (DLogLspM, LSPContext (..)) import Datalog.LSP.Types (DLogLspM, LSPState (..))
import Datalog.LSP.Utils (currentBufferUri) import Datalog.LSP.Utils (currentBufferUri)
import Datalog.Parser (SrcLoc (..)) import Datalog.Parser (SrcLoc (..))
import Datalog.Syntax (Atom' (Atom), Program' (..), RelId (RelId), Rule' (..), Term' (..)) import Datalog.Syntax (Atom' (Atom), Program' (..), RelId (RelId), Rule' (..), Term' (..))
@ -15,7 +15,7 @@ import Language.LSP.Server
tokenHandler :: Handlers DLogLspM tokenHandler :: Handlers DLogLspM
tokenHandler = requestHandler SMethod_TextDocumentSemanticTokensFull $ \req responder -> do tokenHandler = requestHandler SMethod_TextDocumentSemanticTokensFull $ \req responder -> do
LSPContext parseRef <- getConfig LSPState parseRef <- getConfig
p <- lift . readTVarIO $ parseRef p <- lift . readTVarIO $ parseRef
case M.lookup (currentBufferUri req) p of case M.lookup (currentBufferUri req) p of
Nothing -> Nothing ->
@ -26,7 +26,7 @@ tokenHandler = requestHandler SMethod_TextDocumentSemanticTokensFull $ \req resp
responder responder
( Right ( Right
. InL . InL
. fromRight (error "") . fromRight (SemanticTokens Nothing [])
. makeSemanticTokens defaultSemanticTokensLegend . makeSemanticTokens defaultSemanticTokensLegend
$ highlightProg prog $ highlightProg prog
) )

View File

@ -1,24 +1,19 @@
module Datalog.LSP.Hover where module Datalog.LSP.Hover where
import Control.Lens ((^.), Lens') import Datalog.LSP.Types (DLogLspM)
import Data.Maybe (fromJust) import Datalog.LSP.Utils (currentBufferText)
import Language.LSP.Protocol.Lens (params, textDocument, uri, HasTextDocument, HasParams, HasUri)
import Language.LSP.Protocol.Message import Language.LSP.Protocol.Message
import Language.LSP.Protocol.Types import Language.LSP.Protocol.Types
import Language.LSP.Server import Language.LSP.Server
import Language.LSP.VFS (virtualFileText)
import Datalog.LSP.Types (DLogLspM)
hoverHandler :: Handlers DLogLspM hoverHandler :: Handlers DLogLspM
hoverHandler = requestHandler SMethod_TextDocumentHover $ \req responder -> do hoverHandler = requestHandler SMethod_TextDocumentHover $ \req responder -> do
let text <- currentBufferText req
openUri = toNormalizedUri $ req ^. docUri responder
TRequestMessage _ _ _ (HoverParams _doc pos _workDone) = req ( Right
Position _l _c' = pos . InL
rsp txt = Hover (InL . mkMarkdown $ txt) (Just range) $ Hover
range = Range pos pos { _contents = InL . mkMarkdown $ text
c <- fromJust <$> getVirtualFile openUri , _range = Nothing
responder (Right . InL . rsp . virtualFileText $ c) }
)
docUri :: (HasParams s s1, HasTextDocument s1 s2, HasUri s2 a) => Lens' s a
docUri = params . textDocument . uri

View File

@ -1,6 +1,5 @@
module Datalog.LSP.Types (DLogLspM, LSPContext(..)) where module Datalog.LSP.Types (DLogLspM, LSPState(..)) where
import Control.Monad.Reader (ReaderT)
import Language.LSP.Server (LspM) import Language.LSP.Server (LspM)
import Control.Concurrent.STM (TVar) import Control.Concurrent.STM (TVar)
import Text.Megaparsec import Text.Megaparsec
@ -11,10 +10,10 @@ import Language.LSP.Protocol.Types qualified as J
import Datalog.Syntax (Program') import Datalog.Syntax (Program')
import Datalog.Parser (SrcLoc) import Datalog.Parser (SrcLoc)
type DLogLspM = LspM LSPContext type DLogLspM = LspM LSPState
type UriBundle a = Map J.NormalizedUri a type UriBundle a = Map J.NormalizedUri a
data LSPContext = LSPContext data LSPState = LSPState
{ parseState :: TVar (UriBundle (Either (ParseErrorBundle Text Void) (Program' SrcLoc))) { parseState :: TVar (UriBundle (Either (ParseErrorBundle Text Void) (Program' SrcLoc)))
} }

View File

@ -8,9 +8,12 @@ import Language.LSP.Protocol.Types
import Language.LSP.Server import Language.LSP.Server
import Language.LSP.VFS import Language.LSP.VFS
currentBufferText :: (MonadLsp config f, HasParams s a1, HasTextDocument a1 a2, HasUri a2 Uri) => s -> f T.Text currentBufferText :: (MonadLsp config m, HasParams s a1, HasTextDocument a1 a2, HasUri a2 Uri) => s -> m T.Text
currentBufferText req = virtualFileText . fromJust <$> getVirtualFile (currentBufferUri req) currentBufferText = fmap (virtualFileText . fromJust) . (getVirtualFile . currentBufferUri)
currentBufferUri :: (HasParams s a1, HasTextDocument a1 a2, HasUri a2 Uri) => s -> NormalizedUri currentBufferUri :: (HasParams s a1, HasTextDocument a1 a2, HasUri a2 Uri) => s -> NormalizedUri
currentBufferUri req = toNormalizedUri $ req ^. (params . textDocument . uri) currentBufferUri = toNormalizedUri . view currentBufferUriUnNormalized
currentBufferUriUnNormalized :: (HasParams s a1, HasTextDocument a1 a2, HasUri a2 a3) => Lens' s a3
currentBufferUriUnNormalized = params . textDocument . uri

View File

@ -1,5 +1,14 @@
import Control.Concurrent.STM (newTVarIO)
import Datalog.LSP (serverDefinition) import Datalog.LSP (serverDefinition)
import Datalog.LSP.Types (LSPState (..))
import Language.LSP.Server (runServer) import Language.LSP.Server (runServer)
main :: IO Int main :: IO Int
main = serverDefinition >>= runServer main = do
ref <- newTVarIO mempty
runServer $
serverDefinition
( LSPState
{ parseState = ref
}
)

View File

@ -1,10 +1,11 @@
vim.filetype.add({ vim.filetype.add({
extension = { extension = {
lsptest = "lsptest" mydatalog = "dl"
} }
}) })
vim.lsp.config['geolog'] = { vim.lsp.config['mydatalog'] = {
cmd = { './result/bin/geolog-lsp' }, cmd = { './result/bin/datalog-lsp' },
filetypes = { 'lsptest'} filetypes = { 'mydatalog' }
} }
vim.lsp.enable('geolog') vim.lsp.enable('mydatalog')