mirror of
https://github.com/VSCodeVim/Vim.git
synced 2024-11-10 00:59:44 +03:00
commit
f5885a6d75
@ -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);
|
||||
|
||||
|
@ -6,6 +6,7 @@ export enum ModeName {
|
||||
Visual,
|
||||
VisualLine,
|
||||
SearchInProgressMode,
|
||||
Replace,
|
||||
}
|
||||
|
||||
export enum VSCodeVimCursorType {
|
||||
|
@ -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
13
src/mode/modeReplace.ts
Normal 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);
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
84
test/mode/modeReplace.test.ts
Normal file
84
test/mode/modeReplace.test.ts
Normal 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"]
|
||||
});
|
||||
});
|
Loading…
Reference in New Issue
Block a user