mirror of
https://github.com/microsoft/vscode-extension-samples.git
synced 2026-06-13 07:10:26 +08:00
Switches all samples to use eslint 9 with flat configs. I've tried to migrate existing settings as much as possible. However our eslint configs were also inconsistent so I've tried to align these too
304 lines
8.6 KiB
TypeScript
304 lines
8.6 KiB
TypeScript
/*---------------------------------------------------------------------------------------------
|
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
|
* Licensed under the MIT License. See License.txt in the project root for license information.
|
|
*--------------------------------------------------------------------------------------------*/
|
|
|
|
import { 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);
|
|
}
|