@nvl/sveltex-language-server 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +288 -0
  3. package/bin/server.js +10 -0
  4. package/dist/core/config.d.ts +126 -0
  5. package/dist/core/config.js +569 -0
  6. package/dist/core/diagnostics.d.ts +34 -0
  7. package/dist/core/diagnostics.js +67 -0
  8. package/dist/core/frontmatter-data.d.ts +74 -0
  9. package/dist/core/frontmatter-data.js +323 -0
  10. package/dist/core/frontmatter.d.ts +25 -0
  11. package/dist/core/frontmatter.js +348 -0
  12. package/dist/core/lsp-proxy.d.ts +77 -0
  13. package/dist/core/lsp-proxy.js +165 -0
  14. package/dist/core/mapper.d.ts +86 -0
  15. package/dist/core/mapper.js +223 -0
  16. package/dist/core/mapping.d.ts +59 -0
  17. package/dist/core/mapping.js +37 -0
  18. package/dist/core/markdown.d.ts +34 -0
  19. package/dist/core/markdown.js +215 -0
  20. package/dist/core/region-forwarding.d.ts +90 -0
  21. package/dist/core/region-forwarding.js +428 -0
  22. package/dist/core/region-virtual.d.ts +71 -0
  23. package/dist/core/region-virtual.js +131 -0
  24. package/dist/core/regions.d.ts +56 -0
  25. package/dist/core/regions.js +221 -0
  26. package/dist/core/remap.d.ts +84 -0
  27. package/dist/core/remap.js +272 -0
  28. package/dist/core/server-helpers.d.ts +109 -0
  29. package/dist/core/server-helpers.js +182 -0
  30. package/dist/core/server.d.ts +13 -0
  31. package/dist/core/server.js +604 -0
  32. package/dist/core/svelte-proxy.d.ts +100 -0
  33. package/dist/core/svelte-proxy.js +144 -0
  34. package/dist/core/texlab.d.ts +26 -0
  35. package/dist/core/texlab.js +121 -0
  36. package/dist/core/virtual-svelte.d.ts +32 -0
  37. package/dist/core/virtual-svelte.js +67 -0
  38. package/dist/index.d.ts +29 -0
  39. package/dist/index.js +46 -0
  40. package/package.json +73 -0
@@ -0,0 +1,100 @@
1
+ import type { InitializeParams, InitializeResult } from 'vscode-languageserver-protocol';
2
+ /**
3
+ * Resolves the absolute path of `svelte-language-server`'s `bin/server.js`.
4
+ *
5
+ * The package's `exports` map intentionally exposes `./bin/server.js`, so a
6
+ * plain module resolution works from this package's location — the standalone
7
+ * and Zed scenarios, where the server runs out of `node_modules`.
8
+ *
9
+ * A host that has bundled `svelte-language-server` to a sibling file (the VS
10
+ * Code extension does exactly this — see `packages/vscode-sveltex`) cannot rely
11
+ * on `node_modules` existing and passes the bundled file's absolute path
12
+ * explicitly via `override`.
13
+ *
14
+ * @param override - An explicit absolute path to use instead of resolving from
15
+ * `node_modules`. When given, it is returned verbatim.
16
+ * @throws If `override` is omitted and `svelte-language-server` is not
17
+ * installed.
18
+ */
19
+ export declare function resolveSvelteServerPath(override?: string): string;
20
+ /**
21
+ * Handlers the host server registers with a {@link SvelteProxy} so that
22
+ * messages _originating_ in the child (diagnostics, log messages, ...) can be
23
+ * routed back out.
24
+ *
25
+ * Both handlers are required (the host always wants to know about
26
+ * `publishDiagnostics`, `window/logMessage`, `client/registerCapability`,
27
+ * etc.). `LspProxyHandlers` has them optional, since not every child needs
28
+ * them.
29
+ */
30
+ export interface SvelteProxyHandlers {
31
+ /**
32
+ * Invoked for every notification the child sends (e.g.
33
+ * `textDocument/publishDiagnostics`, `window/logMessage`).
34
+ */
35
+ onNotification: (method: string, params: unknown) => void;
36
+ /**
37
+ * Invoked for every server-to-client request the child sends (e.g.
38
+ * `client/registerCapability`, `workspace/configuration`). The returned
39
+ * value is sent back as the response.
40
+ */
41
+ onRequest: (method: string, params: unknown) => Promise<unknown>;
42
+ }
43
+ /**
44
+ * A live connection to a child `svelte-language-server` process.
45
+ *
46
+ * Lifecycle: {@link setServerPath} optionally points at a bundled
47
+ * `bin/server.js`; {@link start} spawns the child and performs the LSP
48
+ * `initialize` handshake; {@link sendRequest} / {@link sendNotification}
49
+ * forward messages to it; {@link stop} shuts it down gracefully.
50
+ */
51
+ export declare class SvelteProxy {
52
+ #private;
53
+ constructor(handlers: SvelteProxyHandlers);
54
+ /**
55
+ * Overrides the location of the child `svelte-language-server`.
56
+ *
57
+ * Standalone use (Zed, the CLI) needs no override — the server is
58
+ * resolved from `node_modules`. A host that has bundled the server to a
59
+ * sibling file (the VS Code extension) calls this with that file's
60
+ * absolute path before {@link start}, since `node_modules` will not
61
+ * exist at runtime.
62
+ *
63
+ * @param serverPath - Absolute path of the child's `bin/server.js`, or
64
+ * `undefined` to keep resolving from `node_modules`.
65
+ */
66
+ setServerPath(serverPath: string | undefined): void;
67
+ /** The `InitializeResult` returned by the child, available after `start`. */
68
+ get initializeResult(): InitializeResult | undefined;
69
+ /** Whether the child process is running and initialized. */
70
+ get isRunning(): boolean;
71
+ /**
72
+ * Forks the child `svelte-language-server` and completes the
73
+ * `initialize` handshake.
74
+ *
75
+ * @param initializeParams - The `initialize` params received by the host
76
+ * server, forwarded to the child mostly unchanged (the child's root /
77
+ * capabilities should match the host's so its TypeScript service
78
+ * resolves the project correctly).
79
+ * @returns The child's `InitializeResult`.
80
+ */
81
+ start(initializeParams: InitializeParams): Promise<InitializeResult>;
82
+ /**
83
+ * Forwards a request to the child and resolves with its response.
84
+ *
85
+ * @throws If the proxy is not running.
86
+ */
87
+ sendRequest<R = unknown>(method: string, params: unknown): Promise<R>;
88
+ /**
89
+ * Forwards a notification to the child.
90
+ *
91
+ * No-op if the proxy is not running (notifications are fire-and-forget
92
+ * and a not-yet-started child must not crash the host).
93
+ */
94
+ sendNotification(method: string, params: unknown): Promise<void>;
95
+ /**
96
+ * Gracefully shuts the child down: LSP `shutdown` + `exit`, then
97
+ * disposes the connection and kills the process if it has not exited.
98
+ */
99
+ stop(): Promise<void>;
100
+ }
@@ -0,0 +1,144 @@
1
+ // File description: `SvelteProxy` — a thin {@link LspProxy} adaptor for the
2
+ // embedded `svelte-language-server`.
3
+ //
4
+ // The mechanics of "spawn a child language server over stdio, do the
5
+ // `initialize` handshake, forward messages, shut down cleanly" live in
6
+ // `LspProxy` (used by the math language server and TexLab as well).
7
+ // `SvelteProxy` keeps two pieces of `svelte-language-server`-specific
8
+ // behaviour around that generic core:
9
+ //
10
+ // 1. `resolveSvelteServerPath` — locating `bin/server.js` (from a host
11
+ // override or from `node_modules`), and
12
+ // 2. an API that lets the path be supplied *after* construction but before
13
+ // `start()` (via {@link setServerPath}). That separation is what lets the
14
+ // VS Code extension, which bundles the server to a sibling file, pass
15
+ // the bundled path in `initializationOptions` once it arrives.
16
+ //
17
+ // Per-request forwarding (`sendRequest`, `sendNotification`, `stop`, …) is
18
+ // delegated straight through.
19
+ //
20
+ // `svelte-language-server` is not a clean embeddable library: its npm
21
+ // `exports` expose only `.` and `./bin/server.js`, and it drives
22
+ // TypeScript/CSS/HTML semantics itself. The official Svelte VS Code
23
+ // extension therefore spawns `bin/server.js` as a child process and proxies
24
+ // over JSON-RPC; we do the same.
25
+ import { createRequire } from 'node:module';
26
+ import { LspProxy } from './lsp-proxy.js';
27
+ /**
28
+ * Resolves the absolute path of `svelte-language-server`'s `bin/server.js`.
29
+ *
30
+ * The package's `exports` map intentionally exposes `./bin/server.js`, so a
31
+ * plain module resolution works from this package's location — the standalone
32
+ * and Zed scenarios, where the server runs out of `node_modules`.
33
+ *
34
+ * A host that has bundled `svelte-language-server` to a sibling file (the VS
35
+ * Code extension does exactly this — see `packages/vscode-sveltex`) cannot rely
36
+ * on `node_modules` existing and passes the bundled file's absolute path
37
+ * explicitly via `override`.
38
+ *
39
+ * @param override - An explicit absolute path to use instead of resolving from
40
+ * `node_modules`. When given, it is returned verbatim.
41
+ * @throws If `override` is omitted and `svelte-language-server` is not
42
+ * installed.
43
+ */
44
+ export function resolveSvelteServerPath(override) {
45
+ if (override)
46
+ return override;
47
+ const require = createRequire(import.meta.url);
48
+ return require.resolve('svelte-language-server/bin/server.js');
49
+ }
50
+ /**
51
+ * A live connection to a child `svelte-language-server` process.
52
+ *
53
+ * Lifecycle: {@link setServerPath} optionally points at a bundled
54
+ * `bin/server.js`; {@link start} spawns the child and performs the LSP
55
+ * `initialize` handshake; {@link sendRequest} / {@link sendNotification}
56
+ * forward messages to it; {@link stop} shuts it down gracefully.
57
+ */
58
+ export class SvelteProxy {
59
+ #handlers;
60
+ /**
61
+ * An explicit `svelte-language-server` `bin/server.js` path, or
62
+ * `undefined` to resolve it from `node_modules`. Set via
63
+ * {@link setServerPath} before {@link start}. See
64
+ * {@link resolveSvelteServerPath}.
65
+ */
66
+ #serverPathOverride;
67
+ #inner;
68
+ constructor(handlers) {
69
+ this.#handlers = handlers;
70
+ }
71
+ /**
72
+ * Overrides the location of the child `svelte-language-server`.
73
+ *
74
+ * Standalone use (Zed, the CLI) needs no override — the server is
75
+ * resolved from `node_modules`. A host that has bundled the server to a
76
+ * sibling file (the VS Code extension) calls this with that file's
77
+ * absolute path before {@link start}, since `node_modules` will not
78
+ * exist at runtime.
79
+ *
80
+ * @param serverPath - Absolute path of the child's `bin/server.js`, or
81
+ * `undefined` to keep resolving from `node_modules`.
82
+ */
83
+ setServerPath(serverPath) {
84
+ this.#serverPathOverride = serverPath;
85
+ }
86
+ /** The `InitializeResult` returned by the child, available after `start`. */
87
+ get initializeResult() {
88
+ return this.#inner?.initializeResult;
89
+ }
90
+ /** Whether the child process is running and initialized. */
91
+ get isRunning() {
92
+ return this.#inner?.isRunning ?? false;
93
+ }
94
+ /**
95
+ * Forks the child `svelte-language-server` and completes the
96
+ * `initialize` handshake.
97
+ *
98
+ * @param initializeParams - The `initialize` params received by the host
99
+ * server, forwarded to the child mostly unchanged (the child's root /
100
+ * capabilities should match the host's so its TypeScript service
101
+ * resolves the project correctly).
102
+ * @returns The child's `InitializeResult`.
103
+ */
104
+ async start(initializeParams) {
105
+ const serverPath = resolveSvelteServerPath(this.#serverPathOverride);
106
+ // Pass `--stdio` and forward the handlers verbatim; `LspProxyHandlers`
107
+ // accepts the (more permissive) optional shape, but
108
+ // `SvelteProxyHandlers` always provides both, so nothing is lost.
109
+ this.#inner = new LspProxy({ kind: 'fork', module: serverPath, args: ['--stdio'] }, 'svelte-language-server', this.#handlers);
110
+ return this.#inner.start(initializeParams);
111
+ }
112
+ /**
113
+ * Forwards a request to the child and resolves with its response.
114
+ *
115
+ * @throws If the proxy is not running.
116
+ */
117
+ async sendRequest(method, params) {
118
+ if (!this.#inner) {
119
+ throw new Error('SvelteProxy is not running.');
120
+ }
121
+ return this.#inner.sendRequest(method, params);
122
+ }
123
+ /**
124
+ * Forwards a notification to the child.
125
+ *
126
+ * No-op if the proxy is not running (notifications are fire-and-forget
127
+ * and a not-yet-started child must not crash the host).
128
+ */
129
+ async sendNotification(method, params) {
130
+ if (!this.#inner)
131
+ return;
132
+ await this.#inner.sendNotification(method, params);
133
+ }
134
+ /**
135
+ * Gracefully shuts the child down: LSP `shutdown` + `exit`, then
136
+ * disposes the connection and kills the process if it has not exited.
137
+ */
138
+ async stop() {
139
+ const inner = this.#inner;
140
+ this.#inner = undefined;
141
+ if (inner)
142
+ await inner.stop();
143
+ }
144
+ }
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Searches for the TexLab executable — on `PATH`, then in well-known install
3
+ * directories.
4
+ *
5
+ * @param env - The environment to read `PATH` / `PATHEXT` / `HOME` from.
6
+ * Defaults to `process.env`; injectable so tests can simulate texlab being
7
+ * present or absent without touching the real environment.
8
+ * @param extraDirs - Directories searched after `PATH`. Defaults to
9
+ * {@link wellKnownTexlabDirs}; tests pass `[]` to confine the search to `env`.
10
+ * @returns The absolute path of the `texlab` executable, or `undefined` if it
11
+ * cannot be found.
12
+ *
13
+ * @remarks
14
+ * The lookup is cross-platform: on Windows every `PATHEXT` extension is tried,
15
+ * on POSIX the bare name. A directory that does not exist, or a candidate that
16
+ * is not executable, is simply skipped — detection never throws.
17
+ */
18
+ export declare function findTexlab(env?: NodeJS.ProcessEnv, extraDirs?: readonly string[]): string | undefined;
19
+ /**
20
+ * Whether TexLab is available on this machine.
21
+ *
22
+ * @param env - Optional environment override (see {@link findTexlab}).
23
+ * @param extraDirs - Optional extra-directories override (see
24
+ * {@link findTexlab}).
25
+ */
26
+ export declare function isTexlabAvailable(env?: NodeJS.ProcessEnv, extraDirs?: readonly string[]): boolean;
@@ -0,0 +1,121 @@
1
+ // File description: Robust, cross-platform detection of the TexLab binary.
2
+ //
3
+ // TexLab (https://github.com/latex-lsp/texlab) is a full LaTeX language server
4
+ // shipped as a standalone native executable. When it is installed, the SvelTeX
5
+ // language server forwards hover/completion/... within LaTeX verbatim regions
6
+ // to a spawned `texlab` child. When it is NOT installed, that forwarding is
7
+ // skipped silently — no error, no crash. This module answers the one question
8
+ // that drives that decision: "where is the `texlab` executable?".
9
+ //
10
+ // `PATH` is searched first (split by the platform delimiter; on Windows every
11
+ // `PATHEXT` extension is tried). It is then backed up by a list of well-known
12
+ // install directories: an editor launched from a macOS Dock / Finder, or via a
13
+ // Linux desktop launcher, inherits only a minimal `PATH` — often just
14
+ // `/usr/bin:/bin:/usr/sbin:/sbin` — that omits `/usr/local/bin`, Homebrew and
15
+ // `~/.cargo/bin`, exactly where `texlab` tends to live. Each candidate is
16
+ // probed with `fs.accessSync(..., X_OK)`; detection never throws.
17
+ import { accessSync, constants } from 'node:fs';
18
+ import { delimiter, join } from 'node:path';
19
+ /** The base name of the TexLab executable (no extension). */
20
+ const TEXLAB_BASENAME = 'texlab';
21
+ /** Whether the host platform is Windows. */
22
+ function isWindows() {
23
+ return process.platform === 'win32';
24
+ }
25
+ /** The fallback Windows executable extensions when `PATHEXT` is unset. */
26
+ const DEFAULT_PATHEXT = ['.EXE', '.CMD', '.BAT', '.COM'];
27
+ /**
28
+ * The executable file extensions to try for a bare command name.
29
+ *
30
+ * On Windows a command may be `texlab.exe`, `texlab.cmd`, ...; the set comes
31
+ * from `PATHEXT`. On POSIX there is no extension, so a single empty string is
32
+ * used.
33
+ *
34
+ * @param env - The environment to read `PATHEXT` from.
35
+ */
36
+ function executableExtensions(env) {
37
+ if (!isWindows())
38
+ return [''];
39
+ const pathext = env['PATHEXT'];
40
+ if (!pathext)
41
+ return [...DEFAULT_PATHEXT];
42
+ return pathext.split(';').filter((ext) => ext.length > 0);
43
+ }
44
+ /** Whether `file` exists and is executable. */
45
+ function isExecutable(file) {
46
+ try {
47
+ accessSync(file, constants.X_OK);
48
+ return true;
49
+ }
50
+ catch {
51
+ return false;
52
+ }
53
+ }
54
+ /**
55
+ * Directories `texlab` is commonly installed into but which a GUI-launched
56
+ * editor's `PATH` frequently omits.
57
+ *
58
+ * Searched _after_ `PATH`, as a fallback. Empty on Windows, where the usual
59
+ * installers (scoop, winget, choco) reliably amend `PATH`.
60
+ *
61
+ * @param env - The environment, read for `HOME` to locate `~/.cargo/bin` (the
62
+ * `cargo install texlab` target) and `~/.local/bin`.
63
+ */
64
+ function wellKnownTexlabDirs(env) {
65
+ if (isWindows())
66
+ return [];
67
+ const dirs = [
68
+ '/opt/homebrew/bin', // Homebrew, Apple silicon
69
+ '/opt/homebrew/sbin',
70
+ '/usr/local/bin', // Homebrew (Intel) / manual installs
71
+ '/usr/local/sbin',
72
+ '/opt/local/bin', // MacPorts
73
+ ];
74
+ const home = env['HOME'];
75
+ if (home) {
76
+ dirs.push(join(home, '.cargo', 'bin'), join(home, '.local', 'bin'));
77
+ }
78
+ return dirs;
79
+ }
80
+ /**
81
+ * Searches for the TexLab executable — on `PATH`, then in well-known install
82
+ * directories.
83
+ *
84
+ * @param env - The environment to read `PATH` / `PATHEXT` / `HOME` from.
85
+ * Defaults to `process.env`; injectable so tests can simulate texlab being
86
+ * present or absent without touching the real environment.
87
+ * @param extraDirs - Directories searched after `PATH`. Defaults to
88
+ * {@link wellKnownTexlabDirs}; tests pass `[]` to confine the search to `env`.
89
+ * @returns The absolute path of the `texlab` executable, or `undefined` if it
90
+ * cannot be found.
91
+ *
92
+ * @remarks
93
+ * The lookup is cross-platform: on Windows every `PATHEXT` extension is tried,
94
+ * on POSIX the bare name. A directory that does not exist, or a candidate that
95
+ * is not executable, is simply skipped — detection never throws.
96
+ */
97
+ export function findTexlab(env = process.env, extraDirs = wellKnownTexlabDirs(env)) {
98
+ // `PATH` casing varies (Windows uses `Path`); accept the common forms.
99
+ const pathValue = env['PATH'] ?? env['Path'] ?? env['path'] ?? '';
100
+ const extensions = executableExtensions(env);
101
+ for (const dir of [...pathValue.split(delimiter), ...extraDirs]) {
102
+ if (dir.length === 0)
103
+ continue;
104
+ for (const ext of extensions) {
105
+ const candidate = join(dir, `${TEXLAB_BASENAME}${ext}`);
106
+ if (isExecutable(candidate))
107
+ return candidate;
108
+ }
109
+ }
110
+ return undefined;
111
+ }
112
+ /**
113
+ * Whether TexLab is available on this machine.
114
+ *
115
+ * @param env - Optional environment override (see {@link findTexlab}).
116
+ * @param extraDirs - Optional extra-directories override (see
117
+ * {@link findTexlab}).
118
+ */
119
+ export function isTexlabAvailable(env, extraDirs) {
120
+ return findTexlab(env, extraDirs) !== undefined;
121
+ }
@@ -0,0 +1,32 @@
1
+ import { type Region } from './regions.js';
2
+ import { type Mapping } from './mapping.js';
3
+ import { SourceMap } from './mapper.js';
4
+ /** The result of {@link buildVirtualSvelte}. */
5
+ export interface VirtualSvelteDocument {
6
+ /** Full text of the generated `.svelte` document. */
7
+ text: string;
8
+ /** Span pairs linking the generated document to the source document. */
9
+ mappings: Mapping[];
10
+ /** A ready-to-use bidirectional mapper over {@link mappings}. */
11
+ sourceMap: SourceMap;
12
+ }
13
+ /**
14
+ * Builds the virtual `.svelte` document for a `.sveltex` source file.
15
+ *
16
+ * @param source - Full text of the `.sveltex` document.
17
+ * @param regions - The document's regions, as returned by `computeRegions`.
18
+ * Must tile `[0, source.length)` gap-free (which `computeRegions` guarantees).
19
+ * @returns The generated text, the mappings, and a {@link SourceMap}.
20
+ *
21
+ * @remarks
22
+ * Delegated content reaches the Svelte language server unchanged, so Svelte /
23
+ * HTML / TypeScript / CSS diagnostics and IntelliSense work exactly as they
24
+ * would in a real `.svelte` file. Non-delegated content is invisible to that
25
+ * server (it sees only blanks), which is what stops it from, say, reporting
26
+ * "unexpected token" inside a block of LaTeX.
27
+ *
28
+ * TODO (phase 2): expand Markdown regions to the HTML the Svelte compiler will
29
+ * actually see, emitting non-identity mappings; the `Mapping` model and
30
+ * {@link SourceMap} already support differing span lengths.
31
+ */
32
+ export declare function buildVirtualSvelte(source: string, regions: Region[]): VirtualSvelteDocument;
@@ -0,0 +1,67 @@
1
+ // File description: Builds the virtual `.svelte` document that is handed to the
2
+ // embedded Svelte language server, together with the `Mapping[]` that links it
3
+ // back to the source `.sveltex` file.
4
+ //
5
+ // Strategy (v1): delegated regions (plain Markdown/HTML, Svelte markup, mustache
6
+ // tags) are copied byte-for-byte and get an identity `Mapping`; non-delegated
7
+ // regions (verbatim / code / math / frontmatter) are replaced by an equal-length
8
+ // run of whitespace — newlines preserved so line numbers stay aligned — and get
9
+ // NO mapping. Because every region keeps its exact length, the virtual document
10
+ // is the same length as the source, which keeps the geometry trivially correct.
11
+ import { isDelegated } from './regions.js';
12
+ import { identityMapping } from './mapping.js';
13
+ import { SourceMap } from './mapper.js';
14
+ /**
15
+ * Replaces every non-whitespace character of `slice` with a space, leaving
16
+ * `\r` and `\n` untouched.
17
+ *
18
+ * Keeping the line breaks means a caret on line N of the source sits on line N
19
+ * of the virtual document too, so even the (unmapped) blanked regions do not
20
+ * shift anything below them.
21
+ */
22
+ function blankOut(slice) {
23
+ return slice.replace(/[^\r\n]/gu, ' ');
24
+ }
25
+ /**
26
+ * Builds the virtual `.svelte` document for a `.sveltex` source file.
27
+ *
28
+ * @param source - Full text of the `.sveltex` document.
29
+ * @param regions - The document's regions, as returned by `computeRegions`.
30
+ * Must tile `[0, source.length)` gap-free (which `computeRegions` guarantees).
31
+ * @returns The generated text, the mappings, and a {@link SourceMap}.
32
+ *
33
+ * @remarks
34
+ * Delegated content reaches the Svelte language server unchanged, so Svelte /
35
+ * HTML / TypeScript / CSS diagnostics and IntelliSense work exactly as they
36
+ * would in a real `.svelte` file. Non-delegated content is invisible to that
37
+ * server (it sees only blanks), which is what stops it from, say, reporting
38
+ * "unexpected token" inside a block of LaTeX.
39
+ *
40
+ * TODO (phase 2): expand Markdown regions to the HTML the Svelte compiler will
41
+ * actually see, emitting non-identity mappings; the `Mapping` model and
42
+ * {@link SourceMap} already support differing span lengths.
43
+ */
44
+ export function buildVirtualSvelte(source, regions) {
45
+ const chunks = [];
46
+ const mappings = [];
47
+ let generatedOffset = 0;
48
+ for (const region of regions) {
49
+ const slice = source.slice(region.sourceStart, region.sourceEnd);
50
+ if (isDelegated(region.kind)) {
51
+ // Copy verbatim and record an identity mapping.
52
+ chunks.push(slice);
53
+ mappings.push(identityMapping(region.sourceStart, generatedOffset, slice.length));
54
+ }
55
+ else {
56
+ // Blank out; emit no mapping so requests here are dropped.
57
+ chunks.push(blankOut(slice));
58
+ }
59
+ generatedOffset += slice.length;
60
+ }
61
+ const text = chunks.join('');
62
+ return {
63
+ text,
64
+ mappings,
65
+ sourceMap: SourceMap.create(mappings, source, text),
66
+ };
67
+ }
@@ -0,0 +1,29 @@
1
+ export { createServer } from './core/server.js';
2
+ export type { Region, RegionKind } from './core/regions.js';
3
+ export { computeRegions, isDelegated } from './core/regions.js';
4
+ export type { Mapping, MappingFeatures } from './core/mapping.js';
5
+ export { SourceMap } from './core/mapper.js';
6
+ export type { MapDirection } from './core/mapper.js';
7
+ export { buildVirtualSvelte } from './core/virtual-svelte.js';
8
+ export type { VirtualSvelteDocument } from './core/virtual-svelte.js';
9
+ export type { MathBackend, SveltexConfigSnapshot } from './core/config.js';
10
+ export { defaultConfigSnapshot, loadConfigSnapshot } from './core/config.js';
11
+ export { buildRegionVirtualDocument, type RegionVirtualDocument, } from './core/region-virtual.js';
12
+ export { findTexlab, isTexlabAvailable } from './core/texlab.js';
13
+ export { RegionForwarder, isLatexVerbatimRegion, } from './core/region-forwarding.js';
14
+ export { LspProxy, type LspSpawnSpec } from './core/lsp-proxy.js';
15
+ /**
16
+ * Starts the SvelTeX language server over stdio.
17
+ *
18
+ * Creates an LSP connection bound to the current process's standard streams,
19
+ * wires it up with {@link createServer}, and begins listening. The returned
20
+ * promise never resolves under normal operation — the process lives as long as
21
+ * the connection does.
22
+ *
23
+ * @remarks
24
+ * Editors that spawn the server as a child process and talk LSP over stdio
25
+ * (the Zed extension will do exactly this) need only run `bin/server.js`, which
26
+ * calls this function. The VS Code extension instead runs the server over an
27
+ * IPC transport via `vscode-languageclient`, but still through `bin/server.js`.
28
+ */
29
+ export declare function startServer(): void;
package/dist/index.js ADDED
@@ -0,0 +1,46 @@
1
+ // File description: Public entry point of `@nvl/sveltex-language-server`.
2
+ //
3
+ // Two things are exported:
4
+ //
5
+ // - `createServer(connection)` — the transport-agnostic core. A host that
6
+ // already owns an LSP `Connection` (e.g. the VS Code extension running the
7
+ // server in-process, or a test harness) calls this directly.
8
+ //
9
+ // - `startServer()` — a stdio convenience wrapper: it creates a connection on
10
+ // `process.stdin`/`process.stdout`, hands it to `createServer`, and starts
11
+ // listening. This is what `bin/server.js` invokes, and what any editor
12
+ // (VS Code, Zed, ...) gets when it launches the binary over stdio.
13
+ // The Node-flavoured `createConnection(options?)` lives in the package's node
14
+ // entry point. `vscode-languageserver`'s `typings` field points at the
15
+ // _common_ (browser) API, so importing the concrete node file path is what
16
+ // surfaces the correct overload under `Node16` module resolution.
17
+ import { ProposedFeatures, createConnection, } from 'vscode-languageserver/lib/node/main.js';
18
+ import { createServer } from './core/server.js';
19
+ export { createServer } from './core/server.js';
20
+ export { computeRegions, isDelegated } from './core/regions.js';
21
+ export { SourceMap } from './core/mapper.js';
22
+ export { buildVirtualSvelte } from './core/virtual-svelte.js';
23
+ export { defaultConfigSnapshot, loadConfigSnapshot } from './core/config.js';
24
+ export { buildRegionVirtualDocument, } from './core/region-virtual.js';
25
+ export { findTexlab, isTexlabAvailable } from './core/texlab.js';
26
+ export { RegionForwarder, isLatexVerbatimRegion, } from './core/region-forwarding.js';
27
+ export { LspProxy } from './core/lsp-proxy.js';
28
+ /**
29
+ * Starts the SvelTeX language server over stdio.
30
+ *
31
+ * Creates an LSP connection bound to the current process's standard streams,
32
+ * wires it up with {@link createServer}, and begins listening. The returned
33
+ * promise never resolves under normal operation — the process lives as long as
34
+ * the connection does.
35
+ *
36
+ * @remarks
37
+ * Editors that spawn the server as a child process and talk LSP over stdio
38
+ * (the Zed extension will do exactly this) need only run `bin/server.js`, which
39
+ * calls this function. The VS Code extension instead runs the server over an
40
+ * IPC transport via `vscode-languageclient`, but still through `bin/server.js`.
41
+ */
42
+ export function startServer() {
43
+ const connection = createConnection(ProposedFeatures.all);
44
+ createServer(connection);
45
+ connection.listen();
46
+ }
package/package.json ADDED
@@ -0,0 +1,73 @@
1
+ {
2
+ "name": "@nvl/sveltex-language-server",
3
+ "version": "0.2.0",
4
+ "description": "Language Server Protocol implementation for SvelTeX (Svelte + Markdown + LaTeX)",
5
+ "type": "module",
6
+ "license": "MIT",
7
+ "author": {
8
+ "name": "N. V. Lang",
9
+ "email": "npm@nvlang.dev",
10
+ "url": "https://nvlang.dev/"
11
+ },
12
+ "repository": {
13
+ "type": "git",
14
+ "url": "git+https://github.com/nvlang/sveltex.git",
15
+ "directory": "packages/sveltex-language-server"
16
+ },
17
+ "bugs": {
18
+ "url": "https://github.com/nvlang/sveltex/issues"
19
+ },
20
+ "homepage": "https://sveltex.dev",
21
+ "keywords": [
22
+ "sveltex",
23
+ "svelte",
24
+ "markdown",
25
+ "latex",
26
+ "lsp",
27
+ "language-server"
28
+ ],
29
+ "main": "./dist/index.js",
30
+ "types": "./dist/index.d.ts",
31
+ "exports": {
32
+ ".": {
33
+ "types": "./dist/index.d.ts",
34
+ "default": "./dist/index.js"
35
+ },
36
+ "./bin/server.js": "./bin/server.js",
37
+ "./package.json": "./package.json"
38
+ },
39
+ "bin": {
40
+ "sveltex-language-server": "bin/server.js"
41
+ },
42
+ "files": [
43
+ "dist",
44
+ "bin",
45
+ "package.json",
46
+ "LICENSE",
47
+ "README.md"
48
+ ],
49
+ "engines": {
50
+ "node": ">=16"
51
+ },
52
+ "dependencies": {
53
+ "svelte-language-server": "^0.18.0",
54
+ "vscode-languageserver": "^9.0.1",
55
+ "vscode-languageserver-protocol": "^3.17.5",
56
+ "vscode-languageserver-textdocument": "^1.0.12",
57
+ "vscode-uri": "^3.1.0",
58
+ "@nvl/sveltex": "0.5.0",
59
+ "@nvl/sveltex-math-language-server": "0.2.0"
60
+ },
61
+ "devDependencies": {
62
+ "@types/node": "^24.10.15",
63
+ "tslib": "^2.8.1",
64
+ "typescript": "^6.0.3",
65
+ "vitest": "^4.1.6"
66
+ },
67
+ "scripts": {
68
+ "clean": "rimraf coverage dist tmp",
69
+ "build": "pnpm run clean && tsc -p tsconfig.json",
70
+ "lint": "eslint . && tsc -p tsconfig.json --noEmit",
71
+ "test": "vitest run"
72
+ }
73
+ }