@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
package/src/navigation.ts
CHANGED
|
@@ -7,6 +7,7 @@ import { rangeHolds } from './diagnostics-model.ts';
|
|
|
7
7
|
import { placesOfName } from './symbol-links.ts';
|
|
8
8
|
import { isShortcut } from './shortcut-keys.ts';
|
|
9
9
|
import { messageOf } from './error-message.ts';
|
|
10
|
+
import { placeKey, uniqueBy } from './unique.ts';
|
|
10
11
|
|
|
11
12
|
export type NavigationKind = 'definition' | 'declaration' | 'typeDefinition' | 'implementation';
|
|
12
13
|
|
|
@@ -34,23 +35,16 @@ const TOAST_ID = 'language-navigation';
|
|
|
34
35
|
|
|
35
36
|
/* The places of a result, each once: the two servers of a Vue file may both know the same one. `target` is the whole declaration when the server gave it. */
|
|
36
37
|
export function uniquePlaces(result: NavigationResult): { location: Location; target: Range | null }[] {
|
|
37
|
-
const seen = new Set<string>();
|
|
38
38
|
const list = result === null ? [] : Array.isArray(result) ? (result as (Location | LocationLink)[]) : [result];
|
|
39
|
-
|
|
40
|
-
|
|
39
|
+
|
|
40
|
+
return uniqueBy(
|
|
41
|
+
list.map((entry) =>
|
|
41
42
|
'targetUri' in entry
|
|
42
43
|
? { location: { uri: entry.targetUri, range: entry.targetSelectionRange }, target: entry.targetRange as Range | null }
|
|
43
44
|
: { location: entry, target: null }
|
|
44
|
-
)
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
const key = `${location.uri}\0${start.line}\0${start.character}`;
|
|
48
|
-
if (seen.has(key)) {
|
|
49
|
-
return false;
|
|
50
|
-
}
|
|
51
|
-
seen.add(key);
|
|
52
|
-
return true;
|
|
53
|
-
});
|
|
45
|
+
),
|
|
46
|
+
({ location }) => placeKey(location)
|
|
47
|
+
);
|
|
54
48
|
}
|
|
55
49
|
|
|
56
50
|
/* The places of a result, each once. */
|
|
@@ -69,23 +63,30 @@ export class NavigationFeature {
|
|
|
69
63
|
constructor(language: EditorLanguage) {
|
|
70
64
|
this.language = language;
|
|
71
65
|
const { editor } = language;
|
|
66
|
+
|
|
72
67
|
const offs = [
|
|
73
68
|
editor.onClick((click) => {
|
|
74
69
|
if (!click.mod || click.alt || click.shift || !this.supports('definition')) {
|
|
75
70
|
return false;
|
|
76
71
|
}
|
|
72
|
+
|
|
77
73
|
void this.go('definition', click.position);
|
|
74
|
+
|
|
78
75
|
return true;
|
|
79
76
|
}),
|
|
80
77
|
language.onKey((event) => {
|
|
81
78
|
const found = SHORTCUT_KINDS.find(([, name]) => isShortcut(this.language.shortcuts[name], event));
|
|
79
|
+
|
|
82
80
|
if (found === undefined) {
|
|
83
81
|
return false;
|
|
84
82
|
}
|
|
83
|
+
|
|
85
84
|
void this.go(found[0]);
|
|
85
|
+
|
|
86
86
|
return true;
|
|
87
87
|
}, KEY_PRIORITY.navigation)
|
|
88
88
|
];
|
|
89
|
+
|
|
89
90
|
language.onDispose(() => {
|
|
90
91
|
for (const off of offs) {
|
|
91
92
|
off();
|
|
@@ -99,20 +100,27 @@ export class NavigationFeature {
|
|
|
99
100
|
|
|
100
101
|
async go(kind: NavigationKind, position: EditorPosition = this.language.editor.getCaret()): Promise<void> {
|
|
101
102
|
const { project, uri, editor } = this.language;
|
|
103
|
+
|
|
102
104
|
if (!this.supports(kind)) {
|
|
103
105
|
this.tell(this.say('unavailable'));
|
|
106
|
+
|
|
104
107
|
return;
|
|
105
108
|
}
|
|
109
|
+
|
|
106
110
|
let result: NavigationResult;
|
|
111
|
+
|
|
107
112
|
try {
|
|
108
113
|
result = await REQUESTS[kind](project.service, uri, position);
|
|
109
114
|
} catch (error) {
|
|
110
115
|
if (!(error instanceof StaleResultError)) {
|
|
111
116
|
this.tell(this.say('failed', { message: messageOf(error) }));
|
|
112
117
|
}
|
|
118
|
+
|
|
113
119
|
return;
|
|
114
120
|
}
|
|
121
|
+
|
|
115
122
|
const places = uniqueLocations(result);
|
|
123
|
+
|
|
116
124
|
if (places.length === 0) {
|
|
117
125
|
this.tell(this.say('none'));
|
|
118
126
|
} else if (kind === 'definition' && places.length === 1 && places[0]!.uri === uri && rangeHolds(places[0]!.range, position)) {
|
|
@@ -123,6 +131,7 @@ export class NavigationFeature {
|
|
|
123
131
|
} else {
|
|
124
132
|
this.language.locations(places, position, this.say(`titles.${kind}`));
|
|
125
133
|
}
|
|
134
|
+
|
|
126
135
|
editor.focus();
|
|
127
136
|
}
|
|
128
137
|
|
|
@@ -133,15 +142,20 @@ export class NavigationFeature {
|
|
|
133
142
|
*/
|
|
134
143
|
async goToName(name: string, anchor: EditorPosition, own: Location | null): Promise<void> {
|
|
135
144
|
const { project, uri, editor } = this.language;
|
|
145
|
+
|
|
136
146
|
if (own !== null) {
|
|
137
147
|
this.language.goTo(own);
|
|
148
|
+
|
|
138
149
|
return;
|
|
139
150
|
}
|
|
151
|
+
|
|
140
152
|
if (!project.service.supports('workspace/symbol', uri)) {
|
|
141
153
|
return;
|
|
142
154
|
}
|
|
155
|
+
|
|
143
156
|
try {
|
|
144
157
|
const places = placesOfName(await project.service.workspaceSymbols(uri, name), name, editor.getText());
|
|
158
|
+
|
|
145
159
|
if (places.length === 1) {
|
|
146
160
|
this.language.goTo(places[0]!);
|
|
147
161
|
} else if (places.length > 1) {
|
|
@@ -166,5 +180,6 @@ export function locationRow(location: Location, stored: string | null): { label:
|
|
|
166
180
|
const path = stored ?? fileUriToPath(location.uri) ?? location.uri;
|
|
167
181
|
const name = basenameOf(path);
|
|
168
182
|
const folder = path.slice(0, Math.max(0, path.length - name.length)).replace(/\/$/, '');
|
|
183
|
+
|
|
169
184
|
return { label: `${name}:${location.range.start.line + 1}`, detail: folder, path: true };
|
|
170
185
|
}
|
package/src/panel-size.ts
CHANGED
|
@@ -1,12 +1,17 @@
|
|
|
1
1
|
/* The height of the list-and-preview area of a peek or a call hierarchy, which is what a person drags; the bands and the title bar around it stay. */
|
|
2
2
|
export const PANEL_BODY_HEIGHT = 180;
|
|
3
|
+
|
|
3
4
|
/* Four rows of 22px, so a list never shrinks to nothing. */
|
|
4
5
|
export const PANEL_BODY_MIN = 88;
|
|
6
|
+
|
|
5
7
|
/* What the bands and the title bar take of the panel, which the share of the editor has to leave room for. */
|
|
6
8
|
const PANEL_CHROME = 63;
|
|
9
|
+
|
|
7
10
|
/* The share of the editor's height a panel may take, so the line it belongs to stays in view. */
|
|
8
11
|
const PANEL_MAX_SHARE = 0.8;
|
|
12
|
+
|
|
9
13
|
const PANEL_FALLBACK_AVAILABLE = 800;
|
|
14
|
+
|
|
10
15
|
export const PANEL_KEY_STEP = 22;
|
|
11
16
|
|
|
12
17
|
/* The tallest body for an editor that is `available` pixels high; never below the minimum, so a tiny editor still gets a usable panel. */
|
|
@@ -18,11 +23,13 @@ export function clampPanelBody(height: number, available: number = PANEL_FALLBAC
|
|
|
18
23
|
if (!Number.isFinite(height)) {
|
|
19
24
|
return PANEL_BODY_HEIGHT;
|
|
20
25
|
}
|
|
26
|
+
|
|
21
27
|
return Math.min(panelBodyMax(available), Math.max(PANEL_BODY_MIN, Math.round(height)));
|
|
22
28
|
}
|
|
23
29
|
|
|
24
30
|
/* The height of the editor's scrolling area around a panel's row; the fallback stands in where nothing is laid out. */
|
|
25
31
|
export function availableHeightOf(container: HTMLElement | null): number {
|
|
26
32
|
const height = container?.closest<HTMLElement>('.se-viewport')?.clientHeight ?? 0;
|
|
33
|
+
|
|
27
34
|
return height > 0 ? height : PANEL_FALLBACK_AVAILABLE;
|
|
28
35
|
}
|
package/src/patch-decorations.ts
CHANGED
|
@@ -49,10 +49,13 @@ function numberOf(line: PatchLayoutLine, numbers: 'single' | 'both', width: numb
|
|
|
49
49
|
if (line.old === null && line.new === null) {
|
|
50
50
|
return null;
|
|
51
51
|
}
|
|
52
|
+
|
|
52
53
|
if (numbers === 'single') {
|
|
53
54
|
return String(line.new ?? line.old);
|
|
54
55
|
}
|
|
56
|
+
|
|
55
57
|
const pad = (value: number | null): string => (value === null ? '' : String(value)).padStart(width, ' ');
|
|
58
|
+
|
|
56
59
|
return `${pad(line.old)} ${pad(line.new)}`;
|
|
57
60
|
}
|
|
58
61
|
|
|
@@ -61,28 +64,35 @@ function highlightsOf(layout: PatchLayout): { highlights: EditorLineHighlight[];
|
|
|
61
64
|
const highlights: EditorLineHighlight[] = [];
|
|
62
65
|
const marks: EditorChangeMark[] = [];
|
|
63
66
|
const lines = layout.lines;
|
|
67
|
+
|
|
64
68
|
for (let index = 0; index < lines.length; index++) {
|
|
65
69
|
const role = lines[index]!.role;
|
|
70
|
+
|
|
66
71
|
if (role === 'file-header') {
|
|
67
72
|
highlights.push({ startLine: index + 1, endLine: index + 1, color: '--text-muted', fill: '--surface-raised' });
|
|
68
73
|
} else if (role === 'hunk-header') {
|
|
69
74
|
highlights.push({ startLine: index + 1, endLine: index + 1, color: '--accent' });
|
|
70
75
|
} else if (role === 'added' || role === 'removed') {
|
|
71
76
|
let end = index;
|
|
77
|
+
|
|
72
78
|
while (end + 1 < lines.length && lines[end + 1]!.role === role) {
|
|
73
79
|
end++;
|
|
74
80
|
}
|
|
81
|
+
|
|
75
82
|
const removed = role === 'removed';
|
|
83
|
+
|
|
76
84
|
highlights.push({
|
|
77
85
|
startLine: index + 1,
|
|
78
86
|
endLine: end + 1,
|
|
79
87
|
color: removed ? '--editor-deleted' : '--editor-added',
|
|
80
88
|
bar: removed ? 'hatched' : 'solid'
|
|
81
89
|
});
|
|
90
|
+
|
|
82
91
|
marks.push({ startLine: index + 1, endLine: end + 1, kind: removed ? 'deleted' : 'added' });
|
|
83
92
|
index = end;
|
|
84
93
|
}
|
|
85
94
|
}
|
|
95
|
+
|
|
86
96
|
return { highlights, marks };
|
|
87
97
|
}
|
|
88
98
|
|
|
@@ -101,86 +111,111 @@ export function decoratePatch(editor: Editor, options: PatchDecorationOptions =
|
|
|
101
111
|
const timer = options.timer ?? DEFAULT_TIMER;
|
|
102
112
|
const delay = options.delay ?? 150;
|
|
103
113
|
const immediateBelow = options.immediateBelow ?? 100_000;
|
|
114
|
+
|
|
104
115
|
const clearWait = (): void => {
|
|
105
116
|
if (wait !== null) {
|
|
106
117
|
timer.clear(wait);
|
|
107
118
|
wait = null;
|
|
108
119
|
}
|
|
109
120
|
};
|
|
121
|
+
|
|
110
122
|
const after = (callback: () => void): void => {
|
|
111
123
|
clearWait();
|
|
124
|
+
|
|
112
125
|
wait = timer.set(() => {
|
|
113
126
|
wait = null;
|
|
114
127
|
callback();
|
|
115
128
|
}, delay);
|
|
116
129
|
};
|
|
130
|
+
|
|
117
131
|
const apply = (): void => {
|
|
118
132
|
const numbers = options.numbers ?? 'single';
|
|
119
133
|
const lines = layout.lines;
|
|
120
134
|
const width = lines.reduce((digits, line) => Math.max(digits, String(Math.max(line.old ?? 0, line.new ?? 0)).length), 1);
|
|
135
|
+
|
|
121
136
|
editor.setLineNumbers((line) => {
|
|
122
137
|
const entry = lines[line];
|
|
138
|
+
|
|
123
139
|
return entry === undefined ? null : numberOf(entry, numbers, width);
|
|
124
140
|
});
|
|
141
|
+
|
|
125
142
|
const labels = options.labels;
|
|
143
|
+
|
|
126
144
|
editor.setLineLabels(
|
|
127
145
|
labels === undefined
|
|
128
146
|
? null
|
|
129
147
|
: (line) => {
|
|
130
148
|
const entry = lines[line];
|
|
149
|
+
|
|
131
150
|
if (entry?.role === 'removed') {
|
|
132
151
|
return labels.removed(entry.old!);
|
|
133
152
|
}
|
|
153
|
+
|
|
134
154
|
if (entry?.role === 'added') {
|
|
135
155
|
return labels.added(entry.new!);
|
|
136
156
|
}
|
|
157
|
+
|
|
137
158
|
return entry?.role === 'context' ? labels.unchanged(entry.old!, entry.new!) : null;
|
|
138
159
|
}
|
|
139
160
|
);
|
|
161
|
+
|
|
140
162
|
const { highlights, marks } = highlightsOf(layout);
|
|
141
163
|
editor.setLineHighlights(highlights);
|
|
142
164
|
editor.setChangeMarks(marks, CHANGE_LAYER);
|
|
143
165
|
};
|
|
166
|
+
|
|
144
167
|
const decorate = (): void => {
|
|
145
168
|
pending = false;
|
|
169
|
+
|
|
146
170
|
if (disposed) {
|
|
147
171
|
return;
|
|
148
172
|
}
|
|
173
|
+
|
|
149
174
|
const text = editor.getText();
|
|
150
175
|
length = text.length;
|
|
151
176
|
layout = patchLayoutOf(text, options.limits);
|
|
152
177
|
apply();
|
|
153
178
|
};
|
|
179
|
+
|
|
154
180
|
const announce = (): void => {
|
|
155
181
|
if (!disposed) {
|
|
156
182
|
options.onLayout?.(layout);
|
|
157
183
|
}
|
|
158
184
|
};
|
|
185
|
+
|
|
159
186
|
const read = (): void => {
|
|
160
187
|
clearWait();
|
|
161
188
|
decorate();
|
|
162
189
|
announce();
|
|
163
190
|
};
|
|
191
|
+
|
|
164
192
|
const schedule = (): void => {
|
|
165
193
|
if (length < immediateBelow) {
|
|
166
194
|
if (pending) {
|
|
167
195
|
return;
|
|
168
196
|
}
|
|
197
|
+
|
|
169
198
|
pending = true;
|
|
199
|
+
|
|
170
200
|
queueMicrotask(() => {
|
|
171
201
|
decorate();
|
|
172
202
|
after(announce);
|
|
173
203
|
});
|
|
204
|
+
|
|
174
205
|
return;
|
|
175
206
|
}
|
|
207
|
+
|
|
176
208
|
pending = true;
|
|
209
|
+
|
|
177
210
|
after(() => {
|
|
178
211
|
decorate();
|
|
179
212
|
announce();
|
|
180
213
|
});
|
|
181
214
|
};
|
|
215
|
+
|
|
182
216
|
const stop = editor.onTextChange(schedule);
|
|
183
217
|
read();
|
|
218
|
+
|
|
184
219
|
return {
|
|
185
220
|
get layout() {
|
|
186
221
|
return layout;
|
|
@@ -204,10 +239,12 @@ export function usePatchDecorations(editor: Editor | null, options: Omit<PatchDe
|
|
|
204
239
|
const [state, setState] = useState<{ editor: Editor; layout: PatchLayout } | null>(null);
|
|
205
240
|
const { numbers } = options;
|
|
206
241
|
const { maxLines, maxCharacters } = options.limits ?? {};
|
|
242
|
+
|
|
207
243
|
useEffect(() => {
|
|
208
244
|
if (editor === null) {
|
|
209
245
|
return;
|
|
210
246
|
}
|
|
247
|
+
|
|
211
248
|
const decoration = decoratePatch(editor, {
|
|
212
249
|
...(numbers === undefined ? {} : { numbers }),
|
|
213
250
|
limits: { ...(maxLines === undefined ? {} : { maxLines }), ...(maxCharacters === undefined ? {} : { maxCharacters }) },
|
|
@@ -218,7 +255,9 @@ export function usePatchDecorations(editor: Editor | null, options: Omit<PatchDe
|
|
|
218
255
|
},
|
|
219
256
|
onLayout: (layout) => setState({ editor, layout })
|
|
220
257
|
});
|
|
258
|
+
|
|
221
259
|
return () => decoration.dispose();
|
|
222
260
|
}, [editor, numbers, maxLines, maxCharacters, t]);
|
|
261
|
+
|
|
223
262
|
return state !== null && state.editor === editor ? state.layout : null;
|
|
224
263
|
}
|
package/src/patch-layout.ts
CHANGED
|
@@ -101,18 +101,23 @@ function unquote(raw: string): string {
|
|
|
101
101
|
if (raw.length < 2 || !raw.startsWith('"') || !raw.endsWith('"')) {
|
|
102
102
|
return raw;
|
|
103
103
|
}
|
|
104
|
+
|
|
104
105
|
const bytes: number[] = [];
|
|
105
106
|
const encoder = new TextEncoder();
|
|
106
107
|
const body = raw.slice(1, -1);
|
|
108
|
+
|
|
107
109
|
for (let i = 0; i < body.length; i++) {
|
|
108
110
|
if (body[i] !== '\\') {
|
|
109
111
|
const point = String.fromCodePoint(body.codePointAt(i)!);
|
|
110
112
|
bytes.push(...encoder.encode(point));
|
|
111
113
|
i += point.length - 1;
|
|
114
|
+
|
|
112
115
|
continue;
|
|
113
116
|
}
|
|
117
|
+
|
|
114
118
|
const octal = /^[0-3][0-7]{2}/.exec(body.slice(i + 1, i + 4));
|
|
115
119
|
const escaped = ESCAPES[body[i + 1] ?? ''];
|
|
120
|
+
|
|
116
121
|
if (octal !== null) {
|
|
117
122
|
bytes.push(Number.parseInt(octal[0], 8));
|
|
118
123
|
i += 3;
|
|
@@ -123,6 +128,7 @@ function unquote(raw: string): string {
|
|
|
123
128
|
bytes.push(92);
|
|
124
129
|
}
|
|
125
130
|
}
|
|
131
|
+
|
|
126
132
|
return new TextDecoder().decode(new Uint8Array(bytes));
|
|
127
133
|
}
|
|
128
134
|
|
|
@@ -130,6 +136,7 @@ function unquote(raw: string): string {
|
|
|
130
136
|
function pathOf(raw: string): string | null {
|
|
131
137
|
const tab = raw.indexOf('\t');
|
|
132
138
|
const name = unquote(tab === -1 ? raw : raw.slice(0, tab));
|
|
139
|
+
|
|
133
140
|
return name === '/dev/null' ? null : name;
|
|
134
141
|
}
|
|
135
142
|
|
|
@@ -141,23 +148,30 @@ function stripPrefix(path: string | null, prefix: 'a/' | 'b/'): string | null {
|
|
|
141
148
|
function gitPaths(rest: string): { oldPath: string | null; newPath: string | null; prefixed: boolean } {
|
|
142
149
|
if (rest.startsWith('"')) {
|
|
143
150
|
const end = rest.indexOf('" ', 1);
|
|
151
|
+
|
|
144
152
|
if (end !== -1) {
|
|
145
153
|
const oldName = unquote(rest.slice(0, end + 1));
|
|
146
154
|
const newName = unquote(rest.slice(end + 2));
|
|
147
155
|
const prefixed = oldName.startsWith('a/') && newName.startsWith('b/');
|
|
156
|
+
|
|
148
157
|
return prefixed ? { oldPath: oldName.slice(2), newPath: newName.slice(2), prefixed } : { oldPath: oldName, newPath: newName, prefixed };
|
|
149
158
|
}
|
|
150
159
|
}
|
|
160
|
+
|
|
151
161
|
const middle = rest.indexOf(' b/');
|
|
162
|
+
|
|
152
163
|
if (rest.startsWith('a/') && middle !== -1) {
|
|
153
164
|
for (let at = middle; at !== -1; at = rest.indexOf(' b/', at + 1)) {
|
|
154
165
|
if (rest.slice(2, at) === rest.slice(at + 3)) {
|
|
155
166
|
return { oldPath: rest.slice(2, at), newPath: rest.slice(at + 3), prefixed: true };
|
|
156
167
|
}
|
|
157
168
|
}
|
|
169
|
+
|
|
158
170
|
return { oldPath: rest.slice(2, middle), newPath: unquote(rest.slice(middle + 3)), prefixed: true };
|
|
159
171
|
}
|
|
172
|
+
|
|
160
173
|
const split = rest.indexOf(' ');
|
|
174
|
+
|
|
161
175
|
return split === -1
|
|
162
176
|
? { oldPath: unquote(rest), newPath: null, prefixed: false }
|
|
163
177
|
: { oldPath: unquote(rest.slice(0, split)), newPath: unquote(rest.slice(split + 1)), prefixed: false };
|
|
@@ -187,30 +201,39 @@ function statusOf(section: Section): PatchFileStatus {
|
|
|
187
201
|
if (section.binary) {
|
|
188
202
|
return 'binary';
|
|
189
203
|
}
|
|
204
|
+
|
|
190
205
|
if (section.created || (!section.git && section.oldPath === null && section.newPath !== null)) {
|
|
191
206
|
return 'added';
|
|
192
207
|
}
|
|
208
|
+
|
|
193
209
|
if (section.deleted || (!section.git && section.newPath === null && section.oldPath !== null)) {
|
|
194
210
|
return 'deleted';
|
|
195
211
|
}
|
|
212
|
+
|
|
196
213
|
if (section.renamed) {
|
|
197
214
|
return 'renamed';
|
|
198
215
|
}
|
|
216
|
+
|
|
199
217
|
return section.copied ? 'copied' : 'modified';
|
|
200
218
|
}
|
|
201
219
|
|
|
202
220
|
function linesOf(text: string, limits: Required<PatchLimits>): { lines: string[]; truncated: boolean } {
|
|
203
221
|
const lines: string[] = [];
|
|
204
222
|
let start = 0;
|
|
223
|
+
|
|
205
224
|
while (lines.length < limits.maxLines && start <= limits.maxCharacters) {
|
|
206
225
|
const end = text.indexOf('\n', start);
|
|
226
|
+
|
|
207
227
|
if (end === -1) {
|
|
208
228
|
lines.push(text.slice(start).replace(/\r$/, ''));
|
|
229
|
+
|
|
209
230
|
return { lines, truncated: false };
|
|
210
231
|
}
|
|
232
|
+
|
|
211
233
|
lines.push(text.slice(start, end).replace(/\r$/, ''));
|
|
212
234
|
start = end + 1;
|
|
213
235
|
}
|
|
236
|
+
|
|
214
237
|
// Stopping right after a final newline leaves nothing unread.
|
|
215
238
|
return { lines, truncated: start < text.length };
|
|
216
239
|
}
|
|
@@ -228,33 +251,42 @@ export function patchLayoutOf(text: string, limits: PatchLimits = {}): PatchLayo
|
|
|
228
251
|
let section: Section | undefined;
|
|
229
252
|
let hunk: OpenHunk | undefined;
|
|
230
253
|
let binaryData = false;
|
|
254
|
+
|
|
231
255
|
const seal = (): void => {
|
|
232
256
|
if (hunk !== undefined && (hunk.oldLeft > 0 || hunk.newLeft > 0)) {
|
|
233
257
|
hunk.layout.consistent = false;
|
|
234
258
|
}
|
|
259
|
+
|
|
235
260
|
hunk = undefined;
|
|
236
261
|
};
|
|
262
|
+
|
|
237
263
|
const open = (line: number, git: boolean): Section => {
|
|
238
264
|
seal();
|
|
239
265
|
binaryData = false;
|
|
240
266
|
section = emptySection(line, git);
|
|
241
267
|
sections.push(section);
|
|
268
|
+
|
|
242
269
|
return section;
|
|
243
270
|
};
|
|
271
|
+
|
|
244
272
|
// A removed line can read `-- x`, so `---` starts a file only with `+++` under it.
|
|
245
273
|
const startsFile = (index: number): boolean => source[index]!.startsWith('--- ') && (source[index + 1]?.startsWith('+++ ') ?? false);
|
|
274
|
+
|
|
246
275
|
const push = (line: PatchLayoutLine, index: number): void => {
|
|
247
276
|
lines.push(line);
|
|
277
|
+
|
|
248
278
|
if (section !== undefined) {
|
|
249
279
|
section.lastLine = index;
|
|
250
280
|
}
|
|
251
281
|
};
|
|
282
|
+
|
|
252
283
|
for (let index = 0; index < source.length; index++) {
|
|
253
284
|
const line = source[index]!;
|
|
254
285
|
const remaining = hunk !== undefined && (hunk.oldLeft > 0 || hunk.newLeft > 0);
|
|
255
286
|
const gitStart = line.startsWith('diff --git ');
|
|
256
287
|
const header = line.startsWith('@@') ? parseHunkHeader(line) : undefined;
|
|
257
288
|
const fileStart = startsFile(index) && (!remaining || source[index + 2]?.startsWith('@@') === true);
|
|
289
|
+
|
|
258
290
|
if (remaining && !gitStart && header === undefined && !fileStart) {
|
|
259
291
|
if (isContent(line) || line === '' || line.startsWith('\\')) {
|
|
260
292
|
push(contentLine(line, hunk!, section!), index);
|
|
@@ -262,14 +294,18 @@ export function patchLayoutOf(text: string, limits: PatchLimits = {}): PatchLayo
|
|
|
262
294
|
hunk!.layout.consistent = false;
|
|
263
295
|
push(OTHER, index);
|
|
264
296
|
}
|
|
297
|
+
|
|
265
298
|
continue;
|
|
266
299
|
}
|
|
300
|
+
|
|
267
301
|
if (gitStart) {
|
|
268
302
|
const created = open(index, true);
|
|
269
303
|
({ oldPath: created.oldPath, newPath: created.newPath, prefixed: created.prefixed } = gitPaths(line.slice('diff --git '.length)));
|
|
270
304
|
push(FILE_HEADER, index);
|
|
305
|
+
|
|
271
306
|
continue;
|
|
272
307
|
}
|
|
308
|
+
|
|
273
309
|
if (fileStart) {
|
|
274
310
|
const reuse = section !== undefined && section.git && !section.named && section.hunks.length === 0;
|
|
275
311
|
const target = reuse ? section! : open(index, false);
|
|
@@ -278,8 +314,10 @@ export function patchLayoutOf(text: string, limits: PatchLimits = {}): PatchLayo
|
|
|
278
314
|
push(FILE_HEADER, index);
|
|
279
315
|
push(FILE_HEADER, index + 1);
|
|
280
316
|
index++;
|
|
317
|
+
|
|
281
318
|
continue;
|
|
282
319
|
}
|
|
320
|
+
|
|
283
321
|
if (header !== undefined) {
|
|
284
322
|
seal();
|
|
285
323
|
const target = section ?? open(index, false);
|
|
@@ -287,8 +325,10 @@ export function patchLayoutOf(text: string, limits: PatchLimits = {}): PatchLayo
|
|
|
287
325
|
target.hunks.push(layout);
|
|
288
326
|
hunk = { layout, oldLeft: header.oldCount, newLeft: header.newCount, oldNumber: header.oldStart, newNumber: header.newStart };
|
|
289
327
|
push(HUNK_HEADER, index);
|
|
328
|
+
|
|
290
329
|
continue;
|
|
291
330
|
}
|
|
331
|
+
|
|
292
332
|
if (section === undefined) {
|
|
293
333
|
push(OTHER, index);
|
|
294
334
|
} else if (hunk !== undefined && line.startsWith('\\')) {
|
|
@@ -312,7 +352,9 @@ export function patchLayoutOf(text: string, limits: PatchLimits = {}): PatchLayo
|
|
|
312
352
|
push(OTHER, index);
|
|
313
353
|
}
|
|
314
354
|
}
|
|
355
|
+
|
|
315
356
|
seal();
|
|
357
|
+
|
|
316
358
|
return {
|
|
317
359
|
lines,
|
|
318
360
|
truncated,
|
|
@@ -338,21 +380,26 @@ function contentLine(line: string, hunk: OpenHunk, section: Section): PatchLayou
|
|
|
338
380
|
if (line.startsWith('\\')) {
|
|
339
381
|
return NO_NEWLINE;
|
|
340
382
|
}
|
|
383
|
+
|
|
341
384
|
const kind = line.startsWith('+') ? 'added' : line.startsWith('-') ? 'removed' : 'context';
|
|
342
385
|
const takesOld = kind !== 'added';
|
|
343
386
|
const takesNew = kind !== 'removed';
|
|
387
|
+
|
|
344
388
|
if ((takesOld && hunk.oldLeft === 0) || (takesNew && hunk.newLeft === 0)) {
|
|
345
389
|
hunk.layout.consistent = false;
|
|
346
390
|
}
|
|
391
|
+
|
|
347
392
|
if (kind === 'added') {
|
|
348
393
|
section.added++;
|
|
349
394
|
} else if (kind === 'removed') {
|
|
350
395
|
section.removed++;
|
|
351
396
|
}
|
|
397
|
+
|
|
352
398
|
const oldNumber = takesOld ? hunk.oldNumber++ : null;
|
|
353
399
|
const newNumber = takesNew ? hunk.newNumber++ : null;
|
|
354
400
|
hunk.oldLeft = takesOld ? Math.max(0, hunk.oldLeft - 1) : hunk.oldLeft;
|
|
355
401
|
hunk.newLeft = takesNew ? Math.max(0, hunk.newLeft - 1) : hunk.newLeft;
|
|
402
|
+
|
|
356
403
|
return { role: kind, old: oldNumber, new: newNumber };
|
|
357
404
|
}
|
|
358
405
|
|
package/src/peek-model.ts
CHANGED
|
@@ -30,7 +30,9 @@ function columnsOf(range: Range, line: number, length: number): NameRange | null
|
|
|
30
30
|
if (range.start.line !== line) {
|
|
31
31
|
return null;
|
|
32
32
|
}
|
|
33
|
+
|
|
33
34
|
const end = Math.min(length, range.end.line === line ? range.end.character : length);
|
|
35
|
+
|
|
34
36
|
return range.start.character < end ? { start: range.start.character, end } : null;
|
|
35
37
|
}
|
|
36
38
|
|
|
@@ -39,9 +41,11 @@ function trimmedNameOf(raw: string, range: Range, line: number): NameRange | nul
|
|
|
39
41
|
const leading = raw.length - raw.trimStart().length;
|
|
40
42
|
const columns = columnsOf(range, line, raw.length);
|
|
41
43
|
const length = raw.trim().length;
|
|
44
|
+
|
|
42
45
|
if (columns === null || columns.start < leading || columns.start >= leading + length) {
|
|
43
46
|
return null;
|
|
44
47
|
}
|
|
48
|
+
|
|
45
49
|
return { start: columns.start - leading, end: Math.min(length, columns.end - leading) };
|
|
46
50
|
}
|
|
47
51
|
|
|
@@ -52,22 +56,28 @@ function trimmedNameOf(raw: string, range: Range, line: number): NameRange | nul
|
|
|
52
56
|
export function peekFilesOf(locations: readonly Location[], current: string, textOf: (uri: string) => string | null): PeekFile[] {
|
|
53
57
|
const order: string[] = [];
|
|
54
58
|
const byFile = new Map<string, Location[]>();
|
|
59
|
+
|
|
55
60
|
for (const location of locations) {
|
|
56
61
|
if (!byFile.has(location.uri)) {
|
|
57
62
|
order.push(location.uri);
|
|
58
63
|
byFile.set(location.uri, []);
|
|
59
64
|
}
|
|
65
|
+
|
|
60
66
|
byFile.get(location.uri)!.push(location);
|
|
61
67
|
}
|
|
68
|
+
|
|
62
69
|
const uris = [...order.filter((uri) => uri === current), ...order.filter((uri) => uri !== current)];
|
|
70
|
+
|
|
63
71
|
return uris.map((uri, fileIndex) => {
|
|
64
72
|
const text = textOf(uri);
|
|
65
73
|
const lines = text === null ? null : text.split(/\r\n|\r|\n/);
|
|
74
|
+
|
|
66
75
|
const places = [...byFile.get(uri)!]
|
|
67
76
|
.sort((left, right) => left.range.start.line - right.range.start.line || left.range.start.character - right.range.start.character)
|
|
68
77
|
.map((location, index) => {
|
|
69
78
|
const line = location.range.start.line;
|
|
70
79
|
const raw = lines?.[line] ?? '';
|
|
80
|
+
|
|
71
81
|
return {
|
|
72
82
|
id: `${fileIndex}:${index}`,
|
|
73
83
|
location,
|
|
@@ -76,6 +86,7 @@ export function peekFilesOf(locations: readonly Location[], current: string, tex
|
|
|
76
86
|
name: lines === null ? null : trimmedNameOf(raw, location.range, line)
|
|
77
87
|
};
|
|
78
88
|
});
|
|
89
|
+
|
|
79
90
|
return { uri, places };
|
|
80
91
|
});
|
|
81
92
|
}
|
|
@@ -97,6 +108,7 @@ export function snippetOf(text: string, line: number, range: Range): PeekSnippet
|
|
|
97
108
|
const lines = text.split(/\r\n|\r|\n/);
|
|
98
109
|
const startLine = Math.max(0, line - BEFORE);
|
|
99
110
|
const end = Math.min(lines.length, line + AFTER + 1);
|
|
111
|
+
|
|
100
112
|
return { startLine, text: lines.slice(startLine, end).join('\n'), active: line - startLine, name: columnsOf(range, line, (lines[line] ?? '').length) };
|
|
101
113
|
}
|
|
102
114
|
|
|
@@ -110,6 +122,7 @@ export function definitionSnippetOf(text: string, line: number, declaration: Ran
|
|
|
110
122
|
const lines = text.split(/\r\n|\r|\n/);
|
|
111
123
|
const startLine = declaration === null ? Math.max(0, line - 2) : Math.min(declaration.start.line, line);
|
|
112
124
|
const end = Math.min(lines.length - 1, declaration === null ? line + DEFINITION_LINES / 2 : Math.min(declaration.end.line, startLine + DEFINITION_LINES));
|
|
125
|
+
|
|
113
126
|
return { startLine, text: lines.slice(startLine, end + 1).join('\n'), active: line - startLine, name: columnsOf(range, line, (lines[line] ?? '').length) };
|
|
114
127
|
}
|
|
115
128
|
|
|
@@ -120,19 +133,25 @@ export function definitionSnippetOf(text: string, line: number, declaration: Ran
|
|
|
120
133
|
export function distinguishingFolders(paths: readonly string[]): string[] {
|
|
121
134
|
const folders = paths.map((path) => path.split('/').slice(0, -1));
|
|
122
135
|
const names = paths.map((path) => path.slice(path.lastIndexOf('/') + 1));
|
|
136
|
+
|
|
123
137
|
return paths.map((path, index) => {
|
|
124
138
|
const same = paths.map((_, other) => other).filter((other) => names[other] === names[index] && paths[other] !== path);
|
|
139
|
+
|
|
125
140
|
if (same.length === 0) {
|
|
126
141
|
return '';
|
|
127
142
|
}
|
|
143
|
+
|
|
128
144
|
const rivals = [...new Set(same.map((other) => paths[other]!))].map((other) => folders[paths.indexOf(other)]!);
|
|
129
145
|
const mine = folders[index]!;
|
|
146
|
+
|
|
130
147
|
for (let depth = 1; depth <= mine.length; depth++) {
|
|
131
148
|
const tail = mine.slice(-depth).join('/');
|
|
149
|
+
|
|
132
150
|
if (rivals.every((rival) => rival.slice(-depth).join('/') !== tail)) {
|
|
133
151
|
return tail;
|
|
134
152
|
}
|
|
135
153
|
}
|
|
154
|
+
|
|
136
155
|
return mine.join('/');
|
|
137
156
|
});
|
|
138
157
|
}
|
|
@@ -140,8 +159,10 @@ export function distinguishingFolders(paths: readonly string[]): string[] {
|
|
|
140
159
|
/* The column a character stands at once tabs are expanded to their stops, which is where a monospace face draws it. */
|
|
141
160
|
export function visualColumnOf(line: string, character: number, tabSize: number): number {
|
|
142
161
|
let column = 0;
|
|
162
|
+
|
|
143
163
|
for (let i = 0; i < Math.min(character, line.length); i++) {
|
|
144
164
|
column += line[i] === '\t' ? tabSize - (column % tabSize) : 1;
|
|
145
165
|
}
|
|
166
|
+
|
|
146
167
|
return column;
|
|
147
168
|
}
|