Merge pull request #580 from rebornix/ReplaceMode

Replace mode
This commit is contained in:
Grant Mathews 2016-08-08 10:51:23 -07:00 committed by GitHub
commit f5885a6d75
6 changed files with 259 additions and 30 deletions

View File

@ -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';
@ -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> {
@ -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];
@ -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);

View File

@ -6,6 +6,7 @@ export enum ModeName {
Visual,
VisualLine,
SearchInProgressMode,
Replace,
}
export enum VSCodeVimCursorType {

View File

@ -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';
@ -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.
*/
@ -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:
@ -408,6 +429,7 @@ export class ModeHandler implements vscode.Disposable {
new VisualMode(),
new VisualLineMode(),
new SearchInProgressMode(),
new ReplaceMode(),
];
this.vimState.historyTracker = new HistoryTracker();

13
src/mode/modeReplace.ts Normal file
View File

@ -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);
}
}

View File

@ -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;
}

View File

@ -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"]
});
});