Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# Changelog

## 0.1.1.0

- `--locate EXE` names an optional program that maps source positions to places in the output and
back. Each output that answers an edit carries where the edit landed (`position`), and
`codedown.render.locate` / `codedown.render.sourceAt` answer the same queries on request.

## 0.1.0.0

- First release. A language server that renders a document from the buffers the client sends and
Expand Down
23 changes: 21 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ server as `didChange` and a new render comes back without a trip through the dis
The server knows nothing about document formats. The packaging names the program that renders:

```
lsp-export-server serve --render latex-render --settle latex-render -o engine=pdflatex # the LSP, on stdio
lsp-export-server serve --render latex-render --settle latex-render --locate latex-locate -o engine=pdflatex # the LSP, on stdio
lsp-export-server render --render latex-render input.tex output.pdf # once, from disk
```

Expand Down Expand Up @@ -40,10 +40,29 @@ that exit code. Paths listed in `RENDER_DEPS` under `RENDER_SRC` or `RENDER_OVER
back to the documents they stand for; the client is told to watch the ones it does not hold open,
and a `rebuild` renders again only when one of them changed.

## The locate program

Optional. It maps between source positions and places in the last output, as SyncTeX does for
LaTeX. It runs in the document's directory with `RENDER_DOCUMENT`, `RENDER_JOB`, `RENDER_FORMAT`,
`RENDER_OUTPUT`, `RENDER_WORK` and `RENDER_OUT` as for the render program, and:

| Variable | |
|---|---|
| `RENDER_QUERY` | `forward` or `inverse` |
| `RENDER_SOURCE`, `RENDER_LINE`, `RENDER_COLUMN` | forward: the file as the render program saw it (the copy under `RENDER_WORK` for a buffer), and a one-based line and column |
| `RENDER_PAGE`, `RENDER_X`, `RENDER_Y` | inverse: a one-based page, and a point on it in PDF points from its top-left corner |
| `RENDER_RESULT` | where to write the answer, one line: `page left top width height` for forward, in the same units; `line column file` for inverse, one-based. Nothing when there is no match |

After each render that follows an edit, the server locates the edit and sends the box with the
output as `position`. The `locate` and `sourceAt` commands answer the same queries for the client,
from the last render. Files under `RENDER_WORK` in an inverse answer are mapped back to the
documents they stand for, as with `RENDER_DEPS`.

## Protocol

Commands (`workspace/executeCommand`): `codedown.render.start`, `codedown.render.stop`,
`codedown.render.rebuild`, `codedown.render.setPaused`, `codedown.render.resync`. Notifications:
`codedown.render.rebuild`, `codedown.render.setPaused`, `codedown.render.resync`,
`codedown.render.locate`, `codedown.render.sourceAt`. Notifications:
`codedown/render/status`, `codedown/render/output`, `codedown/render/log`. See `src/Render/Protocol.hs`.
Statuses carry the pass (`phase`) and what triggered it (`reason`), and `──` marker lines in the
log say when each pass started, why, how long it took and what was sent.
Expand Down
3 changes: 2 additions & 1 deletion app/Main.hs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@ commonParser :: Parser Common
commonParser = Common
<$> (Programs
<$> strOption (long "render" <> metavar "EXE" <> help "The program that renders: run in the document's directory with the RENDER_* variables; writes RENDER_OUTPUT")
<*> optional (strOption (long "settle" <> metavar "EXE" <> help "The program for the settle pass once edits stay quiet; without one there is no settle pass")))
<*> optional (strOption (long "settle" <> metavar "EXE" <> help "The program for the settle pass once edits stay quiet; without one there is no settle pass"))
<*> optional (strOption (long "locate" <> metavar "EXE" <> help "The program that maps source positions to places in the output and back; without one, outputs carry no position")))
<*> many (option (eitherReader keyValue) (long "option" <> short 'o' <> metavar "KEY=VALUE" <> help "An option passed on as RENDER_OPT_KEY"))
where
keyValue s = case break (== '=') s of
Expand Down
2 changes: 1 addition & 1 deletion lsp-export-protocol/lsp-export-protocol.cabal
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ cabal-version: 2.2
-- see: https://github.com/sol/hpack

name: lsp-export-protocol
version: 0.1.0.0
version: 0.1.1.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
Expand Down
2 changes: 1 addition & 1 deletion lsp-export-protocol/package.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
name: lsp-export-protocol
version: 0.1.0.0
version: 0.1.1.0
synopsis: The codedown/render wire protocol spoken by lsp-export-server
license: BSD-3-Clause
github: codedownio/lsp-export-server
Expand Down
59 changes: 51 additions & 8 deletions lsp-export-protocol/src/Render/Protocol.hs
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
{-# LANGUAGE DeriveGeneric #-}

-- The wire protocol between a render client (the codedown runner) and this server. Commands go
-- client -> server as workspace/executeCommand; results come back as custom notifications.
module Render.Protocol (
commandStart
, commandStop
, commandRebuild
, commandSetPaused
, commandResync
, commandLocate
, commandSourceAt
, allCommands

, methodStatus
Expand All @@ -17,6 +17,10 @@ module Render.Protocol (
, StartParams(..)
, StartResult(..)
, RebuildParams(..)
, LocateParams(..)
, SourceAtParams(..)
, SourceLocation(..)
, PageBox(..)

, Status(..)
, Phase(..)
Expand All @@ -37,29 +41,29 @@ import Data.Text (Text)
import GHC.Generics (Generic)


commandStart, commandStop, commandRebuild, commandSetPaused, commandResync :: Text
commandStart, commandStop, commandRebuild, commandSetPaused, commandResync, commandLocate, commandSourceAt :: Text
commandStart = "codedown.render.start"
commandStop = "codedown.render.stop"
commandRebuild = "codedown.render.rebuild"
commandSetPaused = "codedown.render.setPaused"
commandResync = "codedown.render.resync"
commandLocate = "codedown.render.locate"
commandSourceAt = "codedown.render.sourceAt"

allCommands :: [Text]
allCommands = [commandStart, commandStop, commandRebuild, commandSetPaused, commandResync]
allCommands = [commandStart, commandStop, commandRebuild, commandSetPaused, commandResync, commandLocate, commandSourceAt]

methodStatus, methodOutput, methodLog :: Text
methodStatus = "codedown/render/status"
methodOutput = "codedown/render/output"
methodLog = "codedown/render/log"

-- Field names are the record field with its prefix dropped, lower-cased: startTaskId -> taskId
fieldOptions :: Int -> Options
fieldOptions n = defaultOptions {
fieldLabelModifier = lowerFirst . drop n
, omitNothingFields = True
}

-- Constructor names with their prefix dropped, lower-cased: StatusReady -> ready
tagOptions :: Int -> Options
tagOptions n = defaultOptions { constructorTagModifier = map toLower . drop n }

Expand Down Expand Up @@ -89,6 +93,46 @@ newtype RebuildParams = RebuildParams {
instance ToJSON RebuildParams where toJSON = genericToJSON (fieldOptions 7)
instance FromJSON RebuildParams where parseJSON = genericParseJSON (fieldOptions 7)

-- | Zero-based
data LocateParams = LocateParams {
locateTaskId :: Text
, locateUri :: Text
, locateLine :: Int
, locateCharacter :: Int
} deriving (Show, Eq, Generic)
instance ToJSON LocateParams where toJSON = genericToJSON (fieldOptions 6)
instance FromJSON LocateParams where parseJSON = genericParseJSON (fieldOptions 6)

-- | One-based page; PDF points from its top-left
data SourceAtParams = SourceAtParams {
sourceAtTaskId :: Text
, sourceAtPage :: Int
, sourceAtX :: Double
, sourceAtY :: Double
} deriving (Show, Eq, Generic)
instance ToJSON SourceAtParams where toJSON = genericToJSON (fieldOptions 8)
instance FromJSON SourceAtParams where parseJSON = genericParseJSON (fieldOptions 8)

-- | Zero-based
data SourceLocation = SourceLocation {
sourceUri :: Text
, sourceLine :: Int
, sourceCharacter :: Int
} deriving (Show, Eq, Generic)
instance ToJSON SourceLocation where toJSON = genericToJSON (fieldOptions 6)
instance FromJSON SourceLocation where parseJSON = genericParseJSON (fieldOptions 6)

-- | One-based page; PDF points from its top-left
data PageBox = PageBox {
boxPage :: Int
, boxLeft :: Double
, boxTop :: Double
, boxWidth :: Double
, boxHeight :: Double
} deriving (Show, Eq, Generic)
instance ToJSON PageBox where toJSON = genericToJSON (fieldOptions 3)
instance FromJSON PageBox where parseJSON = genericParseJSON (fieldOptions 3)

data Timing = Timing {
timingLabel :: Text
, timingMs :: Double
Expand All @@ -101,7 +145,6 @@ data Status = StatusRendering | StatusReady | StatusErrored
instance ToJSON Status where toJSON = genericToJSON (tagOptions 6)
instance FromJSON Status where parseJSON = genericParseJSON (tagOptions 6)

-- | The quick pass runs right after the debounce; the settle pass follows once edits stay quiet
data Phase = PhaseQuick | PhaseSettle
deriving (Show, Eq, Generic)
instance ToJSON Phase where toJSON = genericToJSON (tagOptions 5)
Expand All @@ -111,7 +154,6 @@ data StatusParams = StatusParams {
statusTaskId :: Text
, statusStatus :: Status
, statusPhase :: Phase
-- | What made the render run, e.g. the edit it answers
, statusReason :: Maybe Text
, statusExitCode :: Maybe Int
, statusRenderMs :: Maybe Double
Expand Down Expand Up @@ -144,6 +186,7 @@ data OutputParams = OutputParams {
, outputDeps :: [FilePath]
, outputRenderMs :: Double
, outputTimings :: [Timing]
, outputPosition :: Maybe PageBox
} deriving (Show, Eq, Generic)
instance ToJSON OutputParams where toJSON = genericToJSON (fieldOptions 6)
instance FromJSON OutputParams where parseJSON = genericParseJSON (fieldOptions 6)
Expand Down
7 changes: 6 additions & 1 deletion lsp-export-server.cabal
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ cabal-version: 2.2
-- see: https://github.com/sol/hpack

name: lsp-export-server
version: 0.1.0.0
version: 0.1.1.0
synopsis: A language server that renders documents from their in-memory buffers
homepage: https://github.com/codedownio/lsp-export-server#readme
bug-reports: https://github.com/codedownio/lsp-export-server/issues
Expand Down Expand Up @@ -40,6 +40,10 @@ library
Render.Command
Render.Process
Render.Server
Render.Server.Commands
Render.Server.Env
Render.Server.Publish
Render.Server.Worker
Render.Xdelta
other-modules:
Paths_lsp_export_server
Expand Down Expand Up @@ -185,6 +189,7 @@ test-suite lsp-export-server-test
other-modules:
Spec
Spec.Deps
Spec.Locate
Spec.Protocol
Spec.Server
Spec.Xdelta
Expand Down
2 changes: 1 addition & 1 deletion package.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
name: lsp-export-server
version: 0.1.0.0
version: 0.1.1.0
synopsis: A language server that renders documents from their in-memory buffers
license: BSD-3-Clause
github: codedownio/lsp-export-server
Expand Down
Loading