豆豆友情提示:这是一个非官方 GitHub 代理镜像,主要用于网络测试或访问加速。请勿在此进行登录、注册或处理任何敏感信息。进行这些操作请务必访问官方网站 github.com。 Raw 内容也通过此代理提供。
Skip to content

Commit 5ca7637

Browse files
hjanuschkaDevtools-frontend LUCI CQ
authored andcommitted
Preserve edits during console history navigation
When editing a command from history, Up/Down navigation now preserves the edits instead of dropping them. Edits are kept during navigation and cleared when a new command is committed. Bug: 355108929 Change-Id: Iaaf9e4a934161322133e3fa93521e83e9b8eb1bf Reviewed-on: https://chromium-review.googlesource.com/c/devtools/devtools-frontend/+/7363772 Commit-Queue: Helmut Januschka <helmut@januschka.com> Reviewed-by: Samiya Caur <samiyac@chromium.org> Reviewed-by: Nikolay Vitkov <nvitkov@chromium.org>
1 parent 27aec29 commit 5ca7637

File tree

3 files changed

+164
-1
lines changed

3 files changed

+164
-1
lines changed

front_end/ui/components/text_editor/AutocompleteHistory.test.ts

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ describeWithEnvironment('AutocompleteHistory', () => {
6060
assert.strictEqual(history.previous(''), 'entry 1');
6161
assert.isUndefined(history.previous(''));
6262
});
63+
6364
});
6465

6566
describe('next', () => {
@@ -84,6 +85,7 @@ describeWithEnvironment('AutocompleteHistory', () => {
8485
assert.strictEqual(history.next(), 'entry 1');
8586
assert.strictEqual(history.next(), 'entry 2');
8687
});
88+
8789
});
8890

8991
it('stores the "temporary input" on the first "previous" call with a non-empty history', () => {
@@ -123,4 +125,99 @@ describeWithEnvironment('AutocompleteHistory', () => {
123125
assert.deepEqual([...matches], ['x === 19', 'x === 18', 'x === 17']);
124126
});
125127
});
128+
129+
describe('edit preservation during navigation', () => {
130+
it('preserves edits made to history entries when navigating backward', () => {
131+
history.pushHistoryItem('entry 1');
132+
history.pushHistoryItem('entry 2');
133+
history.pushHistoryItem('entry 3');
134+
135+
// Navigate to entry 2
136+
history.previous(''); // now at entry 3
137+
history.previous(''); // now at entry 2
138+
139+
// Edit entry 2 and navigate further back (passing edited text to previous)
140+
history.previous('entry 2 EDITED'); // now at entry 1
141+
142+
// Navigate forward - should see the edit
143+
assert.strictEqual(history.next(), 'entry 2 EDITED');
144+
});
145+
146+
it('preserves multiple edits at different history positions', () => {
147+
history.pushHistoryItem('entry 1');
148+
history.pushHistoryItem('entry 2');
149+
history.pushHistoryItem('entry 3');
150+
151+
// Edit entry 3 by passing edit when navigating away
152+
history.previous('entry 3 EDITED'); // save edit, now at entry 2
153+
154+
// Edit entry 2 by passing edit when navigating away
155+
history.previous('entry 2 EDITED'); // save edit, now at entry 1
156+
157+
// Navigate forward through edits
158+
assert.strictEqual(history.next(), 'entry 2 EDITED');
159+
assert.strictEqual(history.next(), 'entry 3 EDITED');
160+
});
161+
162+
it('clears edits when a new command is committed', () => {
163+
history.pushHistoryItem('entry 1');
164+
history.pushHistoryItem('entry 2');
165+
166+
// Edit entry 2 by passing edit when navigating away
167+
history.previous('entry 2 EDITED');
168+
169+
// Commit a new command
170+
history.pushHistoryItem('entry 3');
171+
172+
// Navigate back - edit should be gone
173+
assert.strictEqual(history.previous(''), 'entry 3');
174+
assert.strictEqual(history.previous(''), 'entry 2'); // Original, not edited
175+
});
176+
177+
it('preserves uncommitted input alongside history edits', () => {
178+
history.pushHistoryItem('entry 1');
179+
history.pushHistoryItem('entry 2');
180+
181+
// Start typing, then navigate back
182+
history.previous('my uncommitted input'); // now at entry 2
183+
184+
// Edit entry 2 and go further back
185+
history.previous('entry 2 EDITED'); // now at entry 1
186+
187+
// Navigate all the way forward
188+
assert.strictEqual(history.next(), 'entry 2 EDITED');
189+
assert.strictEqual(history.next(), 'my uncommitted input');
190+
});
191+
192+
it('restores original if edit matches original', () => {
193+
history.pushHistoryItem('entry 1');
194+
history.pushHistoryItem('entry 2');
195+
196+
// Navigate to entry 2, "edit" it to same value
197+
history.previous('');
198+
history.previous('entry 2'); // edit back to original
199+
200+
// Should still work
201+
assert.strictEqual(history.next(), 'entry 2');
202+
});
203+
204+
it('restores original if text was emptied', () => {
205+
history.pushHistoryItem('entry 1');
206+
history.pushHistoryItem('entry 2');
207+
208+
// Navigate to entry 2, edit it
209+
history.previous('');
210+
history.previous('entry 2 EDITED'); // now at entry 1 with edit saved
211+
212+
// Verify edit is there
213+
assert.strictEqual(history.next(), 'entry 2 EDITED');
214+
215+
// Now empty the text and navigate away
216+
history.previous(''); // empty text, navigate back to entry 1
217+
218+
// Edit should be cleared, original restored
219+
assert.strictEqual(history.next(), 'entry 2');
220+
});
221+
});
222+
126223
});

front_end/ui/components/text_editor/AutocompleteHistory.ts

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,12 @@ export class AutocompleteHistory {
2020
#historyOffset = 1;
2121
#uncommittedIsTop = false;
2222

23+
/**
24+
* Tracks session-local edits made to history entries during navigation.
25+
* Maps history index to edited text. Cleared when a new command is committed.
26+
*/
27+
#editedEntries = new Map<number, string>();
28+
2329
/**
2430
* Creates a new settings-backed history. The class assumes it has sole
2531
* ownership of the setting.
@@ -33,6 +39,7 @@ export class AutocompleteHistory {
3339
this.#data = [];
3440
this.#setting.set([]);
3541
this.#historyOffset = 1;
42+
this.#editedEntries.clear();
3643
}
3744

3845
length(): number {
@@ -49,6 +56,7 @@ export class AutocompleteHistory {
4956
}
5057

5158
this.#historyOffset = 1;
59+
this.#editedEntries.clear();
5260
if (text !== this.#currentHistoryItem()) {
5361
this.#data.push(text);
5462
}
@@ -72,11 +80,33 @@ export class AutocompleteHistory {
7280
}
7381
if (this.#historyOffset === 1) {
7482
this.#pushCurrentText(currentText);
83+
} else {
84+
this.#saveCurrentEdit(currentText);
7585
}
7686
++this.#historyOffset;
7787
return this.#currentHistoryItem();
7888
}
7989

90+
/**
91+
* Saves the current text as an edit if it differs from the current history item
92+
* (which may already have edits from a previous navigation).
93+
* Only saves non-empty edits to avoid issues with navigation-only calls.
94+
*/
95+
#saveCurrentEdit(text: string): void {
96+
const index = this.#data.length - this.#historyOffset;
97+
const currentValue = this.#currentHistoryItem();
98+
if (text === currentValue) {
99+
return;
100+
}
101+
const original = this.#data[index];
102+
if (text !== original && text.length > 0) {
103+
this.#editedEntries.set(index, text);
104+
} else {
105+
// Remove edit if text was restored to original (or emptied)
106+
this.#editedEntries.delete(index);
107+
}
108+
}
109+
80110
next(): string|undefined {
81111
if (this.#historyOffset === 1) {
82112
return undefined;
@@ -98,7 +128,9 @@ export class AutocompleteHistory {
98128
}
99129

100130
#currentHistoryItem(): string|undefined {
101-
return this.#data[this.#data.length - this.#historyOffset];
131+
const index = this.#data.length - this.#historyOffset;
132+
// Return edited version if available, otherwise return original
133+
return this.#editedEntries.get(index) ?? this.#data[index];
102134
}
103135

104136
#store(): void {

front_end/ui/components/text_editor/TextEditorHistory.test.ts

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,40 @@ describeWithEnvironment('TextEditorHistory', () => {
128128

129129
assert.strictEqual(editor.state.selection.main.head, 10);
130130
});
131+
132+
describe('edit preservation', () => {
133+
it('preserves edits when navigating through history', () => {
134+
history.pushHistoryItem('entry 1');
135+
history.pushHistoryItem('entry 2');
136+
history.pushHistoryItem('entry 3');
137+
138+
// Navigate to entry 2 and edit it
139+
editorHistory.moveHistory(Direction.BACKWARD); // entry 3
140+
editorHistory.moveHistory(Direction.BACKWARD); // entry 2
141+
setCodeMirrorContent(editor, 'entry 2 EDITED');
142+
143+
// Navigate back then forward
144+
editorHistory.moveHistory(Direction.BACKWARD); // entry 1
145+
editorHistory.moveHistory(Direction.FORWARD); // should be entry 2 EDITED
146+
147+
assert.strictEqual(editor.state.doc.toString(), 'entry 2 EDITED');
148+
});
149+
150+
it('preserves uncommitted input when navigating through edited history', () => {
151+
history.pushHistoryItem('entry 1');
152+
history.pushHistoryItem('entry 2');
153+
setCodeMirrorContent(editor, 'my new input');
154+
155+
// Navigate back, edit, navigate back more, then all the way forward
156+
editorHistory.moveHistory(Direction.BACKWARD); // entry 2
157+
setCodeMirrorContent(editor, 'entry 2 EDITED');
158+
editorHistory.moveHistory(Direction.BACKWARD); // entry 1
159+
editorHistory.moveHistory(Direction.FORWARD); // entry 2 EDITED
160+
editorHistory.moveHistory(Direction.FORWARD); // my new input
161+
162+
assert.strictEqual(editor.state.doc.toString(), 'my new input');
163+
});
164+
});
131165
});
132166

133167
describe('historyCompletions', () => {

0 commit comments

Comments
 (0)