2016-06-30 19:35:56 +03:00
|
|
|
'use strict';
|
2016-05-28 01:49:35 +03:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Extension.ts is a lightweight wrapper around ModeHandler. It converts key
|
|
|
|
* events to their string names and passes them on to ModeHandler via
|
|
|
|
* handleKeyEvent().
|
|
|
|
*/
|
2016-02-07 09:37:46 +03:00
|
|
|
|
2015-11-13 10:38:13 +03:00
|
|
|
import * as vscode from 'vscode';
|
2016-09-03 10:36:44 +03:00
|
|
|
import * as _ from "lodash";
|
2016-06-30 19:35:56 +03:00
|
|
|
import { showCmdLine } from './src/cmd_line/main';
|
|
|
|
import { ModeHandler } from './src/mode/modeHandler';
|
2016-08-19 11:55:15 +03:00
|
|
|
import { taskQueue } from './src/taskQueue';
|
2016-07-14 20:54:04 +03:00
|
|
|
import { Position } from './src/motion/position';
|
2016-08-28 22:20:32 +03:00
|
|
|
import { Globals } from './src/globals';
|
2016-09-06 21:26:15 +03:00
|
|
|
import { AngleBracketNotation } from './src/notation';
|
2015-11-12 22:51:40 +03:00
|
|
|
|
2016-08-03 09:27:23 +03:00
|
|
|
interface VSCodeKeybinding {
|
|
|
|
key: string;
|
2016-10-03 22:36:21 +03:00
|
|
|
mac?: string;
|
2016-08-03 09:27:23 +03:00
|
|
|
command: string;
|
|
|
|
when: string;
|
|
|
|
}
|
|
|
|
|
|
|
|
const packagejson: {
|
|
|
|
contributes: {
|
|
|
|
keybindings: VSCodeKeybinding[];
|
|
|
|
}
|
|
|
|
} = require('../package.json'); // out/../package.json
|
|
|
|
|
2016-07-21 03:17:50 +03:00
|
|
|
export class EditorIdentity {
|
|
|
|
private _fileName: string;
|
|
|
|
private _viewColumn: vscode.ViewColumn;
|
|
|
|
|
|
|
|
constructor(textEditor?: vscode.TextEditor) {
|
|
|
|
this._fileName = textEditor && textEditor.document.fileName || "";
|
|
|
|
this._viewColumn = textEditor && textEditor.viewColumn || vscode.ViewColumn.One;
|
|
|
|
}
|
|
|
|
|
|
|
|
get fileName() {
|
|
|
|
return this._fileName;
|
|
|
|
}
|
|
|
|
|
|
|
|
get viewColumn() {
|
|
|
|
return this._viewColumn;
|
|
|
|
}
|
|
|
|
|
|
|
|
public hasSameBuffer(identity: EditorIdentity): boolean {
|
|
|
|
return this.fileName === identity.fileName;
|
|
|
|
}
|
|
|
|
|
|
|
|
public isEqual(identity: EditorIdentity): boolean {
|
|
|
|
return this.fileName === identity.fileName && this.viewColumn === identity.viewColumn;
|
|
|
|
}
|
|
|
|
|
|
|
|
public toString() {
|
|
|
|
return this.fileName + this.viewColumn;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-06-30 19:35:56 +03:00
|
|
|
let extensionContext: vscode.ExtensionContext;
|
2016-06-18 10:21:17 +03:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Note: We can't initialize modeHandler here, or even inside activate(), because some people
|
|
|
|
* see a bug where VSC hasn't fully initialized yet, which pretty much breaks VSCodeVim entirely.
|
|
|
|
*/
|
2016-07-21 03:17:50 +03:00
|
|
|
let modeHandlerToEditorIdentity: { [key: string]: ModeHandler } = {};
|
|
|
|
let previousActiveEditorId: EditorIdentity = new EditorIdentity();
|
2016-06-30 19:35:56 +03:00
|
|
|
|
2016-07-04 02:09:48 +03:00
|
|
|
export async function getAndUpdateModeHandler(): Promise<ModeHandler> {
|
2016-07-21 03:17:50 +03:00
|
|
|
const oldHandler = modeHandlerToEditorIdentity[previousActiveEditorId.toString()];
|
|
|
|
const activeEditorId = new EditorIdentity(vscode.window.activeTextEditor);
|
2016-07-04 02:09:48 +03:00
|
|
|
|
2016-07-21 03:17:50 +03:00
|
|
|
if (!modeHandlerToEditorIdentity[activeEditorId.toString()]) {
|
2016-08-28 22:20:32 +03:00
|
|
|
const newModeHandler = new ModeHandler(activeEditorId.fileName);
|
2016-07-04 02:09:48 +03:00
|
|
|
|
2016-07-21 03:17:50 +03:00
|
|
|
modeHandlerToEditorIdentity[activeEditorId.toString()] = newModeHandler;
|
2016-07-16 20:02:31 +03:00
|
|
|
extensionContext.subscriptions.push(newModeHandler);
|
|
|
|
}
|
2016-07-04 02:09:48 +03:00
|
|
|
|
2016-07-21 03:17:50 +03:00
|
|
|
const handler = modeHandlerToEditorIdentity[activeEditorId.toString()];
|
2016-07-04 02:09:48 +03:00
|
|
|
|
2016-07-21 03:17:50 +03:00
|
|
|
if (previousActiveEditorId.hasSameBuffer(activeEditorId)) {
|
|
|
|
if (!previousActiveEditorId.isEqual(activeEditorId)) {
|
|
|
|
// We have opened two editors, working on the same file.
|
|
|
|
previousActiveEditorId = activeEditorId;
|
|
|
|
|
|
|
|
handler.vimState.cursorPosition = Position.FromVSCodePosition(vscode.window.activeTextEditor.selection.end);
|
|
|
|
handler.vimState.cursorStartPosition = Position.FromVSCodePosition(vscode.window.activeTextEditor.selection.start);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
previousActiveEditorId = activeEditorId;
|
2016-07-04 02:09:48 +03:00
|
|
|
|
2016-07-16 20:02:31 +03:00
|
|
|
await handler.updateView(handler.vimState);
|
|
|
|
}
|
2016-07-04 02:09:48 +03:00
|
|
|
|
2016-07-16 20:02:31 +03:00
|
|
|
if (oldHandler && oldHandler.vimState.focusChanged) {
|
|
|
|
oldHandler.vimState.focusChanged = false;
|
|
|
|
handler.vimState.focusChanged = true;
|
|
|
|
}
|
2016-06-30 19:35:56 +03:00
|
|
|
|
2016-07-16 20:02:31 +03:00
|
|
|
return handler;
|
2016-06-30 19:35:56 +03:00
|
|
|
}
|
2015-11-17 09:14:48 +03:00
|
|
|
|
2016-07-22 01:16:37 +03:00
|
|
|
class CompositionState {
|
|
|
|
public isInComposition: boolean = false;
|
|
|
|
public composingText: string = "";
|
|
|
|
}
|
|
|
|
|
2016-07-17 06:39:30 +03:00
|
|
|
export async function activate(context: vscode.ExtensionContext) {
|
2016-07-16 20:02:31 +03:00
|
|
|
extensionContext = context;
|
2016-07-22 01:16:37 +03:00
|
|
|
let compositionState = new CompositionState();
|
2016-07-16 20:02:31 +03:00
|
|
|
|
2016-08-28 22:20:32 +03:00
|
|
|
vscode.window.onDidChangeActiveTextEditor(handleActiveEditorChange, this);
|
|
|
|
|
2016-09-03 10:36:44 +03:00
|
|
|
vscode.workspace.onDidChangeTextDocument((event) => {
|
|
|
|
/**
|
|
|
|
* Change from vscode editor should set document.isDirty to true but they initially don't!
|
|
|
|
* There is a timing issue in vscode codebase between when the isDirty flag is set and
|
|
|
|
* when registered callbacks are fired. https://github.com/Microsoft/vscode/issues/11339
|
|
|
|
*/
|
|
|
|
setTimeout(() => {
|
2016-09-04 03:44:07 +03:00
|
|
|
if (!event.document.isDirty && !event.document.isUntitled) {
|
2016-09-03 10:36:44 +03:00
|
|
|
handleContentChangedFromDisk(event.document);
|
|
|
|
}
|
|
|
|
}, 0);
|
|
|
|
});
|
2016-07-16 20:02:31 +03:00
|
|
|
|
|
|
|
registerCommand(context, 'type', async (args) => {
|
|
|
|
taskQueue.enqueueTask({
|
|
|
|
promise: async () => {
|
|
|
|
const mh = await getAndUpdateModeHandler();
|
2016-07-22 01:16:37 +03:00
|
|
|
|
|
|
|
if (compositionState.isInComposition) {
|
|
|
|
compositionState.composingText += args.text;
|
|
|
|
} else {
|
|
|
|
await mh.handleKeyEvent(args.text);
|
|
|
|
}
|
2016-07-16 20:02:31 +03:00
|
|
|
},
|
2016-07-17 06:39:30 +03:00
|
|
|
isRunning: false
|
2016-03-24 10:28:55 +03:00
|
|
|
});
|
2016-07-16 20:02:31 +03:00
|
|
|
});
|
2016-05-28 01:49:35 +03:00
|
|
|
|
2016-07-16 20:02:31 +03:00
|
|
|
registerCommand(context, 'replacePreviousChar', async (args) => {
|
|
|
|
taskQueue.enqueueTask({
|
|
|
|
promise: async () => {
|
|
|
|
const mh = await getAndUpdateModeHandler();
|
2016-07-22 01:16:37 +03:00
|
|
|
|
|
|
|
if (compositionState.isInComposition) {
|
|
|
|
compositionState.composingText = compositionState.composingText.substr(0, compositionState.composingText.length - args.replaceCharCnt) + args.text;
|
|
|
|
} else {
|
|
|
|
await vscode.commands.executeCommand('default:replacePreviousChar', {
|
|
|
|
text: args.text,
|
|
|
|
replaceCharCnt: args.replaceCharCnt
|
|
|
|
});
|
|
|
|
mh.vimState.cursorPosition = Position.FromVSCodePosition(vscode.window.activeTextEditor.selection.start);
|
|
|
|
mh.vimState.cursorStartPosition = Position.FromVSCodePosition(vscode.window.activeTextEditor.selection.start);
|
|
|
|
}
|
|
|
|
},
|
|
|
|
isRunning: false
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
registerCommand(context, 'compositionStart', async (args) => {
|
|
|
|
taskQueue.enqueueTask({
|
|
|
|
promise: async () => {
|
|
|
|
const mh = await getAndUpdateModeHandler();
|
|
|
|
compositionState.isInComposition = true;
|
|
|
|
},
|
|
|
|
isRunning: false
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
registerCommand(context, 'compositionEnd', async (args) => {
|
|
|
|
taskQueue.enqueueTask({
|
|
|
|
promise: async () => {
|
|
|
|
const mh = await getAndUpdateModeHandler();
|
|
|
|
let text = compositionState.composingText;
|
|
|
|
compositionState = new CompositionState();
|
|
|
|
await mh.handleMultipleKeyEvents(text.split(""));
|
2016-07-16 20:02:31 +03:00
|
|
|
},
|
2016-07-17 06:39:30 +03:00
|
|
|
isRunning: false
|
2016-07-16 20:02:31 +03:00
|
|
|
});
|
2016-07-17 06:39:30 +03:00
|
|
|
});
|
2016-07-14 20:54:04 +03:00
|
|
|
|
2016-07-16 20:02:31 +03:00
|
|
|
registerCommand(context, 'extension.showCmdLine', () => {
|
2016-07-21 03:17:50 +03:00
|
|
|
showCmdLine("", modeHandlerToEditorIdentity[new EditorIdentity(vscode.window.activeTextEditor).toString()]);
|
2016-07-16 20:02:31 +03:00
|
|
|
});
|
2015-11-17 23:41:38 +03:00
|
|
|
|
2016-10-02 11:34:11 +03:00
|
|
|
for (let keybinding of packagejson.contributes.keybindings) {
|
2016-10-03 22:36:21 +03:00
|
|
|
let keyToBeBound = "";
|
|
|
|
|
|
|
|
/**
|
|
|
|
* On OSX, handle mac keybindings if we specified one.
|
|
|
|
*/
|
|
|
|
if (process.platform === "darwin") {
|
|
|
|
keyToBeBound = keybinding.mac || keybinding.key;
|
|
|
|
} else {
|
|
|
|
keyToBeBound = keybinding.key;
|
|
|
|
}
|
|
|
|
|
|
|
|
let bracketedKey = AngleBracketNotation.Normalize(keyToBeBound);
|
|
|
|
|
2016-10-02 11:34:11 +03:00
|
|
|
registerCommand(context, keybinding.command, () => handleKeyEvent(`${ bracketedKey }`));
|
2016-08-03 09:27:23 +03:00
|
|
|
}
|
2016-07-17 06:39:30 +03:00
|
|
|
|
|
|
|
// Initialize mode handler for current active Text Editor at startup.
|
|
|
|
if (vscode.window.activeTextEditor) {
|
2016-10-03 22:36:21 +03:00
|
|
|
let mh = await getAndUpdateModeHandler();
|
2016-07-17 06:39:30 +03:00
|
|
|
mh.updateView(mh.vimState, false);
|
|
|
|
}
|
2015-11-29 10:11:55 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
function registerCommand(context: vscode.ExtensionContext, command: string, callback: (...args: any[]) => any) {
|
2016-09-14 08:20:53 +03:00
|
|
|
let disposable = vscode.commands.registerCommand(command, async (args) => {
|
|
|
|
if (!vscode.window.activeTextEditor) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (vscode.window.activeTextEditor.document && vscode.window.activeTextEditor.document.uri.toString() === "debug:input") {
|
|
|
|
await vscode.commands.executeCommand("default:" + command, args);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
callback(args);
|
|
|
|
});
|
2016-07-16 20:02:31 +03:00
|
|
|
context.subscriptions.push(disposable);
|
2015-11-17 09:14:48 +03:00
|
|
|
}
|
|
|
|
|
2016-07-04 02:09:48 +03:00
|
|
|
async function handleKeyEvent(key: string): Promise<void> {
|
2016-07-16 20:02:31 +03:00
|
|
|
const mh = await getAndUpdateModeHandler();
|
2016-01-30 22:41:53 +03:00
|
|
|
|
2016-07-16 20:02:31 +03:00
|
|
|
taskQueue.enqueueTask({
|
2016-10-02 08:35:48 +03:00
|
|
|
promise : async () => { await mh.handleKeyEvent(key); },
|
2016-07-16 20:02:31 +03:00
|
|
|
isRunning : false
|
|
|
|
});
|
2016-06-30 19:35:56 +03:00
|
|
|
}
|
|
|
|
|
2016-09-03 10:36:44 +03:00
|
|
|
function handleContentChangedFromDisk(document : vscode.TextDocument) : void {
|
|
|
|
_.filter(modeHandlerToEditorIdentity, modeHandler => modeHandler.fileName === document.fileName)
|
|
|
|
.forEach(modeHandler => {
|
|
|
|
modeHandler.vimState.historyTracker.clear();
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2016-08-28 22:20:32 +03:00
|
|
|
async function handleActiveEditorChange(): Promise<void> {
|
|
|
|
|
|
|
|
// Don't run this event handler during testing
|
|
|
|
if (Globals.isTesting) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2016-09-04 08:54:10 +03:00
|
|
|
taskQueue.enqueueTask({
|
|
|
|
promise: async () => {
|
|
|
|
if (vscode.window.activeTextEditor !== undefined) {
|
|
|
|
const mh = await getAndUpdateModeHandler();
|
|
|
|
|
|
|
|
mh.updateView(mh.vimState, false);
|
|
|
|
}
|
|
|
|
},
|
|
|
|
isRunning: false
|
|
|
|
});
|
2016-08-28 22:20:32 +03:00
|
|
|
}
|
|
|
|
|
2016-06-30 19:35:56 +03:00
|
|
|
process.on('unhandledRejection', function(reason: any, p: any) {
|
2016-07-16 20:02:31 +03:00
|
|
|
console.log("Unhandled Rejection at: Promise ", p, " reason: ", reason);
|
2016-07-15 10:59:08 +03:00
|
|
|
});
|