treelsp 0.0.1
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/dist/codegen/ast-types.d.ts +9 -0
- package/dist/codegen/grammar.d.ts +9 -0
- package/dist/codegen/highlights.d.ts +9 -0
- package/dist/codegen/index.d.ts +9 -0
- package/dist/codegen/index.js +983 -0
- package/dist/codegen/locals.d.ts +9 -0
- package/dist/codegen/server.d.ts +31 -0
- package/dist/defaults/completion.d.ts +18 -0
- package/dist/defaults/hover.d.ts +11 -0
- package/dist/defaults/index.d.ts +4 -0
- package/dist/defaults/symbols.d.ts +10 -0
- package/dist/defaults/validation.d.ts +19 -0
- package/dist/define.d.ts +25 -0
- package/dist/definition/grammar.d.ts +36 -0
- package/dist/definition/index.d.ts +33 -0
- package/dist/definition/lsp.d.ts +93 -0
- package/dist/definition/semantic.d.ts +80 -0
- package/dist/definition/validation.d.ts +62 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +148 -0
- package/dist/runtime/index.d.ts +19 -0
- package/dist/runtime/index.js +3 -0
- package/dist/runtime/lsp/completion.d.ts +24 -0
- package/dist/runtime/lsp/context.d.ts +59 -0
- package/dist/runtime/lsp/definition.d.ts +23 -0
- package/dist/runtime/lsp/diagnostics.d.ts +33 -0
- package/dist/runtime/lsp/documents.d.ts +55 -0
- package/dist/runtime/lsp/hover.d.ts +30 -0
- package/dist/runtime/lsp/references.d.ts +28 -0
- package/dist/runtime/lsp/rename.d.ts +50 -0
- package/dist/runtime/lsp/semantic-tokens.d.ts +32 -0
- package/dist/runtime/lsp/server.d.ts +52 -0
- package/dist/runtime/lsp/symbols.d.ts +36 -0
- package/dist/runtime/parser/index.d.ts +7 -0
- package/dist/runtime/parser/node.d.ts +185 -0
- package/dist/runtime/parser/tree.d.ts +153 -0
- package/dist/runtime/parser/wasm.d.ts +32 -0
- package/dist/runtime/scope/index.d.ts +7 -0
- package/dist/runtime/scope/resolver.d.ts +49 -0
- package/dist/runtime/scope/scope.d.ts +127 -0
- package/dist/runtime/scope/workspace.d.ts +104 -0
- package/dist/runtime-CnCx2QS4.js +1914 -0
- package/dist/server/index.d.ts +25 -0
- package/dist/server/index.js +230 -0
- package/package.json +64 -0
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LSP server transport for Node.js stdio
|
|
3
|
+
*
|
|
4
|
+
* Provides startStdioServer() that wires a LanguageService to a
|
|
5
|
+
* vscode-languageserver connection over stdio. Separate from treelsp/runtime
|
|
6
|
+
* to keep the runtime browser-compatible.
|
|
7
|
+
*/
|
|
8
|
+
import type { LanguageDefinition } from '../definition/index.js';
|
|
9
|
+
/**
|
|
10
|
+
* Options for starting a stdio LSP server
|
|
11
|
+
*/
|
|
12
|
+
export interface StdioServerOptions {
|
|
13
|
+
/** Language definition from defineLanguage() */
|
|
14
|
+
definition: LanguageDefinition;
|
|
15
|
+
/** Absolute path to grammar.wasm file */
|
|
16
|
+
wasmPath: string;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Start an LSP server over stdio transport
|
|
20
|
+
*
|
|
21
|
+
* Creates a vscode-languageserver connection, wires all LSP protocol handlers
|
|
22
|
+
* to a LanguageService, and starts listening. This is the main entry point
|
|
23
|
+
* for generated LSP servers.
|
|
24
|
+
*/
|
|
25
|
+
export declare function startStdioServer(options: StdioServerOptions): void;
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
import { COMPLETION_KIND_MAP, SEMANTIC_TOKEN_MODIFIERS, SEMANTIC_TOKEN_TYPES, createDocumentState, createServer } from "../runtime-CnCx2QS4.js";
|
|
2
|
+
import { DiagnosticSeverity, ProposedFeatures, TextDocumentSyncKind, TextDocuments, createConnection } from "vscode-languageserver/lib/node/main.js";
|
|
3
|
+
import { TextDocument } from "vscode-languageserver-textdocument";
|
|
4
|
+
|
|
5
|
+
//#region src/server/index.ts
|
|
6
|
+
/**
|
|
7
|
+
* Convert internal CompletionItem to LSP protocol CompletionItem
|
|
8
|
+
*/
|
|
9
|
+
function toLspCompletionItem(item) {
|
|
10
|
+
const lspItem = { label: item.label };
|
|
11
|
+
if (item.kind) lspItem.kind = COMPLETION_KIND_MAP[item.kind];
|
|
12
|
+
if (item.detail) lspItem.detail = item.detail;
|
|
13
|
+
if (item.documentation) lspItem.documentation = item.documentation;
|
|
14
|
+
if (item.insertText) lspItem.insertText = item.insertText;
|
|
15
|
+
return lspItem;
|
|
16
|
+
}
|
|
17
|
+
function getRangeStr(start, end) {
|
|
18
|
+
return `${start.line}:${start.character}-${end.line}:${end.character}`;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Start an LSP server over stdio transport
|
|
22
|
+
*
|
|
23
|
+
* Creates a vscode-languageserver connection, wires all LSP protocol handlers
|
|
24
|
+
* to a LanguageService, and starts listening. This is the main entry point
|
|
25
|
+
* for generated LSP servers.
|
|
26
|
+
*/
|
|
27
|
+
function startStdioServer(options) {
|
|
28
|
+
const { definition, wasmPath } = options;
|
|
29
|
+
const langId = definition.name.toLowerCase();
|
|
30
|
+
const connection = createConnection(ProposedFeatures.all);
|
|
31
|
+
const textDocuments = new TextDocuments(TextDocument);
|
|
32
|
+
const service = createServer(definition);
|
|
33
|
+
const documentStates = /* @__PURE__ */ new Map();
|
|
34
|
+
const pendingInits = /* @__PURE__ */ new Map();
|
|
35
|
+
async function getDocumentState(textDoc) {
|
|
36
|
+
const existing = documentStates.get(textDoc.uri);
|
|
37
|
+
if (existing) return existing;
|
|
38
|
+
let promise = pendingInits.get(textDoc.uri);
|
|
39
|
+
if (!promise) {
|
|
40
|
+
promise = createDocumentState(wasmPath, {
|
|
41
|
+
uri: textDoc.uri,
|
|
42
|
+
version: textDoc.version,
|
|
43
|
+
languageId: langId
|
|
44
|
+
}, textDoc.getText());
|
|
45
|
+
pendingInits.set(textDoc.uri, promise);
|
|
46
|
+
}
|
|
47
|
+
const state = await promise;
|
|
48
|
+
documentStates.set(textDoc.uri, state);
|
|
49
|
+
pendingInits.delete(textDoc.uri);
|
|
50
|
+
return state;
|
|
51
|
+
}
|
|
52
|
+
const severityMap = {
|
|
53
|
+
error: DiagnosticSeverity.Error,
|
|
54
|
+
warning: DiagnosticSeverity.Warning,
|
|
55
|
+
info: DiagnosticSeverity.Information,
|
|
56
|
+
hint: DiagnosticSeverity.Hint
|
|
57
|
+
};
|
|
58
|
+
async function validateDocument(textDoc) {
|
|
59
|
+
const state = await getDocumentState(textDoc);
|
|
60
|
+
const diagnostics = service.computeDiagnostics(state);
|
|
61
|
+
connection.console.log(`[validation] ${diagnostics.map((d) => `range=${getRangeStr(d.range.start, d.range.end)} message=${d.message}`).join(", ")}`);
|
|
62
|
+
connection.sendDiagnostics({
|
|
63
|
+
uri: textDoc.uri,
|
|
64
|
+
version: textDoc.version,
|
|
65
|
+
diagnostics: diagnostics.map((d) => {
|
|
66
|
+
const lspDiag = {
|
|
67
|
+
range: d.range,
|
|
68
|
+
severity: severityMap[d.severity],
|
|
69
|
+
message: d.message,
|
|
70
|
+
source: d.source ?? langId
|
|
71
|
+
};
|
|
72
|
+
if (d.code) lspDiag.code = d.code;
|
|
73
|
+
return lspDiag;
|
|
74
|
+
})
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
connection.onInitialize(() => ({ capabilities: {
|
|
78
|
+
textDocumentSync: TextDocumentSyncKind.Full,
|
|
79
|
+
hoverProvider: true,
|
|
80
|
+
definitionProvider: true,
|
|
81
|
+
referencesProvider: true,
|
|
82
|
+
completionProvider: { resolveProvider: false },
|
|
83
|
+
renameProvider: { prepareProvider: true },
|
|
84
|
+
documentSymbolProvider: true,
|
|
85
|
+
semanticTokensProvider: {
|
|
86
|
+
legend: {
|
|
87
|
+
tokenTypes: [...SEMANTIC_TOKEN_TYPES],
|
|
88
|
+
tokenModifiers: [...SEMANTIC_TOKEN_MODIFIERS]
|
|
89
|
+
},
|
|
90
|
+
full: true
|
|
91
|
+
}
|
|
92
|
+
} }));
|
|
93
|
+
textDocuments.onDidOpen(async (event) => {
|
|
94
|
+
connection.console.log(`[open] ${event.document.uri} v${event.document.version}`);
|
|
95
|
+
await validateDocument(event.document);
|
|
96
|
+
connection.console.log(`[open] done ${event.document.uri}`);
|
|
97
|
+
});
|
|
98
|
+
textDocuments.onDidChangeContent(async (event) => {
|
|
99
|
+
connection.console.log(`[change] ${event.document.uri} v${event.document.version}`);
|
|
100
|
+
const state = await getDocumentState(event.document);
|
|
101
|
+
state.update(event.document.getText(), event.document.version);
|
|
102
|
+
service.documents.change(state);
|
|
103
|
+
await validateDocument(event.document);
|
|
104
|
+
});
|
|
105
|
+
textDocuments.onDidClose((event) => {
|
|
106
|
+
const state = documentStates.get(event.document.uri);
|
|
107
|
+
if (state) {
|
|
108
|
+
service.documents.close(event.document.uri);
|
|
109
|
+
state.dispose();
|
|
110
|
+
documentStates.delete(event.document.uri);
|
|
111
|
+
}
|
|
112
|
+
connection.sendDiagnostics({
|
|
113
|
+
uri: event.document.uri,
|
|
114
|
+
diagnostics: []
|
|
115
|
+
});
|
|
116
|
+
});
|
|
117
|
+
connection.onHover(async (params) => {
|
|
118
|
+
const textDoc = textDocuments.get(params.textDocument.uri);
|
|
119
|
+
if (!textDoc) return null;
|
|
120
|
+
const state = await getDocumentState(textDoc);
|
|
121
|
+
const result = service.provideHover(state, params.position);
|
|
122
|
+
if (!result) return null;
|
|
123
|
+
return {
|
|
124
|
+
contents: {
|
|
125
|
+
kind: "markdown",
|
|
126
|
+
value: result.contents
|
|
127
|
+
},
|
|
128
|
+
range: result.range
|
|
129
|
+
};
|
|
130
|
+
});
|
|
131
|
+
connection.onDefinition(async (params) => {
|
|
132
|
+
const textDoc = textDocuments.get(params.textDocument.uri);
|
|
133
|
+
if (!textDoc) {
|
|
134
|
+
connection.console.log(`[definition] no textDoc for ${params.textDocument.uri}`);
|
|
135
|
+
return null;
|
|
136
|
+
}
|
|
137
|
+
try {
|
|
138
|
+
const state = await getDocumentState(textDoc);
|
|
139
|
+
const pos = params.position;
|
|
140
|
+
const node = state.root.descendantForPosition(pos);
|
|
141
|
+
connection.console.log(`[definition] pos=${pos.line}:${pos.character} node=${node.type} "${node.text}" range=${getRangeStr(node.startPosition, node.endPosition)}`);
|
|
142
|
+
const result = service.provideDefinition(state, params.position);
|
|
143
|
+
connection.console.log(`[definition] result=${result ? `${result.uri} ${result.range.start.line}:${result.range.start.character}` : "null"}`);
|
|
144
|
+
if (!result) return null;
|
|
145
|
+
return {
|
|
146
|
+
uri: result.uri,
|
|
147
|
+
range: result.range
|
|
148
|
+
};
|
|
149
|
+
} catch (e) {
|
|
150
|
+
connection.console.error(`[definition] error: ${String(e)}`);
|
|
151
|
+
return null;
|
|
152
|
+
}
|
|
153
|
+
});
|
|
154
|
+
connection.onReferences(async (params) => {
|
|
155
|
+
const textDoc = textDocuments.get(params.textDocument.uri);
|
|
156
|
+
if (!textDoc) {
|
|
157
|
+
connection.console.log(`[references] no textDoc for ${params.textDocument.uri}`);
|
|
158
|
+
return [];
|
|
159
|
+
}
|
|
160
|
+
try {
|
|
161
|
+
const state = await getDocumentState(textDoc);
|
|
162
|
+
const pos = params.position;
|
|
163
|
+
const node = state.root.descendantForPosition(pos);
|
|
164
|
+
connection.console.log(`[references] pos=${pos.line}:${pos.character} node=${node.type} "${node.text}" range=${node.startPosition.line}:${node.startPosition.character}-${node.endPosition.line}:${node.endPosition.character}`);
|
|
165
|
+
const results = service.provideReferences(state, params.position);
|
|
166
|
+
connection.console.log(`[references] found ${results.length} references`);
|
|
167
|
+
return results.map((r) => ({
|
|
168
|
+
uri: r.uri,
|
|
169
|
+
range: r.range
|
|
170
|
+
}));
|
|
171
|
+
} catch (e) {
|
|
172
|
+
connection.console.error(`[references] error: ${String(e)}`);
|
|
173
|
+
return [];
|
|
174
|
+
}
|
|
175
|
+
});
|
|
176
|
+
connection.onCompletion(async (params) => {
|
|
177
|
+
const textDoc = textDocuments.get(params.textDocument.uri);
|
|
178
|
+
if (!textDoc) return [];
|
|
179
|
+
const state = await getDocumentState(textDoc);
|
|
180
|
+
const items = service.provideCompletion(state, params.position);
|
|
181
|
+
return items.map(toLspCompletionItem);
|
|
182
|
+
});
|
|
183
|
+
connection.onPrepareRename(async (params) => {
|
|
184
|
+
const textDoc = textDocuments.get(params.textDocument.uri);
|
|
185
|
+
if (!textDoc) return null;
|
|
186
|
+
const state = await getDocumentState(textDoc);
|
|
187
|
+
return service.prepareRename(state, params.position);
|
|
188
|
+
});
|
|
189
|
+
connection.onRenameRequest(async (params) => {
|
|
190
|
+
const textDoc = textDocuments.get(params.textDocument.uri);
|
|
191
|
+
if (!textDoc) return null;
|
|
192
|
+
const state = await getDocumentState(textDoc);
|
|
193
|
+
const result = service.provideRename(state, params.position, params.newName);
|
|
194
|
+
if (!result) return null;
|
|
195
|
+
const changes = {};
|
|
196
|
+
for (const [uri, edits] of Object.entries(result.changes)) changes[uri] = edits.map((e) => ({
|
|
197
|
+
range: e.range,
|
|
198
|
+
newText: e.newText
|
|
199
|
+
}));
|
|
200
|
+
return { changes };
|
|
201
|
+
});
|
|
202
|
+
connection.onDocumentSymbol(async (params) => {
|
|
203
|
+
const textDoc = textDocuments.get(params.textDocument.uri);
|
|
204
|
+
if (!textDoc) return [];
|
|
205
|
+
const state = await getDocumentState(textDoc);
|
|
206
|
+
const symbols = service.provideSymbols(state);
|
|
207
|
+
return symbols.map((s) => {
|
|
208
|
+
const sym = {
|
|
209
|
+
name: s.name,
|
|
210
|
+
kind: s.kindNumber,
|
|
211
|
+
range: s.range,
|
|
212
|
+
selectionRange: s.selectionRange,
|
|
213
|
+
children: []
|
|
214
|
+
};
|
|
215
|
+
if (s.detail) sym.detail = s.detail;
|
|
216
|
+
return sym;
|
|
217
|
+
});
|
|
218
|
+
});
|
|
219
|
+
connection.languages.semanticTokens.on(async (params) => {
|
|
220
|
+
const textDoc = textDocuments.get(params.textDocument.uri);
|
|
221
|
+
if (!textDoc) return { data: [] };
|
|
222
|
+
const state = await getDocumentState(textDoc);
|
|
223
|
+
return service.provideSemanticTokensFull(state);
|
|
224
|
+
});
|
|
225
|
+
textDocuments.listen(connection);
|
|
226
|
+
connection.listen();
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
//#endregion
|
|
230
|
+
export { startStdioServer };
|
package/package.json
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "treelsp",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "A Langium-style LSP generator using Tree-sitter as the parsing backend",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"import": "./dist/index.js"
|
|
12
|
+
},
|
|
13
|
+
"./codegen": {
|
|
14
|
+
"types": "./dist/codegen/index.d.ts",
|
|
15
|
+
"import": "./dist/codegen/index.js"
|
|
16
|
+
},
|
|
17
|
+
"./runtime": {
|
|
18
|
+
"types": "./dist/runtime/index.d.ts",
|
|
19
|
+
"import": "./dist/runtime/index.js"
|
|
20
|
+
},
|
|
21
|
+
"./server": {
|
|
22
|
+
"types": "./dist/server/index.d.ts",
|
|
23
|
+
"import": "./dist/server/index.js"
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
"files": [
|
|
27
|
+
"dist",
|
|
28
|
+
"README.md"
|
|
29
|
+
],
|
|
30
|
+
"keywords": [
|
|
31
|
+
"lsp",
|
|
32
|
+
"language-server",
|
|
33
|
+
"tree-sitter",
|
|
34
|
+
"parser",
|
|
35
|
+
"typescript"
|
|
36
|
+
],
|
|
37
|
+
"author": "Dhrubomoy Das Gupta",
|
|
38
|
+
"license": "MIT",
|
|
39
|
+
"repository": {
|
|
40
|
+
"type": "git",
|
|
41
|
+
"url": "https://github.com/dhrubomoy/treelsp.git",
|
|
42
|
+
"directory": "packages/treelsp"
|
|
43
|
+
},
|
|
44
|
+
"homepage": "https://github.com/dhrubomoy/treelsp#readme",
|
|
45
|
+
"bugs": "https://github.com/dhrubomoy/treelsp/issues",
|
|
46
|
+
"dependencies": {
|
|
47
|
+
"vscode-languageserver": "^9.0.1",
|
|
48
|
+
"vscode-languageserver-textdocument": "^1.0.12",
|
|
49
|
+
"web-tree-sitter": "^0.24.4"
|
|
50
|
+
},
|
|
51
|
+
"devDependencies": {
|
|
52
|
+
"tsdown": "^0.2.17",
|
|
53
|
+
"typescript": "^5.7.3",
|
|
54
|
+
"vitest": "^2.1.8"
|
|
55
|
+
},
|
|
56
|
+
"scripts": {
|
|
57
|
+
"build": "tsdown",
|
|
58
|
+
"dev": "tsdown --watch",
|
|
59
|
+
"test": "vitest run",
|
|
60
|
+
"test:watch": "vitest",
|
|
61
|
+
"lint": "eslint src",
|
|
62
|
+
"clean": "rm -rf dist *.tsbuildinfo"
|
|
63
|
+
}
|
|
64
|
+
}
|