@kerebron/extension-codemirror 0.4.28 → 0.4.29
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/ExtensionCodeMirror.js +1 -0
- package/esm/ExtensionCodeMirror.js.map +1 -0
- package/esm/NodeCodeMirror.js +1 -0
- package/esm/NodeCodeMirror.js.map +1 -0
- package/esm/codeMirrorBlockNodeView.js +1 -0
- package/esm/codeMirrorBlockNodeView.js.map +1 -0
- package/esm/defaults.js +1 -0
- package/esm/defaults.js.map +1 -0
- package/esm/languageLoaders.js +1 -0
- package/esm/languageLoaders.js.map +1 -0
- package/esm/languages.js +1 -0
- package/esm/languages.js.map +1 -0
- package/esm/lsp/LSPExtension.js +1 -0
- package/esm/lsp/LSPExtension.js.map +1 -0
- package/esm/lsp/completion.js +1 -0
- package/esm/lsp/completion.js.map +1 -0
- package/esm/lsp/hover.js +1 -0
- package/esm/lsp/hover.js.map +1 -0
- package/esm/lsp/index.js +1 -0
- package/esm/lsp/index.js.map +1 -0
- package/esm/lsp/plugin.js +1 -0
- package/esm/lsp/plugin.js.map +1 -0
- package/esm/lsp/pos.js +1 -0
- package/esm/lsp/pos.js.map +1 -0
- package/esm/lsp/text.js +1 -0
- package/esm/lsp/text.js.map +1 -0
- package/esm/lsp/theme.js +1 -0
- package/esm/lsp/theme.js.map +1 -0
- package/esm/remote-selections.js +1 -0
- package/esm/remote-selections.js.map +1 -0
- package/esm/remote-sync.js +1 -0
- package/esm/remote-sync.js.map +1 -0
- package/esm/types.js +1 -0
- package/esm/types.js.map +1 -0
- package/esm/utils.js +1 -0
- package/esm/utils.js.map +1 -0
- package/package.json +9 -5
- package/src/ExtensionCodeMirror.ts +84 -0
- package/src/NodeCodeMirror.ts +135 -0
- package/src/codeMirrorBlockNodeView.ts +400 -0
- package/src/defaults.ts +80 -0
- package/src/languageLoaders.ts +401 -0
- package/src/languages.ts +109 -0
- package/src/lsp/LSPExtension.ts +42 -0
- package/src/lsp/completion.ts +231 -0
- package/src/lsp/hover.ts +100 -0
- package/src/lsp/index.ts +55 -0
- package/src/lsp/plugin.ts +128 -0
- package/src/lsp/pos.ts +12 -0
- package/src/lsp/text.ts +63 -0
- package/src/lsp/theme.ts +81 -0
- package/src/remote-selections.ts +263 -0
- package/src/remote-sync.ts +23 -0
- package/src/types.ts +55 -0
- package/src/utils.ts +336 -0
- package/assets/codemirror.css +0 -80
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
import type * as lsp from 'vscode-languageserver-protocol';
|
|
2
|
+
import { EditorState, Extension, Facet } from '@codemirror/state';
|
|
3
|
+
import {
|
|
4
|
+
autocompletion,
|
|
5
|
+
Completion,
|
|
6
|
+
CompletionContext,
|
|
7
|
+
CompletionSource,
|
|
8
|
+
snippet,
|
|
9
|
+
} from '@codemirror/autocomplete';
|
|
10
|
+
|
|
11
|
+
import { LSPPlugin } from './plugin.js';
|
|
12
|
+
import { CompletionTriggerKind } from 'vscode-languageserver-protocol';
|
|
13
|
+
|
|
14
|
+
export function serverCompletion(config: {
|
|
15
|
+
override?: boolean;
|
|
16
|
+
validFor?: RegExp;
|
|
17
|
+
} = {}): Extension {
|
|
18
|
+
let result: Extension[];
|
|
19
|
+
if (config.override) {
|
|
20
|
+
result = [autocompletion({ override: [serverCompletionSource] })];
|
|
21
|
+
} else {
|
|
22
|
+
const data = [{ autocomplete: serverCompletionSource }];
|
|
23
|
+
result = [autocompletion(), EditorState.languageData.of(() => data)];
|
|
24
|
+
}
|
|
25
|
+
if (config.validFor) {
|
|
26
|
+
result.push(completionConfig.of({ validFor: config.validFor }));
|
|
27
|
+
}
|
|
28
|
+
return result;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const completionConfig = Facet.define<
|
|
32
|
+
{ validFor: RegExp },
|
|
33
|
+
{ validFor: RegExp | null }
|
|
34
|
+
>({
|
|
35
|
+
combine: (results) => results.length ? results[0] : { validFor: null },
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
async function getCompletions(
|
|
39
|
+
plugin: LSPPlugin,
|
|
40
|
+
pos: number,
|
|
41
|
+
context: lsp.CompletionContext,
|
|
42
|
+
abort?: CompletionContext,
|
|
43
|
+
) {
|
|
44
|
+
const client = plugin.extensionLsp.getClient(plugin.lang);
|
|
45
|
+
if (!client) {
|
|
46
|
+
return [];
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (client.hasCapability('completionProvider') === false) {
|
|
50
|
+
null;
|
|
51
|
+
}
|
|
52
|
+
client.sync();
|
|
53
|
+
const params: lsp.CompletionParams = {
|
|
54
|
+
position: plugin.toPosition(pos),
|
|
55
|
+
textDocument: { uri: plugin.uri },
|
|
56
|
+
context,
|
|
57
|
+
};
|
|
58
|
+
if (abort) {
|
|
59
|
+
abort.addEventListener('abort', () => client.cancelRequest(params));
|
|
60
|
+
}
|
|
61
|
+
const result = await client.request<
|
|
62
|
+
lsp.CompletionParams,
|
|
63
|
+
lsp.CompletionItem[] | lsp.CompletionList | null
|
|
64
|
+
>(
|
|
65
|
+
'textDocument/completion',
|
|
66
|
+
params,
|
|
67
|
+
);
|
|
68
|
+
return result;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Look for non-alphanumeric prefixes in the completions, and return a
|
|
72
|
+
// regexp that matches them, to use in validFor
|
|
73
|
+
function prefixRegexp(items: readonly lsp.CompletionItem[]) {
|
|
74
|
+
let step = Math.ceil(items.length / 50), prefixes: string[] = [];
|
|
75
|
+
for (let i = 0; i < items.length; i += step) {
|
|
76
|
+
let item = items[i],
|
|
77
|
+
text = item.textEdit?.newText || item.textEditText || item.insertText ||
|
|
78
|
+
item.label;
|
|
79
|
+
if (!/^\w/.test(text)) {
|
|
80
|
+
let prefix = /^[^\w]*/.exec(text)![0];
|
|
81
|
+
if (prefixes.indexOf(prefix) < 0) prefixes.push(prefix);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
if (!prefixes.length) return /^\w*$/;
|
|
85
|
+
return new RegExp(
|
|
86
|
+
'^(?:' + prefixes.map(
|
|
87
|
+
(RegExp as any).escape || ((s) => s.replace(/[^\w\s]/g, '\\$&')),
|
|
88
|
+
).join('|') + ')?\\w*$',
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/// A completion source that requests completions from a language
|
|
93
|
+
/// server.
|
|
94
|
+
export const serverCompletionSource: CompletionSource = async (context) => {
|
|
95
|
+
const plugin = context.view && LSPPlugin.get(context.view);
|
|
96
|
+
if (!plugin) return null;
|
|
97
|
+
|
|
98
|
+
let triggerChar = '';
|
|
99
|
+
if (!context.explicit) {
|
|
100
|
+
triggerChar = context.view.state.sliceDoc(context.pos - 1, context.pos);
|
|
101
|
+
|
|
102
|
+
const client = plugin.extensionLsp.getClient(plugin.lang);
|
|
103
|
+
if (!client) {
|
|
104
|
+
return null;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const triggers = client.serverCapabilities?.completionProvider
|
|
108
|
+
?.triggerCharacters;
|
|
109
|
+
if (
|
|
110
|
+
!/[a-zA-Z_]/.test(triggerChar) &&
|
|
111
|
+
!(triggers && triggers.indexOf(triggerChar) > -1)
|
|
112
|
+
) return null;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
try {
|
|
116
|
+
let result = await getCompletions(plugin, context.pos, {
|
|
117
|
+
triggerCharacter: triggerChar,
|
|
118
|
+
triggerKind: context.explicit
|
|
119
|
+
? CompletionTriggerKind.Invoked
|
|
120
|
+
: CompletionTriggerKind.TriggerCharacter,
|
|
121
|
+
}, context);
|
|
122
|
+
|
|
123
|
+
if (!result) return null;
|
|
124
|
+
if (Array.isArray(result)) {
|
|
125
|
+
result = { items: result } as lsp.CompletionList;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const { from, to } = completionResultRange(context, result);
|
|
129
|
+
const defaultCommitChars = result.itemDefaults?.commitCharacters;
|
|
130
|
+
const config = context.state.facet(completionConfig);
|
|
131
|
+
|
|
132
|
+
const options = result.items.map<Completion>((item) => {
|
|
133
|
+
let text = item.textEdit?.newText || item.textEditText ||
|
|
134
|
+
item.insertText || item.label;
|
|
135
|
+
let option: Completion = {
|
|
136
|
+
label: text,
|
|
137
|
+
type: item.kind && kindToType[item.kind],
|
|
138
|
+
};
|
|
139
|
+
if (
|
|
140
|
+
item.commitCharacters && item.commitCharacters != defaultCommitChars
|
|
141
|
+
) {
|
|
142
|
+
option.commitCharacters = item.commitCharacters;
|
|
143
|
+
}
|
|
144
|
+
if (item.detail) option.detail = item.detail;
|
|
145
|
+
if (item.insertTextFormat == 2 /* Snippet */) {
|
|
146
|
+
option.apply = (view, c, from, to) => snippet(text)(view, c, from, to);
|
|
147
|
+
option.label = item.label;
|
|
148
|
+
}
|
|
149
|
+
if (item.documentation) {
|
|
150
|
+
option.info = () => renderDocInfo(plugin, item.documentation!);
|
|
151
|
+
}
|
|
152
|
+
return option;
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
return {
|
|
156
|
+
from,
|
|
157
|
+
to,
|
|
158
|
+
options,
|
|
159
|
+
commitCharacters: defaultCommitChars,
|
|
160
|
+
validFor: config.validFor ?? prefixRegexp(result.items),
|
|
161
|
+
map: (result, changes) => ({
|
|
162
|
+
...result,
|
|
163
|
+
from: changes.mapPos(result.from),
|
|
164
|
+
}),
|
|
165
|
+
};
|
|
166
|
+
} catch (err: any) {
|
|
167
|
+
if (
|
|
168
|
+
'code' in err &&
|
|
169
|
+
(err as lsp.ResponseError).code == -32800 /* RequestCancelled */
|
|
170
|
+
) {
|
|
171
|
+
return null;
|
|
172
|
+
}
|
|
173
|
+
throw err;
|
|
174
|
+
}
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
function completionResultRange(
|
|
178
|
+
cx: CompletionContext,
|
|
179
|
+
result: lsp.CompletionList,
|
|
180
|
+
): { from: number; to: number } {
|
|
181
|
+
if (!result.items.length) {
|
|
182
|
+
return { from: cx.pos, to: cx.pos };
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
const defaultRange = result.itemDefaults?.editRange;
|
|
186
|
+
const item0 = result.items[0];
|
|
187
|
+
|
|
188
|
+
const range = defaultRange
|
|
189
|
+
? ('insert' in defaultRange ? defaultRange.insert : defaultRange)
|
|
190
|
+
: item0.textEdit
|
|
191
|
+
? ('range' in item0.textEdit ? item0.textEdit.range : item0.textEdit.insert)
|
|
192
|
+
: null;
|
|
193
|
+
|
|
194
|
+
if (!range) return cx.state.wordAt(cx.pos) || { from: cx.pos, to: cx.pos };
|
|
195
|
+
|
|
196
|
+
const line = cx.state.doc.lineAt(cx.pos);
|
|
197
|
+
|
|
198
|
+
return {
|
|
199
|
+
from: line.from + range.start.character,
|
|
200
|
+
to: line.from + range.end.character,
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function renderDocInfo(plugin: LSPPlugin, doc: string | lsp.MarkupContent) {
|
|
205
|
+
let elt = document.createElement('div');
|
|
206
|
+
elt.className = 'cm-lsp-documentation cm-lsp-completion-documentation';
|
|
207
|
+
elt.innerHTML = plugin.docToHTML(doc);
|
|
208
|
+
return elt;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
const kindToType: { [kind: number]: string } = {
|
|
212
|
+
1: 'text', // Text
|
|
213
|
+
2: 'method', // Method
|
|
214
|
+
3: 'function', // Function
|
|
215
|
+
4: 'class', // Constructor
|
|
216
|
+
5: 'property', // Field
|
|
217
|
+
6: 'variable', // Variable
|
|
218
|
+
7: 'class', // Class
|
|
219
|
+
8: 'interface', // Interface
|
|
220
|
+
9: 'namespace', // Module
|
|
221
|
+
10: 'property', // Property
|
|
222
|
+
11: 'keyword', // Unit
|
|
223
|
+
12: 'constant', // Value
|
|
224
|
+
13: 'constant', // Enum
|
|
225
|
+
14: 'keyword', // Keyword
|
|
226
|
+
16: 'constant', // Color
|
|
227
|
+
20: 'constant', // EnumMember
|
|
228
|
+
21: 'constant', // Constant
|
|
229
|
+
22: 'class', // Struct
|
|
230
|
+
25: 'type', // TypeParameter
|
|
231
|
+
};
|
package/src/lsp/hover.ts
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import type * as lsp from 'vscode-languageserver-protocol';
|
|
2
|
+
import { EditorView, hoverTooltip, Tooltip } from '@codemirror/view';
|
|
3
|
+
import { Extension } from '@codemirror/state';
|
|
4
|
+
import {
|
|
5
|
+
highlightingFor,
|
|
6
|
+
language as languageFacet,
|
|
7
|
+
} from '@codemirror/language';
|
|
8
|
+
import { fromPosition } from './pos.js';
|
|
9
|
+
import { LSPPlugin } from './plugin.js';
|
|
10
|
+
// import {highlightCode} from "@lezer/highlight"
|
|
11
|
+
// import {escHTML} from "./text.ts"
|
|
12
|
+
|
|
13
|
+
/// Create an extension that queries the language server for hover
|
|
14
|
+
/// tooltips when the user hovers over the code with their pointer,
|
|
15
|
+
/// and displays a tooltip when the server provides one.
|
|
16
|
+
export function hoverTooltips(config: { hoverTime?: number } = {}): Extension {
|
|
17
|
+
return hoverTooltip(lspTooltipSource, {
|
|
18
|
+
hideOn: (tr) => tr.docChanged,
|
|
19
|
+
hoverTime: config.hoverTime,
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function hoverRequest(plugin: LSPPlugin, pos: number) {
|
|
24
|
+
const client = plugin.getClient();
|
|
25
|
+
if (client?.hasCapability('hoverProvider') === false) {
|
|
26
|
+
return Promise.resolve(null);
|
|
27
|
+
}
|
|
28
|
+
client?.sync();
|
|
29
|
+
return client?.request<lsp.HoverParams, lsp.Hover | null>(
|
|
30
|
+
'textDocument/hover',
|
|
31
|
+
{
|
|
32
|
+
position: plugin.toPosition(pos),
|
|
33
|
+
textDocument: { uri: plugin.uri },
|
|
34
|
+
},
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function lspTooltipSource(
|
|
39
|
+
view: EditorView,
|
|
40
|
+
pos: number,
|
|
41
|
+
): Promise<Tooltip | null> {
|
|
42
|
+
const plugin = LSPPlugin.get(view);
|
|
43
|
+
if (!plugin) return Promise.resolve(null);
|
|
44
|
+
return hoverRequest(plugin, pos).then((result) => {
|
|
45
|
+
if (!result) return null;
|
|
46
|
+
return {
|
|
47
|
+
pos: result.range
|
|
48
|
+
? fromPosition(view.state.doc, result.range.start)
|
|
49
|
+
: pos,
|
|
50
|
+
end: result.range ? fromPosition(view.state.doc, result.range.end) : pos,
|
|
51
|
+
create() {
|
|
52
|
+
let elt = document.createElement('div');
|
|
53
|
+
elt.className = 'cm-lsp-hover-tooltip cm-lsp-documentation';
|
|
54
|
+
elt.innerHTML = renderTooltipContent(plugin, result.contents);
|
|
55
|
+
return { dom: elt };
|
|
56
|
+
},
|
|
57
|
+
above: true,
|
|
58
|
+
};
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function renderTooltipContent(
|
|
63
|
+
plugin: LSPPlugin,
|
|
64
|
+
value: string | lsp.MarkupContent | lsp.MarkedString | lsp.MarkedString[],
|
|
65
|
+
) {
|
|
66
|
+
if (Array.isArray(value)) {
|
|
67
|
+
return value.map((m) => renderCode(plugin, m)).join('<br>');
|
|
68
|
+
}
|
|
69
|
+
if (
|
|
70
|
+
typeof value == 'string' || typeof value == 'object' && 'language' in value
|
|
71
|
+
) return renderCode(plugin, value);
|
|
72
|
+
return plugin.docToHTML(value);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function renderCode(plugin: LSPPlugin, code: lsp.MarkedString) {
|
|
76
|
+
if (typeof code == 'string') return plugin.docToHTML(code, 'markdown');
|
|
77
|
+
let { language, value } = code;
|
|
78
|
+
// return plugin.docToHTML(value, language); // TODO highlight language
|
|
79
|
+
return plugin.docToHTML(value, 'plaintext');
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/*
|
|
83
|
+
function renderCode(plugin: LSPPlugin, code: lsp.MarkedString) {
|
|
84
|
+
if (typeof code == "string") return plugin.docToHTML(code, "markdown")
|
|
85
|
+
let {language, value} = code
|
|
86
|
+
let lang = plugin.client.config.highlightLanguage && plugin.client.config.highlightLanguage(language || "")
|
|
87
|
+
if (!lang) {
|
|
88
|
+
let viewLang = plugin.view.state.facet(languageFacet)
|
|
89
|
+
if (viewLang && (!language || viewLang.name == language)) lang = viewLang
|
|
90
|
+
}
|
|
91
|
+
if (!lang) return escHTML(value)
|
|
92
|
+
let result = ""
|
|
93
|
+
highlightCode(value, lang.parser.parse(value), {style: tags => highlightingFor(plugin.view.state, tags)}, (text, cls) => {
|
|
94
|
+
result += cls ? `<span class="${cls}">${escHTML(text)}</span>` : escHTML(text)
|
|
95
|
+
}, () => {
|
|
96
|
+
result += "<br>"
|
|
97
|
+
})
|
|
98
|
+
return result
|
|
99
|
+
}
|
|
100
|
+
*/
|
package/src/lsp/index.ts
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { Extension } from '@codemirror/state';
|
|
2
|
+
import { keymap } from '@codemirror/view';
|
|
3
|
+
|
|
4
|
+
export { LSPExtension, type LSPExtensionConfig } from './LSPExtension.js';
|
|
5
|
+
export { LSPPlugin } from './plugin.js';
|
|
6
|
+
export { serverCompletion, serverCompletionSource } from './completion.js';
|
|
7
|
+
export { hoverTooltips } from './hover.js';
|
|
8
|
+
// export { formatDocument, formatKeymap } from './formatting.ts';
|
|
9
|
+
// export { renameKeymap, renameSymbol } from './rename.ts';
|
|
10
|
+
// export {
|
|
11
|
+
// nextSignature,
|
|
12
|
+
// prevSignature,
|
|
13
|
+
// showSignatureHelp,
|
|
14
|
+
// signatureHelp,
|
|
15
|
+
// signatureKeymap,
|
|
16
|
+
// } from './signature.ts';
|
|
17
|
+
// export {
|
|
18
|
+
// jumpToDeclaration,
|
|
19
|
+
// jumpToDefinition,
|
|
20
|
+
// jumpToDefinitionKeymap,
|
|
21
|
+
// jumpToImplementation,
|
|
22
|
+
// jumpToTypeDefinition,
|
|
23
|
+
// } from './definition.ts';
|
|
24
|
+
// export {
|
|
25
|
+
// closeReferencePanel,
|
|
26
|
+
// findReferences,
|
|
27
|
+
// findReferencesKeymap,
|
|
28
|
+
// } from './references.ts';
|
|
29
|
+
// export { serverDiagnostics } from './diagnostics.ts';
|
|
30
|
+
|
|
31
|
+
import { serverCompletion } from './completion.js';
|
|
32
|
+
import { hoverTooltips } from './hover.js';
|
|
33
|
+
// import { formatKeymap } from './formatting.ts';
|
|
34
|
+
// import { renameKeymap } from './rename.ts';
|
|
35
|
+
// import { signatureHelp } from './signature.ts';
|
|
36
|
+
// import { jumpToDefinitionKeymap } from './definition.ts';
|
|
37
|
+
// import { findReferencesKeymap } from './references.ts';
|
|
38
|
+
// import { serverDiagnostics } from './diagnostics.ts';
|
|
39
|
+
|
|
40
|
+
export function languageServerExtensions(): readonly (
|
|
41
|
+
Extension
|
|
42
|
+
)[] {
|
|
43
|
+
return [
|
|
44
|
+
serverCompletion(),
|
|
45
|
+
hoverTooltips(),
|
|
46
|
+
keymap.of([
|
|
47
|
+
// ...formatKeymap,
|
|
48
|
+
// ...renameKeymap,
|
|
49
|
+
// ...jumpToDefinitionKeymap,
|
|
50
|
+
// ...findReferencesKeymap,
|
|
51
|
+
]),
|
|
52
|
+
// signatureHelp(),
|
|
53
|
+
// serverDiagnostics(),
|
|
54
|
+
];
|
|
55
|
+
}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import type * as lsp from 'vscode-languageserver-protocol';
|
|
2
|
+
import { EditorView, ViewPlugin } from '@codemirror/view';
|
|
3
|
+
import { Text } from '@codemirror/state';
|
|
4
|
+
|
|
5
|
+
import { type CoreEditor, RawTextResult } from '@kerebron/editor';
|
|
6
|
+
import type {
|
|
7
|
+
ExtensionLsp,
|
|
8
|
+
LSPClient,
|
|
9
|
+
LspSource,
|
|
10
|
+
} from '@kerebron/extension-lsp';
|
|
11
|
+
|
|
12
|
+
import { fromPosition, toPosition } from './pos.js';
|
|
13
|
+
import { escHTML } from './text.js';
|
|
14
|
+
import { LSPExtension } from './index.js';
|
|
15
|
+
import { PositionMapper } from '@kerebron/extension-markdown/PositionMapper';
|
|
16
|
+
import { toRawTextResult } from '@kerebron/editor/utilities';
|
|
17
|
+
|
|
18
|
+
export class LSPPlugin {
|
|
19
|
+
readonly extensionLsp: ExtensionLsp;
|
|
20
|
+
readonly uri: string;
|
|
21
|
+
readonly editor: CoreEditor;
|
|
22
|
+
readonly extension: LSPExtension;
|
|
23
|
+
lang = 'plaintext';
|
|
24
|
+
client?: LSPClient;
|
|
25
|
+
source: LspSource;
|
|
26
|
+
|
|
27
|
+
/// @internal
|
|
28
|
+
constructor(
|
|
29
|
+
/// The editor view that this plugin belongs to.
|
|
30
|
+
readonly view: EditorView,
|
|
31
|
+
{ extension, extensionLsp, uri, editor }: {
|
|
32
|
+
extension: LSPExtension;
|
|
33
|
+
extensionLsp: ExtensionLsp;
|
|
34
|
+
uri: string;
|
|
35
|
+
editor: CoreEditor;
|
|
36
|
+
},
|
|
37
|
+
) {
|
|
38
|
+
this.extension = extension;
|
|
39
|
+
this.extensionLsp = extensionLsp;
|
|
40
|
+
this.uri = uri;
|
|
41
|
+
this.editor = editor;
|
|
42
|
+
|
|
43
|
+
this.source = {
|
|
44
|
+
ui: this.editor.ui,
|
|
45
|
+
getMappedContent: async () => {
|
|
46
|
+
const editor = this.editor;
|
|
47
|
+
const result: RawTextResult = toRawTextResult(
|
|
48
|
+
this.view.state.doc.toString().toString(),
|
|
49
|
+
0,
|
|
50
|
+
);
|
|
51
|
+
const mapper = new PositionMapper(editor, result.rawTextMap);
|
|
52
|
+
return {
|
|
53
|
+
...result,
|
|
54
|
+
mapper,
|
|
55
|
+
};
|
|
56
|
+
},
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
getClient() {
|
|
61
|
+
return this.client;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
setLang(lang: string) {
|
|
65
|
+
this.lang = lang;
|
|
66
|
+
|
|
67
|
+
const client = this.extensionLsp.getClient(lang);
|
|
68
|
+
this.client = client;
|
|
69
|
+
if (client) {
|
|
70
|
+
client.disconnect(this.uri);
|
|
71
|
+
client.connect(this.uri, this.source);
|
|
72
|
+
client.workspace.openFile(
|
|
73
|
+
this.uri,
|
|
74
|
+
lang,
|
|
75
|
+
this.source,
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
update() {
|
|
81
|
+
if (this.client) {
|
|
82
|
+
this.client.workspace.changedFile(
|
|
83
|
+
this.uri,
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// /// Render a doc string from the server to HTML.
|
|
89
|
+
// docToHTML(value: string | lsp.MarkupContent, defaultKind: lsp.MarkupKind = "plaintext") {
|
|
90
|
+
// let html = withContext(this.view, this.client.config.highlightLanguage, () => docToHTML(value, defaultKind))
|
|
91
|
+
// return this.client.config.sanitizeHTML ? this.client.config.sanitizeHTML(html) : html
|
|
92
|
+
// }
|
|
93
|
+
|
|
94
|
+
docToHTML(
|
|
95
|
+
value: string | lsp.MarkupContent,
|
|
96
|
+
defaultKind: lsp.MarkupKind = 'plaintext',
|
|
97
|
+
) {
|
|
98
|
+
if ('string' === typeof value) {
|
|
99
|
+
return escHTML(value);
|
|
100
|
+
}
|
|
101
|
+
return `docToHTML ${value.kind} ${JSON.stringify(value.value, null, 2)}`;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/// Convert a CodeMirror document offset into an LSP `{line,
|
|
105
|
+
/// character}` object. Defaults to using the view's current
|
|
106
|
+
/// document, but can be given another one.
|
|
107
|
+
toPosition(pos: number, doc: Text = this.view.state.doc): lsp.Position {
|
|
108
|
+
return toPosition(doc, pos);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/// Convert an LSP `{line, character}` object to a CodeMirror
|
|
112
|
+
/// document offset.
|
|
113
|
+
fromPosition(pos: lsp.Position, doc: Text = this.view.state.doc): number {
|
|
114
|
+
return fromPosition(doc, pos);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/// Display an error in this plugin's editor.
|
|
118
|
+
reportError(message: string, err: any) {
|
|
119
|
+
this.editor.ui.showError(err);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/// Get the LSP plugin associated with an editor, if any.
|
|
123
|
+
static get(view: EditorView) {
|
|
124
|
+
return view.plugin(lspPlugin);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
export const lspPlugin = ViewPlugin.fromClass(LSPPlugin);
|
package/src/lsp/pos.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type * as lsp from 'vscode-languageserver-protocol';
|
|
2
|
+
import { Text } from '@codemirror/state';
|
|
3
|
+
|
|
4
|
+
export function toPosition(doc: Text, pos: number): lsp.Position {
|
|
5
|
+
let line = doc.lineAt(pos);
|
|
6
|
+
return { line: line.number - 1, character: pos - line.from };
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function fromPosition(doc: Text, pos: lsp.Position): number {
|
|
10
|
+
let line = doc.line(pos.line + 1);
|
|
11
|
+
return line.from + pos.character;
|
|
12
|
+
}
|
package/src/lsp/text.ts
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import type * as lsp from 'vscode-languageserver-protocol';
|
|
2
|
+
import { EditorView } from '@codemirror/view';
|
|
3
|
+
import {
|
|
4
|
+
highlightingFor,
|
|
5
|
+
Language,
|
|
6
|
+
language as languageFacet,
|
|
7
|
+
} from '@codemirror/language';
|
|
8
|
+
// import {Marked} from "marked"
|
|
9
|
+
// import {highlightCode, Highlighter} from "@lezer/highlight"
|
|
10
|
+
|
|
11
|
+
// let context: {view: EditorView, language: ((name: string) => Language | null) | undefined} | null = null
|
|
12
|
+
|
|
13
|
+
// export function withContext<T>(view: EditorView, language: ((name: string) => Language | null) | undefined, f: () => T): T {
|
|
14
|
+
// let prev = context
|
|
15
|
+
// try {
|
|
16
|
+
// context = {view, language}
|
|
17
|
+
// return f()
|
|
18
|
+
// } finally {
|
|
19
|
+
// context = prev
|
|
20
|
+
// }
|
|
21
|
+
// }
|
|
22
|
+
|
|
23
|
+
export function escHTML(text: string) {
|
|
24
|
+
return text.replace(
|
|
25
|
+
/[\n<&]/g,
|
|
26
|
+
(ch) => ch == '\n' ? '<br>' : ch == '<' ? '<' : '&',
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// const marked = new Marked({
|
|
31
|
+
// walkTokens(token) {
|
|
32
|
+
// if (!context || token.type != "code") return
|
|
33
|
+
|
|
34
|
+
// let lang = context.language && context.language(token.lang)
|
|
35
|
+
// if (!lang) {
|
|
36
|
+
// let viewLang = context.view.state.facet(languageFacet)
|
|
37
|
+
// if (viewLang && viewLang.name == token.lang) lang = viewLang
|
|
38
|
+
// }
|
|
39
|
+
// if (!lang) return
|
|
40
|
+
// let highlighter: Highlighter = {style: tags => highlightingFor(context!.view.state, tags)}
|
|
41
|
+
// let result = ""
|
|
42
|
+
// highlightCode(token.text, lang.parser.parse(token.text), highlighter, (text, cls) => {
|
|
43
|
+
// result += cls ? `<span class="${cls}">${escHTML(text)}</span>` : escHTML(text)
|
|
44
|
+
// }, () => {
|
|
45
|
+
// result += "<br>"
|
|
46
|
+
// })
|
|
47
|
+
// token.escaped = true
|
|
48
|
+
// token.text = result
|
|
49
|
+
// }
|
|
50
|
+
// })
|
|
51
|
+
|
|
52
|
+
// export function docToHTML(value: string | lsp.MarkupContent, defaultKind: lsp.MarkupKind) {
|
|
53
|
+
// let kind = defaultKind, text = value
|
|
54
|
+
// if (typeof text != "string") {
|
|
55
|
+
// kind = text.kind
|
|
56
|
+
// text = text.value
|
|
57
|
+
// }
|
|
58
|
+
// if (kind == "plaintext") {
|
|
59
|
+
// return escHTML(text)
|
|
60
|
+
// } else {
|
|
61
|
+
// return marked.parse(text, {async: false, })
|
|
62
|
+
// }
|
|
63
|
+
// }
|
package/src/lsp/theme.ts
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { EditorView } from '@codemirror/view';
|
|
2
|
+
|
|
3
|
+
export const lspTheme = EditorView.baseTheme({
|
|
4
|
+
'.cm-lsp-documentation': {
|
|
5
|
+
padding: '0 7px',
|
|
6
|
+
'& p, & pre': {
|
|
7
|
+
margin: '2px 0',
|
|
8
|
+
},
|
|
9
|
+
},
|
|
10
|
+
|
|
11
|
+
'.cm-lsp-signature-tooltip': {
|
|
12
|
+
padding: '2px 6px',
|
|
13
|
+
borderRadius: '2.5px',
|
|
14
|
+
position: 'relative',
|
|
15
|
+
maxWidth: '30em',
|
|
16
|
+
maxHeight: '10em',
|
|
17
|
+
overflowY: 'scroll',
|
|
18
|
+
'& .cm-lsp-documentation': {
|
|
19
|
+
padding: '0',
|
|
20
|
+
fontSize: '80%',
|
|
21
|
+
},
|
|
22
|
+
'& .cm-lsp-signature-num': {
|
|
23
|
+
fontFamily: 'monospace',
|
|
24
|
+
position: 'absolute',
|
|
25
|
+
left: '2px',
|
|
26
|
+
top: '4px',
|
|
27
|
+
fontSize: '70%',
|
|
28
|
+
lineHeight: '1.3',
|
|
29
|
+
},
|
|
30
|
+
'& .cm-lsp-signature': {
|
|
31
|
+
fontFamily: 'monospace',
|
|
32
|
+
textIndent: '1em hanging',
|
|
33
|
+
},
|
|
34
|
+
'& .cm-lsp-active-parameter': {
|
|
35
|
+
fontWeight: 'bold',
|
|
36
|
+
},
|
|
37
|
+
},
|
|
38
|
+
'.cm-lsp-signature-multiple': {
|
|
39
|
+
paddingLeft: '1.5em',
|
|
40
|
+
},
|
|
41
|
+
|
|
42
|
+
'.cm-panel.cm-lsp-rename-panel': {
|
|
43
|
+
padding: '2px 6px 4px',
|
|
44
|
+
position: 'relative',
|
|
45
|
+
'& label': { fontSize: '80%' },
|
|
46
|
+
'& [name=close]': {
|
|
47
|
+
position: 'absolute',
|
|
48
|
+
top: '0',
|
|
49
|
+
bottom: '0',
|
|
50
|
+
right: '4px',
|
|
51
|
+
backgroundColor: 'inherit',
|
|
52
|
+
border: 'none',
|
|
53
|
+
font: 'inherit',
|
|
54
|
+
padding: '0',
|
|
55
|
+
},
|
|
56
|
+
},
|
|
57
|
+
|
|
58
|
+
'.cm-lsp-message button[type=submit]': {
|
|
59
|
+
display: 'block',
|
|
60
|
+
},
|
|
61
|
+
|
|
62
|
+
'.cm-lsp-reference-panel': {
|
|
63
|
+
fontFamily: 'monospace',
|
|
64
|
+
whiteSpace: 'pre',
|
|
65
|
+
padding: '3px 6px',
|
|
66
|
+
maxHeight: '120px',
|
|
67
|
+
overflow: 'auto',
|
|
68
|
+
'& .cm-lsp-reference-file': {
|
|
69
|
+
fontWeight: 'bold',
|
|
70
|
+
},
|
|
71
|
+
'& .cm-lsp-reference': {
|
|
72
|
+
cursor: 'pointer',
|
|
73
|
+
'&[aria-selected]': {
|
|
74
|
+
backgroundColor: '#0077ee44',
|
|
75
|
+
},
|
|
76
|
+
},
|
|
77
|
+
'& .cm-lsp-reference-line': {
|
|
78
|
+
opacity: '0.7',
|
|
79
|
+
},
|
|
80
|
+
},
|
|
81
|
+
});
|