From 6a902530ccba4d23c45572e71d1beb01b3129b99 Mon Sep 17 00:00:00 2001 From: thomasjm Date: Sun, 11 Oct 2026 19:40:08 -0700 Subject: [PATCH] Take ClientNot from the core library, with the per-language parts in a NotebookConfig --- app/Main.hs | 5 +- package.yaml | 7 +- rust-notebook-language-server.cabal | 11 +-- src/Transform/ClientNot.hs | 146 ---------------------------- src/Transform/Config.hs | 52 ++++++++++ stack.yaml | 2 +- stack.yaml.lock | 13 ++- 7 files changed, 72 insertions(+), 164 deletions(-) delete mode 100644 src/Transform/ClientNot.hs create mode 100644 src/Transform/Config.hs diff --git a/app/Main.hs b/app/Main.hs index 44353c0..05a0a24 100644 --- a/app/Main.hs +++ b/app/Main.hs @@ -34,7 +34,7 @@ import UnliftIO.Exception import UnliftIO.Process import UnliftIO.Temporary (withSystemTempDirectory) -import Transform.ClientNot +import Language.LSP.NotebookLanguageServer.Transform.ClientNot import Language.LSP.NotebookLanguageServer.Transform.ClientReq import Language.LSP.NotebookLanguageServer.Transform.ClientRsp import Language.LSP.NotebookLanguageServer.Transform.ServerNot @@ -47,6 +47,7 @@ import Language.LSP.NotebookLanguageServer.Process import Language.LSP.NotebookLanguageServer.RequestMap import Streams import Language.LSP.Notebook +import Transform.Config data Options = Options { @@ -178,7 +179,7 @@ handleStdin wrappedIn clientReqMap serverReqMap = do Nothing -> return m transformClientReq meth msg >>= liftIO . writeToHandle wrappedIn . A.encode Right (ClientToServerNot meth msg) -> - transformClientNot sendExtraNotification meth msg >>= (liftIO . writeToHandle wrappedIn . A.encode) + transformClientNot rustNotebookConfig sendExtraNotification meth msg >>= (liftIO . writeToHandle wrappedIn . A.encode) where sendExtraNotification :: SendExtraNotificationFn m sendExtraNotification msg = do diff --git a/package.yaml b/package.yaml index 2c10f27..7d03177 100644 --- a/package.yaml +++ b/package.yaml @@ -38,17 +38,12 @@ ghc-options: library: source-dirs: src dependencies: - - aeson - - auto-update - containers - filepath - - lens - mtl - notebook-language-server-core + - pcre-light - text-rope - - time - - unliftio-core - - uuid executables: rust-notebook-language-server: diff --git a/rust-notebook-language-server.cabal b/rust-notebook-language-server.cabal index 2978645..b3d1985 100644 --- a/rust-notebook-language-server.cabal +++ b/rust-notebook-language-server.cabal @@ -16,7 +16,7 @@ library Language.LSP.Notebook Language.LSP.Notebook.HeadTailTransformer Language.LSP.Notebook.StripDirective - Transform.ClientNot + Transform.Config other-modules: Paths_rust_notebook_language_server hs-source-dirs: @@ -38,23 +38,18 @@ library TypeApplications ghc-options: -Wunused-packages -Wall -fno-warn-name-shadowing build-depends: - aeson - , auto-update - , base >=4.7 && <5 + base >=4.7 && <5 , containers , filepath - , lens , lsp-types >=2.0.0.1 , monad-logger , mtl , notebook-language-server-core + , pcre-light , string-interpolate , text , text-rope - , time , unliftio - , unliftio-core - , uuid default-language: Haskell2010 executable rust-notebook-language-server diff --git a/src/Transform/ClientNot.hs b/src/Transform/ClientNot.hs deleted file mode 100644 index c5bcb00..0000000 --- a/src/Transform/ClientNot.hs +++ /dev/null @@ -1,146 +0,0 @@ -{-# LANGUAGE DataKinds #-} -{-# LANGUAGE GADTs #-} -{-# LANGUAGE PolyKinds #-} -{-# LANGUAGE RankNTypes #-} - -module Transform.ClientNot where - -import Control.Debounce -import Control.Lens hiding ((:>), (<.>), List) -import Control.Monad -import Control.Monad.IO.Class -import Control.Monad.IO.Unlift -import Control.Monad.Logger -import Control.Monad.Reader -import Data.Aeson as A -import Data.Map as M -import Data.String.Interpolate -import Data.Text as T -import qualified Data.Text.IO as T -import qualified Data.Text.Rope as Rope -import Data.Time -import qualified Data.UUID.V4 as UUID -import Language.LSP.Notebook -import Language.LSP.Protocol.Lens as Lens -import Language.LSP.Protocol.Message -import Language.LSP.Protocol.Types -import Language.LSP.Transformer -import System.FilePath -import Language.LSP.NotebookLanguageServer.Transform.ClientNot.DidSave -import Language.LSP.NotebookLanguageServer.Transform.Common -import Language.LSP.NotebookLanguageServer.Transform.ServerRsp.Hover (mkDocRegex) -import Language.LSP.NotebookLanguageServer.Transform.Util -import UnliftIO.Concurrent -import UnliftIO.Directory - - -type ClientNotMethod m = SMethod (m :: Method 'ClientToServer 'Notification) - -transformClientNot :: ( - TransformerMonad RustNotebookTransformer n, HasJSON (TNotificationMessage m) - ) => SendExtraNotificationFn n - -> ClientNotMethod m - -> TNotificationMessage m - -> n (TNotificationMessage m) -transformClientNot sendExtraNotification meth msg = do - start <- liftIO getCurrentTime - p' <- transformClientNot' sendExtraNotification meth (msg ^. params) - stop <- liftIO getCurrentTime - let msg' = set params p' msg - when (msg' /= msg) $ logDebugN [i|Transforming client not #{meth} in #{diffUTCTime stop start}: (#{A.encode msg} --> #{A.encode msg'})|] - return msg' - -transformClientNot' :: ( - TransformerMonad RustNotebookTransformer n - ) => (forall (o :: Method 'ClientToServer 'Notification). ToJSON (TNotificationMessage o) => TNotificationMessage o -> n ()) - -> ClientNotMethod m - -> MessageParams m - -> n (MessageParams m) - -transformClientNot' sendExtraNotification SMethod_TextDocumentDidOpen params = whenAnything params $ \u -> do - let t = params ^. (textDocument . text) - let ls = Rope.fromText t - let txParams = if isNotebook u then transformerParams else idTransformerParams - (ls', transformer' :: RustNotebookTransformer, eitherErr) <- project txParams ls - case eitherErr of - Left err -> logWarnN [i|(#{u}) Project failed: #{err}|] - Right () -> return () - TransformerState {..} <- ask - (newPath, referenceRegex) <- do - identifier <- makeUUID' 15 - let path = transformerShadowDir "src" identifier <.> "rs" - - newPath <- do - createDirectoryIfMissing True (takeDirectory path) - liftIO $ T.writeFile path (Rope.toText ls') - return path - - pure (newPath, mkDocRegex (T.pack (identifier <.> "rs"))) - - let newUri = filePathToUri newPath - - uuid <- liftIO UUID.nextRandom - - debouncedDidChange <- withRunInIO $ \runInIO -> mkDebounce ( - defaultDebounceSettings { - debounceAction = do - withMVar transformerDocuments $ \m -> - case M.lookup (getUri u) m of - (Just ds@(DocumentState {..})) - | documentUuid == uuid -> runInIO $ doDidSave ds sendExtraNotification - | otherwise -> return () - _ -> return () - , debounceFreq = transformerDidSaveDebouncePeriodMs * 1000 - , debounceEdge = trailingEdge - }) - - modifyMVar_ transformerDocuments $ \x -> return $! M.insert (getUri u) ( - DocumentState { - transformer = transformer' - , curLines = ls - , curLines' = ls' - , origUri = u - , newUri = newUri - , newPath = newPath - , referenceRegex = referenceRegex - , documentUuid = uuid - , debouncedDidChange = debouncedDidChange - }) x - - updateLibRs - - return $ params - & set (textDocument . text) (Rope.toText ls') - & set (textDocument . uri) newUri - -transformClientNot' _ SMethod_TextDocumentDidChange params = whenAnything params $ modifyTransformer params $ \ds@(DocumentState {transformer=tx, curLines=before, curLines'=before', origUri, newUri, debouncedDidChange}) -> do - let txParams = if isNotebook origUri then transformerParams else idTransformerParams - let changeEvents = params ^. contentChanges - (changeEvents', tx') <- handleDiffMulti txParams before changeEvents tx - let after = applyChanges changeEvents before - let after' = applyChanges changeEvents' before' - - liftIO debouncedDidChange - - return ( - ds { transformer = tx' - , curLines = after - , curLines' = after' - } - , params & set contentChanges changeEvents' - & set (textDocument . uri) newUri - ) - -transformClientNot' _ SMethod_TextDocumentDidClose params = whenAnything params $ \u -> do - TransformerState {..} <- ask - maybeDocumentState <- modifyMVar transformerDocuments (return . flipTuple . M.updateLookupWithKey (\_ _ -> Nothing) (getUri u)) - newUri <- case maybeDocumentState of - Just (DocumentState {..}) -> do - removePathForcibly newPath - pure newUri - Nothing -> addExtensionToUri ".rs" u -- The client shouldn't be closing a non-open doc - updateLibRs - return $ params - & set (textDocument . uri) newUri - -transformClientNot' _ _ params = return params diff --git a/src/Transform/Config.hs b/src/Transform/Config.hs new file mode 100644 index 0000000..c81a6d7 --- /dev/null +++ b/src/Transform/Config.hs @@ -0,0 +1,52 @@ +{-# LANGUAGE RankNTypes #-} + +module Transform.Config (rustNotebookConfig) where + +import Control.Monad.IO.Class +import Control.Monad.Logger +import Control.Monad.Reader +import qualified Data.Map as M +import Data.String.Interpolate +import qualified Data.Text as T +import qualified Data.Text.IO as T +import qualified Data.Text.Rope as Rope +import Language.LSP.Notebook +import Language.LSP.NotebookLanguageServer.Config +import Language.LSP.NotebookLanguageServer.Transform.ServerRsp.Hover (mkDocRegex) +import Language.LSP.NotebookLanguageServer.Transform.Util +import Language.LSP.Protocol.Types +import Language.LSP.Transformer (Doc) +import Text.Regex.PCRE.Light (Regex) +import System.FilePath +import UnliftIO.Directory +import UnliftIO.MVar + + +rustNotebookConfig :: NotebookConfig RustNotebookTransformer +rustNotebookConfig = NotebookConfig { + notebookConfigTransformerParams = transformerParams + , notebookConfigIdTransformerParams = idTransformerParams + , notebookConfigMakeShadowDocument = makeShadowDocument + , notebookConfigUnopenedUri = addExtensionToUri ".rs" + , notebookConfigOnDocumentsChanged = updateLibRs + , notebookConfigLanguageName = "rust" + } + +makeShadowDocument :: TransformerMonad RustNotebookTransformer n => Uri -> Doc -> n (Uri, FilePath, Regex) +makeShadowDocument _origUri projected = do + TransformerState {transformerShadowDir} <- ask + identifier <- makeUUID' 15 + let path = transformerShadowDir "src" identifier <.> "rs" + createDirectoryIfMissing True (takeDirectory path) + liftIO $ T.writeFile path (Rope.toText projected) + pure (filePathToUri path, path, mkDocRegex (T.pack (identifier <.> "rs"))) + +updateLibRs :: TransformerMonad RustNotebookTransformer m => m () +updateLibRs = do + TransformerState {..} <- ask + docs <- readMVar transformerDocuments + let identifiers = [dropExtension (takeFileName newPath) | DocumentState {..} <- fmap snd (M.toList docs)] + let libRsContent = T.intercalate "\n" [T.pack ("mod " <> x <> ";") | x <- identifiers] + logInfoN [i|libRsContent: #{libRsContent}|] + liftIO $ createDirectoryIfMissing True (transformerShadowDir "src") + liftIO $ T.writeFile (transformerShadowDir "src" "lib.rs") libRsContent diff --git a/stack.yaml b/stack.yaml index 91662d3..6957565 100644 --- a/stack.yaml +++ b/stack.yaml @@ -13,7 +13,7 @@ system-ghc: true extra-deps: - git: https://github.com/codedownio/notebook-language-server-core.git - commit: 6bcfcb6f9deabc8a0a37636a9dcb6ff835495dce + commit: db16c415168a1f150d43d1bf1f388d6e6272ec72 # - language-rust-0.2.0.27 diff --git a/stack.yaml.lock b/stack.yaml.lock index dbe8057..c6c8636 100644 --- a/stack.yaml.lock +++ b/stack.yaml.lock @@ -3,7 +3,18 @@ # For more information, please see the documentation at: # https://docs.haskellstack.org/en/stable/topics/lock_files -packages: [] +packages: +- completed: + commit: db16c415168a1f150d43d1bf1f388d6e6272ec72 + git: https://github.com/codedownio/notebook-language-server-core.git + name: notebook-language-server-core + pantry-tree: + sha256: 8008b9ad9e853f1d819cb66c4bd01a5d0fc3bab7acd0f4549bfed09f1445f910 + size: 1919 + version: 0.1.0.0 + original: + commit: db16c415168a1f150d43d1bf1f388d6e6272ec72 + git: https://github.com/codedownio/notebook-language-server-core.git snapshots: - completed: sha256: e532f0987cce63d549ae1ac942f7e52c91712c95c36447756a60e9279a68626d