Skip to content

Commit

Permalink
Make VisualLine and VisualBlock modes work with multiple cursors
Browse files Browse the repository at this point in the history
Fixes #4270
  • Loading branch information
J-Fields committed Feb 25, 2020
1 parent d67eacb commit e2c61f9
Show file tree
Hide file tree
Showing 5 changed files with 58 additions and 26 deletions.
20 changes: 10 additions & 10 deletions src/actions/commands/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -865,7 +865,7 @@ class CommandOverrideCopy extends BaseCommand {
})
.join('\n');
} else if (vimState.currentMode === Mode.VisualBlock) {
for (const { line } of Position.IterateLine(vimState)) {
for (const { line } of Position.IterateLinesInCursorBlock(vimState)) {
text += line + '\n';
}
} else if (vimState.currentMode === Mode.Insert || vimState.currentMode === Mode.Normal) {
Expand Down Expand Up @@ -3486,7 +3486,7 @@ class ActionReplaceCharacterVisualBlock extends BaseCommand {
toInsert = TextEditor.getTabCharacter(vimState.editor);
}

for (const { start, end } of Position.IterateLine(vimState)) {
for (const { start, end } of Position.IterateLinesInCursorBlock(vimState)) {
if (end.isBeforeOrEqual(start)) {
continue;
}
Expand Down Expand Up @@ -3524,7 +3524,7 @@ class ActionDeleteVisualBlock extends BaseCommand {
public async exec(position: Position, vimState: VimState): Promise<VimState> {
const lines: string[] = [];

for (const { line, start, end } of Position.IterateLine(vimState)) {
for (const { line, start, end } of Position.IterateLinesInCursorBlock(vimState)) {
lines.push(line);
vimState.recordedState.transformations.push({
type: 'deleteRange',
Expand Down Expand Up @@ -3560,7 +3560,7 @@ class ActionShiftDVisualBlock extends BaseCommand {
mightChangeDocument = true;

public async exec(position: Position, vimState: VimState): Promise<VimState> {
for (const { start } of Position.IterateLine(vimState)) {
for (const { start } of Position.IterateLinesInCursorBlock(vimState)) {
vimState.recordedState.transformations.push({
type: 'deleteRange',
range: new Range(start, start.getLineEnd()),
Expand Down Expand Up @@ -3593,7 +3593,7 @@ class ActionGoToInsertVisualBlockMode extends BaseCommand {
vimState.isMultiCursor = true;
vimState.isFakeMultiCursor = true;

for (const { line, start } of Position.IterateLine(vimState)) {
for (const { line, start } of Position.IterateLinesInCursorBlock(vimState)) {
if (line === '' && start.character !== 0) {
continue;
}
Expand All @@ -3614,7 +3614,7 @@ class ActionChangeInVisualBlockMode extends BaseCommand {
}

public async exec(position: Position, vimState: VimState): Promise<VimState> {
for (const { start, end } of Position.IterateLine(vimState)) {
for (const { start, end } of Position.IterateLinesInCursorBlock(vimState)) {
vimState.recordedState.transformations.push({
type: 'deleteRange',
range: new Range(start, end),
Expand All @@ -3626,7 +3626,7 @@ class ActionChangeInVisualBlockMode extends BaseCommand {
vimState.isMultiCursor = true;
vimState.isFakeMultiCursor = true;

for (const { start } of Position.IterateLine(vimState)) {
for (const { start } of Position.IterateLinesInCursorBlock(vimState)) {
vimState.cursors.push(new Range(start, start));
}
vimState.cursors = vimState.cursors.slice(1);
Expand All @@ -3645,7 +3645,7 @@ class ActionChangeToEOLInVisualBlockMode extends BaseCommand {
}

public async exec(position: Position, vimState: VimState): Promise<VimState> {
for (const { start } of Position.IterateLine(vimState)) {
for (const { start } of Position.IterateLinesInCursorBlock(vimState)) {
vimState.recordedState.transformations.push({
type: 'deleteRange',
range: new Range(start, start.getLineEnd()),
Expand All @@ -3657,7 +3657,7 @@ class ActionChangeToEOLInVisualBlockMode extends BaseCommand {
vimState.isMultiCursor = true;
vimState.isFakeMultiCursor = true;

for (const { end } of Position.IterateLine(vimState)) {
for (const { end } of Position.IterateLinesInCursorBlock(vimState)) {
vimState.cursors.push(new Range(end, end));
}
vimState.cursors = vimState.cursors.slice(1);
Expand Down Expand Up @@ -3776,7 +3776,7 @@ class ActionGoToInsertVisualBlockModeAppend extends BaseCommand {
vimState.isMultiCursor = true;
vimState.isFakeMultiCursor = true;

for (const { line, end } of Position.IterateLine(vimState)) {
for (const { line, end } of Position.IterateLinesInCursorBlock(vimState)) {
if (line.trim() === '') {
vimState.recordedState.transformations.push({
type: 'replaceText',
Expand Down
8 changes: 4 additions & 4 deletions src/actions/operator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -425,7 +425,7 @@ class UpperCaseVisualBlockOperator extends BaseOperator {
public modes = [Mode.VisualBlock];

public async run(vimState: VimState, startPos: Position, endPos: Position): Promise<VimState> {
for (const { start, end } of Position.IterateLine(vimState)) {
for (const { start, end } of Position.IterateLinesInCursorBlock(vimState)) {
const range = new vscode.Range(start, end);
let text = vimState.editor.document.getText(range);
await TextEditor.replace(range, text.toUpperCase());
Expand Down Expand Up @@ -470,7 +470,7 @@ class LowerCaseVisualBlockOperator extends BaseOperator {
public modes = [Mode.VisualBlock];

public async run(vimState: VimState, startPos: Position, endPos: Position): Promise<VimState> {
for (const { start, end } of Position.IterateLine(vimState)) {
for (const { start, end } of Position.IterateLinesInCursorBlock(vimState)) {
const range = new vscode.Range(start, end);
let text = vimState.editor.document.getText(range);
await TextEditor.replace(range, text.toLowerCase());
Expand Down Expand Up @@ -687,7 +687,7 @@ export class YankVisualBlockMode extends BaseOperator {

const isMultiline = startPos.line !== endPos.line;

for (const { line, start, end } of Position.IterateLine(vimState)) {
for (const { line, start, end } of Position.IterateLinesInCursorBlock(vimState)) {
ranges.push(new vscode.Range(start, end));
if (isMultiline) {
toCopy += line + '\n';
Expand Down Expand Up @@ -755,7 +755,7 @@ class ToggleCaseVisualBlockOperator extends BaseOperator {
public modes = [Mode.VisualBlock];

public async run(vimState: VimState, startPos: Position, endPos: Position): Promise<VimState> {
for (const { start, end } of Position.IterateLine(vimState)) {
for (const { start, end } of Position.IterateLinesInCursorBlock(vimState)) {
const range = new vscode.Range(start, end);
await ToggleCaseOperator.toggleCase(range);
}
Expand Down
29 changes: 21 additions & 8 deletions src/common/motion/position.ts
Original file line number Diff line number Diff line change
Expand Up @@ -211,25 +211,38 @@ export class Position extends vscode.Position {
}

/**
* Iterate over every line in the block defined by the two positions passed in.
* Iterate over every line in the block defined by the primary cursor.
*
* This is intended for visual block mode.
*/
public static *IterateLine(
public static *IterateLinesInCursorBlock(
vimState: VimState,
options: { reverse?: boolean } = { reverse: false }
): Iterable<{ line: string; start: Position; end: Position }> {
return this.IterateLinesInBlock(
vimState.cursorStartPosition,
vimState.cursorStopPosition,
vimState.desiredColumn === Number.POSITIVE_INFINITY,
options
);
}

/**
* Iterate over every line in the block defined by the two positions passed in.
*
* This is intended for visual block mode.
*/
public static *IterateLinesInBlock(
start: Position,
stop: Position,
runToLineEnd: boolean = false,
options: { reverse?: boolean } = { reverse: false }
): Iterable<{ line: string; start: Position; end: Position }> {
const { reverse } = options;
const start = vimState.cursorStartPosition;
const stop = vimState.cursorStopPosition;

const topLeft = visualBlockGetTopLeftPosition(start, stop);
const bottomRight = visualBlockGetBottomRightPosition(start, stop);

// Special case for $, which potentially makes the block ragged
// on the right side.
const runToLineEnd = vimState.desiredColumn === Number.POSITIVE_INFINITY;

const itrStart = reverse ? bottomRight.line : topLeft.line;
const itrEnd = reverse ? topLeft.line : bottomRight.line;

Expand Down
20 changes: 19 additions & 1 deletion src/mode/modeHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1264,7 +1264,7 @@ export class ModeHandler implements vscode.Disposable {
} else if (selectionMode === Mode.VisualBlock) {
selections = [];

for (const { start: lineStart, end } of Position.IterateLine(vimState)) {
for (const { start: lineStart, end } of Position.IterateLinesInCursorBlock(vimState)) {
selections.push(new vscode.Selection(lineStart, end));
}
} else {
Expand All @@ -1284,6 +1284,24 @@ export class ModeHandler implements vscode.Disposable {
}
break;
}
case Mode.VisualLine: {
selections = vimState.cursors.map((c: Range) => {
return new vscode.Selection(c.start.getLineBegin(), c.stop.getLineEnd());
});
break;
}
case Mode.VisualBlock: {
for (const c of vimState.cursors) {
for (const { start: lineStart, end } of Position.IterateLinesInBlock(
c.start,
c.stop,
vimState.desiredColumn === Number.POSITIVE_INFINITY
)) {
selections.push(new vscode.Selection(lineStart, end));
}
}
break;
}
case Mode.Normal:
case Mode.Insert: {
for (const { stop: cursorStop } of vimState.cursors) {
Expand Down
7 changes: 4 additions & 3 deletions src/textEditor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ export class TextEditor {
text: string,
at: Position | undefined = undefined,
letVSCodeHandleKeystrokes: boolean | undefined = undefined
): Promise<boolean> {
): Promise<void> {
// If we insert "blah(" with default:type, VSCode will insert the closing ).
// We *probably* don't want that to happen if we're inserting a lot of text.
if (letVSCodeHandleKeystrokes === undefined) {
Expand All @@ -56,8 +56,6 @@ export class TextEditor {
} else {
await vscode.commands.executeCommand('default:type', { text });
}

return true;
}

/**
Expand All @@ -69,6 +67,9 @@ export class TextEditor {
});
}

/**
* @deprecated Use DeleteTextTransformation or DeleteTextRangeTransformation instead.
*/
static async delete(range: vscode.Range): Promise<boolean> {
return vscode.window.activeTextEditor!.edit(editBuilder => {
editBuilder.delete(range);
Expand Down

0 comments on commit e2c61f9

Please sign in to comment.