Skip to content

Commit aacad51

Browse files
committed
Fix logic for merging content changes that came from a tab completion
This makes repeating an insertion that contains a tab completion work again (this broke when we moved to using transformations). As a bonus, it's all done in a single transformation. Yay! The core logic here is essentially identical to that in HistoryTracker, and it should probably be abstracted out and unit tested. This deserves integration testing as well, but that'll require a bit of nuance, and it's getting late. Fixes #6610
1 parent 5bf568e commit aacad51

File tree

1 file changed

+34
-10
lines changed

1 file changed

+34
-10
lines changed

src/actions/commands/actions.ts

Lines changed: 34 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,14 @@ import { WriteQuitCommand } from '../../cmd_line/commands/writequit';
3737
import { shouldWrapKey } from '../wrapping';
3838
import { ErrorCode, VimError } from '../../error';
3939

40+
/**
41+
* A very special snowflake.
42+
*
43+
* Each keystroke when typing in Insert mode is its own Action, which means naively replaying a
44+
* realistic insertion (via `.` or a macro) does many small insertions, which is very slow.
45+
* So instead, we fold all those actions after the fact into a single DocumentContentChangeAction,
46+
* which compresses the changes, generally into a single document edit per cursor.
47+
*/
4048
export class DocumentContentChangeAction extends BaseCommand {
4149
modes: [];
4250

@@ -128,19 +136,35 @@ export class DocumentContentChangeAction extends BaseCommand {
128136
second: vscode.TextDocumentContentChangeEvent
129137
): vscode.TextDocumentContentChangeEvent | undefined {
130138
if (
131-
first.rangeOffset + first.text.length !== second.rangeOffset ||
132-
second.rangeLength !== 0
139+
first.rangeOffset + first.text.length === second.rangeOffset ||
140+
second.rangeLength === 0
141+
) {
142+
// Simple concatenation
143+
return {
144+
text: first.text + second.text,
145+
range: first.range,
146+
rangeOffset: first.rangeOffset,
147+
rangeLength: first.rangeLength,
148+
};
149+
} else if (
150+
first.rangeOffset <= second.rangeOffset &&
151+
first.text.length >= second.rangeLength
133152
) {
134-
// TODO: We should be able to do better, but I'm not sure if this is actually relevant.
153+
const start = second.rangeOffset - first.rangeOffset;
154+
const end = start + second.rangeLength;
155+
const text = first.text.slice(0, start) + second.text + first.text.slice(end);
156+
// `second` replaces part of `first`
157+
// Most often, this is the result of confirming an auto-completion
158+
return {
159+
text,
160+
range: first.range,
161+
rangeOffset: first.rangeOffset,
162+
rangeLength: first.rangeLength,
163+
};
164+
} else {
165+
// TODO: Do any of the cases falling into this `else` matter?
135166
return undefined;
136167
}
137-
138-
return {
139-
text: first.text + second.text,
140-
range: first.range,
141-
rangeOffset: first.rangeOffset,
142-
rangeLength: first.rangeLength,
143-
};
144168
}
145169

146170
const compressed: vscode.TextDocumentContentChangeEvent[] = [];

0 commit comments

Comments
 (0)