@kerebron/extension-lsp 0.7.9 → 0.8.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/esm/ExtensionLsp.d.ts +2 -6
- package/esm/ExtensionLsp.d.ts.map +1 -1
- package/esm/ExtensionLsp.js +44 -36
- package/esm/ExtensionLsp.js.map +1 -1
- package/esm/LSPClient.d.ts +5 -4
- package/esm/LSPClient.d.ts.map +1 -1
- package/esm/LSPClient.js +14 -5
- package/esm/LSPClient.js.map +1 -1
- package/esm/LSPPlugin.d.ts +38 -0
- package/esm/LSPPlugin.d.ts.map +1 -0
- package/esm/LSPPlugin.js +190 -0
- package/esm/LSPPlugin.js.map +1 -0
- package/esm/{LspWebSocketTransport.d.ts → LSPWebSocketTransport.d.ts} +2 -2
- package/esm/{LspWebSocketTransport.d.ts.map → LSPWebSocketTransport.d.ts.map} +1 -1
- package/esm/{LspWebSocketTransport.js → LSPWebSocketTransport.js} +2 -2
- package/esm/{LspWebSocketTransport.js.map → LSPWebSocketTransport.js.map} +1 -1
- package/esm/LSPWorkspace.d.ts +39 -0
- package/esm/LSPWorkspace.d.ts.map +1 -0
- package/esm/LSPWorkspace.js +118 -0
- package/esm/LSPWorkspace.js.map +1 -0
- package/esm/createLspAutocomplete.d.ts +3 -9
- package/esm/createLspAutocomplete.d.ts.map +1 -1
- package/esm/createLspAutocomplete.js +4 -5
- package/esm/createLspAutocomplete.js.map +1 -1
- package/esm/mod.d.ts +1 -1
- package/esm/mod.d.ts.map +1 -1
- package/esm/mod.js +1 -1
- package/esm/mod.js.map +1 -1
- package/esm/workspace.d.ts +6 -35
- package/esm/workspace.d.ts.map +1 -1
- package/esm/workspace.js +3 -98
- package/esm/workspace.js.map +1 -1
- package/package.json +6 -6
- package/src/ExtensionLsp.ts +47 -47
- package/src/LSPClient.ts +21 -13
- package/src/LSPPlugin.ts +323 -0
- package/src/{LspWebSocketTransport.ts → LSPWebSocketTransport.ts} +1 -1
- package/src/LSPWorkspace.ts +172 -0
- package/src/createLspAutocomplete.ts +10 -7
- package/src/mod.ts +1 -1
- package/src/workspace.ts +8 -147
- package/esm/DiagnosticPlugin.d.ts +0 -32
- package/esm/DiagnosticPlugin.d.ts.map +0 -1
- package/esm/DiagnosticPlugin.js +0 -117
- package/esm/DiagnosticPlugin.js.map +0 -1
- package/src/DiagnosticPlugin.ts +0 -196
package/src/LSPClient.ts
CHANGED
|
@@ -4,12 +4,9 @@ import {
|
|
|
4
4
|
TextDocumentSyncKind,
|
|
5
5
|
} from 'vscode-languageserver-protocol';
|
|
6
6
|
|
|
7
|
-
import {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
Workspace,
|
|
11
|
-
WorkspaceFile,
|
|
12
|
-
} from './workspace.js';
|
|
7
|
+
import { Workspace, WorkspaceFile } from './workspace.js';
|
|
8
|
+
|
|
9
|
+
import { LSPSource, LSPWorkspace } from './LSPWorkspace.js';
|
|
13
10
|
|
|
14
11
|
const defaultNotificationHandlers: {
|
|
15
12
|
[method: string]: (client: LSPClient, params: any) => void;
|
|
@@ -76,6 +73,11 @@ const clientCapabilities: lsp.ClientCapabilities = {
|
|
|
76
73
|
parser: 'marked',
|
|
77
74
|
},
|
|
78
75
|
},
|
|
76
|
+
workspace: {
|
|
77
|
+
'diagnostics': {
|
|
78
|
+
'refreshSupport': true,
|
|
79
|
+
},
|
|
80
|
+
},
|
|
79
81
|
textDocument: {
|
|
80
82
|
publishDiagnostics: { versionSupport: true },
|
|
81
83
|
completion: {
|
|
@@ -108,7 +110,7 @@ const clientCapabilities: lsp.ClientCapabilities = {
|
|
|
108
110
|
implementation: {},
|
|
109
111
|
typeDefinition: {},
|
|
110
112
|
references: {},
|
|
111
|
-
diagnostic: {},
|
|
113
|
+
// diagnostic: {},
|
|
112
114
|
},
|
|
113
115
|
window: {
|
|
114
116
|
showMessage: {},
|
|
@@ -156,7 +158,7 @@ export class LSPError extends Error {
|
|
|
156
158
|
}
|
|
157
159
|
|
|
158
160
|
export class LSPClient extends EventTarget {
|
|
159
|
-
sources: Record<string,
|
|
161
|
+
sources: Record<string, LSPSource> = {};
|
|
160
162
|
|
|
161
163
|
workspace: Workspace;
|
|
162
164
|
private nextReqID = 0;
|
|
@@ -209,7 +211,7 @@ export class LSPClient extends EventTarget {
|
|
|
209
211
|
|
|
210
212
|
this.workspace = config.workspace
|
|
211
213
|
? config.workspace(this)
|
|
212
|
-
: new
|
|
214
|
+
: new LSPWorkspace(this);
|
|
213
215
|
}
|
|
214
216
|
|
|
215
217
|
startInitializing() {
|
|
@@ -272,7 +274,7 @@ export class LSPClient extends EventTarget {
|
|
|
272
274
|
this.workspace.connected();
|
|
273
275
|
}
|
|
274
276
|
|
|
275
|
-
connect(uri: string, source:
|
|
277
|
+
connect(uri: string, source: LSPSource) {
|
|
276
278
|
if (this.sources[uri] && this.sources[uri] !== source) {
|
|
277
279
|
throw new Error(`Source for ${uri} already connected`);
|
|
278
280
|
}
|
|
@@ -311,6 +313,14 @@ export class LSPClient extends EventTarget {
|
|
|
311
313
|
},
|
|
312
314
|
},
|
|
313
315
|
);
|
|
316
|
+
await this.notification<lsp.DocumentDiagnosticParams>(
|
|
317
|
+
'textDocument/diagnostic',
|
|
318
|
+
{
|
|
319
|
+
textDocument: {
|
|
320
|
+
uri: file.uri,
|
|
321
|
+
},
|
|
322
|
+
},
|
|
323
|
+
);
|
|
314
324
|
return true;
|
|
315
325
|
}
|
|
316
326
|
|
|
@@ -464,7 +474,7 @@ export class LSPClient extends EventTarget {
|
|
|
464
474
|
return this.serverCapabilities ? !!this.serverCapabilities[name] : null;
|
|
465
475
|
}
|
|
466
476
|
|
|
467
|
-
|
|
477
|
+
syncFiles() {
|
|
468
478
|
this.workspace.syncFiles();
|
|
469
479
|
}
|
|
470
480
|
|
|
@@ -474,8 +484,6 @@ export class LSPClient extends EventTarget {
|
|
|
474
484
|
id: number,
|
|
475
485
|
params: any,
|
|
476
486
|
) {
|
|
477
|
-
console.error('this.timeoutRequest', this.timeout, method, id, params);
|
|
478
|
-
|
|
479
487
|
const index = this.requests.indexOf(req);
|
|
480
488
|
if (index > -1) {
|
|
481
489
|
req.reject(LSPError.createTimeout());
|
package/src/LSPPlugin.ts
ADDED
|
@@ -0,0 +1,323 @@
|
|
|
1
|
+
import { Plugin, PluginKey, Transaction } from 'prosemirror-state';
|
|
2
|
+
import { Decoration, DecorationSet } from 'prosemirror-view';
|
|
3
|
+
import { Diagnostic } from 'vscode-languageserver-protocol';
|
|
4
|
+
|
|
5
|
+
import { PositionMapper } from '@kerebron/extension-markdown/PositionMapper';
|
|
6
|
+
import { CoreEditor } from '@kerebron/editor';
|
|
7
|
+
import {
|
|
8
|
+
type ExtensionMarkdown,
|
|
9
|
+
type MarkdownResult,
|
|
10
|
+
} from '@kerebron/extension-markdown';
|
|
11
|
+
|
|
12
|
+
import { ExtensionLsp, LspConfig } from './ExtensionLsp.js';
|
|
13
|
+
|
|
14
|
+
export interface LspRange {
|
|
15
|
+
line: number;
|
|
16
|
+
character: number;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
interface DiagnosticsItem {
|
|
20
|
+
severity: number;
|
|
21
|
+
range: {
|
|
22
|
+
start: LspRange;
|
|
23
|
+
end: LspRange;
|
|
24
|
+
};
|
|
25
|
+
message: string;
|
|
26
|
+
source: string;
|
|
27
|
+
code: string;
|
|
28
|
+
data: any; // "data":{"kind":"todo","keyword":"TODO"}},
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
interface LSPMeta {
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
interface WorkspaceMetaOpen {
|
|
35
|
+
operation: 'openFile';
|
|
36
|
+
uri: string;
|
|
37
|
+
lang: string;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
interface WorkspaceMetaModify {
|
|
41
|
+
operation: 'modifyFile';
|
|
42
|
+
uri: string;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
interface WorkspaceMetaClose {
|
|
46
|
+
operation: 'closeFile';
|
|
47
|
+
uri: string;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
type WorkspaceMeta =
|
|
51
|
+
| WorkspaceMetaOpen
|
|
52
|
+
| WorkspaceMetaClose
|
|
53
|
+
| WorkspaceMetaModify;
|
|
54
|
+
|
|
55
|
+
class LSPPluginState {
|
|
56
|
+
diagnostics: DiagnosticsItem[] = [];
|
|
57
|
+
|
|
58
|
+
snapshot?: {
|
|
59
|
+
version: number;
|
|
60
|
+
mapper: PositionMapper;
|
|
61
|
+
markdownResult: MarkdownResult;
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
constructor(private editor: CoreEditor) {
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const LSPPluginKey = new PluginKey('lsp');
|
|
69
|
+
|
|
70
|
+
export class LSPPlugin extends Plugin<LSPPluginState> {
|
|
71
|
+
listenerDiag: ((event: Event) => void) | undefined;
|
|
72
|
+
listenerDisconnect: ((event: Event) => void) | undefined;
|
|
73
|
+
listenerChange: ((event: Event) => void) | undefined;
|
|
74
|
+
|
|
75
|
+
constructor(config: LspConfig, extension: ExtensionLsp) {
|
|
76
|
+
super({
|
|
77
|
+
key: LSPPluginKey,
|
|
78
|
+
view(view) {
|
|
79
|
+
return {
|
|
80
|
+
destroy: () => {
|
|
81
|
+
const client = extension.getClient(extension.mainLang);
|
|
82
|
+
|
|
83
|
+
if (this.listenerDiag && client) {
|
|
84
|
+
client.removeEventListener(
|
|
85
|
+
'textDocument/publishDiagnostics',
|
|
86
|
+
this.listenerDiag,
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
if (this.listenerDisconnect && client) {
|
|
90
|
+
client.removeEventListener(
|
|
91
|
+
'close',
|
|
92
|
+
this.listenerDisconnect,
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
// if (this.listenerChange) {
|
|
96
|
+
// extension.getEditor().removeEventListener(
|
|
97
|
+
// 'change',
|
|
98
|
+
// this.listenerChange,
|
|
99
|
+
// );
|
|
100
|
+
// }
|
|
101
|
+
},
|
|
102
|
+
};
|
|
103
|
+
},
|
|
104
|
+
state: {
|
|
105
|
+
init() {
|
|
106
|
+
return new LSPPluginState(editor);
|
|
107
|
+
},
|
|
108
|
+
apply(
|
|
109
|
+
tr: Transaction,
|
|
110
|
+
value: LSPPluginState,
|
|
111
|
+
oldState,
|
|
112
|
+
newState,
|
|
113
|
+
) {
|
|
114
|
+
const workspaceMeta: WorkspaceMeta | undefined = tr.getMeta(
|
|
115
|
+
'workspace',
|
|
116
|
+
);
|
|
117
|
+
|
|
118
|
+
if (workspaceMeta) {
|
|
119
|
+
console.log(
|
|
120
|
+
'workspaceMeta',
|
|
121
|
+
workspaceMeta.operation,
|
|
122
|
+
workspaceMeta,
|
|
123
|
+
);
|
|
124
|
+
|
|
125
|
+
const uri = workspaceMeta.uri;
|
|
126
|
+
// const lang = workspaceMeta.lang;
|
|
127
|
+
|
|
128
|
+
switch (workspaceMeta.operation) {
|
|
129
|
+
case 'openFile':
|
|
130
|
+
{
|
|
131
|
+
const client = extension.getClient(workspaceMeta.lang);
|
|
132
|
+
if (client) {
|
|
133
|
+
client.addEventListener(
|
|
134
|
+
'textDocument/publishDiagnostics',
|
|
135
|
+
this.diagListener,
|
|
136
|
+
);
|
|
137
|
+
|
|
138
|
+
const go = async () => {
|
|
139
|
+
client.connect(uri, this.source);
|
|
140
|
+
if (uri === editor.config.uri) {
|
|
141
|
+
await client.restart();
|
|
142
|
+
}
|
|
143
|
+
client.workspace.openFile(
|
|
144
|
+
workspaceMeta.uri,
|
|
145
|
+
workspaceMeta.lang,
|
|
146
|
+
this.source,
|
|
147
|
+
);
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
go();
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
break;
|
|
154
|
+
case 'modifyFile':
|
|
155
|
+
break;
|
|
156
|
+
case 'closeFile':
|
|
157
|
+
// this.editor.view.dispatch(tr);
|
|
158
|
+
// const client = this.getClient(this.mainLang);
|
|
159
|
+
// if (client) {
|
|
160
|
+
// client.disconnect(this.uri);
|
|
161
|
+
// }
|
|
162
|
+
|
|
163
|
+
break;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
return value;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
const meta: LSPMeta | undefined = tr.getMeta(LSPPluginKey);
|
|
170
|
+
if (meta?.diagnostics) {
|
|
171
|
+
value.diagnostics = meta.diagnostics;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
const markdownMeta = tr.getMeta('markdown');
|
|
175
|
+
if (markdownMeta?.snapshot) {
|
|
176
|
+
value.snapshot = markdownMeta.snapshot;
|
|
177
|
+
return value;
|
|
178
|
+
}
|
|
179
|
+
if (markdownMeta?.clearSnapshot) {
|
|
180
|
+
value.snapshot = undefined;
|
|
181
|
+
return value;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
if (tr.docChanged) {
|
|
185
|
+
// value.snapshot = undefined;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// let codeBlockChanged = false;
|
|
189
|
+
|
|
190
|
+
// // Compare changed ranges only
|
|
191
|
+
// tr.mapping.maps.forEach((stepMap) => {
|
|
192
|
+
// stepMap.forEach((oldStart, oldEnd, newStart, newEnd) => {
|
|
193
|
+
// oldState.doc.nodesBetween(oldStart, oldEnd, (node) => {
|
|
194
|
+
// if (node.type.name === "code_block") {
|
|
195
|
+
// codeBlockChanged = true;
|
|
196
|
+
// }
|
|
197
|
+
// });
|
|
198
|
+
|
|
199
|
+
// newState.doc.nodesBetween(newStart, newEnd, (node) => {
|
|
200
|
+
// if (node.type.name === "code_block") {
|
|
201
|
+
// codeBlockChanged = true;
|
|
202
|
+
// }
|
|
203
|
+
// });
|
|
204
|
+
// });
|
|
205
|
+
// });
|
|
206
|
+
|
|
207
|
+
// if (codeBlockChanged) {
|
|
208
|
+
// console.log("Code block changed");
|
|
209
|
+
// // trigger your logic here
|
|
210
|
+
// }
|
|
211
|
+
|
|
212
|
+
return value;
|
|
213
|
+
},
|
|
214
|
+
},
|
|
215
|
+
props: {
|
|
216
|
+
decorations(state) {
|
|
217
|
+
const decorations: Decoration[] = [];
|
|
218
|
+
|
|
219
|
+
const pluginState = this.getState(state);
|
|
220
|
+
if (pluginState) {
|
|
221
|
+
const { diagnostics, snapshot } = pluginState;
|
|
222
|
+
|
|
223
|
+
if (snapshot?.version === editor.version) {
|
|
224
|
+
for (const diag of diagnostics) {
|
|
225
|
+
const from = snapshot.mapper.fromLineChar(
|
|
226
|
+
diag.range.start.line,
|
|
227
|
+
diag.range.start.character,
|
|
228
|
+
);
|
|
229
|
+
const end = snapshot.mapper.fromLineChar(
|
|
230
|
+
diag.range.end.line,
|
|
231
|
+
diag.range.end.character,
|
|
232
|
+
);
|
|
233
|
+
|
|
234
|
+
if (from > -1 && end > -1) {
|
|
235
|
+
const decoration = Decoration.inline(
|
|
236
|
+
from,
|
|
237
|
+
end,
|
|
238
|
+
{ class: 'kb-lsp__error', title: diag.message },
|
|
239
|
+
);
|
|
240
|
+
decorations.push(decoration);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
return DecorationSet.create(state.doc, decorations);
|
|
247
|
+
},
|
|
248
|
+
},
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
let lastDiag = 0;
|
|
252
|
+
|
|
253
|
+
const editor = extension.getEditor();
|
|
254
|
+
|
|
255
|
+
const uri = editor.config.uri;
|
|
256
|
+
if (uri) {
|
|
257
|
+
const client = extension.getClient(extension.mainLang);
|
|
258
|
+
|
|
259
|
+
this.listenerDiag = (event: Event) => {
|
|
260
|
+
const detail = (event as CustomEvent).detail;
|
|
261
|
+
if (detail.params.uri !== uri) {
|
|
262
|
+
return;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
event.preventDefault();
|
|
266
|
+
|
|
267
|
+
lastDiag = +Date();
|
|
268
|
+
|
|
269
|
+
if (client) {
|
|
270
|
+
const file = client.workspace.getFile(uri);
|
|
271
|
+
if (file) {
|
|
272
|
+
const { mapper } = file;
|
|
273
|
+
|
|
274
|
+
const diagnostics: Array<Diagnostic> = detail.params.diagnostics;
|
|
275
|
+
|
|
276
|
+
// file.dia
|
|
277
|
+
|
|
278
|
+
const tr = editor.view.state.tr.setMeta(LSPPluginKey, {
|
|
279
|
+
diagnostics: detail.params.diagnostics,
|
|
280
|
+
mapper,
|
|
281
|
+
});
|
|
282
|
+
editor.view.dispatch(tr);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
};
|
|
286
|
+
|
|
287
|
+
if (client) {
|
|
288
|
+
client.addEventListener(
|
|
289
|
+
'textDocument/publishDiagnostics',
|
|
290
|
+
this.listenerDiag,
|
|
291
|
+
);
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
this.listenerDisconnect = (event: Event) => {
|
|
295
|
+
const tr = editor.view.state.tr.setMeta(LSPPluginKey, {
|
|
296
|
+
diagnostics: [],
|
|
297
|
+
mapper: undefined,
|
|
298
|
+
});
|
|
299
|
+
editor.view.dispatch(tr);
|
|
300
|
+
};
|
|
301
|
+
|
|
302
|
+
if (client) {
|
|
303
|
+
client.addEventListener(
|
|
304
|
+
'close',
|
|
305
|
+
this.listenerDisconnect,
|
|
306
|
+
);
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
this.listenerChange = (event: Event) => {
|
|
310
|
+
if (lastDiag === 0 && +Date() - lastDiag > 10_1000) {
|
|
311
|
+
return;
|
|
312
|
+
}
|
|
313
|
+
const tr = editor.view.state.tr.setMeta(LSPPluginKey, {
|
|
314
|
+
diagnostics: [],
|
|
315
|
+
mapper: undefined,
|
|
316
|
+
});
|
|
317
|
+
editor.view.dispatch(tr);
|
|
318
|
+
};
|
|
319
|
+
|
|
320
|
+
// extension.getEditor().addEventListener('changed', this.listenerChange);
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
}
|
|
@@ -7,7 +7,7 @@ function shouldReconnectOnCode(code: number) {
|
|
|
7
7
|
return reconnectCodes.includes(code);
|
|
8
8
|
}
|
|
9
9
|
|
|
10
|
-
export class
|
|
10
|
+
export class LSPWebSocketTransport extends EventTarget implements Transport {
|
|
11
11
|
socket: WebSocket | undefined;
|
|
12
12
|
private reconnectAttempts = 0;
|
|
13
13
|
private maxAttempts = 10;
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import type * as lsp from 'vscode-languageserver-protocol';
|
|
2
|
+
import { TextDocumentSyncKind } from 'vscode-languageserver-protocol';
|
|
3
|
+
|
|
4
|
+
import type { EditorUi } from '@kerebron/editor';
|
|
5
|
+
import { Workspace, WorkspaceFile } from './workspace.js';
|
|
6
|
+
|
|
7
|
+
import { PositionMapper } from '@kerebron/extension-markdown/PositionMapper';
|
|
8
|
+
|
|
9
|
+
import { LSPClient } from './LSPClient.js';
|
|
10
|
+
import { computeIncrementalChanges } from './computeIncrementalChanges.js';
|
|
11
|
+
|
|
12
|
+
export interface LSPSource {
|
|
13
|
+
ui: EditorUi;
|
|
14
|
+
getMappedContent(): Promise<{ content: string; mapper: PositionMapper }>;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
class LSPWorkspaceFile implements WorkspaceFile {
|
|
18
|
+
public syncedContent: string = '';
|
|
19
|
+
public diagnostics: Array<lsp.Diagnostic> = [];
|
|
20
|
+
|
|
21
|
+
constructor(
|
|
22
|
+
readonly uri: string,
|
|
23
|
+
readonly languageId: string,
|
|
24
|
+
public version: number,
|
|
25
|
+
public content: string,
|
|
26
|
+
public mapper: PositionMapper,
|
|
27
|
+
public source: LSPSource,
|
|
28
|
+
) {
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
getUi() {
|
|
32
|
+
return this.source.ui;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export class LSPWorkspace extends Workspace {
|
|
37
|
+
files: LSPWorkspaceFile[] = [];
|
|
38
|
+
private fileVersions: { [uri: string]: number } = Object.create(null);
|
|
39
|
+
|
|
40
|
+
constructor(private client: LSPClient) {
|
|
41
|
+
super();
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
nextFileVersion(uri: string) {
|
|
45
|
+
return this.fileVersions[uri] = (this.fileVersions[uri] ?? -1) + 1;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
override async connected(): Promise<void> {
|
|
49
|
+
super.connected();
|
|
50
|
+
for await (const file of this.files) {
|
|
51
|
+
const result = await this.client.didOpen(file);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
override disconnected(): void {
|
|
56
|
+
for (const file of this.files) {
|
|
57
|
+
this.client.workspace.closeFile(file.uri);
|
|
58
|
+
}
|
|
59
|
+
super.disconnected();
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
syncFiles() {
|
|
63
|
+
if (!this.client.supportSync) {
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
for (const file of this.files) {
|
|
68
|
+
this.changedFile(file.uri);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
async changedFile(uri: string) {
|
|
73
|
+
const file = this.files.find((f) => f.uri == uri) || null;
|
|
74
|
+
|
|
75
|
+
if (file) {
|
|
76
|
+
const { content, mapper } = await file.source.getMappedContent();
|
|
77
|
+
|
|
78
|
+
if (!this.isConnected) {
|
|
79
|
+
file.content = content;
|
|
80
|
+
file.mapper = mapper;
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (
|
|
85
|
+
await this.client.notification<lsp.DidChangeTextDocumentParams>(
|
|
86
|
+
'textDocument/didChange',
|
|
87
|
+
{
|
|
88
|
+
textDocument: { uri: file.uri, version: file.version },
|
|
89
|
+
contentChanges: contentChangesFor(
|
|
90
|
+
file,
|
|
91
|
+
content,
|
|
92
|
+
mapper,
|
|
93
|
+
this.client.supportSync == TextDocumentSyncKind.Incremental,
|
|
94
|
+
),
|
|
95
|
+
},
|
|
96
|
+
)
|
|
97
|
+
) {
|
|
98
|
+
file.syncedContent = file.content;
|
|
99
|
+
file.content = content;
|
|
100
|
+
file.mapper = mapper;
|
|
101
|
+
file.version = this.nextFileVersion(file.uri);
|
|
102
|
+
file.diagnostics = [];
|
|
103
|
+
|
|
104
|
+
this.dispatchEvent(
|
|
105
|
+
new CustomEvent('fileChanged', { // TODO: clear diagnosrics
|
|
106
|
+
detail: {
|
|
107
|
+
uri: file.uri,
|
|
108
|
+
file,
|
|
109
|
+
},
|
|
110
|
+
}),
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
await this.client.notification<lsp.DocumentDiagnosticParams>(
|
|
115
|
+
'textDocument/diagnostic',
|
|
116
|
+
{
|
|
117
|
+
textDocument: {
|
|
118
|
+
uri: file.uri,
|
|
119
|
+
},
|
|
120
|
+
},
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
async openFile(uri: string, languageId: string, source: LSPSource) {
|
|
126
|
+
// if (uri) {}
|
|
127
|
+
|
|
128
|
+
if (this.getFile(uri)) {
|
|
129
|
+
this.closeFile(uri);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const mappedContent = await source.getMappedContent();
|
|
133
|
+
const { content, mapper } = mappedContent;
|
|
134
|
+
const file = new LSPWorkspaceFile(
|
|
135
|
+
uri,
|
|
136
|
+
languageId,
|
|
137
|
+
this.nextFileVersion(uri),
|
|
138
|
+
content,
|
|
139
|
+
mapper,
|
|
140
|
+
source,
|
|
141
|
+
);
|
|
142
|
+
this.files.push(file);
|
|
143
|
+
|
|
144
|
+
this.client.didOpen(file);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
closeFile(uri: string) {
|
|
148
|
+
const file = this.getFile(uri);
|
|
149
|
+
if (file) {
|
|
150
|
+
this.files = this.files.filter((f) => f.uri !== uri);
|
|
151
|
+
this.client.didClose(uri);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const enum Sync {
|
|
157
|
+
AlwaysIfSmaller = 1024,
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function contentChangesFor(
|
|
161
|
+
file: WorkspaceFile,
|
|
162
|
+
newContent: string,
|
|
163
|
+
mapper: PositionMapper,
|
|
164
|
+
supportInc: boolean,
|
|
165
|
+
): lsp.TextDocumentContentChangeEvent[] {
|
|
166
|
+
if (!supportInc || newContent.length < Sync.AlwaysIfSmaller) {
|
|
167
|
+
return [{ text: newContent }];
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const changes = computeIncrementalChanges(file.content, newContent);
|
|
171
|
+
return changes.reverse();
|
|
172
|
+
}
|
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
import { DefaultRenderer } from '@kerebron/extension-autocomplete/DefaultRenderer';
|
|
1
|
+
import { DefaultRenderer } from '@kerebron/extension-ui/autocomplete/DefaultRenderer';
|
|
2
2
|
import { type CoreEditor, TextRange } from '@kerebron/editor';
|
|
3
3
|
|
|
4
4
|
import {
|
|
5
|
+
AutocompleteConfig,
|
|
5
6
|
type AutocompleteProps,
|
|
6
7
|
createRegexMatcher,
|
|
7
|
-
} from '@kerebron/extension-autocomplete';
|
|
8
|
+
} from '@kerebron/extension-ui/autocomplete';
|
|
8
9
|
|
|
9
10
|
import { ExtensionLsp } from './ExtensionLsp.js';
|
|
10
11
|
|
|
@@ -35,13 +36,15 @@ export function cleanPlaceholders(input: string): string {
|
|
|
35
36
|
return input.replace(regex, '$1');
|
|
36
37
|
}
|
|
37
38
|
|
|
38
|
-
export function createLspAutocomplete(
|
|
39
|
+
export function createLspAutocomplete(
|
|
40
|
+
extensionLsp: ExtensionLsp,
|
|
41
|
+
): { autocompleteConfig: AutocompleteConfig } {
|
|
39
42
|
const editor: CoreEditor = extensionLsp.getEditor();
|
|
40
43
|
const renderer = new CustomRenderer(editor);
|
|
41
44
|
|
|
42
|
-
const config = {
|
|
43
|
-
renderer,
|
|
44
|
-
matchers: [createRegexMatcher([/\w+/, /(^|\s)@\w*/, /^#\w*/])],
|
|
45
|
+
const config: AutocompleteConfig = {
|
|
46
|
+
// renderer,
|
|
47
|
+
// matchers: [createRegexMatcher([/\w+/, /(^|\s)@\w*/, /^#\w*/])],
|
|
45
48
|
getItems: async (query: string, props: AutocompleteProps) => {
|
|
46
49
|
const { mapper } = await extensionLsp.source.getMappedContent();
|
|
47
50
|
|
|
@@ -49,7 +52,7 @@ export function createLspAutocomplete(extensionLsp: ExtensionLsp) {
|
|
|
49
52
|
|
|
50
53
|
const client = extensionLsp.getClient(extensionLsp.mainLang);
|
|
51
54
|
if (client) {
|
|
52
|
-
client.
|
|
55
|
+
client.syncFiles();
|
|
53
56
|
try {
|
|
54
57
|
const completions:
|
|
55
58
|
| { items: CompletionItem[] }
|
package/src/mod.ts
CHANGED