@razdolbai/merls 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (104) hide show
  1. package/.serena/memories/conventions.md +6 -0
  2. package/.serena/memories/core.md +8 -0
  3. package/.serena/memories/memory_maintenance.md +33 -0
  4. package/.serena/memories/suggested_commands.md +4 -0
  5. package/.serena/memories/task_completion.md +7 -0
  6. package/.serena/memories/tech_stack.md +6 -0
  7. package/.serena/project.yml +132 -0
  8. package/AGENTS.md +63 -0
  9. package/README.md +137 -0
  10. package/dist/src/asm/diagnostics.js +202 -0
  11. package/dist/src/asm/document.js +26 -0
  12. package/dist/src/asm/expression.js +163 -0
  13. package/dist/src/asm/lexer.js +122 -0
  14. package/dist/src/asm/local-labels.js +140 -0
  15. package/dist/src/asm/metadata.js +101 -0
  16. package/dist/src/asm/parser.js +118 -0
  17. package/dist/src/asm/symbols.js +40 -0
  18. package/dist/src/asm/syntax.js +44 -0
  19. package/dist/src/asm/workspace.js +73 -0
  20. package/dist/src/cli.js +21 -0
  21. package/dist/src/index.js +4 -0
  22. package/dist/src/lsp/completion.js +32 -0
  23. package/dist/src/lsp/diagnostics.js +63 -0
  24. package/dist/src/lsp/document-symbols.js +80 -0
  25. package/dist/src/lsp/hover.js +75 -0
  26. package/dist/src/lsp/symbol-navigation.js +181 -0
  27. package/dist/src/lsp/workspace-symbols.js +17 -0
  28. package/dist/src/server.js +77 -0
  29. package/dist/test/bootstrap.test.js +11 -0
  30. package/dist/test/cli-contract.test.js +74 -0
  31. package/dist/test/coc-config.test.js +21 -0
  32. package/dist/test/completion.test.js +126 -0
  33. package/dist/test/definition-references.test.js +126 -0
  34. package/dist/test/diagnostics.test.js +66 -0
  35. package/dist/test/document-model.test.js +30 -0
  36. package/dist/test/document-symbol.test.js +107 -0
  37. package/dist/test/expression.test.js +100 -0
  38. package/dist/test/fixture-corpus.test.js +33 -0
  39. package/dist/test/hover.test.js +142 -0
  40. package/dist/test/lexer.test.js +53 -0
  41. package/dist/test/line-parser.test.js +67 -0
  42. package/dist/test/local-labels.test.js +43 -0
  43. package/dist/test/metadata.test.js +27 -0
  44. package/dist/test/publish-diagnostics.test.js +137 -0
  45. package/dist/test/run-tests.js +132 -0
  46. package/dist/test/server-entrypoint.test.js +14 -0
  47. package/dist/test/server-initialize.test.js +77 -0
  48. package/dist/test/symbols.test.js +37 -0
  49. package/dist/test/syntax-shape.test.js +18 -0
  50. package/dist/test/workspace-symbol.test.js +113 -0
  51. package/dist/test/workspace.test.js +24 -0
  52. package/examples/coc-settings.json +18 -0
  53. package/package.json +26 -0
  54. package/publish.ps1 +9 -0
  55. package/src/asm/diagnostics.ts +294 -0
  56. package/src/asm/document.ts +43 -0
  57. package/src/asm/expression.ts +242 -0
  58. package/src/asm/lexer.ts +197 -0
  59. package/src/asm/local-labels.ts +204 -0
  60. package/src/asm/metadata.ts +150 -0
  61. package/src/asm/parser.ts +197 -0
  62. package/src/asm/symbols.ts +55 -0
  63. package/src/asm/syntax.ts +76 -0
  64. package/src/asm/workspace.ts +105 -0
  65. package/src/cli.ts +24 -0
  66. package/src/index.ts +1 -0
  67. package/src/lsp/completion.ts +42 -0
  68. package/src/lsp/diagnostics.ts +82 -0
  69. package/src/lsp/document-symbols.ts +111 -0
  70. package/src/lsp/hover.ts +90 -0
  71. package/src/lsp/symbol-navigation.ts +244 -0
  72. package/src/lsp/workspace-symbols.ts +24 -0
  73. package/src/server.ts +121 -0
  74. package/test/bootstrap.test.ts +7 -0
  75. package/test/cli-contract.test.ts +94 -0
  76. package/test/coc-config.test.ts +28 -0
  77. package/test/completion.test.ts +151 -0
  78. package/test/definition-references.test.ts +152 -0
  79. package/test/diagnostics.test.ts +129 -0
  80. package/test/document-model.test.ts +29 -0
  81. package/test/document-symbol.test.ts +131 -0
  82. package/test/expression.test.ts +111 -0
  83. package/test/fixture-corpus.test.ts +33 -0
  84. package/test/fixtures/invalid/65816-bank-ops.asm +17 -0
  85. package/test/fixtures/invalid/65816-long-addressing.asm +26 -0
  86. package/test/fixtures/valid/merlin32-linkscript.asm +16 -0
  87. package/test/fixtures/valid/merlin32-main-6502.asm +103 -0
  88. package/test/fixtures/valid/smoke-test.asm +7 -0
  89. package/test/hover.test.ts +175 -0
  90. package/test/lexer.test.ts +87 -0
  91. package/test/line-parser.test.ts +69 -0
  92. package/test/local-labels.test.ts +47 -0
  93. package/test/metadata.test.ts +27 -0
  94. package/test/publish-diagnostics.test.ts +206 -0
  95. package/test/run-tests.ts +139 -0
  96. package/test/server-entrypoint.test.ts +11 -0
  97. package/test/server-initialize.test.ts +101 -0
  98. package/test/smoke/run-smoke.ps1 +177 -0
  99. package/test/smoke/vimrc +17 -0
  100. package/test/symbols.test.ts +41 -0
  101. package/test/syntax-shape.test.ts +18 -0
  102. package/test/workspace-symbol.test.ts +139 -0
  103. package/test/workspace.test.ts +29 -0
  104. package/tsconfig.json +16 -0
@@ -0,0 +1,82 @@
1
+ import { fileURLToPath } from "node:url";
2
+
3
+ import {
4
+ DiagnosticSeverity,
5
+ type Diagnostic as LspDiagnostic
6
+ } from "vscode-languageserver/node";
7
+
8
+ import { parseDocument } from "../asm/document";
9
+ import {
10
+ collectWorkspaceDiagnostics,
11
+ type Diagnostic as AsmDiagnostic,
12
+ type DocumentEntry
13
+ } from "../asm/diagnostics";
14
+
15
+ export function collectDiagnosticsByUri(
16
+ openDocuments: ReadonlyMap<string, string>
17
+ ): Map<string, readonly LspDiagnostic[]> {
18
+ const sourcesByFilePath = new Map<string, string>();
19
+ const uriByFilePath = new Map<string, string>();
20
+ const entries: DocumentEntry[] = [];
21
+
22
+ for (const [uri, source] of openDocuments.entries()) {
23
+ const filePath = uriToFilePath(uri);
24
+ sourcesByFilePath.set(filePath, source);
25
+ uriByFilePath.set(filePath, uri);
26
+ entries.push({
27
+ filePath,
28
+ document: parseDocument(source)
29
+ });
30
+ }
31
+
32
+ const diagnosticsByFilePath = new Map<string, LspDiagnostic[]>();
33
+ for (const entry of entries) {
34
+ diagnosticsByFilePath.set(entry.filePath, []);
35
+ }
36
+
37
+ for (const diagnostic of collectWorkspaceDiagnostics(entries)) {
38
+ const source = sourcesByFilePath.get(diagnostic.filePath);
39
+ const diagnostics = diagnosticsByFilePath.get(diagnostic.filePath);
40
+ if (source === undefined || diagnostics === undefined) {
41
+ continue;
42
+ }
43
+
44
+ diagnostics.push(toLspDiagnostic(source, diagnostic));
45
+ }
46
+
47
+ const diagnosticsByUri = new Map<string, readonly LspDiagnostic[]>();
48
+ for (const [filePath, uri] of uriByFilePath.entries()) {
49
+ diagnosticsByUri.set(uri, diagnosticsByFilePath.get(filePath) ?? []);
50
+ }
51
+
52
+ return diagnosticsByUri;
53
+ }
54
+
55
+ function toLspDiagnostic(source: string, diagnostic: AsmDiagnostic): LspDiagnostic {
56
+ const lineText = source.split(/\r?\n/)[diagnostic.line] ?? "";
57
+
58
+ return {
59
+ severity: DiagnosticSeverity.Error,
60
+ message: diagnostic.message,
61
+ source: "merls",
62
+ code: diagnostic.code,
63
+ range: {
64
+ start: {
65
+ line: diagnostic.line,
66
+ character: 0
67
+ },
68
+ end: {
69
+ line: diagnostic.line,
70
+ character: lineText.length
71
+ }
72
+ }
73
+ };
74
+ }
75
+
76
+ function uriToFilePath(uri: string): string {
77
+ if (uri.startsWith("file://")) {
78
+ return fileURLToPath(uri);
79
+ }
80
+
81
+ return uri;
82
+ }
@@ -0,0 +1,111 @@
1
+ import {
2
+ type Location,
3
+ type Range,
4
+ type SymbolInformation,
5
+ SymbolKind
6
+ } from "vscode-languageserver/node";
7
+
8
+ import { parseDocument } from "../asm/document";
9
+ import { type ParsedLine } from "../asm/parser";
10
+
11
+ export function buildDocumentSymbols(
12
+ uri: string,
13
+ source: string
14
+ ): SymbolInformation[] {
15
+ const document = parseDocument(source);
16
+ const symbols: SymbolInformation[] = [];
17
+
18
+ for (const line of document.lines) {
19
+ const symbol = toSymbolInformation(uri, line.node, line.line);
20
+ if (symbol !== null) {
21
+ symbols.push(symbol);
22
+ }
23
+ }
24
+
25
+ return symbols;
26
+ }
27
+
28
+ function toSymbolInformation(
29
+ uri: string,
30
+ node: ParsedLine,
31
+ line: number
32
+ ): SymbolInformation | null {
33
+ const name = getNodeName(node);
34
+ const kind = getNodeKind(node);
35
+
36
+ if (name === null || kind === null) {
37
+ return null;
38
+ }
39
+
40
+ return {
41
+ name,
42
+ kind,
43
+ location: createLocation(uri, line, name)
44
+ };
45
+ }
46
+
47
+ function getNodeName(node: ParsedLine): string | null {
48
+ if (node.shape === "equate") {
49
+ return node.label;
50
+ }
51
+
52
+ if (node.shape === "labelOnly") {
53
+ return node.label;
54
+ }
55
+
56
+ if (node.shape === "instruction" && node.label !== null) {
57
+ return node.label;
58
+ }
59
+
60
+ if (node.shape === "directive" && node.label !== null) {
61
+ return node.label;
62
+ }
63
+
64
+ if (node.shape === "data" && node.label !== null) {
65
+ return node.label;
66
+ }
67
+
68
+ return null;
69
+ }
70
+
71
+ function getNodeKind(node: ParsedLine): SymbolKind | null {
72
+ if (node.shape === "equate") {
73
+ return SymbolKind.Variable;
74
+ }
75
+
76
+ if (node.shape === "labelOnly") {
77
+ return SymbolKind.Method;
78
+ }
79
+
80
+ if (node.shape === "instruction" && node.label !== null) {
81
+ return SymbolKind.Method;
82
+ }
83
+
84
+ if (node.shape === "directive" && node.label !== null) {
85
+ return SymbolKind.Field;
86
+ }
87
+
88
+ if (node.shape === "data" && node.label !== null) {
89
+ return SymbolKind.Field;
90
+ }
91
+
92
+ return null;
93
+ }
94
+
95
+ function createLocation(uri: string, line: number, name: string): Location {
96
+ const range: Range = {
97
+ start: {
98
+ line,
99
+ character: 0
100
+ },
101
+ end: {
102
+ line,
103
+ character: name.length
104
+ }
105
+ };
106
+
107
+ return {
108
+ uri,
109
+ range
110
+ };
111
+ }
@@ -0,0 +1,90 @@
1
+ import { type Hover } from "vscode-languageserver/node";
2
+
3
+ import { directiveTable, opcodeTable } from "../asm/metadata";
4
+ import { parseDocument } from "../asm/document";
5
+ import { lexSource, type Token } from "../asm/lexer";
6
+ import { findDefinition } from "./symbol-navigation";
7
+
8
+ export function buildHover(
9
+ openDocuments: ReadonlyMap<string, string>,
10
+ uri: string,
11
+ line: number,
12
+ character: number
13
+ ): Hover | null {
14
+ const source = openDocuments.get(uri);
15
+ if (source === undefined) {
16
+ return null;
17
+ }
18
+
19
+ const documentLine = parseDocument(source).lines[line];
20
+ const lexedLine = lexSource(source).lines[line];
21
+ if (documentLine === undefined || lexedLine === undefined) {
22
+ return null;
23
+ }
24
+
25
+ const token = tokenAtCharacter(lexedLine.tokens, character);
26
+ if (token?.kind === "mnemonic") {
27
+ const definition = opcodeTable.get(token.lexeme.toLowerCase());
28
+ if (definition !== undefined) {
29
+ return {
30
+ contents: `Opcode ${definition.mnemonic}: ${definition.modes.join(", ")}`
31
+ };
32
+ }
33
+ }
34
+
35
+ if (token?.kind === "directive") {
36
+ const definition = directiveTable.get(token.lexeme.toLowerCase());
37
+ if (definition !== undefined) {
38
+ return {
39
+ contents: `Directive ${definition.name}: ${definition.summary}`
40
+ };
41
+ }
42
+ }
43
+
44
+ if (token?.kind === "identifier" || token?.kind === "label" || token?.kind === "localLabel") {
45
+ const lexemeLower = token.lexeme.toLowerCase();
46
+ if (lexemeLower === "a" || lexemeLower === "x" || lexemeLower === "y") {
47
+ return {
48
+ contents: `Register ${token.lexeme.toUpperCase()}`
49
+ };
50
+ }
51
+
52
+ const definition = findDefinition(openDocuments, uri, line);
53
+ if (definition !== null) {
54
+ return {
55
+ contents: `Symbol ${token.lexeme} defined at line ${definition.range.start.line}`
56
+ };
57
+ }
58
+ }
59
+
60
+ const node = documentLine.node;
61
+ if (node.shape === "instruction") {
62
+ const definition = opcodeTable.get(node.mnemonic);
63
+ if (definition !== undefined) {
64
+ return {
65
+ contents: `Opcode ${definition.mnemonic}: ${definition.modes.join(", ")}`
66
+ };
67
+ }
68
+ }
69
+
70
+ if (node.shape === "directive") {
71
+ const definition = directiveTable.get(node.directive);
72
+ if (definition !== undefined) {
73
+ return {
74
+ contents: `Directive ${definition.name}: ${definition.summary}`
75
+ };
76
+ }
77
+ }
78
+
79
+ return null;
80
+ }
81
+
82
+ function tokenAtCharacter(tokens: readonly Token[], character: number): Token | null {
83
+ for (const token of tokens) {
84
+ if (character >= token.start && character < token.end) {
85
+ return token;
86
+ }
87
+ }
88
+
89
+ return null;
90
+ }
@@ -0,0 +1,244 @@
1
+ import { type Location } from "vscode-languageserver/node";
2
+
3
+ import { parseDocument } from "../asm/document";
4
+ import { type Expression, type Operand } from "../asm/expression";
5
+ import { type ParsedLine } from "../asm/parser";
6
+
7
+ type SymbolDefinition = {
8
+ name: string;
9
+ location: Location;
10
+ };
11
+
12
+ type SymbolReference = {
13
+ name: string;
14
+ location: Location;
15
+ };
16
+
17
+ export function findDefinition(
18
+ openDocuments: ReadonlyMap<string, string>,
19
+ uri: string,
20
+ line: number
21
+ ): Location | null {
22
+ const targetName = getSymbolAtLine(openDocuments.get(uri), line);
23
+ if (targetName === null) {
24
+ return null;
25
+ }
26
+
27
+ for (const [documentUri, source] of openDocuments.entries()) {
28
+ for (const definition of collectDefinitions(documentUri, source)) {
29
+ if (definition.name === targetName) {
30
+ return definition.location;
31
+ }
32
+ }
33
+ }
34
+
35
+ return null;
36
+ }
37
+
38
+ export function findReferences(
39
+ openDocuments: ReadonlyMap<string, string>,
40
+ uri: string,
41
+ line: number,
42
+ includeDeclaration: boolean
43
+ ): Location[] {
44
+ const targetName = getSymbolAtLine(openDocuments.get(uri), line);
45
+ if (targetName === null) {
46
+ return [];
47
+ }
48
+
49
+ const locations: Location[] = [];
50
+
51
+ for (const [documentUri, source] of openDocuments.entries()) {
52
+ if (includeDeclaration) {
53
+ for (const definition of collectDefinitions(documentUri, source)) {
54
+ if (definition.name === targetName) {
55
+ locations.push(definition.location);
56
+ }
57
+ }
58
+ }
59
+
60
+ for (const reference of collectReferences(documentUri, source)) {
61
+ if (reference.name === targetName) {
62
+ locations.push(reference.location);
63
+ }
64
+ }
65
+ }
66
+
67
+ return locations;
68
+ }
69
+
70
+ function getSymbolAtLine(source: string | undefined, line: number): string | null {
71
+ if (source === undefined) {
72
+ return null;
73
+ }
74
+
75
+ const node = parseDocument(source).lines[line]?.node;
76
+ if (node === undefined) {
77
+ return null;
78
+ }
79
+
80
+ if (node.shape === "labelOnly") {
81
+ return node.label;
82
+ }
83
+
84
+ if (node.shape === "instruction") {
85
+ if (node.label !== null) {
86
+ return node.label;
87
+ }
88
+
89
+ return readOperandIdentifier(node.operand);
90
+ }
91
+
92
+ if (node.shape === "directive") {
93
+ if (node.label !== null) {
94
+ return node.label;
95
+ }
96
+
97
+ return readExpressionIdentifier(node.operand);
98
+ }
99
+
100
+ if (node.shape === "equate") {
101
+ return node.label;
102
+ }
103
+
104
+ if (node.shape === "data" && node.label !== null) {
105
+ return node.label;
106
+ }
107
+
108
+ return null;
109
+ }
110
+
111
+ function collectDefinitions(uri: string, source: string): readonly SymbolDefinition[] {
112
+ const document = parseDocument(source);
113
+ const definitions: SymbolDefinition[] = [];
114
+
115
+ for (const line of document.lines) {
116
+ const name = getDefinedName(line.node);
117
+ if (name === null) {
118
+ continue;
119
+ }
120
+
121
+ definitions.push({
122
+ name,
123
+ location: createLocation(uri, line.line, name)
124
+ });
125
+ }
126
+
127
+ return definitions;
128
+ }
129
+
130
+ function collectReferences(uri: string, source: string): readonly SymbolReference[] {
131
+ const document = parseDocument(source);
132
+ const references: SymbolReference[] = [];
133
+
134
+ for (const line of document.lines) {
135
+ for (const name of getReferencedNames(line.node)) {
136
+ references.push({
137
+ name,
138
+ location: createLocation(uri, line.line, name)
139
+ });
140
+ }
141
+ }
142
+
143
+ return references;
144
+ }
145
+
146
+ function getDefinedName(node: ParsedLine): string | null {
147
+ if (node.shape === "equate") {
148
+ return node.label;
149
+ }
150
+
151
+ if (node.shape === "labelOnly") {
152
+ return node.label;
153
+ }
154
+
155
+ if (node.shape === "instruction" && node.label !== null) {
156
+ return node.label;
157
+ }
158
+
159
+ if (node.shape === "directive" && node.label !== null) {
160
+ return node.label;
161
+ }
162
+
163
+ if (node.shape === "data" && node.label !== null) {
164
+ return node.label;
165
+ }
166
+
167
+ return null;
168
+ }
169
+
170
+ function getReferencedNames(node: ParsedLine): readonly string[] {
171
+ if (node.shape === "instruction" && node.operand !== null) {
172
+ return collectExpressionIdentifiers(node.operand.expression);
173
+ }
174
+
175
+ if (node.shape === "directive" && node.operand !== null) {
176
+ return collectExpressionIdentifiers(node.operand);
177
+ }
178
+
179
+ if (node.shape === "equate") {
180
+ return collectExpressionIdentifiers(node.expression);
181
+ }
182
+
183
+ return [];
184
+ }
185
+
186
+ function readOperandIdentifier(operand: Operand | null): string | null {
187
+ if (operand === null) {
188
+ return null;
189
+ }
190
+
191
+ return readExpressionIdentifier(operand.expression);
192
+ }
193
+
194
+ function readExpressionIdentifier(expression: Expression | null): string | null {
195
+ if (expression === null) {
196
+ return null;
197
+ }
198
+
199
+ if (expression.kind === "identifier") {
200
+ return expression.value;
201
+ }
202
+
203
+ if (expression.kind === "modifier") {
204
+ return readExpressionIdentifier(expression.expression);
205
+ }
206
+
207
+ if (expression.kind === "binary") {
208
+ return readExpressionIdentifier(expression.left);
209
+ }
210
+
211
+ return null;
212
+ }
213
+
214
+ function collectExpressionIdentifiers(expression: Expression): readonly string[] {
215
+ switch (expression.kind) {
216
+ case "identifier":
217
+ return [expression.value];
218
+ case "modifier":
219
+ return collectExpressionIdentifiers(expression.expression);
220
+ case "binary":
221
+ return [
222
+ ...collectExpressionIdentifiers(expression.left),
223
+ ...collectExpressionIdentifiers(expression.right)
224
+ ];
225
+ default:
226
+ return [];
227
+ }
228
+ }
229
+
230
+ function createLocation(uri: string, line: number, name: string): Location {
231
+ return {
232
+ uri,
233
+ range: {
234
+ start: {
235
+ line,
236
+ character: 0
237
+ },
238
+ end: {
239
+ line,
240
+ character: name.length
241
+ }
242
+ }
243
+ };
244
+ }
@@ -0,0 +1,24 @@
1
+ import { type SymbolInformation } from "vscode-languageserver/node";
2
+
3
+ import { buildDocumentSymbols } from "./document-symbols";
4
+
5
+ export function buildWorkspaceSymbols(
6
+ openDocuments: ReadonlyMap<string, string>,
7
+ query: string
8
+ ): SymbolInformation[] {
9
+ const normalizedQuery = query.trim().toLowerCase();
10
+ const symbols: SymbolInformation[] = [];
11
+
12
+ for (const [uri, source] of openDocuments.entries()) {
13
+ for (const symbol of buildDocumentSymbols(uri, source)) {
14
+ if (
15
+ normalizedQuery.length === 0 ||
16
+ symbol.name.toLowerCase().includes(normalizedQuery)
17
+ ) {
18
+ symbols.push(symbol);
19
+ }
20
+ }
21
+ }
22
+
23
+ return symbols;
24
+ }
package/src/server.ts ADDED
@@ -0,0 +1,121 @@
1
+ import {
2
+ Connection,
3
+ ProposedFeatures,
4
+ TextDocumentSyncKind,
5
+ createConnection
6
+ } from "vscode-languageserver/node";
7
+
8
+ import { buildDocumentSymbols } from "./lsp/document-symbols";
9
+ import { buildCompletionItems } from "./lsp/completion";
10
+ import { collectDiagnosticsByUri } from "./lsp/diagnostics";
11
+ import { buildHover } from "./lsp/hover";
12
+ import { findDefinition, findReferences } from "./lsp/symbol-navigation";
13
+ import { buildWorkspaceSymbols } from "./lsp/workspace-symbols";
14
+
15
+ export function createServerConnection(
16
+ inputStream: NodeJS.ReadableStream = process.stdin,
17
+ outputStream: NodeJS.WritableStream = process.stdout
18
+ ): Connection {
19
+ return createConnection(ProposedFeatures.all, inputStream, outputStream);
20
+ }
21
+
22
+ export function startServer(
23
+ inputStream: NodeJS.ReadableStream = process.stdin,
24
+ outputStream: NodeJS.WritableStream = process.stdout
25
+ ): Connection {
26
+ const connection = createServerConnection(inputStream, outputStream);
27
+ const openDocuments = new Map<string, string>();
28
+
29
+ connection.onInitialize(() => ({
30
+ capabilities: {
31
+ completionProvider: {},
32
+ definitionProvider: true,
33
+ documentSymbolProvider: true,
34
+ hoverProvider: true,
35
+ referencesProvider: true,
36
+ workspaceSymbolProvider: true,
37
+ textDocumentSync: {
38
+ openClose: true,
39
+ change: TextDocumentSyncKind.Full
40
+ }
41
+ }
42
+ }));
43
+ connection.onDidOpenTextDocument((params) => {
44
+ openDocuments.set(params.textDocument.uri, params.textDocument.text);
45
+ publishDiagnostics();
46
+ });
47
+ connection.onDidChangeTextDocument((params) => {
48
+ const nextText = params.contentChanges.at(-1)?.text;
49
+ if (nextText === undefined) {
50
+ return;
51
+ }
52
+
53
+ openDocuments.set(params.textDocument.uri, nextText);
54
+ publishDiagnostics();
55
+ });
56
+ connection.onDidCloseTextDocument((params) => {
57
+ openDocuments.delete(params.textDocument.uri);
58
+ connection.sendDiagnostics({
59
+ uri: params.textDocument.uri,
60
+ diagnostics: []
61
+ });
62
+ publishDiagnostics();
63
+ });
64
+ connection.onDocumentSymbol((params) => {
65
+ const source = openDocuments.get(params.textDocument.uri);
66
+ if (source === undefined) {
67
+ return [];
68
+ }
69
+
70
+ return buildDocumentSymbols(params.textDocument.uri, source);
71
+ });
72
+ connection.onWorkspaceSymbol((params) =>
73
+ buildWorkspaceSymbols(openDocuments, params.query)
74
+ );
75
+ connection.onDefinition((params) =>
76
+ findDefinition(
77
+ openDocuments,
78
+ params.textDocument.uri,
79
+ params.position.line
80
+ )
81
+ );
82
+ connection.onReferences((params) =>
83
+ findReferences(
84
+ openDocuments,
85
+ params.textDocument.uri,
86
+ params.position.line,
87
+ params.context.includeDeclaration
88
+ )
89
+ );
90
+ connection.onHover((params) =>
91
+ buildHover(
92
+ openDocuments,
93
+ params.textDocument.uri,
94
+ params.position.line,
95
+ params.position.character
96
+ )
97
+ );
98
+ connection.onCompletion((params) =>
99
+ buildCompletionItems(
100
+ openDocuments,
101
+ params.textDocument.uri,
102
+ params.position.line
103
+ )
104
+ );
105
+
106
+ function publishDiagnostics(): void {
107
+ for (const [uri, diagnostics] of collectDiagnosticsByUri(openDocuments).entries()) {
108
+ connection.sendDiagnostics({
109
+ uri,
110
+ diagnostics: [...diagnostics]
111
+ });
112
+ }
113
+ }
114
+
115
+ connection.listen();
116
+ return connection;
117
+ }
118
+
119
+ if (require.main === module) {
120
+ startServer();
121
+ }
@@ -0,0 +1,7 @@
1
+ import assert from "node:assert/strict";
2
+
3
+ import { projectName } from "../src/index";
4
+
5
+ export function runBootstrapTest(): void {
6
+ assert.equal(projectName, "merls");
7
+ }