Merge pull request #123 from levineuwirth/json-yaml-grammar
feat(json-yaml): JSON + YAML grammars and language servers
This commit is contained in:
commit
bb17ec955e
|
|
@ -2558,6 +2558,7 @@ dependencies = [
|
|||
"tree-sitter-cuda",
|
||||
"tree-sitter-go",
|
||||
"tree-sitter-javascript",
|
||||
"tree-sitter-json",
|
||||
"tree-sitter-lua",
|
||||
"tree-sitter-make",
|
||||
"tree-sitter-md",
|
||||
|
|
@ -2565,6 +2566,7 @@ dependencies = [
|
|||
"tree-sitter-rust",
|
||||
"tree-sitter-toml-ng",
|
||||
"tree-sitter-typescript",
|
||||
"tree-sitter-yaml",
|
||||
"tree-sitter-zig",
|
||||
"unicode-width",
|
||||
]
|
||||
|
|
@ -3807,6 +3809,16 @@ dependencies = [
|
|||
"tree-sitter-language",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tree-sitter-json"
|
||||
version = "0.24.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4d727acca406c0020cffc6cf35516764f36c8e3dc4408e5ebe2cb35a947ec471"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"tree-sitter-language",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tree-sitter-language"
|
||||
version = "0.1.7"
|
||||
|
|
@ -3883,6 +3895,16 @@ dependencies = [
|
|||
"tree-sitter-language",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tree-sitter-yaml"
|
||||
version = "0.7.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "53c223db85f05e34794f065454843b0668ebc15d240ada63e2b5939f43ce7c97"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"tree-sitter-language",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tree-sitter-zig"
|
||||
version = "1.1.2"
|
||||
|
|
|
|||
|
|
@ -196,6 +196,13 @@ tree-sitter-javascript = "0.25"
|
|||
tree-sitter-typescript = "0.23"
|
||||
tree-sitter-toml-ng = "0.7"
|
||||
tree-sitter-zig = "1.1"
|
||||
# JSON + YAML — config formats + the honest gate on the Jupyter path
|
||||
# (JSON). Both are ABI-current (`LANGUAGE: LanguageFn` via
|
||||
# `tree-sitter-language`), NOT a `tree-sitter ^0.20` fork. Registering
|
||||
# yaml also lights up markdown `---` frontmatter through the #122
|
||||
# injection engine (the block injection query already sets yaml for it).
|
||||
tree-sitter-json = "0.24"
|
||||
tree-sitter-yaml = "0.7"
|
||||
# T M9.7: markdown grammar so prompt result buffers with
|
||||
# `_meta.format = "markdown"` get structured highlighting through the
|
||||
# same M4 path as rust/lua — no special-case painter in Lua.
|
||||
|
|
|
|||
|
|
@ -200,6 +200,62 @@ pmacs.lsp.config.zig = pmacs.lsp.config.zig or {
|
|||
args = {},
|
||||
}
|
||||
|
||||
-- JSON via the VS Code JSON server, binary `vscode-json-language-server`.
|
||||
-- It is a PUSH-model server: it reads config from
|
||||
-- `workspace/didChangeConfiguration` (the daemon now sends one after
|
||||
-- `initialized`) and does NOT issue `workspace/configuration` pulls, so
|
||||
-- without that push these settings would be inert. `json.validate.enable`
|
||||
-- is set explicitly true — the server treats a MISSING value as false, so
|
||||
-- an empty `json = {}` would silently disable validation. Schema
|
||||
-- retrieval performs NETWORK ACCESS for remote `$schema` URLs (left
|
||||
-- enabled; `handledSchemaProtocols = {"file"}` would disable it but break
|
||||
-- remote schemas without a `vscode/content` impl). Note: the server does
|
||||
-- NOT auto-associate `package.json`/`tsconfig.json` — it starts with empty
|
||||
-- contributions; explicit `$schema` refs or configured `json.schemas` /
|
||||
-- a `json/schemaAssociations` push (not implemented) are required.
|
||||
-- Provider: pin `@t1ckbase/vscode-langservers-extracted@2.0.2`
|
||||
-- (`npm install -g @t1ckbase/vscode-langservers-extracted@2.0.2`).
|
||||
-- Its published payload bundles the JSON server from VS Code 1.129.0,
|
||||
-- preserves this command name, and was live-smoked through initialize →
|
||||
-- config push → invalid-JSON diagnostic → shutdown. The older unscoped
|
||||
-- package is stale and the current `@zed-industries` payload has a broken
|
||||
-- JSON launcher; neither is the recommended provider.
|
||||
pmacs.lsp.config.json = pmacs.lsp.config.json or {
|
||||
command = "vscode-json-language-server",
|
||||
args = { "--stdio" },
|
||||
settings = {
|
||||
json = { validate = { enable = true } },
|
||||
http = {},
|
||||
},
|
||||
}
|
||||
|
||||
-- YAML via Red Hat `yaml-language-server`. On
|
||||
-- `workspace/didChangeConfiguration` (now pushed after `initialized`) it
|
||||
-- reads the `yaml`, `http`, `[yaml]`, `editor`, and `files` sections — all
|
||||
-- ship present-not-null (empty ⇒ server defaults). SchemaStore / remote
|
||||
-- schema retrieval performs NETWORK ACCESS by default. The standalone
|
||||
-- server does not upload telemetry itself — it emits `telemetry/event`
|
||||
-- notifications to its client, and pmacs has no telemetry uploader, so a
|
||||
-- `redhat.telemetry` setting would be inert and is not shipped. Sections
|
||||
-- live-observed with Red Hat `yaml-language-server@1.24.0`: its initial
|
||||
-- pull requests exactly those five sections, and opening a YAML document
|
||||
-- requests a second scoped `[yaml]` section. The standalone smoke reached
|
||||
-- a real syntax diagnostic and clean shutdown. The PATH-gated pmacs
|
||||
-- acceptance also proves auto-attach, initialization, config pulls, a
|
||||
-- syntax diagnostic, and continued server liveness with both catalogs
|
||||
-- disabled for network-free determinism.
|
||||
pmacs.lsp.config.yaml = pmacs.lsp.config.yaml or {
|
||||
command = "yaml-language-server",
|
||||
args = { "--stdio" },
|
||||
settings = {
|
||||
yaml = {},
|
||||
http = {},
|
||||
["[yaml]"] = {},
|
||||
editor = {},
|
||||
files = {},
|
||||
},
|
||||
}
|
||||
|
||||
-- LSP-side extension → language map, deliberately independent of the
|
||||
-- tree-sitter detection in `pmacs.parse`. Consulted only when
|
||||
-- `pmacs.parse.language_for_path` finds nothing (an extension with a
|
||||
|
|
@ -267,6 +323,11 @@ pmacs.lsp.filetypes.toml = pmacs.lsp.filetypes.toml or "toml"
|
|||
-- Zig (zls). `.zon` is Zig Object Notation, handled by the same server.
|
||||
pmacs.lsp.filetypes.zig = pmacs.lsp.filetypes.zig or "zig"
|
||||
pmacs.lsp.filetypes.zon = pmacs.lsp.filetypes.zon or "zig"
|
||||
-- JSON / YAML. Both ship grammars, so `language_for_path` already resolves
|
||||
-- these and the map is the stable-id fallback (same role as `lua`/`cuda`).
|
||||
pmacs.lsp.filetypes.json = pmacs.lsp.filetypes.json or "json"
|
||||
pmacs.lsp.filetypes.yaml = pmacs.lsp.filetypes.yaml or "yaml"
|
||||
pmacs.lsp.filetypes.yml = pmacs.lsp.filetypes.yml or "yaml"
|
||||
|
||||
-- Per-buffer attachment record: { language, server, uri, version }.
|
||||
-- Keyed by `tostring(BufferIdLua)` because BufferIdLua hands out fresh
|
||||
|
|
|
|||
|
|
@ -323,11 +323,18 @@ runtime/Lua-registered languages (v1 resolves only against
|
|||
`BUILTIN_LANGUAGES`), and the next injection *consumers* gated on new
|
||||
grammars — HTML/CSS/GraphQL/SQL (`<script>`/`<style>`, JS/TS template
|
||||
literals, doc-comment code); modeline detection as a 5th layer
|
||||
(`-*- mode: … -*-` / `# vim: ft=…`); JSON/YAML grammars+LSP;
|
||||
(`-*- mode: … -*-` / `# vim: ft=…`);
|
||||
byte-accurate multibyte cursor placement in `move_active_cursor_to`
|
||||
(still steps one codepoint per LSP byte column). A full Jupyter `.ipynb`
|
||||
setup (reader → editable → kernel execution) is a real arc now gated on
|
||||
**JSON** (injections shipped in #122), NOT a one-shot.
|
||||
(still steps one codepoint per LSP byte column). **JSON + YAML PR #123
|
||||
OPEN** (grammar-gap style, `tree-sitter-json`/`-yaml`; LSP configs
|
||||
`vscode-json-language-server` with provider pin
|
||||
`@t1ckbase/vscode-langservers-extracted@2.0.2`, plus
|
||||
`yaml-language-server`). The public and checkpoint branches are both at
|
||||
fully gated `5c202c5`, rebased onto `f8096ff`; the JSON and YAML
|
||||
PATH-gated pmacs smokes both passed, and the PR awaits user review.
|
||||
Once merged, YAML `---` and TOML `+++` markdown frontmatter highlight via
|
||||
the #122 engine and the Jupyter reader → editable → kernel arc has both
|
||||
grammar prerequisites.
|
||||
GPU: auto-reconnect after daemon restart, splits/multi-buffer, gutter
|
||||
riders (whitespace guides, folding, git markers).
|
||||
Themes (full list in theme-faces framing rev 9 "Deferred (named)"):
|
||||
|
|
|
|||
|
|
@ -0,0 +1,220 @@
|
|||
# JSON + YAML grammars — framing (side quest, highlight family)
|
||||
|
||||
**Revision 4 — 2026-07-21. Status: PR #123 open and awaiting review;
|
||||
the public and checkpoint branches are at fully gated `5c202c5`, rebased
|
||||
onto `main` `f8096ff`.**
|
||||
The JSON provider and Red Hat YAML 1.24.0 have each passed their
|
||||
PATH-gated pmacs acceptance, in addition to the deterministic fake-server
|
||||
config-push proof and the YAML standalone protocol smoke.
|
||||
|
||||
**Intent.** Add `tree-sitter-json` and `tree-sitter-yaml` grammars (plus
|
||||
their language servers) to the bundle. Two config formats that pmacs
|
||||
currently renders as plain text, and — the reason this is the natural
|
||||
next side quest — the **honest gate on the Jupyter `.ipynb` path** (JSON)
|
||||
and an **immediate payoff from the injection engine just shipped (#122)**:
|
||||
the markdown block grammar's `injections.scm` already sets
|
||||
`injection.language "yaml"` for `---` frontmatter and `"toml"` for `+++`
|
||||
frontmatter, so registering YAML lights up YAML frontmatter highlighting
|
||||
with zero extra wiring (TOML frontmatter already works — `toml` landed in
|
||||
#118 and injections in #122). This is a mostly-additive grammar-gap-style
|
||||
change, following the #118 pattern, with the frontmatter/fence synergy as
|
||||
the demonstrable headline.
|
||||
|
||||
---
|
||||
|
||||
## Ground truth (as of `main` @ `56eb67e`, #121)
|
||||
|
||||
- **Adding a grammar** is a one-line `LanguageEntry` in
|
||||
`crate::syntax::BUILTIN_LANGUAGES` (`name`, `extensions`, `loader`,
|
||||
`highlights_query`, `injections_query`) + a `tree-sitter-foo` dep. The
|
||||
Lua `buffer.after-load` path picks it up automatically; detection is
|
||||
extension → LSP filetype → filename → shebang
|
||||
(`resolve_active_language`).
|
||||
- **Grammar name MUST equal the `pmacs.lsp.config.<name>` key** — grammar
|
||||
detection wins over the filetype map, so the name it resolves is the id
|
||||
the LSP client keys off (the #118 invariant; there's an acceptance test
|
||||
that pins every grammar-gap language to its config key).
|
||||
- **LSP configs** are `pmacs.lsp.config.<name> = … or { command, args,
|
||||
[settings|init_options] }` (`builtin/runtime/lsp.lua`); no json/yaml
|
||||
config today. `pmacs.lsp.filetypes` is the LSP-only extension fallback
|
||||
(consulted only when `language_for_path` misses).
|
||||
- **Injection synergy (#122).** The bundled `tree_sitter_md::
|
||||
INJECTION_QUERY_BLOCK` contains:
|
||||
- `((minus_metadata) @injection.content (#set! injection.language
|
||||
"yaml"))` — `---`-fenced frontmatter,
|
||||
- `((plus_metadata) @injection.content (#set! injection.language
|
||||
"toml"))` — `+++`-fenced frontmatter,
|
||||
- fenced code blocks via the dynamic info-string.
|
||||
So a registered `yaml` grammar is injected into markdown frontmatter
|
||||
automatically, and ` ```json `/` ```yaml `/` ```yml ` fences resolve
|
||||
(`yml`→yaml is already in `default_injection_aliases`; `json` is the
|
||||
bundled name).
|
||||
|
||||
**Confirmed crate facts** (probed against the registry + a build under
|
||||
tree-sitter 0.26):
|
||||
|
||||
1. `tree-sitter-json` **0.24.8** — `pub const LANGUAGE: LanguageFn` (via
|
||||
`tree-sitter-language`, the modern shared ABI) + `HIGHLIGHTS_QUERY`.
|
||||
Compiles and links under our tree-sitter 0.26. No `INJECTIONS_QUERY`
|
||||
(JSON embeds nothing).
|
||||
2. `tree-sitter-yaml` **0.7.2** — same shape (`LANGUAGE: LanguageFn`,
|
||||
`HIGHLIGHTS_QUERY`, `tree-sitter-language` dep). Compiles under 0.26.
|
||||
No `INJECTIONS_QUERY`.
|
||||
3. Both are the ABI-current crates — **not** a `tree-sitter ^0.20` fork
|
||||
(the dockerfile trap from #118). A single build confirmed link +
|
||||
compile; runtime `set_language` is pinned by the ABI acceptance test.
|
||||
|
||||
---
|
||||
|
||||
## Decisions
|
||||
|
||||
### Q#JY1 — Two `LanguageEntry`s, self-contained highlights, no injections
|
||||
|
||||
Add `json` and `yaml` to `BUILTIN_LANGUAGES`, each
|
||||
`highlights_query: &[…::HIGHLIGHTS_QUERY]` (self-contained, no
|
||||
`; inherits:` delta), `injections_query: &[]`. Extensions:
|
||||
|
||||
- **json:** `.json`. (`.jsonc`/`.json5` — comment/trailing-comma variants
|
||||
the plain JSON grammar rejects — are **deferred**; a `.jsonc` grammar
|
||||
or a lenient mode is a separate call.)
|
||||
- **yaml:** `.yaml`, `.yml`.
|
||||
|
||||
Root kinds (pinned by the ABI test): json `document`, yaml `stream`.
|
||||
|
||||
### Q#JY2 — LSP configs: `vscode-json-language-server` + `yaml-language-server`
|
||||
|
||||
- **json:** binary `vscode-json-language-server --stdio` (the VS Code
|
||||
JSON server). It is **push-model**: it reads config from
|
||||
`workspace/didChangeConfiguration` and does **not** issue
|
||||
`workspace/configuration` pulls — so pmacs, which previously only
|
||||
*answered* pulls, must now also **push** a `didChangeConfiguration`
|
||||
after `initialized` (a general LSP-client fix in `src/lsp.rs`; pull
|
||||
servers ignore it). `json.validate.enable` is set **explicitly true**
|
||||
— a missing value reads as false and silently disables validation, so
|
||||
an empty `json = {}` is wrong. The server does **not** auto-associate
|
||||
`package.json`/`tsconfig.json` (it starts with empty contributions);
|
||||
explicit `$schema` refs or configured `json.schemas` / a
|
||||
`json/schemaAssociations` push (not implemented) are required. Schema
|
||||
retrieval performs **network access** for remote `$schema` URLs, left
|
||||
enabled (`handledSchemaProtocols = {"file"}` would disable it but break
|
||||
remote schemas without a `vscode/content` impl). **Provider:** pin
|
||||
`@t1ckbase/vscode-langservers-extracted@2.0.2`
|
||||
(`npm install -g @t1ckbase/vscode-langservers-extracted@2.0.2`). Its
|
||||
published payload bundles the JSON server from VS Code 1.129.0,
|
||||
preserves the `vscode-json-language-server` command, and was
|
||||
live-smoked through initialize → config push → invalid-JSON diagnostic
|
||||
→ shutdown. The unscoped package is stale; the current
|
||||
`@zed-industries` payload has a broken JSON launcher, so neither is the
|
||||
recommended provider.
|
||||
- **yaml:** `yaml-language-server --stdio` (Red Hat). Its settings handler
|
||||
reads the sections **`yaml`, `http`, `[yaml]`, `editor`, `files`** (via
|
||||
`didChangeConfiguration` / pulls) — all ship present-not-null. It does
|
||||
**not** upload telemetry itself (it emits `telemetry/event` to the
|
||||
client; pmacs has no uploader), so a `redhat.telemetry` setting is inert
|
||||
and is not shipped. SchemaStore / remote schema retrieval performs
|
||||
**network access** by default.
|
||||
|
||||
Both servers stay **external** (installed by the user), adding **no
|
||||
licensing payload** to pmacs; if either is ever bundled, retain its MIT +
|
||||
dependency notices. The exact sections are **pinned in the config + a
|
||||
test** (not merely "some non-nil table exists"). The pinned JSON
|
||||
provider was installed into an isolated temporary prefix and
|
||||
live-smoked through pmacs. Red Hat `yaml-language-server@1.24.0` was
|
||||
also installed in an isolated prefix and live-smoked over stdio: its
|
||||
initial configuration pull was exactly `yaml`, `http`, `[yaml]`,
|
||||
`editor`, `files`; opening the document caused a second scoped
|
||||
`[yaml]` pull; invalid YAML produced a parser diagnostic; shutdown was
|
||||
clean. Both providers have also passed their PATH-gated pmacs acceptance.
|
||||
Config-push delivery is proven deterministically through the fake server's
|
||||
config sink. Servers activate only if installed; the grammar is the
|
||||
always-on value.
|
||||
|
||||
### Q#JY3 — Filetype fallback + alias entries
|
||||
|
||||
Add `pmacs.lsp.filetypes` entries (`json`→json, `yaml`/`yml`→yaml) as the
|
||||
stable-id fallback (grammar detection wins in practice, same role as the
|
||||
`cuda`/`lua` entries). `default_injection_aliases` already has `yml`→yaml;
|
||||
`json`/`yaml` are bundled names needing no alias. Special *filenames*
|
||||
(`.prettierrc`, `docker-compose.yml` is already `.yml`, extensionless
|
||||
CI/config yaml) are **deferred** to the filename map as a follow-up.
|
||||
|
||||
### Q#JY4 — Frontmatter/fence highlighting is the headline, and it's free
|
||||
|
||||
No new injection wiring: registering `yaml` makes the existing markdown
|
||||
`minus_metadata`→yaml injection resolve, and ` ```json `/` ```yaml `
|
||||
fences resolve through the #122 engine. Acceptance proves both end to end
|
||||
(this is the demonstrable payoff and the tie-back to injections).
|
||||
|
||||
---
|
||||
|
||||
## Bets
|
||||
|
||||
1. The two crates are ABI-current and drop in like the #118 grammar-gap
|
||||
languages — verified by a build; the ABI test is the runtime pin.
|
||||
2. The frontmatter/fence synergy needs zero engine changes — it falls out
|
||||
of #122 + the markdown injection query.
|
||||
3. The two configuration models are now observed: JSON consumes the
|
||||
pushed full settings object; YAML 1.24.0 pulls the five documented
|
||||
sections plus a document-scoped `[yaml]` request. The remaining bet
|
||||
was that pmacs answers the real YAML server correctly end to end;
|
||||
the PATH-gated acceptance now proves that against version 1.24.0.
|
||||
|
||||
## Deferred (named)
|
||||
|
||||
- `.jsonc` / `.json5` (comments / trailing commas) — needs a lenient
|
||||
grammar or variant entry.
|
||||
- Special-filename detection for extensionless config files (`.prettierrc`,
|
||||
CI yaml) via the filename map.
|
||||
- JSON **schema** wiring (custom `json.schemas` / `yaml.schemas` settings)
|
||||
beyond the servers' built-in schema stores.
|
||||
- The Jupyter `.ipynb` arc itself (JSON is its prerequisite, not its
|
||||
delivery).
|
||||
|
||||
## Acceptance
|
||||
|
||||
1. `builtin_languages_include_json_and_yaml` — entries present, claim
|
||||
their extensions, ship non-empty highlights.
|
||||
2. `json_grammar_loads_and_parses` — ABI: `set_language` + parse a JSON
|
||||
object; root `document`, no error (the runtime ABI pin).
|
||||
3. `yaml_grammar_loads_and_parses` — ABI: parse a YAML mapping; root
|
||||
`stream`, no error.
|
||||
4. `json_yaml_highlights_compile` — both highlights queries compile and
|
||||
resolve several capture classes.
|
||||
5. `language_for_path_resolves_json_yaml` — `.json`→json, `.yaml`/`.yml`→
|
||||
yaml.
|
||||
6. `json_yaml_align_with_lsp_configs` — grammar name == the
|
||||
`pmacs.lsp.config.<name>` key (the #118 invariant).
|
||||
7. **`yaml_frontmatter_injects_in_markdown`** — a markdown doc with a
|
||||
`---\nkey: val\n---` frontmatter yields a `yaml` child layer that
|
||||
highlights; **the headline synergy with #122**.
|
||||
8. `json_fence_injects_in_markdown` — a ` ```json ` fence yields a `json`
|
||||
child layer.
|
||||
9. `m4_json_yaml_lsp_configs_pin_command_and_sections` — the configs
|
||||
pin the binary + `json.validate.enable = true` + the exact section
|
||||
sets (json: `json`,`http`; yaml: `yaml`,`http`,`[yaml]`,`editor`,
|
||||
`files`; no inert `redhat.telemetry`) — pinned, not merely non-nil.
|
||||
10. `m4_5_initial_config_pushed_via_did_change_configuration` — the
|
||||
daemon PUSHES `workspace/didChangeConfiguration` after `initialized`
|
||||
(the push-model delivery path), verified through the fake server's
|
||||
config sink. Without it, push-only servers' settings are inert.
|
||||
11. `m4_real_json_provider_receives_config_and_reports_diagnostics` —
|
||||
PATH-gated live smoke for the pinned provider: initialize through
|
||||
pmacs, receive the pushed default config, open invalid JSON, and
|
||||
publish a syntax diagnostic. Skips when the binary is absent.
|
||||
12. `m4_real_yaml_provider_pulls_config_and_reports_diagnostics` —
|
||||
PATH-gated live smoke for Red Hat `yaml-language-server@1.24.0`:
|
||||
auto-attach through pmacs, disable SchemaStore and Kubernetes CRD
|
||||
catalog network access for determinism, reach initialized, open
|
||||
invalid YAML, publish a diagnostic, and remain alive.
|
||||
|
||||
## Risks / interactions
|
||||
|
||||
- **LSP configuration** (Q#JY2) — JSON push, YAML standalone pulls, and
|
||||
the real YAML-through-pmacs path are observed. Both live provider
|
||||
tests remain PATH-gated, so release verification must put the pinned
|
||||
binaries on PATH rather than accepting their skip paths.
|
||||
- **Themes / injections** — untouched. This is pure grammar+detection
|
||||
addition; it consumes the #122 engine, doesn't change it. No protocol
|
||||
bump.
|
||||
- **`.yml` vs `.yaml`** — both map to `yaml`; no collision with any
|
||||
existing entry.
|
||||
|
|
@ -414,6 +414,26 @@ fn main() {
|
|||
});
|
||||
write_frame(&mut stdout, &resp);
|
||||
}
|
||||
("workspace/didChangeConfiguration", _) => {
|
||||
// Record the pushed `settings` so a test can assert the
|
||||
// daemon delivered configuration after `initialized` (the
|
||||
// push-model config-delivery path push-only servers like the
|
||||
// VS Code JSON server rely on). One JSON line per push.
|
||||
if let Ok(sink) = std::env::var("PMACS_FAKE_LSP_CONFIG_SINK") {
|
||||
use std::io::Write as _;
|
||||
if let Ok(mut f) = std::fs::OpenOptions::new()
|
||||
.create(true)
|
||||
.append(true)
|
||||
.open(&sink)
|
||||
{
|
||||
let settings = params
|
||||
.get("settings")
|
||||
.cloned()
|
||||
.unwrap_or(serde_json::Value::Null);
|
||||
let _ = writeln!(f, "{settings}");
|
||||
}
|
||||
}
|
||||
}
|
||||
("textDocument/didOpen" | "textDocument/didChange", _) => {
|
||||
let uri = params
|
||||
.get("textDocument")
|
||||
|
|
|
|||
22
src/lsp.rs
22
src/lsp.rs
|
|
@ -2497,6 +2497,25 @@ impl LspManager {
|
|||
}
|
||||
}
|
||||
|
||||
/// Push the client's configured `settings` via
|
||||
/// `workspace/didChangeConfiguration` immediately after `initialized`.
|
||||
/// Push-model servers — notably the VS Code JSON server, which listens
|
||||
/// for this notification and does NOT issue `workspace/configuration`
|
||||
/// pulls — only learn their config this way; pull-model servers
|
||||
/// (pyright, clangd, gopls) ignore it and pull instead, so it is safe
|
||||
/// to send unconditionally. No-op when no `settings` are configured.
|
||||
fn push_initial_configuration(&self, sid: LspServerId) {
|
||||
if let Some(client) = self.clients.get(&sid)
|
||||
&& let Some(settings) = client.spec.settings.clone()
|
||||
{
|
||||
let cfg = make_notification(
|
||||
"workspace/didChangeConfiguration",
|
||||
json!({ "settings": settings }),
|
||||
);
|
||||
let _ = send_frame_to(&self.supervisor, client, &cfg);
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_response(
|
||||
&mut self,
|
||||
sid: LspServerId,
|
||||
|
|
@ -2552,6 +2571,9 @@ impl LspManager {
|
|||
if let Some(client) = self.clients.get(&sid) {
|
||||
let _ = send_frame_to(&self.supervisor, client, &body);
|
||||
}
|
||||
// Push the configured settings right after `initialized`
|
||||
// (before any deferred `didOpen`).
|
||||
self.push_initial_configuration(sid);
|
||||
// T M4.5 Option B: honour the server's negotiated
|
||||
// `general.positionEncoding`. Absent ⇒ LSP spec default
|
||||
// (UTF-16). We advertised `["utf-8","utf-16"]`, so a
|
||||
|
|
|
|||
182
src/syntax.rs
182
src/syntax.rs
|
|
@ -1027,6 +1027,27 @@ pub const BUILTIN_LANGUAGES: &[LanguageEntry] = &[
|
|||
highlights_query: &[tree_sitter_zig::HIGHLIGHTS_QUERY],
|
||||
injections_query: &[],
|
||||
},
|
||||
// JSON + YAML — config formats, both self-contained highlights and no
|
||||
// injections of their own. Registering `yaml` also lights up markdown
|
||||
// `---` frontmatter via the #122 injection engine (the markdown block
|
||||
// injection query sets `injection.language "yaml"` for `minus_metadata`;
|
||||
// `+++` TOML frontmatter already works). Root kinds: json `document`,
|
||||
// yaml `stream`. `.jsonc`/`.json5` (comments / trailing commas) are a
|
||||
// deferred variant — the plain JSON grammar rejects them.
|
||||
LanguageEntry {
|
||||
name: "json",
|
||||
extensions: &["json"],
|
||||
loader: || tree_sitter_json::LANGUAGE.into(),
|
||||
highlights_query: &[tree_sitter_json::HIGHLIGHTS_QUERY],
|
||||
injections_query: &[],
|
||||
},
|
||||
LanguageEntry {
|
||||
name: "yaml",
|
||||
extensions: &["yaml", "yml"],
|
||||
loader: || tree_sitter_yaml::LANGUAGE.into(),
|
||||
highlights_query: &[tree_sitter_yaml::HIGHLIGHTS_QUERY],
|
||||
injections_query: &[],
|
||||
},
|
||||
];
|
||||
|
||||
/// Registry that the Lua surface ([`crate::lua_bindings::install_parse`])
|
||||
|
|
@ -2062,6 +2083,167 @@ mod tests {
|
|||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn builtin_languages_include_json_and_yaml() {
|
||||
// Framing acceptance #1: both entries present, claim their
|
||||
// extensions, ship non-empty highlights.
|
||||
let json = BUILTIN_LANGUAGES
|
||||
.iter()
|
||||
.find(|l| l.name == "json")
|
||||
.expect("`json` entry present");
|
||||
assert!(json.extensions.contains(&"json"), "`json` claims `.json`");
|
||||
assert!(!json.highlights_query.is_empty(), "`json` ships highlights");
|
||||
let yaml = BUILTIN_LANGUAGES
|
||||
.iter()
|
||||
.find(|l| l.name == "yaml")
|
||||
.expect("`yaml` entry present");
|
||||
assert!(yaml.extensions.contains(&"yaml"), "`yaml` claims `.yaml`");
|
||||
assert!(yaml.extensions.contains(&"yml"), "`yaml` claims `.yml`");
|
||||
assert!(!yaml.highlights_query.is_empty(), "`yaml` ships highlights");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn json_grammar_loads_and_parses() {
|
||||
// Framing acceptance #2 / ABI pin: `tree-sitter-json` 0.24 is
|
||||
// accepted by our tree-sitter 0.26 core; a JSON object parses to a
|
||||
// `document` root without error.
|
||||
let reg = SyntaxRegistry::new();
|
||||
let language = reg.language("json").expect("`json` loads");
|
||||
let mut buf = fresh_buffer("data.json");
|
||||
buf.apply_edit(EditOp::Insert {
|
||||
pos: 0,
|
||||
bytes: b"{\n \"name\": \"pmacs\",\n \"nums\": [1, 2, 3],\n \"ok\": true\n}\n",
|
||||
})
|
||||
.unwrap();
|
||||
let view = ParseView::new(&buf, language, "json".to_owned());
|
||||
let handle = view.handle();
|
||||
let _vid = buf.attach_view(Box::new(view));
|
||||
let bundle = parse_synchronously(&handle);
|
||||
assert_eq!(
|
||||
bundle.root_tree().root_node().kind(),
|
||||
"document",
|
||||
"json grammar roots at `document`"
|
||||
);
|
||||
assert!(
|
||||
!bundle.root_tree().root_node().has_error(),
|
||||
"json grammar parses an object without error"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn yaml_grammar_loads_and_parses() {
|
||||
// Framing acceptance #3 / ABI pin: `tree-sitter-yaml` 0.7 loads and
|
||||
// a YAML mapping parses to a `stream` root without error.
|
||||
let reg = SyntaxRegistry::new();
|
||||
let language = reg.language("yaml").expect("`yaml` loads");
|
||||
let mut buf = fresh_buffer("config.yaml");
|
||||
buf.apply_edit(EditOp::Insert {
|
||||
pos: 0,
|
||||
bytes: b"name: pmacs\nversion: 1\ntags:\n - a\n - b\n",
|
||||
})
|
||||
.unwrap();
|
||||
let view = ParseView::new(&buf, language, "yaml".to_owned());
|
||||
let handle = view.handle();
|
||||
let _vid = buf.attach_view(Box::new(view));
|
||||
let bundle = parse_synchronously(&handle);
|
||||
assert_eq!(
|
||||
bundle.root_tree().root_node().kind(),
|
||||
"stream",
|
||||
"yaml grammar roots at `stream`"
|
||||
);
|
||||
assert!(
|
||||
!bundle.root_tree().root_node().has_error(),
|
||||
"yaml grammar parses a mapping without error"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn json_yaml_highlights_compile() {
|
||||
// Framing acceptance #4: both highlights queries compile against
|
||||
// their grammars and resolve capture classes.
|
||||
let reg = SyntaxRegistry::new();
|
||||
let json = reg
|
||||
.highlights_query("json")
|
||||
.expect("json highlights compile");
|
||||
assert!(
|
||||
json.capture_names().len() >= 3,
|
||||
"json highlights resolve capture classes; got {}",
|
||||
json.capture_names().len()
|
||||
);
|
||||
let yaml = reg
|
||||
.highlights_query("yaml")
|
||||
.expect("yaml highlights compile");
|
||||
assert!(
|
||||
yaml.capture_names().len() >= 3,
|
||||
"yaml highlights resolve capture classes; got {}",
|
||||
yaml.capture_names().len()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn language_for_path_resolves_json_yaml() {
|
||||
// Framing acceptance #5.
|
||||
let reg = SyntaxRegistry::new();
|
||||
assert_eq!(
|
||||
reg.language_name_for_path("tsconfig.json").as_deref(),
|
||||
Some("json")
|
||||
);
|
||||
assert_eq!(
|
||||
reg.language_name_for_path("config.yaml").as_deref(),
|
||||
Some("yaml")
|
||||
);
|
||||
assert_eq!(
|
||||
reg.language_name_for_path("ci.yml").as_deref(),
|
||||
Some("yaml")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn yaml_frontmatter_injects_in_markdown() {
|
||||
// Framing acceptance #7 — THE headline synergy with #122: a markdown
|
||||
// `---` frontmatter block (a `minus_metadata` node) is injected as
|
||||
// yaml by the bundled markdown injection query, so registering the
|
||||
// yaml grammar lights it up with no extra wiring.
|
||||
let reg = SyntaxRegistry::new();
|
||||
let src = b"---\ntitle: Hello\ntags: [a, b]\n---\n\n# Body\n";
|
||||
let bundle = parse_layered(®, "markdown", src);
|
||||
let yaml = bundle
|
||||
.layers
|
||||
.iter()
|
||||
.find(|l| l.language_name == "yaml")
|
||||
.expect("`---` frontmatter yields a yaml child layer");
|
||||
assert_eq!(
|
||||
yaml.tree.root_node().kind(),
|
||||
"stream",
|
||||
"yaml layer roots at stream"
|
||||
);
|
||||
let query = yaml
|
||||
.highlight_query
|
||||
.as_ref()
|
||||
.expect("yaml highlights resolved");
|
||||
let spans = compute_highlight_spans_for(query, &yaml.tree, &bundle.source, None);
|
||||
assert!(!spans.is_empty(), "the yaml frontmatter layer highlights");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn json_fence_injects_in_markdown() {
|
||||
// Framing acceptance #8: a ```json fence yields a json child layer
|
||||
// through the #122 engine.
|
||||
let reg = SyntaxRegistry::new();
|
||||
let src = b"# Doc\n\n```json\n{\"a\": 1, \"b\": [2, 3]}\n```\n";
|
||||
let bundle = parse_layered(®, "markdown", src);
|
||||
let json = bundle
|
||||
.layers
|
||||
.iter()
|
||||
.find(|l| l.language_name == "json")
|
||||
.expect("a ```json fence yields a json child layer");
|
||||
assert_eq!(
|
||||
json.tree.root_node().kind(),
|
||||
"document",
|
||||
"json layer roots at document"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn builtin_languages_include_dockerfile_make_cmake() {
|
||||
for (name, exts) in [
|
||||
|
|
|
|||
|
|
@ -6265,6 +6265,9 @@ fn m4_gap_grammars_align_with_lsp_configs() {
|
|||
("A.tsx", "typescriptreact"),
|
||||
("Cargo.toml", "toml"),
|
||||
("build.zig", "zig"),
|
||||
("tsconfig.json", "json"),
|
||||
("config.yaml", "yaml"),
|
||||
("ci.yml", "yaml"),
|
||||
] {
|
||||
let (grammar, has_cfg): (Option<String>, bool) = s
|
||||
.lua_host
|
||||
|
|
@ -6287,6 +6290,298 @@ fn m4_gap_grammars_align_with_lsp_configs() {
|
|||
}
|
||||
}
|
||||
|
||||
/// JSON/YAML LSP configs pin the server commands and the settings shape
|
||||
/// each consumes (framing Q#JY2): JSON receives a pushed full object;
|
||||
/// YAML pulls five named sections. The pinned JSON and YAML providers each
|
||||
/// have a separate PATH-gated live smoke below. The point is to pin the
|
||||
/// contract, not merely assert that some settings table exists.
|
||||
#[test]
|
||||
fn m4_json_yaml_lsp_configs_pin_command_and_sections() {
|
||||
use pmacs::editor::EditorState;
|
||||
let s = EditorState::new();
|
||||
let lua = s.lua_host.lua();
|
||||
|
||||
// json: the `@t1ckbase/vscode-langservers-extracted@2.0.2` binary
|
||||
// (NOT the stale standalone `vscode-json-languageserver`), `--stdio`,
|
||||
// and the `json` + `http` workspace-config sections present. The
|
||||
// provider preserves this stable command name; its exact pin and live
|
||||
// handshake evidence are documented beside the default config.
|
||||
let json_command: String = lua
|
||||
.load("return pmacs.lsp.config.json.command")
|
||||
.eval()
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
json_command, "vscode-json-language-server",
|
||||
"json uses the pinned T1ckbase provider's stable command name"
|
||||
);
|
||||
// The JSON server is push-model (reads didChangeConfiguration, no
|
||||
// pulls), so `json.validate.enable` must be EXPLICITLY true — a missing
|
||||
// value reads as false and disables validation. Remote schemas left on.
|
||||
let json_ok: bool = lua
|
||||
.load(
|
||||
"local c = pmacs.lsp.config.json
|
||||
return c.args[1] == '--stdio'
|
||||
and c.settings.json.validate.enable == true
|
||||
and c.settings.http ~= nil
|
||||
and c.settings.handledSchemaProtocols == nil",
|
||||
)
|
||||
.eval()
|
||||
.unwrap();
|
||||
assert!(
|
||||
json_ok,
|
||||
"json config: --stdio, json.validate.enable=true, http present, remote schemas on"
|
||||
);
|
||||
|
||||
// yaml: `yaml-language-server --stdio`. Its settings handler reads the
|
||||
// `yaml`, `http`, `[yaml]`, `editor`, and `files` sections — pin all
|
||||
// five, and confirm the inert `redhat.telemetry` is NOT shipped (the
|
||||
// standalone server emits telemetry events to the client; it does not
|
||||
// upload, and pmacs has no uploader).
|
||||
let yaml_command: String = lua
|
||||
.load("return pmacs.lsp.config.yaml.command")
|
||||
.eval()
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
yaml_command, "yaml-language-server",
|
||||
"yaml uses the Red Hat yaml-language-server"
|
||||
);
|
||||
let yaml_ok: bool = lua
|
||||
.load(
|
||||
"local c = pmacs.lsp.config.yaml
|
||||
return c.args[1] == '--stdio'
|
||||
and c.settings.yaml ~= nil
|
||||
and c.settings.http ~= nil
|
||||
and c.settings['[yaml]'] ~= nil
|
||||
and c.settings.editor ~= nil
|
||||
and c.settings.files ~= nil
|
||||
and c.settings.redhat == nil",
|
||||
)
|
||||
.eval()
|
||||
.unwrap();
|
||||
assert!(
|
||||
yaml_ok,
|
||||
"yaml config: --stdio, the five pulled sections present, no inert redhat.telemetry"
|
||||
);
|
||||
}
|
||||
|
||||
/// Round-1 finding (P1): the daemon must PUSH configuration via
|
||||
/// `workspace/didChangeConfiguration` after `initialized`. Push-model
|
||||
/// servers — notably the VS Code JSON server — never issue
|
||||
/// `workspace/configuration` pulls, so without the push their `settings`
|
||||
/// (including `json.validate.enable`) are inert. Verified through the fake
|
||||
/// server's config sink: the settings the daemon sends are recorded and
|
||||
/// inspected, proving delivery end to end.
|
||||
#[test]
|
||||
fn m4_5_initial_config_pushed_via_did_change_configuration() {
|
||||
use pmacs::editor::EditorState;
|
||||
let mut state = EditorState::new();
|
||||
let fake = fake_lsp_path();
|
||||
let dir = tempfile::tempdir().expect("tempdir");
|
||||
let sink = dir.path().join("config.jsonl");
|
||||
let file = dir.path().join("probe.rs");
|
||||
std::fs::write(&file, "fn main() {}\n").expect("write");
|
||||
let sink_disp = sink.display().to_string();
|
||||
let file_disp = file.display().to_string();
|
||||
|
||||
// Point rust at the fake server WITH a settings table and route the
|
||||
// config sink into the spawned process, then open the file (auto-attach
|
||||
// → initialize → initialized → the config push).
|
||||
state
|
||||
.lua_host
|
||||
.lua()
|
||||
.load(format!(
|
||||
"pmacs.lsp.config.rust = {{
|
||||
command = '{fake}',
|
||||
env = {{ PMACS_FAKE_LSP_CONFIG_SINK = '{sink_disp}' }},
|
||||
settings = {{ rust = {{ probe = true }} }},
|
||||
}}
|
||||
pmacs.buffer.find_or_open('{file_disp}')"
|
||||
))
|
||||
.exec()
|
||||
.expect("configure + open");
|
||||
|
||||
assert!(
|
||||
pump_lua_flag(
|
||||
&mut state,
|
||||
"(function() for _,r in ipairs(pmacs.lsp.list()) do \
|
||||
if r.state and r.state.kind=='initialized' then return true end \
|
||||
end return false end)()",
|
||||
5,
|
||||
),
|
||||
"fake never initialized"
|
||||
);
|
||||
// A few more ticks for the push + the server's sink write to land.
|
||||
let sink_probe = sink.clone();
|
||||
pump_async(&mut state, move |_| {
|
||||
std::fs::read_to_string(&sink_probe).is_ok_and(|s| s.contains("probe"))
|
||||
});
|
||||
|
||||
let recorded = std::fs::read_to_string(&sink).unwrap_or_else(|e| {
|
||||
panic!("config sink not written ({e}); the didChangeConfiguration push did not arrive")
|
||||
});
|
||||
assert!(
|
||||
recorded.contains("\"probe\":true"),
|
||||
"the daemon pushed the configured settings after initialized: {recorded}"
|
||||
);
|
||||
}
|
||||
|
||||
/// PATH-gated provider smoke: drive a real `vscode-json-language-server`
|
||||
/// through pmacs's default JSON config, including the post-initialize
|
||||
/// `didChangeConfiguration` push, and require a syntax diagnostic for an
|
||||
/// invalid document. The reviewed provider is
|
||||
/// `@t1ckbase/vscode-langservers-extracted@2.0.2`; CI skips cleanly when
|
||||
/// no compatible binary is installed.
|
||||
#[test]
|
||||
fn m4_real_json_provider_receives_config_and_reports_diagnostics() {
|
||||
use pmacs::editor::EditorState;
|
||||
|
||||
let Ok(command) = which_binary("vscode-json-language-server") else {
|
||||
eprintln!("vscode-json-language-server not on PATH; skipping");
|
||||
return;
|
||||
};
|
||||
let command = command.display().to_string();
|
||||
let dir = tempfile::tempdir().expect("tempdir");
|
||||
let file = std::fs::canonicalize(dir.path())
|
||||
.expect("canonicalize")
|
||||
.join("invalid.json");
|
||||
std::fs::write(&file, b"{\"broken\": }\n").expect("write invalid json");
|
||||
let file_disp = file.display().to_string();
|
||||
let uri = format!("file://{file_disp}");
|
||||
|
||||
let mut state = EditorState::new();
|
||||
state
|
||||
.lua_host
|
||||
.lua()
|
||||
.load(format!(
|
||||
"pmacs.lsp.config.json.command = '{command}'
|
||||
pmacs.buffer.find_or_open('{file_disp}')"
|
||||
))
|
||||
.exec()
|
||||
.expect("configure real JSON server + open file");
|
||||
|
||||
assert!(
|
||||
pump_lua_flag(
|
||||
&mut state,
|
||||
"(function() for _,r in ipairs(pmacs.lsp.list()) do \
|
||||
if r.state and r.state.kind=='initialized' then return true end \
|
||||
end return false end)()",
|
||||
30,
|
||||
),
|
||||
"real JSON server never reached initialized"
|
||||
);
|
||||
|
||||
let deadline = Instant::now() + Duration::from_secs(10);
|
||||
let mut got_diagnostic = false;
|
||||
while Instant::now() < deadline {
|
||||
state.tick_processes();
|
||||
state.tick_lsp();
|
||||
state.tick_async();
|
||||
got_diagnostic = state
|
||||
.lua_host
|
||||
.lua()
|
||||
.load(format!("return pmacs.diag.count('{uri}') > 0"))
|
||||
.eval()
|
||||
.unwrap_or(false);
|
||||
if got_diagnostic {
|
||||
break;
|
||||
}
|
||||
std::thread::sleep(Duration::from_millis(10));
|
||||
}
|
||||
assert!(
|
||||
got_diagnostic,
|
||||
"real JSON server produced no diagnostic; config delivery or validation is broken"
|
||||
);
|
||||
assert_no_lsp_crash(&mut state, "real JSON server");
|
||||
}
|
||||
|
||||
/// PATH-gated provider smoke: drive Red Hat
|
||||
/// `yaml-language-server@1.24.0` through pmacs's default YAML config and
|
||||
/// require a syntax diagnostic for an invalid document. `SchemaStore` and
|
||||
/// the Kubernetes CRD catalog are disabled in this test so the result is
|
||||
/// deterministic and does not depend on network access. CI skips cleanly
|
||||
/// when no compatible binary is installed.
|
||||
#[test]
|
||||
fn m4_real_yaml_provider_pulls_config_and_reports_diagnostics() {
|
||||
use pmacs::editor::EditorState;
|
||||
|
||||
let Ok(command) = which_binary("yaml-language-server") else {
|
||||
eprintln!("yaml-language-server not on PATH; skipping");
|
||||
return;
|
||||
};
|
||||
let command = command.display().to_string();
|
||||
let dir = tempfile::tempdir().expect("tempdir");
|
||||
let file = std::fs::canonicalize(dir.path())
|
||||
.expect("canonicalize")
|
||||
.join("invalid.yaml");
|
||||
std::fs::write(&file, b"root:\n broken: [one,\n").expect("write invalid yaml");
|
||||
let file_disp = file.display().to_string();
|
||||
let uri = format!("file://{file_disp}");
|
||||
|
||||
let mut state = EditorState::new();
|
||||
state
|
||||
.lua_host
|
||||
.lua()
|
||||
.load(format!(
|
||||
"local c = pmacs.lsp.config.yaml
|
||||
c.command = '{command}'
|
||||
c.settings.yaml.schemaStore = {{ enable = false }}
|
||||
c.settings.yaml.kubernetesCRDStore = {{ enable = false }}
|
||||
pmacs.buffer.find_or_open('{file_disp}')"
|
||||
))
|
||||
.exec()
|
||||
.expect("configure real YAML server + open file");
|
||||
|
||||
assert!(
|
||||
pump_lua_flag(
|
||||
&mut state,
|
||||
"(function() for _,r in ipairs(pmacs.lsp.list()) do \
|
||||
if r.language_id=='yaml' and r.state \
|
||||
and r.state.kind=='initialized' then return true end \
|
||||
end return false end)()",
|
||||
30,
|
||||
),
|
||||
"auto-attached real YAML server never reached initialized"
|
||||
);
|
||||
|
||||
let deadline = Instant::now() + Duration::from_secs(10);
|
||||
let mut got_diagnostic = false;
|
||||
while Instant::now() < deadline {
|
||||
state.tick_processes();
|
||||
state.tick_lsp();
|
||||
state.tick_async();
|
||||
got_diagnostic = state
|
||||
.lua_host
|
||||
.lua()
|
||||
.load(format!("return pmacs.diag.count('{uri}') > 0"))
|
||||
.eval()
|
||||
.unwrap_or(false);
|
||||
if got_diagnostic {
|
||||
break;
|
||||
}
|
||||
std::thread::sleep(Duration::from_millis(10));
|
||||
}
|
||||
assert!(
|
||||
got_diagnostic,
|
||||
"real YAML server produced no diagnostic; config pulls or validation are broken"
|
||||
);
|
||||
assert_no_lsp_crash(&mut state, "real YAML server");
|
||||
let still_initialized: bool = state
|
||||
.lua_host
|
||||
.lua()
|
||||
.load(
|
||||
"for _,r in ipairs(pmacs.lsp.list()) do \
|
||||
if r.language_id=='yaml' and r.state \
|
||||
and r.state.kind=='initialized' then return true end \
|
||||
end return false",
|
||||
)
|
||||
.eval()
|
||||
.expect("inspect YAML server state");
|
||||
assert!(
|
||||
still_initialized,
|
||||
"real YAML server did not remain alive after publishing diagnostics"
|
||||
);
|
||||
}
|
||||
|
||||
/// Typing-perf: the default bundle coalesces full-document
|
||||
/// `didChange` notifications instead of sending one per keystroke
|
||||
/// (each send copies the whole buffer several times and writes
|
||||
|
|
|
|||
Loading…
Reference in New Issue