@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.
- package/LICENSE +21 -0
- package/README.md +288 -0
- package/bin/server.js +10 -0
- package/dist/core/config.d.ts +126 -0
- package/dist/core/config.js +569 -0
- package/dist/core/diagnostics.d.ts +34 -0
- package/dist/core/diagnostics.js +67 -0
- package/dist/core/frontmatter-data.d.ts +74 -0
- package/dist/core/frontmatter-data.js +323 -0
- package/dist/core/frontmatter.d.ts +25 -0
- package/dist/core/frontmatter.js +348 -0
- package/dist/core/lsp-proxy.d.ts +77 -0
- package/dist/core/lsp-proxy.js +165 -0
- package/dist/core/mapper.d.ts +86 -0
- package/dist/core/mapper.js +223 -0
- package/dist/core/mapping.d.ts +59 -0
- package/dist/core/mapping.js +37 -0
- package/dist/core/markdown.d.ts +34 -0
- package/dist/core/markdown.js +215 -0
- package/dist/core/region-forwarding.d.ts +90 -0
- package/dist/core/region-forwarding.js +428 -0
- package/dist/core/region-virtual.d.ts +71 -0
- package/dist/core/region-virtual.js +131 -0
- package/dist/core/regions.d.ts +56 -0
- package/dist/core/regions.js +221 -0
- package/dist/core/remap.d.ts +84 -0
- package/dist/core/remap.js +272 -0
- package/dist/core/server-helpers.d.ts +109 -0
- package/dist/core/server-helpers.js +182 -0
- package/dist/core/server.d.ts +13 -0
- package/dist/core/server.js +604 -0
- package/dist/core/svelte-proxy.d.ts +100 -0
- package/dist/core/svelte-proxy.js +144 -0
- package/dist/core/texlab.d.ts +26 -0
- package/dist/core/texlab.js +121 -0
- package/dist/core/virtual-svelte.d.ts +32 -0
- package/dist/core/virtual-svelte.js +67 -0
- package/dist/index.d.ts +29 -0
- package/dist/index.js +46 -0
- 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
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -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
|
+
}
|