diff --git a/lsp-export-protocol/README.md b/lsp-export-protocol/README.md new file mode 100644 index 0000000..d8b679f --- /dev/null +++ b/lsp-export-protocol/README.md @@ -0,0 +1,72 @@ +# lsp-export-protocol + +The `codedown/render` wire types, shared by `lsp-export-server` and its clients. Depends only on +`aeson`, `containers` and `text`, so a client needs no LSP dependency to speak the protocol. + +A *task* is one document rendered to one format. The client picks its `taskId`; every message +carries it. + +Field names are the record field with its prefix dropped, first letter lower-cased: `startTaskId` +is `taskId`. Enums drop the constructor prefix and lower-case: `StatusRendering` is `"rendering"`. + +## Messages + +The document arrives as ordinary `textDocument/didOpen` and `didChange`, so renders come from the +buffer rather than from disk. Commands are `workspace/executeCommand` requests; a rejected one +comes back as an `InvalidParams` error response. Renders report progress as custom notifications, +since they outlive the command that started them. + +| client → server | arguments | result | +| --- | --- | --- | +| `codedown.render.start` | `[StartParams]` | `StartResult` | +| `codedown.render.stop` | `[taskId]` | `null` | +| `codedown.render.rebuild` | `[taskId, RebuildParams]` | `null` | +| `codedown.render.setPaused` | `[taskId, bool]` | `null` | +| `codedown.render.resync` | `[taskId]` | `null` | + +| server → client | params | +| --- | --- | +| `codedown/render/status` | `StatusParams` | +| `codedown/render/output` | `OutputParams` | +| `codedown/render/log` | `LogParams` | + +`StartResult` only echoes the `taskId` the client chose, so a client that tracks its own ids can +treat every command as fire-and-forget and watch for the error response. + +`rebuild` says a file the last render read has changed; the server re-hashes the deps it knows and +renders only if one differs. `setPaused` suspends rendering without ending the task. `resync` makes +the next output a full one. + +The notifications are externally tagged: the variant is the method, and the params carry no +discriminator of their own, so a client dispatches on the method and then parses the params. + +## The cycle + +1. An edit marks the task dirty. The worker sleeps `debounceMs`; another edit restarts the wait. +2. Quick pass: `status` `rendering` (`phase: "quick"`), a `log` with `clear: true`, then the render + program runs with its output streaming as `log`. +3. `output` if the bytes changed, then `status` `ready` with `renderMs` and `timings`. +4. If the program has a settle pass and edits stay quiet for 1500ms, it runs the same way with + `phase: "settle"`, its logs flushed only if it failed or changed the output. +5. An edit mid-render cancels it, with no `status` for that pass. + +A failure sends `status` `errored` with the program's `exitCode`. Both passes of one edit report +`ready`, so a client tracking "is it rendering" should let `phase: "settle"` leave its state alone. + +## Output + +`OutputFile` has the base64 `bytes`, an `encoding`, and `decodedSize` — the size of the +reconstructed file, not of this message. + +The first output is `full`; later ones are `xdelta3` patches against `baseRenderId`, the `renderId` +of the last output sent. So a client keeps the bytes it last reconstructed and: + +- `baseRenderId` matches → apply, store, remember `renderId`. +- it doesn't, or the patch won't apply → send `resync`. +- bytes identical to the last render → nothing is sent at all. + +If xdelta3 fails the server falls back to `full`. + +`deps` lists files the render read from disk, minus the entry document and any buffer the client +sends over `didChange`; watch them and send `rebuild`. `documentVersion` is the entry buffer's LSP +version, absent when the server read from disk. diff --git a/lsp-export-protocol/lsp-export-protocol.cabal b/lsp-export-protocol/lsp-export-protocol.cabal new file mode 100644 index 0000000..7e11a50 --- /dev/null +++ b/lsp-export-protocol/lsp-export-protocol.cabal @@ -0,0 +1,39 @@ +cabal-version: 2.2 + +-- This file has been generated from package.yaml by hpack version 0.39.1. +-- +-- see: https://github.com/sol/hpack + +name: lsp-export-protocol +version: 0.1.0.0 +synopsis: The codedown/render wire protocol spoken by lsp-export-server +homepage: https://github.com/codedownio/lsp-export-server#readme +bug-reports: https://github.com/codedownio/lsp-export-server/issues +license: BSD-3-Clause +build-type: Simple +extra-source-files: + README.md + +source-repository head + type: git + location: https://github.com/codedownio/lsp-export-server + +library + exposed-modules: + Render.Protocol + other-modules: + Paths_lsp_export_protocol + autogen-modules: + Paths_lsp_export_protocol + hs-source-dirs: + src + default-extensions: + LambdaCase + OverloadedStrings + ghc-options: -Wunused-packages -Wall -fno-warn-name-shadowing + build-depends: + aeson + , base >=4.7 && <5 + , containers + , text + default-language: Haskell2010 diff --git a/lsp-export-protocol/package.yaml b/lsp-export-protocol/package.yaml new file mode 100644 index 0000000..e9b5317 --- /dev/null +++ b/lsp-export-protocol/package.yaml @@ -0,0 +1,26 @@ +name: lsp-export-protocol +version: 0.1.0.0 +synopsis: The codedown/render wire protocol spoken by lsp-export-server +license: BSD-3-Clause +github: codedownio/lsp-export-server + +extra-source-files: +- README.md + +dependencies: +- base >= 4.7 && < 5 +- aeson +- containers +- text + +default-extensions: +- LambdaCase +- OverloadedStrings + +ghc-options: +- -Wunused-packages +- -Wall +- -fno-warn-name-shadowing + +library: + source-dirs: src diff --git a/src/Render/Protocol.hs b/lsp-export-protocol/src/Render/Protocol.hs similarity index 99% rename from src/Render/Protocol.hs rename to lsp-export-protocol/src/Render/Protocol.hs index 1c0394a..bba5f09 100644 --- a/src/Render/Protocol.hs +++ b/lsp-export-protocol/src/Render/Protocol.hs @@ -161,3 +161,4 @@ data LogParams = LogParams { } deriving (Show, Eq, Generic) instance ToJSON LogParams where toJSON = genericToJSON (fieldOptions 3) instance FromJSON LogParams where parseJSON = genericParseJSON (fieldOptions 3) + diff --git a/lsp-export-server.cabal b/lsp-export-server.cabal index 47110ba..6c6b582 100644 --- a/lsp-export-server.cabal +++ b/lsp-export-server.cabal @@ -39,7 +39,6 @@ library exposed-modules: Render.Command Render.Process - Render.Protocol Render.Server Render.Xdelta other-modules: @@ -81,6 +80,7 @@ library , filepath , lens , lsp + , lsp-export-protocol , lsp-types , process , stm @@ -128,6 +128,7 @@ library test-lib , directory , filepath , lens + , lsp-export-protocol , lsp-export-server , lsp-test , lsp-types @@ -169,6 +170,7 @@ executable lsp-export-server , containers , directory , filepath + , lsp-export-protocol , lsp-export-server , optparse-applicative , temporary @@ -217,6 +219,7 @@ test-suite lsp-export-server-test , bytestring , containers , filepath + , lsp-export-protocol , lsp-export-server , lsp-test , sandwich diff --git a/package.yaml b/package.yaml index cfeb3b0..8ac5e9c 100644 --- a/package.yaml +++ b/package.yaml @@ -51,6 +51,7 @@ library: - filepath - lens - lsp + - lsp-export-protocol - lsp-types - process - stm @@ -74,6 +75,7 @@ executables: - directory - filepath - optparse-applicative + - lsp-export-protocol - lsp-export-server - temporary - time @@ -88,6 +90,7 @@ internal-libraries: - directory - filepath - lens + - lsp-export-protocol - lsp-export-server - lsp-test - lsp-types @@ -112,6 +115,7 @@ tests: dependencies: - aeson - filepath + - lsp-export-protocol - lsp-export-server - lsp-test - sandwich diff --git a/stack.yaml b/stack.yaml index c13d3c5..2dccc1a 100644 --- a/stack.yaml +++ b/stack.yaml @@ -2,6 +2,7 @@ resolver: nightly-2026-04-19 packages: - . +- ./lsp-export-protocol nix: enable: false