@adecore/editor-react 0.39.0 → 0.40.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/CallHierarchyPanel.js +30 -55
- package/dist/CodeAuthorsCard.js +0 -1
- package/dist/CompletionPopup.js +150 -181
- package/dist/DiffView.d.ts +1 -1
- package/dist/DiffView.js +2 -29
- package/dist/PickPopup.js +54 -80
- package/dist/SplitDiffView.js +27 -57
- package/dist/SymbolPicker.js +2 -8
- package/dist/code-actions-model.js +2 -9
- package/dist/code-actions.js +10 -11
- package/dist/code-vision.js +2 -1
- package/dist/completion-model.js +2 -2
- package/dist/completion.js +13 -34
- package/dist/definition-link.d.ts +1 -0
- package/dist/definition-link.js +22 -18
- package/dist/diagnostics-model.d.ts +1 -0
- package/dist/diagnostics-model.js +3 -1
- package/dist/diagnostics.js +1 -2
- package/dist/diff-syntax.d.ts +1 -0
- package/dist/diff-syntax.js +31 -0
- package/dist/doc-comment-model.d.ts +1 -0
- package/dist/doc-comment-model.js +1 -1
- package/dist/folding.js +2 -3
- package/dist/format-on-type.js +6 -10
- package/dist/hover-content.d.ts +0 -2
- package/dist/hover-content.js +1 -9
- package/dist/inlay-hints.d.ts +0 -1
- package/dist/inlay-hints.js +11 -41
- package/dist/navigation.js +4 -14
- package/dist/peek.js +2 -12
- package/dist/popup-focus.d.ts +3 -1
- package/dist/popup-focus.js +33 -0
- package/dist/recent-choices.d.ts +0 -1
- package/dist/recent-choices.js +0 -4
- package/dist/rendered-docs.d.ts +1 -0
- package/dist/rendered-docs.js +15 -28
- package/dist/snippet-field-transforms.d.ts +5 -0
- package/dist/snippet-field-transforms.js +27 -16
- package/dist/snippet-session.js +8 -20
- package/dist/symbol-links.js +3 -10
- package/dist/symbol-picker-model.d.ts +1 -0
- package/dist/symbol-picker-model.js +7 -0
- package/dist/unique.d.ts +10 -0
- package/dist/unique.js +15 -0
- package/package.json +9 -9
- package/src/AnchoredPopup.tsx +2 -0
- package/src/BaselineStatus.tsx +1 -0
- package/src/CallHierarchyPanel.tsx +16 -6
- package/src/ChangeReview.tsx +7 -0
- package/src/CodeAuthorsCard.tsx +2 -5
- package/src/CompletionPopup.tsx +21 -15
- package/src/DiffView.tsx +53 -30
- package/src/EditorContextMenu.tsx +7 -0
- package/src/EditorView.tsx +28 -0
- package/src/FindReplace.tsx +8 -0
- package/src/HoverCard.tsx +8 -0
- package/src/HoverSections.tsx +11 -0
- package/src/LanguagePopups.tsx +1 -0
- package/src/PanelResizeHandle.tsx +8 -0
- package/src/PathText.tsx +1 -0
- package/src/PeekPanel.tsx +13 -0
- package/src/PickPopup.tsx +7 -6
- package/src/RenameCard.tsx +13 -0
- package/src/RenderedDocs.tsx +11 -0
- package/src/SplitDiffView.tsx +91 -56
- package/src/SymbolPicker.tsx +10 -18
- package/src/call-hierarchy-model.ts +10 -0
- package/src/call-hierarchy.ts +82 -0
- package/src/code-actions-model.ts +21 -9
- package/src/code-actions.ts +83 -7
- package/src/code-authors.ts +20 -0
- package/src/code-vision-model.ts +37 -0
- package/src/code-vision.ts +47 -3
- package/src/completion-call.ts +19 -0
- package/src/completion-model.ts +58 -2
- package/src/completion.ts +156 -30
- package/src/conflict-model.ts +4 -0
- package/src/context-menu-rows.ts +1 -0
- package/src/context-menu.ts +7 -0
- package/src/definition-link.ts +42 -18
- package/src/diagnostics-model.ts +19 -1
- package/src/diagnostics.ts +31 -2
- package/src/diff-expansion.ts +9 -0
- package/src/diff-overlays.tsx +8 -0
- package/src/diff-syntax.ts +54 -0
- package/src/diff-wordmarks.ts +8 -0
- package/src/doc-comment-model.ts +51 -1
- package/src/editor-language.ts +11 -0
- package/src/find-query.ts +7 -0
- package/src/folding.ts +10 -3
- package/src/format-on-type.ts +23 -10
- package/src/highlight-layers.ts +3 -0
- package/src/highlights.ts +18 -0
- package/src/history.ts +11 -0
- package/src/holder.ts +3 -0
- package/src/hover-content.ts +34 -17
- package/src/hover.ts +57 -0
- package/src/index.ts +4 -0
- package/src/inlay-hints.ts +86 -47
- package/src/key-priority.ts +2 -0
- package/src/line-edits.ts +8 -0
- package/src/models.ts +6 -0
- package/src/navigation-history.ts +5 -0
- package/src/navigation.ts +28 -13
- package/src/panel-size.ts +7 -0
- package/src/patch-decorations.ts +39 -0
- package/src/patch-layout.ts +47 -0
- package/src/peek-model.ts +21 -0
- package/src/peek.ts +49 -13
- package/src/pick.ts +35 -0
- package/src/popup-focus.ts +11 -1
- package/src/popup-placement.ts +5 -0
- package/src/popups.ts +2 -6
- package/src/project-language.ts +33 -0
- package/src/project-problems.ts +11 -0
- package/src/proposal-model.ts +40 -0
- package/src/recent-choices.ts +3 -5
- package/src/refresher.ts +3 -0
- package/src/rename-model.ts +28 -0
- package/src/rename.ts +56 -0
- package/src/rendered-docs.ts +89 -19
- package/src/review-group.ts +4 -0
- package/src/row-host.ts +12 -0
- package/src/selection-ranges.ts +6 -0
- package/src/semantic-delta.ts +7 -0
- package/src/semantic-model.ts +11 -0
- package/src/semantic-tokens.ts +37 -0
- package/src/shortcut-keys.ts +3 -0
- package/src/signature-model.ts +7 -0
- package/src/signature.ts +27 -0
- package/src/snippet-field-transforms.ts +48 -13
- package/src/snippet-indent.ts +10 -0
- package/src/snippet-range-order.ts +5 -0
- package/src/snippet-session.ts +173 -31
- package/src/snippet-transform-client.ts +14 -0
- package/src/snippet-transform-format.ts +98 -0
- package/src/snippet-transform-protocol.ts +5 -0
- package/src/snippet-transform-queue.ts +53 -0
- package/src/snippet-transform-worker.ts +14 -0
- package/src/snippet-variables.ts +9 -0
- package/src/snippet.ts +107 -0
- package/src/split-alignment.ts +10 -0
- package/src/split-scroll.ts +8 -0
- package/src/symbol-links.ts +9 -12
- package/src/symbol-picker-model.ts +25 -0
- package/src/symbol-picker.ts +9 -0
- package/src/symbols.ts +21 -0
- package/src/testing.ts +7 -0
- package/src/timers.ts +5 -0
- package/src/unique.ts +23 -0
- package/src/use-problem-counts.ts +4 -0
- package/src/view-state.ts +9 -0
- package/src/word-diff.ts +5 -0
- package/src/workspace-edit.ts +84 -0
|
@@ -62,6 +62,14 @@ export function letterOfKind(kind: number): string {
|
|
|
62
62
|
|
|
63
63
|
export type SymbolTone = 'callable' | 'value' | 'type' | 'other';
|
|
64
64
|
|
|
65
|
+
/* The badge colors of a tone, shared by the symbol picker and the suggestions list. */
|
|
66
|
+
export const TONE_CLASSES: Record<SymbolTone, string> = {
|
|
67
|
+
callable: 'bg-accent-soft text-accent',
|
|
68
|
+
value: 'bg-status-running/15 text-status-running',
|
|
69
|
+
type: 'bg-status-idle/15 text-status-idle',
|
|
70
|
+
other: 'bg-surface-hover text-text-muted'
|
|
71
|
+
};
|
|
72
|
+
|
|
65
73
|
const TONES: Record<SymbolGroup, SymbolTone> = {
|
|
66
74
|
namespaces: 'other',
|
|
67
75
|
classes: 'type',
|
|
@@ -100,18 +108,22 @@ function isHierarchical(symbols: readonly (DocumentSymbol | SymbolInformation)[]
|
|
|
100
108
|
/* The symbols of a file, parents before their children, in the order of the file. */
|
|
101
109
|
export function entriesOf(result: DocumentSymbolResult): SymbolEntry[] {
|
|
102
110
|
const entries: SymbolEntry[] = [];
|
|
111
|
+
|
|
103
112
|
const add = (name: string, kind: number, container: string, detail: string, at: EditorRange['start']): void => {
|
|
104
113
|
entries.push({ id: String(entries.length), name, kind, container, detail, line: at.line, character: at.character });
|
|
105
114
|
};
|
|
115
|
+
|
|
106
116
|
const walk = (symbols: readonly DocumentSymbol[], container: string): void => {
|
|
107
117
|
for (const symbol of symbols) {
|
|
108
118
|
add(symbol.name, symbol.kind, container, symbol.detail ?? '', symbol.selectionRange.start);
|
|
109
119
|
walk(symbol.children ?? [], container === '' ? symbol.name : `${container}.${symbol.name}`);
|
|
110
120
|
}
|
|
111
121
|
};
|
|
122
|
+
|
|
112
123
|
if (result === null || result.length === 0) {
|
|
113
124
|
return entries;
|
|
114
125
|
}
|
|
126
|
+
|
|
115
127
|
if (isHierarchical(result)) {
|
|
116
128
|
walk(result, '');
|
|
117
129
|
} else {
|
|
@@ -119,6 +131,7 @@ export function entriesOf(result: DocumentSymbolResult): SymbolEntry[] {
|
|
|
119
131
|
add(symbol.name, symbol.kind, symbol.containerName ?? '', '', symbol.location.range.start);
|
|
120
132
|
}
|
|
121
133
|
}
|
|
134
|
+
|
|
122
135
|
return entries;
|
|
123
136
|
}
|
|
124
137
|
|
|
@@ -126,22 +139,29 @@ export function entriesOf(result: DocumentSymbolResult): SymbolEntry[] {
|
|
|
126
139
|
export function scoreOf(name: string, query: string): number {
|
|
127
140
|
const haystack = name.toLowerCase();
|
|
128
141
|
const needle = query.toLowerCase();
|
|
142
|
+
|
|
129
143
|
if (needle === '') {
|
|
130
144
|
return 1;
|
|
131
145
|
}
|
|
146
|
+
|
|
132
147
|
if (haystack.startsWith(needle)) {
|
|
133
148
|
return 3;
|
|
134
149
|
}
|
|
150
|
+
|
|
135
151
|
if (haystack.includes(needle)) {
|
|
136
152
|
return 2;
|
|
137
153
|
}
|
|
154
|
+
|
|
138
155
|
let from = 0;
|
|
156
|
+
|
|
139
157
|
for (const character of needle) {
|
|
140
158
|
from = haystack.indexOf(character, from) + 1;
|
|
159
|
+
|
|
141
160
|
if (from === 0) {
|
|
142
161
|
return 0;
|
|
143
162
|
}
|
|
144
163
|
}
|
|
164
|
+
|
|
145
165
|
return 1;
|
|
146
166
|
}
|
|
147
167
|
|
|
@@ -149,6 +169,7 @@ export function scoreOf(name: string, query: string): number {
|
|
|
149
169
|
export function filterEntries(entries: readonly SymbolEntry[], query: string): SymbolEntry[] {
|
|
150
170
|
const trimmed = query.trim();
|
|
151
171
|
const scored = entries.map((entry, index) => ({ entry, index, score: scoreOf(entry.name, trimmed) })).filter((candidate) => candidate.score > 0);
|
|
172
|
+
|
|
152
173
|
return scored
|
|
153
174
|
.sort((left, right) => (trimmed === '' ? left.index - right.index : right.score - left.score || left.index - right.index))
|
|
154
175
|
.map((candidate) => candidate.entry);
|
|
@@ -163,6 +184,7 @@ export interface EntryGroup {
|
|
|
163
184
|
export function groupEntries(entries: readonly SymbolEntry[]): EntryGroup[] {
|
|
164
185
|
return SYMBOL_GROUPS.flatMap((group) => {
|
|
165
186
|
const own = entries.filter((entry) => groupOfKind(entry.kind) === group);
|
|
187
|
+
|
|
166
188
|
return own.length === 0 ? [] : [{ group, entries: own }];
|
|
167
189
|
});
|
|
168
190
|
}
|
|
@@ -177,10 +199,13 @@ export function modeOf(input: string): PickerMode {
|
|
|
177
199
|
if (input.startsWith('#')) {
|
|
178
200
|
return { mode: 'workspace', text: input.slice(1).trim() };
|
|
179
201
|
}
|
|
202
|
+
|
|
180
203
|
if (input.startsWith(':')) {
|
|
181
204
|
const line = Number.parseInt(input.slice(1), 10);
|
|
205
|
+
|
|
182
206
|
return { mode: 'line', line: Number.isFinite(line) && line > 0 ? line : null };
|
|
183
207
|
}
|
|
208
|
+
|
|
184
209
|
return { mode: 'symbol', text: input };
|
|
185
210
|
}
|
|
186
211
|
|
package/src/symbol-picker.ts
CHANGED
|
@@ -18,13 +18,17 @@ export class SymbolPickerFeature {
|
|
|
18
18
|
|
|
19
19
|
constructor(language: EditorLanguage) {
|
|
20
20
|
this.language = language;
|
|
21
|
+
|
|
21
22
|
const off = language.onKey((event) => {
|
|
22
23
|
if (!isShortcut(this.language.shortcuts.goToSymbol, event)) {
|
|
23
24
|
return false;
|
|
24
25
|
}
|
|
26
|
+
|
|
25
27
|
void this.open();
|
|
28
|
+
|
|
26
29
|
return true;
|
|
27
30
|
}, KEY_PRIORITY.symbolPicker);
|
|
31
|
+
|
|
28
32
|
language.onDispose(() => {
|
|
29
33
|
off();
|
|
30
34
|
this.close();
|
|
@@ -33,10 +37,13 @@ export class SymbolPickerFeature {
|
|
|
33
37
|
|
|
34
38
|
async open(): Promise<void> {
|
|
35
39
|
const { project, uri, popups } = this.language;
|
|
40
|
+
|
|
36
41
|
if (!project.service.supports('textDocument/documentSymbol', uri)) {
|
|
37
42
|
this.language.project.host.notify?.({ id: TOAST_ID, kind: 'error', title: this.language.project.i18n.t('editor:language.symbols.unavailable') });
|
|
43
|
+
|
|
38
44
|
return;
|
|
39
45
|
}
|
|
46
|
+
|
|
40
47
|
try {
|
|
41
48
|
const entries = entriesOf(await project.service.documentSymbols(uri));
|
|
42
49
|
popups.setState({ symbols: { file: basenameOf(this.language.uri), entries } });
|
|
@@ -73,9 +80,11 @@ export class SymbolPickerFeature {
|
|
|
73
80
|
/* The symbols of the project that answer to a name, which is a request to the server and so waits for a pause in the typing. */
|
|
74
81
|
async search(text: string, signal: AbortSignal): Promise<WorkspaceEntry[]> {
|
|
75
82
|
const { project, uri } = this.language;
|
|
83
|
+
|
|
76
84
|
if (text === '' || !project.service.supports('workspace/symbol', uri)) {
|
|
77
85
|
return [];
|
|
78
86
|
}
|
|
87
|
+
|
|
79
88
|
return workspaceEntriesOf(await project.service.workspaceSymbols(uri, text, { signal }));
|
|
80
89
|
}
|
|
81
90
|
|
package/src/symbols.ts
CHANGED
|
@@ -41,13 +41,17 @@ function walk(symbols: readonly DocumentSymbol[], spans: SymbolSpan[]): void {
|
|
|
41
41
|
/* Every symbol of an answer, a parent before its children, whether the server sent a tree or a flat list. */
|
|
42
42
|
export function symbolSpansOf(result: DocumentSymbolResult): SymbolSpan[] {
|
|
43
43
|
const spans: SymbolSpan[] = [];
|
|
44
|
+
|
|
44
45
|
if (result === null || result.length === 0) {
|
|
45
46
|
return spans;
|
|
46
47
|
}
|
|
48
|
+
|
|
47
49
|
if ('range' in result[0]!) {
|
|
48
50
|
walk(result as DocumentSymbol[], spans);
|
|
51
|
+
|
|
49
52
|
return spans;
|
|
50
53
|
}
|
|
54
|
+
|
|
51
55
|
return (result as SymbolInformation[]).map((symbol) => ({ name: symbol.name, kind: symbol.kind, range: symbol.location.range }));
|
|
52
56
|
}
|
|
53
57
|
|
|
@@ -60,29 +64,37 @@ const PRELUDE = /^\s*(?:$|\/\/|\/\*|\*|#\[)/;
|
|
|
60
64
|
*/
|
|
61
65
|
function headerLineOf({ name, range, nameLine }: SymbolSpan, lines: readonly string[] | undefined): number {
|
|
62
66
|
const { start, end } = range;
|
|
67
|
+
|
|
63
68
|
if (nameLine !== undefined && nameLine >= start.line && nameLine <= end.line) {
|
|
64
69
|
return nameLine;
|
|
65
70
|
}
|
|
71
|
+
|
|
66
72
|
if (lines === undefined) {
|
|
67
73
|
return start.line;
|
|
68
74
|
}
|
|
75
|
+
|
|
69
76
|
const last = Math.min(end.line, lines.length - 1);
|
|
70
77
|
let first = start.line;
|
|
78
|
+
|
|
71
79
|
while (first < last && PRELUDE.test(lines[first] ?? '')) {
|
|
72
80
|
first++;
|
|
73
81
|
}
|
|
82
|
+
|
|
74
83
|
const short = name.slice(name.lastIndexOf('\\') + 1);
|
|
84
|
+
|
|
75
85
|
for (let line = first; line <= last; line++) {
|
|
76
86
|
if (!PRELUDE.test(lines[line] ?? '') && (lines[line] ?? '').includes(short)) {
|
|
77
87
|
return line;
|
|
78
88
|
}
|
|
79
89
|
}
|
|
90
|
+
|
|
80
91
|
return first;
|
|
81
92
|
}
|
|
82
93
|
|
|
83
94
|
function blockOf(span: SymbolSpan, lines: readonly string[] | undefined): EditorBlock | null {
|
|
84
95
|
const { name, kind, range } = span;
|
|
85
96
|
const label = KINDS[kind];
|
|
97
|
+
|
|
86
98
|
// A one-line symbol has no body to pin, and a property or an enum member is part of the block around it.
|
|
87
99
|
return label === undefined || range.end.line <= range.start.line
|
|
88
100
|
? null
|
|
@@ -96,6 +108,7 @@ function blockOf(span: SymbolSpan, lines: readonly string[] | undefined): Editor
|
|
|
96
108
|
export function blocksOf(result: DocumentSymbolResult, text?: string): EditorBlock[] {
|
|
97
109
|
const lines = text?.split('\n');
|
|
98
110
|
const blocks = symbolSpansOf(result).flatMap((span) => blockOf(span, lines) ?? []);
|
|
111
|
+
|
|
99
112
|
return blocks.sort((left, right) => left.startLine - right.startLine || right.endLine - left.endLine);
|
|
100
113
|
}
|
|
101
114
|
|
|
@@ -109,14 +122,18 @@ export class SymbolsFeature {
|
|
|
109
122
|
|
|
110
123
|
constructor(language: EditorLanguage, timers: Timers = realTimers) {
|
|
111
124
|
const { editor, project, uri } = language;
|
|
125
|
+
|
|
112
126
|
this.refresher = new Refresher(
|
|
113
127
|
async (signal) => {
|
|
114
128
|
if (!project.service.supports(METHOD, uri)) {
|
|
115
129
|
return;
|
|
116
130
|
}
|
|
131
|
+
|
|
117
132
|
const result = await project.service.documentSymbols(uri, { signal });
|
|
133
|
+
|
|
118
134
|
if (!signal.aborted) {
|
|
119
135
|
editor.setBlocks(result === null ? null : blocksOf(result, editor.getText()));
|
|
136
|
+
|
|
120
137
|
for (const listener of [...this.listeners]) {
|
|
121
138
|
listener(result);
|
|
122
139
|
}
|
|
@@ -125,10 +142,13 @@ export class SymbolsFeature {
|
|
|
125
142
|
PAUSE_MS,
|
|
126
143
|
timers
|
|
127
144
|
);
|
|
145
|
+
|
|
128
146
|
const edits = editor.onTextChange(() => this.refresher.later());
|
|
147
|
+
|
|
129
148
|
const providers = language.onProvidersChanged(() => {
|
|
130
149
|
this.refresher.now();
|
|
131
150
|
});
|
|
151
|
+
|
|
132
152
|
language.onDispose(() => {
|
|
133
153
|
edits();
|
|
134
154
|
providers();
|
|
@@ -140,6 +160,7 @@ export class SymbolsFeature {
|
|
|
140
160
|
/* Called with every answer that was not replaced before it came. */
|
|
141
161
|
onResult(listener: (result: DocumentSymbolResult) => void): () => void {
|
|
142
162
|
this.listeners.add(listener);
|
|
163
|
+
|
|
143
164
|
return () => {
|
|
144
165
|
this.listeners.delete(listener);
|
|
145
166
|
};
|
package/src/testing.ts
CHANGED
|
@@ -72,9 +72,11 @@ export class FakeLanguageService implements LanguageService {
|
|
|
72
72
|
|
|
73
73
|
async changeDocument(uri: string, changes: readonly ContentChange[]): Promise<void> {
|
|
74
74
|
const held = this.documents.get(uri);
|
|
75
|
+
|
|
75
76
|
if (held === undefined) {
|
|
76
77
|
throw new LspError('Document is not open', ErrorCodes.ServerNotInitialized);
|
|
77
78
|
}
|
|
79
|
+
|
|
78
80
|
held.text = applyContentChanges(held.text, changes);
|
|
79
81
|
held.version++;
|
|
80
82
|
this.calls.push({ method: 'change', uri, params: changes, options: {} });
|
|
@@ -95,11 +97,13 @@ export class FakeLanguageService implements LanguageService {
|
|
|
95
97
|
|
|
96
98
|
onProvidersChanged(listener: (uri: string) => void): Disposable {
|
|
97
99
|
this.changes.add(listener);
|
|
100
|
+
|
|
98
101
|
return { dispose: () => this.changes.delete(listener) };
|
|
99
102
|
}
|
|
100
103
|
|
|
101
104
|
onDiagnostics(listener: (report: DiagnosticsReport) => void): Disposable {
|
|
102
105
|
this.diagnostics.add(listener);
|
|
106
|
+
|
|
103
107
|
return { dispose: () => this.diagnostics.delete(listener) };
|
|
104
108
|
}
|
|
105
109
|
|
|
@@ -119,15 +123,18 @@ export class FakeLanguageService implements LanguageService {
|
|
|
119
123
|
if (!this.supports(method, uri)) {
|
|
120
124
|
throw new LspError(`${method} is unavailable`, ErrorCodes.MethodNotFound);
|
|
121
125
|
}
|
|
126
|
+
|
|
122
127
|
options.signal?.throwIfAborted();
|
|
123
128
|
const version = this.documents.get(uri)?.version;
|
|
124
129
|
const call = { method, uri, params, options };
|
|
125
130
|
this.calls.push(call);
|
|
126
131
|
const result = await this.answers.get(method)?.(call);
|
|
127
132
|
options.signal?.throwIfAborted();
|
|
133
|
+
|
|
128
134
|
if (this.documents.get(uri)?.version !== version) {
|
|
129
135
|
throw new StaleResultError(uri);
|
|
130
136
|
}
|
|
137
|
+
|
|
131
138
|
return result as Result;
|
|
132
139
|
}
|
|
133
140
|
|
package/src/timers.ts
CHANGED
|
@@ -18,6 +18,7 @@ export class ManualTimers implements Timers {
|
|
|
18
18
|
set(callback: () => void, delay: number): unknown {
|
|
19
19
|
const id = this.next++;
|
|
20
20
|
this.pending.set(id, { at: this.now + delay, callback });
|
|
21
|
+
|
|
21
22
|
return id;
|
|
22
23
|
}
|
|
23
24
|
|
|
@@ -27,15 +28,19 @@ export class ManualTimers implements Timers {
|
|
|
27
28
|
|
|
28
29
|
advance(milliseconds: number): void {
|
|
29
30
|
const until = this.now + milliseconds;
|
|
31
|
+
|
|
30
32
|
for (;;) {
|
|
31
33
|
const due = [...this.pending].filter(([, entry]) => entry.at <= until).sort((left, right) => left[1].at - right[1].at)[0];
|
|
34
|
+
|
|
32
35
|
if (due === undefined) {
|
|
33
36
|
break;
|
|
34
37
|
}
|
|
38
|
+
|
|
35
39
|
this.now = due[1].at;
|
|
36
40
|
this.pending.delete(due[0]);
|
|
37
41
|
due[1].callback();
|
|
38
42
|
}
|
|
43
|
+
|
|
39
44
|
this.now = until;
|
|
40
45
|
}
|
|
41
46
|
|
package/src/unique.ts
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/* The items in order, without any whose key an earlier one had. */
|
|
2
|
+
export function uniqueBy<T>(items: readonly T[], keyOf: (item: T) => string): T[] {
|
|
3
|
+
const seen = new Set<string>();
|
|
4
|
+
|
|
5
|
+
return items.filter((item) => {
|
|
6
|
+
const key = keyOf(item);
|
|
7
|
+
|
|
8
|
+
if (seen.has(key)) {
|
|
9
|
+
return false;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
seen.add(key);
|
|
13
|
+
|
|
14
|
+
return true;
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function placeKey(location: {
|
|
19
|
+
readonly uri: string;
|
|
20
|
+
readonly range: { readonly start: { readonly line: number; readonly character: number } };
|
|
21
|
+
}): string {
|
|
22
|
+
return `${location.uri}\0${location.range.start.line}\0${location.range.start.character}`;
|
|
23
|
+
}
|
|
@@ -6,12 +6,15 @@ const NONE: ProblemCounts = { error: 0, warning: 0, info: 0 };
|
|
|
6
6
|
|
|
7
7
|
function countsStore(language: EditorLanguage | null): { get(): ProblemCounts; subscribe(listener: () => void): () => void } {
|
|
8
8
|
let current = language?.diagnostics.counts() ?? NONE;
|
|
9
|
+
|
|
9
10
|
return {
|
|
10
11
|
get: () => {
|
|
11
12
|
const next = language?.diagnostics.counts() ?? NONE;
|
|
13
|
+
|
|
12
14
|
if (next.error !== current.error || next.warning !== current.warning || next.info !== current.info) {
|
|
13
15
|
current = next;
|
|
14
16
|
}
|
|
17
|
+
|
|
15
18
|
return current;
|
|
16
19
|
},
|
|
17
20
|
subscribe: (listener) => language?.diagnostics.onChange(listener) ?? (() => undefined)
|
|
@@ -21,5 +24,6 @@ function countsStore(language: EditorLanguage | null): { get(): ProblemCounts; s
|
|
|
21
24
|
/* How many errors, warnings and infos the file has, said again when the servers report. */
|
|
22
25
|
export function useProblemCounts(language: EditorLanguage | null): ProblemCounts {
|
|
23
26
|
const store = useMemo(() => countsStore(language), [language]);
|
|
27
|
+
|
|
24
28
|
return useSyncExternalStore(store.subscribe, store.get);
|
|
25
29
|
}
|
package/src/view-state.ts
CHANGED
|
@@ -32,12 +32,15 @@ export function openingPlace(
|
|
|
32
32
|
foldDefaults: readonly FoldRole[]
|
|
33
33
|
): { line?: number; column?: number; scrollTop?: number; folds?: EditorFolds; foldDefaults?: readonly FoldRole[] } {
|
|
34
34
|
const folds = last === undefined ? { foldDefaults } : { folds: last.folds };
|
|
35
|
+
|
|
35
36
|
if (reveal !== null) {
|
|
36
37
|
return { line: reveal.line, ...folds };
|
|
37
38
|
}
|
|
39
|
+
|
|
38
40
|
if (last !== undefined) {
|
|
39
41
|
return { line: last.line, column: last.column, scrollTop: last.scrollTop, ...folds };
|
|
40
42
|
}
|
|
43
|
+
|
|
41
44
|
return { scrollTop: placeholderScroll, ...folds };
|
|
42
45
|
}
|
|
43
46
|
|
|
@@ -48,6 +51,7 @@ function movedKey(key: string, from: string, to: string): string | null {
|
|
|
48
51
|
if (key === from) {
|
|
49
52
|
return to;
|
|
50
53
|
}
|
|
54
|
+
|
|
51
55
|
return key.startsWith(`${from}/`) ? `${to}${key.slice(from.length)}` : null;
|
|
52
56
|
}
|
|
53
57
|
|
|
@@ -59,13 +63,16 @@ function movedKey(key: string, from: string, to: string): string | null {
|
|
|
59
63
|
export function followViewStates(endpointId: string, from: string, to: string): void {
|
|
60
64
|
const oldKey = endpointKey(endpointId, from);
|
|
61
65
|
const newKey = endpointKey(endpointId, to);
|
|
66
|
+
|
|
62
67
|
for (const [key, state] of [...viewStates]) {
|
|
63
68
|
const target = movedKey(key, oldKey, newKey);
|
|
69
|
+
|
|
64
70
|
if (target !== null) {
|
|
65
71
|
viewStates.delete(key);
|
|
66
72
|
viewStates.set(target, state);
|
|
67
73
|
}
|
|
68
74
|
}
|
|
75
|
+
|
|
69
76
|
moves.push({ from: oldKey, to: newKey });
|
|
70
77
|
}
|
|
71
78
|
|
|
@@ -73,10 +80,12 @@ export function followViewStates(endpointId: string, from: string, to: string):
|
|
|
73
80
|
export function viewStateKey(key: string): string {
|
|
74
81
|
for (const { from, to } of moves) {
|
|
75
82
|
const target = movedKey(key, from, to);
|
|
83
|
+
|
|
76
84
|
if (target !== null) {
|
|
77
85
|
return target;
|
|
78
86
|
}
|
|
79
87
|
}
|
|
88
|
+
|
|
80
89
|
return key;
|
|
81
90
|
}
|
|
82
91
|
|
package/src/word-diff.ts
CHANGED
|
@@ -16,18 +16,23 @@ function replacedRanges(removed: string, added: string): Array<[number, number]>
|
|
|
16
16
|
const after = added.match(TOKEN) ?? [];
|
|
17
17
|
const starts: number[] = [];
|
|
18
18
|
let offset = 0;
|
|
19
|
+
|
|
19
20
|
for (const token of before) {
|
|
20
21
|
starts.push(offset);
|
|
21
22
|
offset += token.length;
|
|
22
23
|
}
|
|
24
|
+
|
|
23
25
|
starts.push(offset);
|
|
24
26
|
const ranges: Array<[number, number]> = [];
|
|
27
|
+
|
|
25
28
|
for (const change of diffLines(before, after)) {
|
|
26
29
|
const text = removed.slice(starts[change.baseStart]!, starts[change.baseEnd]!);
|
|
30
|
+
|
|
27
31
|
if (text.trim() !== '') {
|
|
28
32
|
const start = starts[change.baseStart]! + (text.length - text.trimStart().length);
|
|
29
33
|
ranges.push([start, start + text.trim().length]);
|
|
30
34
|
}
|
|
31
35
|
}
|
|
36
|
+
|
|
32
37
|
return ranges;
|
|
33
38
|
}
|