mirror of
https://github.com/kristoferssolo/solorice.git
synced 2025-10-21 20:10:34 +00:00
498 lines
23 KiB
JavaScript
498 lines
23 KiB
JavaScript
"use strict";
|
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
exports.registerEventListener = exports.registerCommand = exports.activate = exports.getAndUpdateModeHandler = void 0;
|
|
const vscode = require("vscode");
|
|
const compositionState_1 = require("./src/state/compositionState");
|
|
const globals_1 = require("./src/globals");
|
|
const jump_1 = require("./src/jumps/jump");
|
|
const modeHandlerMap_1 = require("./src/mode/modeHandlerMap");
|
|
const mode_1 = require("./src/mode/mode");
|
|
const notation_1 = require("./src/configuration/notation");
|
|
const logger_1 = require("./src/util/logger");
|
|
const statusBar_1 = require("./src/statusBar");
|
|
const vscodeContext_1 = require("./src/util/vscodeContext");
|
|
const commandLine_1 = require("./src/cmd_line/commandLine");
|
|
const configuration_1 = require("./src/configuration/configuration");
|
|
const globalState_1 = require("./src/state/globalState");
|
|
const taskQueue_1 = require("./src/taskQueue");
|
|
const register_1 = require("./src/register/register");
|
|
const specialKeys_1 = require("./src/util/specialKeys");
|
|
const historyTracker_1 = require("./src/history/historyTracker");
|
|
let extensionContext;
|
|
let previousActiveEditorUri;
|
|
let lastClosedModeHandler = null;
|
|
async function getAndUpdateModeHandler(forceSyncAndUpdate = false) {
|
|
const activeTextEditor = vscode.window.activeTextEditor;
|
|
if (activeTextEditor === undefined || activeTextEditor.document.isClosed) {
|
|
return undefined;
|
|
}
|
|
const uri = activeTextEditor.document.uri;
|
|
const [curHandler, isNew] = await modeHandlerMap_1.ModeHandlerMap.getOrCreate(uri);
|
|
if (isNew) {
|
|
extensionContext.subscriptions.push(curHandler);
|
|
}
|
|
curHandler.vimState.editor = activeTextEditor;
|
|
if (forceSyncAndUpdate || !previousActiveEditorUri || previousActiveEditorUri !== uri) {
|
|
// We sync the cursors here because ModeHandler is specific to a document, not an editor, so we
|
|
// need to update our representation of the cursors when switching between editors for the same document.
|
|
// This will be unnecessary once #4889 is fixed.
|
|
curHandler.syncCursors();
|
|
await curHandler.updateView({ drawSelection: false, revealRange: false });
|
|
}
|
|
previousActiveEditorUri = uri;
|
|
if (curHandler.focusChanged) {
|
|
curHandler.focusChanged = false;
|
|
if (previousActiveEditorUri) {
|
|
const prevHandler = modeHandlerMap_1.ModeHandlerMap.get(previousActiveEditorUri);
|
|
prevHandler.focusChanged = true;
|
|
}
|
|
}
|
|
return curHandler;
|
|
}
|
|
exports.getAndUpdateModeHandler = getAndUpdateModeHandler;
|
|
/**
|
|
* Loads and validates the user's configuration
|
|
*/
|
|
async function loadConfiguration() {
|
|
const validatorResults = await configuration_1.configuration.load();
|
|
logger_1.Logger.configChanged(configuration_1.configuration);
|
|
const logger = logger_1.Logger.get('Configuration');
|
|
logger.debug(`${validatorResults.numErrors} errors found with vim configuration`);
|
|
if (validatorResults.numErrors > 0) {
|
|
for (const validatorResult of validatorResults.get()) {
|
|
switch (validatorResult.level) {
|
|
case 'error':
|
|
logger.error(validatorResult.message);
|
|
break;
|
|
case 'warning':
|
|
logger.warn(validatorResult.message);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
/**
|
|
* The extension's entry point
|
|
*/
|
|
async function activate(context, handleLocal = true) {
|
|
// before we do anything else, we need to load the configuration
|
|
await loadConfiguration();
|
|
const logger = logger_1.Logger.get('Extension Startup');
|
|
logger.debug('Start');
|
|
extensionContext = context;
|
|
extensionContext.subscriptions.push(statusBar_1.StatusBar);
|
|
// Load state
|
|
register_1.Register.loadFromDisk(handleLocal);
|
|
await Promise.all([commandLine_1.ExCommandLine.loadHistory(context), commandLine_1.SearchCommandLine.loadHistory(context)]);
|
|
if (vscode.window.activeTextEditor) {
|
|
const filepathComponents = vscode.window.activeTextEditor.document.fileName.split(/\\|\//);
|
|
register_1.Register.setReadonlyRegister('%', filepathComponents[filepathComponents.length - 1]);
|
|
}
|
|
// workspace events
|
|
registerEventListener(context, vscode.workspace.onDidChangeConfiguration, async () => {
|
|
await loadConfiguration();
|
|
}, false);
|
|
registerEventListener(context, vscode.workspace.onDidChangeTextDocument, async (event) => {
|
|
const textWasDeleted = (changeEvent) => changeEvent.contentChanges.length === 1 &&
|
|
changeEvent.contentChanges[0].text === '' &&
|
|
changeEvent.contentChanges[0].range.start.line !==
|
|
changeEvent.contentChanges[0].range.end.line;
|
|
const textWasAdded = (changeEvent) => changeEvent.contentChanges.length === 1 &&
|
|
(changeEvent.contentChanges[0].text === '\n' ||
|
|
changeEvent.contentChanges[0].text === '\r\n') &&
|
|
changeEvent.contentChanges[0].range.start.line ===
|
|
changeEvent.contentChanges[0].range.end.line;
|
|
if (textWasDeleted(event)) {
|
|
globalState_1.globalState.jumpTracker.handleTextDeleted(event.document, event.contentChanges[0].range);
|
|
}
|
|
else if (textWasAdded(event)) {
|
|
globalState_1.globalState.jumpTracker.handleTextAdded(event.document, event.contentChanges[0].range, event.contentChanges[0].text);
|
|
}
|
|
// 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
|
|
const contentChangeHandler = (modeHandler) => {
|
|
if (modeHandler.vimState.currentMode === mode_1.Mode.Insert) {
|
|
if (modeHandler.vimState.historyTracker.currentContentChanges === undefined) {
|
|
modeHandler.vimState.historyTracker.currentContentChanges = [];
|
|
}
|
|
modeHandler.vimState.historyTracker.currentContentChanges =
|
|
modeHandler.vimState.historyTracker.currentContentChanges.concat(event.contentChanges);
|
|
}
|
|
};
|
|
modeHandlerMap_1.ModeHandlerMap.getAll()
|
|
.filter((modeHandler) => modeHandler.vimState.documentUri === event.document.uri)
|
|
.forEach((modeHandler) => {
|
|
contentChangeHandler(modeHandler);
|
|
});
|
|
if (handleLocal) {
|
|
setTimeout(() => {
|
|
if (!event.document.isDirty &&
|
|
!event.document.isUntitled &&
|
|
event.document.uri.scheme !== 'vscode-notebook-cell' && // TODO: Notebooks never seem to be marked dirty...
|
|
event.contentChanges.length) {
|
|
handleContentChangedFromDisk(event.document);
|
|
}
|
|
}, 0);
|
|
}
|
|
});
|
|
registerEventListener(context, vscode.workspace.onDidCloseTextDocument, async (closedDocument) => {
|
|
const documents = vscode.workspace.textDocuments;
|
|
// Delete modehandler once all tabs of this document have been closed
|
|
for (const uri of modeHandlerMap_1.ModeHandlerMap.keys()) {
|
|
const modeHandler = modeHandlerMap_1.ModeHandlerMap.get(uri);
|
|
let shouldDelete = false;
|
|
if (modeHandler == null || modeHandler.vimState.editor === undefined) {
|
|
shouldDelete = true;
|
|
}
|
|
else {
|
|
const document = modeHandler.vimState.document;
|
|
if (!documents.includes(document)) {
|
|
shouldDelete = true;
|
|
if (closedDocument === document) {
|
|
lastClosedModeHandler = modeHandler;
|
|
}
|
|
}
|
|
}
|
|
if (shouldDelete) {
|
|
modeHandlerMap_1.ModeHandlerMap.delete(uri);
|
|
}
|
|
}
|
|
}, false);
|
|
// window events
|
|
registerEventListener(context, vscode.window.onDidChangeActiveTextEditor, async () => {
|
|
var _a;
|
|
const mhPrevious = previousActiveEditorUri
|
|
? modeHandlerMap_1.ModeHandlerMap.get(previousActiveEditorUri)
|
|
: undefined;
|
|
// Track the closed editor so we can use it the next time an open event occurs.
|
|
// When vscode changes away from a temporary file, onDidChangeActiveTextEditor first twice.
|
|
// First it fires when leaving the closed editor. Then onDidCloseTextDocument first, and we delete
|
|
// the old ModeHandler. Then a new editor opens.
|
|
//
|
|
// This also applies to files that are merely closed, which allows you to jump back to that file similarly
|
|
// once a new file is opened.
|
|
lastClosedModeHandler = mhPrevious || lastClosedModeHandler;
|
|
if (vscode.window.activeTextEditor === undefined) {
|
|
register_1.Register.setReadonlyRegister('%', '');
|
|
return;
|
|
}
|
|
const oldFileRegister = (_a = (await register_1.Register.get('%'))) === null || _a === void 0 ? void 0 : _a.text;
|
|
const relativePath = vscode.workspace.asRelativePath(vscode.window.activeTextEditor.document.uri, false);
|
|
if (relativePath !== oldFileRegister) {
|
|
if (oldFileRegister && oldFileRegister !== '') {
|
|
register_1.Register.setReadonlyRegister('#', oldFileRegister);
|
|
}
|
|
register_1.Register.setReadonlyRegister('%', relativePath);
|
|
}
|
|
taskQueue_1.taskQueue.enqueueTask(async () => {
|
|
const mh = await getAndUpdateModeHandler(true);
|
|
if (mh) {
|
|
globalState_1.globalState.jumpTracker.handleFileJump(lastClosedModeHandler ? jump_1.Jump.fromStateNow(lastClosedModeHandler.vimState) : null, jump_1.Jump.fromStateNow(mh.vimState));
|
|
}
|
|
});
|
|
}, true, true);
|
|
registerEventListener(context, vscode.window.onDidChangeTextEditorSelection, async (e) => {
|
|
if (vscode.window.activeTextEditor === undefined ||
|
|
e.textEditor.document !== vscode.window.activeTextEditor.document) {
|
|
// We don't care if user selection changed in a paneled window (e.g debug console/terminal)
|
|
return;
|
|
}
|
|
const mh = modeHandlerMap_1.ModeHandlerMap.get(vscode.window.activeTextEditor.document.uri);
|
|
if (mh === undefined) {
|
|
// We don't care if there is no active editor
|
|
return;
|
|
}
|
|
if (e.kind !== vscode.TextEditorSelectionChangeKind.Mouse) {
|
|
const selectionsHash = e.selections.reduce((hash, s) => hash +
|
|
`[${s.anchor.line}, ${s.anchor.character}; ${s.active.line}, ${s.active.character}]`, '');
|
|
const idx = mh.vimState.selectionsChanged.ourSelections.indexOf(selectionsHash);
|
|
if (idx > -1) {
|
|
mh.vimState.selectionsChanged.ourSelections.splice(idx, 1);
|
|
logger.debug(`Selections: Ignoring selection: ${selectionsHash}, Count left: ${mh.vimState.selectionsChanged.ourSelections.length}`);
|
|
return;
|
|
}
|
|
else if (mh.vimState.selectionsChanged.ignoreIntermediateSelections) {
|
|
logger.debug(`Selections: ignoring intermediate selection change: ${selectionsHash}`);
|
|
return;
|
|
}
|
|
else if (mh.vimState.selectionsChanged.ourSelections.length > 0) {
|
|
// Some intermediate selection must have slipped in after setting the
|
|
// 'ignoreIntermediateSelections' to false. Which means we didn't count
|
|
// for it yet, but since we have selections to be ignored then we probably
|
|
// wanted this one to be ignored as well.
|
|
logger.debug(`Selections: Ignoring slipped selection: ${selectionsHash}`);
|
|
return;
|
|
}
|
|
}
|
|
// We may receive changes from other panels when, having selections in them containing the same file
|
|
// and changing text before the selection in current panel.
|
|
if (e.textEditor !== mh.vimState.editor) {
|
|
return;
|
|
}
|
|
if (mh.focusChanged) {
|
|
mh.focusChanged = false;
|
|
return;
|
|
}
|
|
if (mh.currentMode === mode_1.Mode.EasyMotionMode) {
|
|
return;
|
|
}
|
|
taskQueue_1.taskQueue.enqueueTask(() => mh.handleSelectionChange(e));
|
|
}, true, false);
|
|
registerEventListener(context, vscode.window.onDidChangeTextEditorVisibleRanges, async (e) => {
|
|
taskQueue_1.taskQueue.enqueueTask(async () => {
|
|
// Scrolling the viewport clears any status bar message, even errors.
|
|
const mh = await getAndUpdateModeHandler();
|
|
if (mh && statusBar_1.StatusBar.lastMessageTime) {
|
|
// TODO: Using the time elapsed works most of the time, but is a bit of a hack
|
|
const timeElapsed = Number(new Date()) - Number(statusBar_1.StatusBar.lastMessageTime);
|
|
if (timeElapsed > 100) {
|
|
statusBar_1.StatusBar.clear(mh.vimState, true);
|
|
}
|
|
}
|
|
});
|
|
});
|
|
const compositionState = new compositionState_1.CompositionState();
|
|
// Override VSCode commands
|
|
overrideCommand(context, 'type', async (args) => {
|
|
taskQueue_1.taskQueue.enqueueTask(async () => {
|
|
const mh = await getAndUpdateModeHandler();
|
|
if (mh) {
|
|
if (compositionState.isInComposition) {
|
|
compositionState.composingText += args.text;
|
|
if (mh.vimState.currentMode === mode_1.Mode.Insert) {
|
|
compositionState.insertedText = true;
|
|
vscode.commands.executeCommand('default:type', { text: args.text });
|
|
}
|
|
}
|
|
else {
|
|
await mh.handleKeyEvent(args.text);
|
|
}
|
|
}
|
|
});
|
|
});
|
|
overrideCommand(context, 'replacePreviousChar', async (args) => {
|
|
taskQueue_1.taskQueue.enqueueTask(async () => {
|
|
const mh = await getAndUpdateModeHandler();
|
|
if (mh) {
|
|
if (compositionState.isInComposition) {
|
|
compositionState.composingText =
|
|
compositionState.composingText.substr(0, compositionState.composingText.length - args.replaceCharCnt) + args.text;
|
|
}
|
|
if (compositionState.insertedText) {
|
|
await vscode.commands.executeCommand('default:replacePreviousChar', {
|
|
text: args.text,
|
|
replaceCharCnt: args.replaceCharCnt,
|
|
});
|
|
mh.vimState.cursorStopPosition = mh.vimState.editor.selection.start;
|
|
mh.vimState.cursorStartPosition = mh.vimState.editor.selection.start;
|
|
}
|
|
}
|
|
else {
|
|
await vscode.commands.executeCommand('default:replacePreviousChar', {
|
|
text: args.text,
|
|
replaceCharCnt: args.replaceCharCnt,
|
|
});
|
|
}
|
|
});
|
|
});
|
|
overrideCommand(context, 'compositionStart', async () => {
|
|
taskQueue_1.taskQueue.enqueueTask(async () => {
|
|
compositionState.isInComposition = true;
|
|
});
|
|
});
|
|
overrideCommand(context, 'compositionEnd', async () => {
|
|
taskQueue_1.taskQueue.enqueueTask(async () => {
|
|
const mh = await getAndUpdateModeHandler();
|
|
if (mh) {
|
|
if (compositionState.insertedText) {
|
|
mh.vimState.selectionsChanged.ignoreIntermediateSelections = true;
|
|
await vscode.commands.executeCommand('default:replacePreviousChar', {
|
|
text: '',
|
|
replaceCharCnt: compositionState.composingText.length,
|
|
});
|
|
mh.vimState.cursorStopPosition = mh.vimState.editor.selection.active;
|
|
mh.vimState.cursorStartPosition = mh.vimState.editor.selection.active;
|
|
mh.vimState.selectionsChanged.ignoreIntermediateSelections = false;
|
|
}
|
|
const text = compositionState.composingText;
|
|
await mh.handleMultipleKeyEvents(text.split(''));
|
|
}
|
|
compositionState.reset();
|
|
});
|
|
});
|
|
// Register extension commands
|
|
registerCommand(context, 'vim.showQuickpickCmdLine', async () => {
|
|
const mh = await getAndUpdateModeHandler();
|
|
if (mh) {
|
|
const cmd = await vscode.window.showInputBox({
|
|
prompt: 'Vim command line',
|
|
value: '',
|
|
ignoreFocusOut: false,
|
|
valueSelection: [0, 0],
|
|
});
|
|
if (cmd) {
|
|
await new commandLine_1.ExCommandLine(cmd, mh.vimState.currentMode).run(mh.vimState);
|
|
}
|
|
mh.updateView();
|
|
}
|
|
});
|
|
registerCommand(context, 'vim.remap', async (args) => {
|
|
taskQueue_1.taskQueue.enqueueTask(async () => {
|
|
const mh = await getAndUpdateModeHandler();
|
|
if (mh === undefined) {
|
|
return;
|
|
}
|
|
if (!args) {
|
|
throw new Error("'args' is undefined. For this remap to work it needs to have 'args' with an '\"after\": string[]' and/or a '\"commands\": { command: string; args: any[] }[]'");
|
|
}
|
|
if (args.after) {
|
|
for (const key of args.after) {
|
|
await mh.handleKeyEvent(notation_1.Notation.NormalizeKey(key, configuration_1.configuration.leader));
|
|
}
|
|
}
|
|
if (args.commands) {
|
|
for (const command of args.commands) {
|
|
// Check if this is a vim command by looking for :
|
|
if (command.command.startsWith(':')) {
|
|
await new commandLine_1.ExCommandLine(command.command.slice(1, command.command.length), mh.vimState.currentMode).run(mh.vimState);
|
|
mh.updateView();
|
|
}
|
|
else {
|
|
vscode.commands.executeCommand(command.command, command.args);
|
|
}
|
|
}
|
|
}
|
|
});
|
|
});
|
|
registerCommand(context, 'toggleVim', async () => {
|
|
configuration_1.configuration.disableExtension = !configuration_1.configuration.disableExtension;
|
|
toggleExtension(configuration_1.configuration.disableExtension, compositionState);
|
|
});
|
|
for (const boundKey of configuration_1.configuration.boundKeyCombinations) {
|
|
const command = ['<Esc>', '<C-c>'].includes(boundKey.key)
|
|
? async () => {
|
|
const mh = await getAndUpdateModeHandler();
|
|
if (mh && !(await forceStopRecursiveRemap(mh))) {
|
|
await mh.handleKeyEvent(`${boundKey.key}`);
|
|
}
|
|
}
|
|
: async () => {
|
|
const mh = await getAndUpdateModeHandler();
|
|
if (mh) {
|
|
await mh.handleKeyEvent(`${boundKey.key}`);
|
|
}
|
|
};
|
|
registerCommand(context, boundKey.command, async () => {
|
|
taskQueue_1.taskQueue.enqueueTask(command);
|
|
});
|
|
}
|
|
{
|
|
// Initialize mode handler for current active Text Editor at startup.
|
|
const modeHandler = await getAndUpdateModeHandler();
|
|
if (modeHandler) {
|
|
if (!configuration_1.configuration.startInInsertMode) {
|
|
const vimState = modeHandler.vimState;
|
|
// Make sure no cursors start on the EOL character (which is invalid in normal mode)
|
|
// This can happen if we quit last session in insert mode at the end of the line
|
|
vimState.cursors = vimState.cursors.map((cursor) => {
|
|
const eolColumn = vimState.document.lineAt(cursor.stop).text.length;
|
|
if (cursor.stop.character >= eolColumn) {
|
|
const character = Math.max(eolColumn - 1, 0);
|
|
return cursor.withNewStop(cursor.stop.with({ character }));
|
|
}
|
|
else {
|
|
return cursor;
|
|
}
|
|
});
|
|
}
|
|
// This is called last because getAndUpdateModeHandler() will change cursor
|
|
modeHandler.updateView({ drawSelection: true, revealRange: false });
|
|
}
|
|
}
|
|
// Disable automatic keyboard navigation in lists, so it doesn't interfere
|
|
// with our list navigation keybindings
|
|
await vscodeContext_1.VSCodeContext.set('listAutomaticKeyboardNavigation', false);
|
|
await toggleExtension(configuration_1.configuration.disableExtension, compositionState);
|
|
logger.debug('Finish.');
|
|
}
|
|
exports.activate = activate;
|
|
/**
|
|
* Toggles the VSCodeVim extension between Enabled mode and Disabled mode. This
|
|
* function is activated by calling the 'toggleVim' command from the Command Palette.
|
|
*
|
|
* @param isDisabled if true, sets VSCodeVim to Disabled mode; else sets to enabled mode
|
|
*/
|
|
async function toggleExtension(isDisabled, compositionState) {
|
|
await vscodeContext_1.VSCodeContext.set('vim.active', !isDisabled);
|
|
const mh = await getAndUpdateModeHandler();
|
|
if (mh) {
|
|
if (isDisabled) {
|
|
await mh.handleKeyEvent(specialKeys_1.SpecialKeys.ExtensionDisable);
|
|
compositionState.reset();
|
|
modeHandlerMap_1.ModeHandlerMap.clear();
|
|
}
|
|
else {
|
|
await mh.handleKeyEvent(specialKeys_1.SpecialKeys.ExtensionEnable);
|
|
}
|
|
}
|
|
}
|
|
function overrideCommand(context, command, callback) {
|
|
const disposable = vscode.commands.registerCommand(command, async (args) => {
|
|
if (configuration_1.configuration.disableExtension) {
|
|
return vscode.commands.executeCommand('default:' + command, args);
|
|
}
|
|
if (!vscode.window.activeTextEditor) {
|
|
return;
|
|
}
|
|
if (vscode.window.activeTextEditor.document &&
|
|
vscode.window.activeTextEditor.document.uri.toString() === 'debug:input') {
|
|
return vscode.commands.executeCommand('default:' + command, args);
|
|
}
|
|
return callback(args);
|
|
});
|
|
context.subscriptions.push(disposable);
|
|
}
|
|
function registerCommand(context, command, callback, requiresActiveEditor = true) {
|
|
const disposable = vscode.commands.registerCommand(command, async (args) => {
|
|
if (requiresActiveEditor && !vscode.window.activeTextEditor) {
|
|
return;
|
|
}
|
|
callback(args);
|
|
});
|
|
context.subscriptions.push(disposable);
|
|
}
|
|
exports.registerCommand = registerCommand;
|
|
function registerEventListener(context, event, listener, exitOnExtensionDisable = true, exitOnTests = false) {
|
|
const disposable = event(async (e) => {
|
|
if (exitOnExtensionDisable && configuration_1.configuration.disableExtension) {
|
|
return;
|
|
}
|
|
if (exitOnTests && globals_1.Globals.isTesting) {
|
|
return;
|
|
}
|
|
listener(e);
|
|
});
|
|
context.subscriptions.push(disposable);
|
|
}
|
|
exports.registerEventListener = registerEventListener;
|
|
/**
|
|
* @returns true if there was a remap being executed to stop
|
|
*/
|
|
async function forceStopRecursiveRemap(mh) {
|
|
if (mh.remapState.isCurrentlyPerformingRecursiveRemapping) {
|
|
mh.remapState.forceStopRecursiveRemapping = true;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
function handleContentChangedFromDisk(document) {
|
|
modeHandlerMap_1.ModeHandlerMap.getAll()
|
|
.filter((modeHandler) => modeHandler.vimState.documentUri === document.uri)
|
|
.forEach((modeHandler) => {
|
|
modeHandler.vimState.historyTracker = new historyTracker_1.HistoryTracker(modeHandler.vimState);
|
|
});
|
|
}
|
|
|
|
//# sourceMappingURL=extensionBase.js.map
|