Skip to content

Commit

Permalink
Merge pull request #580 from rebornix/ReplaceMode
Browse files Browse the repository at this point in the history
Replace mode
  • Loading branch information
johnfn authored Aug 8, 2016
2 parents f3dfd1d + f1f7c58 commit f5885a6
Show file tree
Hide file tree
Showing 6 changed files with 259 additions and 30 deletions.
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 @@ -241,6 +244,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()) {
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 @@ -408,6 +429,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"]
});
});

0 comments on commit f5885a6

Please sign in to comment.