Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Replace mode #580

Merged
merged 5 commits into from
Aug 8, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
138 changes: 108 additions & 30 deletions src/actions/actions.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { VimSpecialCommands, VimState, SearchState, SearchDirection } from './../mode/modeHandler';
import { VimSpecialCommands, VimState, SearchState, SearchDirection, ReplaceState } from './../mode/modeHandler';
import { ModeName } from './../mode/mode';
import { TextEditor } from './../textEditor';
import { Register, RegisterMode } from './../register/register';
Expand Down Expand Up @@ -394,7 +394,7 @@ class CommandRegister extends BaseCommand {

@RegisterAction
class CommandEsc extends BaseCommand {
modes = [ModeName.Insert, ModeName.Visual, ModeName.VisualLine, ModeName.SearchInProgressMode];
modes = [ModeName.Insert, ModeName.Visual, ModeName.VisualLine, ModeName.SearchInProgressMode, ModeName.Replace];
keys = ["<escape>"];

public async exec(position: Position, vimState: VimState): Promise<VimState> {
Expand Down Expand Up @@ -477,6 +477,109 @@ class CommandInsertAtCursor extends BaseCommand {
}
}

@RegisterAction
class CommandReplacecAtCursor extends BaseCommand {
modes = [ModeName.Normal];
keys = ["R"];
mustBeFirstKey = true;

public async exec(position: Position, vimState: VimState): Promise<VimState> {
vimState.currentMode = ModeName.Replace;
vimState.replaceState = new ReplaceState(position);

return vimState;
}
}

@RegisterAction
class CommandReplaceInReplaceMode extends BaseCommand {
modes = [ModeName.Replace];
keys = ["<character>"];
canBeRepeatedWithDot = true;

public async exec(position: Position, vimState: VimState): Promise<VimState> {
const char = this.keysPressed[0];

const replaceState = vimState.replaceState!;

if (char === "<backspace>") {
if (position.isBeforeOrEqual(replaceState.replaceCursorStartPosition)) {
vimState.cursorPosition = position.getLeft();
vimState.cursorStartPosition = position.getLeft();
} else if (position.line > replaceState.replaceCursorStartPosition.line ||
position.character > replaceState.originalChars.length) {
const newPosition = await TextEditor.backspace(position);
vimState.cursorPosition = newPosition;
vimState.cursorStartPosition = newPosition;
} else {
await TextEditor.replace(new vscode.Range(position.getLeft(), position), replaceState.originalChars[position.character - 1]);
const leftPosition = position.getLeft();
vimState.cursorPosition = leftPosition;
vimState.cursorStartPosition = leftPosition;
}
} else {
if (!position.isLineEnd()) {
vimState = await new DeleteOperator().run(vimState, position, position);
}
await TextEditor.insertAt(char, position);

vimState.cursorStartPosition = Position.FromVSCodePosition(vscode.window.activeTextEditor.selection.start);
vimState.cursorPosition = Position.FromVSCodePosition(vscode.window.activeTextEditor.selection.start);
}

vimState.currentMode = ModeName.Replace;
return vimState;
}
}

class ArrowsInReplaceMode extends BaseMovement {
modes = [ModeName.Replace];
keys: string[];

public async execAction(position: Position, vimState: VimState): Promise<Position> {
let newPosition: Position = position;

switch (this.keys[0]) {
case "<up>":
newPosition = await new MoveUpArrow().execAction(position, vimState);
break;
case "<down>":
newPosition = await new MoveDownArrow().execAction(position, vimState);
break;
case "<left>":
newPosition = await new MoveLeftArrow().execAction(position, vimState);
break;
case "<right>":
newPosition = await new MoveRightArrow().execAction(position, vimState);
break;
default:
break;
}
vimState.replaceState = new ReplaceState(newPosition);
return newPosition;
}
}

@RegisterAction
class UpArrowInReplaceMode extends ArrowsInReplaceMode {
keys = ["<up>"];
}

@RegisterAction
class DownArrowInReplaceMode extends ArrowsInReplaceMode {
keys = ["<down>"];
}

@RegisterAction
class LeftArrowInReplaceMode extends ArrowsInReplaceMode {
keys = ["<left>"];
}

@RegisterAction
class RightArrowInReplaceMode extends ArrowsInReplaceMode {
keys = ["<right>"];
}

@RegisterAction
class CommandInsertInSearchMode extends BaseCommand {
modes = [ModeName.SearchInProgressMode];
Expand Down Expand Up @@ -620,34 +723,9 @@ class CommandInsertInInsertMode extends BaseCommand {
const char = this.keysPressed[this.keysPressed.length - 1];

if (char === "<backspace>") {
if (position.character === 0) {
if (position.line > 0) {
const prevEndOfLine = position.getPreviousLineBegin().getLineEnd();

await TextEditor.delete(new vscode.Range(
position.getPreviousLineBegin().getLineEnd(),
position.getLineBegin()
));

vimState.cursorPosition = prevEndOfLine;
vimState.cursorStartPosition = prevEndOfLine;
}
} else {
let leftPosition = position.getLeft();

if (position.getFirstLineNonBlankChar().character >= position.character) {
let tabStop = vscode.workspace.getConfiguration("editor").get("useTabStops", true);

if (tabStop) {
leftPosition = position.getLeftTabStop();
}
}

await TextEditor.delete(new vscode.Range(position, leftPosition));

vimState.cursorPosition = leftPosition;
vimState.cursorStartPosition = leftPosition;
}
const newPosition = await TextEditor.backspace(position);
vimState.cursorPosition = newPosition;
vimState.cursorStartPosition = newPosition;
} else {
await TextEditor.insert(char, vimState.cursorPosition);

Expand Down
1 change: 1 addition & 0 deletions src/mode/mode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ export enum ModeName {
Visual,
VisualLine,
SearchInProgressMode,
Replace,
}

export enum VSCodeVimCursorType {
Expand Down
22 changes: 22 additions & 0 deletions src/mode/modeHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { InsertModeRemapper, OtherModesRemapper } from './remapper';
import { NormalMode } from './modeNormal';
import { InsertMode } from './modeInsert';
import { VisualMode } from './modeVisual';
import { ReplaceMode } from './modeReplace';
import { SearchInProgressMode } from './modeSearchInProgress';
import { TextEditor } from './../textEditor';
import { VisualLineMode } from './modeVisualLine';
Expand Down Expand Up @@ -84,6 +85,8 @@ export class VimState {

public searchState: SearchState | undefined = undefined;

public replaceState: ReplaceState | undefined = undefined;

/**
* The mode Vim will be in once this action finishes.
*/
Expand Down Expand Up @@ -239,6 +242,24 @@ export class SearchState {
}
}

export class ReplaceState {
private _replaceCursorStartPosition: Position;

public get replaceCursorStartPosition() {
return this._replaceCursorStartPosition;
}

public originalChars: string[] = [];

constructor(startPosition: Position) {
this._replaceCursorStartPosition = startPosition;
let text = TextEditor.getLineAt(startPosition).text.substring(startPosition.character);
for (let [key, value] of text.split("").entries()) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's that ES6 style that I like :-)

this.originalChars[key + startPosition.character] = value;
}
}
}

/**
* The RecordedState class holds the current action that the user is
* doing. Example: Imagine that the user types:
Expand Down Expand Up @@ -406,6 +427,7 @@ export class ModeHandler implements vscode.Disposable {
new VisualMode(),
new VisualLineMode(),
new SearchInProgressMode(),
new ReplaceMode(),
];
this.vimState.historyTracker = new HistoryTracker();

Expand Down
13 changes: 13 additions & 0 deletions src/mode/modeReplace.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
"use strict";

import { ModeName, Mode } from './mode';
import { VSCodeVimCursorType } from './mode';

export class ReplaceMode extends Mode {
public text = "Replace";
public cursorType = VSCodeVimCursorType.TextDecoration;

constructor() {
super(ModeName.Replace);
}
}
31 changes: 31 additions & 0 deletions src/textEditor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,37 @@ export class TextEditor {
});
}

static async backspace(position: Position): Promise<Position> {
if (position.character === 0) {
if (position.line > 0) {
const prevEndOfLine = position.getPreviousLineBegin().getLineEnd();

await TextEditor.delete(new vscode.Range(
position.getPreviousLineBegin().getLineEnd(),
position.getLineBegin()
));

return prevEndOfLine;
} else {
return position;
}
} else {
let leftPosition = position.getLeft();

if (position.getFirstLineNonBlankChar().character >= position.character) {
let tabStop = vscode.workspace.getConfiguration("editor").get("useTabStops", true);

if (tabStop) {
leftPosition = position.getLeftTabStop();
}
}

await TextEditor.delete(new vscode.Range(position, leftPosition));

return leftPosition;
}
}

static getDocumentVersion(): number {
return vscode.window.activeTextEditor.document.version;
}
Expand Down
84 changes: 84 additions & 0 deletions test/mode/modeReplace.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
"use strict";

import { setupWorkspace, cleanUpWorkspace} from './../testUtils';
import { ModeName } from '../../src/mode/mode';
import { ModeHandler } from '../../src/mode/modeHandler';
import { getTestingFunctions } from '../testSimplifier';

suite("Mode Replace", () => {
let modeHandler: ModeHandler = new ModeHandler();

let {
newTest,
newTestOnly,
} = getTestingFunctions(modeHandler);

setup(async () => {
await setupWorkspace();
});

teardown(cleanUpWorkspace);

newTest({
title: "Can handle R",
start: ['123|456'],
keysPressed: 'Rab',
end: ["123ab|6"]
});

newTest({
title: "Can handle R",
start: ['123|456'],
keysPressed: 'Rabcd',
end: ["123abcd|"]
});

newTest({
title: "Can handle R across lines",
start: ['123|456', '789'],
keysPressed: 'Rabcd\nefg',
end: ["123abcd", "efg|", "789"]
});

newTest({
title: "Can handle backspace",
start: ['123|456'],
keysPressed: 'Rabc<backspace><backspace><backspace>',
end: ["123|456"]
});

newTest({
title: "Can handle backspace",
start: ['123|456'],
keysPressed: 'Rabcd<backspace><backspace><backspace><backspace><backspace>',
end: ["12|3456"]
});

newTest({
title: "Can handle backspace across lines",
start: ['123|456'],
keysPressed: 'Rabcd\nef<backspace><backspace><backspace><backspace><backspace>',
end: ["123ab|6"]
});

newTest({
title: "Can handle arrows",
start: ['123|456'],
keysPressed: 'Rabc<left><backspace><backspace>',
end: ["123|abc"]
});

newTest({
title: "Can handle .",
start: ['123|456', '123456'],
keysPressed: 'Rabc<escape>j0.',
end: ["123abc", "ab|c456"]
});

newTest({
title: "Can handle . across lines",
start: ['123|456', '123456'],
keysPressed: 'Rabc\ndef<escape>j0.',
end: ["123abc", "def", "abc", "de|f"]
});
});