@difizen/libro-codemirror 0.0.0-snapshot-20241017072317

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.
Files changed (163) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +0 -0
  3. package/es/auto-complete/closebrackets.d.ts +12 -0
  4. package/es/auto-complete/closebrackets.d.ts.map +1 -0
  5. package/es/auto-complete/closebrackets.js +409 -0
  6. package/es/auto-complete/completion.d.ts +57 -0
  7. package/es/auto-complete/completion.d.ts.map +1 -0
  8. package/es/auto-complete/completion.js +267 -0
  9. package/es/auto-complete/config.d.ts +22 -0
  10. package/es/auto-complete/config.d.ts.map +1 -0
  11. package/es/auto-complete/config.js +44 -0
  12. package/es/auto-complete/filter.d.ts +13 -0
  13. package/es/auto-complete/filter.d.ts.map +1 -0
  14. package/es/auto-complete/filter.js +190 -0
  15. package/es/auto-complete/index.d.ts +17 -0
  16. package/es/auto-complete/index.d.ts.map +1 -0
  17. package/es/auto-complete/index.js +107 -0
  18. package/es/auto-complete/snippet.d.ts +14 -0
  19. package/es/auto-complete/snippet.d.ts.map +1 -0
  20. package/es/auto-complete/snippet.js +445 -0
  21. package/es/auto-complete/state.d.ts +63 -0
  22. package/es/auto-complete/state.d.ts.map +1 -0
  23. package/es/auto-complete/state.js +448 -0
  24. package/es/auto-complete/theme.d.ts +6 -0
  25. package/es/auto-complete/theme.d.ts.map +1 -0
  26. package/es/auto-complete/theme.js +150 -0
  27. package/es/auto-complete/tooltip.d.ts +5 -0
  28. package/es/auto-complete/tooltip.d.ts.map +1 -0
  29. package/es/auto-complete/tooltip.js +365 -0
  30. package/es/auto-complete/view.d.ts +38 -0
  31. package/es/auto-complete/view.d.ts.map +1 -0
  32. package/es/auto-complete/view.js +368 -0
  33. package/es/auto-complete/word.d.ts +3 -0
  34. package/es/auto-complete/word.d.ts.map +1 -0
  35. package/es/auto-complete/word.js +119 -0
  36. package/es/completion.d.ts +6 -0
  37. package/es/completion.d.ts.map +1 -0
  38. package/es/completion.js +84 -0
  39. package/es/config.d.ts +189 -0
  40. package/es/config.d.ts.map +1 -0
  41. package/es/config.js +482 -0
  42. package/es/editor-contribution.d.ts +9 -0
  43. package/es/editor-contribution.d.ts.map +1 -0
  44. package/es/editor-contribution.js +35 -0
  45. package/es/editor.d.ts +397 -0
  46. package/es/editor.d.ts.map +1 -0
  47. package/es/editor.js +1338 -0
  48. package/es/factory.d.ts +4 -0
  49. package/es/factory.d.ts.map +1 -0
  50. package/es/factory.js +24 -0
  51. package/es/hyperlink.d.ts +15 -0
  52. package/es/hyperlink.d.ts.map +1 -0
  53. package/es/hyperlink.js +120 -0
  54. package/es/indent.d.ts +8 -0
  55. package/es/indent.d.ts.map +1 -0
  56. package/es/indent.js +58 -0
  57. package/es/indentation-markers/config.d.ts +17 -0
  58. package/es/indentation-markers/config.d.ts.map +1 -0
  59. package/es/indentation-markers/config.js +10 -0
  60. package/es/indentation-markers/index.d.ts +3 -0
  61. package/es/indentation-markers/index.d.ts.map +1 -0
  62. package/es/indentation-markers/index.js +160 -0
  63. package/es/indentation-markers/map.d.ts +77 -0
  64. package/es/indentation-markers/map.d.ts.map +1 -0
  65. package/es/indentation-markers/map.js +265 -0
  66. package/es/indentation-markers/utils.d.ts +27 -0
  67. package/es/indentation-markers/utils.d.ts.map +1 -0
  68. package/es/indentation-markers/utils.js +91 -0
  69. package/es/index.d.ts +13 -0
  70. package/es/index.d.ts.map +1 -0
  71. package/es/index.js +12 -0
  72. package/es/libro-icon.d.ts +3 -0
  73. package/es/libro-icon.d.ts.map +1 -0
  74. package/es/libro-icon.js +2 -0
  75. package/es/lsp/completion.d.ts +5 -0
  76. package/es/lsp/completion.d.ts.map +1 -0
  77. package/es/lsp/completion.js +245 -0
  78. package/es/lsp/format.d.ts +7 -0
  79. package/es/lsp/format.d.ts.map +1 -0
  80. package/es/lsp/format.js +193 -0
  81. package/es/lsp/index.d.ts +7 -0
  82. package/es/lsp/index.d.ts.map +1 -0
  83. package/es/lsp/index.js +6 -0
  84. package/es/lsp/lint.d.ts +3 -0
  85. package/es/lsp/lint.d.ts.map +1 -0
  86. package/es/lsp/lint.js +113 -0
  87. package/es/lsp/protocol.d.ts +7 -0
  88. package/es/lsp/protocol.d.ts.map +1 -0
  89. package/es/lsp/protocol.js +1 -0
  90. package/es/lsp/tooltip.d.ts +3 -0
  91. package/es/lsp/tooltip.d.ts.map +1 -0
  92. package/es/lsp/tooltip.js +113 -0
  93. package/es/lsp/util.d.ts +15 -0
  94. package/es/lsp/util.d.ts.map +1 -0
  95. package/es/lsp/util.js +57 -0
  96. package/es/mimetype.d.ts +22 -0
  97. package/es/mimetype.d.ts.map +1 -0
  98. package/es/mimetype.js +59 -0
  99. package/es/mode.d.ts +86 -0
  100. package/es/mode.d.ts.map +1 -0
  101. package/es/mode.js +280 -0
  102. package/es/module.d.ts +3 -0
  103. package/es/module.d.ts.map +1 -0
  104. package/es/module.js +4 -0
  105. package/es/monitor.d.ts +32 -0
  106. package/es/monitor.d.ts.map +1 -0
  107. package/es/monitor.js +129 -0
  108. package/es/python-lang.d.ts +3 -0
  109. package/es/python-lang.d.ts.map +1 -0
  110. package/es/python-lang.js +7 -0
  111. package/es/style/base.css +129 -0
  112. package/es/style/theme.css +12 -0
  113. package/es/style/variables.css +405 -0
  114. package/es/theme.d.ts +35 -0
  115. package/es/theme.d.ts.map +1 -0
  116. package/es/theme.js +223 -0
  117. package/es/tooltip.d.ts +10 -0
  118. package/es/tooltip.d.ts.map +1 -0
  119. package/es/tooltip.js +168 -0
  120. package/package.json +74 -0
  121. package/src/auto-complete/README.md +71 -0
  122. package/src/auto-complete/closebrackets.ts +423 -0
  123. package/src/auto-complete/completion.ts +345 -0
  124. package/src/auto-complete/config.ts +101 -0
  125. package/src/auto-complete/filter.ts +214 -0
  126. package/src/auto-complete/index.ts +112 -0
  127. package/src/auto-complete/snippet.ts +392 -0
  128. package/src/auto-complete/state.ts +465 -0
  129. package/src/auto-complete/theme.ts +127 -0
  130. package/src/auto-complete/tooltip.ts +386 -0
  131. package/src/auto-complete/view.ts +339 -0
  132. package/src/auto-complete/word.ts +118 -0
  133. package/src/completion.ts +61 -0
  134. package/src/config.ts +701 -0
  135. package/src/editor-contribution.ts +22 -0
  136. package/src/editor.ts +1287 -0
  137. package/src/factory.ts +31 -0
  138. package/src/hyperlink.ts +95 -0
  139. package/src/indent.ts +69 -0
  140. package/src/indentation-markers/config.ts +31 -0
  141. package/src/indentation-markers/index.ts +192 -0
  142. package/src/indentation-markers/map.ts +273 -0
  143. package/src/indentation-markers/utils.ts +84 -0
  144. package/src/index.spec.ts +12 -0
  145. package/src/index.ts +14 -0
  146. package/src/libro-icon.tsx +4 -0
  147. package/src/lsp/completion.ts +175 -0
  148. package/src/lsp/format.ts +144 -0
  149. package/src/lsp/index.ts +6 -0
  150. package/src/lsp/lint.ts +125 -0
  151. package/src/lsp/protocol.ts +8 -0
  152. package/src/lsp/tooltip.ts +76 -0
  153. package/src/lsp/util.ts +69 -0
  154. package/src/mimetype.ts +49 -0
  155. package/src/mode.ts +265 -0
  156. package/src/module.ts +8 -0
  157. package/src/monitor.ts +105 -0
  158. package/src/python-lang.ts +7 -0
  159. package/src/style/base.css +129 -0
  160. package/src/style/theme.css +12 -0
  161. package/src/style/variables.css +405 -0
  162. package/src/theme.ts +231 -0
  163. package/src/tooltip.ts +143 -0
@@ -0,0 +1,84 @@
1
+ import type { EditorState, Line } from '@codemirror/state';
2
+ import type { EditorView } from '@codemirror/view';
3
+
4
+ /**
5
+ * Gets the visible lines in the editor. Lines will not be repeated.
6
+ *
7
+ * @param view - The editor view to get the visible lines from.
8
+ * @param state - The editor state. Defaults to the view's current one.
9
+ */
10
+ export function getVisibleLines(view: EditorView, state = view.state) {
11
+ const lines = new Set<Line>();
12
+
13
+ for (const { from, to } of view.visibleRanges) {
14
+ let pos = from;
15
+
16
+ while (pos <= to) {
17
+ const line = state.doc.lineAt(pos);
18
+
19
+ if (!lines.has(line)) {
20
+ lines.add(line);
21
+ }
22
+
23
+ pos = line.to + 1;
24
+ }
25
+ }
26
+
27
+ return lines;
28
+ }
29
+
30
+ /**
31
+ * Gets the line at the position of the primary cursor.
32
+ *
33
+ * @param state - The editor state from which to extract the line.
34
+ */
35
+ export function getCurrentLine(state: EditorState) {
36
+ const currentPos = state.selection.main.head;
37
+ return state.doc.lineAt(currentPos);
38
+ }
39
+
40
+ /**
41
+ * Returns the number of columns that a string is indented, controlling for
42
+ * tabs. This is useful for determining the indentation level of a line.
43
+ *
44
+ * Note that this only returns the number of _visible_ columns, not the number
45
+ * of whitespace characters at the start of the string.
46
+ *
47
+ * @param str - The string to check.
48
+ * @param tabSize - The size of a tab character. Usually 2 or 4.
49
+ */
50
+ export function numColumns(str: string, tabSize: number) {
51
+ // as far as I can tell, this is pretty much the fastest way to do this,
52
+ // at least involving iteration. `str.length - str.trimStart().length` is
53
+ // much faster, but it has some edge cases that are hard to deal with.
54
+
55
+ let col = 0;
56
+
57
+ // eslint-disable-next-line no-restricted-syntax
58
+ loop: for (let i = 0; i < str.length; i++) {
59
+ switch (str[i]) {
60
+ case ' ': {
61
+ col += 1;
62
+ continue loop;
63
+ }
64
+
65
+ case '\t': {
66
+ // if the current column is a multiple of the tab size, we can just
67
+ // add the tab size to the column. otherwise, we need to add the
68
+ // difference between the tab size and the current column.
69
+ col += tabSize - (col % tabSize);
70
+ continue loop;
71
+ }
72
+
73
+ case '\r': {
74
+ continue loop;
75
+ }
76
+
77
+ default: {
78
+ break loop;
79
+ }
80
+ }
81
+ }
82
+
83
+ return col;
84
+ }
@@ -0,0 +1,12 @@
1
+ import 'reflect-metadata';
2
+ import assert from 'assert';
3
+
4
+ // import { CodeMirrorEditor, codeMirrorEditorFactory } from './index.js';
5
+
6
+ describe('libro-codemirror', () => {
7
+ it('#import', () => {
8
+ assert(true);
9
+ // assert(CodeMirrorEditor);
10
+ // assert(codeMirrorEditorFactory);
11
+ });
12
+ });
package/src/index.ts ADDED
@@ -0,0 +1,14 @@
1
+ import './style/base.css';
2
+ import './style/theme.css';
3
+ import './style/variables.css';
4
+
5
+ export * from './config.js';
6
+ export * from './editor.js';
7
+ export * from './lsp/index.js';
8
+ export * from './mode.js';
9
+ export * from './module.js';
10
+ export * from './factory.js';
11
+ export * from './monitor.js';
12
+ export * from './theme.js';
13
+
14
+ export * from './auto-complete/index.js';
@@ -0,0 +1,4 @@
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" fill-rule="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" fill-rule="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>';
@@ -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
+ };
@@ -0,0 +1,6 @@
1
+ export * from './lint.js';
2
+ export * from './protocol.js';
3
+ export * from './tooltip.js';
4
+ export * from './util.js';
5
+ export * from './completion.js';
6
+ export * from './format.js';
@@ -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,8 @@
1
+ import type { Extension } from '@codemirror/state';
2
+ import type { LSPProvider } from '@difizen/libro-lsp';
3
+
4
+ export interface LSPExtensionOptions {
5
+ lspProvider?: LSPProvider;
6
+ }
7
+
8
+ export type CMLSPExtension = (option: LSPExtensionOptions) => Extension;
@@ -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
+ };