diff --git a/extension.ts b/extension.ts index 7008a11ed66d..c33307b151d5 100644 --- a/extension.ts +++ b/extension.ts @@ -16,14 +16,13 @@ import { ModeHandler } from './src/mode/modeHandler'; import { ModeHandlerMap } from './src/mode/modeHandlerMap'; import { ModeName } from './src/mode/mode'; import { Notation } from './src/configuration/notation'; +import { Logger } from './src/util/logger'; import { Position } from './src/common/motion/position'; import { StatusBar } from './src/statusBar'; import { VsCodeContext } from './src/util/vscode-context'; import { commandLine } from './src/cmd_line/commandLine'; import { configuration } from './src/configuration/configuration'; -import { configurationValidator } from './src/configuration/configurationValidator'; import { globalState } from './src/state/globalState'; -import { logger } from './src/util/logger'; import { taskQueue } from './src/taskQueue'; let extensionContext: vscode.ExtensionContext; @@ -68,21 +67,38 @@ export async function getAndUpdateModeHandler(forceSyncAndUpdate = false): Promi return curHandler; } +async function loadConfiguration() { + const configurationErrors = await configuration.load(); + const logger = Logger.get('Configuration'); + + const numErrors = configurationErrors.filter(e => e.level === 'error').length; + logger.debug(`${numErrors} found with vim configuration`); + + if (numErrors > 0) { + for (let configurationError of configurationErrors) { + switch (configurationError.level) { + case 'error': + logger.error(configurationError.message); + break; + case 'warning': + logger.warn(configurationError.message); + break; + } + } + } +} export async function activate(context: vscode.ExtensionContext) { - logger.debug('Extension: activating vscodevim.'); - extensionContext = context; extensionContext.subscriptions.push(StatusBar); - logger.debug('Extension: registering event handlers.'); + await loadConfiguration(); // workspace events registerEventListener( context, vscode.workspace.onDidChangeConfiguration, - () => { - logger.debug('onDidChangeConfiguration: reloading configuration'); - configuration.reload(); + async () => { + await loadConfiguration(); }, false ); @@ -344,7 +360,6 @@ export async function activate(context: vscode.ExtensionContext) { await Promise.all([ commandLine.load(), globalState.load(), - configurationValidator.initialize(), // This is called last because getAndUpdateModeHandler() will change cursor toggleExtension(configuration.disableExtension, compositionState), ]); diff --git a/src/actions/plugins/imswitcher.ts b/src/actions/plugins/imswitcher.ts index 677c50a1a40a..58f59d137888 100644 --- a/src/actions/plugins/imswitcher.ts +++ b/src/actions/plugins/imswitcher.ts @@ -3,7 +3,7 @@ import { Globals } from '../../globals'; import { ModeName } from '../../mode/mode'; import { configuration } from '../../configuration/configuration'; import { exists } from 'fs'; -import { logger } from '../../util/logger'; +import { Logger } from '../../util/logger'; import { promisify } from 'util'; /** @@ -14,8 +14,9 @@ export class InputMethodSwitcher { this.execute = execute; } - private savedIMKey = ''; private execute: (cmd: string) => Promise; + private readonly logger = Logger.get('IMSwitcher'); + private savedIMKey = ''; public async switchInputMethod(prevMode: ModeName, newMode: ModeName) { if (configuration.autoSwitchInputMethod.enable !== true) { @@ -48,11 +49,11 @@ export class InputMethodSwitcher { this.savedIMKey = insertIMKey.trim(); } } catch (e) { - logger.error(`IMSwitcher: Error switching to default IM. err=${e}`); + this.logger.error(`Error switching to default IM. err=${e}`); } } else { - logger.error( - `IMSwitcher: Unable to find ${rawObtainIMCmd}. Check your 'vim.autoSwitchInputMethod.obtainIMCmd' in VSCode setting.` + this.logger.error( + `Unable to find ${rawObtainIMCmd}. Check your 'vim.autoSwitchInputMethod.obtainIMCmd' in VSCode setting.` ); this.disableIMSwitch(); } @@ -79,12 +80,12 @@ export class InputMethodSwitcher { try { await this.execute(switchIMCmd); } catch (e) { - logger.error(`IMSwitcher: Error switching to IM. err=${e}`); + this.logger.error(`Error switching to IM. err=${e}`); } } } else { - logger.error( - `IMSwitcher: Unable to find ${rawSwitchIMCmd}. Check your 'vim.autoSwitchInputMethod.switchIMCmd' in VSCode setting.` + this.logger.error( + `Unable to find ${rawSwitchIMCmd}. Check your 'vim.autoSwitchInputMethod.switchIMCmd' in VSCode setting.` ); this.disableIMSwitch(); } @@ -104,27 +105,27 @@ export class InputMethodSwitcher { } private disableIMSwitch() { - logger.warn('IMSwitcher: disabling IMSwitch'); + this.logger.warn('disabling IMSwitch'); configuration.autoSwitchInputMethod.enable = false; } private isConfigurationValid(): boolean { let switchIMCmd = configuration.autoSwitchInputMethod.switchIMCmd; if (!switchIMCmd.includes('{im}')) { - logger.error( - 'IMSwitcher: vim.autoSwitchInputMethod.switchIMCmd is incorrect, \ + this.logger.error( + 'vim.autoSwitchInputMethod.switchIMCmd is incorrect, \ it should contain the placeholder {im}' ); return false; } let obtainIMCmd = configuration.autoSwitchInputMethod.obtainIMCmd; if (obtainIMCmd === undefined || obtainIMCmd === '') { - logger.error('IMSwitcher: vim.autoSwitchInputMethod.obtainIMCmd is empty'); + this.logger.error('vim.autoSwitchInputMethod.obtainIMCmd is empty'); return false; } let defaultIMKey = configuration.autoSwitchInputMethod.defaultIM; if (defaultIMKey === undefined || defaultIMKey === '') { - logger.error('IMSwitcher: vim.autoSwitchInputMethod.defaultIM is empty'); + this.logger.error('vim.autoSwitchInputMethod.defaultIM is empty'); return false; } return true; diff --git a/src/cmd_line/commandLine.ts b/src/cmd_line/commandLine.ts index ef86dcab413b..242c997648f1 100644 --- a/src/cmd_line/commandLine.ts +++ b/src/cmd_line/commandLine.ts @@ -2,14 +2,15 @@ import * as parser from './parser'; import * as vscode from 'vscode'; import { CommandLineHistory } from '../history/historyFile'; import { ModeName } from './../mode/mode'; +import { Logger } from '../util/logger'; import { StatusBar } from '../statusBar'; import { VimError, ErrorCode } from '../error'; import { VimState } from '../state/vimState'; import { configuration } from '../configuration/configuration'; -import { logger } from '../util/logger'; class CommandLine { private _history: CommandLineHistory; + private readonly _logger = Logger.get('CommandLine'); /** * Index used for navigating commandline history with and @@ -78,14 +79,14 @@ class CommandLine { ); } } else { - logger.error(`commandLine: Error executing cmd=${command}. err=${e}.`); + this._logger.error(`Error executing cmd=${command}. err=${e}.`); } } } public async PromptAndRun(initialText: string, vimState: VimState): Promise { if (!vscode.window.activeTextEditor) { - logger.debug('commandLine: No active document'); + this._logger.debug('No active document'); return; } let cmd = await vscode.window.showInputBox(this.getInputBoxOptions(initialText)); @@ -103,7 +104,7 @@ class CommandLine { public async ShowHistory(initialText: string, vimState: VimState): Promise { if (!vscode.window.activeTextEditor) { - logger.debug('commandLine: No active document.'); + this._logger.debug('No active document.'); return ''; } diff --git a/src/cmd_line/commands/file.ts b/src/cmd_line/commands/file.ts index 87b93e930c59..ffbc617516c6 100644 --- a/src/cmd_line/commands/file.ts +++ b/src/cmd_line/commands/file.ts @@ -3,9 +3,9 @@ import * as node from '../node'; import * as path from 'path'; import * as util from 'util'; import * as vscode from 'vscode'; -import { logger } from '../../util/logger'; +import untildify = require('untildify'); +import { Logger } from '../../util/logger'; -const untildify = require('untildify'); const doesFileExist = util.promisify(fs.exists); export enum FilePosition { @@ -23,6 +23,7 @@ export interface IFileCommandArguments extends node.ICommandArgs { export class FileCommand extends node.CommandBase { protected _arguments: IFileCommandArguments; + private readonly _logger = Logger.get('File'); constructor(args: IFileCommandArguments) { super(); @@ -111,7 +112,7 @@ export class FileCommand extends node.CommandBase { if (this._arguments.createFileIfNotExists) { await util.promisify(fs.close)(await util.promisify(fs.open)(filePath, 'w')); } else { - logger.error(`file: ${filePath} does not exist.`); + this._logger.error(`${filePath} does not exist.`); return; } } diff --git a/src/cmd_line/commands/write.ts b/src/cmd_line/commands/write.ts index fbff94a90c3e..5360daca3185 100644 --- a/src/cmd_line/commands/write.ts +++ b/src/cmd_line/commands/write.ts @@ -3,9 +3,9 @@ import * as node from '../node'; import * as path from 'path'; import * as util from 'util'; import * as vscode from 'vscode'; +import { Logger } from '../../util/logger'; import { StatusBar } from '../../statusBar'; import { VimState } from '../../state/vimState'; -import { logger } from '../../util/logger'; export interface IWriteCommandArguments extends node.ICommandArgs { opt?: string; @@ -23,6 +23,7 @@ export interface IWriteCommandArguments extends node.ICommandArgs { // export class WriteCommand extends node.CommandBase { protected _arguments: IWriteCommandArguments; + private readonly _logger = Logger.get('Write'); constructor(args: IWriteCommandArguments) { super(); @@ -36,16 +37,16 @@ export class WriteCommand extends node.CommandBase { async execute(vimState: VimState): Promise { if (this.arguments.opt) { - logger.warn('write: not implemented'); + this._logger.warn('not implemented'); return; } else if (this.arguments.file) { - logger.warn('write: not implemented'); + this._logger.warn('not implemented'); return; } else if (this.arguments.append) { - logger.warn('write: not implemented'); + this._logger.warn('not implemented'); return; } else if (this.arguments.cmd) { - logger.warn('write: not implemented'); + this._logger.warn('not implemented'); return; } diff --git a/src/cmd_line/parser.ts b/src/cmd_line/parser.ts index 6210b155fcc4..e83c79b501a8 100644 --- a/src/cmd_line/parser.ts +++ b/src/cmd_line/parser.ts @@ -1,9 +1,9 @@ import * as lexer from './lexer'; import * as node from './node'; import * as token from './token'; +import { Logger } from '../util/logger'; import { VimError, ErrorCode } from '../error'; import { commandParsers } from './subparser'; -import { logger } from '../util/logger'; interface IParseFunction { (state: ParserState, command: node.CommandLine): IParseFunction | null; @@ -20,6 +20,8 @@ export function parse(input: string): node.CommandLine { } function parseLineRange(state: ParserState, commandLine: node.CommandLine): IParseFunction | null { + const logger = Logger.get('Parser'); + while (true) { let tok = state.next(); switch (tok.type) { diff --git a/src/configuration/configuration.ts b/src/configuration/configuration.ts index f1836965b288..50fade31de3e 100644 --- a/src/configuration/configuration.ts +++ b/src/configuration/configuration.ts @@ -1,8 +1,9 @@ import * as vscode from 'vscode'; - +import { ConfigurationError } from './configurationError'; import { Globals } from '../globals'; import { Notation } from './notation'; -import { taskQueue } from '../taskQueue'; +import { VsCodeContext } from '../util/vscode-context'; +import { configurationValidator } from './configurationValidator'; import { IConfiguration, IKeyRemapping, @@ -10,7 +11,6 @@ import { IAutoSwitchInputMethod, IDebugConfiguration, } from './iconfiguration'; -import { VsCodeContext } from '../util/vscode-context'; const packagejson: { contributes: { @@ -66,15 +66,13 @@ class Configuration implements IConfiguration { 'underline-thin': vscode.TextEditorCursorStyle.UnderlineThin, }; - constructor() { - this.reload(); - } - - reload() { + public async load(): Promise { let vimConfigs: any = Globals.isTesting ? Globals.mockConfiguration : this.getConfiguration('vim'); + let configurationErrors = new Array(); + /* tslint:disable:forin */ // Disable forin rule here as we make accessors enumerable.` for (const option in this) { @@ -99,7 +97,20 @@ class Configuration implements IConfiguration { this.visualModeKeyBindingsNonRecursive, ]; for (const keybindings of keybindingList) { - for (let remapping of keybindings) { + const keybindingMap: { [key: string]: {} } = Object.create(null); + + for (let i = keybindings.length - 1; i >= 0; i--) { + let remapping = keybindings[i]; + + let remappingErrors = await configurationValidator.isRemappingValid(remapping); + configurationErrors = configurationErrors.concat(remappingErrors); + + if (remappingErrors.filter(e => e.level === 'error').length > 0) { + // errors with remapping, skip + keybindings.splice(i, 1); + continue; + } + if (remapping.before) { remapping.before.forEach( (key, idx) => (remapping.before[idx] = Notation.NormalizeKey(key, this.leader)) @@ -111,11 +122,22 @@ class Configuration implements IConfiguration { (key, idx) => (remapping.after![idx] = Notation.NormalizeKey(key, this.leader)) ); } + + const keys = remapping.before.join(''); + if (keys in keybindingMap) { + configurationErrors.push({ + level: 'error', + message: `${remapping.before}. Duplicate remapped key for ${keys}.`, + }); + continue; + } else { + keybindingMap[keys] = {}; + } } } + // wrap keys this.wrapKeys = {}; - for (const wrapKey of this.whichwrap.split(',')) { this.wrapKeys[wrapKey] = true; } @@ -164,11 +186,13 @@ class Configuration implements IConfiguration { VsCodeContext.Set('vim.overrideCopy', this.overrideCopy); VsCodeContext.Set('vim.overrideCtrlC', this.overrideCopy || this.useCtrlKeys); + + return configurationErrors; } getConfiguration(section: string = ''): vscode.WorkspaceConfiguration { - let activeTextEditor = vscode.window.activeTextEditor; - let resource = activeTextEditor ? activeTextEditor.document.uri : undefined; + const activeTextEditor = vscode.window.activeTextEditor; + const resource = activeTextEditor ? activeTextEditor.document.uri : undefined; return vscode.workspace.getConfiguration(section, resource); } @@ -380,22 +404,21 @@ function overlapSetting(args: { return; } - taskQueue.enqueueTask(async () => { - if (args.map) { - for (let [vscodeSetting, vimSetting] of args.map.entries()) { - if (value === vimSetting) { - value = vscodeSetting; - break; - } + if (args.map) { + for (let [vscodeSetting, vimSetting] of args.map.entries()) { + if (value === vimSetting) { + value = vscodeSetting; + break; } } + } - await this.getConfiguration('editor').update( - args.settingName, - value, - vscode.ConfigurationTarget.Global - ); - }, 'config'); + // update configuration asynchronously + this.getConfiguration('editor').update( + args.settingName, + value, + vscode.ConfigurationTarget.Global + ); }, enumerable: true, configurable: true, diff --git a/src/configuration/configurationError.ts b/src/configuration/configurationError.ts new file mode 100644 index 000000000000..92ed822ed1b2 --- /dev/null +++ b/src/configuration/configurationError.ts @@ -0,0 +1,4 @@ +export interface ConfigurationError { + level: 'error' | 'warning'; + message: string; +} diff --git a/src/configuration/configurationValidator.ts b/src/configuration/configurationValidator.ts index cc7574d57d36..ed94292241ea 100644 --- a/src/configuration/configurationValidator.ts +++ b/src/configuration/configurationValidator.ts @@ -1,21 +1,50 @@ import * as vscode from 'vscode'; +import { IKeyRemapping } from './iconfiguration'; +import { ConfigurationError } from './configurationError'; class ConfigurationValidator { - private map: Map; + private _commandMap: Map; - public async initialize(): Promise { - this.map = new Map( - (await vscode.commands.getCommands(true)).map(x => [x, true] as [string, boolean]) - ); - } - - public isCommandValid(command: string): boolean { + public async isCommandValid(command: string): Promise { if (command.startsWith(':')) { return true; } - return this.map.get(command) || false; + return (await this.getCommandMap()).get(command) || false; + } + + public async isRemappingValid(remapping: IKeyRemapping): Promise { + if (!remapping.after && !remapping.commands) { + return [{ level: 'error', message: `${remapping.before} missing 'after' key or 'command'.` }]; + } + + if (remapping.commands) { + for (const command of remapping.commands) { + let cmd: string; + + if (typeof command === 'string') { + cmd = command; + } else { + cmd = command.command; + } + + if (!(await configurationValidator.isCommandValid(cmd))) { + return [{ level: 'warning', message: `${cmd} does not exist.` }]; + } + } + } + + return []; + } + + async getCommandMap(): Promise> { + if (this._commandMap == null) { + this._commandMap = new Map( + (await vscode.commands.getCommands(true)).map(x => [x, true] as [string, boolean]) + ); + } + return this._commandMap; } } -export let configurationValidator = new ConfigurationValidator(); +export const configurationValidator = new ConfigurationValidator(); diff --git a/src/configuration/iconfiguration.ts b/src/configuration/iconfiguration.ts index 6a17955aeb89..405602965774 100644 --- a/src/configuration/iconfiguration.ts +++ b/src/configuration/iconfiguration.ts @@ -32,13 +32,13 @@ export interface IDebugConfiguration { * Maximum level of messages to show as VS Code information message * Supported values: ['error', 'warn', 'info', 'verbose', 'debug'] */ - loggingLevelForAlert: string; + loggingLevelForAlert: 'error' | 'warn' | 'info' | 'verbose' | 'debug'; /** * Maximum level of messages to log to console. * Supported values: ['error', 'warn', 'info', 'verbose', 'debug'] */ - loggingLevelForConsole: string; + loggingLevelForConsole: 'error' | 'warn' | 'info' | 'verbose' | 'debug'; } export interface IConfiguration { @@ -197,6 +197,10 @@ export interface IConfiguration { */ relativenumber: boolean; + /** + * keywords contain alphanumeric characters and '_'. + * If not configured `editor.wordSeparators` is used + */ iskeyword: string; /** @@ -253,7 +257,7 @@ export interface IConfiguration { visualModeKeyBindingsNonRecursive: IKeyRemapping[]; /** - * emulate whichwrap + * Comma-separated list of motion keys that should wrap to next/previous line. */ whichwrap: string; diff --git a/src/configuration/remapper.ts b/src/configuration/remapper.ts index a6583d5df5fe..0a0528388019 100644 --- a/src/configuration/remapper.ts +++ b/src/configuration/remapper.ts @@ -1,14 +1,12 @@ import * as _ from 'lodash'; import * as vscode from 'vscode'; - import { IKeyRemapping } from './iconfiguration'; +import { Logger } from '../util/logger'; import { ModeHandler } from '../mode/modeHandler'; import { ModeName } from '../mode/mode'; import { VimState } from './../state/vimState'; import { commandLine } from '../cmd_line/commandLine'; import { configuration } from '../configuration/configuration'; -import { configurationValidator } from './configurationValidator'; -import { logger } from '../util/logger'; interface IRemapper { /** @@ -57,6 +55,7 @@ export class Remapper implements IRemapper { private readonly _configKey: string; private readonly _remappedModes: ModeName[]; private readonly _recursive: boolean; + private readonly _logger = Logger.get('Remapper'); private _isPotentialRemap = false; get isPotentialRemap(): boolean { @@ -82,8 +81,8 @@ export class Remapper implements IRemapper { const userDefinedRemappings = this._getRemappings(); - logger.debug( - `Remapper: find matching remap. keys=${keys}. mode=${ModeName[vimState.currentMode]}.` + this._logger.debug( + `find matching remap. keys=${keys}. mode=${ModeName[vimState.currentMode]}.` ); let remapping: IKeyRemapping | undefined = Remapper.findMatchingRemap( userDefinedRemappings, @@ -92,8 +91,8 @@ export class Remapper implements IRemapper { ); if (remapping) { - logger.debug( - `Remapper: ${this._configKey}. match found. before=${remapping.before}. after=${ + this._logger.debug( + `${this._configKey}. match found. before=${remapping.before}. after=${ remapping.after }. command=${remapping.commands}.` ); @@ -191,7 +190,6 @@ export class Remapper implements IRemapper { debugMsg += `after=${remapping.after}. `; } - let isCommandValid = true; if (remapping.commands) { for (const command of remapping.commands) { let cmd: string; @@ -206,31 +204,11 @@ export class Remapper implements IRemapper { } debugMsg += `command=${cmd}. args=${args}.`; - - if (!configurationValidator.isCommandValid(cmd)) { - debugMsg += ` Command does not exist.`; - isCommandValid = false; - } } } - if (!remapping.after && !remapping.commands) { - logger.error( - `Remapper: ${this._configKey}. - Invalid configuration. Missing 'after' key or 'command'. ${debugMsg}` - ); - continue; - } - - const keys = remapping.before.join(''); - if (keys in remappings) { - logger.error(`Remapper: ${this._configKey}. Duplicate configuration. ${debugMsg}`); - continue; - } - - let log = (msg: string) => (isCommandValid ? logger.debug(msg) : logger.warn(msg)); - log(`Remapper: ${this._configKey}. ${debugMsg}`); - remappings[keys] = remapping; + this._logger.debug(`${this._configKey}. ${debugMsg}`); + remappings[remapping.before.join('')] = remapping; } return remappings; diff --git a/src/history/historyFile.ts b/src/history/historyFile.ts index 46825a30f0db..31bad299e12c 100644 --- a/src/history/historyFile.ts +++ b/src/history/historyFile.ts @@ -1,13 +1,14 @@ import * as fs from 'fs'; import * as path from 'path'; -import { promisify } from 'util'; +import { Logger } from '../util/logger'; import { configuration } from '../configuration/configuration'; -import { logger } from '../util/logger'; import { getExtensionDirPath } from '../util/util'; +import { promisify } from 'util'; const mkdirp = require('mkdirp'); export class HistoryFile { + private readonly _logger = Logger.get('HistoryFile'); private _historyFileName: string; private _historyDir: string; private _history: string[] = []; @@ -57,7 +58,7 @@ export class HistoryFile { this._history = []; fs.unlinkSync(this.historyFilePath); } catch (err) { - logger.warn(`historyFile: Unable to delete ${this.historyFilePath}. err=${err}.`); + this._logger.warn(`Unable to delete ${this.historyFilePath}. err=${err}.`); } } @@ -68,9 +69,9 @@ export class HistoryFile { data = await promisify(fs.readFile)(this.historyFilePath, 'utf-8'); } catch (err) { if (err.code === 'ENOENT') { - logger.debug(`historyFile: History does not exist. path=${this._historyDir}`); + this._logger.debug(`History does not exist. path=${this._historyDir}`); } else { - logger.warn(`historyFile: Failed to load history. path=${this._historyDir} err=${err}.`); + this._logger.warn(`Failed to load history. path=${this._historyDir} err=${err}.`); } return; } @@ -86,9 +87,7 @@ export class HistoryFile { } this._history = parsedData; } catch (e) { - logger.warn( - `historyFile: Deleting corrupted history file. path=${this._historyDir} err=${e}.` - ); + this._logger.warn(`Deleting corrupted history file. path=${this._historyDir} err=${e}.`); this.clear(); } } @@ -100,9 +99,7 @@ export class HistoryFile { // create file await promisify(fs.writeFile)(this.historyFilePath, JSON.stringify(this._history), 'utf-8'); } catch (err) { - logger.error( - `historyFile: Failed to save history. filepath=${this.historyFilePath}. err=${err}.` - ); + this._logger.error(`Failed to save history. filepath=${this.historyFilePath}. err=${err}.`); throw err; } } diff --git a/src/history/historyTracker.ts b/src/history/historyTracker.ts index 589bc9c64261..28ae2dc72545 100644 --- a/src/history/historyTracker.ts +++ b/src/history/historyTracker.ts @@ -15,9 +15,9 @@ import * as vscode from 'vscode'; import { Position } from './../common/motion/position'; import { RecordedState } from './../state/recordedState'; +import { Logger } from './../util/logger'; import { VimState } from './../state/vimState'; import { TextEditor } from './../textEditor'; -import { logger } from './../util/logger'; const diffEngine = new DiffMatchPatch.diff_match_patch(); diffEngine.Diff_Timeout = 1; // 1 second @@ -156,6 +156,7 @@ class HistoryStep { } export class HistoryTracker { + private readonly _logger = Logger.get('DocumentChange'); public lastContentChanges: vscode.TextDocumentContentChangeEvent[]; public currentContentChanges: vscode.TextDocumentContentChangeEvent[]; @@ -183,9 +184,9 @@ export class HistoryTracker { private get currentHistoryStep(): HistoryStep { if (this.currentHistoryStepIndex === -1) { - let msg = 'HistoryTracker: Tried to modify history at index -1'; - logger.debug(msg); - throw new Error(msg); + const msg = 'Tried to modify history at index -1'; + this._logger.warn(msg); + throw new Error('HistoryTracker:' + msg); } return this.historySteps[this.currentHistoryStepIndex]; @@ -455,7 +456,7 @@ export class HistoryTracker { */ async undoAndRemoveChanges(n: number): Promise { if (this.currentHistoryStep.changes.length < n) { - logger.debug('HistoryTracker: Something bad happened in removeChange'); + this._logger.warn('Something bad happened in removeChange'); return; } diff --git a/src/mode/modeHandler.ts b/src/mode/modeHandler.ts index 319d5117a990..2f19007ba9b2 100644 --- a/src/mode/modeHandler.ts +++ b/src/mode/modeHandler.ts @@ -6,6 +6,7 @@ import { BaseMovement, isIMovement } from './../actions/motion'; import { CommandInsertInInsertMode, CommandInsertPreviousText } from './../actions/commands/insert'; import { Decoration } from '../configuration/decoration'; import { Jump } from '../jumps/jump'; +import { Logger } from '../util/logger'; import { Mode, ModeName, VSCodeVimCursorType } from './mode'; import { PairMatcher } from './../common/matching/matcher'; import { Position, PositionDiff } from './../common/motion/position'; @@ -20,7 +21,6 @@ import { VsCodeContext } from '../util/vscode-context'; import { commandLine } from '../cmd_line/commandLine'; import { configuration } from '../configuration/configuration'; import { getCursorsAfterSync } from '../util/util'; -import { logger } from '../util/logger'; import { BaseCommand, CommandQuitRecordMacro, @@ -36,6 +36,7 @@ export class ModeHandler implements vscode.Disposable { private _disposables: vscode.Disposable[] = []; private _modes: Mode[]; private _remappers: Remappers; + private readonly _logger = Logger.get('ModeHandler'); public vimState: VimState; @@ -234,7 +235,7 @@ export class ModeHandler implements vscode.Disposable { public async handleKeyEvent(key: string): Promise { const now = Number(new Date()); - logger.debug(`ModeHandler: handling key=${key}.`); + this._logger.debug(`handling key=${key}.`); // rewrite copy if (configuration.overrideCopy) { @@ -301,7 +302,7 @@ export class ModeHandler implements vscode.Disposable { this.vimState = await this.handleKeyEventHelper(key, this.vimState); } } catch (e) { - logger.error(`ModeHandler: error handling key=${key}. err=${e}.`); + this._logger.error(`error handling key=${key}. err=${e}.`); throw e; } @@ -735,7 +736,7 @@ export class ModeHandler implements vscode.Disposable { let recordedState = vimState.recordedState; if (!recordedState.operator) { - logger.warn('recordedState.operator: ' + recordedState.operator); + this._logger.warn('recordedState.operator: ' + recordedState.operator); throw new Error("what in god's name. recordedState.operator is falsy."); } @@ -849,7 +850,7 @@ export class ModeHandler implements vscode.Disposable { case 'moveCursor': break; default: - logger.warn(`modeHandler: Unhandled text transformation type: ${command.type}.`); + this._logger.warn(`Unhandled text transformation type: ${command.type}.`); break; } @@ -868,7 +869,7 @@ export class ModeHandler implements vscode.Disposable { if (textTransformations.length > 0) { if (areAnyTransformationsOverlapping(textTransformations)) { - logger.debug( + this._logger.debug( `Text transformations are overlapping. Falling back to serial transformations. This is generally a very bad sign. Try to make your text transformations operate on non-overlapping ranges.` @@ -991,7 +992,7 @@ export class ModeHandler implements vscode.Disposable { } break; default: - logger.warn(`modeHandler: Unhandled text transformation type: ${command.type}.`); + this._logger.warn(`Unhandled text transformation type: ${command.type}.`); break; } } @@ -1258,7 +1259,7 @@ export class ModeHandler implements vscode.Disposable { break; } default: { - logger.error(`ModeHandler: unexpected selection mode. selectionMode=${selectionMode}`); + this._logger.error(`unexpected selection mode. selectionMode=${selectionMode}`); break; } } diff --git a/src/mode/modes.ts b/src/mode/modes.ts index 4b92e718629c..8725b687ebae 100644 --- a/src/mode/modes.ts +++ b/src/mode/modes.ts @@ -1,9 +1,9 @@ -import { Position } from './../common/motion/position'; +import { Logger } from '../util/logger'; import { Mode, ModeName } from './mode'; +import { Position } from './../common/motion/position'; +import { SearchDirection } from '../state/searchState'; import { VSCodeVimCursorType } from './mode'; import { VimState } from '../state/vimState'; -import { SearchDirection } from '../state/searchState'; -import { logger } from '../util/logger'; export enum VisualBlockInsertionType { /** @@ -60,13 +60,15 @@ export class VisualLineMode extends Mode { } export class SearchInProgressMode extends Mode { + private readonly _logger = Logger.get('SearchInProgressMode'); + constructor() { super(ModeName.SearchInProgressMode, '', VSCodeVimCursorType.Block); } getStatusBarText(vimState: VimState): string { if (vimState.globalState.searchState === undefined) { - logger.warn(`SearchInProgressMode: vimState.globalState.searchState is undefined.`); + this._logger.warn(`vimState.globalState.searchState is undefined.`); return ''; } const leadingChar = diff --git a/src/neovim/neovim.ts b/src/neovim/neovim.ts index 4f00f4acbb26..130f2512b8cc 100644 --- a/src/neovim/neovim.ts +++ b/src/neovim/neovim.ts @@ -1,5 +1,6 @@ import * as util from 'util'; import * as vscode from 'vscode'; +import { Logger } from '../util/logger'; import { Position } from './../common/motion/position'; import { Register, RegisterMode } from '../register/register'; import { TextEditor } from '../textEditor'; @@ -7,13 +8,13 @@ import { VimState } from './../state/vimState'; import { configuration } from '../configuration/configuration'; import { dirname } from 'path'; import { exists } from 'fs'; -import { logger } from '../util/logger'; import { spawn, ChildProcess } from 'child_process'; import { attach, Neovim } from 'neovim'; export class NeovimWrapper implements vscode.Disposable { private process: ChildProcess; private nvim: Neovim; + private readonly logger = Logger.get('Neovim'); async run(vimState: VimState, command: string) { if (!this.nvim) { @@ -29,7 +30,7 @@ export class NeovimWrapper implements vscode.Disposable { const apiInfo = await this.nvim.apiInfo; const version = apiInfo[1].version; - logger.debug(`Neovim Version: ${version.major}.${version.minor}.${version.patch}`); + this.logger.debug(`version: ${version.major}.${version.minor}.${version.patch}`); } await this.syncVSToVim(vimState); @@ -40,7 +41,7 @@ export class NeovimWrapper implements vscode.Disposable { await this.nvim.command('let v:errmsg="" | let v:statusmsg=""'); // Execute the command - logger.debug(`Neovim: Running ${command}.`); + this.logger.debug(`Running ${command}.`); await this.nvim.input(command); const mode = await this.nvim.mode; if (mode.blocking) { @@ -67,7 +68,7 @@ export class NeovimWrapper implements vscode.Disposable { } private async startNeovim() { - logger.debug('Neovim: Spawning Neovim process...'); + this.logger.debug('Spawning Neovim process...'); let dir = dirname(vscode.window.activeTextEditor!.document.uri.fsPath); if (!(await util.promisify(exists)(dir))) { dir = __dirname; @@ -77,7 +78,7 @@ export class NeovimWrapper implements vscode.Disposable { }); this.process.on('error', err => { - logger.error(`Neovim: Error spawning neovim. Error=${err.message}.`); + this.logger.error(`Error spawning neovim. Error=${err.message}.`); configuration.enableNeovim = false; }); return attach({ proc: this.process }); @@ -150,7 +151,7 @@ export class NeovimWrapper implements vscode.Disposable { fixedLines.join('\n') ); - logger.debug(`Neovim: ${lines.length} lines in nvim. ${TextEditor.getLineCount()} in editor.`); + this.logger.debug(`${lines.length} lines in nvim. ${TextEditor.getLineCount()} in editor.`); let [row, character] = ((await this.nvim.callFunction('getpos', ['.'])) as Array).slice( 1, diff --git a/src/state/vimState.ts b/src/state/vimState.ts index 6f11bc890a80..06fe1c23e1b8 100644 --- a/src/state/vimState.ts +++ b/src/state/vimState.ts @@ -5,6 +5,7 @@ import { EasyMotion } from './../actions/plugins/easymotion/easymotion'; import { EditorIdentity } from './../editorIdentity'; import { HistoryTracker } from './../history/historyTracker'; import { InputMethodSwitcher } from '../actions/plugins/imswitcher'; +import { Logger } from '../util/logger'; import { ModeName } from '../mode/mode'; import { NeovimWrapper } from '../neovim/neovim'; import { Position } from './../common/motion/position'; @@ -13,7 +14,6 @@ import { RecordedState } from './recordedState'; import { RegisterMode } from './../register/register'; import { ReplaceState } from './../state/replaceState'; import { globalState } from './../state/globalState'; -import { logger } from '../util/logger'; /** * The VimState class holds permanent state that carries over from action @@ -23,6 +23,8 @@ import { logger } from '../util/logger'; * indicate what they want to do. */ export class VimState implements vscode.Disposable { + private readonly logger = Logger.get('VimState'); + /** * The column the cursor wants to be at, or Number.POSITIVE_INFINITY if it should always * be the rightmost column. @@ -151,7 +153,7 @@ export class VimState implements vscode.Disposable { public set allCursors(value: Range[]) { for (const cursor of value) { if (!cursor.start.isValid(this.editor) || !cursor.stop.isValid(this.editor)) { - logger.debug('VimState: invalid value for set cursor position. This is probably bad?'); + this.logger.debug('invalid value for set cursor position.'); } } diff --git a/src/taskQueue.ts b/src/taskQueue.ts index 6f2acda9ea58..8a1eac0e856d 100644 --- a/src/taskQueue.ts +++ b/src/taskQueue.ts @@ -1,4 +1,5 @@ import * as _ from 'lodash'; +import { Logger } from './util/logger'; interface IEnqueuedTask { promise: () => Promise; @@ -8,7 +9,8 @@ interface IEnqueuedTask { } class TaskQueue { - private _taskQueue: { + private readonly _logger = Logger.get('TaskQueue'); + private readonly _taskQueue: { [key: string]: { tasks: IEnqueuedTask[]; }; @@ -37,7 +39,7 @@ class TaskQueue { await task.promise(); task.isRunning = false; } catch (e) { - console.error(`TaskQueue: error running task. err=${e}.`); + this._logger.error(`Error running task. err=${e}.`); } finally { this.dequeueTask(task); } diff --git a/src/util/clipboard.ts b/src/util/clipboard.ts index 377a0654dcfe..cdff7a161922 100644 --- a/src/util/clipboard.ts +++ b/src/util/clipboard.ts @@ -1,12 +1,14 @@ -import { logger } from './logger'; import * as vscode from 'vscode'; +import { Logger } from './logger'; export class Clipboard { + private static readonly logger = Logger.get('Clipboard'); + public static async Copy(text: string): Promise { try { await vscode.env.clipboard.writeText(text); } catch (e) { - logger.error(e, `Clipboard: Error copying to clipboard. err=${e}`); + this.logger.error(e, `Error copying to clipboard. err=${e}`); } } diff --git a/src/util/logger.ts b/src/util/logger.ts index f1b6462d08e7..e13156650afb 100644 --- a/src/util/logger.ts +++ b/src/util/logger.ts @@ -4,18 +4,28 @@ import * as winston from 'winston'; import { ConsoleForElectron } from 'winston-console-for-electron'; import { configuration } from '../configuration/configuration'; +interface VsCodeMessageOptions extends TransportStream.TransportStreamOptions { + section?: string; +} + /** * Implementation of Winston transport * Displays VS Code message to user */ class VsCodeMessage extends TransportStream { + section?: string; actionMessages = ['Dismiss', 'Suppress Errors']; + constructor(options: VsCodeMessageOptions) { + super(options); + + this.section = options.section; + } + public async log(info: { level: string; message: string }, callback: Function) { if (configuration.debug.silent) { return; } - let showMessage: (message: string, ...items: string[]) => Thenable; switch (info.level) { case 'error': @@ -33,7 +43,12 @@ class VsCodeMessage extends TransportStream { throw 'Unsupported ' + info.level; } - let selectedAction = await showMessage(info.message, ...this.actionMessages); + let message = info.message; + if (this.section) { + message = this.section + ': ' + message; + } + + let selectedAction = await showMessage(message, ...this.actionMessages); if (selectedAction === 'Suppress Errors') { vscode.window.showInformationMessage( 'Ignorance is bliss; temporarily suppressing log messages. For more permanence, please configure `vim.debug.suppress`.' @@ -47,15 +62,20 @@ class VsCodeMessage extends TransportStream { } } -export const logger = winston.createLogger({ - format: winston.format.simple(), - transports: [ - new ConsoleForElectron({ - level: configuration.debug.loggingLevelForConsole, - silent: configuration.debug.silent, - }), - new VsCodeMessage({ - level: configuration.debug.loggingLevelForAlert, - }), - ], -}); +export class Logger { + static get(section?: string): winston.Logger { + return winston.createLogger({ + format: winston.format.simple(), + transports: [ + new ConsoleForElectron({ + level: configuration.debug.loggingLevelForConsole, + silent: configuration.debug.silent, + }), + new VsCodeMessage({ + level: configuration.debug.loggingLevelForAlert, + section: section, + }), + ], + }); + } +} diff --git a/src/util/util.ts b/src/util/util.ts index a14ac3bc6c20..9da0842ef1c5 100644 --- a/src/util/util.ts +++ b/src/util/util.ts @@ -1,11 +1,10 @@ import * as vscode from 'vscode'; +import AppDirectory = require('appdirectory'); +import { Logger } from './logger'; import { Position } from '../common/motion/position'; import { Range } from '../common/motion/range'; -import { logger } from './logger'; import { exec } from 'child_process'; -const AppDirectory = require('appdirectory'); - /** * This is certainly quite janky! The problem we're trying to solve * is that writing editor.selection = new Position() won't immediately @@ -27,6 +26,7 @@ export async function waitForCursorSync( } export async function getCursorsAfterSync(timeoutInMilliseconds: number = 0): Promise { + const logger = Logger.get('getCursorsAfterSync'); try { await waitForCursorSync(timeoutInMilliseconds, true); } catch (e) { @@ -39,7 +39,9 @@ export async function getCursorsAfterSync(timeoutInMilliseconds: number = 0): Pr } export function getExtensionDirPath(): string { + const logger = Logger.get('getExtensionDirPath'); const dirs = new AppDirectory('VSCodeVim'); + logger.debug('VSCodeVim Cache Directory: ' + dirs.userCache()); return dirs.userCache();