@treelsp/cli 0.0.2 → 0.0.4

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 (3) hide show
  1. package/README.md +50 -22
  2. package/dist/index.js +420 -102
  3. package/package.json +2 -2
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @treelsp/cli
2
2
 
3
- CLI tool for [treelsp](https://github.com/dhrubomoy/treelsp) — generate Tree-sitter grammars, WASM parsers, and language servers from TypeScript definitions.
3
+ CLI tool for [treelsp](https://github.com/dhrubomoy/treelsp) — generate parsers and language servers from TypeScript grammar definitions. Supports Tree-sitter and Lezer backends.
4
4
 
5
5
  ## Installation
6
6
 
@@ -20,15 +20,15 @@ Scaffold a new language project interactively.
20
20
  treelsp init
21
21
  ```
22
22
 
23
- Prompts for a language name and file extension, then creates a project directory with:
24
- - `grammar.ts` language definition template
25
- - `package.json`dependencies and scripts
26
- - `tsconfig.json` — TypeScript configuration
27
- - `.gitignore`
23
+ Prompts for a language name, file extension, and parser backend (Tree-sitter or Lezer), then creates a pnpm monorepo with two packages:
24
+ - `packages/language/` — grammar definition (`grammar.ts`) and generated files
25
+ - `packages/extension/`VS Code extension that launches the language server
26
+
27
+ Also generates root config files (`treelsp-config.json`, `pnpm-workspace.yaml`, `.vscode/launch.json`).
28
28
 
29
29
  ### `treelsp generate`
30
30
 
31
- Generate grammar.js, AST types, manifest, and syntax highlighting queries from a language definition.
31
+ Generate grammar files, AST types, manifest, and syntax highlighting queries from a language definition.
32
32
 
33
33
  ```
34
34
  Usage: treelsp generate [options]
@@ -38,16 +38,26 @@ Options:
38
38
  -w, --watch watch for changes
39
39
  ```
40
40
 
41
- **Output files** (written to the output directory, default `generated/`):
41
+ **Output files** depend on the backend:
42
+
43
+ **Tree-sitter** (default, written to `generated/`):
42
44
  - `grammar.js` — Tree-sitter grammar (CommonJS)
43
45
  - `ast.ts` — typed AST interfaces
44
46
  - `treelsp.json` — manifest for VS Code extension discovery
47
+ - `syntax.tmLanguage.json` — TextMate grammar
45
48
  - `queries/highlights.scm` — Tree-sitter syntax highlighting
46
49
  - `queries/locals.scm` — Tree-sitter scope/locals
47
50
 
51
+ **Lezer** (written to `generated-lezer/` or custom `out` path):
52
+ - `grammar.lezer` — Lezer grammar specification
53
+ - `parser-meta.json` — field/node metadata
54
+ - `ast.ts` — typed AST interfaces
55
+ - `treelsp.json` — manifest for VS Code extension discovery
56
+ - `syntax.tmLanguage.json` — TextMate grammar
57
+
48
58
  ### `treelsp build`
49
59
 
50
- Compile the generated grammar to WASM and bundle the language server.
60
+ Compile the generated grammar and bundle the language server.
51
61
 
52
62
  ```
53
63
  Usage: treelsp build [options]
@@ -56,13 +66,22 @@ Options:
56
66
  -f, --file <file> path to treelsp-config.json or package.json with treelsp field
57
67
  ```
58
68
 
59
- Requires the [tree-sitter CLI](https://github.com/tree-sitter/tree-sitter/blob/master/cli/README.md) to be installed (`npm install -g tree-sitter-cli` or `cargo install tree-sitter-cli`).
69
+ **Tree-sitter backend** requires the [tree-sitter CLI](https://github.com/tree-sitter/tree-sitter/blob/master/cli/README.md) (`npm install -g tree-sitter-cli` or `cargo install tree-sitter-cli`).
70
+
71
+ **Lezer backend** requires no external tools (pure JavaScript compilation).
60
72
 
61
73
  **Output files:**
74
+
75
+ Tree-sitter:
62
76
  - `grammar.wasm` — compiled WebAssembly parser
63
77
  - `server.bundle.cjs` — self-contained language server bundle
64
78
  - `tree-sitter.wasm` — web-tree-sitter runtime
65
79
 
80
+ Lezer:
81
+ - `parser.js` — compiled Lezer parser
82
+ - `parser.bundle.js` — self-contained parser bundle (includes @lezer/lr)
83
+ - `server.bundle.cjs` — self-contained language server bundle
84
+
66
85
  ### `treelsp watch`
67
86
 
68
87
  Watch mode — re-runs `generate` + `build` automatically when grammar files change.
@@ -76,7 +95,7 @@ Options:
76
95
 
77
96
  ## Configuration
78
97
 
79
- By default, treelsp looks for `grammar.ts` in the current directory and outputs to `generated/`. For multi-language projects, create a config file to specify all languages.
98
+ By default, treelsp looks for `grammar.ts` in the current directory and outputs to `generated/` using the Tree-sitter backend. For multi-language or multi-backend projects, create a config file.
80
99
 
81
100
  ### Config discovery order
82
101
 
@@ -91,8 +110,8 @@ When `-f` is not provided, the CLI searches from the current directory upward fo
91
110
  ```json
92
111
  {
93
112
  "languages": [
94
- { "grammar": "mini-lang/grammar.ts" },
95
- { "grammar": "schema-lang/grammar.ts" }
113
+ { "grammar": "packages/language/grammar.ts" },
114
+ { "grammar": "packages/language/grammar.ts", "backend": "lezer", "out": "packages/language/generated-lezer" }
96
115
  ]
97
116
  }
98
117
  ```
@@ -116,24 +135,33 @@ When `-f` is not provided, the CLI searches from the current directory upward fo
116
135
  | `languages` | array | yes | List of language projects |
117
136
  | `languages[].grammar` | string | yes | Path to `grammar.ts`, relative to the config file |
118
137
  | `languages[].out` | string | no | Output directory (default: `<grammar dir>/generated`) |
138
+ | `languages[].backend` | string | no | Parser backend: `"tree-sitter"` (default) or `"lezer"` |
119
139
 
120
140
  ## Typical Workflow
121
141
 
122
- **Single language (no config needed):**
142
+ **New project:**
123
143
 
124
144
  ```bash
125
- treelsp init # scaffold project
145
+ treelsp init # scaffold project with language + extension packages
126
146
  cd my-lang
127
- npm install
128
- treelsp generate # generate grammar.js, ast.ts, etc.
129
- treelsp build # compile WASM + bundle server
147
+ pnpm install
148
+ pnpm build # generate + compile + bundle
149
+ # Press F5 in VS Code to launch the extension
130
150
  ```
131
151
 
132
- **Multi-language monorepo:**
152
+ **Generate for both backends:**
153
+
154
+ ```json
155
+ {
156
+ "languages": [
157
+ { "grammar": "packages/language/grammar.ts" },
158
+ { "grammar": "packages/language/grammar.ts", "backend": "lezer", "out": "packages/language/generated-lezer" }
159
+ ]
160
+ }
161
+ ```
133
162
 
134
163
  ```bash
135
- # treelsp-config.json at repo root
136
- treelsp generate # generates all languages
137
- treelsp build # builds all languages
164
+ treelsp generate # generates for all configured backends
165
+ treelsp build # builds all configured backends
138
166
  treelsp watch # watches all grammar files
139
167
  ```
package/dist/index.js CHANGED
@@ -28,28 +28,48 @@ async function getCliVersion() {
28
28
  }
29
29
  async function init() {
30
30
  console.log(pc.bold("treelsp init\n"));
31
- const answers = await prompts([{
32
- type: "text",
33
- name: "name",
34
- message: "Language name:",
35
- initial: "my-lang",
36
- validate: (value) => value.length > 0 || "Name is required"
37
- }, {
38
- type: "text",
39
- name: "extension",
40
- message: "File extension:",
41
- initial: ".mylang",
42
- validate: (value) => {
43
- if (!value.startsWith(".")) return "Extension must start with a dot";
44
- if (value.length < 2) return "Extension is too short";
45
- return true;
31
+ const answers = await prompts([
32
+ {
33
+ type: "text",
34
+ name: "name",
35
+ message: "Language name:",
36
+ initial: "my-lang",
37
+ validate: (value) => value.length > 0 || "Name is required"
38
+ },
39
+ {
40
+ type: "text",
41
+ name: "extension",
42
+ message: "File extension:",
43
+ initial: ".mylang",
44
+ validate: (value) => {
45
+ if (!value.startsWith(".")) return "Extension must start with a dot";
46
+ if (value.length < 2) return "Extension is too short";
47
+ return true;
48
+ }
49
+ },
50
+ {
51
+ type: "select",
52
+ name: "backend",
53
+ message: "Parser backend:",
54
+ choices: [{
55
+ title: "Tree-sitter",
56
+ description: "Default — generates WASM parser, requires tree-sitter CLI",
57
+ value: "tree-sitter"
58
+ }, {
59
+ title: "Lezer",
60
+ description: "Pure JavaScript — no external CLI required",
61
+ value: "lezer"
62
+ }],
63
+ initial: 0
46
64
  }
47
- }]);
48
- if (!answers.name || !answers.extension) {
65
+ ]);
66
+ if (!answers.name || !answers.extension || !answers.backend) {
49
67
  console.log(pc.dim("\nCancelled"));
50
68
  process.exit(0);
51
69
  }
52
- const { name, extension } = answers;
70
+ const { name, extension, backend } = answers;
71
+ const capitalizedName = name.split("-").map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join("");
72
+ const languageId = name.replace(/-/g, "").toLowerCase();
53
73
  const spinner = ora("Creating project structure...").start();
54
74
  try {
55
75
  const projectDir = resolve(process.cwd(), name);
@@ -58,38 +78,178 @@ async function init() {
58
78
  console.log(pc.dim("\nChoose a different name or remove the existing directory"));
59
79
  process.exit(1);
60
80
  }
61
- await mkdir(projectDir);
81
+ await mkdir(resolve(projectDir, ".vscode"), { recursive: true });
82
+ await mkdir(resolve(projectDir, "packages", "language"), { recursive: true });
83
+ await mkdir(resolve(projectDir, "packages", "extension", "src"), { recursive: true });
62
84
  const version = await getCliVersion();
63
85
  const versionRange = `^${version}`;
64
- const packageJson = {
65
- name,
66
- version: "0.1.0",
67
- type: "module",
68
- dependencies: { treelsp: versionRange },
69
- devDependencies: {
70
- "@treelsp/cli": versionRange,
71
- typescript: "^5.7.3"
72
- },
73
- scripts: {
74
- generate: "treelsp generate",
75
- build: "treelsp build",
76
- watch: "treelsp watch"
77
- }
78
- };
79
- await writeFile(resolve(projectDir, "package.json"), JSON.stringify(packageJson, null, 2) + "\n", "utf-8");
80
- const tsconfig = {
81
- compilerOptions: {
82
- target: "ES2022",
83
- module: "NodeNext",
84
- moduleResolution: "NodeNext",
85
- strict: true,
86
- outDir: "./dist"
87
- },
88
- include: ["grammar.ts"]
89
- };
90
- await writeFile(resolve(projectDir, "tsconfig.json"), JSON.stringify(tsconfig, null, 2) + "\n", "utf-8");
91
- const capitalizedName = name.split("-").map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join("");
92
- const grammarTemplate = `/**
86
+ const files = [
87
+ ["pnpm-workspace.yaml", pnpmWorkspaceYaml()],
88
+ ["package.json", rootPackageJson(name, versionRange)],
89
+ ["treelsp-config.json", treelspConfigJson(backend)],
90
+ [".gitignore", rootGitignore()],
91
+ ["README.md", rootReadme(capitalizedName, backend)],
92
+ [".vscode/launch.json", vscodeLaunchJson()],
93
+ [".vscode/tasks.json", vscodeTasksJson()],
94
+ ["packages/language/package.json", languagePackageJson(name, versionRange)],
95
+ ["packages/language/tsconfig.json", languageTsconfig()],
96
+ ["packages/language/grammar.ts", grammarTemplate(capitalizedName, extension)],
97
+ ["packages/extension/package.json", extensionPackageJson(name, capitalizedName, languageId, extension)],
98
+ ["packages/extension/tsconfig.json", extensionTsconfig()],
99
+ ["packages/extension/tsdown.config.ts", extensionTsdownConfig()],
100
+ ["packages/extension/.vscodeignore", extensionVscodeignore()],
101
+ ["packages/extension/src/extension.ts", extensionTs(capitalizedName, languageId)]
102
+ ];
103
+ for (const [filePath, content] of files) await writeFile(resolve(projectDir, filePath), content, "utf-8");
104
+ spinner.succeed("Project created!");
105
+ console.log(pc.dim("\nNext steps:"));
106
+ console.log(pc.dim(` cd ${name}`));
107
+ console.log(pc.dim(" pnpm install"));
108
+ console.log(pc.dim(" Edit packages/language/grammar.ts to define your language"));
109
+ console.log(pc.dim(" pnpm build"));
110
+ console.log(pc.dim(" Press F5 in VS Code to launch the extension"));
111
+ } catch (error) {
112
+ spinner.fail("Failed to create project");
113
+ if (error instanceof Error) console.error(pc.red(`\n${error.message}`));
114
+ process.exit(1);
115
+ }
116
+ }
117
+ function pnpmWorkspaceYaml() {
118
+ return `packages:\n - 'packages/*'\n`;
119
+ }
120
+ function rootPackageJson(name, versionRange) {
121
+ const pkg = {
122
+ name,
123
+ version: "0.1.0",
124
+ private: true,
125
+ type: "module",
126
+ scripts: {
127
+ generate: "treelsp generate",
128
+ build: "treelsp generate && treelsp build",
129
+ "build:extension": `pnpm --filter ${name}-extension build`,
130
+ watch: "treelsp watch"
131
+ },
132
+ devDependencies: {
133
+ "@treelsp/cli": versionRange,
134
+ typescript: "^5.7.3"
135
+ }
136
+ };
137
+ return JSON.stringify(pkg, null, 2) + "\n";
138
+ }
139
+ function treelspConfigJson(backend) {
140
+ const entry = { grammar: "packages/language/grammar.ts" };
141
+ if (backend !== "tree-sitter") entry["backend"] = backend;
142
+ const config = { languages: [entry] };
143
+ return JSON.stringify(config, null, 2) + "\n";
144
+ }
145
+ function rootGitignore() {
146
+ return `node_modules
147
+ dist
148
+ generated/
149
+ generated-lezer/
150
+ *.wasm
151
+ *.log
152
+ *.tsbuildinfo
153
+ .DS_Store
154
+ `;
155
+ }
156
+ function rootReadme(capitalizedName, backend) {
157
+ const generatedDir = backend === "lezer" ? "generated-lezer" : "generated";
158
+ return `# ${capitalizedName}
159
+
160
+ A language powered by [treelsp](https://github.com/dhrubomoy/treelsp) using the ${backend} parser backend.
161
+
162
+ ## Getting Started
163
+
164
+ Install dependencies:
165
+
166
+ \`\`\`bash
167
+ pnpm install
168
+ \`\`\`
169
+
170
+ Generate grammar and build parser:
171
+
172
+ \`\`\`bash
173
+ pnpm build
174
+ \`\`\`
175
+
176
+ Launch the VS Code extension for development:
177
+
178
+ \`\`\`
179
+ Press F5 in VS Code, or:
180
+ pnpm build:extension
181
+ \`\`\`
182
+
183
+ Development workflow:
184
+
185
+ \`\`\`bash
186
+ pnpm watch # Auto-rebuild grammar on changes
187
+ \`\`\`
188
+
189
+ ## Project Structure
190
+
191
+ - \`packages/language/grammar.ts\` - Language definition (grammar, semantics, validation, LSP)
192
+ - \`packages/language/${generatedDir}/\` - Generated files (parser, AST types, server bundle)
193
+ - \`packages/extension/\` - VS Code extension that launches the language server
194
+
195
+ ## License
196
+
197
+ MIT
198
+ `;
199
+ }
200
+ function vscodeLaunchJson() {
201
+ const config = {
202
+ version: "0.2.0",
203
+ configurations: [{
204
+ name: "Launch Extension",
205
+ type: "extensionHost",
206
+ request: "launch",
207
+ args: ["--extensionDevelopmentPath=${workspaceFolder}/packages/extension", "${workspaceFolder}/packages/language"],
208
+ outFiles: ["${workspaceFolder}/packages/extension/dist/**/*.js"],
209
+ preLaunchTask: "build"
210
+ }]
211
+ };
212
+ return JSON.stringify(config, null, 2) + "\n";
213
+ }
214
+ function vscodeTasksJson() {
215
+ const config = {
216
+ version: "2.0.0",
217
+ tasks: [{
218
+ label: "build",
219
+ type: "shell",
220
+ command: "pnpm build && pnpm build:extension",
221
+ group: "build",
222
+ problemMatcher: []
223
+ }]
224
+ };
225
+ return JSON.stringify(config, null, 2) + "\n";
226
+ }
227
+ function languagePackageJson(name, versionRange) {
228
+ const pkg = {
229
+ name: `${name}-language`,
230
+ version: "0.1.0",
231
+ private: true,
232
+ type: "module",
233
+ dependencies: { treelsp: versionRange },
234
+ devDependencies: { typescript: "^5.7.3" }
235
+ };
236
+ return JSON.stringify(pkg, null, 2) + "\n";
237
+ }
238
+ function languageTsconfig() {
239
+ const config = {
240
+ compilerOptions: {
241
+ target: "ES2022",
242
+ module: "NodeNext",
243
+ moduleResolution: "NodeNext",
244
+ strict: true,
245
+ outDir: "./dist"
246
+ },
247
+ include: ["grammar.ts"]
248
+ };
249
+ return JSON.stringify(config, null, 2) + "\n";
250
+ }
251
+ function grammarTemplate(capitalizedName, extension) {
252
+ return `/**
93
253
  * ${capitalizedName} - Language definition for treelsp
94
254
  */
95
255
 
@@ -184,70 +344,228 @@ export default defineLanguage({
184
344
  },
185
345
  });
186
346
  `;
187
- await writeFile(resolve(projectDir, "grammar.ts"), grammarTemplate, "utf-8");
188
- const gitignore = `node_modules
189
- dist
190
- generated/
191
- *.wasm
192
- *.log
347
+ }
348
+ function extensionPackageJson(name, capitalizedName, languageId, extension) {
349
+ const pkg = {
350
+ name: `${name}-extension`,
351
+ displayName: capitalizedName,
352
+ description: `VS Code extension for ${capitalizedName}`,
353
+ version: "0.1.0",
354
+ private: true,
355
+ publisher: name,
356
+ engines: { vscode: "^1.80.0" },
357
+ categories: ["Programming Languages"],
358
+ activationEvents: ["workspaceContains:**/generated/treelsp.json", "workspaceContains:**/generated-lezer/treelsp.json"],
359
+ main: "./dist/extension.js",
360
+ contributes: { languages: [{
361
+ id: languageId,
362
+ extensions: [extension],
363
+ aliases: [capitalizedName]
364
+ }] },
365
+ scripts: {
366
+ build: "tsdown",
367
+ dev: "tsdown --watch",
368
+ package: "vsce package"
369
+ },
370
+ dependencies: { "vscode-languageclient": "^9.0.1" },
371
+ devDependencies: {
372
+ "@types/vscode": "^1.80.0",
373
+ tsdown: "^0.2.17",
374
+ typescript: "^5.7.3"
375
+ }
376
+ };
377
+ return JSON.stringify(pkg, null, 2) + "\n";
378
+ }
379
+ function extensionTsconfig() {
380
+ const config = {
381
+ compilerOptions: {
382
+ target: "ES2022",
383
+ module: "NodeNext",
384
+ moduleResolution: "NodeNext",
385
+ strict: true,
386
+ outDir: "./dist",
387
+ rootDir: "./src",
388
+ lib: ["ES2022"]
389
+ },
390
+ include: ["src/**/*"],
391
+ exclude: ["node_modules", "dist"]
392
+ };
393
+ return JSON.stringify(config, null, 2) + "\n";
394
+ }
395
+ function extensionTsdownConfig() {
396
+ return `import { defineConfig } from 'tsdown';
397
+
398
+ export default defineConfig({
399
+ entry: ['src/extension.ts'],
400
+ format: ['cjs'],
401
+ dts: false,
402
+ clean: true,
403
+ platform: 'node',
404
+ external: ['vscode'],
405
+ });
406
+ `;
407
+ }
408
+ function extensionVscodeignore() {
409
+ return `src/
410
+ node_modules/
411
+ .gitignore
412
+ tsconfig.json
413
+ tsdown.config.ts
193
414
  *.tsbuildinfo
194
- .DS_Store
195
415
  `;
196
- await writeFile(resolve(projectDir, ".gitignore"), gitignore, "utf-8");
197
- const readme = `# ${capitalizedName}
198
-
199
- A language definition for treelsp.
200
-
201
- ## Getting Started
202
-
203
- Install dependencies:
204
-
205
- \`\`\`bash
206
- npm install
207
- # or: pnpm install
208
- \`\`\`
209
-
210
- Generate grammar and build parser:
211
-
212
- \`\`\`bash
213
- npm run generate # Generates grammar.js, ast.ts, server.ts
214
- npm run build # Compiles to WASM
215
- \`\`\`
216
-
217
- Development workflow:
416
+ }
417
+ function extensionTs(capitalizedName, languageId) {
418
+ return `/**
419
+ * ${capitalizedName} - VS Code Extension
420
+ * Discovers and launches the treelsp-generated language server.
421
+ */
218
422
 
219
- \`\`\`bash
220
- npm run watch # Auto-rebuild on changes
221
- \`\`\`
423
+ import * as vscode from 'vscode';
424
+ import * as path from 'path';
425
+ import * as fs from 'fs';
426
+ import {
427
+ LanguageClient,
428
+ LanguageClientOptions,
429
+ ServerOptions,
430
+ TransportKind,
431
+ State,
432
+ } from 'vscode-languageclient/node';
433
+
434
+ interface TreelspManifest {
435
+ name: string;
436
+ languageId: string;
437
+ fileExtensions: string[];
438
+ server: string;
439
+ textmateGrammar?: string;
440
+ }
222
441
 
223
- ## Project Structure
442
+ const clients = new Map<string, LanguageClient>();
443
+
444
+ export async function activate(context: vscode.ExtensionContext) {
445
+ const manifests = await discoverManifests();
446
+
447
+ for (const { manifest, manifestPath } of manifests) {
448
+ await startLanguageClient(manifest, manifestPath, context);
449
+ }
450
+
451
+ // Watch for new or updated manifests
452
+ for (const pattern of ['**/generated/treelsp.json', '**/generated-lezer/treelsp.json']) {
453
+ const watcher = vscode.workspace.createFileSystemWatcher(pattern);
454
+ watcher.onDidCreate(async (uri) => {
455
+ const data = await readManifest(uri.fsPath);
456
+ if (data) await startLanguageClient(data, uri.fsPath, context);
457
+ });
458
+ watcher.onDidChange(async (uri) => {
459
+ await stopClient(uri.fsPath);
460
+ const data = await readManifest(uri.fsPath);
461
+ if (data) await startLanguageClient(data, uri.fsPath, context);
462
+ });
463
+ watcher.onDidDelete(async (uri) => {
464
+ await stopClient(uri.fsPath);
465
+ });
466
+ context.subscriptions.push(watcher);
467
+ }
468
+ }
224
469
 
225
- - \`grammar.ts\` - Language definition (grammar, semantic, validation, LSP)
226
- - \`generated/\` - Generated files (grammar.js, WASM, types)
227
- - \`package.json\` - Project dependencies
470
+ export async function deactivate(): Promise<void> {
471
+ const stops = [...clients.values()].map(c => c.stop());
472
+ await Promise.all(stops);
473
+ clients.clear();
474
+ }
228
475
 
229
- ## Documentation
476
+ async function discoverManifests(): Promise<Array<{ manifest: TreelspManifest; manifestPath: string }>> {
477
+ const results: Array<{ manifest: TreelspManifest; manifestPath: string }> = [];
478
+ for (const pattern of ['**/generated/treelsp.json', '**/generated-lezer/treelsp.json']) {
479
+ const uris = await vscode.workspace.findFiles(pattern, '**/node_modules/**');
480
+ for (const uri of uris) {
481
+ const data = await readManifest(uri.fsPath);
482
+ if (data) results.push({ manifest: data, manifestPath: uri.fsPath });
483
+ }
484
+ }
485
+ return results;
486
+ }
230
487
 
231
- - [treelsp Documentation](https://github.com/yourusername/treelsp)
232
- - [Tree-sitter](https://tree-sitter.github.io/tree-sitter/)
488
+ async function readManifest(fsPath: string): Promise<TreelspManifest | null> {
489
+ try {
490
+ const doc = await vscode.workspace.openTextDocument(fsPath);
491
+ const data = JSON.parse(doc.getText()) as TreelspManifest;
492
+ if (!data.name || !data.languageId || !data.fileExtensions || !data.server) return null;
493
+ return data;
494
+ } catch {
495
+ return null;
496
+ }
497
+ }
233
498
 
234
- ## License
499
+ async function startLanguageClient(
500
+ manifest: TreelspManifest,
501
+ manifestPath: string,
502
+ context: vscode.ExtensionContext,
503
+ ): Promise<void> {
504
+ if (clients.has(manifestPath)) return;
505
+
506
+ const generatedDir = path.dirname(manifestPath);
507
+ const serverModule = path.resolve(generatedDir, manifest.server);
508
+
509
+ if (!fs.existsSync(serverModule)) {
510
+ void vscode.window.showErrorMessage(
511
+ '${capitalizedName}: Server bundle not found. Run "pnpm build" first.',
512
+ );
513
+ return;
514
+ }
515
+
516
+ const serverOptions: ServerOptions = {
517
+ run: { module: serverModule, transport: TransportKind.stdio },
518
+ debug: {
519
+ module: serverModule,
520
+ transport: TransportKind.stdio,
521
+ options: { execArgv: ['--nolazy', '--inspect=6009'] },
522
+ },
523
+ };
524
+
525
+ const clientOptions: LanguageClientOptions = {
526
+ documentSelector: manifest.fileExtensions.map(ext => ({
527
+ scheme: 'file' as const,
528
+ language: manifest.languageId,
529
+ pattern: \`**/*\${ext}\`,
530
+ })),
531
+ outputChannelName: '${capitalizedName} Language Server',
532
+ };
533
+
534
+ const client = new LanguageClient(
535
+ '${languageId}',
536
+ '${capitalizedName} Language Server',
537
+ serverOptions,
538
+ clientOptions,
539
+ );
540
+
541
+ client.onDidChangeState((event) => {
542
+ if (event.oldState === State.Running && event.newState === State.Stopped) {
543
+ void vscode.window.showWarningMessage(
544
+ '${capitalizedName}: Language server stopped unexpectedly.',
545
+ );
546
+ }
547
+ });
548
+
549
+ clients.set(manifestPath, client);
550
+ context.subscriptions.push(client);
551
+
552
+ try {
553
+ await client.start();
554
+ } catch (e) {
555
+ clients.delete(manifestPath);
556
+ void vscode.window.showErrorMessage(
557
+ \`${capitalizedName}: Failed to start language server: \${e instanceof Error ? e.message : String(e)}\`,
558
+ );
559
+ }
560
+ }
235
561
 
236
- MIT
562
+ async function stopClient(key: string): Promise<void> {
563
+ const client = clients.get(key);
564
+ if (!client) return;
565
+ await client.stop();
566
+ clients.delete(key);
567
+ }
237
568
  `;
238
- await writeFile(resolve(projectDir, "README.md"), readme, "utf-8");
239
- spinner.succeed("Project created!");
240
- console.log(pc.dim("\nNext steps:"));
241
- console.log(pc.dim(` cd ${name}`));
242
- console.log(pc.dim(" npm install"));
243
- console.log(pc.dim(" Edit grammar.ts to define your language"));
244
- console.log(pc.dim(" npm run generate"));
245
- console.log(pc.dim(" npm run build"));
246
- } catch (error) {
247
- spinner.fail("Failed to create project");
248
- if (error instanceof Error) console.error(pc.red(`\n${error.message}`));
249
- process.exit(1);
250
- }
251
569
  }
252
570
 
253
571
  //#endregion
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@treelsp/cli",
3
- "version": "0.0.2",
3
+ "version": "0.0.4",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
@@ -41,7 +41,7 @@
41
41
  "picocolors": "^1.1.1",
42
42
  "prompts": "^2.4.2",
43
43
  "tree-sitter-cli": "^0.26.5",
44
- "treelsp": "0.0.2"
44
+ "treelsp": "0.0.4"
45
45
  },
46
46
  "devDependencies": {
47
47
  "@types/prompts": "^2.4.9",