diff --git a/src/actions/actions.ts b/src/actions/actions.ts index 6bd84e7ad68..0150c2bef04 100644 --- a/src/actions/actions.ts +++ b/src/actions/actions.ts @@ -1489,7 +1489,7 @@ class CommandMoveHalfPageDown extends BaseMovement { public async execAction(position: Position, vimState: VimState): Promise { return new Position( - Math.min(TextEditor.getLineCount() - 1, position.line + Configuration.getInstance().get("scroll")), + Math.min(TextEditor.getLineCount() - 1, position.line + Configuration.getInstance().scroll), position.character ); } @@ -1500,7 +1500,7 @@ class CommandMoveHalfPageUp extends BaseMovement { keys = ["ctrl+u"]; public async execAction(position: Position, vimState: VimState): Promise { - return new Position(Math.max(0, position.line - Configuration.getInstance().get("scroll")), position.character); + return new Position(Math.max(0, position.line - Configuration.getInstance().scroll), position.character); } } diff --git a/src/cmd_line/commands/setoptions.ts b/src/cmd_line/commands/setoptions.ts index 9aa88aefcd3..9c51e5f2a0d 100644 --- a/src/cmd_line/commands/setoptions.ts +++ b/src/cmd_line/commands/setoptions.ts @@ -19,6 +19,10 @@ export enum SetOptionOperator { * Toggle option: Reset, switch it off. */ Reset, + /** + * Toggle option: Insert value. + */ + Invert, /* * Add the {value} to a number option, or append the {value} to a string option. * When the option is a comma separated list, a comma is added, unless the value was empty. @@ -27,7 +31,15 @@ export enum SetOptionOperator { /* * Subtract the {value} from a number option, or remove the {value} from a string option, if it is there. */ - Subtract + Subtract, + /** + * Multiply the {value} to a number option, or prepend the {value} to a string option. + */ + Multiply, + /** + * Show value of {option}. + */ + Info } export interface IOptionArgs extends node.ICommandArgs { diff --git a/src/cmd_line/scanner.ts b/src/cmd_line/scanner.ts index ac6a9447758..39c4e8ac257 100644 --- a/src/cmd_line/scanner.ts +++ b/src/cmd_line/scanner.ts @@ -23,8 +23,9 @@ export class Scanner { } // Returns the next word in the input, or EOF. - nextWord(): string { - this.skipWhiteSpace(); + nextWord(wordSeparators: string[] = [" ", "\t"]): string { + this.skipRun(wordSeparators); + if (this.isAtEof) { this.pos = this.input.length; return Scanner.EOF; @@ -36,7 +37,7 @@ export class Scanner { while (!this.isAtEof) { c = this.next(); - if (!(c !== Scanner.EOF && c !== ' ' && c !== '\t')) { + if (c === Scanner.EOF || wordSeparators.indexOf(c) !== -1) { break; } @@ -47,8 +48,6 @@ export class Scanner { this.backup(); } - this.pos += result.length; - this.ignore(); return result; } diff --git a/src/cmd_line/subparsers/setoptions.ts b/src/cmd_line/subparsers/setoptions.ts index 7915e109f50..761a70435f3 100644 --- a/src/cmd_line/subparsers/setoptions.ts +++ b/src/cmd_line/subparsers/setoptions.ts @@ -11,41 +11,65 @@ export function parseOption(args: string) : node.IOptionArgs { return {}; } - let option = scanner.nextWord(); + let optionName = scanner.nextWord("?!&=:^+-".split("")); - if (option.startsWith("no")) { - // :se[t] no{option} Toggle option: Reset, switch it off. + if (optionName.startsWith("no")) { return { - name: option.substring(2, option.length), + name: optionName.substring(2, optionName.length), operator: node.SetOptionOperator.Reset }; } - if (option.includes('=')) { - // :se[t] {option}={value} Set string or number option to {value}. - let equalSign = option.indexOf('='); + if (optionName.startsWith("inv")) { return { - name: option.substring(0, equalSign), - value: option.substring(equalSign + 1, option.length), - operator: node.SetOptionOperator.Equal + name: optionName.substring(3, optionName.length), + operator: node.SetOptionOperator.Invert }; } - if (option.includes(':')) { - // :se[t] {option}:{value} Set string or number option to {value}. - let equalSign = option.indexOf(':'); + scanner.skipWhiteSpace(); + + if (scanner.isAtEof) { return { - name: option.substring(0, equalSign), - value: option.substring(equalSign + 1, option.length), - operator: node.SetOptionOperator.Equal + name: optionName, + operator: node.SetOptionOperator.Set }; } - // :se[t] {option} - return { - name: option, - operator: node.SetOptionOperator.Set + let operator = scanner.next(); + let optionArgs: node.IOptionArgs = { + name: optionName, + value: scanner.nextWord([]) }; + + switch (operator) { + case "=": + case ":": + optionArgs.operator = node.SetOptionOperator.Equal; + break; + case "!": + optionArgs.operator = node.SetOptionOperator.Invert; + break; + case "^": + optionArgs.operator = node.SetOptionOperator.Multiply; + break; + case "+": + optionArgs.operator = node.SetOptionOperator.Append; + break; + case "-": + optionArgs.operator = node.SetOptionOperator.Subtract; + break; + case "?": + optionArgs.operator = node.SetOptionOperator.Info; + break; + case "&": + optionArgs.operator = node.SetOptionOperator.Reset; + break; + default: + throw new Error("Unknown option"); + } + + return optionArgs; } export function parseOptionsCommandArgs(args : string) : node.SetOptionsCommand { diff --git a/src/configuration/configuration.ts b/src/configuration/configuration.ts index 981c448a887..5edb8300129 100644 --- a/src/configuration/configuration.ts +++ b/src/configuration/configuration.ts @@ -4,277 +4,136 @@ import * as vscode from 'vscode'; export type OptionValue = number | string | boolean; -enum OptionValueSource { - Runtime, - UserSetting, // How can we know if it's frm User Setting or Workspace or Code's Default? - Default -} - /** * Every Vim option we support should * 1. Be added to contribution section of `package.json`. * 2. Named as `vim.{optionName}`, `optionName` is the name we use in Vim. - * 3. Define a subclass below, inherited from `BaseOptionDetails`. + * 3. Define a public property in `Configuration `with the same name and a default value. + * Or define a private propery and define customized Getter/Setter accessors for it. + * Always remember to decorate Getter accessor as @enumerable() * 4. If user doesn't set the option explicitly * a. we don't have a similar setting in Code, initialize the option as default value. - * b. we have a similar setting in Code, use Code's setting.' + * b. we have a similar setting in Code, use Code's setting. + * + * Vim option override sequence. + * 1. `:set {option}` on the fly + * 2. TODO .vimrc. + * 2. `vim.{option}` + * 3. VS Code configuration + * 4. VSCodeVim flavored Vim option default values + * */ -export class BaseOptionDetails { - id: string; - defaultValue: OptionValue; - protected _value: OptionValue; - - // By default, we are fetching options from `vim` section but when we have overlap - // between Code and Vim, eg `tabstop`, we may want to override this method. - protected fetchCodeConfiguration() { - return vscode.workspace.getConfiguration("vim").get(this.id, this.defaultValue); - } - - /** - * Defualt constructor only fetches option value from Code and does nothing more. - * But if there is any overlap between Vim option and Code option, we need to do handle - * the option overriden correctly. That's why we need another custom `initialize()` here. - * Example: - * Vim has an option called `tabstop`, which is used to define number of spaces that a Tab uses. - * Code has a config called `tabSize` which reprents totally the same thing. - * If user defines `vim.tabstop` explicitly in `settings.json`, we need to update Code's `tabSize` - * accordingly to take effect. - */ - public initialize() { - let res: OptionValue; - res = this.fetchCodeConfiguration(); +export class Configuration { + private static _instance: Configuration | null; - if (res !== undefined) { - this._value = res; - this.source = OptionValueSource.UserSetting; - } else { - this._value = this.defaultValue; - this.source = OptionValueSource.Default; + constructor() { + /** + * Load Vim options from User Settings. + */ + let vimOptions = vscode.workspace.getConfiguration("vim"); + /* tslint:disable:forin */ + // Disable forin rule here as we make accessors enumerable.` + for (const option in this) { + const vimOptionValue = vimOptions[option]; + if (vimOptionValue !== null && vimOptionValue !== undefined) { + this[option] = vimOptionValue; + } } - } - - public get value() { - return this._value; } - public set value(value: OptionValue) { - this._value = value; - this.source = OptionValueSource.Runtime; - } - - public source: OptionValueSource = OptionValueSource.Default ; - static globalOptionUpdateHandler: () => void; -} - -export class OptionMap { - public static allOptions: { [key: string]: BaseOptionDetails } = {}; -} - -export function RegisterOption(optionDetails: typeof BaseOptionDetails): void { - let newOption = new optionDetails(); - newOption.initialize(); - OptionMap.allOptions[newOption.id] = newOption; -} - -@RegisterOption -class OptionUseSolidBlockCursor extends BaseOptionDetails { - id = "useSolidBlockCursor"; - defaultValue = false; -} - -@RegisterOption -class OptionUseControlKeys extends BaseOptionDetails { - id = "useCtrlKeys"; - defaultValue = false; -} - -@RegisterOption -class OptionScroll extends BaseOptionDetails { - id = "scroll"; - defaultValue = 20; -} - -@RegisterOption -class OptionHightlightSearch extends BaseOptionDetails { - id = "hlsearch"; - defaultValue = false; -} - -@RegisterOption -class OptionIgnoreCase extends BaseOptionDetails { - id = "ignorecase"; - defaultValue = true; -} - -@RegisterOption -class OptionSmartCase extends BaseOptionDetails { - id = "smartcase"; - defaultValue = true; -} - -@RegisterOption -class OptionTabStop extends BaseOptionDetails { - id = "tabstop"; // aka, tabSize - defaultValue = 8; // Vim default value but here we always use Code configuration - - fetchCodeConfiguration() { - const vimOptionInCode = vscode.workspace.getConfiguration("vim").get(this.id, undefined); - - return vimOptionInCode !== undefined ? vimOptionInCode : this.getTabSizeFromCode(); - } + public static getInstance(): Configuration { + if (Configuration._instance == null) { + Configuration._instance = new Configuration(); + } - initialize() { - super.initialize(); - // TODO we should update through API instead of tweaking editor's copy of config. - const options = vscode.window.activeTextEditor.options; - options.tabSize = this.value as number; - vscode.window.activeTextEditor.options = options; + return Configuration._instance; } - public get value() { - if (this.source === OptionValueSource.Runtime) { - return this._value; - } else { - return this.getTabSizeFromCode(); - } + set(option: string, value: OptionValue): void { + this[option] = value; } - public set value(value: OptionValue) { - super.value = value; + useSolidBlockCursor: boolean = false; + useCtrlKeys: boolean = false; + scroll: number = 20; + hlsearch: boolean = false; + ignorecase: boolean = true; + smartcase: boolean = true; + /** + * Intersection of Vim options and Code configuration. + */ + private _tabstop: number; + public set tabstop(tabstop: number) { + this._tabstop = tabstop; if (!vscode.window.activeTextEditor) { // TODO: We should set configuration by API, which is not currently supported. return; } const oldOptions = vscode.window.activeTextEditor.options; - oldOptions.tabSize = value as number; + oldOptions.tabSize = tabstop; vscode.window.activeTextEditor.options = oldOptions; } - private getTabSizeFromCode(): number { - if (vscode.window.activeTextEditor) { - return vscode.window.activeTextEditor.options.tabSize! as number; + @enumerable() + public get tabstop() { + if (this._tabstop !== undefined) { + return this._tabstop; } else { - return vscode.workspace.getConfiguration("editor").get("tabSize", this.defaultValue); + if (vscode.window.activeTextEditor) { + return vscode.window.activeTextEditor.options.tabSize as number; + } else { + return 8; + } } } -} -@RegisterOption -class OptionInsertSpaces extends BaseOptionDetails { - id = "expandtab"; // aka insertSpaces - defaultValue = false; + private _expandtab: boolean; + public set expandtab(expand: boolean) { + this._expandtab = expand; - fetchCodeConfiguration() { - const vimOptionInCode = vscode.workspace.getConfiguration("vim").get(this.id, undefined); - return vimOptionInCode !== undefined ? vimOptionInCode : this.getInsertSpaccesFromCode(); - } - - initialize() { - super.initialize(); - const options = vscode.window.activeTextEditor.options; - options.insertSpaces = this.value as boolean; - vscode.window.activeTextEditor.options = options; - } - - public get value() { - if (this.source === OptionValueSource.Runtime) { - return this._value; - } else { - return this.getInsertSpaccesFromCode(); + if (!vscode.window.activeTextEditor) { + // TODO: We should set configuration by API, which is not currently supported. + return; } - } - public set value(value: OptionValue) { - super.value = value; const oldOptions = vscode.window.activeTextEditor.options; - oldOptions.insertSpaces = value; + oldOptions.insertSpaces = expand; vscode.window.activeTextEditor.options = oldOptions; } - private getInsertSpaccesFromCode(): boolean { - if (vscode.window.activeTextEditor) { - return vscode.window.activeTextEditor.options.insertSpaces! as boolean; + @enumerable() + public get expandtab() { + if (this._expandtab !== undefined) { + return this._expandtab; } else { - return vscode.workspace.getConfiguration("editor").get("insertSpaces", this.defaultValue); + if (vscode.window.activeTextEditor) { + return vscode.window.activeTextEditor.options.insertSpaces as boolean; + } else { + // Default value. + return false; + } } } -} - -@RegisterOption -class OptionIskeyword extends BaseOptionDetails { - id = "iskeyword"; - defaultValue = "/\\()\"':,.;<>~!@#$%^&*|+=[]{}`?-"; - - fetchCodeConfiguration() { - const vimOptionInCode = vscode.workspace.getConfiguration("vim").get(this.id, undefined); - return vimOptionInCode !== undefined ? - vimOptionInCode : - vscode.workspace.getConfiguration("editor").get("wordSeparators", this.defaultValue); - } - initialize() { - super.initialize(); - // TODO: modify Code's wordSeparators configuration. + private _iskeyword: string; + public set iskeyword(keyword) { + this._iskeyword = keyword; } -} - -/** - * Vim option override sequence. - * 1. `:set {option}` on the fly - * 2. VS Code Vim configuration section && overlap configurations between Code and Vim - * 3. TODO .vimrc. - * 4. VSCodeVim flavored Vim option default values - */ -export class Configuration { - private static _instance: Configuration | null; - constructor() { - // TODO: Read the real Vim config and override existing configration. - // Besides, vimrc settings' priority should be between Default and User Settings. - } - - public static getInstance(): Configuration { - if (Configuration._instance == null) { - Configuration._instance = new Configuration(); - } - - return Configuration._instance; - } - - set(option: string, value: OptionValue, source?: OptionValueSource): void { - let targetOption = OptionMap.allOptions[option]; - - if (!targetOption) { - return; - } - - if (value === targetOption.value) { - targetOption.source = OptionValueSource.Runtime; - return; + @enumerable() + public get iskeyword() { + if (this._iskeyword) { + return this._iskeyword; + } else { + return vscode.workspace.getConfiguration("editor").get("wordSeparators", "/\\()\"':,.;<>~!@#$%^&*|+=[]{}`?"); } - - targetOption.value = value; } +} - /** - * Return a value of option from vim settings. - * @param vim option. - * @param defaultValue A value should be returned when no value could be found, is `undefined`. - */ - get(option: string, defaultValue?: OptionValue): OptionValue | undefined { - let targetOption = OptionMap.allOptions[option]; - - if (targetOption) { - // Return directly if the option from Runtime (including VimRc) - // as in these two senarios configurations are updated automatically. - return targetOption.value; - } - - // TODO: Not sure if there is only overlap between Code's `editor` section and Vim. - // Not sure if auto update of VS Code configuration is necessary - // maybe we can just require users to restart VS Code after they update User Setting? - return vscode.workspace.getConfiguration("editor").get(option, defaultValue); - } +function enumerable() { + return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) { + descriptor.enumerable = true; + }; } \ No newline at end of file diff --git a/src/mode/modeHandler.ts b/src/mode/modeHandler.ts index b3bb4fb5867..da7c33118c1 100644 --- a/src/mode/modeHandler.ts +++ b/src/mode/modeHandler.ts @@ -974,7 +974,7 @@ export class ModeHandler implements vscode.Disposable { // Draw block cursor. - if (Configuration.getInstance().get("useSolidBlockCursor", false)) { + if (Configuration.getInstance().useSolidBlockCursor) { if (this.currentMode.name !== ModeName.Insert) { rangesToDraw.push(new vscode.Range( vimState.cursorPosition, @@ -1016,7 +1016,7 @@ export class ModeHandler implements vscode.Disposable { // Draw search highlight if (this.currentMode.name === ModeName.SearchInProgressMode || - (Configuration.getInstance().get("hlsearch") && vimState.searchState)) { + (Configuration.getInstance().hlsearch && vimState.searchState)) { const searchState = vimState.searchState!; rangesToDraw.push.apply(rangesToDraw, searchState.matchRanges); @@ -1039,7 +1039,7 @@ export class ModeHandler implements vscode.Disposable { } vscode.commands.executeCommand('setContext', 'vim.mode', this.currentMode.text); - vscode.commands.executeCommand('setContext', 'vim.useCtrlKeys', Configuration.getInstance().get("useCtrlKeys", false)); + vscode.commands.executeCommand('setContext', 'vim.useCtrlKeys', Configuration.getInstance().useCtrlKeys); } async handleMultipleKeyEvents(keys: string[]): Promise { diff --git a/src/motion/position.ts b/src/motion/position.ts index 37fd3f30154..b0307281910 100644 --- a/src/motion/position.ts +++ b/src/motion/position.ts @@ -8,7 +8,7 @@ import { VisualBlockMode } from './../mode/modeVisualBlock'; import { Configuration } from "./../configuration/configuration"; export class Position extends vscode.Position { - private static NonWordCharacters = Configuration.getInstance().get("iskeyword") as string; // "; + private static NonWordCharacters = Configuration.getInstance().iskeyword; private static NonBigWordCharacters = ""; private _nonWordCharRegex : RegExp; diff --git a/src/textEditor.ts b/src/textEditor.ts index 34365e64561..f0f6f26eea6 100644 --- a/src/textEditor.ts +++ b/src/textEditor.ts @@ -149,7 +149,7 @@ export class TextEditor { } static getIndentationLevel(line: string): number { - let tabSize = Configuration.getInstance().get("tabSize"); + let tabSize = Configuration.getInstance().tabstop; let firstNonWhiteSpace = line.match(/^\s*/)[0].length; let visibleColumn: number = 0; @@ -174,8 +174,8 @@ export class TextEditor { } static setIndentationLevel(line: string, screenCharacters: number): string { - let tabSize = Configuration.getInstance().get("tabstop"); - let insertTabAsSpaces = Configuration.getInstance().get("expandtab"); + let tabSize = Configuration.getInstance().tabstop; + let insertTabAsSpaces = Configuration.getInstance().expandtab; if (screenCharacters < 0) { screenCharacters = 0; diff --git a/tsconfig.json b/tsconfig.json index 640887c11dd..c1cc8d4ca22 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -4,6 +4,7 @@ "target": "es6", "outDir": "out", "noImplicitAny": true, + "suppressImplicitAnyIndexErrors": true, "noLib": true, "sourceMap": true, "strictNullChecks": true,