naider 1.24.5 → 2.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/README.md +133 -112
- package/SPEC.naide +8 -6
- package/bin/naide.js +509 -124
- package/bin/postinstall.js +5 -15
- package/examples/model.naide +1 -1
- package/lsp/server.js +210 -101
- package/mcp/server.js +247 -22
- package/package.json +8 -5
- package/src/gen.js +853 -324
- package/src/generator-c.js +34 -14
- package/src/generator-go.js +26 -0
- package/src/generator.js +5 -1
- package/src/index.js +34 -14
- package/src/parser.js +15 -14
- package/src/project-tools.js +785 -0
- package/src/typechecker.js +147 -18
- package/vscode-naide/README.md +4 -6
- package/vscode-naide/extension.js +1 -2
- package/vscode-naide/package-lock.json +2 -2
- package/vscode-naide/package.json +3 -15
- package/SPEC-X.nx +0 -276
- package/assets/nx.ico +0 -0
- package/assets/nx_check.png +0 -0
- package/examples/ai-app.nx +0 -21
- package/examples/async.nx +0 -40
- package/examples/crud.nx +0 -64
- package/examples/fullapp.nx +0 -74
- package/examples/hello.nx +0 -29
- package/examples/server.nx +0 -27
- package/examples/sql-app.nx +0 -23
- package/examples/todo-api.nx +0 -31
- package/src/preprocess.js +0 -308
- package/vscode-naide/language-configuration-nx.json +0 -32
- package/vscode-naide/naide-lang-1.1.0.vsix +0 -0
- package/vscode-naide/syntaxes/naidex.tmLanguage.json +0 -287
package/bin/postinstall.js
CHANGED
|
@@ -32,19 +32,14 @@ function registerWindows() {
|
|
|
32
32
|
const regAdd = (key, value) => run(`reg add "${key}" /ve /d "${value}" /f`);
|
|
33
33
|
|
|
34
34
|
regAdd('HKCU\\Software\\Classes\\.naide', 'NAIDEFile');
|
|
35
|
-
regAdd('HKCU\\Software\\Classes\\.nx', 'NAIDEXFile');
|
|
36
35
|
regAdd('HKCU\\Software\\Classes\\NAIDEFile', 'NAIDE Source File');
|
|
37
|
-
regAdd('HKCU\\Software\\Classes\\NAIDEXFile', 'NAIDE-X Source File');
|
|
38
36
|
regAdd('HKCU\\Software\\Classes\\NAIDEFile\\shell\\open\\command', cmd);
|
|
39
|
-
regAdd('HKCU\\Software\\Classes\\NAIDEXFile\\shell\\open\\command', cmd);
|
|
40
37
|
|
|
41
38
|
const assetsDir = resolve(__dirname, '..', 'assets');
|
|
42
39
|
const naideIco = resolve(assetsDir, 'naide.ico');
|
|
43
|
-
const nxIco = resolve(assetsDir, 'nx.ico');
|
|
44
40
|
regAdd('HKCU\\Software\\Classes\\NAIDEFile\\DefaultIcon', naideIco);
|
|
45
|
-
regAdd('HKCU\\Software\\Classes\\NAIDEXFile\\DefaultIcon', nxIco);
|
|
46
41
|
|
|
47
|
-
console.log(' .naide
|
|
42
|
+
console.log(' .naide file association registered (Windows)');
|
|
48
43
|
}
|
|
49
44
|
|
|
50
45
|
function registerMacOS() {
|
|
@@ -68,7 +63,7 @@ function registerMacOS() {
|
|
|
68
63
|
<key>CFBundleDocumentTypes</key>
|
|
69
64
|
<array>
|
|
70
65
|
<dict>
|
|
71
|
-
<key>CFBundleTypeExtensions</key><array><string>naide</string
|
|
66
|
+
<key>CFBundleTypeExtensions</key><array><string>naide</string></array>
|
|
72
67
|
<key>CFBundleTypeName</key><string>NAIDE Source</string>
|
|
73
68
|
<key>CFBundleTypeRole</key><string>Editor</string>
|
|
74
69
|
</dict>
|
|
@@ -81,7 +76,7 @@ function registerMacOS() {
|
|
|
81
76
|
|
|
82
77
|
run(`/System/Library/Frameworks/CoreServices.framework/Frameworks/LaunchServices.framework/Support/lsregister -f "${handlerApp}"`);
|
|
83
78
|
|
|
84
|
-
console.log(' .naide
|
|
79
|
+
console.log(' .naide file association registered (macOS)');
|
|
85
80
|
}
|
|
86
81
|
|
|
87
82
|
function registerLinux() {
|
|
@@ -96,26 +91,21 @@ function registerLinux() {
|
|
|
96
91
|
<comment>NAIDE Source File</comment>
|
|
97
92
|
<glob pattern="*.naide"/>
|
|
98
93
|
</mime-type>
|
|
99
|
-
<mime-type type="text/x-naidex">
|
|
100
|
-
<comment>NAIDE-X Source File</comment>
|
|
101
|
-
<glob pattern="*.nx"/>
|
|
102
|
-
</mime-type>
|
|
103
94
|
</mime-info>`);
|
|
104
95
|
|
|
105
96
|
writeFileSync(join(appDir, 'naide.desktop'), `[Desktop Entry]
|
|
106
97
|
Type=Application
|
|
107
98
|
Name=NAIDE
|
|
108
99
|
Exec=node "${naideBin}" %f
|
|
109
|
-
MimeType=text/x-naide;
|
|
100
|
+
MimeType=text/x-naide;
|
|
110
101
|
Terminal=true
|
|
111
102
|
Categories=Development;
|
|
112
103
|
`);
|
|
113
104
|
|
|
114
105
|
run('update-mime-database ~/.local/share/mime');
|
|
115
106
|
run('xdg-mime default naide.desktop text/x-naide');
|
|
116
|
-
run('xdg-mime default naide.desktop text/x-naidex');
|
|
117
107
|
|
|
118
|
-
console.log(' .naide
|
|
108
|
+
console.log(' .naide file association registered (Linux)');
|
|
119
109
|
}
|
|
120
110
|
|
|
121
111
|
console.log('NAIDE: Registering file extensions...');
|
package/examples/model.naide
CHANGED
package/lsp/server.js
CHANGED
|
@@ -1,24 +1,74 @@
|
|
|
1
1
|
import { compile } from '../src/index.js';
|
|
2
|
+
import { Lexer } from '../src/lexer.js';
|
|
2
3
|
|
|
3
4
|
const documents = new Map();
|
|
4
5
|
const symbolIndex = new Map();
|
|
5
|
-
let buffer =
|
|
6
|
+
let buffer = Buffer.alloc(0);
|
|
7
|
+
const MAX_HEADER_BYTES = 8 * 1024;
|
|
8
|
+
const MAX_MESSAGE_BYTES = 32 * 1024 * 1024;
|
|
6
9
|
|
|
7
|
-
process.stdin.setEncoding('utf-8');
|
|
8
10
|
process.stdin.on('data', (chunk) => {
|
|
9
|
-
buffer
|
|
11
|
+
buffer = Buffer.concat([buffer, chunk]);
|
|
10
12
|
while (true) {
|
|
11
|
-
const headerEnd = buffer.indexOf('\r\n\r\n');
|
|
12
|
-
if (headerEnd === -1)
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
13
|
+
const headerEnd = buffer.indexOf('\r\n\r\n', 0, 'ascii');
|
|
14
|
+
if (headerEnd === -1) {
|
|
15
|
+
if (buffer.length > MAX_HEADER_BYTES) {
|
|
16
|
+
console.error('NAIDE LSP: rejected an oversized message header');
|
|
17
|
+
process.exit(1);
|
|
18
|
+
}
|
|
19
|
+
break;
|
|
20
|
+
}
|
|
21
|
+
if (headerEnd > MAX_HEADER_BYTES) {
|
|
22
|
+
console.error('NAIDE LSP: rejected an oversized message header');
|
|
23
|
+
process.exit(1);
|
|
24
|
+
}
|
|
25
|
+
const header = buffer.toString('ascii', 0, headerEnd);
|
|
26
|
+
const lengthMatch = header.match(/^Content-Length:\s*(\d+)\s*$/im);
|
|
27
|
+
if (!lengthMatch) {
|
|
28
|
+
buffer = buffer.subarray(headerEnd + 4);
|
|
29
|
+
console.error('NAIDE LSP: discarded a message with no Content-Length header');
|
|
30
|
+
continue;
|
|
31
|
+
}
|
|
32
|
+
const contentLength = Number(lengthMatch[1]);
|
|
33
|
+
if (!Number.isSafeInteger(contentLength) || contentLength > MAX_MESSAGE_BYTES) {
|
|
34
|
+
console.error('NAIDE LSP: rejected an oversized or invalid message body');
|
|
35
|
+
process.exit(1);
|
|
36
|
+
}
|
|
17
37
|
const start = headerEnd + 4;
|
|
18
38
|
if (buffer.length < start + contentLength) break;
|
|
19
|
-
const content = buffer.
|
|
20
|
-
buffer = buffer.
|
|
21
|
-
|
|
39
|
+
const content = buffer.toString('utf8', start, start + contentLength);
|
|
40
|
+
buffer = buffer.subarray(start + contentLength);
|
|
41
|
+
|
|
42
|
+
let message;
|
|
43
|
+
try {
|
|
44
|
+
message = JSON.parse(content);
|
|
45
|
+
} catch (error) {
|
|
46
|
+
console.error(`NAIDE LSP: invalid JSON-RPC message: ${error.message}`);
|
|
47
|
+
send({ jsonrpc: '2.0', id: null, error: { code: -32700, message: 'Parse error' } });
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (!isValidMessage(message)) {
|
|
52
|
+
const id = message && typeof message === 'object' && !Array.isArray(message)
|
|
53
|
+
&& (typeof message.id === 'string' || typeof message.id === 'number' || message.id === null)
|
|
54
|
+
? message.id
|
|
55
|
+
: null;
|
|
56
|
+
send({ jsonrpc: '2.0', id, error: { code: -32600, message: 'Invalid Request' } });
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
try {
|
|
61
|
+
handleMessage(message);
|
|
62
|
+
} catch (error) {
|
|
63
|
+
console.error(`NAIDE LSP: failed to handle ${message.method}: ${error.stack || error}`);
|
|
64
|
+
if (message.id !== undefined) {
|
|
65
|
+
send({
|
|
66
|
+
jsonrpc: '2.0',
|
|
67
|
+
id: message.id,
|
|
68
|
+
error: { code: -32603, message: 'Internal error' },
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
}
|
|
22
72
|
}
|
|
23
73
|
});
|
|
24
74
|
|
|
@@ -30,6 +80,20 @@ function send(msg) {
|
|
|
30
80
|
function respond(id, result) { send({ jsonrpc: '2.0', id, result }); }
|
|
31
81
|
function notify(method, params) { send({ jsonrpc: '2.0', method, params }); }
|
|
32
82
|
|
|
83
|
+
function isValidMessage(message) {
|
|
84
|
+
return Boolean(message)
|
|
85
|
+
&& typeof message === 'object'
|
|
86
|
+
&& !Array.isArray(message)
|
|
87
|
+
&& message.jsonrpc === '2.0'
|
|
88
|
+
&& typeof message.method === 'string'
|
|
89
|
+
&& (message.id === undefined
|
|
90
|
+
|| message.id === null
|
|
91
|
+
|| typeof message.id === 'string'
|
|
92
|
+
|| (typeof message.id === 'number' && Number.isFinite(message.id)))
|
|
93
|
+
&& (message.params === undefined
|
|
94
|
+
|| (message.params !== null && typeof message.params === 'object'));
|
|
95
|
+
}
|
|
96
|
+
|
|
33
97
|
function handleMessage(msg) {
|
|
34
98
|
switch (msg.method) {
|
|
35
99
|
case 'initialize':
|
|
@@ -40,6 +104,7 @@ function handleMessage(msg) {
|
|
|
40
104
|
hoverProvider: true,
|
|
41
105
|
definitionProvider: true,
|
|
42
106
|
referencesProvider: true,
|
|
107
|
+
documentSymbolProvider: true,
|
|
43
108
|
}
|
|
44
109
|
});
|
|
45
110
|
break;
|
|
@@ -60,6 +125,7 @@ function handleMessage(msg) {
|
|
|
60
125
|
|
|
61
126
|
case 'textDocument/didClose':
|
|
62
127
|
documents.delete(msg.params.textDocument.uri);
|
|
128
|
+
symbolIndex.delete(msg.params.textDocument.uri);
|
|
63
129
|
notify('textDocument/publishDiagnostics', { uri: msg.params.textDocument.uri, diagnostics: [] });
|
|
64
130
|
break;
|
|
65
131
|
|
|
@@ -79,6 +145,25 @@ function handleMessage(msg) {
|
|
|
79
145
|
respond(msg.id, getReferences(msg.params));
|
|
80
146
|
break;
|
|
81
147
|
|
|
148
|
+
case 'textDocument/documentSymbol': {
|
|
149
|
+
const index = symbolIndex.get(msg.params.textDocument.uri);
|
|
150
|
+
const symbols = [];
|
|
151
|
+
if (index) {
|
|
152
|
+
for (const [name, definition] of index.definitions) {
|
|
153
|
+
const symbolKind = definition.kind === 'function' ? 12
|
|
154
|
+
: definition.kind === 'class' ? 5
|
|
155
|
+
: definition.kind === 'schema' ? 23 : 13;
|
|
156
|
+
const range = {
|
|
157
|
+
start: { line: definition.line, character: definition.col },
|
|
158
|
+
end: { line: definition.line, character: definition.col + name.length },
|
|
159
|
+
};
|
|
160
|
+
symbols.push({ name, kind: symbolKind, range, selectionRange: range });
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
respond(msg.id, symbols);
|
|
164
|
+
break;
|
|
165
|
+
}
|
|
166
|
+
|
|
82
167
|
case 'shutdown':
|
|
83
168
|
respond(msg.id, null);
|
|
84
169
|
break;
|
|
@@ -94,43 +179,36 @@ function handleMessage(msg) {
|
|
|
94
179
|
|
|
95
180
|
function validateDocument(uri) {
|
|
96
181
|
const text = documents.get(uri);
|
|
97
|
-
if (
|
|
182
|
+
if (text === undefined) return;
|
|
98
183
|
|
|
99
|
-
const mode = uri.endsWith('.nx') ? 'x' : 'naide';
|
|
100
184
|
const diagnostics = [];
|
|
101
185
|
|
|
102
186
|
try {
|
|
103
|
-
const result = compile(text, {
|
|
104
|
-
|
|
187
|
+
const result = compile(text, { typeCheck: true });
|
|
105
188
|
const lines = text.split('\n');
|
|
106
|
-
for (
|
|
107
|
-
const line =
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
severity: 2, source: 'naide',
|
|
130
|
-
message: `Type hint: value may not be boolean`
|
|
131
|
-
});
|
|
132
|
-
}
|
|
133
|
-
}
|
|
189
|
+
for (const diagnostic of result.typeErrors.errors) {
|
|
190
|
+
const line = Math.max(0, diagnostic.line - 1);
|
|
191
|
+
diagnostics.push({
|
|
192
|
+
range: {
|
|
193
|
+
start: { line, character: 0 },
|
|
194
|
+
end: { line, character: lines[line]?.length || 0 },
|
|
195
|
+
},
|
|
196
|
+
severity: 1,
|
|
197
|
+
source: 'naide',
|
|
198
|
+
message: diagnostic.message,
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
for (const diagnostic of result.typeErrors.warnings) {
|
|
202
|
+
const line = Math.max(0, diagnostic.line - 1);
|
|
203
|
+
diagnostics.push({
|
|
204
|
+
range: {
|
|
205
|
+
start: { line, character: 0 },
|
|
206
|
+
end: { line, character: lines[line]?.length || 0 },
|
|
207
|
+
},
|
|
208
|
+
severity: 2,
|
|
209
|
+
source: 'naide',
|
|
210
|
+
message: diagnostic.message,
|
|
211
|
+
});
|
|
134
212
|
}
|
|
135
213
|
} catch (e) {
|
|
136
214
|
const lineMatch = e.message.match(/line (\d+):(\d+)/);
|
|
@@ -149,8 +227,15 @@ function validateDocument(uri) {
|
|
|
149
227
|
}
|
|
150
228
|
|
|
151
229
|
function indexSymbols(uri, text) {
|
|
152
|
-
const symbols = { definitions: new Map(), references: new Map() };
|
|
230
|
+
const symbols = { definitions: new Map(), references: new Map(), identifiers: [] };
|
|
153
231
|
const lines = text.split('\n');
|
|
232
|
+
let tokens;
|
|
233
|
+
try {
|
|
234
|
+
tokens = new Lexer(text).tokenize();
|
|
235
|
+
} catch {
|
|
236
|
+
symbolIndex.delete(uri);
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
154
239
|
|
|
155
240
|
for (let i = 0; i < lines.length; i++) {
|
|
156
241
|
const line = lines[i];
|
|
@@ -191,46 +276,59 @@ function indexSymbols(uri, text) {
|
|
|
191
276
|
symbols.definitions.set(name, { line: i, col, kind: 'prompt' });
|
|
192
277
|
}
|
|
193
278
|
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
const declarationPositions = new Set(
|
|
282
|
+
[...symbols.definitions.entries()].map(([name, location]) =>
|
|
283
|
+
`${location.line}:${location.col}:${name}`,
|
|
284
|
+
),
|
|
285
|
+
);
|
|
286
|
+
for (const [index, token] of tokens.entries()) {
|
|
287
|
+
if (token.type !== 'IDENT') continue;
|
|
288
|
+
const line = token.line - 1;
|
|
289
|
+
const col = token.col - 1;
|
|
290
|
+
symbols.identifiers.push({ name: token.value, line, col });
|
|
291
|
+
const previous = tokens[index - 1];
|
|
292
|
+
const next = tokens[index + 1];
|
|
293
|
+
if (previous?.type === 'DOT' || previous?.type === 'OPTIONAL' || next?.type === 'COLON') continue;
|
|
294
|
+
if (declarationPositions.has(`${line}:${col}:${token.value}`)) continue;
|
|
295
|
+
if (!symbols.references.has(token.value)) symbols.references.set(token.value, []);
|
|
296
|
+
symbols.references.get(token.value).push({ line, col });
|
|
203
297
|
}
|
|
204
298
|
|
|
205
299
|
symbolIndex.set(uri, symbols);
|
|
206
300
|
}
|
|
207
301
|
|
|
208
|
-
function
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
while (end < lineText.length && /\w/.test(lineText[end])) end++;
|
|
215
|
-
return lineText.slice(start, end) || null;
|
|
302
|
+
function getIdentifierAtPosition(symbols, position) {
|
|
303
|
+
return symbols.identifiers?.find(identifier =>
|
|
304
|
+
identifier.line === position.line
|
|
305
|
+
&& position.character >= identifier.col
|
|
306
|
+
&& position.character < identifier.col + identifier.name.length,
|
|
307
|
+
)?.name || null;
|
|
216
308
|
}
|
|
217
309
|
|
|
218
310
|
function getDefinition(params) {
|
|
219
311
|
const uri = params.textDocument.uri;
|
|
220
|
-
const text = documents.get(uri);
|
|
221
|
-
if (!text) return null;
|
|
222
|
-
|
|
223
|
-
const word = getWordAtPosition(text, params.position.line, params.position.character);
|
|
224
|
-
if (!word) return null;
|
|
225
|
-
|
|
226
312
|
const symbols = symbolIndex.get(uri);
|
|
227
313
|
if (!symbols) return null;
|
|
314
|
+
const word = getIdentifierAtPosition(symbols, params.position);
|
|
315
|
+
if (!word) return null;
|
|
228
316
|
|
|
229
|
-
|
|
317
|
+
let definitionUri = uri;
|
|
318
|
+
let def = symbols.definitions.get(word);
|
|
319
|
+
if (!def) {
|
|
320
|
+
for (const [candidateUri, index] of symbolIndex) {
|
|
321
|
+
def = index.definitions.get(word);
|
|
322
|
+
if (def) {
|
|
323
|
+
definitionUri = candidateUri;
|
|
324
|
+
break;
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
}
|
|
230
328
|
if (!def) return null;
|
|
231
329
|
|
|
232
330
|
return {
|
|
233
|
-
uri,
|
|
331
|
+
uri: definitionUri,
|
|
234
332
|
range: {
|
|
235
333
|
start: { line: def.line, character: def.col },
|
|
236
334
|
end: { line: def.line, character: def.col + word.length },
|
|
@@ -240,43 +338,40 @@ function getDefinition(params) {
|
|
|
240
338
|
|
|
241
339
|
function getReferences(params) {
|
|
242
340
|
const uri = params.textDocument.uri;
|
|
243
|
-
const text = documents.get(uri);
|
|
244
|
-
if (!text) return [];
|
|
245
|
-
|
|
246
|
-
const word = getWordAtPosition(text, params.position.line, params.position.character);
|
|
247
|
-
if (!word) return [];
|
|
248
|
-
|
|
249
341
|
const symbols = symbolIndex.get(uri);
|
|
250
342
|
if (!symbols) return [];
|
|
343
|
+
const word = getIdentifierAtPosition(symbols, params.position);
|
|
344
|
+
if (!word) return [];
|
|
251
345
|
|
|
252
346
|
const results = [];
|
|
253
347
|
|
|
254
|
-
const
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
348
|
+
for (const [documentUri, index] of symbolIndex) {
|
|
349
|
+
const def = index.definitions.get(word);
|
|
350
|
+
if (def) {
|
|
351
|
+
results.push({
|
|
352
|
+
uri: documentUri,
|
|
353
|
+
range: {
|
|
354
|
+
start: { line: def.line, character: def.col },
|
|
355
|
+
end: { line: def.line, character: def.col + word.length },
|
|
356
|
+
},
|
|
357
|
+
});
|
|
358
|
+
}
|
|
264
359
|
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
}
|
|
273
|
-
}
|
|
360
|
+
for (const ref of index.references.get(word) || []) {
|
|
361
|
+
results.push({
|
|
362
|
+
uri: documentUri,
|
|
363
|
+
range: {
|
|
364
|
+
start: { line: ref.line, character: ref.col },
|
|
365
|
+
end: { line: ref.line, character: ref.col + word.length },
|
|
366
|
+
},
|
|
367
|
+
});
|
|
368
|
+
}
|
|
274
369
|
}
|
|
275
370
|
|
|
276
371
|
return results;
|
|
277
372
|
}
|
|
278
373
|
|
|
279
|
-
function getCompletions() {
|
|
374
|
+
function getCompletions(params = {}) {
|
|
280
375
|
const keywords = [
|
|
281
376
|
'fn', 'ret', 'if', 'elif', 'else', 'each', 'for', 'while', 'match',
|
|
282
377
|
'try', 'fail', 'ensure', 'server', 'model', 'schema', 'use', 'pub', 'mut',
|
|
@@ -369,12 +464,28 @@ function getCompletions() {
|
|
|
369
464
|
{ label: 'init(params):', detail: 'Constructor method', insertText: 'init(' },
|
|
370
465
|
];
|
|
371
466
|
|
|
372
|
-
|
|
467
|
+
const items = [
|
|
373
468
|
...keywords.map(k => ({ label: k, kind: 14 })),
|
|
374
469
|
...types.map(t => ({ label: t, kind: 25 })),
|
|
375
470
|
...features.map(f => ({ label: f, kind: 14 })),
|
|
376
471
|
...builtins.map(b => ({ label: b.label, kind: 3, detail: b.detail, insertText: b.insertText })),
|
|
377
472
|
];
|
|
473
|
+
const uri = params.textDocument?.uri;
|
|
474
|
+
const text = uri ? documents.get(uri) : null;
|
|
475
|
+
const lineText = text?.split('\n')[params.position?.line] || '';
|
|
476
|
+
const beforeCursor = lineText.slice(0, params.position?.character || 0);
|
|
477
|
+
const prefix = beforeCursor.match(/[A-Za-z_]\w*$/)?.[0] || '';
|
|
478
|
+
if (uri) {
|
|
479
|
+
for (const index of symbolIndex.values()) {
|
|
480
|
+
for (const identifier of index.identifiers || []) {
|
|
481
|
+
items.push({ label: identifier.name, kind: 6, detail: 'Project symbol' });
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
const uniqueItems = [...new Map(items.map(item => [item.label, item])).values()];
|
|
486
|
+
return prefix
|
|
487
|
+
? uniqueItems.filter(item => item.label.startsWith(prefix))
|
|
488
|
+
: uniqueItems;
|
|
378
489
|
}
|
|
379
490
|
|
|
380
491
|
const HOVER_DOCS = {
|
|
@@ -439,10 +550,8 @@ function getHover(params) {
|
|
|
439
550
|
const line = text.split('\n')[params.position.line];
|
|
440
551
|
if (!line) return null;
|
|
441
552
|
const col = params.position.character;
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
while (end < line.length && /\w/.test(line[end])) end++;
|
|
445
|
-
const word = line.slice(start, end);
|
|
553
|
+
const symbols = symbolIndex.get(params.textDocument.uri);
|
|
554
|
+
const word = symbols ? getIdentifierAtPosition(symbols, params.position) : null;
|
|
446
555
|
if (HOVER_DOCS[word]) return { contents: { kind: 'markdown', value: HOVER_DOCS[word] } };
|
|
447
556
|
return null;
|
|
448
557
|
}
|