Use STM to share state between handlers

This commit is contained in:
Patrick Aldis 2026-03-05 15:46:01 +00:00
parent dd761e8321
commit 35b2fa4282
9 changed files with 139 additions and 85 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,31 @@
{-# LANGUAGE BlockArguments #-}
module Datalog.LSP.DocChange (docChangeHandler, docOpenHandler) where
import Control.Concurrent.STM
import Control.Monad.Trans
import Data.Map qualified as M
import Datalog.LSP.Types
import Datalog.LSP.Utils (currentBufferText, currentBufferUri)
import Datalog.Parser (parseProgram)
import Language.LSP.Protocol.Lens (HasParams, HasTextDocument, HasUri)
import Language.LSP.Protocol.Message (SMethod (SMethod_TextDocumentDidChange, SMethod_TextDocumentDidOpen))
import Language.LSP.Protocol.Types (Uri)
import Language.LSP.Server (Handlers, MonadLsp, getConfig, notificationHandler)
import Text.Megaparsec
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
text <- currentBufferText req
lift . atomically $ do
v <- readTVar parseStateRef
writeTVar parseStateRef $
M.insert uri (runParser parseProgram (show uri) text) v

View File

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

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,16 @@
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 req = toNormalizedUri $ 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

@ -12,8 +12,8 @@ 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
@ -21,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
@ -65,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
@ -83,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

@ -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
@ -44,10 +45,15 @@ executable datalog-lsp
containers, containers,
lens, lens,
megaparsec, megaparsec,
datalog-parser 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