mirror of
https://github.com/microsoft/vscode-extension-samples.git
synced 2026-06-13 07:10:26 +08:00
Run format on repo
This commit is contained in:
@ -1,51 +1,51 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { MotionState, Motion } from './motions';
|
||||
|
||||
export enum Mode {
|
||||
INSERT,
|
||||
NORMAL,
|
||||
REPLACE
|
||||
}
|
||||
|
||||
export interface ModifierKeys {
|
||||
ctrl?: boolean;
|
||||
alt?: boolean;
|
||||
shifit?: boolean;
|
||||
}
|
||||
|
||||
export class DeleteRegister {
|
||||
public isWholeLine: boolean;
|
||||
public content: string;
|
||||
|
||||
constructor(isWholeLine: boolean, content: string) {
|
||||
this.isWholeLine = isWholeLine;
|
||||
this.content = content;
|
||||
}
|
||||
}
|
||||
|
||||
export interface IController {
|
||||
motionState: MotionState;
|
||||
|
||||
setMode(mode: Mode): void;
|
||||
setVisual(newVisual: boolean): void;
|
||||
findMotion(input: string): Motion;
|
||||
isMotionPrefix(input: string): boolean;
|
||||
|
||||
setDeleteRegister(register: DeleteRegister): void;
|
||||
getDeleteRegister(): DeleteRegister;
|
||||
}
|
||||
|
||||
export abstract class AbstractCommandDescriptor {
|
||||
|
||||
public abstract createCommand(args?: any): Command;
|
||||
|
||||
}
|
||||
|
||||
export interface Command {
|
||||
commandId: string;
|
||||
args?: any[];
|
||||
}
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { MotionState, Motion } from './motions';
|
||||
|
||||
export enum Mode {
|
||||
INSERT,
|
||||
NORMAL,
|
||||
REPLACE
|
||||
}
|
||||
|
||||
export interface ModifierKeys {
|
||||
ctrl?: boolean;
|
||||
alt?: boolean;
|
||||
shifit?: boolean;
|
||||
}
|
||||
|
||||
export class DeleteRegister {
|
||||
public isWholeLine: boolean;
|
||||
public content: string;
|
||||
|
||||
constructor(isWholeLine: boolean, content: string) {
|
||||
this.isWholeLine = isWholeLine;
|
||||
this.content = content;
|
||||
}
|
||||
}
|
||||
|
||||
export interface IController {
|
||||
motionState: MotionState;
|
||||
|
||||
setMode(mode: Mode): void;
|
||||
setVisual(newVisual: boolean): void;
|
||||
findMotion(input: string): Motion;
|
||||
isMotionPrefix(input: string): boolean;
|
||||
|
||||
setDeleteRegister(register: DeleteRegister): void;
|
||||
getDeleteRegister(): DeleteRegister;
|
||||
}
|
||||
|
||||
export abstract class AbstractCommandDescriptor {
|
||||
|
||||
public abstract createCommand(args?: any): Command;
|
||||
|
||||
}
|
||||
|
||||
export interface Command {
|
||||
commandId: string;
|
||||
args?: any[];
|
||||
}
|
||||
|
||||
@ -1,303 +1,303 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { TextEditorCursorStyle, Position, Range, Selection, TextEditor, TextEditorRevealType, window } from 'vscode';
|
||||
|
||||
import { Words } from './words';
|
||||
import { MotionState, Motion } from './motions';
|
||||
import { Mode, IController, DeleteRegister, Command, ModifierKeys } from './common';
|
||||
import { Mappings } from './mappings';
|
||||
|
||||
export interface ITypeResult {
|
||||
hasConsumedInput: boolean;
|
||||
executeEditorCommand: Command;
|
||||
}
|
||||
|
||||
export class Controller implements IController {
|
||||
|
||||
private _currentMode: Mode;
|
||||
private _currentInput: string;
|
||||
private _motionState: MotionState;
|
||||
private _isVisual: boolean;
|
||||
|
||||
public get motionState(): MotionState { return this._motionState; }
|
||||
public findMotion(input: string): Motion { return Mappings.findMotion(input); }
|
||||
public isMotionPrefix(input: string): boolean { return Mappings.isMotionPrefix(input); }
|
||||
|
||||
private _deleteRegister: DeleteRegister;
|
||||
public setDeleteRegister(register: DeleteRegister): void { this._deleteRegister = register; }
|
||||
public getDeleteRegister(): DeleteRegister { return this._deleteRegister; }
|
||||
|
||||
constructor() {
|
||||
this._motionState = new MotionState();
|
||||
this._deleteRegister = null;
|
||||
this.setVisual(false);
|
||||
this.setMode(Mode.NORMAL);
|
||||
}
|
||||
|
||||
public setWordSeparators(wordSeparators: string): void {
|
||||
this._motionState.wordCharacterClass = Words.createWordCharacters(wordSeparators);
|
||||
}
|
||||
|
||||
public ensureNormalModePosition(editor: TextEditor): void {
|
||||
if (this._currentMode !== Mode.NORMAL) {
|
||||
return;
|
||||
}
|
||||
if (this._isVisual) {
|
||||
return;
|
||||
}
|
||||
const sel = editor.selection;
|
||||
const pos = sel.active;
|
||||
const doc = editor.document;
|
||||
const lineContent = doc.lineAt(pos.line).text;
|
||||
if (lineContent.length === 0) {
|
||||
return;
|
||||
}
|
||||
const maxCharacter = lineContent.length - 1;
|
||||
if (pos.character > maxCharacter) {
|
||||
setPositionAndReveal(editor, pos.line, maxCharacter);
|
||||
}
|
||||
}
|
||||
|
||||
public hasInput(): boolean {
|
||||
return this._currentInput.length > 0;
|
||||
}
|
||||
|
||||
public clearInput(): void {
|
||||
this._currentInput = '';
|
||||
}
|
||||
|
||||
public getMode(): Mode {
|
||||
return this._currentMode;
|
||||
}
|
||||
|
||||
public setMode(newMode: Mode): void {
|
||||
if (newMode !== this._currentMode) {
|
||||
this._currentMode = newMode;
|
||||
this._motionState.cursorDesiredCharacter = -1; // uninitialized
|
||||
this._currentInput = '';
|
||||
}
|
||||
}
|
||||
|
||||
public setVisual(newVisual: boolean): void {
|
||||
if (this._isVisual !== newVisual) {
|
||||
this._isVisual = newVisual;
|
||||
}
|
||||
}
|
||||
|
||||
public getVisual(): boolean {
|
||||
return this._isVisual;
|
||||
}
|
||||
|
||||
public getCursorStyle(): TextEditorCursorStyle {
|
||||
if (this._currentMode === Mode.NORMAL) {
|
||||
if (/^([1-9]\d*)?(r|c)/.test(this._currentInput)) {
|
||||
return TextEditorCursorStyle.Underline;
|
||||
}
|
||||
return TextEditorCursorStyle.Block;
|
||||
}
|
||||
if (this._currentMode === Mode.REPLACE) {
|
||||
return TextEditorCursorStyle.Underline;
|
||||
}
|
||||
return TextEditorCursorStyle.Line;
|
||||
}
|
||||
|
||||
private _getModeLabel(): string {
|
||||
if (this._currentMode === Mode.NORMAL) {
|
||||
if (this._isVisual) {
|
||||
return '-- VISUAL --';
|
||||
}
|
||||
return '-- NORMAL --';
|
||||
}
|
||||
|
||||
if (this._currentMode === Mode.REPLACE) {
|
||||
if (this._isVisual) {
|
||||
return '-- (replace) VISUAL --';
|
||||
}
|
||||
return '-- REPLACE --';
|
||||
}
|
||||
|
||||
if (this._isVisual) {
|
||||
return '-- (insert) VISUAL --';
|
||||
}
|
||||
return '-- INSERT --';
|
||||
}
|
||||
|
||||
public getStatusText(): string {
|
||||
const label = this._getModeLabel();
|
||||
return `VIM:> ${label}` + (this._currentInput ? ` >${this._currentInput}` : ``);
|
||||
}
|
||||
|
||||
private _isInComposition = false;
|
||||
private _composingText = '';
|
||||
|
||||
public compositionStart(editor: TextEditor): void {
|
||||
this._isInComposition = true;
|
||||
this._composingText = '';
|
||||
}
|
||||
|
||||
public compositionEnd(editor: TextEditor): Thenable<ITypeResult> {
|
||||
this._isInComposition = false;
|
||||
const text = this._composingText;
|
||||
this._composingText = '';
|
||||
|
||||
if (text.length === 0) {
|
||||
return Promise.resolve({
|
||||
hasConsumedInput: true,
|
||||
executeEditorCommand: null
|
||||
});
|
||||
}
|
||||
|
||||
return this.type(editor, text, {});
|
||||
}
|
||||
|
||||
public type(editor: TextEditor, text: string, modifierKeys: ModifierKeys): Thenable<ITypeResult> {
|
||||
if (this._currentMode !== Mode.NORMAL && this._currentMode !== Mode.REPLACE) {
|
||||
return Promise.resolve({
|
||||
hasConsumedInput: false,
|
||||
executeEditorCommand: null
|
||||
});
|
||||
}
|
||||
|
||||
if (this._isInComposition) {
|
||||
this._composingText += text;
|
||||
return Promise.resolve({
|
||||
hasConsumedInput: true,
|
||||
executeEditorCommand: null
|
||||
});
|
||||
}
|
||||
|
||||
if (this._currentMode === Mode.REPLACE) {
|
||||
const pos = editor.selection.active;
|
||||
editor.edit((builder) => {
|
||||
builder.replace(new Range(pos.line, pos.character, pos.line, pos.character + 1), text);
|
||||
}).then(() => {
|
||||
setPositionAndReveal(editor, pos.line, pos.character + 1);
|
||||
});
|
||||
|
||||
return Promise.resolve({
|
||||
hasConsumedInput: true,
|
||||
executeEditorCommand: null
|
||||
});
|
||||
}
|
||||
this._currentInput += text;
|
||||
return this._interpretNormalModeInput(editor, modifierKeys);
|
||||
}
|
||||
|
||||
public replacePrevChar(editor: TextEditor, text: string, replaceCharCnt: number): boolean {
|
||||
if (this._currentMode !== Mode.NORMAL && this._currentMode !== Mode.REPLACE) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this._isInComposition) {
|
||||
this._composingText = this._composingText.substr(0, this._composingText.length - replaceCharCnt) + text;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (this._currentMode === Mode.REPLACE) {
|
||||
const pos = editor.selection.active;
|
||||
editor.edit((builder) => {
|
||||
builder.replace(new Range(pos.line, pos.character - replaceCharCnt, pos.line, pos.character), text);
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private _interpretNormalModeInput(editor: TextEditor, modifierKeys: ModifierKeys): Thenable<ITypeResult> {
|
||||
if (this._currentInput.startsWith(':')) {
|
||||
return window.showInputBox({ value: 'tabm' }).then((value) => {
|
||||
return this._findMapping(value || '', editor, modifierKeys);
|
||||
});
|
||||
}
|
||||
const result = this._findMapping(this._currentInput, editor, modifierKeys);
|
||||
return Promise.resolve(result);
|
||||
}
|
||||
|
||||
private _findMapping(input: string, editor: TextEditor, modifierKeys: ModifierKeys): ITypeResult {
|
||||
const command = Mappings.findCommand(input, modifierKeys);
|
||||
if (command) {
|
||||
this._currentInput = '';
|
||||
return {
|
||||
hasConsumedInput: true,
|
||||
executeEditorCommand: command
|
||||
};
|
||||
}
|
||||
|
||||
const operator = Mappings.findOperator(input, modifierKeys);
|
||||
if (operator) {
|
||||
if (this._isVisual) {
|
||||
if (operator.runVisual(this, editor)) {
|
||||
this._currentInput = '';
|
||||
}
|
||||
} else {
|
||||
// Mode.NORMAL
|
||||
if (operator.runNormal(this, editor)) {
|
||||
this._currentInput = '';
|
||||
}
|
||||
}
|
||||
return {
|
||||
hasConsumedInput: true,
|
||||
executeEditorCommand: null
|
||||
};
|
||||
}
|
||||
|
||||
const motionCommand = Mappings.findMotionCommand(input, this._isVisual, modifierKeys);
|
||||
if (motionCommand) {
|
||||
this._currentInput = '';
|
||||
return {
|
||||
hasConsumedInput: true,
|
||||
executeEditorCommand: motionCommand
|
||||
};
|
||||
}
|
||||
|
||||
const motion = Mappings.findMotion(input);
|
||||
if (motion) {
|
||||
const newPos = motion.run(editor.document, editor.selection.active, this._motionState);
|
||||
if (this._isVisual) {
|
||||
setSelectionAndReveal(editor, this._motionState.anchor, newPos.line, newPos.character);
|
||||
} else {
|
||||
// Mode.NORMAL
|
||||
setPositionAndReveal(editor, newPos.line, newPos.character);
|
||||
}
|
||||
this._currentInput = '';
|
||||
return {
|
||||
hasConsumedInput: true,
|
||||
executeEditorCommand: null
|
||||
};
|
||||
}
|
||||
|
||||
// is it motion building
|
||||
if (this.isMotionPrefix(input)) {
|
||||
return {
|
||||
hasConsumedInput: true,
|
||||
executeEditorCommand: null
|
||||
};
|
||||
}
|
||||
|
||||
// INVALID INPUT - beep!!
|
||||
this._currentInput = '';
|
||||
return {
|
||||
hasConsumedInput: true,
|
||||
executeEditorCommand: null
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
function setSelectionAndReveal(editor: TextEditor, anchor: Position, line: number, char: number): void {
|
||||
editor.selection = new Selection(anchor, new Position(line, char));
|
||||
revealPosition(editor, line, char);
|
||||
}
|
||||
|
||||
function setPositionAndReveal(editor: TextEditor, line: number, char: number): void {
|
||||
editor.selection = new Selection(new Position(line, char), new Position(line, char));
|
||||
revealPosition(editor, line, char);
|
||||
}
|
||||
|
||||
function revealPosition(editor: TextEditor, line: number, char: number): void {
|
||||
editor.revealRange(new Range(line, char, line, char), TextEditorRevealType.Default);
|
||||
}
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { TextEditorCursorStyle, Position, Range, Selection, TextEditor, TextEditorRevealType, window } from 'vscode';
|
||||
|
||||
import { Words } from './words';
|
||||
import { MotionState, Motion } from './motions';
|
||||
import { Mode, IController, DeleteRegister, Command, ModifierKeys } from './common';
|
||||
import { Mappings } from './mappings';
|
||||
|
||||
export interface ITypeResult {
|
||||
hasConsumedInput: boolean;
|
||||
executeEditorCommand: Command;
|
||||
}
|
||||
|
||||
export class Controller implements IController {
|
||||
|
||||
private _currentMode: Mode;
|
||||
private _currentInput: string;
|
||||
private _motionState: MotionState;
|
||||
private _isVisual: boolean;
|
||||
|
||||
public get motionState(): MotionState { return this._motionState; }
|
||||
public findMotion(input: string): Motion { return Mappings.findMotion(input); }
|
||||
public isMotionPrefix(input: string): boolean { return Mappings.isMotionPrefix(input); }
|
||||
|
||||
private _deleteRegister: DeleteRegister;
|
||||
public setDeleteRegister(register: DeleteRegister): void { this._deleteRegister = register; }
|
||||
public getDeleteRegister(): DeleteRegister { return this._deleteRegister; }
|
||||
|
||||
constructor() {
|
||||
this._motionState = new MotionState();
|
||||
this._deleteRegister = null;
|
||||
this.setVisual(false);
|
||||
this.setMode(Mode.NORMAL);
|
||||
}
|
||||
|
||||
public setWordSeparators(wordSeparators: string): void {
|
||||
this._motionState.wordCharacterClass = Words.createWordCharacters(wordSeparators);
|
||||
}
|
||||
|
||||
public ensureNormalModePosition(editor: TextEditor): void {
|
||||
if (this._currentMode !== Mode.NORMAL) {
|
||||
return;
|
||||
}
|
||||
if (this._isVisual) {
|
||||
return;
|
||||
}
|
||||
const sel = editor.selection;
|
||||
const pos = sel.active;
|
||||
const doc = editor.document;
|
||||
const lineContent = doc.lineAt(pos.line).text;
|
||||
if (lineContent.length === 0) {
|
||||
return;
|
||||
}
|
||||
const maxCharacter = lineContent.length - 1;
|
||||
if (pos.character > maxCharacter) {
|
||||
setPositionAndReveal(editor, pos.line, maxCharacter);
|
||||
}
|
||||
}
|
||||
|
||||
public hasInput(): boolean {
|
||||
return this._currentInput.length > 0;
|
||||
}
|
||||
|
||||
public clearInput(): void {
|
||||
this._currentInput = '';
|
||||
}
|
||||
|
||||
public getMode(): Mode {
|
||||
return this._currentMode;
|
||||
}
|
||||
|
||||
public setMode(newMode: Mode): void {
|
||||
if (newMode !== this._currentMode) {
|
||||
this._currentMode = newMode;
|
||||
this._motionState.cursorDesiredCharacter = -1; // uninitialized
|
||||
this._currentInput = '';
|
||||
}
|
||||
}
|
||||
|
||||
public setVisual(newVisual: boolean): void {
|
||||
if (this._isVisual !== newVisual) {
|
||||
this._isVisual = newVisual;
|
||||
}
|
||||
}
|
||||
|
||||
public getVisual(): boolean {
|
||||
return this._isVisual;
|
||||
}
|
||||
|
||||
public getCursorStyle(): TextEditorCursorStyle {
|
||||
if (this._currentMode === Mode.NORMAL) {
|
||||
if (/^([1-9]\d*)?(r|c)/.test(this._currentInput)) {
|
||||
return TextEditorCursorStyle.Underline;
|
||||
}
|
||||
return TextEditorCursorStyle.Block;
|
||||
}
|
||||
if (this._currentMode === Mode.REPLACE) {
|
||||
return TextEditorCursorStyle.Underline;
|
||||
}
|
||||
return TextEditorCursorStyle.Line;
|
||||
}
|
||||
|
||||
private _getModeLabel(): string {
|
||||
if (this._currentMode === Mode.NORMAL) {
|
||||
if (this._isVisual) {
|
||||
return '-- VISUAL --';
|
||||
}
|
||||
return '-- NORMAL --';
|
||||
}
|
||||
|
||||
if (this._currentMode === Mode.REPLACE) {
|
||||
if (this._isVisual) {
|
||||
return '-- (replace) VISUAL --';
|
||||
}
|
||||
return '-- REPLACE --';
|
||||
}
|
||||
|
||||
if (this._isVisual) {
|
||||
return '-- (insert) VISUAL --';
|
||||
}
|
||||
return '-- INSERT --';
|
||||
}
|
||||
|
||||
public getStatusText(): string {
|
||||
const label = this._getModeLabel();
|
||||
return `VIM:> ${label}` + (this._currentInput ? ` >${this._currentInput}` : ``);
|
||||
}
|
||||
|
||||
private _isInComposition = false;
|
||||
private _composingText = '';
|
||||
|
||||
public compositionStart(editor: TextEditor): void {
|
||||
this._isInComposition = true;
|
||||
this._composingText = '';
|
||||
}
|
||||
|
||||
public compositionEnd(editor: TextEditor): Thenable<ITypeResult> {
|
||||
this._isInComposition = false;
|
||||
const text = this._composingText;
|
||||
this._composingText = '';
|
||||
|
||||
if (text.length === 0) {
|
||||
return Promise.resolve({
|
||||
hasConsumedInput: true,
|
||||
executeEditorCommand: null
|
||||
});
|
||||
}
|
||||
|
||||
return this.type(editor, text, {});
|
||||
}
|
||||
|
||||
public type(editor: TextEditor, text: string, modifierKeys: ModifierKeys): Thenable<ITypeResult> {
|
||||
if (this._currentMode !== Mode.NORMAL && this._currentMode !== Mode.REPLACE) {
|
||||
return Promise.resolve({
|
||||
hasConsumedInput: false,
|
||||
executeEditorCommand: null
|
||||
});
|
||||
}
|
||||
|
||||
if (this._isInComposition) {
|
||||
this._composingText += text;
|
||||
return Promise.resolve({
|
||||
hasConsumedInput: true,
|
||||
executeEditorCommand: null
|
||||
});
|
||||
}
|
||||
|
||||
if (this._currentMode === Mode.REPLACE) {
|
||||
const pos = editor.selection.active;
|
||||
editor.edit((builder) => {
|
||||
builder.replace(new Range(pos.line, pos.character, pos.line, pos.character + 1), text);
|
||||
}).then(() => {
|
||||
setPositionAndReveal(editor, pos.line, pos.character + 1);
|
||||
});
|
||||
|
||||
return Promise.resolve({
|
||||
hasConsumedInput: true,
|
||||
executeEditorCommand: null
|
||||
});
|
||||
}
|
||||
this._currentInput += text;
|
||||
return this._interpretNormalModeInput(editor, modifierKeys);
|
||||
}
|
||||
|
||||
public replacePrevChar(editor: TextEditor, text: string, replaceCharCnt: number): boolean {
|
||||
if (this._currentMode !== Mode.NORMAL && this._currentMode !== Mode.REPLACE) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this._isInComposition) {
|
||||
this._composingText = this._composingText.substr(0, this._composingText.length - replaceCharCnt) + text;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (this._currentMode === Mode.REPLACE) {
|
||||
const pos = editor.selection.active;
|
||||
editor.edit((builder) => {
|
||||
builder.replace(new Range(pos.line, pos.character - replaceCharCnt, pos.line, pos.character), text);
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private _interpretNormalModeInput(editor: TextEditor, modifierKeys: ModifierKeys): Thenable<ITypeResult> {
|
||||
if (this._currentInput.startsWith(':')) {
|
||||
return window.showInputBox({ value: 'tabm' }).then((value) => {
|
||||
return this._findMapping(value || '', editor, modifierKeys);
|
||||
});
|
||||
}
|
||||
const result = this._findMapping(this._currentInput, editor, modifierKeys);
|
||||
return Promise.resolve(result);
|
||||
}
|
||||
|
||||
private _findMapping(input: string, editor: TextEditor, modifierKeys: ModifierKeys): ITypeResult {
|
||||
const command = Mappings.findCommand(input, modifierKeys);
|
||||
if (command) {
|
||||
this._currentInput = '';
|
||||
return {
|
||||
hasConsumedInput: true,
|
||||
executeEditorCommand: command
|
||||
};
|
||||
}
|
||||
|
||||
const operator = Mappings.findOperator(input, modifierKeys);
|
||||
if (operator) {
|
||||
if (this._isVisual) {
|
||||
if (operator.runVisual(this, editor)) {
|
||||
this._currentInput = '';
|
||||
}
|
||||
} else {
|
||||
// Mode.NORMAL
|
||||
if (operator.runNormal(this, editor)) {
|
||||
this._currentInput = '';
|
||||
}
|
||||
}
|
||||
return {
|
||||
hasConsumedInput: true,
|
||||
executeEditorCommand: null
|
||||
};
|
||||
}
|
||||
|
||||
const motionCommand = Mappings.findMotionCommand(input, this._isVisual, modifierKeys);
|
||||
if (motionCommand) {
|
||||
this._currentInput = '';
|
||||
return {
|
||||
hasConsumedInput: true,
|
||||
executeEditorCommand: motionCommand
|
||||
};
|
||||
}
|
||||
|
||||
const motion = Mappings.findMotion(input);
|
||||
if (motion) {
|
||||
const newPos = motion.run(editor.document, editor.selection.active, this._motionState);
|
||||
if (this._isVisual) {
|
||||
setSelectionAndReveal(editor, this._motionState.anchor, newPos.line, newPos.character);
|
||||
} else {
|
||||
// Mode.NORMAL
|
||||
setPositionAndReveal(editor, newPos.line, newPos.character);
|
||||
}
|
||||
this._currentInput = '';
|
||||
return {
|
||||
hasConsumedInput: true,
|
||||
executeEditorCommand: null
|
||||
};
|
||||
}
|
||||
|
||||
// is it motion building
|
||||
if (this.isMotionPrefix(input)) {
|
||||
return {
|
||||
hasConsumedInput: true,
|
||||
executeEditorCommand: null
|
||||
};
|
||||
}
|
||||
|
||||
// INVALID INPUT - beep!!
|
||||
this._currentInput = '';
|
||||
return {
|
||||
hasConsumedInput: true,
|
||||
executeEditorCommand: null
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
function setSelectionAndReveal(editor: TextEditor, anchor: Position, line: number, char: number): void {
|
||||
editor.selection = new Selection(anchor, new Position(line, char));
|
||||
revealPosition(editor, line, char);
|
||||
}
|
||||
|
||||
function setPositionAndReveal(editor: TextEditor, line: number, char: number): void {
|
||||
editor.selection = new Selection(new Position(line, char), new Position(line, char));
|
||||
revealPosition(editor, line, char);
|
||||
}
|
||||
|
||||
function revealPosition(editor: TextEditor, line: number, char: number): void {
|
||||
editor.revealRange(new Range(line, char, line, char), TextEditorRevealType.Default);
|
||||
}
|
||||
|
||||
@ -13,7 +13,7 @@ export function activate(context: vscode.ExtensionContext) {
|
||||
context.subscriptions.push(vscode.commands.registerCommand(commandId, run));
|
||||
}
|
||||
function registerCtrlKeyBinding(key: string): void {
|
||||
registerCommandNice(key, function (args) {
|
||||
registerCommandNice(key, function(args) {
|
||||
if (!vscode.window.activeTextEditor) {
|
||||
return;
|
||||
}
|
||||
@ -23,34 +23,34 @@ export function activate(context: vscode.ExtensionContext) {
|
||||
|
||||
const vimExt = new VimExt();
|
||||
|
||||
registerCommandNice('type', function (args) {
|
||||
registerCommandNice('type', function(args) {
|
||||
if (!vscode.window.activeTextEditor) {
|
||||
return;
|
||||
}
|
||||
vimExt.type(args.text);
|
||||
});
|
||||
registerCommandNice('replacePreviousChar', function (args) {
|
||||
registerCommandNice('replacePreviousChar', function(args) {
|
||||
if (!vscode.window.activeTextEditor) {
|
||||
return;
|
||||
}
|
||||
vimExt.replacePrevChar(args.text, args.replaceCharCnt);
|
||||
});
|
||||
registerCommandNice('compositionStart', function (args) {
|
||||
registerCommandNice('compositionStart', function(args) {
|
||||
if (!vscode.window.activeTextEditor) {
|
||||
return;
|
||||
}
|
||||
vimExt.compositionStart();
|
||||
});
|
||||
registerCommandNice('compositionEnd', function (args) {
|
||||
registerCommandNice('compositionEnd', function(args) {
|
||||
if (!vscode.window.activeTextEditor) {
|
||||
return;
|
||||
}
|
||||
vimExt.compositionEnd();
|
||||
});
|
||||
registerCommandNice('vim.goToNormalMode', function (args) {
|
||||
registerCommandNice('vim.goToNormalMode', function(args) {
|
||||
vimExt.goToNormalMode();
|
||||
});
|
||||
registerCommandNice('vim.clearInput', function (args) {
|
||||
registerCommandNice('vim.clearInput', function(args) {
|
||||
vimExt.clearInput();
|
||||
});
|
||||
// registerCommandNice('paste', function(args) {
|
||||
|
||||
@ -1,232 +1,232 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { TextEditor } from 'vscode';
|
||||
import { Motion, Motions } from './motions';
|
||||
import { Operator, Operators } from './operators';
|
||||
import { IController, Command, AbstractCommandDescriptor, ModifierKeys } from './common';
|
||||
|
||||
const CHAR_TO_BINDING: { [char: string]: any; } = {};
|
||||
function defineBinding(char: string, value: any, modifierKeys: ModifierKeys): void {
|
||||
const key = modifierKeys.ctrl ? 'CTRL + ' + char : char;
|
||||
CHAR_TO_BINDING[key] = value;
|
||||
}
|
||||
function getBinding(char: string, modifierKeys: ModifierKeys): any {
|
||||
const key = modifierKeys.ctrl ? 'CTRL + ' + char : char;
|
||||
return CHAR_TO_BINDING[key];
|
||||
}
|
||||
|
||||
function defineOperator(char: string, operator: Operator, modifierKeys: ModifierKeys = {}): void {
|
||||
defineBinding(char + '__operator__', operator, modifierKeys);
|
||||
}
|
||||
function getOperator(char: string, modifierKeys: ModifierKeys = {}): Operator {
|
||||
return getBinding(char + '__operator__', modifierKeys);
|
||||
}
|
||||
|
||||
function defineCommand(char: string, commandId: string, modifierKeys: ModifierKeys = {}): void {
|
||||
defineBinding(char + '__command__', { commandId: commandId }, modifierKeys);
|
||||
}
|
||||
function getCommand(char: string, modifierKeys: ModifierKeys = {}): Command {
|
||||
return getBinding(char + '__command__', modifierKeys);
|
||||
}
|
||||
|
||||
function defineMotion(char: string, motion: Motion, modifierKeys: ModifierKeys = {}): void {
|
||||
defineBinding(char + '__motion__', motion, modifierKeys);
|
||||
}
|
||||
function getMotion(char: string, modifierKeys: ModifierKeys = {}): Motion {
|
||||
return getBinding(char + '__motion__', modifierKeys);
|
||||
}
|
||||
|
||||
function defineMotionCommand(char: string, motionCommand: AbstractCommandDescriptor, modifierKeys: ModifierKeys = {}): void {
|
||||
defineBinding(char + '__motioncommand__', motionCommand, modifierKeys);
|
||||
}
|
||||
function getMotionCommand(char: string, modifierKeys: ModifierKeys = {}): AbstractCommandDescriptor {
|
||||
return getBinding(char + '__motioncommand__', modifierKeys);
|
||||
}
|
||||
|
||||
// Operators
|
||||
defineOperator('x', Operators.DeleteCharUnderCursor);
|
||||
defineOperator('i', Operators.Insert);
|
||||
defineOperator('a', Operators.Append);
|
||||
defineOperator('A', Operators.AppendEndOfLine);
|
||||
defineOperator('d', Operators.DeleteTo);
|
||||
defineOperator('p', Operators.Put);
|
||||
defineOperator('r', Operators.Replace);
|
||||
defineOperator('R', Operators.ReplaceMode);
|
||||
defineOperator('c', Operators.Change);
|
||||
defineOperator('v', Operators.Visual);
|
||||
|
||||
// Commands
|
||||
defineCommand('u', 'undo');
|
||||
defineCommand('U', 'undo');
|
||||
|
||||
// Left-right motions
|
||||
defineMotionCommand('h', Motions.Left);
|
||||
defineMotionCommand('l', Motions.Right);
|
||||
defineMotion('0', Motions.StartOfLine);
|
||||
defineMotion('$', Motions.EndOfLine);
|
||||
defineMotionCommand('g0', Motions.WrappedLineStart);
|
||||
defineMotionCommand('g^', Motions.WrappedLineFirstNonWhiteSpaceCharacter);
|
||||
defineMotionCommand('gm', Motions.WrappedLineColumnCenter);
|
||||
defineMotionCommand('g$', Motions.WrappedLineEnd);
|
||||
defineMotionCommand('g_', Motions.WrappedLineLastNonWhiteSpaceCharacter);
|
||||
|
||||
// Cursor scroll motions
|
||||
defineMotionCommand('zh', Motions.CursorScrollLeft);
|
||||
defineMotionCommand('zl', Motions.CursorScrollRight);
|
||||
defineMotionCommand('zH', Motions.CursorScrollLeftByHalfLine);
|
||||
defineMotionCommand('zL', Motions.CursorScrollRightByHalfLine);
|
||||
|
||||
// Up-down motions
|
||||
defineMotionCommand('j', Motions.Down);
|
||||
defineMotionCommand('k', Motions.Up);
|
||||
defineMotionCommand('gj', Motions.WrappedLineDown);
|
||||
defineMotionCommand('gk', Motions.WrappedLineUp);
|
||||
defineMotion('G', Motions.GoToLine);
|
||||
defineMotion('gg', Motions.GoToFirstLine);
|
||||
|
||||
defineMotionCommand('H', Motions.ViewPortTop);
|
||||
defineMotionCommand('M', Motions.ViewPortCenter);
|
||||
defineMotionCommand('L', Motions.ViewPortBottom);
|
||||
|
||||
// Word motions
|
||||
defineMotion('w', Motions.NextWordStart);
|
||||
defineMotion('e', Motions.NextWordEnd);
|
||||
|
||||
// Tab motions
|
||||
defineMotionCommand('tabm', Motions.MoveActiveEditor);
|
||||
defineMotionCommand('tabm<', Motions.MoveActiveEditorLeft);
|
||||
defineMotionCommand('tabm>', Motions.MoveActiveEditorRight);
|
||||
defineMotionCommand('tabm<<', Motions.MoveActiveEditorFirst);
|
||||
defineMotionCommand('tabm>>', Motions.MoveActiveEditorLast);
|
||||
defineMotionCommand('tabm.', Motions.MoveActiveEditorCenter);
|
||||
|
||||
// Scroll motions
|
||||
defineMotionCommand('e', Motions.ScrollDownByLine, { ctrl: true });
|
||||
defineMotionCommand('d', Motions.ScrollDownByHalfPage, { ctrl: true });
|
||||
defineMotionCommand('f', Motions.ScrollDownByPage, { ctrl: true });
|
||||
defineMotionCommand('y', Motions.ScrollUpByLine, { ctrl: true });
|
||||
defineMotionCommand('u', Motions.ScrollUpByHalfPage, { ctrl: true });
|
||||
defineMotionCommand('b', Motions.ScrollUpByPage, { ctrl: true });
|
||||
|
||||
defineMotionCommand('zt', Motions.RevealCurrentLineAtTop);
|
||||
defineMotionCommand('zz', Motions.RevealCurrentLineAtCenter);
|
||||
defineMotionCommand('zb', Motions.RevealCurrentLineAtBottom);
|
||||
|
||||
// Folding
|
||||
defineMotionCommand('zc', Motions.FoldUnder);
|
||||
defineMotionCommand('zo', Motions.UnfoldUnder);
|
||||
|
||||
|
||||
export interface IFoundOperator {
|
||||
runNormal(controller: IController, editor: TextEditor): boolean;
|
||||
runVisual(controller: IController, editor: TextEditor): boolean;
|
||||
}
|
||||
|
||||
export class Mappings {
|
||||
|
||||
public static findMotion(input: string): Motion {
|
||||
const parsed = _parseNumberAndString(input);
|
||||
let motion = getMotion(parsed.input.substr(0, 1));
|
||||
if (!motion) {
|
||||
motion = getMotion(parsed.input.substr(0, 2));
|
||||
if (!motion) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return motion.repeat(parsed.hasRepeatCount, parsed.repeatCount);
|
||||
}
|
||||
|
||||
public static findMotionCommand(input: string, isVisual: boolean, modifierKeys: ModifierKeys): Command {
|
||||
let parsed = _parseNumberAndString(input);
|
||||
let command = Mappings.findMotionCommandFromNumberAndString(parsed, isVisual, modifierKeys);
|
||||
if (!command) {
|
||||
parsed = _parseNumberAndString(input, false);
|
||||
command = Mappings.findMotionCommandFromNumberAndString(parsed, isVisual, modifierKeys);
|
||||
}
|
||||
return command;
|
||||
}
|
||||
|
||||
private static findMotionCommandFromNumberAndString(numberAndString: INumberAndString, isVisual: boolean, modifierKeys: ModifierKeys): Command {
|
||||
let motionCommand = getMotionCommand(numberAndString.input.substr(0, 1), modifierKeys);
|
||||
if (!motionCommand) {
|
||||
motionCommand = getMotionCommand(numberAndString.input.substr(0, 2), modifierKeys);
|
||||
}
|
||||
if (!motionCommand) {
|
||||
motionCommand = getMotionCommand(numberAndString.input.substr(1, 2), modifierKeys);
|
||||
}
|
||||
if (!motionCommand) {
|
||||
motionCommand = getMotionCommand(numberAndString.input.substr(1, 3), modifierKeys);
|
||||
}
|
||||
if (!motionCommand) {
|
||||
motionCommand = getMotionCommand(numberAndString.input, modifierKeys);
|
||||
}
|
||||
return motionCommand ? motionCommand.createCommand({ isVisual: isVisual, repeat: numberAndString.hasRepeatCount ? numberAndString.repeatCount : undefined }) : null;
|
||||
}
|
||||
|
||||
public static findOperator(input: string, modifierKeys: ModifierKeys): IFoundOperator {
|
||||
const parsed = _parseNumberAndString(input);
|
||||
const operator = getOperator(parsed.input.substr(0, 1), modifierKeys);
|
||||
if (!operator) {
|
||||
return null;
|
||||
}
|
||||
const operatorArgs = parsed.input.substr(1);
|
||||
return {
|
||||
runNormal: (controller: IController, editor: TextEditor) => {
|
||||
return operator.runNormalMode(controller, editor, parsed.repeatCount, operatorArgs);
|
||||
},
|
||||
runVisual: (controller: IController, editor: TextEditor) => {
|
||||
return operator.runVisualMode(controller, editor, operatorArgs);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static findCommand(input: string, modifierKeys: ModifierKeys): Command {
|
||||
return getCommand(input, modifierKeys) || null;
|
||||
}
|
||||
|
||||
public static isMotionPrefix(input: string): boolean {
|
||||
if (input.length === 0) {
|
||||
return true;
|
||||
}
|
||||
if (input === 'g' || input === 'v' || input === 'z') {
|
||||
return true;
|
||||
}
|
||||
return /^[1-9]\d*v?g?z?$/.test(input);
|
||||
}
|
||||
}
|
||||
|
||||
function _parseNumberAndString(input: string, numberAtBeginning = true): INumberAndString {
|
||||
if (numberAtBeginning) {
|
||||
const repeatCountMatch = input.match(/^([1-9]\d*)/);
|
||||
if (repeatCountMatch) {
|
||||
return {
|
||||
hasRepeatCount: true,
|
||||
repeatCount: parseInt(repeatCountMatch[0], 10),
|
||||
input: input.substr(repeatCountMatch[0].length)
|
||||
};
|
||||
}
|
||||
} else {
|
||||
const repeatCountMatch = input.match(/(\d+)$/);
|
||||
if (repeatCountMatch) {
|
||||
return {
|
||||
hasRepeatCount: true,
|
||||
repeatCount: parseInt(repeatCountMatch[1], 10),
|
||||
input: input.substr(0, input.length - repeatCountMatch[1].length)
|
||||
};
|
||||
}
|
||||
}
|
||||
return {
|
||||
hasRepeatCount: false,
|
||||
repeatCount: 1,
|
||||
input: input
|
||||
};
|
||||
}
|
||||
|
||||
interface INumberAndString {
|
||||
hasRepeatCount: boolean;
|
||||
repeatCount: number;
|
||||
input: string;
|
||||
}
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { TextEditor } from 'vscode';
|
||||
import { Motion, Motions } from './motions';
|
||||
import { Operator, Operators } from './operators';
|
||||
import { IController, Command, AbstractCommandDescriptor, ModifierKeys } from './common';
|
||||
|
||||
const CHAR_TO_BINDING: { [char: string]: any; } = {};
|
||||
function defineBinding(char: string, value: any, modifierKeys: ModifierKeys): void {
|
||||
const key = modifierKeys.ctrl ? 'CTRL + ' + char : char;
|
||||
CHAR_TO_BINDING[key] = value;
|
||||
}
|
||||
function getBinding(char: string, modifierKeys: ModifierKeys): any {
|
||||
const key = modifierKeys.ctrl ? 'CTRL + ' + char : char;
|
||||
return CHAR_TO_BINDING[key];
|
||||
}
|
||||
|
||||
function defineOperator(char: string, operator: Operator, modifierKeys: ModifierKeys = {}): void {
|
||||
defineBinding(char + '__operator__', operator, modifierKeys);
|
||||
}
|
||||
function getOperator(char: string, modifierKeys: ModifierKeys = {}): Operator {
|
||||
return getBinding(char + '__operator__', modifierKeys);
|
||||
}
|
||||
|
||||
function defineCommand(char: string, commandId: string, modifierKeys: ModifierKeys = {}): void {
|
||||
defineBinding(char + '__command__', { commandId: commandId }, modifierKeys);
|
||||
}
|
||||
function getCommand(char: string, modifierKeys: ModifierKeys = {}): Command {
|
||||
return getBinding(char + '__command__', modifierKeys);
|
||||
}
|
||||
|
||||
function defineMotion(char: string, motion: Motion, modifierKeys: ModifierKeys = {}): void {
|
||||
defineBinding(char + '__motion__', motion, modifierKeys);
|
||||
}
|
||||
function getMotion(char: string, modifierKeys: ModifierKeys = {}): Motion {
|
||||
return getBinding(char + '__motion__', modifierKeys);
|
||||
}
|
||||
|
||||
function defineMotionCommand(char: string, motionCommand: AbstractCommandDescriptor, modifierKeys: ModifierKeys = {}): void {
|
||||
defineBinding(char + '__motioncommand__', motionCommand, modifierKeys);
|
||||
}
|
||||
function getMotionCommand(char: string, modifierKeys: ModifierKeys = {}): AbstractCommandDescriptor {
|
||||
return getBinding(char + '__motioncommand__', modifierKeys);
|
||||
}
|
||||
|
||||
// Operators
|
||||
defineOperator('x', Operators.DeleteCharUnderCursor);
|
||||
defineOperator('i', Operators.Insert);
|
||||
defineOperator('a', Operators.Append);
|
||||
defineOperator('A', Operators.AppendEndOfLine);
|
||||
defineOperator('d', Operators.DeleteTo);
|
||||
defineOperator('p', Operators.Put);
|
||||
defineOperator('r', Operators.Replace);
|
||||
defineOperator('R', Operators.ReplaceMode);
|
||||
defineOperator('c', Operators.Change);
|
||||
defineOperator('v', Operators.Visual);
|
||||
|
||||
// Commands
|
||||
defineCommand('u', 'undo');
|
||||
defineCommand('U', 'undo');
|
||||
|
||||
// Left-right motions
|
||||
defineMotionCommand('h', Motions.Left);
|
||||
defineMotionCommand('l', Motions.Right);
|
||||
defineMotion('0', Motions.StartOfLine);
|
||||
defineMotion('$', Motions.EndOfLine);
|
||||
defineMotionCommand('g0', Motions.WrappedLineStart);
|
||||
defineMotionCommand('g^', Motions.WrappedLineFirstNonWhiteSpaceCharacter);
|
||||
defineMotionCommand('gm', Motions.WrappedLineColumnCenter);
|
||||
defineMotionCommand('g$', Motions.WrappedLineEnd);
|
||||
defineMotionCommand('g_', Motions.WrappedLineLastNonWhiteSpaceCharacter);
|
||||
|
||||
// Cursor scroll motions
|
||||
defineMotionCommand('zh', Motions.CursorScrollLeft);
|
||||
defineMotionCommand('zl', Motions.CursorScrollRight);
|
||||
defineMotionCommand('zH', Motions.CursorScrollLeftByHalfLine);
|
||||
defineMotionCommand('zL', Motions.CursorScrollRightByHalfLine);
|
||||
|
||||
// Up-down motions
|
||||
defineMotionCommand('j', Motions.Down);
|
||||
defineMotionCommand('k', Motions.Up);
|
||||
defineMotionCommand('gj', Motions.WrappedLineDown);
|
||||
defineMotionCommand('gk', Motions.WrappedLineUp);
|
||||
defineMotion('G', Motions.GoToLine);
|
||||
defineMotion('gg', Motions.GoToFirstLine);
|
||||
|
||||
defineMotionCommand('H', Motions.ViewPortTop);
|
||||
defineMotionCommand('M', Motions.ViewPortCenter);
|
||||
defineMotionCommand('L', Motions.ViewPortBottom);
|
||||
|
||||
// Word motions
|
||||
defineMotion('w', Motions.NextWordStart);
|
||||
defineMotion('e', Motions.NextWordEnd);
|
||||
|
||||
// Tab motions
|
||||
defineMotionCommand('tabm', Motions.MoveActiveEditor);
|
||||
defineMotionCommand('tabm<', Motions.MoveActiveEditorLeft);
|
||||
defineMotionCommand('tabm>', Motions.MoveActiveEditorRight);
|
||||
defineMotionCommand('tabm<<', Motions.MoveActiveEditorFirst);
|
||||
defineMotionCommand('tabm>>', Motions.MoveActiveEditorLast);
|
||||
defineMotionCommand('tabm.', Motions.MoveActiveEditorCenter);
|
||||
|
||||
// Scroll motions
|
||||
defineMotionCommand('e', Motions.ScrollDownByLine, { ctrl: true });
|
||||
defineMotionCommand('d', Motions.ScrollDownByHalfPage, { ctrl: true });
|
||||
defineMotionCommand('f', Motions.ScrollDownByPage, { ctrl: true });
|
||||
defineMotionCommand('y', Motions.ScrollUpByLine, { ctrl: true });
|
||||
defineMotionCommand('u', Motions.ScrollUpByHalfPage, { ctrl: true });
|
||||
defineMotionCommand('b', Motions.ScrollUpByPage, { ctrl: true });
|
||||
|
||||
defineMotionCommand('zt', Motions.RevealCurrentLineAtTop);
|
||||
defineMotionCommand('zz', Motions.RevealCurrentLineAtCenter);
|
||||
defineMotionCommand('zb', Motions.RevealCurrentLineAtBottom);
|
||||
|
||||
// Folding
|
||||
defineMotionCommand('zc', Motions.FoldUnder);
|
||||
defineMotionCommand('zo', Motions.UnfoldUnder);
|
||||
|
||||
|
||||
export interface IFoundOperator {
|
||||
runNormal(controller: IController, editor: TextEditor): boolean;
|
||||
runVisual(controller: IController, editor: TextEditor): boolean;
|
||||
}
|
||||
|
||||
export class Mappings {
|
||||
|
||||
public static findMotion(input: string): Motion {
|
||||
const parsed = _parseNumberAndString(input);
|
||||
let motion = getMotion(parsed.input.substr(0, 1));
|
||||
if (!motion) {
|
||||
motion = getMotion(parsed.input.substr(0, 2));
|
||||
if (!motion) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return motion.repeat(parsed.hasRepeatCount, parsed.repeatCount);
|
||||
}
|
||||
|
||||
public static findMotionCommand(input: string, isVisual: boolean, modifierKeys: ModifierKeys): Command {
|
||||
let parsed = _parseNumberAndString(input);
|
||||
let command = Mappings.findMotionCommandFromNumberAndString(parsed, isVisual, modifierKeys);
|
||||
if (!command) {
|
||||
parsed = _parseNumberAndString(input, false);
|
||||
command = Mappings.findMotionCommandFromNumberAndString(parsed, isVisual, modifierKeys);
|
||||
}
|
||||
return command;
|
||||
}
|
||||
|
||||
private static findMotionCommandFromNumberAndString(numberAndString: INumberAndString, isVisual: boolean, modifierKeys: ModifierKeys): Command {
|
||||
let motionCommand = getMotionCommand(numberAndString.input.substr(0, 1), modifierKeys);
|
||||
if (!motionCommand) {
|
||||
motionCommand = getMotionCommand(numberAndString.input.substr(0, 2), modifierKeys);
|
||||
}
|
||||
if (!motionCommand) {
|
||||
motionCommand = getMotionCommand(numberAndString.input.substr(1, 2), modifierKeys);
|
||||
}
|
||||
if (!motionCommand) {
|
||||
motionCommand = getMotionCommand(numberAndString.input.substr(1, 3), modifierKeys);
|
||||
}
|
||||
if (!motionCommand) {
|
||||
motionCommand = getMotionCommand(numberAndString.input, modifierKeys);
|
||||
}
|
||||
return motionCommand ? motionCommand.createCommand({ isVisual: isVisual, repeat: numberAndString.hasRepeatCount ? numberAndString.repeatCount : undefined }) : null;
|
||||
}
|
||||
|
||||
public static findOperator(input: string, modifierKeys: ModifierKeys): IFoundOperator {
|
||||
const parsed = _parseNumberAndString(input);
|
||||
const operator = getOperator(parsed.input.substr(0, 1), modifierKeys);
|
||||
if (!operator) {
|
||||
return null;
|
||||
}
|
||||
const operatorArgs = parsed.input.substr(1);
|
||||
return {
|
||||
runNormal: (controller: IController, editor: TextEditor) => {
|
||||
return operator.runNormalMode(controller, editor, parsed.repeatCount, operatorArgs);
|
||||
},
|
||||
runVisual: (controller: IController, editor: TextEditor) => {
|
||||
return operator.runVisualMode(controller, editor, operatorArgs);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static findCommand(input: string, modifierKeys: ModifierKeys): Command {
|
||||
return getCommand(input, modifierKeys) || null;
|
||||
}
|
||||
|
||||
public static isMotionPrefix(input: string): boolean {
|
||||
if (input.length === 0) {
|
||||
return true;
|
||||
}
|
||||
if (input === 'g' || input === 'v' || input === 'z') {
|
||||
return true;
|
||||
}
|
||||
return /^[1-9]\d*v?g?z?$/.test(input);
|
||||
}
|
||||
}
|
||||
|
||||
function _parseNumberAndString(input: string, numberAtBeginning = true): INumberAndString {
|
||||
if (numberAtBeginning) {
|
||||
const repeatCountMatch = input.match(/^([1-9]\d*)/);
|
||||
if (repeatCountMatch) {
|
||||
return {
|
||||
hasRepeatCount: true,
|
||||
repeatCount: parseInt(repeatCountMatch[0], 10),
|
||||
input: input.substr(repeatCountMatch[0].length)
|
||||
};
|
||||
}
|
||||
} else {
|
||||
const repeatCountMatch = input.match(/(\d+)$/);
|
||||
if (repeatCountMatch) {
|
||||
return {
|
||||
hasRepeatCount: true,
|
||||
repeatCount: parseInt(repeatCountMatch[1], 10),
|
||||
input: input.substr(0, input.length - repeatCountMatch[1].length)
|
||||
};
|
||||
}
|
||||
}
|
||||
return {
|
||||
hasRepeatCount: false,
|
||||
repeatCount: 1,
|
||||
input: input
|
||||
};
|
||||
}
|
||||
|
||||
interface INumberAndString {
|
||||
hasRepeatCount: boolean;
|
||||
repeatCount: number;
|
||||
input: string;
|
||||
}
|
||||
|
||||
@ -1,432 +1,432 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Position, TextDocument, window } from 'vscode';
|
||||
import { Words, WordCharacters } from './words';
|
||||
import { Command, AbstractCommandDescriptor } from './common';
|
||||
|
||||
export class MotionState {
|
||||
|
||||
public anchor: Position | null;
|
||||
public cursorDesiredCharacter: number;
|
||||
public wordCharacterClass: WordCharacters | null;
|
||||
|
||||
constructor() {
|
||||
this.cursorDesiredCharacter = -1;
|
||||
this.wordCharacterClass = null;
|
||||
this.anchor = null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export abstract class Motion {
|
||||
public abstract run(doc: TextDocument, pos: Position, state: MotionState): Position;
|
||||
|
||||
public repeat(hasRepeatCount: boolean, count: number): Motion {
|
||||
if (!hasRepeatCount) {
|
||||
return this;
|
||||
}
|
||||
return new RepeatingMotion(this, count);
|
||||
}
|
||||
}
|
||||
|
||||
class RepeatingMotion extends Motion {
|
||||
|
||||
private _actual: Motion;
|
||||
private _repeatCount: number;
|
||||
|
||||
constructor(actual: Motion, repeatCount: number) {
|
||||
super();
|
||||
this._actual = actual;
|
||||
this._repeatCount = repeatCount;
|
||||
}
|
||||
|
||||
public run(doc: TextDocument, pos: Position, state: MotionState): Position {
|
||||
for (let cnt = 0; cnt < this._repeatCount; cnt++) {
|
||||
pos = this._actual.run(doc, pos, state);
|
||||
}
|
||||
return pos;
|
||||
}
|
||||
}
|
||||
|
||||
class NextCharacterMotion extends Motion {
|
||||
public run(doc: TextDocument, pos: Position, state: MotionState): Position {
|
||||
if (pos.character === doc.lineAt(pos.line).text.length) {
|
||||
// on last character
|
||||
return ((pos.line + 1 < doc.lineCount) ? new Position(pos.line + 1, 0) : pos);
|
||||
}
|
||||
|
||||
return new Position(pos.line, pos.character + 1);
|
||||
}
|
||||
}
|
||||
|
||||
class LeftMotion extends Motion {
|
||||
public run(doc: TextDocument, pos: Position, state: MotionState): Position {
|
||||
const line = pos.line;
|
||||
|
||||
if (pos.character > 0) {
|
||||
state.cursorDesiredCharacter = pos.character - 1;
|
||||
return new Position(line, state.cursorDesiredCharacter);
|
||||
}
|
||||
|
||||
return pos;
|
||||
}
|
||||
}
|
||||
|
||||
class DownMotion extends Motion {
|
||||
public run(doc: TextDocument, pos: Position, state: MotionState): Position {
|
||||
let line = pos.line;
|
||||
|
||||
state.cursorDesiredCharacter = (state.cursorDesiredCharacter === -1 ? pos.character : state.cursorDesiredCharacter);
|
||||
|
||||
if (line < doc.lineCount - 1) {
|
||||
line++;
|
||||
return new Position(line, Math.min(state.cursorDesiredCharacter, doc.lineAt(line).text.length));
|
||||
}
|
||||
|
||||
return pos;
|
||||
}
|
||||
}
|
||||
|
||||
class UpMotion extends Motion {
|
||||
public run(doc: TextDocument, pos: Position, state: MotionState): Position {
|
||||
let line = pos.line;
|
||||
|
||||
state.cursorDesiredCharacter = (state.cursorDesiredCharacter === -1 ? pos.character : state.cursorDesiredCharacter);
|
||||
|
||||
if (line > 0) {
|
||||
line--;
|
||||
return new Position(line, Math.min(state.cursorDesiredCharacter, doc.lineAt(line).text.length));
|
||||
}
|
||||
|
||||
return pos;
|
||||
}
|
||||
}
|
||||
|
||||
class RightMotion extends Motion {
|
||||
public run(doc: TextDocument, pos: Position, state: MotionState): Position {
|
||||
const line = pos.line;
|
||||
const maxCharacter = doc.lineAt(line).text.length;
|
||||
|
||||
if (pos.character < maxCharacter) {
|
||||
state.cursorDesiredCharacter = pos.character + 1;
|
||||
return new Position(line, state.cursorDesiredCharacter);
|
||||
}
|
||||
|
||||
return pos;
|
||||
}
|
||||
}
|
||||
|
||||
class EndOfLineMotion extends Motion {
|
||||
public run(doc: TextDocument, pos: Position, state: MotionState): Position {
|
||||
return new Position(pos.line, doc.lineAt(pos.line).text.length);
|
||||
}
|
||||
}
|
||||
|
||||
class StartOfLineMotion extends Motion {
|
||||
public run(doc: TextDocument, pos: Position, state: MotionState): Position {
|
||||
return new Position(pos.line, 0);
|
||||
}
|
||||
}
|
||||
|
||||
class NextWordStartMotion extends Motion {
|
||||
public run(doc: TextDocument, pos: Position, state: MotionState): Position {
|
||||
const lineContent = doc.lineAt(pos.line).text;
|
||||
|
||||
if (pos.character >= lineContent.length - 1) {
|
||||
// cursor at end of line
|
||||
return ((pos.line + 1 < doc.lineCount) ? new Position(pos.line + 1, 0) : pos);
|
||||
}
|
||||
|
||||
const nextWord = Words.findNextWord(doc, pos, state.wordCharacterClass);
|
||||
|
||||
if (!nextWord) {
|
||||
// return end of the line
|
||||
return Motions.EndOfLine.run(doc, pos, state);
|
||||
}
|
||||
|
||||
if (nextWord.start <= pos.character && pos.character < nextWord.end) {
|
||||
// Sitting on a word
|
||||
const nextNextWord = Words.findNextWord(doc, new Position(pos.line, nextWord.end), state.wordCharacterClass);
|
||||
if (nextNextWord) {
|
||||
// return start of the next next word
|
||||
return new Position(pos.line, nextNextWord.start);
|
||||
} else {
|
||||
// return end of line
|
||||
return Motions.EndOfLine.run(doc, pos, state);
|
||||
}
|
||||
} else {
|
||||
// return start of the next word
|
||||
return new Position(pos.line, nextWord.start);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class NextWordEndMotion extends Motion {
|
||||
public run(doc: TextDocument, pos: Position, state: MotionState): Position {
|
||||
const lineContent = doc.lineAt(pos.line).text;
|
||||
|
||||
if (pos.character >= lineContent.length - 1) {
|
||||
// no content on this line or cursor at end of line
|
||||
return ((pos.line + 1 < doc.lineCount) ? new Position(pos.line + 1, 0) : pos);
|
||||
}
|
||||
|
||||
const nextWord = Words.findNextWord(doc, pos, state.wordCharacterClass);
|
||||
|
||||
if (!nextWord) {
|
||||
// return end of the line
|
||||
return Motions.EndOfLine.run(doc, pos, state);
|
||||
}
|
||||
|
||||
// return start of the next word
|
||||
return new Position(pos.line, nextWord.end);
|
||||
}
|
||||
}
|
||||
|
||||
class GoToLineUndefinedMotion extends Motion {
|
||||
public run(doc: TextDocument, pos: Position, state: MotionState): Position {
|
||||
// does not do anything
|
||||
return pos;
|
||||
}
|
||||
|
||||
public repeat(hasRepeatCount: boolean, count: number): Motion {
|
||||
if (!hasRepeatCount) {
|
||||
return Motions.GoToLastLine;
|
||||
}
|
||||
return new GoToLineDefinedMotion(count);
|
||||
}
|
||||
}
|
||||
|
||||
abstract class GoToLineMotion extends Motion {
|
||||
|
||||
protected firstNonWhitespaceChar(doc: TextDocument, line: number): number {
|
||||
const lineContent = doc.lineAt(line).text;
|
||||
let character = 0;
|
||||
while (character < lineContent.length) {
|
||||
const ch = lineContent.charAt(character);
|
||||
if (ch !== ' ' && ch !== '\t') {
|
||||
break;
|
||||
}
|
||||
character++;
|
||||
}
|
||||
return character;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class GoToFirstLineMotion extends GoToLineMotion {
|
||||
public run(doc: TextDocument, pos: Position, state: MotionState): Position {
|
||||
return new Position(0, this.firstNonWhitespaceChar(doc, 0));
|
||||
}
|
||||
}
|
||||
|
||||
class GoToLastLineMotion extends GoToLineMotion {
|
||||
public run(doc: TextDocument, pos: Position, state: MotionState): Position {
|
||||
const lastLine = doc.lineCount - 1;
|
||||
return new Position(lastLine, this.firstNonWhitespaceChar(doc, lastLine));
|
||||
}
|
||||
}
|
||||
|
||||
class GoToLineDefinedMotion extends GoToLineMotion {
|
||||
private _lineNumber: number;
|
||||
|
||||
constructor(lineNumber: number) {
|
||||
super();
|
||||
this._lineNumber = lineNumber;
|
||||
}
|
||||
|
||||
public run(doc: TextDocument, pos: Position, state: MotionState): Position {
|
||||
const line = Math.min(doc.lineCount - 1, Math.max(0, this._lineNumber - 1));
|
||||
return new Position(line, this.firstNonWhitespaceChar(doc, line));
|
||||
}
|
||||
}
|
||||
|
||||
class CursorMoveCommand extends AbstractCommandDescriptor {
|
||||
|
||||
constructor(private to: string, private by?: string) {
|
||||
super();
|
||||
}
|
||||
|
||||
public createCommand(args?: any): Command {
|
||||
const cursorMoveArgs: any = {
|
||||
to: this.to,
|
||||
by: this.by,
|
||||
value: args.repeat || 1,
|
||||
select: !!args.isVisual
|
||||
};
|
||||
return {
|
||||
commandId: 'cursorMove',
|
||||
args: cursorMoveArgs
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
class EditorScrollCommand extends AbstractCommandDescriptor {
|
||||
|
||||
constructor(private to: string, private by?: string) {
|
||||
super();
|
||||
}
|
||||
|
||||
public createCommand(args?: any): Command {
|
||||
const editorScrollArgs: any = {
|
||||
to: this.to,
|
||||
by: this.by,
|
||||
value: args.repeat || 1,
|
||||
revealCursor: true
|
||||
};
|
||||
return {
|
||||
commandId: 'editorScroll',
|
||||
args: editorScrollArgs
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
class RevealCurrentLineCommand extends AbstractCommandDescriptor {
|
||||
|
||||
constructor(private at: string) {
|
||||
super();
|
||||
}
|
||||
|
||||
public createCommand(args?: any): Command {
|
||||
const lineNumber = window.activeTextEditor.selection.start.line;
|
||||
const revealLineArgs: any = {
|
||||
lineNumber,
|
||||
at: this.at
|
||||
};
|
||||
return {
|
||||
commandId: 'revealLine',
|
||||
args: revealLineArgs
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
class MoveActiveEditorCommandByPosition extends AbstractCommandDescriptor {
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
|
||||
public createCommand(args?: any): Command {
|
||||
const moveActiveEditorArgs: any = {
|
||||
to: args.repeat === void 0 ? 'last' : 'position',
|
||||
value: args.repeat !== void 0 ? args.repeat + 1 : undefined
|
||||
};
|
||||
return {
|
||||
commandId: 'moveActiveEditor',
|
||||
args: moveActiveEditorArgs
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
class MoveActiveEditorCommand extends AbstractCommandDescriptor {
|
||||
|
||||
constructor(private to: string) {
|
||||
super();
|
||||
}
|
||||
|
||||
public createCommand(args?: any): Command {
|
||||
const moveActiveEditorArgs: any = {
|
||||
to: this.to,
|
||||
value: args.repeat ? args.repeat : 1
|
||||
};
|
||||
return {
|
||||
commandId: 'moveActiveEditor',
|
||||
args: moveActiveEditorArgs
|
||||
};
|
||||
}
|
||||
}
|
||||
class FoldCommand extends AbstractCommandDescriptor {
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
|
||||
public createCommand(args?: any): Command {
|
||||
const foldEditorArgs: any = {
|
||||
levels: args.repeat ? args.repeat : 1,
|
||||
direction: 'up'
|
||||
};
|
||||
return {
|
||||
commandId: 'editor.fold',
|
||||
args: foldEditorArgs
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
class UnfoldCommand extends AbstractCommandDescriptor {
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
|
||||
public createCommand(args?: any): Command {
|
||||
const foldEditorArgs: any = {
|
||||
levels: args.repeat ? args.repeat : 1,
|
||||
direction: 'up'
|
||||
};
|
||||
return {
|
||||
commandId: 'editor.unfold',
|
||||
args: foldEditorArgs
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export const Motions = {
|
||||
RightMotion: new RightMotion(),
|
||||
|
||||
NextCharacter: new NextCharacterMotion(),
|
||||
|
||||
Left: new CursorMoveCommand('left'),
|
||||
Right: new CursorMoveCommand('right'),
|
||||
Down: new CursorMoveCommand('down'),
|
||||
Up: new CursorMoveCommand('up'),
|
||||
|
||||
EndOfLine: new EndOfLineMotion(),
|
||||
StartOfLine: new StartOfLineMotion(),
|
||||
NextWordStart: new NextWordStartMotion(),
|
||||
NextWordEnd: new NextWordEndMotion(),
|
||||
GoToLine: new GoToLineUndefinedMotion(),
|
||||
GoToFirstLine: new GoToFirstLineMotion(),
|
||||
GoToLastLine: new GoToLastLineMotion(),
|
||||
|
||||
CursorScrollLeft: new CursorMoveCommand('left'),
|
||||
CursorScrollRight: new CursorMoveCommand('right'),
|
||||
CursorScrollLeftByHalfLine: new CursorMoveCommand('left', 'halfLine'),
|
||||
CursorScrollRightByHalfLine: new CursorMoveCommand('right', 'halfLine'),
|
||||
|
||||
WrappedLineUp: new CursorMoveCommand('up', 'wrappedLine'),
|
||||
WrappedLineDown: new CursorMoveCommand('down', 'wrappedLine'),
|
||||
|
||||
WrappedLineStart: new CursorMoveCommand('wrappedLineStart'),
|
||||
WrappedLineFirstNonWhiteSpaceCharacter: new CursorMoveCommand('wrappedLineFirstNonWhitespaceCharacter'),
|
||||
WrappedLineColumnCenter: new CursorMoveCommand('wrappedLineColumnCenter'),
|
||||
WrappedLineEnd: new CursorMoveCommand('wrappedLineEnd'),
|
||||
WrappedLineLastNonWhiteSpaceCharacter: new CursorMoveCommand('wrappedLineLastNonWhitespaceCharacter'),
|
||||
|
||||
ViewPortTop: new CursorMoveCommand('viewPortTop'),
|
||||
ViewPortBottom: new CursorMoveCommand('viewPortBottom'),
|
||||
ViewPortCenter: new CursorMoveCommand('viewPortCenter'),
|
||||
|
||||
MoveActiveEditor: new MoveActiveEditorCommandByPosition(),
|
||||
MoveActiveEditorLeft: new MoveActiveEditorCommand('left'),
|
||||
MoveActiveEditorRight: new MoveActiveEditorCommand('right'),
|
||||
MoveActiveEditorFirst: new MoveActiveEditorCommand('first'),
|
||||
MoveActiveEditorLast: new MoveActiveEditorCommand('last'),
|
||||
MoveActiveEditorCenter: new MoveActiveEditorCommand('center'),
|
||||
|
||||
ScrollDownByLine: new EditorScrollCommand('down', 'line'),
|
||||
ScrollDownByHalfPage: new EditorScrollCommand('down', 'halfPage'),
|
||||
ScrollDownByPage: new EditorScrollCommand('down', 'page'),
|
||||
ScrollUpByLine: new EditorScrollCommand('up', 'line'),
|
||||
ScrollUpByHalfPage: new EditorScrollCommand('up', 'halfPage'),
|
||||
ScrollUpByPage: new EditorScrollCommand('up', 'page'),
|
||||
|
||||
RevealCurrentLineAtTop: new RevealCurrentLineCommand('top'),
|
||||
RevealCurrentLineAtCenter: new RevealCurrentLineCommand('center'),
|
||||
RevealCurrentLineAtBottom: new RevealCurrentLineCommand('bottom'),
|
||||
|
||||
FoldUnder: new FoldCommand(),
|
||||
UnfoldUnder: new UnfoldCommand()
|
||||
};
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Position, TextDocument, window } from 'vscode';
|
||||
import { Words, WordCharacters } from './words';
|
||||
import { Command, AbstractCommandDescriptor } from './common';
|
||||
|
||||
export class MotionState {
|
||||
|
||||
public anchor: Position | null;
|
||||
public cursorDesiredCharacter: number;
|
||||
public wordCharacterClass: WordCharacters | null;
|
||||
|
||||
constructor() {
|
||||
this.cursorDesiredCharacter = -1;
|
||||
this.wordCharacterClass = null;
|
||||
this.anchor = null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export abstract class Motion {
|
||||
public abstract run(doc: TextDocument, pos: Position, state: MotionState): Position;
|
||||
|
||||
public repeat(hasRepeatCount: boolean, count: number): Motion {
|
||||
if (!hasRepeatCount) {
|
||||
return this;
|
||||
}
|
||||
return new RepeatingMotion(this, count);
|
||||
}
|
||||
}
|
||||
|
||||
class RepeatingMotion extends Motion {
|
||||
|
||||
private _actual: Motion;
|
||||
private _repeatCount: number;
|
||||
|
||||
constructor(actual: Motion, repeatCount: number) {
|
||||
super();
|
||||
this._actual = actual;
|
||||
this._repeatCount = repeatCount;
|
||||
}
|
||||
|
||||
public run(doc: TextDocument, pos: Position, state: MotionState): Position {
|
||||
for (let cnt = 0; cnt < this._repeatCount; cnt++) {
|
||||
pos = this._actual.run(doc, pos, state);
|
||||
}
|
||||
return pos;
|
||||
}
|
||||
}
|
||||
|
||||
class NextCharacterMotion extends Motion {
|
||||
public run(doc: TextDocument, pos: Position, state: MotionState): Position {
|
||||
if (pos.character === doc.lineAt(pos.line).text.length) {
|
||||
// on last character
|
||||
return ((pos.line + 1 < doc.lineCount) ? new Position(pos.line + 1, 0) : pos);
|
||||
}
|
||||
|
||||
return new Position(pos.line, pos.character + 1);
|
||||
}
|
||||
}
|
||||
|
||||
class LeftMotion extends Motion {
|
||||
public run(doc: TextDocument, pos: Position, state: MotionState): Position {
|
||||
const line = pos.line;
|
||||
|
||||
if (pos.character > 0) {
|
||||
state.cursorDesiredCharacter = pos.character - 1;
|
||||
return new Position(line, state.cursorDesiredCharacter);
|
||||
}
|
||||
|
||||
return pos;
|
||||
}
|
||||
}
|
||||
|
||||
class DownMotion extends Motion {
|
||||
public run(doc: TextDocument, pos: Position, state: MotionState): Position {
|
||||
let line = pos.line;
|
||||
|
||||
state.cursorDesiredCharacter = (state.cursorDesiredCharacter === -1 ? pos.character : state.cursorDesiredCharacter);
|
||||
|
||||
if (line < doc.lineCount - 1) {
|
||||
line++;
|
||||
return new Position(line, Math.min(state.cursorDesiredCharacter, doc.lineAt(line).text.length));
|
||||
}
|
||||
|
||||
return pos;
|
||||
}
|
||||
}
|
||||
|
||||
class UpMotion extends Motion {
|
||||
public run(doc: TextDocument, pos: Position, state: MotionState): Position {
|
||||
let line = pos.line;
|
||||
|
||||
state.cursorDesiredCharacter = (state.cursorDesiredCharacter === -1 ? pos.character : state.cursorDesiredCharacter);
|
||||
|
||||
if (line > 0) {
|
||||
line--;
|
||||
return new Position(line, Math.min(state.cursorDesiredCharacter, doc.lineAt(line).text.length));
|
||||
}
|
||||
|
||||
return pos;
|
||||
}
|
||||
}
|
||||
|
||||
class RightMotion extends Motion {
|
||||
public run(doc: TextDocument, pos: Position, state: MotionState): Position {
|
||||
const line = pos.line;
|
||||
const maxCharacter = doc.lineAt(line).text.length;
|
||||
|
||||
if (pos.character < maxCharacter) {
|
||||
state.cursorDesiredCharacter = pos.character + 1;
|
||||
return new Position(line, state.cursorDesiredCharacter);
|
||||
}
|
||||
|
||||
return pos;
|
||||
}
|
||||
}
|
||||
|
||||
class EndOfLineMotion extends Motion {
|
||||
public run(doc: TextDocument, pos: Position, state: MotionState): Position {
|
||||
return new Position(pos.line, doc.lineAt(pos.line).text.length);
|
||||
}
|
||||
}
|
||||
|
||||
class StartOfLineMotion extends Motion {
|
||||
public run(doc: TextDocument, pos: Position, state: MotionState): Position {
|
||||
return new Position(pos.line, 0);
|
||||
}
|
||||
}
|
||||
|
||||
class NextWordStartMotion extends Motion {
|
||||
public run(doc: TextDocument, pos: Position, state: MotionState): Position {
|
||||
const lineContent = doc.lineAt(pos.line).text;
|
||||
|
||||
if (pos.character >= lineContent.length - 1) {
|
||||
// cursor at end of line
|
||||
return ((pos.line + 1 < doc.lineCount) ? new Position(pos.line + 1, 0) : pos);
|
||||
}
|
||||
|
||||
const nextWord = Words.findNextWord(doc, pos, state.wordCharacterClass);
|
||||
|
||||
if (!nextWord) {
|
||||
// return end of the line
|
||||
return Motions.EndOfLine.run(doc, pos, state);
|
||||
}
|
||||
|
||||
if (nextWord.start <= pos.character && pos.character < nextWord.end) {
|
||||
// Sitting on a word
|
||||
const nextNextWord = Words.findNextWord(doc, new Position(pos.line, nextWord.end), state.wordCharacterClass);
|
||||
if (nextNextWord) {
|
||||
// return start of the next next word
|
||||
return new Position(pos.line, nextNextWord.start);
|
||||
} else {
|
||||
// return end of line
|
||||
return Motions.EndOfLine.run(doc, pos, state);
|
||||
}
|
||||
} else {
|
||||
// return start of the next word
|
||||
return new Position(pos.line, nextWord.start);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class NextWordEndMotion extends Motion {
|
||||
public run(doc: TextDocument, pos: Position, state: MotionState): Position {
|
||||
const lineContent = doc.lineAt(pos.line).text;
|
||||
|
||||
if (pos.character >= lineContent.length - 1) {
|
||||
// no content on this line or cursor at end of line
|
||||
return ((pos.line + 1 < doc.lineCount) ? new Position(pos.line + 1, 0) : pos);
|
||||
}
|
||||
|
||||
const nextWord = Words.findNextWord(doc, pos, state.wordCharacterClass);
|
||||
|
||||
if (!nextWord) {
|
||||
// return end of the line
|
||||
return Motions.EndOfLine.run(doc, pos, state);
|
||||
}
|
||||
|
||||
// return start of the next word
|
||||
return new Position(pos.line, nextWord.end);
|
||||
}
|
||||
}
|
||||
|
||||
class GoToLineUndefinedMotion extends Motion {
|
||||
public run(doc: TextDocument, pos: Position, state: MotionState): Position {
|
||||
// does not do anything
|
||||
return pos;
|
||||
}
|
||||
|
||||
public repeat(hasRepeatCount: boolean, count: number): Motion {
|
||||
if (!hasRepeatCount) {
|
||||
return Motions.GoToLastLine;
|
||||
}
|
||||
return new GoToLineDefinedMotion(count);
|
||||
}
|
||||
}
|
||||
|
||||
abstract class GoToLineMotion extends Motion {
|
||||
|
||||
protected firstNonWhitespaceChar(doc: TextDocument, line: number): number {
|
||||
const lineContent = doc.lineAt(line).text;
|
||||
let character = 0;
|
||||
while (character < lineContent.length) {
|
||||
const ch = lineContent.charAt(character);
|
||||
if (ch !== ' ' && ch !== '\t') {
|
||||
break;
|
||||
}
|
||||
character++;
|
||||
}
|
||||
return character;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class GoToFirstLineMotion extends GoToLineMotion {
|
||||
public run(doc: TextDocument, pos: Position, state: MotionState): Position {
|
||||
return new Position(0, this.firstNonWhitespaceChar(doc, 0));
|
||||
}
|
||||
}
|
||||
|
||||
class GoToLastLineMotion extends GoToLineMotion {
|
||||
public run(doc: TextDocument, pos: Position, state: MotionState): Position {
|
||||
const lastLine = doc.lineCount - 1;
|
||||
return new Position(lastLine, this.firstNonWhitespaceChar(doc, lastLine));
|
||||
}
|
||||
}
|
||||
|
||||
class GoToLineDefinedMotion extends GoToLineMotion {
|
||||
private _lineNumber: number;
|
||||
|
||||
constructor(lineNumber: number) {
|
||||
super();
|
||||
this._lineNumber = lineNumber;
|
||||
}
|
||||
|
||||
public run(doc: TextDocument, pos: Position, state: MotionState): Position {
|
||||
const line = Math.min(doc.lineCount - 1, Math.max(0, this._lineNumber - 1));
|
||||
return new Position(line, this.firstNonWhitespaceChar(doc, line));
|
||||
}
|
||||
}
|
||||
|
||||
class CursorMoveCommand extends AbstractCommandDescriptor {
|
||||
|
||||
constructor(private to: string, private by?: string) {
|
||||
super();
|
||||
}
|
||||
|
||||
public createCommand(args?: any): Command {
|
||||
const cursorMoveArgs: any = {
|
||||
to: this.to,
|
||||
by: this.by,
|
||||
value: args.repeat || 1,
|
||||
select: !!args.isVisual
|
||||
};
|
||||
return {
|
||||
commandId: 'cursorMove',
|
||||
args: cursorMoveArgs
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
class EditorScrollCommand extends AbstractCommandDescriptor {
|
||||
|
||||
constructor(private to: string, private by?: string) {
|
||||
super();
|
||||
}
|
||||
|
||||
public createCommand(args?: any): Command {
|
||||
const editorScrollArgs: any = {
|
||||
to: this.to,
|
||||
by: this.by,
|
||||
value: args.repeat || 1,
|
||||
revealCursor: true
|
||||
};
|
||||
return {
|
||||
commandId: 'editorScroll',
|
||||
args: editorScrollArgs
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
class RevealCurrentLineCommand extends AbstractCommandDescriptor {
|
||||
|
||||
constructor(private at: string) {
|
||||
super();
|
||||
}
|
||||
|
||||
public createCommand(args?: any): Command {
|
||||
const lineNumber = window.activeTextEditor.selection.start.line;
|
||||
const revealLineArgs: any = {
|
||||
lineNumber,
|
||||
at: this.at
|
||||
};
|
||||
return {
|
||||
commandId: 'revealLine',
|
||||
args: revealLineArgs
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
class MoveActiveEditorCommandByPosition extends AbstractCommandDescriptor {
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
|
||||
public createCommand(args?: any): Command {
|
||||
const moveActiveEditorArgs: any = {
|
||||
to: args.repeat === void 0 ? 'last' : 'position',
|
||||
value: args.repeat !== void 0 ? args.repeat + 1 : undefined
|
||||
};
|
||||
return {
|
||||
commandId: 'moveActiveEditor',
|
||||
args: moveActiveEditorArgs
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
class MoveActiveEditorCommand extends AbstractCommandDescriptor {
|
||||
|
||||
constructor(private to: string) {
|
||||
super();
|
||||
}
|
||||
|
||||
public createCommand(args?: any): Command {
|
||||
const moveActiveEditorArgs: any = {
|
||||
to: this.to,
|
||||
value: args.repeat ? args.repeat : 1
|
||||
};
|
||||
return {
|
||||
commandId: 'moveActiveEditor',
|
||||
args: moveActiveEditorArgs
|
||||
};
|
||||
}
|
||||
}
|
||||
class FoldCommand extends AbstractCommandDescriptor {
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
|
||||
public createCommand(args?: any): Command {
|
||||
const foldEditorArgs: any = {
|
||||
levels: args.repeat ? args.repeat : 1,
|
||||
direction: 'up'
|
||||
};
|
||||
return {
|
||||
commandId: 'editor.fold',
|
||||
args: foldEditorArgs
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
class UnfoldCommand extends AbstractCommandDescriptor {
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
|
||||
public createCommand(args?: any): Command {
|
||||
const foldEditorArgs: any = {
|
||||
levels: args.repeat ? args.repeat : 1,
|
||||
direction: 'up'
|
||||
};
|
||||
return {
|
||||
commandId: 'editor.unfold',
|
||||
args: foldEditorArgs
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export const Motions = {
|
||||
RightMotion: new RightMotion(),
|
||||
|
||||
NextCharacter: new NextCharacterMotion(),
|
||||
|
||||
Left: new CursorMoveCommand('left'),
|
||||
Right: new CursorMoveCommand('right'),
|
||||
Down: new CursorMoveCommand('down'),
|
||||
Up: new CursorMoveCommand('up'),
|
||||
|
||||
EndOfLine: new EndOfLineMotion(),
|
||||
StartOfLine: new StartOfLineMotion(),
|
||||
NextWordStart: new NextWordStartMotion(),
|
||||
NextWordEnd: new NextWordEndMotion(),
|
||||
GoToLine: new GoToLineUndefinedMotion(),
|
||||
GoToFirstLine: new GoToFirstLineMotion(),
|
||||
GoToLastLine: new GoToLastLineMotion(),
|
||||
|
||||
CursorScrollLeft: new CursorMoveCommand('left'),
|
||||
CursorScrollRight: new CursorMoveCommand('right'),
|
||||
CursorScrollLeftByHalfLine: new CursorMoveCommand('left', 'halfLine'),
|
||||
CursorScrollRightByHalfLine: new CursorMoveCommand('right', 'halfLine'),
|
||||
|
||||
WrappedLineUp: new CursorMoveCommand('up', 'wrappedLine'),
|
||||
WrappedLineDown: new CursorMoveCommand('down', 'wrappedLine'),
|
||||
|
||||
WrappedLineStart: new CursorMoveCommand('wrappedLineStart'),
|
||||
WrappedLineFirstNonWhiteSpaceCharacter: new CursorMoveCommand('wrappedLineFirstNonWhitespaceCharacter'),
|
||||
WrappedLineColumnCenter: new CursorMoveCommand('wrappedLineColumnCenter'),
|
||||
WrappedLineEnd: new CursorMoveCommand('wrappedLineEnd'),
|
||||
WrappedLineLastNonWhiteSpaceCharacter: new CursorMoveCommand('wrappedLineLastNonWhitespaceCharacter'),
|
||||
|
||||
ViewPortTop: new CursorMoveCommand('viewPortTop'),
|
||||
ViewPortBottom: new CursorMoveCommand('viewPortBottom'),
|
||||
ViewPortCenter: new CursorMoveCommand('viewPortCenter'),
|
||||
|
||||
MoveActiveEditor: new MoveActiveEditorCommandByPosition(),
|
||||
MoveActiveEditorLeft: new MoveActiveEditorCommand('left'),
|
||||
MoveActiveEditorRight: new MoveActiveEditorCommand('right'),
|
||||
MoveActiveEditorFirst: new MoveActiveEditorCommand('first'),
|
||||
MoveActiveEditorLast: new MoveActiveEditorCommand('last'),
|
||||
MoveActiveEditorCenter: new MoveActiveEditorCommand('center'),
|
||||
|
||||
ScrollDownByLine: new EditorScrollCommand('down', 'line'),
|
||||
ScrollDownByHalfPage: new EditorScrollCommand('down', 'halfPage'),
|
||||
ScrollDownByPage: new EditorScrollCommand('down', 'page'),
|
||||
ScrollUpByLine: new EditorScrollCommand('up', 'line'),
|
||||
ScrollUpByHalfPage: new EditorScrollCommand('up', 'halfPage'),
|
||||
ScrollUpByPage: new EditorScrollCommand('up', 'page'),
|
||||
|
||||
RevealCurrentLineAtTop: new RevealCurrentLineCommand('top'),
|
||||
RevealCurrentLineAtCenter: new RevealCurrentLineCommand('center'),
|
||||
RevealCurrentLineAtBottom: new RevealCurrentLineCommand('bottom'),
|
||||
|
||||
FoldUnder: new FoldCommand(),
|
||||
UnfoldUnder: new UnfoldCommand()
|
||||
};
|
||||
|
||||
@ -1,343 +1,343 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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(),
|
||||
};
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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(),
|
||||
};
|
||||
|
||||
@ -1,104 +1,104 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Position, TextDocument } from 'vscode';
|
||||
|
||||
export enum CharacterClass {
|
||||
REGULAR = 0,
|
||||
WORD_SEPARATOR = 1,
|
||||
WHITESPACE = 2
|
||||
}
|
||||
|
||||
export enum WordType {
|
||||
NONE = 0,
|
||||
SEPARATOR = 1,
|
||||
REGULAR = 2
|
||||
}
|
||||
|
||||
export type WordCharacters = CharacterClass[];
|
||||
|
||||
export interface IWord {
|
||||
start: number;
|
||||
end: number;
|
||||
wordType: WordType;
|
||||
}
|
||||
|
||||
export class Words {
|
||||
|
||||
public static createWordCharacters(wordSeparators: string): WordCharacters {
|
||||
const result: CharacterClass[] = [];
|
||||
|
||||
// Make array fast for ASCII text
|
||||
for (let chCode = 0; chCode < 256; chCode++) {
|
||||
result[chCode] = CharacterClass.REGULAR;
|
||||
}
|
||||
|
||||
for (let i = 0, len = wordSeparators.length; i < len; i++) {
|
||||
result[wordSeparators.charCodeAt(i)] = CharacterClass.WORD_SEPARATOR;
|
||||
}
|
||||
|
||||
result[' '.charCodeAt(0)] = CharacterClass.WHITESPACE;
|
||||
result['\t'.charCodeAt(0)] = CharacterClass.WHITESPACE;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static findNextWord(doc: TextDocument, pos: Position, wordCharacterClass: WordCharacters): IWord | null {
|
||||
|
||||
const lineContent = doc.lineAt(pos.line).text;
|
||||
let wordType = WordType.NONE;
|
||||
const len = lineContent.length;
|
||||
|
||||
for (let chIndex = pos.character; chIndex < len; chIndex++) {
|
||||
const chCode = lineContent.charCodeAt(chIndex);
|
||||
const chClass = (wordCharacterClass[chCode] || CharacterClass.REGULAR);
|
||||
|
||||
if (chClass === CharacterClass.REGULAR) {
|
||||
if (wordType === WordType.SEPARATOR) {
|
||||
return this._createWord(lineContent, wordType, this._findStartOfWord(lineContent, wordCharacterClass, wordType, chIndex - 1), chIndex);
|
||||
}
|
||||
wordType = WordType.REGULAR;
|
||||
} else if (chClass === CharacterClass.WORD_SEPARATOR) {
|
||||
if (wordType === WordType.REGULAR) {
|
||||
return this._createWord(lineContent, wordType, this._findStartOfWord(lineContent, wordCharacterClass, wordType, chIndex - 1), chIndex);
|
||||
}
|
||||
wordType = WordType.SEPARATOR;
|
||||
} else if (chClass === CharacterClass.WHITESPACE) {
|
||||
if (wordType !== WordType.NONE) {
|
||||
return this._createWord(lineContent, wordType, this._findStartOfWord(lineContent, wordCharacterClass, wordType, chIndex - 1), chIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (wordType !== WordType.NONE) {
|
||||
return this._createWord(lineContent, wordType, this._findStartOfWord(lineContent, wordCharacterClass, wordType, len - 1), len);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static _findStartOfWord(lineContent: string, wordCharacterClass: WordCharacters, wordType: WordType, startIndex: number): number {
|
||||
for (let chIndex = startIndex; chIndex >= 0; chIndex--) {
|
||||
const chCode = lineContent.charCodeAt(chIndex);
|
||||
const chClass = (wordCharacterClass[chCode] || CharacterClass.REGULAR);
|
||||
|
||||
if (chClass === CharacterClass.WHITESPACE) {
|
||||
return chIndex + 1;
|
||||
}
|
||||
if (wordType === WordType.REGULAR && chClass === CharacterClass.WORD_SEPARATOR) {
|
||||
return chIndex + 1;
|
||||
}
|
||||
if (wordType === WordType.SEPARATOR && chClass === CharacterClass.REGULAR) {
|
||||
return chIndex + 1;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
private static _createWord(lineContent: string, wordType: WordType, start: number, end: number): IWord {
|
||||
// console.log('WORD ==> ' + start + ' => ' + end + ':::: <<<' + lineContent.substring(start, end) + '>>>');
|
||||
return { start: start, end: end, wordType: wordType };
|
||||
}
|
||||
}
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Position, TextDocument } from 'vscode';
|
||||
|
||||
export enum CharacterClass {
|
||||
REGULAR = 0,
|
||||
WORD_SEPARATOR = 1,
|
||||
WHITESPACE = 2
|
||||
}
|
||||
|
||||
export enum WordType {
|
||||
NONE = 0,
|
||||
SEPARATOR = 1,
|
||||
REGULAR = 2
|
||||
}
|
||||
|
||||
export type WordCharacters = CharacterClass[];
|
||||
|
||||
export interface IWord {
|
||||
start: number;
|
||||
end: number;
|
||||
wordType: WordType;
|
||||
}
|
||||
|
||||
export class Words {
|
||||
|
||||
public static createWordCharacters(wordSeparators: string): WordCharacters {
|
||||
const result: CharacterClass[] = [];
|
||||
|
||||
// Make array fast for ASCII text
|
||||
for (let chCode = 0; chCode < 256; chCode++) {
|
||||
result[chCode] = CharacterClass.REGULAR;
|
||||
}
|
||||
|
||||
for (let i = 0, len = wordSeparators.length; i < len; i++) {
|
||||
result[wordSeparators.charCodeAt(i)] = CharacterClass.WORD_SEPARATOR;
|
||||
}
|
||||
|
||||
result[' '.charCodeAt(0)] = CharacterClass.WHITESPACE;
|
||||
result['\t'.charCodeAt(0)] = CharacterClass.WHITESPACE;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static findNextWord(doc: TextDocument, pos: Position, wordCharacterClass: WordCharacters): IWord | null {
|
||||
|
||||
const lineContent = doc.lineAt(pos.line).text;
|
||||
let wordType = WordType.NONE;
|
||||
const len = lineContent.length;
|
||||
|
||||
for (let chIndex = pos.character; chIndex < len; chIndex++) {
|
||||
const chCode = lineContent.charCodeAt(chIndex);
|
||||
const chClass = (wordCharacterClass[chCode] || CharacterClass.REGULAR);
|
||||
|
||||
if (chClass === CharacterClass.REGULAR) {
|
||||
if (wordType === WordType.SEPARATOR) {
|
||||
return this._createWord(lineContent, wordType, this._findStartOfWord(lineContent, wordCharacterClass, wordType, chIndex - 1), chIndex);
|
||||
}
|
||||
wordType = WordType.REGULAR;
|
||||
} else if (chClass === CharacterClass.WORD_SEPARATOR) {
|
||||
if (wordType === WordType.REGULAR) {
|
||||
return this._createWord(lineContent, wordType, this._findStartOfWord(lineContent, wordCharacterClass, wordType, chIndex - 1), chIndex);
|
||||
}
|
||||
wordType = WordType.SEPARATOR;
|
||||
} else if (chClass === CharacterClass.WHITESPACE) {
|
||||
if (wordType !== WordType.NONE) {
|
||||
return this._createWord(lineContent, wordType, this._findStartOfWord(lineContent, wordCharacterClass, wordType, chIndex - 1), chIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (wordType !== WordType.NONE) {
|
||||
return this._createWord(lineContent, wordType, this._findStartOfWord(lineContent, wordCharacterClass, wordType, len - 1), len);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static _findStartOfWord(lineContent: string, wordCharacterClass: WordCharacters, wordType: WordType, startIndex: number): number {
|
||||
for (let chIndex = startIndex; chIndex >= 0; chIndex--) {
|
||||
const chCode = lineContent.charCodeAt(chIndex);
|
||||
const chClass = (wordCharacterClass[chCode] || CharacterClass.REGULAR);
|
||||
|
||||
if (chClass === CharacterClass.WHITESPACE) {
|
||||
return chIndex + 1;
|
||||
}
|
||||
if (wordType === WordType.REGULAR && chClass === CharacterClass.WORD_SEPARATOR) {
|
||||
return chIndex + 1;
|
||||
}
|
||||
if (wordType === WordType.SEPARATOR && chClass === CharacterClass.REGULAR) {
|
||||
return chIndex + 1;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
private static _createWord(lineContent: string, wordType: WordType, start: number, end: number): IWord {
|
||||
// console.log('WORD ==> ' + start + ' => ' + end + ':::: <<<' + lineContent.substring(start, end) + '>>>');
|
||||
return { start: start, end: end, wordType: wordType };
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user