@platformos/codemirror-language-client 0.0.2
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/CHANGELOG.md +7 -0
- package/README.md +91 -0
- package/dist/esm/CodeMirrorLanguageClient.d.ts +60 -0
- package/dist/esm/CodeMirrorLanguageClient.js +80 -0
- package/dist/esm/CodeMirrorLanguageClient.js.map +1 -0
- package/dist/esm/LanguageClient.d.ts +112 -0
- package/dist/esm/LanguageClient.js +187 -0
- package/dist/esm/LanguageClient.js.map +1 -0
- package/dist/esm/extensions/client.d.ts +6 -0
- package/dist/esm/extensions/client.js +12 -0
- package/dist/esm/extensions/client.js.map +1 -0
- package/dist/esm/extensions/complete.d.ts +17 -0
- package/dist/esm/extensions/complete.js +202 -0
- package/dist/esm/extensions/complete.js.map +1 -0
- package/dist/esm/extensions/complete.spec.d.ts +1 -0
- package/dist/esm/extensions/complete.spec.js +189 -0
- package/dist/esm/extensions/complete.spec.js.map +1 -0
- package/dist/esm/extensions/documentHighlights.d.ts +14 -0
- package/dist/esm/extensions/documentHighlights.js +78 -0
- package/dist/esm/extensions/documentHighlights.js.map +1 -0
- package/dist/esm/extensions/documentHighlights.spec.d.ts +1 -0
- package/dist/esm/extensions/documentHighlights.spec.js +99 -0
- package/dist/esm/extensions/documentHighlights.spec.js.map +1 -0
- package/dist/esm/extensions/hover.d.ts +16 -0
- package/dist/esm/extensions/hover.js +49 -0
- package/dist/esm/extensions/hover.js.map +1 -0
- package/dist/esm/extensions/hover.spec.d.ts +1 -0
- package/dist/esm/extensions/hover.spec.js +59 -0
- package/dist/esm/extensions/hover.spec.js.map +1 -0
- package/dist/esm/extensions/index.d.ts +6 -0
- package/dist/esm/extensions/index.js +7 -0
- package/dist/esm/extensions/index.js.map +1 -0
- package/dist/esm/extensions/lspLinter.d.ts +23 -0
- package/dist/esm/extensions/lspLinter.js +104 -0
- package/dist/esm/extensions/lspLinter.js.map +1 -0
- package/dist/esm/extensions/lspLinter.spec.d.ts +1 -0
- package/dist/esm/extensions/lspLinter.spec.js +141 -0
- package/dist/esm/extensions/lspLinter.spec.js.map +1 -0
- package/dist/esm/extensions/snippet.d.ts +19 -0
- package/dist/esm/extensions/snippet.js +25 -0
- package/dist/esm/extensions/snippet.js.map +1 -0
- package/dist/esm/extensions/snippet.spec.d.ts +1 -0
- package/dist/esm/extensions/snippet.spec.js +23 -0
- package/dist/esm/extensions/snippet.spec.js.map +1 -0
- package/dist/esm/extensions/textDocumentSync.d.ts +4 -0
- package/dist/esm/extensions/textDocumentSync.js +109 -0
- package/dist/esm/extensions/textDocumentSync.js.map +1 -0
- package/dist/esm/extensions/textDocumentSync.spec.d.ts +1 -0
- package/dist/esm/extensions/textDocumentSync.spec.js +163 -0
- package/dist/esm/extensions/textDocumentSync.spec.js.map +1 -0
- package/dist/esm/index.d.ts +2 -0
- package/dist/esm/index.js +3 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/test/MockClient.d.ts +18 -0
- package/dist/esm/test/MockClient.js +63 -0
- package/dist/esm/test/MockClient.js.map +1 -0
- package/dist/esm/tsconfig.tsbuildInfo +1 -0
- package/dist/esm/utils/simpleStateField.d.ts +3 -0
- package/dist/esm/utils/simpleStateField.js +17 -0
- package/dist/esm/utils/simpleStateField.js.map +1 -0
- package/dist/umd/CodeMirrorLanguageClient.d.ts +60 -0
- package/dist/umd/CodeMirrorLanguageClient.js +94 -0
- package/dist/umd/CodeMirrorLanguageClient.js.map +1 -0
- package/dist/umd/LanguageClient.d.ts +112 -0
- package/dist/umd/LanguageClient.js +202 -0
- package/dist/umd/LanguageClient.js.map +1 -0
- package/dist/umd/extensions/client.d.ts +6 -0
- package/dist/umd/extensions/client.js +25 -0
- package/dist/umd/extensions/client.js.map +1 -0
- package/dist/umd/extensions/complete.d.ts +17 -0
- package/dist/umd/extensions/complete.js +217 -0
- package/dist/umd/extensions/complete.js.map +1 -0
- package/dist/umd/extensions/documentHighlights.d.ts +14 -0
- package/dist/umd/extensions/documentHighlights.js +93 -0
- package/dist/umd/extensions/documentHighlights.js.map +1 -0
- package/dist/umd/extensions/hover.d.ts +16 -0
- package/dist/umd/extensions/hover.js +64 -0
- package/dist/umd/extensions/hover.js.map +1 -0
- package/dist/umd/extensions/index.d.ts +6 -0
- package/dist/umd/extensions/index.js +36 -0
- package/dist/umd/extensions/index.js.map +1 -0
- package/dist/umd/extensions/lspLinter.d.ts +23 -0
- package/dist/umd/extensions/lspLinter.js +119 -0
- package/dist/umd/extensions/lspLinter.js.map +1 -0
- package/dist/umd/extensions/snippet.d.ts +19 -0
- package/dist/umd/extensions/snippet.js +38 -0
- package/dist/umd/extensions/snippet.js.map +1 -0
- package/dist/umd/extensions/textDocumentSync.d.ts +4 -0
- package/dist/umd/extensions/textDocumentSync.js +122 -0
- package/dist/umd/extensions/textDocumentSync.js.map +1 -0
- package/dist/umd/index.d.ts +2 -0
- package/dist/umd/index.js +29 -0
- package/dist/umd/index.js.map +1 -0
- package/dist/umd/test/MockClient.d.ts +18 -0
- package/dist/umd/test/MockClient.js +77 -0
- package/dist/umd/test/MockClient.js.map +1 -0
- package/dist/umd/tsconfig.tsbuildInfo +1 -0
- package/dist/umd/utils/simpleStateField.d.ts +3 -0
- package/dist/umd/utils/simpleStateField.js +30 -0
- package/dist/umd/utils/simpleStateField.js.map +1 -0
- package/package.json +67 -0
- package/playground/src/index.html +10 -0
- package/playground/src/language-server-worker.ts +82 -0
- package/playground/src/playground.ts +251 -0
- package/playground/tsconfig.json +26 -0
- package/playground/webpack.config.js +85 -0
- package/src/CodeMirrorLanguageClient.ts +179 -0
- package/src/LanguageClient.ts +329 -0
- package/src/extensions/client.ts +17 -0
- package/src/extensions/complete.spec.ts +200 -0
- package/src/extensions/complete.ts +274 -0
- package/src/extensions/documentHighlights.spec.ts +111 -0
- package/src/extensions/documentHighlights.ts +91 -0
- package/src/extensions/hover.spec.ts +68 -0
- package/src/extensions/hover.ts +66 -0
- package/src/extensions/index.ts +19 -0
- package/src/extensions/lspLinter.spec.ts +156 -0
- package/src/extensions/lspLinter.ts +154 -0
- package/src/extensions/snippet.spec.ts +31 -0
- package/src/extensions/snippet.ts +42 -0
- package/src/extensions/textDocumentSync.spec.ts +188 -0
- package/src/extensions/textDocumentSync.ts +138 -0
- package/src/index.ts +2 -0
- package/src/test/MockClient.ts +96 -0
- package/src/utils/simpleStateField.ts +22 -0
- package/tsconfig.json +18 -0
- package/tsconfig.umd.json +9 -0
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
// @vitest-environment jsdom
|
|
2
|
+
import { expect, describe, it, beforeEach, afterEach } from 'vitest';
|
|
3
|
+
import { EditorState, Extension, StateEffect } from '@codemirror/state';
|
|
4
|
+
import { textDocumentField, textDocumentSync } from './textDocumentSync';
|
|
5
|
+
import { clientFacet, fileUriFacet } from './client';
|
|
6
|
+
import { MockClient } from '../test/MockClient';
|
|
7
|
+
import {
|
|
8
|
+
DidChangeTextDocumentNotification,
|
|
9
|
+
DidCloseTextDocumentNotification,
|
|
10
|
+
DidOpenTextDocumentNotification,
|
|
11
|
+
} from 'vscode-languageserver-protocol';
|
|
12
|
+
import { EditorView } from '@codemirror/view';
|
|
13
|
+
|
|
14
|
+
describe('Module: textDocumentField', () => {
|
|
15
|
+
let state;
|
|
16
|
+
|
|
17
|
+
it('should instantiate the document with a version', () => {
|
|
18
|
+
state = EditorState.create({
|
|
19
|
+
doc: 'hello world',
|
|
20
|
+
extensions: [fileUriFacet.of('browser://input.liquid'), textDocumentSync],
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
const t1 = state.field(textDocumentField);
|
|
24
|
+
expect(t1.version).to.equal(0);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it('should increase the version if the document changes', () => {
|
|
28
|
+
state = EditorState.create({
|
|
29
|
+
doc: 'hello world',
|
|
30
|
+
extensions: [fileUriFacet.of('browser://input.liquid'), textDocumentSync],
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
const t1 = state.field(textDocumentField);
|
|
34
|
+
state = state.update({
|
|
35
|
+
changes: {
|
|
36
|
+
from: 0,
|
|
37
|
+
to: 'hello world'.indexOf(' '),
|
|
38
|
+
insert: 'hi',
|
|
39
|
+
},
|
|
40
|
+
}).state;
|
|
41
|
+
|
|
42
|
+
const t2 = state.field(textDocumentField);
|
|
43
|
+
expect(t1).not.to.equal(t2);
|
|
44
|
+
expect(t2.version).to.equal(t1.version + 1);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it('should not increase the version if the document does not change', () => {
|
|
48
|
+
state = EditorState.create({
|
|
49
|
+
doc: 'hello world',
|
|
50
|
+
extensions: [fileUriFacet.of('browser://input.liquid'), textDocumentSync],
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
const t1 = state.field(textDocumentField);
|
|
54
|
+
state = state.update({
|
|
55
|
+
changes: {
|
|
56
|
+
from: 0,
|
|
57
|
+
to: 0,
|
|
58
|
+
insert: '',
|
|
59
|
+
},
|
|
60
|
+
}).state;
|
|
61
|
+
|
|
62
|
+
const t2 = state.field(textDocumentField);
|
|
63
|
+
expect(t1).to.equal(t2);
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it('should reset the version if the file URI changes', () => {
|
|
67
|
+
state = EditorState.create({
|
|
68
|
+
doc: 'hello world',
|
|
69
|
+
extensions: [fileUriFacet.of('browser://input.liquid'), textDocumentSync],
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
const t1 = state.field(textDocumentField);
|
|
73
|
+
state = state.update({
|
|
74
|
+
effects: StateEffect.reconfigure.of([
|
|
75
|
+
fileUriFacet.of('browser://renamed.liquid'),
|
|
76
|
+
textDocumentSync,
|
|
77
|
+
]),
|
|
78
|
+
}).state;
|
|
79
|
+
|
|
80
|
+
const t2 = state.field(textDocumentField);
|
|
81
|
+
expect(t1).not.to.equal(t2);
|
|
82
|
+
expect(t1.uri).to.equal('browser://input.liquid');
|
|
83
|
+
expect(t1.version).to.equal(0);
|
|
84
|
+
expect(t2.uri).to.equal('browser://renamed.liquid');
|
|
85
|
+
expect(t2.version).to.equal(0);
|
|
86
|
+
});
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
describe('Module: TextDocumentSyncPlugin', () => {
|
|
90
|
+
let state: EditorState, client: MockClient, extensions: Extension;
|
|
91
|
+
let view: EditorView;
|
|
92
|
+
|
|
93
|
+
beforeEach(() => {
|
|
94
|
+
client = new MockClient();
|
|
95
|
+
extensions = [
|
|
96
|
+
clientFacet.of(client),
|
|
97
|
+
fileUriFacet.of('browser://input.liquid'),
|
|
98
|
+
textDocumentSync,
|
|
99
|
+
];
|
|
100
|
+
state = EditorState.create({
|
|
101
|
+
doc: 'hello world',
|
|
102
|
+
extensions,
|
|
103
|
+
});
|
|
104
|
+
view = new EditorView({
|
|
105
|
+
state,
|
|
106
|
+
});
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
afterEach(() => {
|
|
110
|
+
view.destroy();
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
it('should send a textDocument/didOpen notification on start', () => {
|
|
114
|
+
expect(client.sendNotification).toBeCalledTimes(1);
|
|
115
|
+
expect(client.sendNotification).toBeCalledWith(DidOpenTextDocumentNotification.type, {
|
|
116
|
+
textDocument: {
|
|
117
|
+
uri: 'browser://input.liquid',
|
|
118
|
+
version: 0,
|
|
119
|
+
languageId: 'liquid',
|
|
120
|
+
text: 'hello world',
|
|
121
|
+
},
|
|
122
|
+
});
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
it('should send textDocument/didChange notifications on change', () => {
|
|
126
|
+
view.dispatch({
|
|
127
|
+
changes: {
|
|
128
|
+
from: 0,
|
|
129
|
+
to: 'hello world'.indexOf(' '),
|
|
130
|
+
insert: 'hi',
|
|
131
|
+
},
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
expect(client.sendNotification).toBeCalledTimes(2);
|
|
135
|
+
expect(client.sendNotification).toBeCalledWith(DidChangeTextDocumentNotification.type, {
|
|
136
|
+
textDocument: {
|
|
137
|
+
uri: 'browser://input.liquid',
|
|
138
|
+
version: 1,
|
|
139
|
+
},
|
|
140
|
+
contentChanges: [
|
|
141
|
+
{
|
|
142
|
+
text: 'hi world',
|
|
143
|
+
},
|
|
144
|
+
],
|
|
145
|
+
});
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
it('should send textDocument/didClose notification when destroyed', () => {
|
|
149
|
+
view.dispatch({
|
|
150
|
+
effects: StateEffect.reconfigure.of([
|
|
151
|
+
clientFacet.of(client),
|
|
152
|
+
fileUriFacet.of('browser://input.liquid'),
|
|
153
|
+
]),
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
expect(client.sendNotification).toBeCalledTimes(2);
|
|
157
|
+
expect(client.sendNotification).toBeCalledWith(DidCloseTextDocumentNotification.type, {
|
|
158
|
+
textDocument: {
|
|
159
|
+
uri: 'browser://input.liquid',
|
|
160
|
+
},
|
|
161
|
+
});
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
it('should send textDocument/didClose and textDocument/didOpen notifications when the file URI changes', () => {
|
|
165
|
+
view.dispatch({
|
|
166
|
+
effects: StateEffect.reconfigure.of([
|
|
167
|
+
clientFacet.of(client),
|
|
168
|
+
fileUriFacet.of('browser://other-file.liquid'),
|
|
169
|
+
textDocumentSync,
|
|
170
|
+
]),
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
expect(client.sendNotification).toBeCalledTimes(3);
|
|
174
|
+
expect(client.sendNotification).toBeCalledWith(DidCloseTextDocumentNotification.type, {
|
|
175
|
+
textDocument: {
|
|
176
|
+
uri: 'browser://input.liquid',
|
|
177
|
+
},
|
|
178
|
+
});
|
|
179
|
+
expect(client.sendNotification).toBeCalledWith(DidOpenTextDocumentNotification.type, {
|
|
180
|
+
textDocument: {
|
|
181
|
+
uri: 'browser://other-file.liquid',
|
|
182
|
+
version: 0,
|
|
183
|
+
languageId: 'liquid',
|
|
184
|
+
text: 'hello world',
|
|
185
|
+
},
|
|
186
|
+
});
|
|
187
|
+
});
|
|
188
|
+
});
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import { TextDocument } from 'vscode-languageserver-textdocument';
|
|
2
|
+
import {
|
|
3
|
+
DidChangeTextDocumentNotification,
|
|
4
|
+
DidCloseTextDocumentNotification,
|
|
5
|
+
DidOpenTextDocumentNotification,
|
|
6
|
+
} from 'vscode-languageserver-protocol';
|
|
7
|
+
import { Extension, StateField } from '@codemirror/state';
|
|
8
|
+
import { EditorView, PluginValue, ViewPlugin, ViewUpdate } from '@codemirror/view';
|
|
9
|
+
|
|
10
|
+
import { AbstractLanguageClient } from '../LanguageClient';
|
|
11
|
+
|
|
12
|
+
import { clientFacet, fileUriFacet } from './client';
|
|
13
|
+
|
|
14
|
+
class TextDocumentSyncPlugin implements PluginValue {
|
|
15
|
+
private client: AbstractLanguageClient;
|
|
16
|
+
private uri: string;
|
|
17
|
+
|
|
18
|
+
constructor(view: EditorView) {
|
|
19
|
+
const doc = view.state.field(textDocumentField);
|
|
20
|
+
this.client = view.state.facet(clientFacet);
|
|
21
|
+
this.uri = doc.uri;
|
|
22
|
+
this.openFile(doc);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
update(update: ViewUpdate) {
|
|
26
|
+
const doc = update.state.field(textDocumentField);
|
|
27
|
+
const prevFileUri = update.startState.facet(fileUriFacet.reader);
|
|
28
|
+
const currFileUri = update.state.facet(fileUriFacet.reader);
|
|
29
|
+
|
|
30
|
+
if (prevFileUri !== currFileUri) {
|
|
31
|
+
this.closeFile(prevFileUri);
|
|
32
|
+
this.openFile(doc);
|
|
33
|
+
this.uri = currFileUri;
|
|
34
|
+
} else if (update.docChanged) {
|
|
35
|
+
this.changeFile(doc);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
destroy() {
|
|
40
|
+
this.closeFile(this.uri);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
private openFile(doc: TextDocument) {
|
|
44
|
+
// Here we initialize the state in the Language Server by sending a
|
|
45
|
+
// textDocument/didOpen notification.
|
|
46
|
+
this.client.sendNotification(DidOpenTextDocumentNotification.type, {
|
|
47
|
+
textDocument: {
|
|
48
|
+
uri: doc.uri,
|
|
49
|
+
version: doc.version,
|
|
50
|
+
languageId: doc.languageId,
|
|
51
|
+
text: doc.getText(),
|
|
52
|
+
},
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
private changeFile(doc: TextDocument) {
|
|
57
|
+
// Here we send the textDocument/didChange notification to the server
|
|
58
|
+
// to tell it the file was modified. The version attribute is used to
|
|
59
|
+
// verify if the server and client are in sync (if the versions don't
|
|
60
|
+
// match, they aren't).
|
|
61
|
+
this.client.sendNotification(DidChangeTextDocumentNotification.type, {
|
|
62
|
+
textDocument: {
|
|
63
|
+
uri: doc.uri,
|
|
64
|
+
version: doc.version,
|
|
65
|
+
},
|
|
66
|
+
contentChanges: [
|
|
67
|
+
{
|
|
68
|
+
text: doc.getText(),
|
|
69
|
+
},
|
|
70
|
+
],
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
private closeFile(uri: string) {
|
|
75
|
+
this.client.sendNotification(DidCloseTextDocumentNotification.type, {
|
|
76
|
+
textDocument: {
|
|
77
|
+
uri,
|
|
78
|
+
},
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export const textDocumentField = StateField.define<TextDocument>({
|
|
84
|
+
create(state) {
|
|
85
|
+
const fileUri = state.facet(fileUriFacet.reader);
|
|
86
|
+
const version = 0;
|
|
87
|
+
return TextDocument.create(
|
|
88
|
+
fileUri,
|
|
89
|
+
languageId(fileUri),
|
|
90
|
+
version,
|
|
91
|
+
state.doc.sliceString(0, state.doc.length),
|
|
92
|
+
);
|
|
93
|
+
},
|
|
94
|
+
update(previousValue, tr) {
|
|
95
|
+
const prevFileUri = tr.startState.facet(fileUriFacet.reader);
|
|
96
|
+
const currFileUri = tr.state.facet(fileUriFacet.reader);
|
|
97
|
+
const isNewFile = prevFileUri !== currFileUri;
|
|
98
|
+
const doc = tr.newDoc;
|
|
99
|
+
if (isNewFile) {
|
|
100
|
+
const version = 0;
|
|
101
|
+
return TextDocument.create(
|
|
102
|
+
currFileUri,
|
|
103
|
+
languageId(currFileUri),
|
|
104
|
+
version,
|
|
105
|
+
doc.sliceString(0, doc.length),
|
|
106
|
+
);
|
|
107
|
+
} else if (tr.docChanged) {
|
|
108
|
+
return TextDocument.create(
|
|
109
|
+
previousValue.uri,
|
|
110
|
+
previousValue.languageId,
|
|
111
|
+
previousValue.version + 1,
|
|
112
|
+
doc.sliceString(0, doc.length),
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
return previousValue;
|
|
116
|
+
},
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
const textDocumentSyncPlugin = ViewPlugin.fromClass(TextDocumentSyncPlugin);
|
|
120
|
+
|
|
121
|
+
// Implicitly depends on the fileUriFacet and clientFacet...
|
|
122
|
+
// Don't know how that's typically handled yet...
|
|
123
|
+
export const textDocumentSync: Extension = [textDocumentField, textDocumentSyncPlugin];
|
|
124
|
+
|
|
125
|
+
function languageId(uri: string): string {
|
|
126
|
+
const lowerCased = uri.toLowerCase();
|
|
127
|
+
if (lowerCased.endsWith('.liquid')) {
|
|
128
|
+
return 'liquid';
|
|
129
|
+
} else if (lowerCased.endsWith('.graphql')) {
|
|
130
|
+
return 'graphql';
|
|
131
|
+
} else if (lowerCased.endsWith('.js')) {
|
|
132
|
+
return 'javascript';
|
|
133
|
+
} else if (lowerCased.endsWith('.css')) {
|
|
134
|
+
return 'css';
|
|
135
|
+
} else {
|
|
136
|
+
return 'unknown';
|
|
137
|
+
}
|
|
138
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { vi } from 'vitest';
|
|
2
|
+
import {
|
|
3
|
+
ProtocolRequestType,
|
|
4
|
+
ProtocolNotificationType,
|
|
5
|
+
CancellationTokenSource,
|
|
6
|
+
CancellationToken,
|
|
7
|
+
ServerCapabilities,
|
|
8
|
+
} from 'vscode-languageserver-protocol';
|
|
9
|
+
import { PromiseCompletion, AbstractLanguageClient, disposable } from '../LanguageClient';
|
|
10
|
+
|
|
11
|
+
export class MockClient extends EventTarget implements AbstractLanguageClient {
|
|
12
|
+
clientCapabilities: AbstractLanguageClient['clientCapabilities'];
|
|
13
|
+
serverCapabilities: AbstractLanguageClient['serverCapabilities'];
|
|
14
|
+
serverInfo: AbstractLanguageClient['serverInfo'];
|
|
15
|
+
|
|
16
|
+
// I'm too lazy to copy the types for this stuff.
|
|
17
|
+
onNotification: AbstractLanguageClient['onNotification'];
|
|
18
|
+
onRequest: AbstractLanguageClient['onRequest'];
|
|
19
|
+
sendNotification: AbstractLanguageClient['sendNotification'];
|
|
20
|
+
sendRequest: AbstractLanguageClient['sendRequest'];
|
|
21
|
+
|
|
22
|
+
public pendingRequest: Promise<any> | null;
|
|
23
|
+
private promiseCompletion: PromiseCompletion | null;
|
|
24
|
+
|
|
25
|
+
constructor(
|
|
26
|
+
clientCapabilities = {},
|
|
27
|
+
serverCapabilities: AbstractLanguageClient['serverCapabilities'] = null,
|
|
28
|
+
serverInfo = null,
|
|
29
|
+
) {
|
|
30
|
+
super();
|
|
31
|
+
this.clientCapabilities = clientCapabilities;
|
|
32
|
+
this.serverCapabilities = serverCapabilities;
|
|
33
|
+
this.serverInfo = serverInfo;
|
|
34
|
+
this.pendingRequest = null;
|
|
35
|
+
this.promiseCompletion = null;
|
|
36
|
+
|
|
37
|
+
this.onNotification = (type, handler) => {
|
|
38
|
+
const callback = (e: any) => handler(e.detail.params);
|
|
39
|
+
this.addEventListener(type.method, callback);
|
|
40
|
+
return disposable(() => this.removeEventListener(type.method, callback));
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
this.onRequest = (type, handler) => {
|
|
44
|
+
const cancellationToken: CancellationToken = new CancellationTokenSource().token;
|
|
45
|
+
const callback = (e: any) => handler(e.detail.params, cancellationToken);
|
|
46
|
+
this.addEventListener(type.method, callback);
|
|
47
|
+
return disposable(() => this.removeEventListener(type.method, callback));
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
this.sendRequest = vi.fn(async (_type, _params) => {
|
|
51
|
+
this.pendingRequest = new Promise((resolve, reject) => {
|
|
52
|
+
this.promiseCompletion = { resolve, reject };
|
|
53
|
+
});
|
|
54
|
+
return await this.pendingRequest;
|
|
55
|
+
}) as any;
|
|
56
|
+
|
|
57
|
+
this.sendNotification = vi.fn((_type, _params) => {}) as any;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
resolveRequest(value: any) {
|
|
61
|
+
if (!this.promiseCompletion) throw Error('Expecting a pending request');
|
|
62
|
+
this.promiseCompletion.resolve(value);
|
|
63
|
+
this.pendingRequest = null;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
rejectRequest(error: any) {
|
|
67
|
+
if (!this.promiseCompletion) throw Error('Expecting a pending request');
|
|
68
|
+
this.promiseCompletion.reject(error);
|
|
69
|
+
this.pendingRequest = null;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
triggerNotification<P, RO>(type: ProtocolNotificationType<P, RO>, params: P) {
|
|
73
|
+
this.dispatchEvent(
|
|
74
|
+
new CustomEvent(type.method, {
|
|
75
|
+
detail: {
|
|
76
|
+
jsonrpc: '2.0',
|
|
77
|
+
method: type.method,
|
|
78
|
+
params,
|
|
79
|
+
},
|
|
80
|
+
}),
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
triggerRequest<P, R, PR, E, RO>(type: ProtocolRequestType<P, R, PR, E, RO>, params: P) {
|
|
85
|
+
this.dispatchEvent(
|
|
86
|
+
new CustomEvent(type.method, {
|
|
87
|
+
detail: {
|
|
88
|
+
jsonrpc: '2.0',
|
|
89
|
+
requestId: 0,
|
|
90
|
+
method: type.method,
|
|
91
|
+
params,
|
|
92
|
+
},
|
|
93
|
+
}),
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { StateEffectType, StateField } from '@codemirror/state';
|
|
2
|
+
|
|
3
|
+
/** Sometimes you don't want to repeat yourself for a simple state field. */
|
|
4
|
+
export function simpleStateField<T>(
|
|
5
|
+
stateEffect: StateEffectType<T>,
|
|
6
|
+
defaultValue: T,
|
|
7
|
+
): StateField<T> {
|
|
8
|
+
return StateField.define<T>({
|
|
9
|
+
create: () => defaultValue,
|
|
10
|
+
update(value, tr) {
|
|
11
|
+
let updatedValue = value;
|
|
12
|
+
|
|
13
|
+
for (const effect of tr.effects) {
|
|
14
|
+
if (effect.is(stateEffect)) {
|
|
15
|
+
updatedValue = effect.value;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
return updatedValue;
|
|
20
|
+
},
|
|
21
|
+
});
|
|
22
|
+
}
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"extends": "../../tsconfig.json",
|
|
3
|
+
"include": ["./src/**/*.ts"],
|
|
4
|
+
"exclude": ["./dist"],
|
|
5
|
+
"compilerOptions": {
|
|
6
|
+
"outDir": "./dist/esm",
|
|
7
|
+
"tsBuildInfoFile": "./dist/esm/tsconfig.tsbuildInfo",
|
|
8
|
+
"module": "es6",
|
|
9
|
+
"rootDir": "src",
|
|
10
|
+
"resolveJsonModule": false,
|
|
11
|
+
"lib": [
|
|
12
|
+
"es2019",
|
|
13
|
+
"DOM",
|
|
14
|
+
"webworker"
|
|
15
|
+
]
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|