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

267 lines
7.2 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 * as vscode from 'vscode';
import { Mode, ModifierKeys } from './common';
import { Controller } from './controller';
export function activate(context: vscode.ExtensionContext) {
function registerCommandNice(commandId: string, run: (...args: any[]) => void): void {
context.subscriptions.push(vscode.commands.registerCommand(commandId, run));
}
function registerCtrlKeyBinding(key: string): void {
registerCommandNice(key, function (args) {
if (!vscode.window.activeTextEditor) {
return;
}
vimExt.type(key, { ctrl: true });
});
}
const vimExt = new VimExt();
registerCommandNice('type', function (args) {
if (!vscode.window.activeTextEditor) {
return;
}
vimExt.type(args.text);
});
registerCommandNice('replacePreviousChar', function (args) {
if (!vscode.window.activeTextEditor) {
return;
}
vimExt.replacePrevChar(args.text, args.replaceCharCnt);
});
registerCommandNice('compositionStart', function (args) {
if (!vscode.window.activeTextEditor) {
return;
}
vimExt.compositionStart();
});
registerCommandNice('compositionEnd', function (args) {
if (!vscode.window.activeTextEditor) {
return;
}
vimExt.compositionEnd();
});
registerCommandNice('vim.goToNormalMode', function (args) {
vimExt.goToNormalMode();
});
registerCommandNice('vim.clearInput', function (args) {
vimExt.clearInput();
});
// registerCommandNice('paste', function(args) {
// console.log('paste with: ', args.text, args.pasteOnNewLine);
// });
// registerCommandNice('cut', function(args) {
// console.log('cut (no args)');
// });
registerCtrlKeyBinding('e');
registerCtrlKeyBinding('d');
registerCtrlKeyBinding('f');
registerCtrlKeyBinding('y');
registerCtrlKeyBinding('u');
registerCtrlKeyBinding('b');
}
export function deactivate() {
// Everything is nicely registered in context.subscriptions,
// so nothing to do for now.
}
function getConfiguredWordSeparators(): string {
const editorConfig = vscode.workspace.getConfiguration('editor');
return editorConfig['wordSeparators'];
}
class VimExt {
private _inNormalMode: ContextKey;
private _hasInput: ContextKey;
private _statusBar: StatusBar;
private _controller: Controller;
constructor() {
this._inNormalMode = new ContextKey('vim.inNormalMode');
this._hasInput = new ContextKey('vim.hasInput');
this._statusBar = new StatusBar();
this._controller = new Controller();
vscode.window.onDidChangeActiveTextEditor((textEditor) => {
if (!textEditor) {
return;
}
this._ensureState();
});
vscode.window.onDidChangeTextEditorSelection((e) => {
const isVisual = this._controller.getVisual();
if (!isVisual) {
// a selection in the editor brings us to visual mode
let goToVisualMode = false;
if (e.selections.length > 1) {
goToVisualMode = true;
} else {
goToVisualMode = !e.selections[0].isEmpty;
}
if (goToVisualMode) {
this._controller.setVisual(true);
}
} else {
// a collapsed selection in the editor brings us to normal mode
let leaveVisualMode = false;
if (e.selections.length === 1) {
leaveVisualMode = e.selections[0].isEmpty;
}
if (leaveVisualMode) {
this._controller.setVisual(false);
}
}
this._ensureState();
});
const ensureConfig = () => {
this._controller.setWordSeparators(getConfiguredWordSeparators());
};
ensureConfig();
vscode.workspace.onDidChangeConfiguration(ensureConfig);
this._ensureState();
}
public goToNormalMode(): void {
this._controller.setMode(Mode.NORMAL);
this._ensureState();
}
public clearInput(): void {
this._controller.clearInput();
this._ensureState();
}
public type(text: string, modifierKeys: ModifierKeys = { ctrl: false, shifit: false, alt: false }): void {
this._controller.type(vscode.window.activeTextEditor, text, modifierKeys).then((r) => {
if (r.hasConsumedInput) {
this._ensureState();
if (r.executeEditorCommand) {
let args = [r.executeEditorCommand.commandId];
args = args.concat(r.executeEditorCommand.args);
vscode.commands.executeCommand.apply(this, args);
}
return;
}
vscode.commands.executeCommand('default:type', {
text: text
});
});
}
public replacePrevChar(text: string, replaceCharCnt: number): void {
if (this._controller.replacePrevChar(vscode.window.activeTextEditor, text, replaceCharCnt)) {
this._ensureState();
return;
}
vscode.commands.executeCommand('default:replacePreviousChar', {
text: text,
replaceCharCnt: replaceCharCnt
});
}
public compositionStart(): void {
this._controller.compositionStart(vscode.window.activeTextEditor);
}
public compositionEnd(): void {
this._controller.compositionEnd(vscode.window.activeTextEditor).then((r) => {
if (r.hasConsumedInput) {
this._ensureState();
if (r.executeEditorCommand) {
let args = [r.executeEditorCommand.commandId];
args = args.concat(r.executeEditorCommand.args);
vscode.commands.executeCommand.apply(this, args);
}
}
});
}
private _ensureState(): void {
// 0. position
this._ensurePosition();
// 1. status bar
this._statusBar.setText(this._controller.getStatusText());
// 2. cursor style
this._ensureCursorStyle(this._controller.getCursorStyle());
// 3. context: vim.inNormalMode
this._inNormalMode.set(this._controller.getMode() === Mode.NORMAL);
// 4. context: vim.hasInput
this._hasInput.set(this._controller.hasInput());
}
private _ensurePosition(): void {
if (!vscode.window.activeTextEditor) {
return;
}
this._controller.ensureNormalModePosition(vscode.window.activeTextEditor);
}
private _ensureCursorStyle(cursorStyle: vscode.TextEditorCursorStyle): void {
if (!vscode.window.activeTextEditor) {
return;
}
const currentCursorStyle = vscode.window.activeTextEditor.options.cursorStyle;
if (currentCursorStyle !== cursorStyle) {
vscode.window.activeTextEditor.options = {
cursorStyle: cursorStyle
};
}
}
}
class ContextKey {
private _name: string;
private _lastValue: boolean;
constructor(name: string) {
this._name = name;
}
public set(value: boolean): void {
if (this._lastValue === value) {
return;
}
this._lastValue = value;
vscode.commands.executeCommand('setContext', this._name, this._lastValue);
}
}
class StatusBar {
private _actual: vscode.StatusBarItem;
private _lastText: string;
constructor() {
this._actual = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Left);
this._actual.show();
}
public setText(text: string): void {
if (this._lastText === text) {
return;
}
this._lastText = text;
this._actual.text = this._lastText;
}
}