@treelsp/cli 0.0.2 → 0.0.3
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/README.md +50 -22
- package/dist/index.js +420 -102
- 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
|
|
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
|
|
24
|
-
- `grammar.ts`
|
|
25
|
-
- `
|
|
26
|
-
|
|
27
|
-
- `.
|
|
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
|
|
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**
|
|
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
|
|
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
|
-
|
|
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
|
|
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": "
|
|
95
|
-
{ "grammar": "
|
|
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
|
-
**
|
|
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
|
-
|
|
128
|
-
|
|
129
|
-
|
|
147
|
+
pnpm install
|
|
148
|
+
pnpm build # generate + compile + bundle
|
|
149
|
+
# Press F5 in VS Code to launch the extension
|
|
130
150
|
```
|
|
131
151
|
|
|
132
|
-
**
|
|
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
|
-
#
|
|
136
|
-
treelsp
|
|
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
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
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
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
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
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
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
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
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
|
-
|
|
220
|
-
|
|
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
|
-
|
|
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
|
-
|
|
226
|
-
|
|
227
|
-
|
|
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
|
-
|
|
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
|
-
|
|
232
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
3
|
+
"version": "0.0.3",
|
|
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.
|
|
44
|
+
"treelsp": "0.0.3"
|
|
45
45
|
},
|
|
46
46
|
"devDependencies": {
|
|
47
47
|
"@types/prompts": "^2.4.9",
|