@difizen/libro-codemirror 0.1.0 → 0.1.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/es/auto-complete/closebrackets.d.ts +12 -0
- package/es/auto-complete/closebrackets.d.ts.map +1 -0
- package/es/auto-complete/completion.d.ts +57 -0
- package/es/auto-complete/completion.d.ts.map +1 -0
- package/es/auto-complete/config.d.ts +22 -0
- package/es/auto-complete/config.d.ts.map +1 -0
- package/es/auto-complete/filter.d.ts +13 -0
- package/es/auto-complete/filter.d.ts.map +1 -0
- package/es/auto-complete/filter.js +1 -7
- package/es/auto-complete/index.d.ts.map +1 -1
- package/es/auto-complete/index.js +5 -9
- package/es/auto-complete/snippet.d.ts +14 -0
- package/es/auto-complete/snippet.d.ts.map +1 -0
- package/es/auto-complete/snippet.js +2 -2
- package/es/auto-complete/state.d.ts +63 -0
- package/es/auto-complete/state.d.ts.map +1 -0
- package/es/auto-complete/state.js +5 -16
- package/es/auto-complete/theme.d.ts +6 -0
- package/es/auto-complete/theme.d.ts.map +1 -0
- package/es/auto-complete/tooltip.d.ts +5 -0
- package/es/auto-complete/tooltip.d.ts.map +1 -0
- package/es/auto-complete/view.d.ts +43 -0
- package/es/auto-complete/view.d.ts.map +1 -0
- package/es/auto-complete/view.js +2 -13
- package/es/auto-complete/word.d.ts +3 -0
- package/es/auto-complete/word.d.ts.map +1 -0
- package/es/completion.js +1 -1
- package/es/config.d.ts +15 -10
- package/es/config.d.ts.map +1 -1
- package/es/config.js +15 -6
- package/es/editor-contribution.d.ts +8 -0
- package/es/editor-contribution.d.ts.map +1 -0
- package/es/editor-contribution.js +30 -0
- package/es/editor.d.ts +40 -10
- package/es/editor.d.ts.map +1 -1
- package/es/editor.js +241 -45
- package/es/hyperlink.d.ts +15 -0
- package/es/hyperlink.d.ts.map +1 -0
- package/es/indentation-markers/config.d.ts +17 -0
- package/es/indentation-markers/config.d.ts.map +1 -0
- package/es/indentation-markers/index.d.ts +3 -0
- package/es/indentation-markers/index.d.ts.map +1 -0
- package/es/indentation-markers/map.d.ts +77 -0
- package/es/indentation-markers/map.d.ts.map +1 -0
- package/es/indentation-markers/utils.d.ts +27 -0
- package/es/indentation-markers/utils.d.ts.map +1 -0
- package/es/index.d.ts +3 -1
- package/es/index.d.ts.map +1 -1
- package/es/index.js +3 -1
- package/es/libro-icon.d.ts +3 -0
- package/es/libro-icon.d.ts.map +1 -0
- package/es/libro-icon.js +2 -2
- package/es/lsp/completion.d.ts +5 -0
- package/es/lsp/completion.d.ts.map +1 -0
- package/es/lsp/completion.js +245 -0
- package/es/lsp/format.d.ts +7 -0
- package/es/lsp/format.d.ts.map +1 -0
- package/es/lsp/format.js +193 -0
- package/es/lsp/index.d.ts +7 -0
- package/es/lsp/index.d.ts.map +1 -0
- package/es/lsp/index.js +6 -0
- package/es/lsp/lint.d.ts +3 -0
- package/es/lsp/lint.d.ts.map +1 -0
- package/es/lsp/lint.js +114 -0
- package/es/lsp/protocol.d.ts +7 -0
- package/es/lsp/protocol.d.ts.map +1 -0
- package/es/lsp/protocol.js +1 -0
- package/es/lsp/tooltip.d.ts +3 -0
- package/es/lsp/tooltip.d.ts.map +1 -0
- package/es/lsp/tooltip.js +113 -0
- package/es/lsp/util.d.ts +15 -0
- package/es/lsp/util.d.ts.map +1 -0
- package/es/lsp/util.js +58 -0
- package/es/mode.d.ts.map +1 -1
- package/es/module.d.ts +3 -0
- package/es/module.d.ts.map +1 -0
- package/es/module.js +4 -0
- package/es/monitor.d.ts +32 -0
- package/es/monitor.d.ts.map +1 -0
- package/es/python-lang.d.ts +3 -0
- package/es/python-lang.d.ts.map +1 -0
- package/es/theme.d.ts +35 -0
- package/es/theme.d.ts.map +1 -0
- package/es/theme.js +4 -5
- package/es/tooltip.d.ts +1 -1
- package/es/tooltip.d.ts.map +1 -1
- package/es/tooltip.js +2 -4
- package/package.json +7 -5
- package/src/auto-complete/filter.ts +5 -7
- package/src/auto-complete/index.ts +6 -7
- package/src/auto-complete/snippet.ts +8 -2
- package/src/auto-complete/state.ts +13 -18
- package/src/auto-complete/view.ts +7 -13
- package/src/completion.ts +2 -2
- package/src/config.ts +40 -28
- package/src/editor-contribution.ts +17 -0
- package/src/editor.ts +226 -50
- package/src/hyperlink.ts +1 -1
- package/src/indentation-markers/index.ts +3 -3
- package/src/indentation-markers/map.ts +9 -9
- package/src/index.ts +4 -1
- package/src/libro-icon.tsx +4 -0
- package/src/lsp/completion.ts +175 -0
- package/src/lsp/format.ts +144 -0
- package/src/lsp/index.ts +6 -0
- package/src/lsp/lint.ts +125 -0
- package/src/lsp/protocol.ts +8 -0
- package/src/lsp/tooltip.ts +76 -0
- package/src/lsp/util.ts +69 -0
- package/src/mode.ts +1 -1
- package/src/module.ts +8 -0
- package/src/theme.ts +4 -4
- package/src/tooltip.ts +2 -4
- package/src/libro-icon.ts +0 -4
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import { pythonLanguage } from '@codemirror/lang-python';
|
|
2
|
+
import { CompletionItemKind, CompletionTriggerKind } from '@difizen/libro-lsp';
|
|
3
|
+
|
|
4
|
+
import type { Completion, CompletionSource } from '../auto-complete/index.js';
|
|
5
|
+
|
|
6
|
+
import type { CMLSPExtension } from './protocol.js';
|
|
7
|
+
import { offsetToPos, renderMarkupContent } from './util.js';
|
|
8
|
+
|
|
9
|
+
export type CompletionItemDetailReolve = (
|
|
10
|
+
completion: Completion,
|
|
11
|
+
) => Node | null | Promise<Node | null>;
|
|
12
|
+
|
|
13
|
+
const CompletionItemKindMap = Object.fromEntries(
|
|
14
|
+
Object.entries(CompletionItemKind).map(([key, value]) => [value, key]),
|
|
15
|
+
) as Record<CompletionItemKind, string>;
|
|
16
|
+
|
|
17
|
+
function toSet(chars: Set<string>) {
|
|
18
|
+
let preamble = '';
|
|
19
|
+
let flat = Array.from(chars).join('');
|
|
20
|
+
const words = /\w/.test(flat);
|
|
21
|
+
if (words) {
|
|
22
|
+
preamble += '\\w';
|
|
23
|
+
flat = flat.replace(/\w/g, '');
|
|
24
|
+
}
|
|
25
|
+
return `[${preamble}${flat.replace(/[^\w\s]/g, '\\$&')}]`;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function prefixMatch(options: Completion[]) {
|
|
29
|
+
const first = new Set<string>();
|
|
30
|
+
const rest = new Set<string>();
|
|
31
|
+
|
|
32
|
+
for (const { apply } of options) {
|
|
33
|
+
const [initial, ...restStr] = apply as string;
|
|
34
|
+
first.add(initial);
|
|
35
|
+
for (const char of restStr) {
|
|
36
|
+
rest.add(char);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const source = toSet(first) + toSet(rest) + '*$';
|
|
41
|
+
return [new RegExp('^' + source), new RegExp(source)];
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export const lspPythonCompletion: CMLSPExtension = ({ lspProvider }) => {
|
|
45
|
+
const completionSource: CompletionSource = async (context) => {
|
|
46
|
+
/**
|
|
47
|
+
* 只在显式的使用tab触发时调用kernel completion
|
|
48
|
+
* 只在只在隐式的输入时触发时调用lsp completion
|
|
49
|
+
*/
|
|
50
|
+
if (!lspProvider || context.explicit === true) {
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const { virtualDocument: doc, lspConnection, editor } = await lspProvider();
|
|
55
|
+
|
|
56
|
+
const { state } = context;
|
|
57
|
+
let { pos } = context;
|
|
58
|
+
|
|
59
|
+
if (
|
|
60
|
+
!lspConnection ||
|
|
61
|
+
!lspConnection.isReady ||
|
|
62
|
+
!lspConnection.provides('completionProvider')
|
|
63
|
+
) {
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const { line, character } = offsetToPos(state.doc, pos);
|
|
68
|
+
|
|
69
|
+
const rootPos = doc.transformFromEditorToRoot(editor, {
|
|
70
|
+
line,
|
|
71
|
+
ch: character,
|
|
72
|
+
isEditor: true,
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
if (!rootPos) {
|
|
76
|
+
return null;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const virtualPos = doc.virtualPositionAtDocument(rootPos);
|
|
80
|
+
|
|
81
|
+
const result = await lspConnection.clientRequests[
|
|
82
|
+
'textDocument/completion'
|
|
83
|
+
].request({
|
|
84
|
+
position: { line: virtualPos.line, character: virtualPos.ch },
|
|
85
|
+
textDocument: {
|
|
86
|
+
uri: doc.documentInfo.uri,
|
|
87
|
+
},
|
|
88
|
+
context: {
|
|
89
|
+
triggerKind: CompletionTriggerKind.Invoked,
|
|
90
|
+
},
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
if (!result) {
|
|
94
|
+
return null;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const items = 'items' in result ? result.items : result;
|
|
98
|
+
|
|
99
|
+
let options = items.map((item) => {
|
|
100
|
+
const { detail, label, kind, textEdit, documentation, sortText, filterText } =
|
|
101
|
+
item;
|
|
102
|
+
const completion: Completion & {
|
|
103
|
+
filterText: string;
|
|
104
|
+
sortText?: string;
|
|
105
|
+
apply: string;
|
|
106
|
+
} = {
|
|
107
|
+
label,
|
|
108
|
+
detail,
|
|
109
|
+
apply: textEdit?.newText ?? label,
|
|
110
|
+
type: kind && CompletionItemKindMap[kind].toLowerCase(),
|
|
111
|
+
sortText: sortText ?? label,
|
|
112
|
+
filterText: filterText ?? label,
|
|
113
|
+
};
|
|
114
|
+
if (documentation) {
|
|
115
|
+
const resolver: CompletionItemDetailReolve = async () => {
|
|
116
|
+
return renderMarkupContent(documentation);
|
|
117
|
+
};
|
|
118
|
+
completion.info = resolver;
|
|
119
|
+
} else {
|
|
120
|
+
const resolver: CompletionItemDetailReolve = async () => {
|
|
121
|
+
const itemResult =
|
|
122
|
+
await lspConnection.clientRequests['completionItem/resolve'].request(item);
|
|
123
|
+
return itemResult.documentation
|
|
124
|
+
? renderMarkupContent(itemResult.documentation)
|
|
125
|
+
: null;
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
completion.info = resolver;
|
|
129
|
+
}
|
|
130
|
+
return completion;
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
const [, match] = prefixMatch(options);
|
|
134
|
+
const token = context.matchBefore(match);
|
|
135
|
+
|
|
136
|
+
// TODO: sort 方法需要进一步改进
|
|
137
|
+
if (token) {
|
|
138
|
+
pos = token.from;
|
|
139
|
+
const word = token.text.toLowerCase();
|
|
140
|
+
if (/^\w+$/.test(word)) {
|
|
141
|
+
options = options
|
|
142
|
+
.filter(({ filterText }) => filterText.toLowerCase().startsWith(word))
|
|
143
|
+
.sort(
|
|
144
|
+
({ apply: a, sortText: sortTexta }, { apply: b, sortText: sortTextb }) => {
|
|
145
|
+
switch (true) {
|
|
146
|
+
case sortTexta !== undefined && sortTextb !== undefined:
|
|
147
|
+
return sortTexta!.localeCompare(sortTextb!);
|
|
148
|
+
case a.startsWith(token.text) && !b.startsWith(token.text):
|
|
149
|
+
return -1;
|
|
150
|
+
case !a.startsWith(token.text) && b.startsWith(token.text):
|
|
151
|
+
return 1;
|
|
152
|
+
}
|
|
153
|
+
return 0;
|
|
154
|
+
},
|
|
155
|
+
);
|
|
156
|
+
}
|
|
157
|
+
} else {
|
|
158
|
+
options = options.sort(({ sortText: sortTexta }, { sortText: sortTextb }) => {
|
|
159
|
+
switch (true) {
|
|
160
|
+
case sortTexta !== undefined && sortTextb !== undefined:
|
|
161
|
+
return sortTexta!.localeCompare(sortTextb!);
|
|
162
|
+
}
|
|
163
|
+
return 0;
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
return {
|
|
168
|
+
from: pos,
|
|
169
|
+
options,
|
|
170
|
+
};
|
|
171
|
+
};
|
|
172
|
+
return pythonLanguage.data.of({
|
|
173
|
+
autocomplete: completionSource,
|
|
174
|
+
});
|
|
175
|
+
};
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-parameter-properties */
|
|
2
|
+
/* eslint-disable @typescript-eslint/parameter-properties */
|
|
3
|
+
import type { TransactionSpec } from '@codemirror/state';
|
|
4
|
+
import { StateEffect } from '@codemirror/state';
|
|
5
|
+
import type {
|
|
6
|
+
Command,
|
|
7
|
+
EditorView,
|
|
8
|
+
KeyBinding,
|
|
9
|
+
PluginValue,
|
|
10
|
+
ViewUpdate,
|
|
11
|
+
} from '@codemirror/view';
|
|
12
|
+
import { ViewPlugin } from '@codemirror/view';
|
|
13
|
+
|
|
14
|
+
import { insertCompletionText } from '../auto-complete/index.js';
|
|
15
|
+
|
|
16
|
+
import type { CMLSPExtension, LSPExtensionOptions } from './protocol.js';
|
|
17
|
+
import { offsetToPos, posToOffset } from './util.js';
|
|
18
|
+
|
|
19
|
+
export const startFormatEffect = StateEffect.define<boolean>();
|
|
20
|
+
|
|
21
|
+
export const formatCell: Command = (view: EditorView) => {
|
|
22
|
+
view.dispatch({ effects: startFormatEffect.of(true) });
|
|
23
|
+
return true;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export const formatKeymap: readonly KeyBinding[] = [{ key: 'Alt-f', run: formatCell }];
|
|
27
|
+
|
|
28
|
+
class FormatPlugin implements PluginValue {
|
|
29
|
+
constructor(
|
|
30
|
+
readonly view: EditorView,
|
|
31
|
+
readonly options: LSPExtensionOptions,
|
|
32
|
+
) {}
|
|
33
|
+
|
|
34
|
+
update(update: ViewUpdate) {
|
|
35
|
+
for (const tr of update.transactions) {
|
|
36
|
+
for (const effect of tr.effects) {
|
|
37
|
+
if (effect.is(startFormatEffect)) {
|
|
38
|
+
this.doFormat();
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
async doFormat() {
|
|
45
|
+
const lspProvider = await this.options.lspProvider?.();
|
|
46
|
+
if (!lspProvider) {
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
// const { state } = this.view;
|
|
50
|
+
// const currentLine = state.doc.lineAt(state.selection.main.head).number;
|
|
51
|
+
|
|
52
|
+
const { editor, virtualDocument, lspConnection } = lspProvider;
|
|
53
|
+
const virtualStartPos = virtualDocument.transformEditorToVirtual(editor, {
|
|
54
|
+
line: 0,
|
|
55
|
+
ch: 0,
|
|
56
|
+
isEditor: true,
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
const end = offsetToPos(this.view.state.doc, this.view.state.doc.length);
|
|
60
|
+
|
|
61
|
+
const virtualEndPos = virtualDocument.transformEditorToVirtual(editor, {
|
|
62
|
+
line: end.line,
|
|
63
|
+
ch: end.character,
|
|
64
|
+
isEditor: true,
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
if (!virtualStartPos || !virtualEndPos) {
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
lspConnection.clientRequests['textDocument/rangeFormatting']
|
|
72
|
+
.request({
|
|
73
|
+
textDocument: { uri: virtualDocument.uri },
|
|
74
|
+
range: {
|
|
75
|
+
start: { line: virtualStartPos.line, character: virtualStartPos.ch },
|
|
76
|
+
end: { line: virtualEndPos.line, character: virtualEndPos.ch },
|
|
77
|
+
},
|
|
78
|
+
options: {
|
|
79
|
+
tabSize: this.view.state.tabSize,
|
|
80
|
+
insertSpaces: true,
|
|
81
|
+
},
|
|
82
|
+
})
|
|
83
|
+
.then((result) => {
|
|
84
|
+
if (result && result?.length) {
|
|
85
|
+
const items = result;
|
|
86
|
+
const transaction: TransactionSpec[] = [];
|
|
87
|
+
items.forEach((item) => {
|
|
88
|
+
const defaultNewLine = {
|
|
89
|
+
line: end.line + 1,
|
|
90
|
+
ch: 0,
|
|
91
|
+
};
|
|
92
|
+
const editorStart =
|
|
93
|
+
virtualDocument.transformVirtualToEditor({
|
|
94
|
+
line: item.range.start.line,
|
|
95
|
+
ch: item.range.start.character,
|
|
96
|
+
isVirtual: true,
|
|
97
|
+
}) ?? defaultNewLine;
|
|
98
|
+
const editorEnd =
|
|
99
|
+
virtualDocument.transformVirtualToEditor({
|
|
100
|
+
line: item.range.end.line,
|
|
101
|
+
ch: item.range.end.character,
|
|
102
|
+
isVirtual: true,
|
|
103
|
+
}) ?? defaultNewLine;
|
|
104
|
+
|
|
105
|
+
if (!editorStart || !editorEnd) {
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
const from = posToOffset(this.view.state.doc, {
|
|
109
|
+
line: editorStart.line,
|
|
110
|
+
character: editorStart.ch,
|
|
111
|
+
});
|
|
112
|
+
const to = posToOffset(this.view.state.doc, {
|
|
113
|
+
line: editorEnd.line,
|
|
114
|
+
character: editorEnd.ch,
|
|
115
|
+
});
|
|
116
|
+
// FIXME: 需要处理新增行的情况,目前在virtualdocument无法处理
|
|
117
|
+
// console.log('format', item.range, editorStart, editorEnd, from, to);
|
|
118
|
+
if (from !== undefined && to !== undefined) {
|
|
119
|
+
const trans = insertCompletionText(
|
|
120
|
+
this.view.state,
|
|
121
|
+
item.newText,
|
|
122
|
+
from,
|
|
123
|
+
to,
|
|
124
|
+
);
|
|
125
|
+
transaction.push(trans);
|
|
126
|
+
}
|
|
127
|
+
});
|
|
128
|
+
// console.log(transaction, 'format trans');
|
|
129
|
+
|
|
130
|
+
this.view.dispatch(...transaction);
|
|
131
|
+
}
|
|
132
|
+
return;
|
|
133
|
+
})
|
|
134
|
+
.catch(console.error);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
destroy() {
|
|
138
|
+
//
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
export const lspFormat: CMLSPExtension = (options) => {
|
|
143
|
+
return [ViewPlugin.define((view) => new FormatPlugin(view, options))];
|
|
144
|
+
};
|
package/src/lsp/index.ts
ADDED
package/src/lsp/lint.ts
ADDED
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import type { Diagnostic } from '@codemirror/lint';
|
|
2
|
+
import { setDiagnostics } from '@codemirror/lint';
|
|
3
|
+
import type { PluginValue, EditorView } from '@codemirror/view';
|
|
4
|
+
import { ViewPlugin } from '@codemirror/view';
|
|
5
|
+
import { DiagnosticSeverity } from '@difizen/libro-lsp';
|
|
6
|
+
|
|
7
|
+
import type { CMLSPExtension, LSPExtensionOptions } from './protocol.js';
|
|
8
|
+
import { posToOffset } from './util.js';
|
|
9
|
+
|
|
10
|
+
class LintPlugin implements PluginValue {
|
|
11
|
+
constructor(
|
|
12
|
+
readonly view: EditorView,
|
|
13
|
+
readonly options: LSPExtensionOptions,
|
|
14
|
+
) {
|
|
15
|
+
this.processDiagnostic();
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
processDiagnostic() {
|
|
19
|
+
if (!this.options.lspProvider) {
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
this.options
|
|
23
|
+
.lspProvider()
|
|
24
|
+
.then(({ lspConnection, virtualDocument, editor }) => {
|
|
25
|
+
if (!lspConnection) {
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
lspConnection.serverNotifications['textDocument/publishDiagnostics'].event(
|
|
29
|
+
(e) => {
|
|
30
|
+
const diagnostics = e.diagnostics
|
|
31
|
+
.map(({ range, message, severity = DiagnosticSeverity.Information }) => {
|
|
32
|
+
const currentEditor = virtualDocument.getEditorAtVirtualLine({
|
|
33
|
+
line: range.start.line,
|
|
34
|
+
ch: range.start.character,
|
|
35
|
+
isVirtual: true,
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
// the diagnostic range must be in current editor
|
|
39
|
+
if (editor !== currentEditor) {
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const editorStart = virtualDocument.transformVirtualToEditor({
|
|
44
|
+
line: range.start.line,
|
|
45
|
+
ch: range.start.character,
|
|
46
|
+
isVirtual: true,
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
let offset: number | undefined;
|
|
50
|
+
if (editorStart) {
|
|
51
|
+
offset = posToOffset(this.view.state.doc, {
|
|
52
|
+
line: editorStart.line,
|
|
53
|
+
character: editorStart.ch,
|
|
54
|
+
})!;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const editorEnd = virtualDocument.transformVirtualToEditor({
|
|
58
|
+
line: range.end.line,
|
|
59
|
+
ch: range.end.character,
|
|
60
|
+
isVirtual: true,
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
let end: number | undefined;
|
|
64
|
+
if (editorEnd) {
|
|
65
|
+
end = posToOffset(this.view.state.doc, {
|
|
66
|
+
line: editorEnd.line,
|
|
67
|
+
character: editorEnd.ch,
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
return {
|
|
71
|
+
from: offset,
|
|
72
|
+
to: end,
|
|
73
|
+
severity: (
|
|
74
|
+
{
|
|
75
|
+
[DiagnosticSeverity.Error]: 'error',
|
|
76
|
+
[DiagnosticSeverity.Warning]: 'warning',
|
|
77
|
+
[DiagnosticSeverity.Information]: 'info',
|
|
78
|
+
[DiagnosticSeverity.Hint]: 'info',
|
|
79
|
+
} as const
|
|
80
|
+
)[severity],
|
|
81
|
+
message,
|
|
82
|
+
} as Diagnostic;
|
|
83
|
+
})
|
|
84
|
+
.filter<Diagnostic>(isDiagnostic)
|
|
85
|
+
.sort((a, b) => {
|
|
86
|
+
switch (true) {
|
|
87
|
+
case a.from < b.from:
|
|
88
|
+
return -1;
|
|
89
|
+
case a.from > b.from:
|
|
90
|
+
return 1;
|
|
91
|
+
}
|
|
92
|
+
return 0;
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
this.view.dispatch(setDiagnostics(this.view.state, diagnostics));
|
|
96
|
+
},
|
|
97
|
+
);
|
|
98
|
+
return;
|
|
99
|
+
})
|
|
100
|
+
.catch(console.error);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
update() {
|
|
104
|
+
//
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
destroy() {
|
|
108
|
+
//
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export const lspLint: CMLSPExtension = (options) => {
|
|
113
|
+
return [ViewPlugin.define((view) => new LintPlugin(view, options))];
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
function isDiagnostic(item: any): item is Diagnostic {
|
|
117
|
+
return (
|
|
118
|
+
item !== undefined &&
|
|
119
|
+
item !== null &&
|
|
120
|
+
item.from !== null &&
|
|
121
|
+
item.to !== null &&
|
|
122
|
+
item.from !== undefined &&
|
|
123
|
+
item.to !== undefined
|
|
124
|
+
);
|
|
125
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { hoverTooltip } from '@codemirror/view';
|
|
2
|
+
|
|
3
|
+
import type { CMLSPExtension } from './protocol.js';
|
|
4
|
+
import { offsetToPos, posToOffset, renderMarkupContent } from './util.js';
|
|
5
|
+
|
|
6
|
+
export const lspTooltip: CMLSPExtension = (options) => {
|
|
7
|
+
return hoverTooltip(async (view, pos) => {
|
|
8
|
+
if (!options.lspProvider) {
|
|
9
|
+
return null;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const {
|
|
13
|
+
lspConnection: connection,
|
|
14
|
+
virtualDocument: doc,
|
|
15
|
+
editor,
|
|
16
|
+
} = await options.lspProvider();
|
|
17
|
+
|
|
18
|
+
if (!connection || !connection.isReady || !connection.provides('hoverProvider')) {
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const { line, character } = offsetToPos(view.state.doc, pos);
|
|
23
|
+
|
|
24
|
+
const virtualPos = doc.transformEditorToVirtual(editor, {
|
|
25
|
+
line,
|
|
26
|
+
ch: character,
|
|
27
|
+
isEditor: true,
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
if (!virtualPos) {
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const result = await connection.clientRequests['textDocument/hover'].request({
|
|
35
|
+
position: { line: virtualPos.line, character: virtualPos.ch },
|
|
36
|
+
textDocument: {
|
|
37
|
+
uri: doc.documentInfo.uri,
|
|
38
|
+
},
|
|
39
|
+
});
|
|
40
|
+
if (!result) {
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
const { contents, range } = result;
|
|
44
|
+
let offset = posToOffset(view.state.doc, { line, character })!;
|
|
45
|
+
let end;
|
|
46
|
+
if (range) {
|
|
47
|
+
const editorStart = doc.transformVirtualToEditor({
|
|
48
|
+
line: range.start.line,
|
|
49
|
+
ch: range.start.character,
|
|
50
|
+
isVirtual: true,
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
if (editorStart) {
|
|
54
|
+
offset = posToOffset(view.state.doc, {
|
|
55
|
+
line: editorStart.line,
|
|
56
|
+
character: editorStart.ch,
|
|
57
|
+
})!;
|
|
58
|
+
}
|
|
59
|
+
const editorEnd = doc.transformVirtualToEditor({
|
|
60
|
+
line: range.end.line,
|
|
61
|
+
ch: range.end.character,
|
|
62
|
+
isVirtual: true,
|
|
63
|
+
});
|
|
64
|
+
if (editorEnd) {
|
|
65
|
+
end = posToOffset(view.state.doc, {
|
|
66
|
+
line: editorEnd.line,
|
|
67
|
+
character: editorEnd.ch,
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const dom = renderMarkupContent(contents);
|
|
73
|
+
|
|
74
|
+
return { pos: offset, end, create: () => ({ dom }), above: false };
|
|
75
|
+
});
|
|
76
|
+
};
|
package/src/lsp/util.ts
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import type { Text } from '@codemirror/state';
|
|
2
|
+
import hljs from 'highlight.js';
|
|
3
|
+
import MarkdownIt from 'markdown-it';
|
|
4
|
+
import type * as lsp from 'vscode-languageserver-protocol';
|
|
5
|
+
import 'highlight.js/styles/github.css';
|
|
6
|
+
|
|
7
|
+
export function posToOffset(doc: Text, pos: { line: number; character: number }) {
|
|
8
|
+
if (pos.line >= doc.lines) {
|
|
9
|
+
return;
|
|
10
|
+
}
|
|
11
|
+
const offset = doc.line(pos.line + 1).from + pos.character;
|
|
12
|
+
if (offset > doc.length) {
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
return offset;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function offsetToPos(doc: Text, offset: number) {
|
|
19
|
+
const line = doc.lineAt(offset);
|
|
20
|
+
return {
|
|
21
|
+
line: line.number - 1,
|
|
22
|
+
character: offset - line.from,
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function formatContents(
|
|
27
|
+
contents: lsp.MarkupContent | lsp.MarkedString | lsp.MarkedString[],
|
|
28
|
+
): string {
|
|
29
|
+
if (Array.isArray(contents)) {
|
|
30
|
+
return contents.map((c) => formatContents(c) + '\n\n').join('');
|
|
31
|
+
} else if (typeof contents === 'string') {
|
|
32
|
+
return contents;
|
|
33
|
+
} else {
|
|
34
|
+
return contents.value;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export const renderMarkdownContent = (val: string) => {
|
|
39
|
+
const render = new MarkdownIt({
|
|
40
|
+
html: true,
|
|
41
|
+
linkify: true,
|
|
42
|
+
breaks: true,
|
|
43
|
+
highlight: function (str, lang) {
|
|
44
|
+
if (lang && hljs.getLanguage(lang)) {
|
|
45
|
+
try {
|
|
46
|
+
const hl = hljs.highlight(lang, str).value;
|
|
47
|
+
return hl;
|
|
48
|
+
} catch (__) {
|
|
49
|
+
//
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return ''; // use external default escaping
|
|
54
|
+
},
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
return render.render(val);
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
export const renderMarkupContent = (
|
|
61
|
+
contents: lsp.MarkupContent | lsp.MarkedString | lsp.MarkedString[],
|
|
62
|
+
) => {
|
|
63
|
+
const dom = document.createElement('div');
|
|
64
|
+
dom.classList.add('documentation');
|
|
65
|
+
|
|
66
|
+
const res = renderMarkdownContent(formatContents(contents));
|
|
67
|
+
dom.innerHTML = res;
|
|
68
|
+
return dom;
|
|
69
|
+
};
|
package/src/mode.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { markdown } from '@codemirror/lang-markdown';
|
|
2
|
-
import { LanguageDescription } from '@codemirror/language';
|
|
3
2
|
import type { LanguageSupport } from '@codemirror/language';
|
|
3
|
+
import { LanguageDescription } from '@codemirror/language';
|
|
4
4
|
import { defaultMimeType } from '@difizen/libro-code-editor';
|
|
5
5
|
import { PathExt } from '@difizen/libro-common';
|
|
6
6
|
import { highlightTree } from '@lezer/highlight';
|
package/src/module.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { CodeEditorModule } from '@difizen/libro-code-editor';
|
|
2
|
+
import { ManaModule } from '@difizen/mana-app';
|
|
3
|
+
|
|
4
|
+
import { CodeMirrorEditorContribution } from './editor-contribution.js';
|
|
5
|
+
|
|
6
|
+
export const CodeMirrorEditorModule = ManaModule.create()
|
|
7
|
+
.register(CodeMirrorEditorContribution)
|
|
8
|
+
.dependOn(CodeEditorModule);
|
package/src/theme.ts
CHANGED
|
@@ -14,7 +14,7 @@ export const jupyterEditorTheme = EditorView.theme({
|
|
|
14
14
|
* these things differently.
|
|
15
15
|
*/
|
|
16
16
|
'&': {
|
|
17
|
-
background: 'var(--
|
|
17
|
+
background: 'var(--mana-libro-input-background)',
|
|
18
18
|
color: 'var(--mana-libro-text-default-color)',
|
|
19
19
|
},
|
|
20
20
|
|
|
@@ -88,9 +88,9 @@ export const jupyterEditorTheme = EditorView.theme({
|
|
|
88
88
|
lineHeight: '20px',
|
|
89
89
|
},
|
|
90
90
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
91
|
+
'.cm-editor': {
|
|
92
|
+
background: 'var(--mana-libro-input-background)',
|
|
93
|
+
},
|
|
94
94
|
|
|
95
95
|
'.cm-searchMatch': {
|
|
96
96
|
backgroundColor: 'var(--jp-search-unselected-match-background-color)',
|
package/src/tooltip.ts
CHANGED
|
@@ -43,7 +43,7 @@ const tooltipField = StateField.define<Tooltip | null>({
|
|
|
43
43
|
return null;
|
|
44
44
|
},
|
|
45
45
|
|
|
46
|
-
update(
|
|
46
|
+
update(tooltips, tr) {
|
|
47
47
|
const { effects } = tr;
|
|
48
48
|
for (const effect of effects) {
|
|
49
49
|
if (effect.is(closeTooltipEffect)) {
|
|
@@ -119,9 +119,7 @@ class TooltipPlugin implements PluginValue {
|
|
|
119
119
|
});
|
|
120
120
|
return undefined;
|
|
121
121
|
})
|
|
122
|
-
.catch(
|
|
123
|
-
//
|
|
124
|
-
});
|
|
122
|
+
.catch(console.error);
|
|
125
123
|
}
|
|
126
124
|
}
|
|
127
125
|
});
|
package/src/libro-icon.ts
DELETED
|
@@ -1,4 +0,0 @@
|
|
|
1
|
-
export const FoldIcon =
|
|
2
|
-
'<svg width="8px" height="6px" viewBox="0 0 8 6" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><title>1.通用/2.Icon图标/Line/Down</title><g id="0424" stroke="none" stroke-width="1" fill="none" fillRule="evenodd"><g id="Notebook-cell-色阶" transform="translate(-2015.000000, -434.000000)" fill="#A4AECB"><g id="编组-15" transform="translate(2004.000000, 407.000000)"><g id="1.通用/2.Icon图标/Line/Down" transform="translate(11.250000, 27.500000)"><path d="M7.34387369,0 L6.61145181,0 C6.56164712,0 6.51477212,0.0244140625 6.48547525,0.064453125 L3.71106119,3.88867188 L0.936647123,0.064453125 C0.907350248,0.0244140625 0.860475248,0 0.81067056,0 L0.0782486852,0 C0.0147721227,0 -0.0223372523,0.072265625 0.0147721227,0.124023438 L3.4581315,4.87109375 C3.5831315,5.04296875 3.83899087,5.04296875 3.96301431,4.87109375 L7.40637369,0.124023437 C7.44445962,0.072265625 7.40735025,0 7.34387369,0 Z" id="Down"></path></g></g></g></g></svg>';
|
|
3
|
-
export const UnFoldIcon =
|
|
4
|
-
'<svg width="6px" height="8px" viewBox="0 0 6 8" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><title>1.通用/2.Icon图标/Line/Down收起</title><g id="0424" stroke="none" stroke-width="1" fill="none" fillRule="evenodd"><g id="Notebook-cell-色阶" transform="translate(-2094.000000, -433.000000)" fill="#A4AECB"><g id="编组-15备份" transform="translate(2082.000000, 407.000000)"><g id="1.通用/2.Icon图标/Line/Down" transform="translate(15.000000, 30.039124) rotate(270.000000) translate(-15.000000, -30.039124) translate(11.289124, 27.539124)"><path d="M7.34387369,1.77635684e-15 L6.61145181,1.77635684e-15 C6.56164712,1.77635684e-15 6.51477212,0.0244140625 6.48547525,0.064453125 L3.71106119,3.88867188 L0.936647123,0.064453125 C0.907350248,0.0244140625 0.860475248,1.77635684e-15 0.81067056,1.77635684e-15 L0.0782486852,1.77635684e-15 C0.0147721227,1.77635684e-15 -0.0223372523,0.072265625 0.0147721227,0.124023438 L3.4581315,4.87109375 C3.5831315,5.04296875 3.83899087,5.04296875 3.96301431,4.87109375 L7.40637369,0.124023438 C7.44445962,0.072265625 7.40735025,1.77635684e-15 7.34387369,1.77635684e-15 Z" id="Down"></path></g></g></g></g></svg>';
|