mirror of
https://github.com/microsoft/vscode-extension-samples.git
synced 2026-04-27 16:55:44 +08:00
344 lines
9.3 KiB
TypeScript
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(),
|
|
};
|