Compare commits

..

7 Commits

Author SHA1 Message Date
Patrick Aldis
026c66bce0 Add diagnostics for Megaparsec parsing 2026-03-06 12:11:31 +00:00
Patrick Aldis
dedc72789b Refactor cases to move out let binding 2026-03-05 16:05:15 +00:00
Patrick Aldis
35b2fa4282 Use STM to share state between handlers 2026-03-05 15:59:45 +00:00
Patrick Aldis
dd761e8321 Highlighting doesnt crash on failed parse 2026-03-03 16:42:20 +00:00
Patrick Aldis
eac62e4198 working highlighting 2026-03-03 16:31:44 +00:00
Patrick Aldis
108001e987 Rudimentary highlighting 2026-03-03 16:00:36 +00:00
Patrick Aldis
6e726dfe54 datalog-lsp sensible file structure 2026-03-03 16:00:04 +00:00
12 changed files with 227 additions and 56 deletions

View File

@ -1,4 +1,4 @@
module Datalog.LSP where module Datalog.LSP (serverDefinition) where
import Control.Monad.IO.Class import Control.Monad.IO.Class
import Language.LSP.Protocol.Message import Language.LSP.Protocol.Message
@ -6,24 +6,31 @@ 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.DocChange (docChangeHandler, docOpenHandler)
import qualified Data.Map as M
import Control.Concurrent.STM
handlers :: Handlers (LspM ()) handlers :: Handlers DLogLspM
handlers = handlers =
mconcat mconcat
[ initHandler [ initHandler
, docChangeHandler
, docOpenHandler
, hoverHandler , hoverHandler
, tokenHandler , tokenHandler
] ]
initHandler :: Handlers (LspM ()) initHandler :: Handlers DLogLspM
initHandler = notificationHandler SMethod_Initialized $ \_ -> pure () initHandler = notificationHandler SMethod_Initialized $ \_ -> pure ()
serverDefinition :: ServerDefinition () serverDefinition :: IO (ServerDefinition LSPContext)
serverDefinition = serverDefinition = do
ServerDefinition ref <- newTVarIO M.empty
{ parseConfig = const $ const $ Right () pure $ ServerDefinition
{ parseConfig = \c v -> Right c
, onConfigChange = const $ pure () , onConfigChange = const $ pure ()
, defaultConfig = () , defaultConfig = LSPContext ref
, configSection = "demo" , configSection = "demo"
, doInitialize = \env _req -> pure $ Right env , doInitialize = \env _req -> pure $ Right env
, staticHandlers = const handlers , staticHandlers = const handlers

View File

@ -0,0 +1,77 @@
{-# LANGUAGE BlockArguments #-}
module Datalog.LSP.DocChange (docChangeHandler, docOpenHandler) where
import Control.Concurrent.STM
import Control.Monad.Trans
import Data.Map qualified as M
import Data.Text qualified as T
import Datalog.LSP.Types
import Datalog.LSP.Utils (currentBufferText, currentBufferUri, currentBufferUriUnNormalized)
import Datalog.Parser (parseProgram)
import Language.LSP.Protocol.Lens (HasParams, HasTextDocument, HasUri)
import Language.LSP.Protocol.Message (SMethod (SMethod_TextDocumentDidChange, SMethod_TextDocumentDidOpen, SMethod_TextDocumentPublishDiagnostics))
import Language.LSP.Protocol.Types (Diagnostic (..), DiagnosticSeverity (DiagnosticSeverity_Error), Position (..), PublishDiagnosticsParams (..), Range (Range), Uri)
import Language.LSP.Server (Handlers, MonadLsp, getConfig, notificationHandler, sendNotification)
import Text.Megaparsec
import qualified Data.List.NonEmpty as NE
docOpenHandler :: Handlers DLogLspM
docOpenHandler = notificationHandler SMethod_TextDocumentDidOpen updateState
docChangeHandler :: Handlers DLogLspM
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 req = do
let uri = currentBufferUri req
LSPContext parseStateRef <- getConfig
bufferText <- currentBufferText req
x <- lift . atomically $ do
v <- readTVar parseStateRef
let parsedBuffer = runParser parseProgram (show uri) bufferText
writeTVar parseStateRef $
M.insert uri parsedBuffer v
pure parsedBuffer
case x of
Left (ParseErrorBundle errs position) ->
let
(a, b) = attachSourcePos errorOffset errs position
in
sendNotification
SMethod_TextDocumentPublishDiagnostics
PublishDiagnosticsParams
{ _uri = currentBufferUriUnNormalized req
, _version = Nothing
, _diagnostics =
NE.toList . flip fmap a $ \(err, pos) ->
Diagnostic
{ _range = Range (f pos) (f pos)
, _severity = Just DiagnosticSeverity_Error
, _code = Nothing
, _codeDescription = Nothing
, _source = Nothing
, _message = T.pack . parseErrorTextPretty $ err
, _tags = Nothing
, _relatedInformation = Nothing
, _data_ = Nothing
}
}
Right prog ->
sendNotification
SMethod_TextDocumentPublishDiagnostics
PublishDiagnosticsParams
{ _uri = currentBufferUriUnNormalized req
, _version = Nothing
, _diagnostics = []
}
f :: SourcePos -> Position
f (SourcePos _ line column) = Position (unPos' line) (unPos' column)
where
unPos' = fromIntegral . (\x -> x - 1) . unPos

View File

@ -1,28 +1,60 @@
module Datalog.LSP.Highlight where module Datalog.LSP.Highlight where
import Control.Lens ((^.), Lens') import Control.Concurrent.STM
import Control.Monad.Trans
import Data.Either (fromRight) import Data.Either (fromRight)
import Data.Maybe (fromJust) import Data.Map qualified as M
import Language.LSP.Protocol.Lens (params, textDocument, uri, HasTextDocument, HasParams, HasUri) import Data.Text qualified as T
import Datalog.LSP.Types (DLogLspM, LSPContext (..))
import Datalog.LSP.Utils (currentBufferUri)
import Datalog.Parser (SrcLoc (..))
import Datalog.Syntax (Atom' (Atom), Program' (..), RelId (RelId), Rule' (..), Term' (..))
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
tokenHandler :: Handlers (LspM ()) tokenHandler :: Handlers DLogLspM
tokenHandler = requestHandler SMethod_TextDocumentSemanticTokensFull $ \req responder -> do tokenHandler = requestHandler SMethod_TextDocumentSemanticTokensFull $ \req responder -> do
let LSPContext parseRef <- getConfig
openUri = toNormalizedUri $ req ^. docUri p <- lift . readTVarIO $ parseRef
token = case M.lookup (currentBufferUri req) p of
SemanticTokenAbsolute Nothing ->
{ _line = 0 responder (Left $ TResponseError (InL LSPErrorCodes_RequestFailed) "Doc not bundle" Nothing)
, _startChar = 0 Just (Left _) ->
, _length = 5 responder (Left $ TResponseError (InL LSPErrorCodes_RequestFailed) "Failed to parse" Nothing)
, _tokenType = SemanticTokenTypes_Keyword Just (Right prog) -> do
, _tokenModifiers = [] responder
} ( Right
tokens = fromRight (error "failed") $ makeSemanticTokens defaultSemanticTokensLegend [token] . InL
c <- fromJust <$> getVirtualFile openUri . fromRight (error "")
responder (Right $ InL tokens) . makeSemanticTokens defaultSemanticTokensLegend
$ highlightProg prog
)
docUri :: (HasParams s s1, HasTextDocument s1 s2, HasUri s2 a) => Lens' s a highlightProg :: Program' SrcLoc -> [SemanticTokenAbsolute]
docUri = params . textDocument . uri highlightProg (Program _ rs) = rs >>= highlightRule
highlightRule :: Rule' SrcLoc -> [SemanticTokenAbsolute]
highlightRule (Rule _ a as) = a : as >>= highlightAtom
highlightAtom :: Atom' SrcLoc -> [SemanticTokenAbsolute]
highlightAtom (Atom loc (RelId relId) ts) = highlightRel ++ (ts >>= highlightTerm)
where
highlightRel = pure $ tokenFromSrcLoc loc' SemanticTokenTypes_Interface
loc' = loc{endCol = startCol loc + length (T.unpack relId)}
highlightTerm :: Term' SrcLoc -> [SemanticTokenAbsolute]
highlightTerm =
pure <$> \case
Con loc _ -> tokenFromSrcLoc loc SemanticTokenTypes_Number
Var loc _ -> tokenFromSrcLoc loc SemanticTokenTypes_Keyword
tokenFromSrcLoc :: SrcLoc -> SemanticTokenTypes -> SemanticTokenAbsolute
tokenFromSrcLoc (SrcLoc sl sc _ ec) tokenType =
SemanticTokenAbsolute
{ _line = fromIntegral $ sl - 1
, _startChar = fromIntegral $ sc - 1
, _length = fromIntegral $ ec - sc
, _tokenType = tokenType
, _tokenModifiers = []
}

View File

@ -7,8 +7,9 @@ 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 Language.LSP.VFS (virtualFileText)
import Datalog.LSP.Types (DLogLspM)
hoverHandler :: Handlers (LspM ()) hoverHandler :: Handlers DLogLspM
hoverHandler = requestHandler SMethod_TextDocumentHover $ \req responder -> do hoverHandler = requestHandler SMethod_TextDocumentHover $ \req responder -> do
let let
openUri = toNormalizedUri $ req ^. docUri openUri = toNormalizedUri $ req ^. docUri

View File

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

View File

@ -0,0 +1,19 @@
module Datalog.LSP.Utils where
import Control.Lens
import Data.Maybe
import Data.Text qualified as T
import Language.LSP.Protocol.Lens
import Language.LSP.Protocol.Types
import Language.LSP.Server
import Language.LSP.VFS
currentBufferText :: (MonadLsp config f, HasParams s a1, HasTextDocument a1 a2, HasUri a2 Uri) => s -> f T.Text
currentBufferText req = virtualFileText . fromJust <$> getVirtualFile (currentBufferUri req)
currentBufferUri :: (HasParams s a1, HasTextDocument a1 a2, HasUri a2 Uri) => s -> NormalizedUri
currentBufferUri = toNormalizedUri . currentBufferUriUnNormalized
currentBufferUriUnNormalized :: (HasParams s a1, HasTextDocument a1 a2, HasUri a2 a3) => s -> a3
currentBufferUriUnNormalized req = req ^. (params . textDocument . uri)

View File

@ -1,7 +1,5 @@
{-# LANGUAGE OverloadedStrings #-}
import Datalog.LSP (serverDefinition) import Datalog.LSP (serverDefinition)
import Language.LSP.Server (runServer) import Language.LSP.Server (runServer)
main :: IO Int main :: IO Int
main = runServer serverDefinition main = serverDefinition >>= runServer

View File

@ -5,14 +5,15 @@ module Datalog.Parser (
parseTerm, parseTerm,
parseRule, parseRule,
parseProgram, parseProgram,
SrcLoc (..),
) )
where where
import Data.Text (Text) import Data.Text (Text)
import Data.Text qualified as T import Data.Text qualified as T
import Data.Void (Void) import Data.Void (Void)
import Datalog.Syntax hiding (Atom, Program, Rule, Term)
import Datalog.Syntax (Atom' (..), Program' (..), Rule' (..), Term' (..)) import Datalog.Syntax (Atom' (..), Program' (..), Rule' (..), Term' (..))
import Datalog.Syntax hiding (Atom, Program, Rule, Term)
import Text.Megaparsec import Text.Megaparsec
import Text.Megaparsec.Char import Text.Megaparsec.Char
import Text.Megaparsec.Char.Lexer qualified as L import Text.Megaparsec.Char.Lexer qualified as L
@ -20,6 +21,14 @@ import Text.Pretty.Simple
type Parser = Parsec Void Text type Parser = Parsec Void Text
data SrcLoc = SrcLoc
{ startLine :: Int
, startCol :: Int
, endLine :: Int
, endCol :: Int
}
deriving (Show)
type Atom = Atom' SrcLoc type Atom = Atom' SrcLoc
type Term = Term' SrcLoc type Term = Term' SrcLoc
type Rule = Rule' SrcLoc type Rule = Rule' SrcLoc
@ -64,7 +73,8 @@ parseQuery :: Parser [Atom]
parseQuery = parseAtom `sepBy` comma parseQuery = parseAtom `sepBy` comma
parseRule :: Parser Rule parseRule :: Parser Rule
parseRule = parseThingWithSub (\loc (a, as) -> Rule loc a as) $ parseRule =
parseThingWithSub (\loc (a, as) -> Rule loc a as) $
try rule1 <|> rule2 try rule1 <|> rule2
where where
rule1 = do rule1 = do
@ -82,23 +92,10 @@ parseProgram = parseThingWithSub Program (many parseRule)
parseThingWithSub :: (SrcLoc -> c -> f SrcLoc) -> Parser c -> Parser (f SrcLoc) parseThingWithSub :: (SrcLoc -> c -> f SrcLoc) -> Parser c -> Parser (f SrcLoc)
parseThingWithSub f parseSub = do parseThingWithSub f parseSub = do
s <- getSourcePos SourcePos _ sl sc <- getSourcePos
c <- parseSub c <- parseSub
e <- getSourcePos SourcePos _ el ec <- getSourcePos
pure $ f (SrcLoc s e) c pure $ f (SrcLoc (unPos sl) (unPos sc) (unPos el) (unPos ec)) c
annotateSrcLoc :: (Functor f) => Parser (f a) -> Parser (f SrcLoc)
annotateSrcLoc p = do
s <- getSourcePos
res <- p
f <- getSourcePos
pure (SrcLoc s f <$ res)
data SrcLoc = SrcLoc
{ start :: SourcePos
, end :: SourcePos
}
deriving (Show)
test = do test = do
let r = runParser parseProgram "???" prog let r = runParser parseProgram "???" prog

View File

@ -43,7 +43,7 @@
ghcid ghcid
]; ];
}; };
packages.default = haskell.packages."geolog-lsp:exe:geolog-lsp"; packages.default = haskell.packages."geolog-lsp:exe:datalog-lsp";
} }
); );
} }

View File

@ -28,6 +28,7 @@ library datalog-parser
containers, containers,
megaparsec, megaparsec,
pretty-simple, pretty-simple,
lens
exposed-modules: exposed-modules:
Datalog.Parser, Datalog.Parser,
Datalog.Syntax Datalog.Syntax
@ -42,10 +43,17 @@ executable datalog-lsp
lsp, lsp,
text, text,
containers, containers,
lens lens,
megaparsec,
datalog-parser,
mtl,
stm
other-modules: other-modules:
Datalog.LSP Datalog.LSP
Datalog.LSP.DocChange
Datalog.LSP.Types
Datalog.LSP.Highlight Datalog.LSP.Highlight
Datalog.LSP.Hover Datalog.LSP.Hover
Datalog.LSP.Utils
hs-source-dirs: datalog-lsp/src hs-source-dirs: datalog-lsp/src
default-language: GHC2024 default-language: GHC2024

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')

11
test.dl Normal file
View File

@ -0,0 +1,11 @@
odd(X,Y) :- r(X,Y).
odd(X,Y) :- even(X,Z), r(Z,Y).
even(X,Y) :- odd(X,Z), r(Z,Y).
r(0,1).
r(1,2).
r(2,3).
r(3,4).
r(4,5).
r(X,Y) :- r(Y,X).