Files
vscode-extension-samples/vim-sample/src/operators.ts
2020-05-29 14:01:37 -07:00

344 lines
9.3 KiB
TypeScript

/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Position, Selection, Range, TextDocument, TextEditor, TextEditorRevealType } from 'vscode';
import { Motion, Motions } from './motions';
import { Mode, IController, DeleteRegister } from './common';
export abstract class Operator {
public abstract runNormalMode(ctrl: IController, ed: TextEditor, repeatCount: number, args: string): boolean;
public abstract runVisualMode(ctrl: IController, ed: TextEditor, args: string): boolean;
protected doc(ed: TextEditor): TextDocument {
return ed.document;
}
protected pos(ed: TextEditor): Position {
return ed.selection.active;
}
protected sel(ed: TextEditor): Selection {
return ed.selection;
}
protected setPosReveal(ed: TextEditor, line: number, char: number): void {
ed.selection = new Selection(new Position(line, char), new Position(line, char));
ed.revealRange(ed.selection, TextEditorRevealType.Default);
}
protected delete(ctrl: IController, ed: TextEditor, isWholeLine: boolean, range: Range): void {
ctrl.setDeleteRegister(new DeleteRegister(isWholeLine, ed.document.getText(range)));
ed.edit((builder) => {
builder.delete(range);
});
}
}
abstract class OperatorWithNoArgs extends Operator {
public runNormalMode(ctrl: IController, ed: TextEditor, repeatCount: number, args: string): boolean {
this._run(ctrl, ed);
return true;
}
public runVisualMode(ctrl: IController, ed: TextEditor, args: string): boolean {
this._run(ctrl, ed);
return true;
}
protected abstract _run(ctrl: IController, ed: TextEditor): void;
}
class InsertOperator extends OperatorWithNoArgs {
protected _run(ctrl: IController, ed: TextEditor): void {
ctrl.setMode(Mode.INSERT);
}
}
class AppendOperator extends OperatorWithNoArgs {
protected _run(ctrl: IController, ed: TextEditor): void {
const newPos = Motions.RightMotion.run(this.doc(ed), this.pos(ed), ctrl.motionState);
this.setPosReveal(ed, newPos.line, newPos.character);
ctrl.setMode(Mode.INSERT);
}
}
class AppendEndOfLineOperator extends OperatorWithNoArgs {
protected _run(ctrl: IController, ed: TextEditor): void {
const newPos = Motions.EndOfLine.run(this.doc(ed), this.pos(ed), ctrl.motionState);
this.setPosReveal(ed, newPos.line, newPos.character);
ctrl.setMode(Mode.INSERT);
}
}
class VisualOperator extends OperatorWithNoArgs {
protected _run(ctrl: IController, ed: TextEditor): void {
ctrl.motionState.anchor = this.pos(ed);
ctrl.setVisual(true);
}
}
class DeleteCharUnderCursorOperator extends Operator {
public runNormalMode(ctrl: IController, ed: TextEditor, repeatCount: number, args: string): boolean {
const to = Motions.NextCharacter.repeat(repeatCount > 1, repeatCount).run(this.doc(ed), this.pos(ed), ctrl.motionState);
const from = this.pos(ed);
this.delete(ctrl, ed, false, new Range(from.line, from.character, to.line, to.character));
return true;
}
public runVisualMode(ctrl: IController, ed: TextEditor, args: string): boolean {
const sel = this.sel(ed);
this.delete(ctrl, ed, false, sel);
return true;
}
}
class DeleteLineOperator extends Operator {
public runNormalMode(ctrl: IController, ed: TextEditor, repeatCount: number, args: string): boolean {
const pos = this.pos(ed);
const doc = this.doc(ed);
let fromLine = pos.line;
let fromCharacter = 0;
let toLine = fromLine + repeatCount;
let toCharacter = 0;
if (toLine >= doc.lineCount - 1) {
// Deleting last line
toLine = doc.lineCount - 1;
toCharacter = doc.lineAt(toLine).text.length;
if (fromLine > 0) {
fromLine = fromLine - 1;
fromCharacter = doc.lineAt(fromLine).text.length;
}
}
this.delete(ctrl, ed, true, new Range(fromLine, fromCharacter, toLine, toCharacter));
return true;
}
public runVisualMode(ctrl: IController, ed: TextEditor, args: string): boolean {
const sel = this.sel(ed);
this.delete(ctrl, ed, false, sel);
return true;
}
}
abstract class OperatorWithMotion extends Operator {
public runNormalMode(ctrl: IController, ed: TextEditor, repeatCount: number, args: string): boolean {
const motion = ctrl.findMotion(args);
if (!motion) {
// is it motion building
if (ctrl.isMotionPrefix(args)) {
return false;
}
// INVALID INPUT - beep!!
return true;
}
return this._runNormalMode(ctrl, ed, motion.repeat(repeatCount > 1, repeatCount));
}
protected abstract _runNormalMode(ctrl: IController, ed: TextEditor, motion: Motion): boolean;
}
class DeleteToOperator extends OperatorWithMotion {
public runNormalMode(ctrl: IController, ed: TextEditor, repeatCount: number, args: string): boolean {
if (args === 'd') {
// dd
return Operators.DeleteLine.runNormalMode(ctrl, ed, repeatCount, args);
}
return super.runNormalMode(ctrl, ed, repeatCount, args);
}
protected _runNormalMode(ctrl: IController, ed: TextEditor, motion: Motion): boolean {
const to = motion.run(this.doc(ed), this.pos(ed), ctrl.motionState);
const from = this.pos(ed);
this.delete(ctrl, ed, false, new Range(from.line, from.character, to.line, to.character));
return true;
}
public runVisualMode(ctrl: IController, ed: TextEditor, args: string): boolean {
const sel = this.sel(ed);
this.delete(ctrl, ed, false, sel);
return true;
}
}
class PutOperator extends Operator {
public runNormalMode(ctrl: IController, ed: TextEditor, repeatCount: number, args: string): boolean {
const register = ctrl.getDeleteRegister();
if (!register) {
// No delete register - beep!!
return true;
}
let str = repeatString(register.content, repeatCount);
const pos = this.pos(ed);
if (!register.isWholeLine) {
ed.edit((builder) => {
builder.insert(new Position(pos.line, pos.character + 1), str);
});
return true;
}
const doc = this.doc(ed);
let insertLine = pos.line + 1;
let insertCharacter = 0;
if (insertLine >= doc.lineCount) {
// on last line
insertLine = doc.lineCount - 1;
insertCharacter = doc.lineAt(insertLine).text.length;
str = '\n' + str;
}
ed.edit((builder) => {
builder.insert(new Position(insertLine, insertCharacter), str);
});
return true;
}
public runVisualMode(ctrl: IController, ed: TextEditor, args: string): boolean {
const register = ctrl.getDeleteRegister();
if (!register) {
// No delete register - beep!!
return false;
}
const str = register.content;
const sel = this.sel(ed);
ed.edit((builder) => {
builder.replace(sel, str);
});
return true;
}
}
class ReplaceOperator extends Operator {
public runNormalMode(ctrl: IController, ed: TextEditor, repeatCount: number, args: string): boolean {
if (args.length === 0) {
// input not ready
return false;
}
const doc = this.doc(ed);
const pos = this.pos(ed);
const toCharacter = pos.character + repeatCount;
if (toCharacter > doc.lineAt(pos).text.length) {
// invalid replace (beep!)
return true;
}
ed.edit((builder) => {
builder.replace(new Range(pos.line, pos.character, pos.line, toCharacter), repeatString(args, repeatCount));
});
return true;
}
public runVisualMode(ctrl: IController, ed: TextEditor, args: string): boolean {
if (args.length === 0) {
// input not ready
return false;
}
const doc = this.doc(ed);
const sel = this.sel(ed);
const srcString = doc.getText(sel);
let dstString = '';
for (let i = 0; i < srcString.length; i++) {
const ch = srcString.charAt(i);
if (ch === '\r' || ch === '\n') {
dstString += ch;
} else {
dstString += args;
}
}
ed.edit((builder) => {
builder.replace(sel, dstString);
});
return true;
}
}
class ReplaceModeOperator extends Operator {
public runNormalMode(ctrl: IController, ed: TextEditor, repeatCount: number, args: string): boolean {
ctrl.setMode(Mode.REPLACE);
return true;
}
public runVisualMode(ctrl: IController, ed: TextEditor, args: string): boolean {
this.delete(ctrl, ed, false, this.sel(ed));
ctrl.setMode(Mode.INSERT);
return true;
}
}
class ChangeOperator extends OperatorWithMotion {
protected _runNormalMode(ctrl: IController, ed: TextEditor, motion: Motion): boolean {
const to = motion.run(this.doc(ed), this.pos(ed), ctrl.motionState);
const from = this.pos(ed);
this.delete(ctrl, ed, false, new Range(from.line, from.character, to.line, to.character));
ctrl.setMode(Mode.INSERT);
return true;
}
public runVisualMode(ctrl: IController, ed: TextEditor, args: string): boolean {
const sel = this.sel(ed);
this.delete(ctrl, ed, false, sel);
ctrl.setMode(Mode.INSERT);
return true;
}
}
function repeatString(str: string, repeatCount: number): string {
let result = '';
for (let i = 0; i < repeatCount; i++) {
result += str;
}
return result;
}
export const Operators = {
Insert: new InsertOperator(),
Visual: new VisualOperator(),
Append: new AppendOperator(),
AppendEndOfLine: new AppendEndOfLineOperator(),
DeleteCharUnderCursor: new DeleteCharUnderCursorOperator(),
DeleteTo: new DeleteToOperator(),
DeleteLine: new DeleteLineOperator(),
Put: new PutOperator(),
Replace: new ReplaceOperator(),
Change: new ChangeOperator(),
ReplaceMode: new ReplaceModeOperator(),
};