diff --git a/.github/classifier.yml b/.github/classifier.yml index a279a7cb8d2..3236507a472 100644 --- a/.github/classifier.yml +++ b/.github/classifier.yml @@ -63,7 +63,7 @@ assignLabel: false }, file-explorer: { - assignees: [ bpasero ], + assignees: [ isidorn ], assignLabel: false }, file-glob: [], diff --git a/build/builtInExtensions.json b/build/builtInExtensions.json index ecc0b8c72ae..26e7bcc9f7e 100644 --- a/build/builtInExtensions.json +++ b/build/builtInExtensions.json @@ -1,7 +1,7 @@ [ { "name": "ms-vscode.node-debug", - "version": "1.32.1", + "version": "1.32.2", "repo": "https://github.com/Microsoft/vscode-node-debug", "metadata": { "id": "b6ded8fb-a0a0-4c1c-acbd-ab2a3bc995a6", diff --git a/build/gulpfile.vscode.js b/build/gulpfile.vscode.js index cf1af970d75..529d85c37aa 100644 --- a/build/gulpfile.vscode.js +++ b/build/gulpfile.vscode.js @@ -214,10 +214,9 @@ function getElectron(arch) { gulp.task(task.define('electron', task.series(util.rimraf('.build/electron'), getElectron(process.arch)))); gulp.task(task.define('electron-ia32', task.series(util.rimraf('.build/electron'), getElectron('ia32')))); gulp.task(task.define('electron-x64', task.series(util.rimraf('.build/electron'), getElectron('x64')))); -gulp.task(task.define('electron-arm', task.series(util.rimraf('.build/electron'), getElectron('arm')))); +gulp.task(task.define('electron-arm', task.series(util.rimraf('.build/electron'), getElectron('armv7l')))); gulp.task(task.define('electron-arm64', task.series(util.rimraf('.build/electron'), getElectron('arm64')))); - /** * Compute checksums for some files. * diff --git a/build/lib/i18n.js b/build/lib/i18n.js index bf32ebf3676..8c408417fae 100644 --- a/build/lib/i18n.js +++ b/build/lib/i18n.js @@ -573,7 +573,7 @@ function createXlfFilesForExtensions() { } return _xlf; } - gulp.src([`./extensions/${extensionName}/package.nls.json`, `./extensions/${extensionName}/**/nls.metadata.json`]).pipe(event_stream_1.through(function (file) { + gulp.src([`./extensions/${extensionName}/package.nls.json`, `./extensions/${extensionName}/**/nls.metadata.json`], { allowEmpty: true }).pipe(event_stream_1.through(function (file) { if (file.isBuffer()) { const buffer = file.contents; const basename = path.basename(file.path); diff --git a/build/lib/i18n.ts b/build/lib/i18n.ts index 6f6139ad388..33864e4e667 100644 --- a/build/lib/i18n.ts +++ b/build/lib/i18n.ts @@ -684,7 +684,7 @@ export function createXlfFilesForExtensions(): ThroughStream { } return _xlf; } - gulp.src([`./extensions/${extensionName}/package.nls.json`, `./extensions/${extensionName}/**/nls.metadata.json`]).pipe(through(function (file: File) { + gulp.src([`./extensions/${extensionName}/package.nls.json`, `./extensions/${extensionName}/**/nls.metadata.json`], { allowEmpty: true }).pipe(through(function (file: File) { if (file.isBuffer()) { const buffer: Buffer = file.contents as Buffer; const basename = path.basename(file.path); diff --git a/extensions/configuration-editing/src/extension.ts b/extensions/configuration-editing/src/extension.ts index b0a083e7e9e..5003c35ff82 100644 --- a/extensions/configuration-editing/src/extension.ts +++ b/extensions/configuration-editing/src/extension.ts @@ -22,9 +22,6 @@ const fadedDecoration = vscode.window.createTextEditorDecorationType({ let pendingLaunchJsonDecoration: NodeJS.Timer; export function activate(context: vscode.ExtensionContext): void { - //keybindings.json command-suggestions - context.subscriptions.push(registerKeybindingsCompletions()); - //settings.json suggestions context.subscriptions.push(registerSettingsCompletions()); @@ -94,23 +91,6 @@ function autoFixSettingsJSON(willSaveEvent: vscode.TextDocumentWillSaveEvent): v vscode.workspace.applyEdit(edit)); } -function registerKeybindingsCompletions(): vscode.Disposable { - const commands = vscode.commands.getCommands(true); - - return vscode.languages.registerCompletionItemProvider({ pattern: '**/keybindings.json' }, { - - provideCompletionItems(document, position, _token) { - const location = getLocation(document.getText(), document.offsetAt(position)); - if (location.path[1] === 'command') { - - const range = document.getWordRangeAtPosition(position) || new vscode.Range(position, position); - return commands.then(ids => ids.map(id => newSimpleCompletionItem(JSON.stringify(id), range))); - } - return undefined; - } - }); -} - function registerSettingsCompletions(): vscode.Disposable { return vscode.languages.registerCompletionItemProvider({ language: 'jsonc', pattern: '**/settings.json' }, { provideCompletionItems(document, position, token) { @@ -207,16 +187,6 @@ function provideInstalledExtensionProposals(extensionsContent: IExtensionsConten return undefined; } -function newSimpleCompletionItem(label: string, range: vscode.Range, description?: string, insertText?: string): vscode.CompletionItem { - const item = new vscode.CompletionItem(label); - item.kind = vscode.CompletionItemKind.Value; - item.detail = description; - item.insertText = insertText || label; - item.range = range; - - return item; -} - function updateLaunchJsonDecorations(editor: vscode.TextEditor | undefined): void { if (!editor || path.basename(editor.document.fileName) !== 'launch.json') { return; diff --git a/extensions/git/package.json b/extensions/git/package.json index 435a8053c7b..834563c3102 100644 --- a/extensions/git/package.json +++ b/extensions/git/package.json @@ -1182,7 +1182,7 @@ "number", "null" ], - "default": null, + "default": 50, "description": "%config.inputValidationSubjectLength%" }, "git.detectSubmodules": { diff --git a/extensions/git/src/git.ts b/extensions/git/src/git.ts index ea9a05d4a7e..8446b9041fe 100644 --- a/extensions/git/src/git.ts +++ b/extensions/git/src/git.ts @@ -1456,14 +1456,14 @@ export class Repository { async createStash(message?: string, includeUntracked?: boolean): Promise { try { - const args = ['stash', 'save']; + const args = ['stash', 'push']; if (includeUntracked) { args.push('-u'); } if (message) { - args.push('--', message); + args.push('-m', message); } await this.run(args); diff --git a/extensions/git/src/repository.ts b/extensions/git/src/repository.ts index 846d01945a9..388a1f28043 100644 --- a/extensions/git/src/repository.ts +++ b/extensions/git/src/repository.ts @@ -673,20 +673,6 @@ export class Repository implements Disposable { } } - - - - - - - - - - // const subjectThreshold = - - - // Math.max(config.get('inputValidationLength') || 50, config.get('subjectValidationLength') || 50, 0) || 50; - if (line.length <= threshold) { if (setting !== 'always') { return; diff --git a/extensions/handlebars/language-configuration.json b/extensions/handlebars/language-configuration.json index ab66bc5961a..cb2de742fb0 100644 --- a/extensions/handlebars/language-configuration.json +++ b/extensions/handlebars/language-configuration.json @@ -6,6 +6,7 @@ [""], ["<", ">"], ["{{", "}}"], + ["{{{", "}}}"], ["{", "}"], ["(", ")"] ], @@ -19,6 +20,7 @@ "surroundingPairs": [ { "open": "'", "close": "'" }, { "open": "\"", "close": "\"" }, - { "open": "<", "close": ">" } + { "open": "<", "close": ">" }, + { "open": "{", "close": "}" } ] } diff --git a/extensions/lua/language-configuration.json b/extensions/lua/language-configuration.json index 6510d335101..89e5c45b9ff 100644 --- a/extensions/lua/language-configuration.json +++ b/extensions/lua/language-configuration.json @@ -23,7 +23,7 @@ ["'", "'"] ], "indentationRules": { - "increaseIndentPattern": "^\\s*((\\b(else|function|then|do|repeat)\\b((?!\\b(end|until)\\b).)*)|(\\{\\s*))$", + "increaseIndentPattern": "^((?!(\\-\\-)).)*((\\b(else|function|then|do|repeat)\\b((?!\\b(end|until)\\b).)*)|(\\{\\s*))$", "decreaseIndentPattern": "^\\s*((\\b(elseif|else|end|until)\\b)|(\\})|(\\)))" } } diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/workspace.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/workspace.test.ts index a950e90504b..1deca1ad443 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/workspace.test.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/workspace.test.ts @@ -488,9 +488,9 @@ suite('workspace-namespace', () => { }); test('findFiles', () => { - return vscode.workspace.findFiles('*.js').then((res) => { + return vscode.workspace.findFiles('*.png').then((res) => { assert.equal(res.length, 1); - assert.equal(basename(vscode.workspace.asRelativePath(res[0])), 'far.js'); + assert.equal(basename(vscode.workspace.asRelativePath(res[0])), 'image.png'); }); }); diff --git a/package.json b/package.json index 00b54329e5c..3b4e21265d5 100644 --- a/package.json +++ b/package.json @@ -48,12 +48,11 @@ "vscode-chokidar": "1.6.5", "vscode-debugprotocol": "^1.34.0-pre.0", "vscode-nsfw": "1.1.1", - "vscode-proxy-agent": "0.3.0", + "vscode-proxy-agent": "0.4.0", "vscode-ripgrep": "^1.2.5", "vscode-sqlite3": "4.0.7", "vscode-textmate": "^4.0.1", "vscode-xterm": "3.12.0-beta4", - "winreg": "^1.2.4", "yauzl": "^2.9.1", "yazl": "^2.4.3" }, diff --git a/src/tsconfig.strictNullChecks.json b/src/tsconfig.strictNullChecks.json index c8517858e66..ece0a772562 100644 --- a/src/tsconfig.strictNullChecks.json +++ b/src/tsconfig.strictNullChecks.json @@ -95,12 +95,10 @@ "./vs/editor/contrib/indentation/indentation.ts", "./vs/editor/contrib/indentation/test/indentation.test.ts", "./vs/editor/contrib/linesOperations/copyLinesCommand.ts", - "./vs/editor/contrib/linesOperations/deleteLinesCommand.ts", "./vs/editor/contrib/linesOperations/linesOperations.ts", "./vs/editor/contrib/linesOperations/moveLinesCommand.ts", "./vs/editor/contrib/linesOperations/sortLinesCommand.ts", "./vs/editor/contrib/linesOperations/test/copyLinesCommand.test.ts", - "./vs/editor/contrib/linesOperations/test/deleteLinesCommand.test.ts", "./vs/editor/contrib/linesOperations/test/linesOperations.test.ts", "./vs/editor/contrib/linesOperations/test/moveLinesCommand.test.ts", "./vs/editor/contrib/linesOperations/test/sortLinesCommand.test.ts", @@ -418,6 +416,7 @@ "./vs/workbench/api/electron-browser/mainThreadWorkspace.ts", "./vs/workbench/api/node/extHost.protocol.ts", "./vs/workbench/api/node/extHostClipboard.ts", + "vs/workbench/api/node/extHostConfiguration.ts", "./vs/workbench/api/node/extHostDecorations.ts", "./vs/workbench/api/node/extHostDialogs.ts", "./vs/workbench/api/node/extHostDocumentData.ts", @@ -675,6 +674,7 @@ "./vs/workbench/services/configuration/common/configurationModels.ts", "./vs/workbench/services/configuration/common/jsonEditing.ts", "./vs/workbench/services/configuration/node/jsonEditingService.ts", + "vs/workbench/services/configuration/node/configuration.ts", "./vs/workbench/services/configuration/test/common/configurationModels.test.ts", "./vs/workbench/services/configurationResolver/common/configurationResolver.ts", "./vs/workbench/services/configurationResolver/common/configurationResolverSchema.ts", @@ -774,6 +774,7 @@ "./vs/workbench/test/common/editor/editorOptions.test.ts", "./vs/workbench/test/common/notifications.test.ts", "./vs/workbench/test/electron-browser/api/extHost.api.impl.test.ts", + "vs/workbench/test/electron-browser/api/extHostConfiguration.test.ts", "./vs/workbench/test/electron-browser/api/extHostDocumentData.test.ts", "./vs/workbench/test/electron-browser/api/extHostMessagerService.test.ts", "./vs/workbench/test/electron-browser/api/extHostTypes.test.ts", diff --git a/src/vs/base/browser/ui/grid/grid.ts b/src/vs/base/browser/ui/grid/grid.ts index 2272913ff4a..2d554d757ec 100644 --- a/src/vs/base/browser/ui/grid/grid.ts +++ b/src/vs/base/browser/ui/grid/grid.ts @@ -230,6 +230,10 @@ export class Grid implements IDisposable { this.gridview.layout(width, height); } + hasView(view: T): boolean { + return this.views.has(view); + } + addView(newView: T, size: number | Sizing, referenceView: T, direction: Direction): void { if (this.views.has(newView)) { throw new Error('Can\'t add same view twice'); diff --git a/src/vs/base/browser/ui/menu/menu.ts b/src/vs/base/browser/ui/menu/menu.ts index de0c89c6b77..c2b6a7ea36d 100644 --- a/src/vs/base/browser/ui/menu/menu.ts +++ b/src/vs/base/browser/ui/menu/menu.ts @@ -20,8 +20,8 @@ import { Event, Emitter } from 'vs/base/common/event'; import { AnchorAlignment } from 'vs/base/browser/ui/contextview/contextview'; import { isLinux } from 'vs/base/common/platform'; -export const MENU_MNEMONIC_REGEX: RegExp = /\(&{1,2}(.)\)|&{1,2}(.)/; -export const MENU_ESCAPED_MNEMONIC_REGEX: RegExp = /(?:&){1,2}(.)/; +export const MENU_MNEMONIC_REGEX: RegExp = /\(&(\w)\)|(?$1'); + label = label.replace(/&&/g, '&'); this.item.setAttribute('aria-keyshortcuts', (!!matches[1] ? matches[1] : matches[2]).toLocaleLowerCase()); + } else { + label = label.replace(/&&/g, '&'); } } diff --git a/src/vs/base/browser/ui/menu/menubar.ts b/src/vs/base/browser/ui/menu/menubar.ts index 7a5cd31d621..f62951a9750 100644 --- a/src/vs/base/browser/ui/menu/menubar.ts +++ b/src/vs/base/browser/ui/menu/menubar.ts @@ -457,8 +457,8 @@ export class MenuBar extends Disposable { // Update the button label to reflect mnemonics titleElement.innerHTML = this.options.enableMnemonics ? - strings.escape(label).replace(MENU_ESCAPED_MNEMONIC_REGEX, '') : - cleanMenuLabel; + strings.escape(label).replace(MENU_ESCAPED_MNEMONIC_REGEX, '').replace(/&&/g, '&') : + cleanMenuLabel.replace(/&&/g, '&'); let mnemonicMatches = MENU_MNEMONIC_REGEX.exec(label); diff --git a/src/vs/base/browser/ui/tree/abstractTree.ts b/src/vs/base/browser/ui/tree/abstractTree.ts index 7c94ff8e627..ab9e2eba27b 100644 --- a/src/vs/base/browser/ui/tree/abstractTree.ts +++ b/src/vs/base/browser/ui/tree/abstractTree.ts @@ -196,7 +196,7 @@ class TreeRenderer implements IListRenderer>(); private renderedNodes = new Map, ITreeListTemplateData>(); - private indent: number; + private indent: number = TreeRenderer.DefaultIndent; private disposables: IDisposable[] = []; constructor( @@ -215,7 +215,9 @@ class TreeRenderer implements IListRenderer { templateData.twistie.style.marginLeft = `${node.depth * this.indent}px`; diff --git a/src/vs/base/common/extpath.ts b/src/vs/base/common/extpath.ts index 63e51a0dc1c..a829cbcf5fe 100644 --- a/src/vs/base/common/extpath.ts +++ b/src/vs/base/common/extpath.ts @@ -93,14 +93,10 @@ function streql(value: string, start: number, end: number, other: string): boole return start + other.length === end && value.indexOf(other, start) === start; } -export const join: (...parts: string[]) => string = function () { - // Not using a function with var-args because of how TS compiles - // them to JS - it would result in 2*n runtime cost instead - // of 1*n, where n is parts.length. - +export function joinWithSlashes(...parts: string[]): string { let value = ''; - for (let i = 0; i < arguments.length; i++) { - let part = arguments[i]; + for (let i = 0; i < parts.length; i++) { + let part = parts[i]; if (i > 0) { // add the separater between two parts unless // there already is one @@ -116,7 +112,7 @@ export const join: (...parts: string[]) => string = function () { } return normalizeWithSlashes(value); -}; +} // #region extpath diff --git a/src/vs/base/common/glob.ts b/src/vs/base/common/glob.ts index ac7a3d77883..a7569150a48 100644 --- a/src/vs/base/common/glob.ts +++ b/src/vs/base/common/glob.ts @@ -18,7 +18,6 @@ export interface IExpression { export interface IRelativePattern { base: string; pattern: string; - pathToRelative(from: string, to: string): string; } export function getEmptyExpression(): IExpression { @@ -336,7 +335,7 @@ function wrapRelativePattern(parsedPattern: ParsedStringPattern, arg2: string | return null; } - return parsedPattern(arg2.pathToRelative(arg2.base, path), basename); + return parsedPattern(paths.relative(arg2.base, path), basename); }; } @@ -516,7 +515,7 @@ function listToMap(list: string[]) { export function isRelativePattern(obj: any): obj is IRelativePattern { const rp = obj as IRelativePattern; - return rp && typeof rp.base === 'string' && typeof rp.pattern === 'string' && typeof rp.pathToRelative === 'function'; + return rp && typeof rp.base === 'string' && typeof rp.pattern === 'string'; } /** diff --git a/src/vs/base/common/keyCodes.ts b/src/vs/base/common/keyCodes.ts index 38985589354..b2d75920a4c 100644 --- a/src/vs/base/common/keyCodes.ts +++ b/src/vs/base/common/keyCodes.ts @@ -5,7 +5,6 @@ import { OperatingSystem } from 'vs/base/common/platform'; import { illegalArgument } from 'vs/base/common/errors'; -import { Modifiers, UILabelProvider, AriaLabelProvider, ElectronAcceleratorLabelProvider, UserSettingsLabelProvider } from 'vs/base/common/keybindingLabels'; /** * Virtual Key Codes, the value does not hold any inherent meaning. @@ -601,72 +600,3 @@ export abstract class ResolvedKeybinding { */ public abstract getDispatchParts(): (string | null)[]; } - -export abstract class BaseResolvedKeybinding extends ResolvedKeybinding { - - protected readonly _os: OperatingSystem; - protected readonly _parts: T[]; - - constructor(os: OperatingSystem, parts: T[]) { - super(); - if (parts.length === 0) { - throw illegalArgument(`parts`); - } - this._os = os; - this._parts = parts; - } - - public getLabel(): string | null { - return UILabelProvider.toLabel(this._os, this._parts, (keybinding) => this._getLabel(keybinding)); - } - - public getAriaLabel(): string | null { - return AriaLabelProvider.toLabel(this._os, this._parts, (keybinding) => this._getAriaLabel(keybinding)); - } - - public getElectronAccelerator(): string | null { - if (this._parts.length > 1) { - // Electron cannot handle chords - return null; - } - return ElectronAcceleratorLabelProvider.toLabel(this._os, this._parts, (keybinding) => this._getElectronAccelerator(keybinding)); - } - - public getUserSettingsLabel(): string | null { - return UserSettingsLabelProvider.toLabel(this._os, this._parts, (keybinding) => this._getUserSettingsLabel(keybinding)); - } - - public isWYSIWYG(): boolean { - return this._parts.every((keybinding) => this._isWYSIWYG(keybinding)); - } - - public isChord(): boolean { - return (this._parts.length > 1); - } - - public getParts(): ResolvedKeybindingPart[] { - return this._parts.map((keybinding) => this._getPart(keybinding)); - } - - private _getPart(keybinding: T): ResolvedKeybindingPart { - return new ResolvedKeybindingPart( - keybinding.ctrlKey, - keybinding.shiftKey, - keybinding.altKey, - keybinding.metaKey, - this._getLabel(keybinding), - this._getAriaLabel(keybinding) - ); - } - - public getDispatchParts(): (string | null)[] { - return this._parts.map((keybinding) => this._getDispatchPart(keybinding)); - } - - protected abstract _getLabel(keybinding: T): string | null; - protected abstract _getAriaLabel(keybinding: T): string | null; - protected abstract _getElectronAccelerator(keybinding: T): string | null; - protected abstract _getUserSettingsLabel(keybinding: T): string | null; - protected abstract _isWYSIWYG(keybinding: T): boolean; - protected abstract _getDispatchPart(keybinding: T): string | null; -} diff --git a/src/vs/base/common/keybindingParser.ts b/src/vs/base/common/keybindingParser.ts index 880278b602f..68025d9131b 100644 --- a/src/vs/base/common/keybindingParser.ts +++ b/src/vs/base/common/keybindingParser.ts @@ -107,17 +107,18 @@ export class KeybindingParser { return [new SimpleKeybinding(mods.ctrl, mods.shift, mods.alt, mods.meta, keyCode), mods.remains]; } - static parseUserBinding(input: string): [SimpleKeybinding | ScanCodeBinding | null, SimpleKeybinding | ScanCodeBinding | null] { - // TODO@chords: allow users to define N chords + static parseUserBinding(input: string): (SimpleKeybinding | ScanCodeBinding)[] { if (!input) { - return [null, null]; + return []; } - let [firstPart, remains] = this.parseSimpleUserBinding(input); - let chordPart: SimpleKeybinding | ScanCodeBinding | null = null; - if (remains.length > 0) { - [chordPart] = this.parseSimpleUserBinding(remains); + let parts: (SimpleKeybinding | ScanCodeBinding)[] = []; + let part: SimpleKeybinding | ScanCodeBinding; + + while (input.length > 0) { + [part, input] = this.parseSimpleUserBinding(input); + parts.push(part); } - return [firstPart, chordPart]; + return parts; } } \ No newline at end of file diff --git a/src/vs/base/common/labels.ts b/src/vs/base/common/labels.ts index efe10e90646..c1a17aa456b 100644 --- a/src/vs/base/common/labels.ts +++ b/src/vs/base/common/labels.ts @@ -356,10 +356,10 @@ export function template(template: string, values: { [key: string]: string | ISe */ export function mnemonicMenuLabel(label: string, forceDisableMnemonics?: boolean): string { if (isMacintosh || forceDisableMnemonics) { - return label.replace(/\(&&\w\)|&&/g, ''); + return label.replace(/\(&&\w\)|&&/g, '').replace(/&/g, isMacintosh ? '&' : '&&'); } - return label.replace(/&&/g, '&'); + return label.replace(/&&|&/g, m => m === '&' ? '&&' : '&'); } /** diff --git a/src/vs/base/common/resources.ts b/src/vs/base/common/resources.ts index 410af8dd5ad..607aff7911a 100644 --- a/src/vs/base/common/resources.ts +++ b/src/vs/base/common/resources.ts @@ -10,6 +10,8 @@ import { equalsIgnoreCase } from 'vs/base/common/strings'; import { Schemas } from 'vs/base/common/network'; import { isLinux, isWindows } from 'vs/base/common/platform'; import { CharCode } from 'vs/base/common/charCode'; +import { ParsedExpression, IExpression, parse } from 'vs/base/common/glob'; +import { TernarySearchTree } from 'vs/base/common/map'; export function getComparisonKey(resource: URI): string { return hasToIgnoreCase(resource) ? resource.toString().toLowerCase() : resource.toString(); @@ -42,7 +44,10 @@ export function isEqualOrParent(base: URI, parentCandidate: URI, ignoreCase = ha return false; } -function isEqualAuthority(a1: string, a2: string) { +/** + * Tests wheter the two authorities are the same + */ +export function isEqualAuthority(a1: string, a2: string) { return a1 === a2 || equalsIgnoreCase(a1, a2); } @@ -209,6 +214,21 @@ export function relativePath(from: URI, to: URI): string | undefined { return paths.posix.relative(from.path || '/', to.path || '/'); } +/** + * Resolves a absolute or relative path against a base URI. + */ +export function resolvePath(base: URI, path: string): URI { + let resolvedPath: string; + if (base.scheme === Schemas.file) { + resolvedPath = URI.file(paths.resolve(originalFSPath(base), path)).path; + } else { + resolvedPath = paths.posix.resolve(base.path, path); + } + return base.with({ + path: resolvedPath + }); +} + export function distinctParents(items: T[], resourceAccessor: (item: T) => URI): T[] { const distinctParents: T[] = []; for (let i = 0; i < items.length; i++) { @@ -277,3 +297,31 @@ export namespace DataUri { return metadata; } } + + +export class ResourceGlobMatcher { + + private readonly globalExpression: ParsedExpression; + private readonly expressionsByRoot: TernarySearchTree<{ root: URI, expression: ParsedExpression }> = TernarySearchTree.forPaths<{ root: URI, expression: ParsedExpression }>(); + + constructor( + globalExpression: IExpression, + rootExpressions: { root: URI, expression: IExpression }[] + ) { + this.globalExpression = parse(globalExpression); + for (const expression of rootExpressions) { + this.expressionsByRoot.set(expression.root.toString(), { root: expression.root, expression: parse(expression.expression) }); + } + } + + matches(resource: URI): boolean { + const rootExpression = this.expressionsByRoot.findSubstr(resource.toString()); + if (rootExpression) { + const path = relativePath(rootExpression.root, resource); + if (path && !!rootExpression.expression(path)) { + return true; + } + } + return !!this.globalExpression(resource.path); + } +} \ No newline at end of file diff --git a/src/vs/base/test/common/extpath.test.ts b/src/vs/base/test/common/extpath.test.ts index 82364657387..f5947d06593 100644 --- a/src/vs/base/test/common/extpath.test.ts +++ b/src/vs/base/test/common/extpath.test.ts @@ -59,7 +59,7 @@ suite('Paths', () => { assert.equal(extpath.normalizeWithSlashes(undefined), undefined); // https://github.com/Microsoft/vscode/issues/7234 - assert.equal(extpath.join('/home/aeschli/workspaces/vscode/extensions/css', './syntaxes/css.plist'), '/home/aeschli/workspaces/vscode/extensions/css/syntaxes/css.plist'); + assert.equal(extpath.joinWithSlashes('/home/aeschli/workspaces/vscode/extensions/css', './syntaxes/css.plist'), '/home/aeschli/workspaces/vscode/extensions/css/syntaxes/css.plist'); }); test('getRoot', () => { @@ -80,30 +80,30 @@ suite('Paths', () => { }); test('join', () => { - assert.equal(extpath.join('.', 'bar'), 'bar'); - assert.equal(extpath.join('../../foo/bar', '../../foo'), '../../foo'); - assert.equal(extpath.join('../../foo/bar', '../bar/foo'), '../../foo/bar/foo'); - assert.equal(extpath.join('../foo/bar', '../bar/foo'), '../foo/bar/foo'); - assert.equal(extpath.join('/', 'bar'), '/bar'); - assert.equal(extpath.join('//server/far/boo', '../file.txt'), '//server/far/file.txt'); - assert.equal(extpath.join('/foo/', '/bar'), '/foo/bar'); - assert.equal(extpath.join('\\\\server\\far\\boo', '../file.txt'), '//server/far/file.txt'); - assert.equal(extpath.join('\\\\server\\far\\boo', './file.txt'), '//server/far/boo/file.txt'); - assert.equal(extpath.join('\\\\server\\far\\boo', '.\\file.txt'), '//server/far/boo/file.txt'); - assert.equal(extpath.join('\\\\server\\far\\boo', 'file.txt'), '//server/far/boo/file.txt'); - assert.equal(extpath.join('file:///c/users/test', 'test'), 'file:///c/users/test/test'); - assert.equal(extpath.join('file://localhost/c$/GitDevelopment/express', './settings'), 'file://localhost/c$/GitDevelopment/express/settings'); // unc - assert.equal(extpath.join('file://localhost/c$/GitDevelopment/express', '.settings'), 'file://localhost/c$/GitDevelopment/express/.settings'); // unc - assert.equal(extpath.join('foo', '/bar'), 'foo/bar'); - assert.equal(extpath.join('foo', 'bar'), 'foo/bar'); - assert.equal(extpath.join('foo', 'bar/'), 'foo/bar/'); - assert.equal(extpath.join('foo/', '/bar'), 'foo/bar'); - assert.equal(extpath.join('foo/', '/bar/'), 'foo/bar/'); - assert.equal(extpath.join('foo/', 'bar'), 'foo/bar'); - assert.equal(extpath.join('foo/bar', '../bar/foo'), 'foo/bar/foo'); - assert.equal(extpath.join('foo/bar', './bar/foo'), 'foo/bar/bar/foo'); - assert.equal(extpath.join('http://localhost/test', '../next'), 'http://localhost/next'); - assert.equal(extpath.join('http://localhost/test', 'test'), 'http://localhost/test/test'); + assert.equal(extpath.joinWithSlashes('.', 'bar'), 'bar'); + assert.equal(extpath.joinWithSlashes('../../foo/bar', '../../foo'), '../../foo'); + assert.equal(extpath.joinWithSlashes('../../foo/bar', '../bar/foo'), '../../foo/bar/foo'); + assert.equal(extpath.joinWithSlashes('../foo/bar', '../bar/foo'), '../foo/bar/foo'); + assert.equal(extpath.joinWithSlashes('/', 'bar'), '/bar'); + assert.equal(extpath.joinWithSlashes('//server/far/boo', '../file.txt'), '//server/far/file.txt'); + assert.equal(extpath.joinWithSlashes('/foo/', '/bar'), '/foo/bar'); + assert.equal(extpath.joinWithSlashes('\\\\server\\far\\boo', '../file.txt'), '//server/far/file.txt'); + assert.equal(extpath.joinWithSlashes('\\\\server\\far\\boo', './file.txt'), '//server/far/boo/file.txt'); + assert.equal(extpath.joinWithSlashes('\\\\server\\far\\boo', '.\\file.txt'), '//server/far/boo/file.txt'); + assert.equal(extpath.joinWithSlashes('\\\\server\\far\\boo', 'file.txt'), '//server/far/boo/file.txt'); + assert.equal(extpath.joinWithSlashes('file:///c/users/test', 'test'), 'file:///c/users/test/test'); + assert.equal(extpath.joinWithSlashes('file://localhost/c$/GitDevelopment/express', './settings'), 'file://localhost/c$/GitDevelopment/express/settings'); // unc + assert.equal(extpath.joinWithSlashes('file://localhost/c$/GitDevelopment/express', '.settings'), 'file://localhost/c$/GitDevelopment/express/.settings'); // unc + assert.equal(extpath.joinWithSlashes('foo', '/bar'), 'foo/bar'); + assert.equal(extpath.joinWithSlashes('foo', 'bar'), 'foo/bar'); + assert.equal(extpath.joinWithSlashes('foo', 'bar/'), 'foo/bar/'); + assert.equal(extpath.joinWithSlashes('foo/', '/bar'), 'foo/bar'); + assert.equal(extpath.joinWithSlashes('foo/', '/bar/'), 'foo/bar/'); + assert.equal(extpath.joinWithSlashes('foo/', 'bar'), 'foo/bar'); + assert.equal(extpath.joinWithSlashes('foo/bar', '../bar/foo'), 'foo/bar/foo'); + assert.equal(extpath.joinWithSlashes('foo/bar', './bar/foo'), 'foo/bar/bar/foo'); + assert.equal(extpath.joinWithSlashes('http://localhost/test', '../next'), 'http://localhost/next'); + assert.equal(extpath.joinWithSlashes('http://localhost/test', 'test'), 'http://localhost/test/test'); }); test('isUNC', () => { diff --git a/src/vs/base/test/common/resources.test.ts b/src/vs/base/test/common/resources.test.ts index c4fc9cc0f2e..cf1ad37c228 100644 --- a/src/vs/base/test/common/resources.test.ts +++ b/src/vs/base/test/common/resources.test.ts @@ -3,9 +3,13 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import { dirname, basename, distinctParents, joinPath, isEqual, isEqualOrParent, hasToIgnoreCase, normalizePath, isAbsolutePath, isMalformedFileUri, relativePath, removeTrailingPathSeparator, hasTrailingPathSeparator } from 'vs/base/common/resources'; +import { dirname, basename, distinctParents, joinPath, isEqual, isEqualOrParent, hasToIgnoreCase, normalizePath, isAbsolutePath, isMalformedFileUri, relativePath, removeTrailingPathSeparator, hasTrailingPathSeparator, resolvePath } from 'vs/base/common/resources'; import { URI, setUriThrowOnMissingScheme } from 'vs/base/common/uri'; import { isWindows } from 'vs/base/common/platform'; +import { toSlashes } from 'vs/base/common/extpath'; +import { startsWith } from 'vs/base/common/strings'; +import { isAbsolute } from 'vs/base/common/path'; + suite('Resources', () => { @@ -174,6 +178,7 @@ suite('Resources', () => { assertEqualURI(removeTrailingPathSeparator(u1), expected, u1.toString()); } + test('trailingPathSeparator', () => { assertTrailingSeparator(URI.parse('foo://a/foo'), false); assertTrailingSeparator(URI.parse('foo://a/foo/'), true); @@ -262,6 +267,50 @@ suite('Resources', () => { } }); + function assertResolve(u1: URI, path: string, expected: URI) { + const actual = resolvePath(u1, path); + assertEqualURI(actual, expected, `from ${u1.toString()} and ${path}`); + + if (!isAbsolute(path)) { + let expectedPath = isWindows ? toSlashes(path) : path; + expectedPath = startsWith(expectedPath, './') ? expectedPath.substr(2) : expectedPath; + assert.equal(relativePath(u1, actual), expectedPath, `relativePath (${u1.toString()}) on actual (${actual.toString()}) should be to path (${expectedPath})`); + } + } + + test('resolve', () => { + if (isWindows) { + assertResolve(URI.file('c:\\foo\\bar'), 'file.js', URI.file('c:\\foo\\bar\\file.js')); + assertResolve(URI.file('c:\\foo\\bar'), 't\\file.js', URI.file('c:\\foo\\bar\\t\\file.js')); + assertResolve(URI.file('c:\\foo\\bar'), '.\\t\\file.js', URI.file('c:\\foo\\bar\\t\\file.js')); + assertResolve(URI.file('c:\\foo\\bar'), 'a1/file.js', URI.file('c:\\foo\\bar\\a1\\file.js')); + assertResolve(URI.file('c:\\foo\\bar'), './a1/file.js', URI.file('c:\\foo\\bar\\a1\\file.js')); + assertResolve(URI.file('c:\\foo\\bar'), '\\b1\\file.js', URI.file('c:\\b1\\file.js')); + assertResolve(URI.file('c:\\foo\\bar'), '/b1/file.js', URI.file('c:\\b1\\file.js')); + assertResolve(URI.file('c:\\foo\\bar\\'), 'file.js', URI.file('c:\\foo\\bar\\file.js')); + + assertResolve(URI.file('c:\\'), 'file.js', URI.file('c:\\file.js')); + assertResolve(URI.file('c:\\'), '\\b1\\file.js', URI.file('c:\\b1\\file.js')); + assertResolve(URI.file('c:\\'), '/b1/file.js', URI.file('c:\\b1\\file.js')); + assertResolve(URI.file('c:\\'), 'd:\\foo\\bar.txt', URI.file('d:\\foo\\bar.txt')); + + assertResolve(URI.file('\\\\server\\share\\some\\'), 'b1\\file.js', URI.file('\\\\server\\share\\some\\b1\\file.js')); + assertResolve(URI.file('\\\\server\\share\\some\\'), '\\file.js', URI.file('\\\\server\\share\\file.js')); + } else { + assertResolve(URI.file('/foo/bar'), 'file.js', URI.file('/foo/bar/file.js')); + assertResolve(URI.file('/foo/bar'), './file.js', URI.file('/foo/bar/file.js')); + assertResolve(URI.file('/foo/bar'), '/file.js', URI.file('/file.js')); + assertResolve(URI.file('/foo/bar/'), 'file.js', URI.file('/foo/bar/file.js')); + assertResolve(URI.file('/'), 'file.js', URI.file('/file.js')); + assertResolve(URI.file(''), './file.js', URI.file('/file.js')); + assertResolve(URI.file(''), '/file.js', URI.file('/file.js')); + } + + assertResolve(URI.parse('foo://server/foo/bar'), 'file.js', URI.parse('foo://server/foo/bar/file.js')); + assertResolve(URI.parse('foo://server/foo/bar'), './file.js', URI.parse('foo://server/foo/bar/file.js')); + assertResolve(URI.parse('foo://server/foo/bar'), './file.js', URI.parse('foo://server/foo/bar/file.js')); + }); + test('isEqual', () => { let fileURI = isWindows ? URI.file('c:\\foo\\bar') : URI.file('/foo/bar'); let fileURI2 = isWindows ? URI.file('C:\\foo\\Bar') : URI.file('/foo/Bar'); diff --git a/src/vs/base/test/common/utils.ts b/src/vs/base/test/common/utils.ts index 5871f5afea3..0fb02a0193d 100644 --- a/src/vs/base/test/common/utils.ts +++ b/src/vs/base/test/common/utils.ts @@ -3,9 +3,10 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as extpath from 'vs/base/common/extpath'; +import { join } from 'vs/base/common/path'; import { URI } from 'vs/base/common/uri'; import { canceled } from 'vs/base/common/errors'; +import { isWindows } from 'vs/base/common/platform'; export type ValueCallback = (value: T | Promise) => void; @@ -49,7 +50,11 @@ export class DeferredPromise { } export function toResource(this: any, path: string) { - return URI.file(extpath.join('C:\\', Buffer.from(this.test.fullTitle()).toString('base64'), path)); + if (isWindows) { + return URI.file(join('C:\\', Buffer.from(this.test.fullTitle()).toString('base64'), path)); + } + + return URI.file(join('/', Buffer.from(this.test.fullTitle()).toString('base64'), path)); } export function suiteRepeat(n: number, description: string, callback: (this: any) => void): void { diff --git a/src/vs/base/test/node/glob.test.ts b/src/vs/base/test/node/glob.test.ts index 5728751678c..90a645e39bd 100644 --- a/src/vs/base/test/node/glob.test.ts +++ b/src/vs/base/test/node/glob.test.ts @@ -954,14 +954,14 @@ suite('Glob', () => { test('relative pattern - glob star', function () { if (isWindows) { - let p: glob.IRelativePattern = { base: 'C:\\DNXConsoleApp\\foo', pattern: '**/*.cs', pathToRelative: (from, to) => path.relative(from, to) }; + let p: glob.IRelativePattern = { base: 'C:\\DNXConsoleApp\\foo', pattern: '**/*.cs' }; assertGlobMatch(p, 'C:\\DNXConsoleApp\\foo\\Program.cs'); assertGlobMatch(p, 'C:\\DNXConsoleApp\\foo\\bar\\Program.cs'); assertNoGlobMatch(p, 'C:\\DNXConsoleApp\\foo\\Program.ts'); assertNoGlobMatch(p, 'C:\\DNXConsoleApp\\Program.cs'); assertNoGlobMatch(p, 'C:\\other\\DNXConsoleApp\\foo\\Program.ts'); } else { - let p: glob.IRelativePattern = { base: '/DNXConsoleApp/foo', pattern: '**/*.cs', pathToRelative: (from, to) => path.relative(from, to) }; + let p: glob.IRelativePattern = { base: '/DNXConsoleApp/foo', pattern: '**/*.cs' }; assertGlobMatch(p, '/DNXConsoleApp/foo/Program.cs'); assertGlobMatch(p, '/DNXConsoleApp/foo/bar/Program.cs'); assertNoGlobMatch(p, '/DNXConsoleApp/foo/Program.ts'); @@ -972,14 +972,14 @@ suite('Glob', () => { test('relative pattern - single star', function () { if (isWindows) { - let p: glob.IRelativePattern = { base: 'C:\\DNXConsoleApp\\foo', pattern: '*.cs', pathToRelative: (from, to) => path.relative(from, to) }; + let p: glob.IRelativePattern = { base: 'C:\\DNXConsoleApp\\foo', pattern: '*.cs' }; assertGlobMatch(p, 'C:\\DNXConsoleApp\\foo\\Program.cs'); assertNoGlobMatch(p, 'C:\\DNXConsoleApp\\foo\\bar\\Program.cs'); assertNoGlobMatch(p, 'C:\\DNXConsoleApp\\foo\\Program.ts'); assertNoGlobMatch(p, 'C:\\DNXConsoleApp\\Program.cs'); assertNoGlobMatch(p, 'C:\\other\\DNXConsoleApp\\foo\\Program.ts'); } else { - let p: glob.IRelativePattern = { base: '/DNXConsoleApp/foo', pattern: '*.cs', pathToRelative: (from, to) => path.relative(from, to) }; + let p: glob.IRelativePattern = { base: '/DNXConsoleApp/foo', pattern: '*.cs' }; assertGlobMatch(p, '/DNXConsoleApp/foo/Program.cs'); assertNoGlobMatch(p, '/DNXConsoleApp/foo/bar/Program.cs'); assertNoGlobMatch(p, '/DNXConsoleApp/foo/Program.ts'); @@ -990,11 +990,11 @@ suite('Glob', () => { test('relative pattern - single star with path', function () { if (isWindows) { - let p: glob.IRelativePattern = { base: 'C:\\DNXConsoleApp\\foo', pattern: 'something/*.cs', pathToRelative: (from, to) => path.relative(from, to) }; + let p: glob.IRelativePattern = { base: 'C:\\DNXConsoleApp\\foo', pattern: 'something/*.cs' }; assertGlobMatch(p, 'C:\\DNXConsoleApp\\foo\\something\\Program.cs'); assertNoGlobMatch(p, 'C:\\DNXConsoleApp\\foo\\Program.cs'); } else { - let p: glob.IRelativePattern = { base: '/DNXConsoleApp/foo', pattern: 'something/*.cs', pathToRelative: (from, to) => path.relative(from, to) }; + let p: glob.IRelativePattern = { base: '/DNXConsoleApp/foo', pattern: 'something/*.cs' }; assertGlobMatch(p, '/DNXConsoleApp/foo/something/Program.cs'); assertNoGlobMatch(p, '/DNXConsoleApp/foo/Program.cs'); } @@ -1006,11 +1006,11 @@ suite('Glob', () => { test('relative pattern - #57475', function () { if (isWindows) { - let p: glob.IRelativePattern = { base: 'C:\\DNXConsoleApp\\foo', pattern: 'styles/style.css', pathToRelative: (from, to) => path.relative(from, to) }; + let p: glob.IRelativePattern = { base: 'C:\\DNXConsoleApp\\foo', pattern: 'styles/style.css' }; assertGlobMatch(p, 'C:\\DNXConsoleApp\\foo\\styles\\style.css'); assertNoGlobMatch(p, 'C:\\DNXConsoleApp\\foo\\Program.cs'); } else { - let p: glob.IRelativePattern = { base: '/DNXConsoleApp/foo', pattern: 'styles/style.css', pathToRelative: (from, to) => path.relative(from, to) }; + let p: glob.IRelativePattern = { base: '/DNXConsoleApp/foo', pattern: 'styles/style.css' }; assertGlobMatch(p, '/DNXConsoleApp/foo/styles/style.css'); assertNoGlobMatch(p, '/DNXConsoleApp/foo/Program.cs'); } diff --git a/src/vs/code/electron-main/app.ts b/src/vs/code/electron-main/app.ts index c135f674cf1..878a5fe3685 100644 --- a/src/vs/code/electron-main/app.ts +++ b/src/vs/code/electron-main/app.ts @@ -63,9 +63,8 @@ import { hasArgs } from 'vs/platform/environment/node/argv'; import { RunOnceScheduler } from 'vs/base/common/async'; import { registerContextMenuListener } from 'vs/base/parts/contextmenu/electron-main/contextmenu'; import { storeBackgroundColor } from 'vs/code/electron-main/theme'; -import { join } from 'vs/base/common/extpath'; import { homedir } from 'os'; -import { sep } from 'vs/base/common/path'; +import { join, sep } from 'vs/base/common/path'; import { localize } from 'vs/nls'; import { REMOTE_HOST_SCHEME } from 'vs/platform/remote/common/remoteHosts'; import { REMOTE_FILE_SYSTEM_CHANNEL_NAME } from 'vs/platform/remote/node/remoteAgentFileSystemChannel'; @@ -73,7 +72,6 @@ import { ResolvedAuthority } from 'vs/platform/remote/common/remoteAuthorityReso import { SnapUpdateService } from 'vs/platform/update/electron-main/updateService.snap'; import { IStorageMainService, StorageMainService } from 'vs/platform/storage/node/storageMainService'; import { GlobalStorageDatabaseChannel } from 'vs/platform/storage/node/storageIpc'; -import { generateUuid } from 'vs/base/common/uuid'; import { startsWith } from 'vs/base/common/strings'; import { BackupMainService } from 'vs/platform/backup/electron-main/backupMainService'; import { IBackupMainService } from 'vs/platform/backup/common/backup'; @@ -461,6 +459,7 @@ export class CodeApplication extends Disposable { const appInstantiationService = this.instantiationService.createChild(services); + // Init services that require it return appInstantiationService.invokeFunction(accessor => Promise.all([ this.initStorageService(accessor), this.initBackupService(accessor) @@ -473,35 +472,8 @@ export class CodeApplication extends Disposable { // Ensure to close storage on shutdown this.lifecycleService.onWillShutdown(e => e.join(storageMainService.close())); - // Initialize storage service - return storageMainService.initialize().then(undefined, error => { - errors.onUnexpectedError(error); - this.logService.error(error); - }).then(() => { + return Promise.resolve(); - // Apply global telemetry values as part of the initialization - // These are global across all windows and thereby should be - // written from the main process once. - - const telemetryInstanceId = 'telemetry.instanceId'; - const instanceId = storageMainService.get(telemetryInstanceId, undefined); - if (instanceId === undefined) { - storageMainService.store(telemetryInstanceId, generateUuid()); - } - - const telemetryFirstSessionDate = 'telemetry.firstSessionDate'; - const firstSessionDate = storageMainService.get(telemetryFirstSessionDate, undefined); - if (firstSessionDate === undefined) { - storageMainService.store(telemetryFirstSessionDate, new Date().toUTCString()); - } - - const telemetryCurrentSessionDate = 'telemetry.currentSessionDate'; - const telemetryLastSessionDate = 'telemetry.lastSessionDate'; - const lastSessionDate = storageMainService.get(telemetryCurrentSessionDate, undefined); // previous session date was the "current" one at that time - const currentSessionDate = new Date().toUTCString(); // current session date is "now" - storageMainService.store(telemetryLastSessionDate, typeof lastSessionDate === 'undefined' ? null : lastSessionDate); - storageMainService.store(telemetryCurrentSessionDate, currentSessionDate); - }); } private initBackupService(accessor: ServicesAccessor): Promise { @@ -545,7 +517,7 @@ export class CodeApplication extends Disposable { this.electronIpcServer.registerChannel('url', urlChannel); const storageMainService = accessor.get(IStorageMainService); - const storageChannel = this._register(new GlobalStorageDatabaseChannel(storageMainService as StorageMainService)); + const storageChannel = this._register(new GlobalStorageDatabaseChannel(this.logService, storageMainService as StorageMainService)); this.electronIpcServer.registerChannel('storage', storageChannel); // Log level management diff --git a/src/vs/code/electron-main/windows.ts b/src/vs/code/electron-main/windows.ts index 749cd6b4116..912d224997f 100644 --- a/src/vs/code/electron-main/windows.ts +++ b/src/vs/code/electron-main/windows.ts @@ -601,10 +601,11 @@ export class WindowsManager implements IWindowsMainService { return; // ignore folders that are already open } - const fileInputsForWindow = (fileInputs && !fileInputs.remoteAuthority) ? fileInputs : undefined; + const remoteAuthority = getRemoteAuthority(workspaceToOpen.configPath); + const fileInputsForWindow = (fileInputs && fileInputs.remoteAuthority === remoteAuthority) ? fileInputs : undefined; // Do open folder - usedWindows.push(this.doOpenFolderOrWorkspace(openConfig, { workspace: workspaceToOpen }, openFolderInNewWindow, fileInputsForWindow)); + usedWindows.push(this.doOpenFolderOrWorkspace(openConfig, { workspace: workspaceToOpen, remoteAuthority }, openFolderInNewWindow, fileInputsForWindow)); // Reset these because we handled them if (fileInputsForWindow) { @@ -792,12 +793,15 @@ export class WindowsManager implements IWindowsMainService { // folders should be added to the existing window. if (!openConfig.addMode && isCommandLineOrAPICall) { const foldersToOpen = windowsToOpen.filter(path => !!path.folderUri); - if (foldersToOpen.length > 1 && foldersToOpen.every(f => f.folderUri!.scheme === Schemas.file)) { - const workspace = this.workspacesMainService.createUntitledWorkspaceSync(foldersToOpen.map(folder => ({ uri: folder.folderUri! }))); + if (foldersToOpen.length > 1) { + let remoteAuthority = foldersToOpen[0].remoteAuthority; + if (foldersToOpen.every(f => f.remoteAuthority === remoteAuthority)) { // only if all folder have the same authority + const workspace = this.workspacesMainService.createUntitledWorkspaceSync(foldersToOpen.map(folder => ({ uri: folder.folderUri! }))); - // Add workspace and remove folders thereby - windowsToOpen.push({ workspace, remoteAuthority: foldersToOpen[0].remoteAuthority }); - windowsToOpen = windowsToOpen.filter(path => !path.folderUri); + // Add workspace and remove folders thereby + windowsToOpen.push({ workspace, remoteAuthority }); + windowsToOpen = windowsToOpen.filter(path => !path.folderUri); + } } } diff --git a/src/vs/editor/browser/viewParts/indentGuides/indentGuides.ts b/src/vs/editor/browser/viewParts/indentGuides/indentGuides.ts index 1711beeab58..7a7e613f1e7 100644 --- a/src/vs/editor/browser/viewParts/indentGuides/indentGuides.ts +++ b/src/vs/editor/browser/viewParts/indentGuides/indentGuides.ts @@ -103,11 +103,10 @@ export class IndentGuidesOverlay extends DynamicViewOverlay { const visibleStartLineNumber = ctx.visibleRange.startLineNumber; const visibleEndLineNumber = ctx.visibleRange.endLineNumber; - const tabSize = this._context.model.getTabSize(); - const tabWidth = tabSize * this._spaceWidth; + const { indentSize } = this._context.model.getOptions(); + const indentWidth = indentSize * this._spaceWidth; const scrollWidth = ctx.scrollWidth; const lineHeight = this._lineHeight; - const indentGuideWidth = tabWidth; const indents = this._context.model.getLinesIndentGuides(visibleStartLineNumber, visibleEndLineNumber); @@ -132,8 +131,8 @@ export class IndentGuidesOverlay extends DynamicViewOverlay { let left = leftMostVisiblePosition ? leftMostVisiblePosition.left : 0; for (let i = 1; i <= indent; i++) { let className = (containsActiveIndentGuide && i === activeIndentLevel ? 'cigra' : 'cigr'); - result += `
`; - left += tabWidth; + result += `
`; + left += indentWidth; if (left > scrollWidth) { break; } diff --git a/src/vs/editor/browser/viewParts/lineNumbers/lineNumbers.ts b/src/vs/editor/browser/viewParts/lineNumbers/lineNumbers.ts index 339a20654aa..7bd48eed211 100644 --- a/src/vs/editor/browser/viewParts/lineNumbers/lineNumbers.ts +++ b/src/vs/editor/browser/viewParts/lineNumbers/lineNumbers.ts @@ -23,6 +23,7 @@ export class LineNumbersOverlay extends DynamicViewOverlay { private _lineHeight: number; private _renderLineNumbers: RenderLineNumbersType; private _renderCustomLineNumbers: ((lineNumber: number) => string) | null; + private _renderFinalNewline: boolean; private _lineNumbersLeft: number; private _lineNumbersWidth: number; private _lastCursorModelPosition: Position; @@ -44,6 +45,7 @@ export class LineNumbersOverlay extends DynamicViewOverlay { this._lineHeight = config.lineHeight; this._renderLineNumbers = config.viewInfo.renderLineNumbers; this._renderCustomLineNumbers = config.viewInfo.renderCustomLineNumbers; + this._renderFinalNewline = config.viewInfo.renderFinalNewline; this._lineNumbersLeft = config.layoutInfo.lineNumbersLeft; this._lineNumbersWidth = config.layoutInfo.lineNumbersWidth; } @@ -97,6 +99,15 @@ export class LineNumbersOverlay extends DynamicViewOverlay { } let modelLineNumber = modelPosition.lineNumber; + if (!this._renderFinalNewline) { + const lineCount = this._context.model.getLineCount(); + const lineContent = this._context.model.getLineContent(modelLineNumber); + + if (modelLineNumber === lineCount && lineContent === '') { + return ''; + } + } + if (this._renderCustomLineNumbers) { return this._renderCustomLineNumbers(modelLineNumber); } diff --git a/src/vs/editor/browser/viewParts/rulers/rulers.ts b/src/vs/editor/browser/viewParts/rulers/rulers.ts index eda625d0be6..5eca6700d6e 100644 --- a/src/vs/editor/browser/viewParts/rulers/rulers.ts +++ b/src/vs/editor/browser/viewParts/rulers/rulers.ts @@ -64,7 +64,8 @@ export class Rulers extends ViewPart { } if (currentCount < desiredCount) { - const rulerWidth = this._context.model.getTabSize(); + const { tabSize } = this._context.model.getOptions(); + const rulerWidth = tabSize; let addCount = desiredCount - currentCount; while (addCount > 0) { let node = createFastDomNode(document.createElement('div')); diff --git a/src/vs/editor/common/commands/shiftCommand.ts b/src/vs/editor/common/commands/shiftCommand.ts index fdc0a21e052..f24790ec137 100644 --- a/src/vs/editor/common/commands/shiftCommand.ts +++ b/src/vs/editor/common/commands/shiftCommand.ts @@ -15,30 +15,57 @@ import { LanguageConfigurationRegistry } from 'vs/editor/common/modes/languageCo export interface IShiftCommandOpts { isUnshift: boolean; tabSize: number; - oneIndent: string; + indentSize: number; + insertSpaces: boolean; useTabStops: boolean; } +const repeatCache: { [str: string]: string[]; } = Object.create(null); +export function cachedStringRepeat(str: string, count: number): string { + if (!repeatCache[str]) { + repeatCache[str] = ['', str]; + } + const cache = repeatCache[str]; + for (let i = cache.length; i <= count; i++) { + cache[i] = cache[i - 1] + str; + } + return cache[count]; +} + export class ShiftCommand implements ICommand { - public static unshiftIndentCount(line: string, column: number, tabSize: number): number { + public static unshiftIndent(line: string, column: number, tabSize: number, indentSize: number, insertSpaces: boolean): string { // Determine the visible column where the content starts - let contentStartVisibleColumn = CursorColumns.visibleColumnFromColumn(line, column, tabSize); + const contentStartVisibleColumn = CursorColumns.visibleColumnFromColumn(line, column, tabSize); - let desiredTabStop = CursorColumns.prevTabStop(contentStartVisibleColumn, tabSize); - - // The `desiredTabStop` is a multiple of `tabSize` => determine the number of indents - return desiredTabStop / tabSize; + if (insertSpaces) { + const indent = cachedStringRepeat(' ', indentSize); + const desiredTabStop = CursorColumns.prevIndentTabStop(contentStartVisibleColumn, indentSize); + const indentCount = desiredTabStop / indentSize; // will be an integer + return cachedStringRepeat(indent, indentCount); + } else { + const indent = '\t'; + const desiredTabStop = CursorColumns.prevRenderTabStop(contentStartVisibleColumn, tabSize); + const indentCount = desiredTabStop / tabSize; // will be an integer + return cachedStringRepeat(indent, indentCount); + } } - public static shiftIndentCount(line: string, column: number, tabSize: number): number { + public static shiftIndent(line: string, column: number, tabSize: number, indentSize: number, insertSpaces: boolean): string { // Determine the visible column where the content starts - let contentStartVisibleColumn = CursorColumns.visibleColumnFromColumn(line, column, tabSize); + const contentStartVisibleColumn = CursorColumns.visibleColumnFromColumn(line, column, tabSize); - let desiredTabStop = CursorColumns.nextTabStop(contentStartVisibleColumn, tabSize); - - // The `desiredTabStop` is a multiple of `tabSize` => determine the number of indents - return desiredTabStop / tabSize; + if (insertSpaces) { + const indent = cachedStringRepeat(' ', indentSize); + const desiredTabStop = CursorColumns.nextIndentTabStop(contentStartVisibleColumn, indentSize); + const indentCount = desiredTabStop / indentSize; // will be an integer + return cachedStringRepeat(indent, indentCount); + } else { + const indent = '\t'; + const desiredTabStop = CursorColumns.nextRenderTabStop(contentStartVisibleColumn, tabSize); + const indentCount = desiredTabStop / tabSize; // will be an integer + return cachedStringRepeat(indent, indentCount); + } } private _opts: IShiftCommandOpts; @@ -70,8 +97,7 @@ export class ShiftCommand implements ICommand { endLine = endLine - 1; } - const tabSize = this._opts.tabSize; - const oneIndent = this._opts.oneIndent; + const { tabSize, indentSize, insertSpaces } = this._opts; const shouldIndentEmptyLines = (startLine === endLine); // if indenting or outdenting on a whitespace only line @@ -82,9 +108,6 @@ export class ShiftCommand implements ICommand { } if (this._opts.useTabStops) { - // indents[i] represents i * oneIndent - let indents: string[] = ['', oneIndent]; - // keep track of previous line's "miss-alignment" let previousLineExtraSpaces = 0, extraSpaces = 0; for (let lineNumber = startLine; lineNumber <= endLine; lineNumber++ , previousLineExtraSpaces = extraSpaces) { @@ -109,7 +132,7 @@ export class ShiftCommand implements ICommand { if (lineNumber > 1) { let contentStartVisibleColumn = CursorColumns.visibleColumnFromColumn(lineText, indentationEndIndex + 1, tabSize); - if (contentStartVisibleColumn % tabSize !== 0) { + if (contentStartVisibleColumn % indentSize !== 0) { // The current line is "miss-aligned", so let's see if this is expected... // This can only happen when it has trailing commas in the indent if (model.isCheapToTokenize(lineNumber - 1)) { @@ -117,7 +140,7 @@ export class ShiftCommand implements ICommand { if (enterAction) { extraSpaces = previousLineExtraSpaces; if (enterAction.appendText) { - for (let j = 0, lenJ = enterAction.appendText.length; j < lenJ && extraSpaces < tabSize; j++) { + for (let j = 0, lenJ = enterAction.appendText.length; j < lenJ && extraSpaces < indentSize; j++) { if (enterAction.appendText.charCodeAt(j) === CharCode.Space) { extraSpaces++; } else { @@ -147,19 +170,14 @@ export class ShiftCommand implements ICommand { continue; } - let desiredIndentCount: number; + let desiredIndent: string; if (this._opts.isUnshift) { - desiredIndentCount = ShiftCommand.unshiftIndentCount(lineText, indentationEndIndex + 1, tabSize); + desiredIndent = ShiftCommand.unshiftIndent(lineText, indentationEndIndex + 1, tabSize, indentSize, insertSpaces); } else { - desiredIndentCount = ShiftCommand.shiftIndentCount(lineText, indentationEndIndex + 1, tabSize); + desiredIndent = ShiftCommand.shiftIndent(lineText, indentationEndIndex + 1, tabSize, indentSize, insertSpaces); } - // Fill `indents`, as needed - for (let j = indents.length; j <= desiredIndentCount; j++) { - indents[j] = indents[j - 1] + oneIndent; - } - - this._addEditOperation(builder, new Range(lineNumber, 1, lineNumber, indentationEndIndex + 1), indents[desiredIndentCount]); + this._addEditOperation(builder, new Range(lineNumber, 1, lineNumber, indentationEndIndex + 1), desiredIndent); if (lineNumber === startLine) { // Force the startColumn to stay put because we're inserting after it this._selectionStartColumnStaysPut = (this._selection.startColumn <= indentationEndIndex + 1); @@ -167,6 +185,8 @@ export class ShiftCommand implements ICommand { } } else { + const oneIndent = (insertSpaces ? cachedStringRepeat(' ', indentSize) : '\t'); + for (let lineNumber = startLine; lineNumber <= endLine; lineNumber++) { const lineText = model.getLineContent(lineNumber); let indentationEndIndex = strings.firstNonWhitespaceIndex(lineText); @@ -193,7 +213,7 @@ export class ShiftCommand implements ICommand { if (this._opts.isUnshift) { - indentationEndIndex = Math.min(indentationEndIndex, tabSize); + indentationEndIndex = Math.min(indentationEndIndex, indentSize); for (let i = 0; i < indentationEndIndex; i++) { const chr = lineText.charCodeAt(i); if (chr === CharCode.Tab) { diff --git a/src/vs/editor/common/config/commonEditorConfig.ts b/src/vs/editor/common/config/commonEditorConfig.ts index f529a258edc..f437ec14f35 100644 --- a/src/vs/editor/common/config/commonEditorConfig.ts +++ b/src/vs/editor/common/config/commonEditorConfig.ts @@ -264,6 +264,11 @@ const editorConfiguration: IConfigurationNode = { 'default': 'on', 'description': nls.localize('lineNumbers', "Controls the display of line numbers.") }, + 'editor.renderFinalNewline': { + 'type': 'boolean', + 'default': EDITOR_DEFAULTS.viewInfo.renderFinalNewline, + 'description': nls.localize('renderFinalNewline', "Render last line number when the file ends with a newline.") + }, 'editor.rulers': { 'type': 'array', 'items': { @@ -283,6 +288,20 @@ const editorConfiguration: IConfigurationNode = { 'minimum': 1, 'markdownDescription': nls.localize('tabSize', "The number of spaces a tab is equal to. This setting is overridden based on the file contents when `#editor.detectIndentation#` is on.") }, + 'editor.indentSize': { + 'anyOf': [ + { + 'type': 'string', + 'enum': ['tabSize'] + }, + { + 'type': 'number', + 'minimum': 1 + } + ], + 'default': 'tabSize', + 'markdownDescription': nls.localize('indentSize', "The number of spaces used for indentation or 'tabSize' to use the value from `#editor.tabSize#`. This setting is overridden based on the file contents when `#editor.detectIndentation#` is on.") + }, 'editor.insertSpaces': { 'type': 'boolean', 'default': EDITOR_MODEL_DEFAULTS.insertSpaces, diff --git a/src/vs/editor/common/config/editorOptions.ts b/src/vs/editor/common/config/editorOptions.ts index 7802259669d..f4f23181cd1 100644 --- a/src/vs/editor/common/config/editorOptions.ts +++ b/src/vs/editor/common/config/editorOptions.ts @@ -248,6 +248,11 @@ export interface IEditorOptions { * Defaults to true. */ lineNumbers?: 'on' | 'off' | 'relative' | 'interval' | ((lineNumber: number) => string); + /** + * Render last line number when the file ends with a newline. + * Defaults to true on Windows/Mac and to false on Linux. + */ + renderFinalNewline?: boolean; /** * Should the corresponding line be selected when clicking on the line number? * Defaults to true. @@ -951,6 +956,7 @@ export interface InternalEditorViewOptions { readonly ariaLabel: string; readonly renderLineNumbers: RenderLineNumbersType; readonly renderCustomLineNumbers: ((lineNumber: number) => string) | null; + readonly renderFinalNewline: boolean; readonly selectOnLineNumbers: boolean; readonly glyphMargin: boolean; readonly revealHorizontalRightPadding: number; @@ -1258,6 +1264,7 @@ export class InternalEditorOptions { && a.ariaLabel === b.ariaLabel && a.renderLineNumbers === b.renderLineNumbers && a.renderCustomLineNumbers === b.renderCustomLineNumbers + && a.renderFinalNewline === b.renderFinalNewline && a.selectOnLineNumbers === b.selectOnLineNumbers && a.glyphMargin === b.glyphMargin && a.revealHorizontalRightPadding === b.revealHorizontalRightPadding @@ -1995,6 +2002,7 @@ export class EditorOptionsValidator { ariaLabel: _string(opts.ariaLabel, defaults.ariaLabel), renderLineNumbers: renderLineNumbers, renderCustomLineNumbers: renderCustomLineNumbers, + renderFinalNewline: _boolean(opts.renderFinalNewline, defaults.renderFinalNewline), selectOnLineNumbers: _boolean(opts.selectOnLineNumbers, defaults.selectOnLineNumbers), glyphMargin: _boolean(opts.glyphMargin, defaults.glyphMargin), revealHorizontalRightPadding: _clampedInt(opts.revealHorizontalRightPadding, defaults.revealHorizontalRightPadding, 0, 1000), @@ -2115,6 +2123,7 @@ export class InternalEditorOptionsFactory { ariaLabel: (accessibilityIsOff ? nls.localize('accessibilityOffAriaLabel', "The editor is not accessible at this time. Press Alt+F1 for options.") : opts.viewInfo.ariaLabel), renderLineNumbers: opts.viewInfo.renderLineNumbers, renderCustomLineNumbers: opts.viewInfo.renderCustomLineNumbers, + renderFinalNewline: opts.viewInfo.renderFinalNewline, selectOnLineNumbers: opts.viewInfo.selectOnLineNumbers, glyphMargin: opts.viewInfo.glyphMargin, revealHorizontalRightPadding: opts.viewInfo.revealHorizontalRightPadding, @@ -2532,6 +2541,7 @@ export const EDITOR_FONT_DEFAULTS = { */ export const EDITOR_MODEL_DEFAULTS = { tabSize: 4, + indentSize: 4, insertSpaces: true, detectIndentation: true, trimAutoWhitespace: true, @@ -2577,6 +2587,7 @@ export const EDITOR_DEFAULTS: IValidatedEditorOptions = { ariaLabel: nls.localize('editorViewAccessibleLabel', "Editor content"), renderLineNumbers: RenderLineNumbersType.On, renderCustomLineNumbers: null, + renderFinalNewline: (platform.isLinux ? false : true), selectOnLineNumbers: true, glyphMargin: true, revealHorizontalRightPadding: 30, diff --git a/src/vs/editor/common/controller/cursorCommon.ts b/src/vs/editor/common/controller/cursorCommon.ts index 99deb5a4635..0656b3d5ba9 100644 --- a/src/vs/editor/common/controller/cursorCommon.ts +++ b/src/vs/editor/common/controller/cursorCommon.ts @@ -74,8 +74,8 @@ export class CursorConfiguration { public readonly readOnly: boolean; public readonly tabSize: number; + public readonly indentSize: number; public readonly insertSpaces: boolean; - public readonly oneIndent: string; public readonly pageSize: number; public readonly lineHeight: number; public readonly useTabStops: boolean; @@ -112,7 +112,6 @@ export class CursorConfiguration { constructor( languageIdentifier: LanguageIdentifier, - oneIndent: string, modelOptions: TextModelResolvedOptions, configuration: IConfiguration ) { @@ -122,8 +121,8 @@ export class CursorConfiguration { this.readOnly = c.readOnly; this.tabSize = modelOptions.tabSize; + this.indentSize = modelOptions.indentSize; this.insertSpaces = modelOptions.insertSpaces; - this.oneIndent = oneIndent; this.pageSize = Math.max(1, Math.floor(c.layoutInfo.height / c.fontInfo.lineHeight) - 2); this.lineHeight = c.lineHeight; this.useTabStops = c.useTabStops; @@ -176,7 +175,7 @@ export class CursorConfiguration { } public normalizeIndentation(str: string): string { - return TextModel.normalizeIndentation(str, this.tabSize, this.insertSpaces); + return TextModel.normalizeIndentation(str, this.indentSize, this.insertSpaces); } private static _getElectricCharacters(languageIdentifier: LanguageIdentifier): string[] | null { @@ -342,7 +341,6 @@ export class CursorContext { this.viewModel = viewModel; this.config = new CursorConfiguration( this.model.getLanguageIdentifier(), - this.model.getOneIndent(), this.model.getOptions(), configuration ); @@ -518,7 +516,7 @@ export class CursorColumns { for (let i = 0; i < endOffset; i++) { let charCode = lineContent.charCodeAt(i); if (charCode === CharCode.Tab) { - result = this.nextTabStop(result, tabSize); + result = this.nextRenderTabStop(result, tabSize); } else if (strings.isFullWidthCharacter(charCode)) { result = result + 2; } else { @@ -545,7 +543,7 @@ export class CursorColumns { let afterVisibleColumn: number; if (charCode === CharCode.Tab) { - afterVisibleColumn = this.nextTabStop(beforeVisibleColumn, tabSize); + afterVisibleColumn = this.nextRenderTabStop(beforeVisibleColumn, tabSize); } else if (strings.isFullWidthCharacter(charCode)) { afterVisibleColumn = beforeVisibleColumn + 2; } else { @@ -588,16 +586,30 @@ export class CursorColumns { /** * ATTENTION: This works with 0-based columns (as oposed to the regular 1-based columns) */ - public static nextTabStop(visibleColumn: number, tabSize: number): number { + public static nextRenderTabStop(visibleColumn: number, tabSize: number): number { return visibleColumn + tabSize - visibleColumn % tabSize; } /** * ATTENTION: This works with 0-based columns (as oposed to the regular 1-based columns) */ - public static prevTabStop(column: number, tabSize: number): number { + public static nextIndentTabStop(visibleColumn: number, indentSize: number): number { + return visibleColumn + indentSize - visibleColumn % indentSize; + } + + /** + * ATTENTION: This works with 0-based columns (as oposed to the regular 1-based columns) + */ + public static prevRenderTabStop(column: number, tabSize: number): number { return column - 1 - (column - 1) % tabSize; } + + /** + * ATTENTION: This works with 0-based columns (as oposed to the regular 1-based columns) + */ + public static prevIndentTabStop(column: number, indentSize: number): number { + return column - 1 - (column - 1) % indentSize; + } } export function isQuote(ch: string): boolean { diff --git a/src/vs/editor/common/controller/cursorDeleteOperations.ts b/src/vs/editor/common/controller/cursorDeleteOperations.ts index 799a719c485..5a7830e26b3 100644 --- a/src/vs/editor/common/controller/cursorDeleteOperations.ts +++ b/src/vs/editor/common/controller/cursorDeleteOperations.ts @@ -131,7 +131,7 @@ export class DeleteOperations { if (position.column <= lastIndentationColumn) { let fromVisibleColumn = CursorColumns.visibleColumnFromColumn2(config, model, position); - let toVisibleColumn = CursorColumns.prevTabStop(fromVisibleColumn, config.tabSize); + let toVisibleColumn = CursorColumns.prevIndentTabStop(fromVisibleColumn, config.indentSize); let toColumn = CursorColumns.columnFromVisibleColumn2(config, model, position.lineNumber, toVisibleColumn); deleteSelection = new Range(position.lineNumber, toColumn, position.lineNumber, position.column); } else { diff --git a/src/vs/editor/common/controller/cursorTypeOperations.ts b/src/vs/editor/common/controller/cursorTypeOperations.ts index ac585844d86..032d035e41e 100644 --- a/src/vs/editor/common/controller/cursorTypeOperations.ts +++ b/src/vs/editor/common/controller/cursorTypeOperations.ts @@ -31,7 +31,8 @@ export class TypeOperations { commands[i] = new ShiftCommand(selections[i], { isUnshift: false, tabSize: config.tabSize, - oneIndent: config.oneIndent, + indentSize: config.indentSize, + insertSpaces: config.insertSpaces, useTabStops: config.useTabStops }); } @@ -44,7 +45,8 @@ export class TypeOperations { commands[i] = new ShiftCommand(selections[i], { isUnshift: true, tabSize: config.tabSize, - oneIndent: config.oneIndent, + indentSize: config.indentSize, + insertSpaces: config.insertSpaces, useTabStops: config.useTabStops }); } @@ -53,24 +55,12 @@ export class TypeOperations { public static shiftIndent(config: CursorConfiguration, indentation: string, count?: number): string { count = count || 1; - let desiredIndentCount = ShiftCommand.shiftIndentCount(indentation, indentation.length + count, config.tabSize); - let newIndentation = ''; - for (let i = 0; i < desiredIndentCount; i++) { - newIndentation += '\t'; - } - - return newIndentation; + return ShiftCommand.shiftIndent(indentation, indentation.length + count, config.tabSize, config.indentSize, config.insertSpaces); } public static unshiftIndent(config: CursorConfiguration, indentation: string, count?: number): string { count = count || 1; - let desiredIndentCount = ShiftCommand.unshiftIndentCount(indentation, indentation.length + count, config.tabSize); - let newIndentation = ''; - for (let i = 0; i < desiredIndentCount; i++) { - newIndentation += '\t'; - } - - return newIndentation; + return ShiftCommand.unshiftIndent(indentation, indentation.length + count, config.tabSize, config.indentSize, config.insertSpaces); } private static _distributedPaste(config: CursorConfiguration, model: ICursorSimpleModel, selections: Selection[], text: string[]): EditOperationResult { @@ -209,8 +199,8 @@ export class TypeOperations { let position = selection.getStartPosition(); if (config.insertSpaces) { let visibleColumnFromColumn = CursorColumns.visibleColumnFromColumn2(config, model, position); - let tabSize = config.tabSize; - let spacesCnt = tabSize - (visibleColumnFromColumn % tabSize); + let indentSize = config.indentSize; + let spacesCnt = indentSize - (visibleColumnFromColumn % indentSize); for (let i = 0; i < spacesCnt; i++) { typeText += ' '; } @@ -254,7 +244,8 @@ export class TypeOperations { commands[i] = new ShiftCommand(selection, { isUnshift: false, tabSize: config.tabSize, - oneIndent: config.oneIndent, + indentSize: config.indentSize, + insertSpaces: config.insertSpaces, useTabStops: config.useTabStops }); } @@ -377,7 +368,7 @@ export class TypeOperations { let offset = 0; if (oldEndColumn <= firstNonWhitespace + 1) { if (!config.insertSpaces) { - oldEndViewColumn = Math.ceil(oldEndViewColumn / config.tabSize); + oldEndViewColumn = Math.ceil(oldEndViewColumn / config.indentSize); } offset = Math.min(oldEndViewColumn + 1 - config.normalizeIndentation(ir.afterEnter).length - 1, 0); } diff --git a/src/vs/editor/common/model.ts b/src/vs/editor/common/model.ts index 9d9a1a7d8c4..fa9125ff8f8 100644 --- a/src/vs/editor/common/model.ts +++ b/src/vs/editor/common/model.ts @@ -346,6 +346,7 @@ export class TextModelResolvedOptions { _textModelResolvedOptionsBrand: void; readonly tabSize: number; + readonly indentSize: number; readonly insertSpaces: boolean; readonly defaultEOL: DefaultEndOfLine; readonly trimAutoWhitespace: boolean; @@ -355,11 +356,13 @@ export class TextModelResolvedOptions { */ constructor(src: { tabSize: number; + indentSize: number; insertSpaces: boolean; defaultEOL: DefaultEndOfLine; trimAutoWhitespace: boolean; }) { this.tabSize = src.tabSize | 0; + this.indentSize = src.indentSize | 0; this.insertSpaces = Boolean(src.insertSpaces); this.defaultEOL = src.defaultEOL | 0; this.trimAutoWhitespace = Boolean(src.trimAutoWhitespace); @@ -371,6 +374,7 @@ export class TextModelResolvedOptions { public equals(other: TextModelResolvedOptions): boolean { return ( this.tabSize === other.tabSize + && this.indentSize === other.indentSize && this.insertSpaces === other.insertSpaces && this.defaultEOL === other.defaultEOL && this.trimAutoWhitespace === other.trimAutoWhitespace @@ -383,6 +387,7 @@ export class TextModelResolvedOptions { public createChangeEvent(newOpts: TextModelResolvedOptions): IModelOptionsChangedEvent { return { tabSize: this.tabSize !== newOpts.tabSize, + indentSize: this.indentSize !== newOpts.indentSize, insertSpaces: this.insertSpaces !== newOpts.insertSpaces, trimAutoWhitespace: this.trimAutoWhitespace !== newOpts.trimAutoWhitespace, }; @@ -394,6 +399,7 @@ export class TextModelResolvedOptions { */ export interface ITextModelCreationOptions { tabSize: number; + indentSize: number; insertSpaces: boolean; detectIndentation: boolean; trimAutoWhitespace: boolean; @@ -404,6 +410,7 @@ export interface ITextModelCreationOptions { export interface ITextModelUpdateOptions { tabSize?: number; + indentSize?: number; insertSpaces?: boolean; trimAutoWhitespace?: boolean; } @@ -951,11 +958,6 @@ export interface ITextModel { */ normalizeIndentation(str: string): string; - /** - * Get what is considered to be one indent (e.g. a tab character or 4 spaces, etc.). - */ - getOneIndent(): string; - /** * Change the options of this model. */ diff --git a/src/vs/editor/common/model/textModel.ts b/src/vs/editor/common/model/textModel.ts index 3a0d722fdea..7f9d8351b14 100644 --- a/src/vs/editor/common/model/textModel.ts +++ b/src/vs/editor/common/model/textModel.ts @@ -163,6 +163,7 @@ export class TextModel extends Disposable implements model.ITextModel { public static DEFAULT_CREATION_OPTIONS: model.ITextModelCreationOptions = { isForSimpleWidget: false, tabSize: EDITOR_MODEL_DEFAULTS.tabSize, + indentSize: EDITOR_MODEL_DEFAULTS.indentSize, insertSpaces: EDITOR_MODEL_DEFAULTS.insertSpaces, detectIndentation: false, defaultEOL: model.DefaultEndOfLine.LF, @@ -179,6 +180,7 @@ export class TextModel extends Disposable implements model.ITextModel { const guessedIndentation = guessIndentation(textBuffer, options.tabSize, options.insertSpaces); return new model.TextModelResolvedOptions({ tabSize: guessedIndentation.tabSize, + indentSize: guessedIndentation.tabSize, // TODO@Alex: guess indentSize independent of tabSize insertSpaces: guessedIndentation.insertSpaces, trimAutoWhitespace: options.trimAutoWhitespace, defaultEOL: options.defaultEOL @@ -187,6 +189,7 @@ export class TextModel extends Disposable implements model.ITextModel { return new model.TextModelResolvedOptions({ tabSize: options.tabSize, + indentSize: options.indentSize, insertSpaces: options.insertSpaces, trimAutoWhitespace: options.trimAutoWhitespace, defaultEOL: options.defaultEOL @@ -590,11 +593,13 @@ export class TextModel extends Disposable implements model.ITextModel { public updateOptions(_newOpts: model.ITextModelUpdateOptions): void { this._assertNotDisposed(); let tabSize = (typeof _newOpts.tabSize !== 'undefined') ? _newOpts.tabSize : this._options.tabSize; + let indentSize = (typeof _newOpts.indentSize !== 'undefined') ? _newOpts.indentSize : this._options.indentSize; let insertSpaces = (typeof _newOpts.insertSpaces !== 'undefined') ? _newOpts.insertSpaces : this._options.insertSpaces; let trimAutoWhitespace = (typeof _newOpts.trimAutoWhitespace !== 'undefined') ? _newOpts.trimAutoWhitespace : this._options.trimAutoWhitespace; let newOpts = new model.TextModelResolvedOptions({ tabSize: tabSize, + indentSize: indentSize, insertSpaces: insertSpaces, defaultEOL: this._options.defaultEOL, trimAutoWhitespace: trimAutoWhitespace @@ -615,15 +620,16 @@ export class TextModel extends Disposable implements model.ITextModel { let guessedIndentation = guessIndentation(this._buffer, defaultTabSize, defaultInsertSpaces); this.updateOptions({ insertSpaces: guessedIndentation.insertSpaces, - tabSize: guessedIndentation.tabSize + tabSize: guessedIndentation.tabSize, + indentSize: guessedIndentation.tabSize, // TODO@Alex: guess indentSize independent of tabSize }); } - private static _normalizeIndentationFromWhitespace(str: string, tabSize: number, insertSpaces: boolean): string { + private static _normalizeIndentationFromWhitespace(str: string, indentSize: number, insertSpaces: boolean): string { let spacesCnt = 0; for (let i = 0; i < str.length; i++) { if (str.charAt(i) === '\t') { - spacesCnt += tabSize; + spacesCnt += indentSize; } else { spacesCnt++; } @@ -631,8 +637,8 @@ export class TextModel extends Disposable implements model.ITextModel { let result = ''; if (!insertSpaces) { - let tabsCnt = Math.floor(spacesCnt / tabSize); - spacesCnt = spacesCnt % tabSize; + let tabsCnt = Math.floor(spacesCnt / indentSize); + spacesCnt = spacesCnt % indentSize; for (let i = 0; i < tabsCnt; i++) { result += '\t'; } @@ -645,33 +651,17 @@ export class TextModel extends Disposable implements model.ITextModel { return result; } - public static normalizeIndentation(str: string, tabSize: number, insertSpaces: boolean): string { + public static normalizeIndentation(str: string, indentSize: number, insertSpaces: boolean): string { let firstNonWhitespaceIndex = strings.firstNonWhitespaceIndex(str); if (firstNonWhitespaceIndex === -1) { firstNonWhitespaceIndex = str.length; } - return TextModel._normalizeIndentationFromWhitespace(str.substring(0, firstNonWhitespaceIndex), tabSize, insertSpaces) + str.substring(firstNonWhitespaceIndex); + return TextModel._normalizeIndentationFromWhitespace(str.substring(0, firstNonWhitespaceIndex), indentSize, insertSpaces) + str.substring(firstNonWhitespaceIndex); } public normalizeIndentation(str: string): string { this._assertNotDisposed(); - return TextModel.normalizeIndentation(str, this._options.tabSize, this._options.insertSpaces); - } - - public getOneIndent(): string { - this._assertNotDisposed(); - let tabSize = this._options.tabSize; - let insertSpaces = this._options.insertSpaces; - - if (insertSpaces) { - let result = ''; - for (let i = 0; i < tabSize; i++) { - result += ' '; - } - return result; - } else { - return '\t'; - } + return TextModel.normalizeIndentation(str, this._options.indentSize, this._options.insertSpaces); } //#endregion @@ -2574,7 +2564,7 @@ export class TextModel extends Disposable implements model.ITextModel { // Use the line's indent up_belowContentLineIndex = upLineNumber - 1; up_belowContentLineIndent = currentIndent; - upLineIndentLevel = Math.ceil(currentIndent / this._options.tabSize); + upLineIndentLevel = Math.ceil(currentIndent / this._options.indentSize); } else { up_resolveIndents(upLineNumber); upLineIndentLevel = this._getIndentLevelForWhitespaceLine(offSide, up_aboveContentLineIndent, up_belowContentLineIndent); @@ -2609,7 +2599,7 @@ export class TextModel extends Disposable implements model.ITextModel { // Use the line's indent down_aboveContentLineIndex = downLineNumber - 1; down_aboveContentLineIndent = currentIndent; - downLineIndentLevel = Math.ceil(currentIndent / this._options.tabSize); + downLineIndentLevel = Math.ceil(currentIndent / this._options.indentSize); } else { down_resolveIndents(downLineNumber); downLineIndentLevel = this._getIndentLevelForWhitespaceLine(offSide, down_aboveContentLineIndent, down_belowContentLineIndent); @@ -2657,7 +2647,7 @@ export class TextModel extends Disposable implements model.ITextModel { // Use the line's indent aboveContentLineIndex = lineNumber - 1; aboveContentLineIndent = currentIndent; - result[resultIndex] = Math.ceil(currentIndent / this._options.tabSize); + result[resultIndex] = Math.ceil(currentIndent / this._options.indentSize); continue; } @@ -2704,20 +2694,20 @@ export class TextModel extends Disposable implements model.ITextModel { } else if (aboveContentLineIndent < belowContentLineIndent) { // we are inside the region above - return (1 + Math.floor(aboveContentLineIndent / this._options.tabSize)); + return (1 + Math.floor(aboveContentLineIndent / this._options.indentSize)); } else if (aboveContentLineIndent === belowContentLineIndent) { // we are in between two regions - return Math.ceil(belowContentLineIndent / this._options.tabSize); + return Math.ceil(belowContentLineIndent / this._options.indentSize); } else { if (offSide) { // same level as region below - return Math.ceil(belowContentLineIndent / this._options.tabSize); + return Math.ceil(belowContentLineIndent / this._options.indentSize); } else { // we are inside the region that ends below - return (1 + Math.floor(belowContentLineIndent / this._options.tabSize)); + return (1 + Math.floor(belowContentLineIndent / this._options.indentSize)); } } diff --git a/src/vs/editor/common/model/textModelEvents.ts b/src/vs/editor/common/model/textModelEvents.ts index 458566e1014..fc84cb84f93 100644 --- a/src/vs/editor/common/model/textModelEvents.ts +++ b/src/vs/editor/common/model/textModelEvents.ts @@ -97,6 +97,7 @@ export interface IModelTokensChangedEvent { export interface IModelOptionsChangedEvent { readonly tabSize: boolean; + readonly indentSize: boolean; readonly insertSpaces: boolean; readonly trimAutoWhitespace: boolean; } diff --git a/src/vs/editor/common/services/modelServiceImpl.ts b/src/vs/editor/common/services/modelServiceImpl.ts index 183f659fc29..135d4303ac4 100644 --- a/src/vs/editor/common/services/modelServiceImpl.ts +++ b/src/vs/editor/common/services/modelServiceImpl.ts @@ -73,6 +73,7 @@ class ModelData implements IDisposable { interface IRawEditorConfig { tabSize?: any; + indentSize?: any; insertSpaces?: any; detectIndentation?: any; trimAutoWhitespace?: any; @@ -138,6 +139,17 @@ export class ModelServiceImpl extends Disposable implements IModelService { } } + let indentSize = tabSize; + if (config.editor && typeof config.editor.indentSize !== 'undefined' && config.editor.indentSize !== 'tabSize') { + let parsedIndentSize = parseInt(config.editor.indentSize, 10); + if (!isNaN(parsedIndentSize)) { + indentSize = parsedIndentSize; + } + if (indentSize < 1) { + indentSize = 1; + } + } + let insertSpaces = EDITOR_MODEL_DEFAULTS.insertSpaces; if (config.editor && typeof config.editor.insertSpaces !== 'undefined') { insertSpaces = (config.editor.insertSpaces === 'false' ? false : Boolean(config.editor.insertSpaces)); @@ -169,6 +181,7 @@ export class ModelServiceImpl extends Disposable implements IModelService { return { isForSimpleWidget: isForSimpleWidget, tabSize: tabSize, + indentSize: indentSize, insertSpaces: insertSpaces, detectIndentation: detectIndentation, defaultEOL: newDefaultEOL, @@ -210,6 +223,7 @@ export class ModelServiceImpl extends Disposable implements IModelService { && (currentOptions.detectIndentation === newOptions.detectIndentation) && (currentOptions.insertSpaces === newOptions.insertSpaces) && (currentOptions.tabSize === newOptions.tabSize) + && (currentOptions.indentSize === newOptions.indentSize) && (currentOptions.trimAutoWhitespace === newOptions.trimAutoWhitespace) ) { // Same indent opts, no need to touch the model @@ -225,6 +239,7 @@ export class ModelServiceImpl extends Disposable implements IModelService { model.updateOptions({ insertSpaces: newOptions.insertSpaces, tabSize: newOptions.tabSize, + indentSize: newOptions.indentSize, trimAutoWhitespace: newOptions.trimAutoWhitespace }); } diff --git a/src/vs/editor/common/viewModel/viewModel.ts b/src/vs/editor/common/viewModel/viewModel.ts index 3c3dffa41d5..1c889284c76 100644 --- a/src/vs/editor/common/viewModel/viewModel.ts +++ b/src/vs/editor/common/viewModel/viewModel.ts @@ -10,7 +10,7 @@ import { IViewLineTokens } from 'vs/editor/common/core/lineTokens'; import { IPosition, Position } from 'vs/editor/common/core/position'; import { IRange, Range } from 'vs/editor/common/core/range'; import { INewScrollPosition } from 'vs/editor/common/editorCommon'; -import { EndOfLinePreference, IActiveIndentGuideInfo, IModelDecorationOptions } from 'vs/editor/common/model'; +import { EndOfLinePreference, IActiveIndentGuideInfo, IModelDecorationOptions, TextModelResolvedOptions } from 'vs/editor/common/model'; import { IViewEventListener } from 'vs/editor/common/view/viewEvents'; import { IPartialViewLinesViewportData } from 'vs/editor/common/viewLayout/viewLinesViewportData'; import { IEditorWhitespace } from 'vs/editor/common/viewLayout/whitespaceComputer'; @@ -129,7 +129,7 @@ export interface IViewModel { getCompletelyVisibleViewRange(): Range; getCompletelyVisibleViewRangeAtScrollTop(scrollTop: number): Range; - getTabSize(): number; + getOptions(): TextModelResolvedOptions; getLineCount(): number; getLineContent(lineNumber: number): string; getLineLength(lineNumber: number): number; diff --git a/src/vs/editor/common/viewModel/viewModelImpl.ts b/src/vs/editor/common/viewModel/viewModelImpl.ts index 9681d2164b7..e721c4af6cf 100644 --- a/src/vs/editor/common/viewModel/viewModelImpl.ts +++ b/src/vs/editor/common/viewModel/viewModelImpl.ts @@ -10,7 +10,7 @@ import { IConfigurationChangedEvent } from 'vs/editor/common/config/editorOption import { IPosition, Position } from 'vs/editor/common/core/position'; import { IRange, Range } from 'vs/editor/common/core/range'; import * as editorCommon from 'vs/editor/common/editorCommon'; -import { EndOfLinePreference, IActiveIndentGuideInfo, ITextModel, TrackedRangeStickiness } from 'vs/editor/common/model'; +import { EndOfLinePreference, IActiveIndentGuideInfo, ITextModel, TrackedRangeStickiness, TextModelResolvedOptions } from 'vs/editor/common/model'; import { ModelDecorationOverviewRulerOptions } from 'vs/editor/common/model/textModel'; import * as textModelEvents from 'vs/editor/common/model/textModelEvents'; import { ColorId, LanguageId, TokenizationRegistry } from 'vs/editor/common/modes'; @@ -448,10 +448,14 @@ export class ViewModel extends viewEvents.ViewEventEmitter implements IViewModel }; } - public getTabSize(): number { + private getTabSize(): number { return this.model.getOptions().tabSize; } + public getOptions(): TextModelResolvedOptions { + return this.model.getOptions(); + } + public getLineCount(): number { return this.lines.getViewLineCount(); } diff --git a/src/vs/editor/contrib/gotoError/gotoError.ts b/src/vs/editor/contrib/gotoError/gotoError.ts index 83281459b5e..f6da3f9a22c 100644 --- a/src/vs/editor/contrib/gotoError/gotoError.ts +++ b/src/vs/editor/contrib/gotoError/gotoError.ts @@ -120,6 +120,17 @@ class MarkerModel { return this.canNavigate() ? this._markers[this._nextIdx] : undefined; } + set currentMarker(marker: IMarker | undefined) { + const idx = this._nextIdx; + this._nextIdx = -1; + if (marker) { + this._nextIdx = this.indexOf(marker); + } + if (this._nextIdx !== idx) { + this._onCurrentMarkerChanged.fire(marker); + } + } + public move(fwd: boolean, inCircles: boolean): boolean { if (!this.canNavigate()) { this._onCurrentMarkerChanged.fire(undefined); @@ -181,7 +192,7 @@ class MarkerModel { } } -class MarkerController implements editorCommon.IEditorContribution { +export class MarkerController implements editorCommon.IEditorContribution { private static readonly ID = 'editor.contrib.markerController'; @@ -280,6 +291,11 @@ class MarkerController implements editorCommon.IEditorContribution { } } + public show(marker: IMarker): void { + const model = this.getOrCreateModel(); + model.currentMarker = marker; + } + private _onMarkerChanged(changedResources: URI[]): void { let editorModel = this._editor.getModel(); if (!editorModel) { diff --git a/src/vs/editor/contrib/hover/hover.css b/src/vs/editor/contrib/hover/hover.css index 0aaffeb0b08..6aa31e6e1fc 100644 --- a/src/vs/editor/contrib/hover/hover.css +++ b/src/vs/editor/contrib/hover/hover.css @@ -23,12 +23,8 @@ display: none; } -.monaco-editor-hover .monaco-editor-hover-content { - max-width: 500px; -} - .monaco-editor-hover .hover-row { - padding: 4px 5px; + padding: 4px 8px; } .monaco-editor-hover p, @@ -75,3 +71,40 @@ white-space: pre-wrap; word-break: break-all; } + +.monaco-editor-hover .marker-hover { + display: flex; +} + +.monaco-editor-hover .marker-hover > .marker { + flex: 1; +} + +.monaco-editor-hover .marker-hover > .actions { + padding-left: 12px; + display: flex; +} + +.monaco-editor-hover .marker-hover > .actions > .icon { + width: 16px; + height: 16px; +} + +.monaco-editor-hover .marker-hover > .actions .action { + cursor: pointer; + padding-left: 4px; +} + +.monaco-editor-hover .marker-hover > .actions > .peek-marker { + font-size: large; + vertical-align: middle; +} + +.monaco-editor-hover .marker-hover > .actions > .light-bulb { + background: url('../codeAction/lightbulb.svg') center center no-repeat; +} + +.hc-black .monaco-editor-hover .marker-hover > .actions > .light-bulb, +.vs-dark .monaco-editor-hover .marker-hover > .actions > .light-bulb { + background: url('../codeAction/lightbulb-dark.svg') center center no-repeat; +} diff --git a/src/vs/editor/contrib/hover/hover.ts b/src/vs/editor/contrib/hover/hover.ts index 2653086426b..93192a81278 100644 --- a/src/vs/editor/contrib/hover/hover.ts +++ b/src/vs/editor/contrib/hover/hover.ts @@ -26,6 +26,9 @@ import { IOpenerService } from 'vs/platform/opener/common/opener'; import { editorHoverBackground, editorHoverBorder, editorHoverHighlight, textCodeBlockBackground, textLinkForeground } from 'vs/platform/theme/common/colorRegistry'; import { IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService'; import { IMarkerDecorationsService } from 'vs/editor/common/services/markersDecorationService'; +import { IBulkEditService } from 'vs/editor/browser/services/bulkEditService'; +import { ICommandService } from 'vs/platform/commands/common/commands'; +import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; export class ModesHoverController implements IEditorContribution { @@ -62,6 +65,9 @@ export class ModesHoverController implements IEditorContribution { constructor(private readonly _editor: ICodeEditor, @IOpenerService private readonly _openerService: IOpenerService, + @IContextMenuService private readonly _contextMenuService: IContextMenuService, + @IBulkEditService private readonly _bulkEditService: IBulkEditService, + @ICommandService private readonly _commandService: ICommandService, @IModeService private readonly _modeService: IModeService, @IMarkerDecorationsService private readonly _markerDecorationsService: IMarkerDecorationsService, @IThemeService private readonly _themeService: IThemeService @@ -207,7 +213,7 @@ export class ModesHoverController implements IEditorContribution { private _createHoverWidget() { const renderer = new MarkdownRenderer(this._editor, this._modeService, this._openerService); - this._contentWidget = new ModesContentHoverWidget(this._editor, renderer, this._markerDecorationsService, this._themeService, this._openerService); + this._contentWidget = new ModesContentHoverWidget(this._editor, renderer, this._markerDecorationsService, this._themeService, this._contextMenuService, this._bulkEditService, this._commandService, this._openerService); this._glyphWidget = new ModesGlyphHoverWidget(this._editor, renderer); } diff --git a/src/vs/editor/contrib/hover/hoverWidgets.ts b/src/vs/editor/contrib/hover/hoverWidgets.ts index 3b0e5a3961c..0815100b90c 100644 --- a/src/vs/editor/contrib/hover/hoverWidgets.ts +++ b/src/vs/editor/contrib/hover/hoverWidgets.ts @@ -68,9 +68,9 @@ export class ContentHoverWidget extends Widget implements editorBrowser.IContent } })); - this._editor.onDidLayoutChange(e => this.updateMaxHeight()); + this._editor.onDidLayoutChange(e => this.layout()); - this.updateMaxHeight(); + this.layout(); this._editor.addContentWidget(this); this._showAtPosition = null; this._showAtRange = null; @@ -151,13 +151,14 @@ export class ContentHoverWidget extends Widget implements editorBrowser.IContent this.scrollbar.scanDomNode(); } - private updateMaxHeight(): void { + private layout(): void { const height = Math.max(this._editor.getLayoutInfo().height / 4, 250); const { fontSize, lineHeight } = this._editor.getConfiguration().fontInfo; this._domNode.style.fontSize = `${fontSize}px`; this._domNode.style.lineHeight = `${lineHeight}px`; this._domNode.style.maxHeight = `${height}px`; + this._domNode.style.maxWidth = `${Math.max(this._editor.getLayoutInfo().width - 50, 500)}px`; } } diff --git a/src/vs/editor/contrib/hover/modesContentHover.ts b/src/vs/editor/contrib/hover/modesContentHover.ts index a83521453ea..11e8475733f 100644 --- a/src/vs/editor/contrib/hover/modesContentHover.ts +++ b/src/vs/editor/contrib/hover/modesContentHover.ts @@ -8,7 +8,7 @@ import * as dom from 'vs/base/browser/dom'; import { CancellationToken } from 'vs/base/common/cancellation'; import { Color, RGBA } from 'vs/base/common/color'; import { IMarkdownString, MarkdownString, isEmptyMarkdownString, markedStringsEquals } from 'vs/base/common/htmlContent'; -import { Disposable, IDisposable, combinedDisposable } from 'vs/base/common/lifecycle'; +import { Disposable, IDisposable, combinedDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { Position } from 'vs/editor/common/core/position'; import { IRange, Range } from 'vs/editor/common/core/range'; @@ -29,6 +29,15 @@ import { basename } from 'vs/base/common/resources'; import { IMarkerDecorationsService } from 'vs/editor/common/services/markersDecorationService'; import { onUnexpectedError } from 'vs/base/common/errors'; import { IOpenerService, NullOpenerService } from 'vs/platform/opener/common/opener'; +import { MarkerController } from 'vs/editor/contrib/gotoError/gotoError'; +import { getCodeActions } from 'vs/editor/contrib/codeAction/codeAction'; +import { CodeActionKind } from 'vs/editor/contrib/codeAction/codeActionTrigger'; +import { Action } from 'vs/base/common/actions'; +import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; +import { applyCodeAction } from 'vs/editor/contrib/codeAction/codeActionCommands'; +import { ICommandService } from 'vs/platform/commands/common/commands'; +import { IBulkEditService } from 'vs/editor/browser/services/bulkEditService'; +import { CancelablePromise, createCancelablePromise } from 'vs/base/common/async'; const $ = dom.$; @@ -206,6 +215,9 @@ export class ModesContentHoverWidget extends ContentHoverWidget { markdownRenderer: MarkdownRenderer, markerDecorationsService: IMarkerDecorationsService, private readonly _themeService: IThemeService, + private readonly _contextMenuService: IContextMenuService, + private readonly _bulkEditService: IBulkEditService, + private readonly _commandService: ICommandService, private readonly _openerService: IOpenerService | null = NullOpenerService, ) { super(ModesContentHoverWidget.ID, editor); @@ -468,26 +480,26 @@ export class ModesContentHoverWidget extends ContentHoverWidget { } private renderMarkerHover(markerHover: MarkerHover): HTMLElement { - const hoverElement = $('div'); + const hoverElement = $('div.marker-hover'); + const markerElement = dom.append(hoverElement, $('div.marker')); const { source, message, code, relatedInformation } = markerHover.marker; - const messageElement = dom.append(hoverElement, $('span')); + const messageElement = dom.append(markerElement, $('span')); messageElement.style.whiteSpace = 'pre-wrap'; messageElement.innerText = message; - this._editor.applyFontInfo(messageElement); if (source || code) { - const detailsElement = dom.append(hoverElement, $('span')); + const detailsElement = dom.append(markerElement, $('span')); detailsElement.style.opacity = '0.6'; detailsElement.style.paddingLeft = '6px'; detailsElement.innerText = source && code ? `${source}(${code})` : `(${code})`; } if (isNonEmptyArray(relatedInformation)) { - const listElement = dom.append(hoverElement, $('ul')); for (const { message, resource, startLineNumber, startColumn } of relatedInformation) { - const item = dom.append(listElement, $('li')); - const a = dom.append(item, $('a')); + const relatedInfoContainer = dom.append(markerElement, $('div')); + relatedInfoContainer.style.marginTop = '8px'; + const a = dom.append(relatedInfoContainer, $('a')); a.innerText = `${basename(resource)}(${startLineNumber}, ${startColumn})`; a.style.cursor = 'pointer'; a.onclick = e => { @@ -497,13 +509,56 @@ export class ModesContentHoverWidget extends ContentHoverWidget { this._openerService.open(resource.with({ fragment: `${startLineNumber},${startColumn}` })).catch(onUnexpectedError); } }; - const messageElement = dom.append(item, $('span')); + const messageElement = dom.append(relatedInfoContainer, $('span')); messageElement.innerText = `: ${message}`; } } + + const actionsElement = dom.append(hoverElement, $('div.actions')); + const showCodeActions = dom.append(actionsElement, $('a.action.icon.light-bulb', { title: nls.localize('code actions', "Show Fixes...") })); + const disposables: IDisposable[] = []; + disposables.push(dom.addDisposableListener(showCodeActions, dom.EventType.CLICK, async e => { + e.stopPropagation(); + e.preventDefault(); + const codeActionsPromise = this.getCodeActions(markerHover.marker); + disposables.push(toDisposable(() => codeActionsPromise.cancel())); + const actions = await codeActionsPromise; + const elementPosition = dom.getDomNodePagePosition(showCodeActions); + this._contextMenuService.showContextMenu({ + getAnchor: () => ({ x: elementPosition.left + 6, y: elementPosition.top + elementPosition.height + 6 }), + getActions: () => actions + }); + })); + const peekMarkerAction = dom.append(actionsElement, $('a.action.icon.peek-marker', { title: nls.localize('go to problem', "Go to Problem") })); + peekMarkerAction.textContent = '↪'; + disposables.push(dom.addDisposableListener(peekMarkerAction, dom.EventType.CLICK, e => { + e.stopPropagation(); + e.preventDefault(); + this.hide(); + MarkerController.get(this._editor).show(markerHover.marker); + this._editor.focus(); + })); + this.renderDisposable = combinedDisposable(disposables); return hoverElement; } + private getCodeActions(marker: IMarker): CancelablePromise { + return createCancelablePromise(async cancellationToken => { + const codeActions = await getCodeActions(this._editor.getModel()!, new Range(marker.startLineNumber, marker.startColumn, marker.endLineNumber, marker.endColumn), { type: 'manual', filter: { kind: CodeActionKind.QuickFix } }, cancellationToken); + if (codeActions.length) { + return codeActions.map(codeAction => new Action( + codeAction.command ? codeAction.command.id : codeAction.title, + codeAction.title, + undefined, + true, + () => applyCodeAction(codeAction, this._bulkEditService, this._commandService))); + } + return [ + new Action('', nls.localize('editor.action.quickFix.noneMessage', "No code actions available")) + ]; + }); + } + private static readonly _DECORATION_OPTIONS = ModelDecorationOptions.register({ className: 'hoverHighlight' }); diff --git a/src/vs/editor/contrib/indentation/indentation.ts b/src/vs/editor/contrib/indentation/indentation.ts index f436a795573..826f53ed2d4 100644 --- a/src/vs/editor/contrib/indentation/indentation.ts +++ b/src/vs/editor/contrib/indentation/indentation.ts @@ -23,28 +23,6 @@ import { IModelService } from 'vs/editor/common/services/modelService'; import * as indentUtils from 'vs/editor/contrib/indentation/indentUtils'; import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; -export function shiftIndent(tabSize: number, indentation: string, count?: number): string { - count = count || 1; - let desiredIndentCount = ShiftCommand.shiftIndentCount(indentation, indentation.length + count, tabSize); - let newIndentation = ''; - for (let i = 0; i < desiredIndentCount; i++) { - newIndentation += '\t'; - } - - return newIndentation; -} - -export function unshiftIndent(tabSize: number, indentation: string, count?: number): string { - count = count || 1; - let desiredIndentCount = ShiftCommand.unshiftIndentCount(indentation, indentation.length + count, tabSize); - let newIndentation = ''; - for (let i = 0; i < desiredIndentCount; i++) { - newIndentation += '\t'; - } - - return newIndentation; -} - export function getReindentEditOperations(model: ITextModel, startLineNumber: number, endLineNumber: number, inheritedIndent?: string): IIdentifiedSingleEditOperation[] { if (model.getLineCount() === 1 && model.getLineMaxColumn(1) === 1) { // Model is empty @@ -76,7 +54,15 @@ export function getReindentEditOperations(model: ITextModel, startLineNumber: nu return []; } - let { tabSize, insertSpaces } = model.getOptions(); + const { tabSize, indentSize, insertSpaces } = model.getOptions(); + const shiftIndent = (indentation: string, count?: number) => { + count = count || 1; + return ShiftCommand.shiftIndent(indentation, indentation.length + count, tabSize, indentSize, insertSpaces); + }; + const unshiftIndent = (indentation: string, count?: number) => { + count = count || 1; + return ShiftCommand.unshiftIndent(indentation, indentation.length + count, tabSize, indentSize, insertSpaces); + }; let indentEdits: IIdentifiedSingleEditOperation[] = []; // indentation being passed to lines below @@ -92,12 +78,12 @@ export function getReindentEditOperations(model: ITextModel, startLineNumber: nu adjustedLineContent = globalIndent + currentLineText.substring(oldIndentation.length); if (indentationRules.decreaseIndentPattern && indentationRules.decreaseIndentPattern.test(adjustedLineContent)) { - globalIndent = unshiftIndent(tabSize, globalIndent); + globalIndent = unshiftIndent(globalIndent); adjustedLineContent = globalIndent + currentLineText.substring(oldIndentation.length); } if (currentLineText !== adjustedLineContent) { - indentEdits.push(EditOperation.replace(new Selection(startLineNumber, 1, startLineNumber, oldIndentation.length + 1), TextModel.normalizeIndentation(globalIndent, tabSize, insertSpaces))); + indentEdits.push(EditOperation.replace(new Selection(startLineNumber, 1, startLineNumber, oldIndentation.length + 1), TextModel.normalizeIndentation(globalIndent, indentSize, insertSpaces))); } } else { globalIndent = strings.getLeadingWhitespace(currentLineText); @@ -107,11 +93,11 @@ export function getReindentEditOperations(model: ITextModel, startLineNumber: nu let idealIndentForNextLine: string = globalIndent; if (indentationRules.increaseIndentPattern && indentationRules.increaseIndentPattern.test(adjustedLineContent)) { - idealIndentForNextLine = shiftIndent(tabSize, idealIndentForNextLine); - globalIndent = shiftIndent(tabSize, globalIndent); + idealIndentForNextLine = shiftIndent(idealIndentForNextLine); + globalIndent = shiftIndent(globalIndent); } else if (indentationRules.indentNextLinePattern && indentationRules.indentNextLinePattern.test(adjustedLineContent)) { - idealIndentForNextLine = shiftIndent(tabSize, idealIndentForNextLine); + idealIndentForNextLine = shiftIndent(idealIndentForNextLine); } startLineNumber++; @@ -123,12 +109,12 @@ export function getReindentEditOperations(model: ITextModel, startLineNumber: nu let adjustedLineContent = idealIndentForNextLine + text.substring(oldIndentation.length); if (indentationRules.decreaseIndentPattern && indentationRules.decreaseIndentPattern.test(adjustedLineContent)) { - idealIndentForNextLine = unshiftIndent(tabSize, idealIndentForNextLine); - globalIndent = unshiftIndent(tabSize, globalIndent); + idealIndentForNextLine = unshiftIndent(idealIndentForNextLine); + globalIndent = unshiftIndent(globalIndent); } if (oldIndentation !== idealIndentForNextLine) { - indentEdits.push(EditOperation.replace(new Selection(lineNumber, 1, lineNumber, oldIndentation.length + 1), TextModel.normalizeIndentation(idealIndentForNextLine, tabSize, insertSpaces))); + indentEdits.push(EditOperation.replace(new Selection(lineNumber, 1, lineNumber, oldIndentation.length + 1), TextModel.normalizeIndentation(idealIndentForNextLine, indentSize, insertSpaces))); } // calculate idealIndentForNextLine @@ -137,10 +123,10 @@ export function getReindentEditOperations(model: ITextModel, startLineNumber: nu // but don't change globalIndent and idealIndentForNextLine. continue; } else if (indentationRules.increaseIndentPattern && indentationRules.increaseIndentPattern.test(adjustedLineContent)) { - globalIndent = shiftIndent(tabSize, globalIndent); + globalIndent = shiftIndent(globalIndent); idealIndentForNextLine = globalIndent; } else if (indentationRules.indentNextLinePattern && indentationRules.indentNextLinePattern.test(adjustedLineContent)) { - idealIndentForNextLine = shiftIndent(tabSize, idealIndentForNextLine); + idealIndentForNextLine = shiftIndent(idealIndentForNextLine); } else { idealIndentForNextLine = globalIndent; } @@ -484,28 +470,16 @@ export class AutoIndentOnPaste implements IEditorContribution { if (!model.isCheapToTokenize(range.getStartPosition().lineNumber)) { return; } - const { tabSize, insertSpaces } = model.getOptions(); + const { tabSize, indentSize, insertSpaces } = model.getOptions(); this.editor.pushUndoStop(); let textEdits: TextEdit[] = []; let indentConverter = { shiftIndent: (indentation: string) => { - let desiredIndentCount = ShiftCommand.shiftIndentCount(indentation, indentation.length + 1, tabSize); - let newIndentation = ''; - for (let i = 0; i < desiredIndentCount; i++) { - newIndentation += '\t'; - } - - return newIndentation; + return ShiftCommand.shiftIndent(indentation, indentation.length + 1, tabSize, indentSize, insertSpaces); }, unshiftIndent: (indentation: string) => { - let desiredIndentCount = ShiftCommand.unshiftIndentCount(indentation, indentation.length + 1, tabSize); - let newIndentation = ''; - for (let i = 0; i < desiredIndentCount; i++) { - newIndentation += '\t'; - } - - return newIndentation; + return ShiftCommand.unshiftIndent(indentation, indentation.length + 1, tabSize, indentSize, insertSpaces); } }; diff --git a/src/vs/editor/contrib/linesOperations/deleteLinesCommand.ts b/src/vs/editor/contrib/linesOperations/deleteLinesCommand.ts deleted file mode 100644 index 20fcf814318..00000000000 --- a/src/vs/editor/contrib/linesOperations/deleteLinesCommand.ts +++ /dev/null @@ -1,55 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { Range } from 'vs/editor/common/core/range'; -import { Selection } from 'vs/editor/common/core/selection'; -import { ICommand, ICursorStateComputerData, IEditOperationBuilder } from 'vs/editor/common/editorCommon'; -import { ITextModel } from 'vs/editor/common/model'; - -export class DeleteLinesCommand implements ICommand { - - private startLineNumber: number; - private endLineNumber: number; - private restoreCursorToColumn: number; - - constructor(startLineNumber: number, endLineNumber: number, restoreCursorToColumn: number) { - this.startLineNumber = startLineNumber; - this.endLineNumber = endLineNumber; - this.restoreCursorToColumn = restoreCursorToColumn; - } - - public getEditOperations(model: ITextModel, builder: IEditOperationBuilder): void { - if (model.getLineCount() === 1 && model.getLineMaxColumn(1) === 1) { - // Model is empty - return; - } - - let startLineNumber = this.startLineNumber; - let endLineNumber = this.endLineNumber; - - let startColumn = 1; - let endColumn = model.getLineMaxColumn(endLineNumber); - if (endLineNumber < model.getLineCount()) { - endLineNumber += 1; - endColumn = 1; - } else if (startLineNumber > 1) { - startLineNumber -= 1; - startColumn = model.getLineMaxColumn(startLineNumber); - } - - builder.addTrackedEditOperation(new Range(startLineNumber, startColumn, endLineNumber, endColumn), null); - } - - public computeCursorState(model: ITextModel, helper: ICursorStateComputerData): Selection { - let inverseEditOperations = helper.getInverseEditOperations(); - let srcRange = inverseEditOperations[0].range; - return new Selection( - srcRange.endLineNumber, - this.restoreCursorToColumn, - srcRange.endLineNumber, - this.restoreCursorToColumn - ); - } -} diff --git a/src/vs/editor/contrib/linesOperations/linesOperations.ts b/src/vs/editor/contrib/linesOperations/linesOperations.ts index 3f254415f7b..d1559b13f0c 100644 --- a/src/vs/editor/contrib/linesOperations/linesOperations.ts +++ b/src/vs/editor/contrib/linesOperations/linesOperations.ts @@ -6,7 +6,7 @@ import * as nls from 'vs/nls'; import { KeyChord, KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import { CoreEditingCommands } from 'vs/editor/browser/controller/coreCommands'; -import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; +import { ICodeEditor, IActiveCodeEditor } from 'vs/editor/browser/editorBrowser'; import { EditorAction, IActionOptions, ServicesAccessor, registerEditorAction } from 'vs/editor/browser/editorExtensions'; import { ReplaceCommand, ReplaceCommandThatPreservesSelection } from 'vs/editor/common/commands/replaceCommand'; import { TrimTrailingWhitespaceCommand } from 'vs/editor/common/commands/trimTrailingWhitespaceCommand'; @@ -17,9 +17,8 @@ import { Range } from 'vs/editor/common/core/range'; import { Selection } from 'vs/editor/common/core/selection'; import { ICommand } from 'vs/editor/common/editorCommon'; import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; -import { IIdentifiedSingleEditOperation } from 'vs/editor/common/model'; +import { IIdentifiedSingleEditOperation, ITextModel } from 'vs/editor/common/model'; import { CopyLinesCommand } from 'vs/editor/contrib/linesOperations/copyLinesCommand'; -import { DeleteLinesCommand } from 'vs/editor/contrib/linesOperations/deleteLinesCommand'; import { MoveLinesCommand } from 'vs/editor/contrib/linesOperations/moveLinesCommand'; import { SortLinesCommand } from 'vs/editor/contrib/linesOperations/sortLinesCommand'; import { MenuId } from 'vs/platform/actions/common/actions'; @@ -265,6 +264,7 @@ export class TrimTrailingWhitespaceAction extends EditorAction { interface IDeleteLinesOperation { startLineNumber: number; + selectionStartColumn: number; endLineNumber: number; positionColumn: number; } @@ -286,26 +286,50 @@ export class DeleteLinesAction extends EditorAction { } public run(_accessor: ServicesAccessor, editor: ICodeEditor): void { + if (!editor.hasModel()) { + return; + } let ops = this._getLinesToRemove(editor); - // Finally, construct the delete lines commands - let commands: ICommand[] = ops.map((op) => { - return new DeleteLinesCommand(op.startLineNumber, op.endLineNumber, op.positionColumn); - }); + let model: ITextModel = editor.getModel(); + if (model.getLineCount() === 1 && model.getLineMaxColumn(1) === 1) { + // Model is empty + return; + } + + let linesDeleted = 0; + let edits: IIdentifiedSingleEditOperation[] = []; + let cursorState: Selection[] = []; + for (let i = 0, len = ops.length; i < len; i++) { + const op = ops[i]; + + let startLineNumber = op.startLineNumber; + let endLineNumber = op.endLineNumber; + + let startColumn = 1; + let endColumn = model.getLineMaxColumn(endLineNumber); + if (endLineNumber < model.getLineCount()) { + endLineNumber += 1; + endColumn = 1; + } else if (startLineNumber > 1) { + startLineNumber -= 1; + startColumn = model.getLineMaxColumn(startLineNumber); + } + + edits.push(EditOperation.replace(new Selection(startLineNumber, startColumn, endLineNumber, endColumn), '')); + cursorState.push(new Selection(startLineNumber - linesDeleted, op.positionColumn, startLineNumber - linesDeleted, op.positionColumn)); + linesDeleted += (op.endLineNumber - op.startLineNumber + 1); + } editor.pushUndoStop(); - editor.executeCommands(this.id, commands); + editor.executeEdits(this.id, edits, cursorState); editor.pushUndoStop(); } - private _getLinesToRemove(editor: ICodeEditor): IDeleteLinesOperation[] { + private _getLinesToRemove(editor: IActiveCodeEditor): IDeleteLinesOperation[] { // Construct delete operations - let selections = editor.getSelections(); - if (selections === null) { - return []; - } - let operations: IDeleteLinesOperation[] = selections.map((s) => { + let operations: IDeleteLinesOperation[] = editor.getSelections().map((s) => { let endLineNumber = s.endLineNumber; if (s.startLineNumber < s.endLineNumber && s.endColumn === 1) { @@ -314,6 +338,7 @@ export class DeleteLinesAction extends EditorAction { return { startLineNumber: s.startLineNumber, + selectionStartColumn: s.selectionStartColumn, endLineNumber: endLineNumber, positionColumn: s.positionColumn }; @@ -445,10 +470,10 @@ export class InsertLineAfterAction extends EditorAction { export abstract class AbstractDeleteAllToBoundaryAction extends EditorAction { public run(_accessor: ServicesAccessor, editor: ICodeEditor): void { - const primaryCursor = editor.getSelection(); - if (primaryCursor === null) { + if (!editor.hasModel()) { return; } + const primaryCursor = editor.getSelection(); let rangesToDelete = this._getRangesToDelete(editor); // merge overlapping selections @@ -483,7 +508,7 @@ export abstract class AbstractDeleteAllToBoundaryAction extends EditorAction { */ protected abstract _getEndCursorState(primaryCursor: Range, rangesToDelete: Range[]): Selection[]; - protected abstract _getRangesToDelete(editor: ICodeEditor): Range[]; + protected abstract _getRangesToDelete(editor: IActiveCodeEditor): Range[]; } export class DeleteAllLeftAction extends AbstractDeleteAllToBoundaryAction { @@ -532,7 +557,7 @@ export class DeleteAllLeftAction extends AbstractDeleteAllToBoundaryAction { return endCursorState; } - _getRangesToDelete(editor: ICodeEditor): Range[] { + _getRangesToDelete(editor: IActiveCodeEditor): Range[] { let selections = editor.getSelections(); if (selections === null) { return []; @@ -550,7 +575,7 @@ export class DeleteAllLeftAction extends AbstractDeleteAllToBoundaryAction { if (selection.isEmpty()) { if (selection.startColumn === 1) { let deleteFromLine = Math.max(1, selection.startLineNumber - 1); - let deleteFromColumn = selection.startLineNumber === 1 ? 1 : model!.getLineContent(deleteFromLine).length + 1; + let deleteFromColumn = selection.startLineNumber === 1 ? 1 : model.getLineContent(deleteFromLine).length + 1; return new Range(deleteFromLine, deleteFromColumn, selection.startLineNumber, 1); } else { return new Range(selection.startLineNumber, 1, selection.startLineNumber, selection.startColumn); @@ -601,7 +626,7 @@ export class DeleteAllRightAction extends AbstractDeleteAllToBoundaryAction { return endCursorState; } - _getRangesToDelete(editor: ICodeEditor): Range[] { + _getRangesToDelete(editor: IActiveCodeEditor): Range[] { let model = editor.getModel(); if (model === null) { return []; @@ -615,7 +640,7 @@ export class DeleteAllRightAction extends AbstractDeleteAllToBoundaryAction { let rangesToDelete: Range[] = selections.map((sel) => { if (sel.isEmpty()) { - const maxColumn = model!.getLineMaxColumn(sel.startLineNumber); + const maxColumn = model.getLineMaxColumn(sel.startLineNumber); if (sel.startColumn === maxColumn) { return new Range(sel.startLineNumber, sel.startColumn, sel.startLineNumber + 1, 1); diff --git a/src/vs/editor/contrib/linesOperations/moveLinesCommand.ts b/src/vs/editor/contrib/linesOperations/moveLinesCommand.ts index ae17e2f5426..d237c21621b 100644 --- a/src/vs/editor/contrib/linesOperations/moveLinesCommand.ts +++ b/src/vs/editor/contrib/linesOperations/moveLinesCommand.ts @@ -50,9 +50,8 @@ export class MoveLinesCommand implements ICommand { s = s.setEndPosition(s.endLineNumber - 1, model.getLineMaxColumn(s.endLineNumber - 1)); } - let tabSize = model.getOptions().tabSize; - let insertSpaces = model.getOptions().insertSpaces; - let indentConverter = this.buildIndentConverter(tabSize); + const { tabSize, indentSize, insertSpaces } = model.getOptions(); + let indentConverter = this.buildIndentConverter(tabSize, indentSize, insertSpaces); let virtualModel = { getLineTokens: (lineNumber: number) => { return model.getLineTokens(lineNumber); @@ -215,25 +214,13 @@ export class MoveLinesCommand implements ICommand { this._selectionId = builder.trackSelection(s); } - private buildIndentConverter(tabSize: number): IIndentConverter { + private buildIndentConverter(tabSize: number, indentSize: number, insertSpaces: boolean): IIndentConverter { return { shiftIndent: (indentation) => { - let desiredIndentCount = ShiftCommand.shiftIndentCount(indentation, indentation.length + 1, tabSize); - let newIndentation = ''; - for (let i = 0; i < desiredIndentCount; i++) { - newIndentation += '\t'; - } - - return newIndentation; + return ShiftCommand.shiftIndent(indentation, indentation.length + 1, tabSize, indentSize, insertSpaces); }, unshiftIndent: (indentation) => { - let desiredIndentCount = ShiftCommand.unshiftIndentCount(indentation, indentation.length + 1, tabSize); - let newIndentation = ''; - for (let i = 0; i < desiredIndentCount; i++) { - newIndentation += '\t'; - } - - return newIndentation; + return ShiftCommand.unshiftIndent(indentation, indentation.length + 1, tabSize, indentSize, insertSpaces); } }; } diff --git a/src/vs/editor/contrib/linesOperations/test/deleteLinesCommand.test.ts b/src/vs/editor/contrib/linesOperations/test/deleteLinesCommand.test.ts deleted file mode 100644 index 3a9cde68a55..00000000000 --- a/src/vs/editor/contrib/linesOperations/test/deleteLinesCommand.test.ts +++ /dev/null @@ -1,199 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -import { Selection } from 'vs/editor/common/core/selection'; -import { DeleteLinesCommand } from 'vs/editor/contrib/linesOperations/deleteLinesCommand'; -import { testCommand } from 'vs/editor/test/browser/testCommand'; - -function createFromSelection(selection: Selection): DeleteLinesCommand { - let endLineNumber = selection.endLineNumber; - if (selection.startLineNumber < selection.endLineNumber && selection.endColumn === 1) { - endLineNumber -= 1; - } - return new DeleteLinesCommand(selection.startLineNumber, endLineNumber, selection.positionColumn); -} - -function testDeleteLinesCommand(lines: string[], selection: Selection, expectedLines: string[], expectedSelection: Selection): void { - testCommand(lines, null, selection, (sel) => createFromSelection(sel), expectedLines, expectedSelection); -} - -suite('Editor Contrib - Delete Lines Command', () => { - - test('empty selection in middle of lines', function () { - testDeleteLinesCommand( - [ - 'first', - 'second line', - 'third line', - 'fourth line', - 'fifth' - ], - new Selection(2, 3, 2, 3), - [ - 'first', - 'third line', - 'fourth line', - 'fifth' - ], - new Selection(2, 3, 2, 3) - ); - }); - - test('empty selection at top of lines', function () { - testDeleteLinesCommand( - [ - 'first', - 'second line', - 'third line', - 'fourth line', - 'fifth' - ], - new Selection(1, 5, 1, 5), - [ - 'second line', - 'third line', - 'fourth line', - 'fifth' - ], - new Selection(1, 5, 1, 5) - ); - }); - - test('empty selection at end of lines', function () { - testDeleteLinesCommand( - [ - 'first', - 'second line', - 'third line', - 'fourth line', - 'fifth' - ], - new Selection(5, 2, 5, 2), - [ - 'first', - 'second line', - 'third line', - 'fourth line' - ], - new Selection(4, 2, 4, 2) - ); - }); - - test('with selection in middle of lines', function () { - testDeleteLinesCommand( - [ - 'first', - 'second line', - 'third line', - 'fourth line', - 'fifth' - ], - new Selection(3, 3, 2, 2), - [ - 'first', - 'fourth line', - 'fifth' - ], - new Selection(2, 2, 2, 2) - ); - }); - - test('with selection at top of lines', function () { - testDeleteLinesCommand( - [ - 'first', - 'second line', - 'third line', - 'fourth line', - 'fifth' - ], - new Selection(1, 4, 1, 5), - [ - 'second line', - 'third line', - 'fourth line', - 'fifth' - ], - new Selection(1, 5, 1, 5) - ); - }); - - test('with selection at end of lines', function () { - testDeleteLinesCommand( - [ - 'first', - 'second line', - 'third line', - 'fourth line', - 'fifth' - ], - new Selection(5, 1, 5, 2), - [ - 'first', - 'second line', - 'third line', - 'fourth line' - ], - new Selection(4, 2, 4, 2) - ); - }); - - test('with full line selection in middle of lines', function () { - testDeleteLinesCommand( - [ - 'first', - 'second line', - 'third line', - 'fourth line', - 'fifth' - ], - new Selection(4, 1, 2, 1), - [ - 'first', - 'fourth line', - 'fifth' - ], - new Selection(2, 1, 2, 1) - ); - }); - - test('with full line selection at top of lines', function () { - testDeleteLinesCommand( - [ - 'first', - 'second line', - 'third line', - 'fourth line', - 'fifth' - ], - new Selection(2, 1, 1, 5), - [ - 'second line', - 'third line', - 'fourth line', - 'fifth' - ], - new Selection(1, 5, 1, 5) - ); - }); - - test('with full line selection at end of lines', function () { - testDeleteLinesCommand( - [ - 'first', - 'second line', - 'third line', - 'fourth line', - 'fifth' - ], - new Selection(4, 1, 5, 2), - [ - 'first', - 'second line', - 'third line' - ], - new Selection(3, 2, 3, 2) - ); - }); -}); diff --git a/src/vs/editor/contrib/linesOperations/test/linesOperations.test.ts b/src/vs/editor/contrib/linesOperations/test/linesOperations.test.ts index 569d2dfdd55..69dca1d5bbc 100644 --- a/src/vs/editor/contrib/linesOperations/test/linesOperations.test.ts +++ b/src/vs/editor/contrib/linesOperations/test/linesOperations.test.ts @@ -899,4 +899,250 @@ suite('Editor Contrib - Line Operations', () => { assert.equal(editor.getValue(), 'a\nc'); }); }); + + function testDeleteLinesCommand(initialText: string[], _initialSelections: Selection | Selection[], resultingText: string[], _resultingSelections: Selection | Selection[]): void { + const initialSelections = Array.isArray(_initialSelections) ? _initialSelections : [_initialSelections]; + const resultingSelections = Array.isArray(_resultingSelections) ? _resultingSelections : [_resultingSelections]; + withTestCodeEditor(initialText, {}, (editor) => { + editor.setSelections(initialSelections); + const deleteLinesAction = new DeleteLinesAction(); + deleteLinesAction.run(null!, editor); + + assert.equal(editor.getValue(), resultingText.join('\n')); + assert.deepEqual(editor.getSelections(), resultingSelections); + }); + } + + test('empty selection in middle of lines', function () { + testDeleteLinesCommand( + [ + 'first', + 'second line', + 'third line', + 'fourth line', + 'fifth' + ], + new Selection(2, 3, 2, 3), + [ + 'first', + 'third line', + 'fourth line', + 'fifth' + ], + new Selection(2, 3, 2, 3) + ); + }); + + test('empty selection at top of lines', function () { + testDeleteLinesCommand( + [ + 'first', + 'second line', + 'third line', + 'fourth line', + 'fifth' + ], + new Selection(1, 5, 1, 5), + [ + 'second line', + 'third line', + 'fourth line', + 'fifth' + ], + new Selection(1, 5, 1, 5) + ); + }); + + test('empty selection at end of lines', function () { + testDeleteLinesCommand( + [ + 'first', + 'second line', + 'third line', + 'fourth line', + 'fifth' + ], + new Selection(5, 2, 5, 2), + [ + 'first', + 'second line', + 'third line', + 'fourth line' + ], + new Selection(4, 2, 4, 2) + ); + }); + + test('with selection in middle of lines', function () { + testDeleteLinesCommand( + [ + 'first', + 'second line', + 'third line', + 'fourth line', + 'fifth' + ], + new Selection(3, 3, 2, 2), + [ + 'first', + 'fourth line', + 'fifth' + ], + new Selection(2, 2, 2, 2) + ); + }); + + test('with selection at top of lines', function () { + testDeleteLinesCommand( + [ + 'first', + 'second line', + 'third line', + 'fourth line', + 'fifth' + ], + new Selection(1, 4, 1, 5), + [ + 'second line', + 'third line', + 'fourth line', + 'fifth' + ], + new Selection(1, 5, 1, 5) + ); + }); + + test('with selection at end of lines', function () { + testDeleteLinesCommand( + [ + 'first', + 'second line', + 'third line', + 'fourth line', + 'fifth' + ], + new Selection(5, 1, 5, 2), + [ + 'first', + 'second line', + 'third line', + 'fourth line' + ], + new Selection(4, 2, 4, 2) + ); + }); + + test('with full line selection in middle of lines', function () { + testDeleteLinesCommand( + [ + 'first', + 'second line', + 'third line', + 'fourth line', + 'fifth' + ], + new Selection(4, 1, 2, 1), + [ + 'first', + 'fourth line', + 'fifth' + ], + new Selection(2, 1, 2, 1) + ); + }); + + test('with full line selection at top of lines', function () { + testDeleteLinesCommand( + [ + 'first', + 'second line', + 'third line', + 'fourth line', + 'fifth' + ], + new Selection(2, 1, 1, 5), + [ + 'second line', + 'third line', + 'fourth line', + 'fifth' + ], + new Selection(1, 5, 1, 5) + ); + }); + + test('with full line selection at end of lines', function () { + testDeleteLinesCommand( + [ + 'first', + 'second line', + 'third line', + 'fourth line', + 'fifth' + ], + new Selection(4, 1, 5, 2), + [ + 'first', + 'second line', + 'third line' + ], + new Selection(3, 2, 3, 2) + ); + }); + + test('multicursor 1', function () { + testDeleteLinesCommand( + [ + 'class P {', + '', + ' getA() {', + ' if (true) {', + ' return "a";', + ' }', + ' }', + '', + ' getB() {', + ' if (true) {', + ' return "b";', + ' }', + ' }', + '', + ' getC() {', + ' if (true) {', + ' return "c";', + ' }', + ' }', + '}', + ], + [ + new Selection(4, 1, 5, 1), + new Selection(10, 1, 11, 1), + new Selection(16, 1, 17, 1), + ], + [ + 'class P {', + '', + ' getA() {', + ' return "a";', + ' }', + ' }', + '', + ' getB() {', + ' return "b";', + ' }', + ' }', + '', + ' getC() {', + ' return "c";', + ' }', + ' }', + '}', + ], + [ + new Selection(4, 1, 4, 1), + new Selection(9, 1, 9, 1), + new Selection(14, 1, 14, 1), + ] + ); + }); }); diff --git a/src/vs/editor/standalone/browser/simpleServices.ts b/src/vs/editor/standalone/browser/simpleServices.ts index 476a17010df..7aba5deca47 100644 --- a/src/vs/editor/standalone/browser/simpleServices.ts +++ b/src/vs/editor/standalone/browser/simpleServices.ts @@ -409,7 +409,7 @@ export class SimpleConfigurationService implements IConfigurationService { getValue(arg1?: any, arg2?: any): any { const section = typeof arg1 === 'string' ? arg1 : undefined; const overrides = isConfigurationOverrides(arg1) ? arg1 : isConfigurationOverrides(arg2) ? arg2 : {}; - return this.configuration().getValue(section, overrides, null); + return this.configuration().getValue(section, overrides, undefined); } public updateValue(key: string, value: any, arg3?: any, arg4?: any): Promise { @@ -424,11 +424,11 @@ export class SimpleConfigurationService implements IConfigurationService { workspaceFolder?: C value: C, } { - return this.configuration().inspect(key, options, null); + return this.configuration().inspect(key, options, undefined); } public keys() { - return this.configuration().keys(null); + return this.configuration().keys(undefined); } public reloadConfiguration(): Promise { diff --git a/src/vs/editor/test/browser/commands/shiftCommand.test.ts b/src/vs/editor/test/browser/commands/shiftCommand.test.ts index 4c187481918..a2fb70ae69b 100644 --- a/src/vs/editor/test/browser/commands/shiftCommand.test.ts +++ b/src/vs/editor/test/browser/commands/shiftCommand.test.ts @@ -47,7 +47,8 @@ function testShiftCommand(lines: string[], languageIdentifier: LanguageIdentifie testCommand(lines, languageIdentifier, selection, (sel) => new ShiftCommand(sel, { isUnshift: false, tabSize: 4, - oneIndent: '\t', + indentSize: 4, + insertSpaces: false, useTabStops: useTabStops, }), expectedLines, expectedSelection); } @@ -56,7 +57,8 @@ function testUnshiftCommand(lines: string[], languageIdentifier: LanguageIdentif testCommand(lines, languageIdentifier, selection, (sel) => new ShiftCommand(sel, { isUnshift: true, tabSize: 4, - oneIndent: '\t', + indentSize: 4, + insertSpaces: false, useTabStops: useTabStops, }), expectedLines, expectedSelection); } @@ -668,7 +670,8 @@ suite('Editor Commands - ShiftCommand', () => { (sel) => new ShiftCommand(sel, { isUnshift: false, tabSize: 4, - oneIndent: ' ', + indentSize: 4, + insertSpaces: true, useTabStops: false }), [ @@ -712,7 +715,8 @@ suite('Editor Commands - ShiftCommand', () => { (sel) => new ShiftCommand(sel, { isUnshift: true, tabSize: 4, - oneIndent: ' ', + indentSize: 4, + insertSpaces: true, useTabStops: false }), [ @@ -756,7 +760,8 @@ suite('Editor Commands - ShiftCommand', () => { (sel) => new ShiftCommand(sel, { isUnshift: true, tabSize: 4, - oneIndent: '\t', + indentSize: 4, + insertSpaces: false, useTabStops: false }), [ @@ -800,7 +805,8 @@ suite('Editor Commands - ShiftCommand', () => { (sel) => new ShiftCommand(sel, { isUnshift: true, tabSize: 4, - oneIndent: ' ', + indentSize: 4, + insertSpaces: true, useTabStops: false }), [ @@ -833,7 +839,8 @@ suite('Editor Commands - ShiftCommand', () => { (sel) => new ShiftCommand(sel, { isUnshift: false, tabSize: 4, - oneIndent: '\t', + indentSize: 4, + insertSpaces: false, useTabStops: true }), [ @@ -854,98 +861,96 @@ suite('Editor Commands - ShiftCommand', () => { return r; }; - let testOutdent = (tabSize: number, oneIndent: string, lineText: string, expectedIndents: number) => { + let testOutdent = (tabSize: number, indentSize: number, insertSpaces: boolean, lineText: string, expectedIndents: number) => { + const oneIndent = insertSpaces ? repeatStr(' ', indentSize) : '\t'; let expectedIndent = repeatStr(oneIndent, expectedIndents); if (lineText.length > 0) { - _assertUnshiftCommand(tabSize, oneIndent, [lineText + 'aaa'], [createSingleEditOp(expectedIndent, 1, 1, 1, lineText.length + 1)]); + _assertUnshiftCommand(tabSize, indentSize, insertSpaces, [lineText + 'aaa'], [createSingleEditOp(expectedIndent, 1, 1, 1, lineText.length + 1)]); } else { - _assertUnshiftCommand(tabSize, oneIndent, [lineText + 'aaa'], []); + _assertUnshiftCommand(tabSize, indentSize, insertSpaces, [lineText + 'aaa'], []); } }; - let testIndent = (tabSize: number, oneIndent: string, lineText: string, expectedIndents: number) => { + let testIndent = (tabSize: number, indentSize: number, insertSpaces: boolean, lineText: string, expectedIndents: number) => { + const oneIndent = insertSpaces ? repeatStr(' ', indentSize) : '\t'; let expectedIndent = repeatStr(oneIndent, expectedIndents); - _assertShiftCommand(tabSize, oneIndent, [lineText + 'aaa'], [createSingleEditOp(expectedIndent, 1, 1, 1, lineText.length + 1)]); + _assertShiftCommand(tabSize, indentSize, insertSpaces, [lineText + 'aaa'], [createSingleEditOp(expectedIndent, 1, 1, 1, lineText.length + 1)]); }; - let testIndentation = (tabSize: number, lineText: string, expectedOnOutdent: number, expectedOnIndent: number) => { - let spaceIndent = ''; - for (let i = 0; i < tabSize; i++) { - spaceIndent += ' '; - } + let testIndentation = (tabSize: number, indentSize: number, lineText: string, expectedOnOutdent: number, expectedOnIndent: number) => { + testOutdent(tabSize, indentSize, true, lineText, expectedOnOutdent); + testOutdent(tabSize, indentSize, false, lineText, expectedOnOutdent); - testOutdent(tabSize, spaceIndent, lineText, expectedOnOutdent); - testOutdent(tabSize, '\t', lineText, expectedOnOutdent); - - testIndent(tabSize, spaceIndent, lineText, expectedOnIndent); - testIndent(tabSize, '\t', lineText, expectedOnIndent); + testIndent(tabSize, indentSize, true, lineText, expectedOnIndent); + testIndent(tabSize, indentSize, false, lineText, expectedOnIndent); }; // insertSpaces: true // 0 => 0 - testIndentation(4, '', 0, 1); + testIndentation(4, 4, '', 0, 1); // 1 => 0 - testIndentation(4, '\t', 0, 2); - testIndentation(4, ' ', 0, 1); - testIndentation(4, ' \t', 0, 2); - testIndentation(4, ' ', 0, 1); - testIndentation(4, ' \t', 0, 2); - testIndentation(4, ' ', 0, 1); - testIndentation(4, ' \t', 0, 2); - testIndentation(4, ' ', 0, 2); + testIndentation(4, 4, '\t', 0, 2); + testIndentation(4, 4, ' ', 0, 1); + testIndentation(4, 4, ' \t', 0, 2); + testIndentation(4, 4, ' ', 0, 1); + testIndentation(4, 4, ' \t', 0, 2); + testIndentation(4, 4, ' ', 0, 1); + testIndentation(4, 4, ' \t', 0, 2); + testIndentation(4, 4, ' ', 0, 2); // 2 => 1 - testIndentation(4, '\t\t', 1, 3); - testIndentation(4, '\t ', 1, 2); - testIndentation(4, '\t \t', 1, 3); - testIndentation(4, '\t ', 1, 2); - testIndentation(4, '\t \t', 1, 3); - testIndentation(4, '\t ', 1, 2); - testIndentation(4, '\t \t', 1, 3); - testIndentation(4, '\t ', 1, 3); - testIndentation(4, ' \t\t', 1, 3); - testIndentation(4, ' \t ', 1, 2); - testIndentation(4, ' \t \t', 1, 3); - testIndentation(4, ' \t ', 1, 2); - testIndentation(4, ' \t \t', 1, 3); - testIndentation(4, ' \t ', 1, 2); - testIndentation(4, ' \t \t', 1, 3); - testIndentation(4, ' \t ', 1, 3); - testIndentation(4, ' \t\t', 1, 3); - testIndentation(4, ' \t ', 1, 2); - testIndentation(4, ' \t \t', 1, 3); - testIndentation(4, ' \t ', 1, 2); - testIndentation(4, ' \t \t', 1, 3); - testIndentation(4, ' \t ', 1, 2); - testIndentation(4, ' \t \t', 1, 3); - testIndentation(4, ' \t ', 1, 3); - testIndentation(4, ' \t\t', 1, 3); - testIndentation(4, ' \t ', 1, 2); - testIndentation(4, ' \t \t', 1, 3); - testIndentation(4, ' \t ', 1, 2); - testIndentation(4, ' \t \t', 1, 3); - testIndentation(4, ' \t ', 1, 2); - testIndentation(4, ' \t \t', 1, 3); - testIndentation(4, ' \t ', 1, 3); - testIndentation(4, ' \t', 1, 3); - testIndentation(4, ' ', 1, 2); - testIndentation(4, ' \t', 1, 3); - testIndentation(4, ' ', 1, 2); - testIndentation(4, ' \t', 1, 3); - testIndentation(4, ' ', 1, 2); - testIndentation(4, ' \t', 1, 3); - testIndentation(4, ' ', 1, 3); + testIndentation(4, 4, '\t\t', 1, 3); + testIndentation(4, 4, '\t ', 1, 2); + testIndentation(4, 4, '\t \t', 1, 3); + testIndentation(4, 4, '\t ', 1, 2); + testIndentation(4, 4, '\t \t', 1, 3); + testIndentation(4, 4, '\t ', 1, 2); + testIndentation(4, 4, '\t \t', 1, 3); + testIndentation(4, 4, '\t ', 1, 3); + testIndentation(4, 4, ' \t\t', 1, 3); + testIndentation(4, 4, ' \t ', 1, 2); + testIndentation(4, 4, ' \t \t', 1, 3); + testIndentation(4, 4, ' \t ', 1, 2); + testIndentation(4, 4, ' \t \t', 1, 3); + testIndentation(4, 4, ' \t ', 1, 2); + testIndentation(4, 4, ' \t \t', 1, 3); + testIndentation(4, 4, ' \t ', 1, 3); + testIndentation(4, 4, ' \t\t', 1, 3); + testIndentation(4, 4, ' \t ', 1, 2); + testIndentation(4, 4, ' \t \t', 1, 3); + testIndentation(4, 4, ' \t ', 1, 2); + testIndentation(4, 4, ' \t \t', 1, 3); + testIndentation(4, 4, ' \t ', 1, 2); + testIndentation(4, 4, ' \t \t', 1, 3); + testIndentation(4, 4, ' \t ', 1, 3); + testIndentation(4, 4, ' \t\t', 1, 3); + testIndentation(4, 4, ' \t ', 1, 2); + testIndentation(4, 4, ' \t \t', 1, 3); + testIndentation(4, 4, ' \t ', 1, 2); + testIndentation(4, 4, ' \t \t', 1, 3); + testIndentation(4, 4, ' \t ', 1, 2); + testIndentation(4, 4, ' \t \t', 1, 3); + testIndentation(4, 4, ' \t ', 1, 3); + testIndentation(4, 4, ' \t', 1, 3); + testIndentation(4, 4, ' ', 1, 2); + testIndentation(4, 4, ' \t', 1, 3); + testIndentation(4, 4, ' ', 1, 2); + testIndentation(4, 4, ' \t', 1, 3); + testIndentation(4, 4, ' ', 1, 2); + testIndentation(4, 4, ' \t', 1, 3); + testIndentation(4, 4, ' ', 1, 3); // 3 => 2 - testIndentation(4, ' ', 2, 3); + testIndentation(4, 4, ' ', 2, 3); - function _assertUnshiftCommand(tabSize: number, oneIndent: string, text: string[], expected: IIdentifiedSingleEditOperation[]): void { + function _assertUnshiftCommand(tabSize: number, indentSize: number, insertSpaces: boolean, text: string[], expected: IIdentifiedSingleEditOperation[]): void { return withEditorModel(text, (model) => { let op = new ShiftCommand(new Selection(1, 1, text.length + 1, 1), { isUnshift: true, tabSize: tabSize, - oneIndent: oneIndent, + indentSize: indentSize, + insertSpaces: insertSpaces, useTabStops: true }); let actual = getEditOperation(model, op); @@ -953,12 +958,13 @@ suite('Editor Commands - ShiftCommand', () => { }); } - function _assertShiftCommand(tabSize: number, oneIndent: string, text: string[], expected: IIdentifiedSingleEditOperation[]): void { + function _assertShiftCommand(tabSize: number, indentSize: number, insertSpaces: boolean, text: string[], expected: IIdentifiedSingleEditOperation[]): void { return withEditorModel(text, (model) => { let op = new ShiftCommand(new Selection(1, 1, text.length + 1, 1), { isUnshift: false, tabSize: tabSize, - oneIndent: oneIndent, + indentSize: indentSize, + insertSpaces: insertSpaces, useTabStops: true }); let actual = getEditOperation(model, op); diff --git a/src/vs/editor/test/browser/controller/cursor.test.ts b/src/vs/editor/test/browser/controller/cursor.test.ts index 5276a15c3bd..d34c9ff1b1f 100644 --- a/src/vs/editor/test/browser/controller/cursor.test.ts +++ b/src/vs/editor/test/browser/controller/cursor.test.ts @@ -2212,6 +2212,7 @@ suite('Editor Controller - Cursor Configuration', () => { ].join('\n'), { tabSize: 13, + indentSize: 13, } ); @@ -3122,7 +3123,10 @@ suite('Editor Controller - Indentation Rules', () => { '}a}' ], languageIdentifier: mode.getLanguageIdentifier(), - modelOpts: { tabSize: 2 } + modelOpts: { + tabSize: 2, + indentSize: 2 + } }, (model, cursor) => { moveTo(cursor, 3, 3, false); assertCursor(cursor, new Selection(3, 3, 3, 3)); @@ -3594,7 +3598,10 @@ suite('Editor Controller - Indentation Rules', () => { '', ')', ].join('\n'), - { tabSize: 2 }, + { + tabSize: 2, + indentSize: 2 + }, mode.getLanguageIdentifier() ); diff --git a/src/vs/editor/test/common/controller/cursorMoveHelper.test.ts b/src/vs/editor/test/common/controller/cursorMoveHelper.test.ts index 8cb43316d37..4f2fe8b9283 100644 --- a/src/vs/editor/test/common/controller/cursorMoveHelper.test.ts +++ b/src/vs/editor/test/common/controller/cursorMoveHelper.test.ts @@ -7,36 +7,36 @@ import { CursorColumns } from 'vs/editor/common/controller/cursorCommon'; suite('CursorMove', () => { - test('nextTabStop', () => { - assert.equal(CursorColumns.nextTabStop(0, 4), 4); - assert.equal(CursorColumns.nextTabStop(1, 4), 4); - assert.equal(CursorColumns.nextTabStop(2, 4), 4); - assert.equal(CursorColumns.nextTabStop(3, 4), 4); - assert.equal(CursorColumns.nextTabStop(4, 4), 8); - assert.equal(CursorColumns.nextTabStop(5, 4), 8); - assert.equal(CursorColumns.nextTabStop(6, 4), 8); - assert.equal(CursorColumns.nextTabStop(7, 4), 8); - assert.equal(CursorColumns.nextTabStop(8, 4), 12); + test('nextRenderTabStop', () => { + assert.equal(CursorColumns.nextRenderTabStop(0, 4), 4); + assert.equal(CursorColumns.nextRenderTabStop(1, 4), 4); + assert.equal(CursorColumns.nextRenderTabStop(2, 4), 4); + assert.equal(CursorColumns.nextRenderTabStop(3, 4), 4); + assert.equal(CursorColumns.nextRenderTabStop(4, 4), 8); + assert.equal(CursorColumns.nextRenderTabStop(5, 4), 8); + assert.equal(CursorColumns.nextRenderTabStop(6, 4), 8); + assert.equal(CursorColumns.nextRenderTabStop(7, 4), 8); + assert.equal(CursorColumns.nextRenderTabStop(8, 4), 12); - assert.equal(CursorColumns.nextTabStop(0, 2), 2); - assert.equal(CursorColumns.nextTabStop(1, 2), 2); - assert.equal(CursorColumns.nextTabStop(2, 2), 4); - assert.equal(CursorColumns.nextTabStop(3, 2), 4); - assert.equal(CursorColumns.nextTabStop(4, 2), 6); - assert.equal(CursorColumns.nextTabStop(5, 2), 6); - assert.equal(CursorColumns.nextTabStop(6, 2), 8); - assert.equal(CursorColumns.nextTabStop(7, 2), 8); - assert.equal(CursorColumns.nextTabStop(8, 2), 10); + assert.equal(CursorColumns.nextRenderTabStop(0, 2), 2); + assert.equal(CursorColumns.nextRenderTabStop(1, 2), 2); + assert.equal(CursorColumns.nextRenderTabStop(2, 2), 4); + assert.equal(CursorColumns.nextRenderTabStop(3, 2), 4); + assert.equal(CursorColumns.nextRenderTabStop(4, 2), 6); + assert.equal(CursorColumns.nextRenderTabStop(5, 2), 6); + assert.equal(CursorColumns.nextRenderTabStop(6, 2), 8); + assert.equal(CursorColumns.nextRenderTabStop(7, 2), 8); + assert.equal(CursorColumns.nextRenderTabStop(8, 2), 10); - assert.equal(CursorColumns.nextTabStop(0, 1), 1); - assert.equal(CursorColumns.nextTabStop(1, 1), 2); - assert.equal(CursorColumns.nextTabStop(2, 1), 3); - assert.equal(CursorColumns.nextTabStop(3, 1), 4); - assert.equal(CursorColumns.nextTabStop(4, 1), 5); - assert.equal(CursorColumns.nextTabStop(5, 1), 6); - assert.equal(CursorColumns.nextTabStop(6, 1), 7); - assert.equal(CursorColumns.nextTabStop(7, 1), 8); - assert.equal(CursorColumns.nextTabStop(8, 1), 9); + assert.equal(CursorColumns.nextRenderTabStop(0, 1), 1); + assert.equal(CursorColumns.nextRenderTabStop(1, 1), 2); + assert.equal(CursorColumns.nextRenderTabStop(2, 1), 3); + assert.equal(CursorColumns.nextRenderTabStop(3, 1), 4); + assert.equal(CursorColumns.nextRenderTabStop(4, 1), 5); + assert.equal(CursorColumns.nextRenderTabStop(5, 1), 6); + assert.equal(CursorColumns.nextRenderTabStop(6, 1), 7); + assert.equal(CursorColumns.nextRenderTabStop(7, 1), 8); + assert.equal(CursorColumns.nextRenderTabStop(8, 1), 9); }); test('visibleColumnFromColumn', () => { diff --git a/src/vs/editor/test/common/editorTestUtils.ts b/src/vs/editor/test/common/editorTestUtils.ts index 6fae62d5463..3116fa31171 100644 --- a/src/vs/editor/test/common/editorTestUtils.ts +++ b/src/vs/editor/test/common/editorTestUtils.ts @@ -16,6 +16,7 @@ export function withEditorModel(text: string[], callback: (model: TextModel) => export interface IRelaxedTextModelCreationOptions { tabSize?: number; + indentSize?: number; insertSpaces?: boolean; detectIndentation?: boolean; trimAutoWhitespace?: boolean; @@ -27,6 +28,7 @@ export interface IRelaxedTextModelCreationOptions { export function createTextModel(text: string, _options: IRelaxedTextModelCreationOptions = TextModel.DEFAULT_CREATION_OPTIONS, languageIdentifier: LanguageIdentifier | null = null, uri: URI | null = null): TextModel { const options: ITextModelCreationOptions = { tabSize: (typeof _options.tabSize === 'undefined' ? TextModel.DEFAULT_CREATION_OPTIONS.tabSize : _options.tabSize), + indentSize: (typeof _options.indentSize === 'undefined' ? TextModel.DEFAULT_CREATION_OPTIONS.indentSize : _options.indentSize), insertSpaces: (typeof _options.insertSpaces === 'undefined' ? TextModel.DEFAULT_CREATION_OPTIONS.insertSpaces : _options.insertSpaces), detectIndentation: (typeof _options.detectIndentation === 'undefined' ? TextModel.DEFAULT_CREATION_OPTIONS.detectIndentation : _options.detectIndentation), trimAutoWhitespace: (typeof _options.trimAutoWhitespace === 'undefined' ? TextModel.DEFAULT_CREATION_OPTIONS.trimAutoWhitespace : _options.trimAutoWhitespace), diff --git a/src/vs/monaco.d.ts b/src/vs/monaco.d.ts index 6ad774afecc..09c73bd411d 100644 --- a/src/vs/monaco.d.ts +++ b/src/vs/monaco.d.ts @@ -1421,6 +1421,7 @@ declare namespace monaco.editor { export class TextModelResolvedOptions { _textModelResolvedOptionsBrand: void; readonly tabSize: number; + readonly indentSize: number; readonly insertSpaces: boolean; readonly defaultEOL: DefaultEndOfLine; readonly trimAutoWhitespace: boolean; @@ -1428,6 +1429,7 @@ declare namespace monaco.editor { export interface ITextModelUpdateOptions { tabSize?: number; + indentSize?: number; insertSpaces?: boolean; trimAutoWhitespace?: boolean; } @@ -1714,10 +1716,6 @@ declare namespace monaco.editor { * Normalize a string containing whitespace according to indentation rules (converts to spaces or to tabs). */ normalizeIndentation(str: string): string; - /** - * Get what is considered to be one indent (e.g. a tab character or 4 spaces, etc.). - */ - getOneIndent(): string; /** * Change the options of this model. */ @@ -2278,6 +2276,7 @@ declare namespace monaco.editor { export interface IModelOptionsChangedEvent { readonly tabSize: boolean; + readonly indentSize: boolean; readonly insertSpaces: boolean; readonly trimAutoWhitespace: boolean; } @@ -2582,6 +2581,11 @@ declare namespace monaco.editor { * Defaults to true. */ lineNumbers?: 'on' | 'off' | 'relative' | 'interval' | ((lineNumber: number) => string); + /** + * Render last line number when the file ends with a newline. + * Defaults to true on Windows/Mac and to false on Linux. + */ + renderFinalNewline?: boolean; /** * Should the corresponding line be selected when clicking on the line number? * Defaults to true. @@ -3219,6 +3223,7 @@ declare namespace monaco.editor { readonly ariaLabel: string; readonly renderLineNumbers: RenderLineNumbersType; readonly renderCustomLineNumbers: ((lineNumber: number) => string) | null; + readonly renderFinalNewline: boolean; readonly selectOnLineNumbers: boolean; readonly glyphMargin: boolean; readonly revealHorizontalRightPadding: number; diff --git a/src/vs/platform/configuration/common/configurationModels.ts b/src/vs/platform/configuration/common/configurationModels.ts index 2a3e33868f0..a3e0810edd8 100644 --- a/src/vs/platform/configuration/common/configurationModels.ts +++ b/src/vs/platform/configuration/common/configurationModels.ts @@ -291,7 +291,7 @@ export class Configuration { private _freeze: boolean = true) { } - getValue(section: string | undefined, overrides: IConfigurationOverrides, workspace: Workspace | null): any { + getValue(section: string | undefined, overrides: IConfigurationOverrides, workspace: Workspace | undefined): any { const consolidateConfigurationModel = this.getConsolidateConfigurationModel(overrides, workspace); return consolidateConfigurationModel.getValue(section); } @@ -319,7 +319,7 @@ export class Configuration { } } - inspect(key: string, overrides: IConfigurationOverrides, workspace: Workspace | null): { + inspect(key: string, overrides: IConfigurationOverrides, workspace: Workspace | undefined): { default: C, user: C, workspace?: C, @@ -340,7 +340,7 @@ export class Configuration { }; } - keys(workspace: Workspace | null): { + keys(workspace: Workspace | undefined): { default: string[]; user: string[]; workspace: string[]; @@ -399,12 +399,12 @@ export class Configuration { return this._folderConfigurations; } - private getConsolidateConfigurationModel(overrides: IConfigurationOverrides, workspace: Workspace | null): ConfigurationModel { + private getConsolidateConfigurationModel(overrides: IConfigurationOverrides, workspace: Workspace | undefined): ConfigurationModel { let configurationModel = this.getConsolidatedConfigurationModelForResource(overrides, workspace); return overrides.overrideIdentifier ? configurationModel.override(overrides.overrideIdentifier) : configurationModel; } - private getConsolidatedConfigurationModelForResource({ resource }: IConfigurationOverrides, workspace: Workspace | null): ConfigurationModel { + private getConsolidatedConfigurationModelForResource({ resource }: IConfigurationOverrides, workspace: Workspace | undefined): ConfigurationModel { let consolidateConfiguration = this.getWorkspaceConsolidatedConfiguration(); if (workspace && resource) { @@ -449,7 +449,7 @@ export class Configuration { return folderConsolidatedConfiguration; } - private getFolderConfigurationModelForResource(resource: URI | null | undefined, workspace: Workspace | null): ConfigurationModel | null { + private getFolderConfigurationModelForResource(resource: URI | null | undefined, workspace: Workspace | undefined): ConfigurationModel | null { if (workspace && resource) { const root = workspace.getFolder(resource); if (root) { diff --git a/src/vs/platform/configuration/node/configurationService.ts b/src/vs/platform/configuration/node/configurationService.ts index 398b3dafec2..c3d82139cb5 100644 --- a/src/vs/platform/configuration/node/configurationService.ts +++ b/src/vs/platform/configuration/node/configurationService.ts @@ -55,7 +55,7 @@ export class ConfigurationService extends Disposable implements IConfigurationSe getValue(arg1?: any, arg2?: any): any { const section = typeof arg1 === 'string' ? arg1 : undefined; const overrides = isConfigurationOverrides(arg1) ? arg1 : isConfigurationOverrides(arg2) ? arg2 : {}; - return this.configuration.getValue(section, overrides, null); + return this.configuration.getValue(section, overrides, undefined); } updateValue(key: string, value: any): Promise; @@ -73,7 +73,7 @@ export class ConfigurationService extends Disposable implements IConfigurationSe workspaceFolder?: T value: T } { - return this.configuration.inspect(key, {}, null); + return this.configuration.inspect(key, {}, undefined); } keys(): { @@ -82,7 +82,7 @@ export class ConfigurationService extends Disposable implements IConfigurationSe workspace: string[]; workspaceFolder: string[]; } { - return this.configuration.keys(null); + return this.configuration.keys(undefined); } reloadConfiguration(folder?: IWorkspaceFolder): Promise { diff --git a/src/vs/platform/driver/electron-main/driver.ts b/src/vs/platform/driver/electron-main/driver.ts index b84856fb0f7..219f85f6240 100644 --- a/src/vs/platform/driver/electron-main/driver.ts +++ b/src/vs/platform/driver/electron-main/driver.ts @@ -83,16 +83,10 @@ export class Driver implements IDriver, IWindowDriverRegistry { async dispatchKeybinding(windowId: number, keybinding: string): Promise { await this.whenUnfrozen(windowId); - const [first, second] = KeybindingParser.parseUserBinding(keybinding); + const parts = KeybindingParser.parseUserBinding(keybinding); - if (!first) { - return; - } - - await this._dispatchKeybinding(windowId, first); - - if (second) { - await this._dispatchKeybinding(windowId, second); + for (let part of parts) { + await this._dispatchKeybinding(windowId, part); } } diff --git a/src/vs/platform/files/test/files.test.ts b/src/vs/platform/files/test/files.test.ts index 932838a2332..eda1d2dbab8 100644 --- a/src/vs/platform/files/test/files.test.ts +++ b/src/vs/platform/files/test/files.test.ts @@ -5,36 +5,33 @@ import * as assert from 'assert'; import { URI } from 'vs/base/common/uri'; -import { join, isEqual, isEqualOrParent } from 'vs/base/common/extpath'; +import { isEqual, isEqualOrParent } from 'vs/base/common/extpath'; import { FileChangeType, FileChangesEvent, isParent } from 'vs/platform/files/common/files'; import { isLinux, isMacintosh, isWindows } from 'vs/base/common/platform'; +import { toResource } from 'vs/base/test/common/utils'; suite('Files', () => { - function toResource(path) { - return URI.file(join('C:\\', path)); - } - - test('FileChangesEvent', () => { + test('FileChangesEvent', function () { let changes = [ - { resource: URI.file(join('C:\\', '/foo/updated.txt')), type: FileChangeType.UPDATED }, - { resource: URI.file(join('C:\\', '/foo/otherupdated.txt')), type: FileChangeType.UPDATED }, - { resource: URI.file(join('C:\\', '/added.txt')), type: FileChangeType.ADDED }, - { resource: URI.file(join('C:\\', '/bar/deleted.txt')), type: FileChangeType.DELETED }, - { resource: URI.file(join('C:\\', '/bar/folder')), type: FileChangeType.DELETED } + { resource: toResource.call(this, '/foo/updated.txt'), type: FileChangeType.UPDATED }, + { resource: toResource.call(this, '/foo/otherupdated.txt'), type: FileChangeType.UPDATED }, + { resource: toResource.call(this, '/added.txt'), type: FileChangeType.ADDED }, + { resource: toResource.call(this, '/bar/deleted.txt'), type: FileChangeType.DELETED }, + { resource: toResource.call(this, '/bar/folder'), type: FileChangeType.DELETED } ]; let r1 = new FileChangesEvent(changes); - assert(!r1.contains(toResource('/foo'), FileChangeType.UPDATED)); - assert(r1.contains(toResource('/foo/updated.txt'), FileChangeType.UPDATED)); - assert(!r1.contains(toResource('/foo/updated.txt'), FileChangeType.ADDED)); - assert(!r1.contains(toResource('/foo/updated.txt'), FileChangeType.DELETED)); + assert(!r1.contains(toResource.call(this, '/foo'), FileChangeType.UPDATED)); + assert(r1.contains(toResource.call(this, '/foo/updated.txt'), FileChangeType.UPDATED)); + assert(!r1.contains(toResource.call(this, '/foo/updated.txt'), FileChangeType.ADDED)); + assert(!r1.contains(toResource.call(this, '/foo/updated.txt'), FileChangeType.DELETED)); - assert(r1.contains(toResource('/bar/folder'), FileChangeType.DELETED)); - assert(r1.contains(toResource('/bar/folder/somefile'), FileChangeType.DELETED)); - assert(r1.contains(toResource('/bar/folder/somefile/test.txt'), FileChangeType.DELETED)); - assert(!r1.contains(toResource('/bar/folder2/somefile'), FileChangeType.DELETED)); + assert(r1.contains(toResource.call(this, '/bar/folder'), FileChangeType.DELETED)); + assert(r1.contains(toResource.call(this, '/bar/folder/somefile'), FileChangeType.DELETED)); + assert(r1.contains(toResource.call(this, '/bar/folder/somefile/test.txt'), FileChangeType.DELETED)); + assert(!r1.contains(toResource.call(this, '/bar/folder2/somefile'), FileChangeType.DELETED)); assert.strictEqual(5, r1.changes.length); assert.strictEqual(1, r1.getAdded().length); diff --git a/src/vs/platform/keybinding/common/baseResolvedKeybinding.ts b/src/vs/platform/keybinding/common/baseResolvedKeybinding.ts new file mode 100644 index 00000000000..cae3bd603da --- /dev/null +++ b/src/vs/platform/keybinding/common/baseResolvedKeybinding.ts @@ -0,0 +1,78 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { OperatingSystem } from 'vs/base/common/platform'; +import { illegalArgument } from 'vs/base/common/errors'; +import { Modifiers, UILabelProvider, AriaLabelProvider, ElectronAcceleratorLabelProvider, UserSettingsLabelProvider } from 'vs/base/common/keybindingLabels'; +import { ResolvedKeybinding, ResolvedKeybindingPart } from 'vs/base/common/keyCodes'; + +export abstract class BaseResolvedKeybinding extends ResolvedKeybinding { + + protected readonly _os: OperatingSystem; + protected readonly _parts: T[]; + + constructor(os: OperatingSystem, parts: T[]) { + super(); + if (parts.length === 0) { + throw illegalArgument(`parts`); + } + this._os = os; + this._parts = parts; + } + + public getLabel(): string | null { + return UILabelProvider.toLabel(this._os, this._parts, (keybinding) => this._getLabel(keybinding)); + } + + public getAriaLabel(): string | null { + return AriaLabelProvider.toLabel(this._os, this._parts, (keybinding) => this._getAriaLabel(keybinding)); + } + + public getElectronAccelerator(): string | null { + if (this._parts.length > 1) { + // Electron cannot handle chords + return null; + } + return ElectronAcceleratorLabelProvider.toLabel(this._os, this._parts, (keybinding) => this._getElectronAccelerator(keybinding)); + } + + public getUserSettingsLabel(): string | null { + return UserSettingsLabelProvider.toLabel(this._os, this._parts, (keybinding) => this._getUserSettingsLabel(keybinding)); + } + + public isWYSIWYG(): boolean { + return this._parts.every((keybinding) => this._isWYSIWYG(keybinding)); + } + + public isChord(): boolean { + return (this._parts.length > 1); + } + + public getParts(): ResolvedKeybindingPart[] { + return this._parts.map((keybinding) => this._getPart(keybinding)); + } + + private _getPart(keybinding: T): ResolvedKeybindingPart { + return new ResolvedKeybindingPart( + keybinding.ctrlKey, + keybinding.shiftKey, + keybinding.altKey, + keybinding.metaKey, + this._getLabel(keybinding), + this._getAriaLabel(keybinding) + ); + } + + public getDispatchParts(): (string | null)[] { + return this._parts.map((keybinding) => this._getDispatchPart(keybinding)); + } + + protected abstract _getLabel(keybinding: T): string | null; + protected abstract _getAriaLabel(keybinding: T): string | null; + protected abstract _getElectronAccelerator(keybinding: T): string | null; + protected abstract _getUserSettingsLabel(keybinding: T): string | null; + protected abstract _isWYSIWYG(keybinding: T): boolean; + protected abstract _getDispatchPart(keybinding: T): string | null; +} diff --git a/src/vs/platform/keybinding/common/keybindingResolver.ts b/src/vs/platform/keybinding/common/keybindingResolver.ts index 0d631c3a4de..9b714a1150e 100644 --- a/src/vs/platform/keybinding/common/keybindingResolver.ts +++ b/src/vs/platform/keybinding/common/keybindingResolver.ts @@ -40,12 +40,13 @@ export class KeybindingResolver { this._keybindings = KeybindingResolver.combine(defaultKeybindings, overrides); for (let i = 0, len = this._keybindings.length; i < len; i++) { let k = this._keybindings[i]; - if (k.keypressFirstPart === null) { + if (k.keypressParts.length === 0) { // unbound continue; } - this._addKeyPress(k.keypressFirstPart, k); + // TODO@chords + this._addKeyPress(k.keypressParts[0], k); } } @@ -53,10 +54,12 @@ export class KeybindingResolver { if (defaultKb.command !== command) { return false; } - if (keypressFirstPart && defaultKb.keypressFirstPart !== keypressFirstPart) { + // TODO@chords + if (keypressFirstPart && defaultKb.keypressParts[0] !== keypressFirstPart) { return false; } - if (keypressChordPart && defaultKb.keypressChordPart !== keypressChordPart) { + // TODO@chords + if (keypressChordPart && defaultKb.keypressParts[1] !== keypressChordPart) { return false; } if (when) { @@ -84,8 +87,9 @@ export class KeybindingResolver { } const command = override.command.substr(1); - const keypressFirstPart = override.keypressFirstPart; - const keypressChordPart = override.keypressChordPart; + // TODO@chords + const keypressFirstPart = override.keypressParts[0]; + const keypressChordPart = override.keypressParts[1]; const when = override.when; for (let j = defaults.length - 1; j >= 0; j--) { if (this._isTargetedForRemoval(defaults[j], keypressFirstPart, keypressChordPart, command, when)) { @@ -114,10 +118,11 @@ export class KeybindingResolver { continue; } - const conflictIsChord = (conflict.keypressChordPart !== null); - const itemIsChord = (item.keypressChordPart !== null); + const conflictIsChord = (conflict.keypressParts.length > 1); + const itemIsChord = (item.keypressParts.length > 1); - if (conflictIsChord && itemIsChord && conflict.keypressChordPart !== item.keypressChordPart) { + // TODO@chords + if (conflictIsChord && itemIsChord && conflict.keypressParts[1] !== item.keypressParts[1]) { // The conflict only shares the chord start with this command continue; } @@ -247,7 +252,8 @@ export class KeybindingResolver { lookupMap = []; for (let i = 0, len = candidates.length; i < len; i++) { let candidate = candidates[i]; - if (candidate.keypressChordPart === keypress) { + // TODO@chords + if (candidate.keypressParts[1] === keypress) { lookupMap.push(candidate); } } @@ -266,7 +272,8 @@ export class KeybindingResolver { return null; } - if (currentChord === null && result.keypressChordPart !== null) { + // TODO@chords + if (currentChord === null && result.keypressParts.length > 1 && result.keypressParts[1] !== null) { return { enterChord: true, commandId: null, diff --git a/src/vs/platform/keybinding/common/resolvedKeybindingItem.ts b/src/vs/platform/keybinding/common/resolvedKeybindingItem.ts index 5df9723f091..7dfe09262d1 100644 --- a/src/vs/platform/keybinding/common/resolvedKeybindingItem.ts +++ b/src/vs/platform/keybinding/common/resolvedKeybindingItem.ts @@ -11,8 +11,7 @@ export class ResolvedKeybindingItem { _resolvedKeybindingItemBrand: void; public readonly resolvedKeybinding: ResolvedKeybinding | null; - public readonly keypressFirstPart: string | null; - public readonly keypressChordPart: string | null; + public readonly keypressParts: string[]; public readonly bubble: boolean; public readonly command: string | null; public readonly commandArgs: any; @@ -21,23 +20,7 @@ export class ResolvedKeybindingItem { constructor(resolvedKeybinding: ResolvedKeybinding | null, command: string | null, commandArgs: any, when: ContextKeyExpr | null, isDefault: boolean) { this.resolvedKeybinding = resolvedKeybinding; - if (resolvedKeybinding) { - const dispatchParts = resolvedKeybinding.getDispatchParts(); - // TODO@chords: add support for dispatching N chords here - if (dispatchParts.length >= 2) { - this.keypressFirstPart = dispatchParts[0]; - this.keypressChordPart = dispatchParts[1]; - } else if (dispatchParts.length === 1) { - this.keypressFirstPart = dispatchParts[0]; - this.keypressChordPart = null; - } else { - this.keypressFirstPart = null; - this.keypressChordPart = null; - } - } else { - this.keypressFirstPart = null; - this.keypressChordPart = null; - } + this.keypressParts = resolvedKeybinding ? removeElementsAfterNulls(resolvedKeybinding.getDispatchParts()) : []; this.bubble = (command ? command.charCodeAt(0) === CharCode.Caret : false); this.command = this.bubble ? command!.substr(1) : command; this.commandArgs = commandArgs; @@ -45,3 +28,16 @@ export class ResolvedKeybindingItem { this.isDefault = isDefault; } } + +export function removeElementsAfterNulls(arr: (T | null)[]): T[] { + let result: T[] = []; + for (let i = 0, len = arr.length; i < len; i++) { + const element = arr[i]; + if (!element) { + // stop processing at first encountered null + return result; + } + result.push(element); + } + return result; +} diff --git a/src/vs/platform/keybinding/common/usLayoutResolvedKeybinding.ts b/src/vs/platform/keybinding/common/usLayoutResolvedKeybinding.ts index fb6c328c881..5a24412035a 100644 --- a/src/vs/platform/keybinding/common/usLayoutResolvedKeybinding.ts +++ b/src/vs/platform/keybinding/common/usLayoutResolvedKeybinding.ts @@ -3,8 +3,9 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { KeyCode, KeyCodeUtils, Keybinding, SimpleKeybinding, BaseResolvedKeybinding } from 'vs/base/common/keyCodes'; +import { KeyCode, KeyCodeUtils, Keybinding, SimpleKeybinding } from 'vs/base/common/keyCodes'; import { OperatingSystem } from 'vs/base/common/platform'; +import { BaseResolvedKeybinding } from 'vs/platform/keybinding/common/baseResolvedKeybinding'; /** * Do not instantiate. Use KeybindingService to get a ResolvedKeybinding seeded with information about the current kb layout. diff --git a/src/vs/platform/list/browser/listService.ts b/src/vs/platform/list/browser/listService.ts index 1f89c13f399..b1626237c58 100644 --- a/src/vs/platform/list/browser/listService.ts +++ b/src/vs/platform/list/browser/listService.ts @@ -659,7 +659,7 @@ export interface SelectionKeyboardEvent extends KeyboardEvent { preserveFocus?: boolean; } -export function getSelectionKeyboardEvent(typeArg: string, preserveFocus?: boolean): SelectionKeyboardEvent { +export function getSelectionKeyboardEvent(typeArg = 'keydown', preserveFocus?: boolean): SelectionKeyboardEvent { const e = new KeyboardEvent(typeArg); (e).preserveFocus = preserveFocus; @@ -1144,7 +1144,7 @@ configurationRegistry.registerConfiguration({ 'type': 'number', 'default': 8, minimum: 0, - maximum: 20, + maximum: 40, 'description': localize('tree indent setting', "Controls tree indentation in pixels.") }, [keyboardNavigationSettingKey]: { diff --git a/src/vs/platform/storage/node/storageIpc.ts b/src/vs/platform/storage/node/storageIpc.ts index c48b421bb81..01e9448c985 100644 --- a/src/vs/platform/storage/node/storageIpc.ts +++ b/src/vs/platform/storage/node/storageIpc.ts @@ -9,6 +9,10 @@ import { StorageMainService, IStorageChangeEvent } from 'vs/platform/storage/nod import { IUpdateRequest, IStorageDatabase, IStorageItemsChangeEvent } from 'vs/base/node/storage'; import { mapToSerializable, serializableToMap, values } from 'vs/base/common/map'; import { Disposable, IDisposable, dispose } from 'vs/base/common/lifecycle'; +import { onUnexpectedError } from 'vs/base/common/errors'; +import { ILogService } from 'vs/platform/log/common/log'; +import { generateUuid } from 'vs/base/common/uuid'; +import { instanceStorageKey, firstSessionDateStorageKey, lastSessionDateStorageKey, currentSessionDateStorageKey } from 'vs/platform/telemetry/node/workbenchCommonProperties'; type Key = string; type Value = string; @@ -30,10 +34,48 @@ export class GlobalStorageDatabaseChannel extends Disposable implements IServerC private readonly _onDidChangeItems: Emitter = this._register(new Emitter()); get onDidChangeItems(): Event { return this._onDidChangeItems.event; } - constructor(private storageMainService: StorageMainService) { + private whenReady: Promise; + + constructor( + private logService: ILogService, + private storageMainService: StorageMainService + ) { super(); - this.registerListeners(); + this.whenReady = this.init(); + } + + private init(): Promise { + return this.storageMainService.initialize().then(undefined, error => { + onUnexpectedError(error); + this.logService.error(error); + }).then(() => { + + // Apply global telemetry values as part of the initialization + // These are global across all windows and thereby should be + // written from the main process once. + this.initTelemetry(); + + // Setup storage change listeners + this.registerListeners(); + }); + } + + private initTelemetry(): void { + const instanceId = this.storageMainService.get(instanceStorageKey, undefined); + if (instanceId === undefined) { + this.storageMainService.store(instanceStorageKey, generateUuid()); + } + + const firstSessionDate = this.storageMainService.get(firstSessionDateStorageKey, undefined); + if (firstSessionDate === undefined) { + this.storageMainService.store(firstSessionDateStorageKey, new Date().toUTCString()); + } + + const lastSessionDate = this.storageMainService.get(currentSessionDateStorageKey, undefined); // previous session date was the "current" one at that time + const currentSessionDate = new Date().toUTCString(); // current session date is "now" + this.storageMainService.store(lastSessionDateStorageKey, typeof lastSessionDate === 'undefined' ? null : lastSessionDate); + this.storageMainService.store(currentSessionDateStorageKey, currentSessionDate); } private registerListeners(): void { @@ -73,26 +115,26 @@ export class GlobalStorageDatabaseChannel extends Disposable implements IServerC call(_, command: string, arg?: any): Promise { switch (command) { case 'getItems': { - return Promise.resolve(mapToSerializable(this.storageMainService.items)); + return this.whenReady.then(() => mapToSerializable(this.storageMainService.items)); } case 'updateItems': { - const items = arg as ISerializableUpdateRequest; - if (items.insert) { - for (const [key, value] of items.insert) { - this.storageMainService.store(key, value); + return this.whenReady.then(() => { + const items = arg as ISerializableUpdateRequest; + if (items.insert) { + for (const [key, value] of items.insert) { + this.storageMainService.store(key, value); + } } - } - if (items.delete) { - items.delete.forEach(key => this.storageMainService.remove(key)); - } - - return Promise.resolve(); // do not wait for modifications to complete + if (items.delete) { + items.delete.forEach(key => this.storageMainService.remove(key)); + } + }); } case 'checkIntegrity': { - return this.storageMainService.checkIntegrity(arg); + return this.whenReady.then(() => this.storageMainService.checkIntegrity(arg)); } } diff --git a/src/vs/platform/storage/node/storageMainService.ts b/src/vs/platform/storage/node/storageMainService.ts index 858acbf945f..e5d43414b64 100644 --- a/src/vs/platform/storage/node/storageMainService.ts +++ b/src/vs/platform/storage/node/storageMainService.ts @@ -10,7 +10,6 @@ import { ILogService, LogLevel } from 'vs/platform/log/common/log'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { IStorage, Storage, SQLiteStorageDatabase, ISQLiteStorageDatabaseLoggingOptions, InMemoryStorageDatabase } from 'vs/base/node/storage'; import { join } from 'vs/base/common/path'; -import { mark } from 'vs/base/common/performance'; import { exists, readdir } from 'vs/base/node/pfs'; import { Database } from 'vscode-sqlite3'; import { endsWith, startsWith } from 'vs/base/common/strings'; @@ -88,6 +87,8 @@ export class StorageMainService extends Disposable implements IStorageMainServic private storage: IStorage; + private initializePromise: Promise; + constructor( @ILogService private readonly logService: ILogService, @IEnvironmentService private readonly environmentService: IEnvironmentService @@ -114,6 +115,14 @@ export class StorageMainService extends Disposable implements IStorageMainServic } initialize(): Promise { + if (!this.initializePromise) { + this.initializePromise = this.doInitialize(); + } + + return this.initializePromise; + } + + private doInitialize(): Promise { const useInMemoryStorage = this.storagePath === SQLiteStorageDatabase.IN_MEMORY_PATH; let globalStorageExists: Promise; @@ -131,14 +140,7 @@ export class StorageMainService extends Disposable implements IStorageMainServic this._register(this.storage.onDidChangeStorage(key => this._onDidChangeStorage.fire({ key }))); - mark('main:willInitGlobalStorage'); return this.storage.init().then(() => { - mark('main:didInitGlobalStorage'); - }, error => { - mark('main:didInitGlobalStorage'); - - return Promise.reject(error); - }).then(() => { // Migrate storage if this is the first start and we are not using in-memory let migrationPromise: Promise; diff --git a/src/vs/platform/storage/node/storageService.ts b/src/vs/platform/storage/node/storageService.ts index 6b232493c2c..aa8bc0755b8 100644 --- a/src/vs/platform/storage/node/storageService.ts +++ b/src/vs/platform/storage/node/storageService.ts @@ -36,6 +36,8 @@ export class StorageService extends Disposable implements IStorageService { private workspaceStorage: IStorage; private workspaceStorageListener: IDisposable; + private initializePromise: Promise; + constructor( globalStorageDatabase: IStorageDatabase, @ILogService private readonly logService: ILogService, @@ -53,6 +55,14 @@ export class StorageService extends Disposable implements IStorageService { } initialize(payload: IWorkspaceInitializationPayload): Promise { + if (!this.initializePromise) { + this.initializePromise = this.doInitialize(payload); + } + + return this.initializePromise; + } + + private doInitialize(payload: IWorkspaceInitializationPayload): Promise { return Promise.all([ this.initializeGlobalStorage(), this.initializeWorkspaceStorage(payload) @@ -227,7 +237,7 @@ export class StorageService extends Disposable implements IStorageService { workspaceItemsParsed.set(key, safeParse(value)); }); - console.group(`Storage: Global (integrity: ${result[2]}, load: ${getDuration('main:willInitGlobalStorage', 'main:didInitGlobalStorage')}, path: ${this.environmentService.globalStorageHome})`); + console.group(`Storage: Global (integrity: ${result[2]}, path: ${this.environmentService.globalStorageHome})`); let globalValues: { key: string, value: string }[] = []; globalItems.forEach((value, key) => { globalValues.push({ key, value }); diff --git a/src/vs/platform/telemetry/common/telemetryUtils.ts b/src/vs/platform/telemetry/common/telemetryUtils.ts index fe6540c6291..b5a1007a599 100644 --- a/src/vs/platform/telemetry/common/telemetryUtils.ts +++ b/src/vs/platform/telemetry/common/telemetryUtils.ts @@ -95,6 +95,7 @@ const configurationValueWhitelist = [ 'editor.rulers', 'editor.wordSeparators', 'editor.tabSize', + 'editor.indentSize', 'editor.insertSpaces', 'editor.detectIndentation', 'editor.roundedSelection', diff --git a/src/vs/platform/telemetry/node/workbenchCommonProperties.ts b/src/vs/platform/telemetry/node/workbenchCommonProperties.ts index 1a2e119c820..dcb69546f15 100644 --- a/src/vs/platform/telemetry/node/workbenchCommonProperties.ts +++ b/src/vs/platform/telemetry/node/workbenchCommonProperties.ts @@ -6,12 +6,15 @@ import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; import { resolveCommonProperties } from 'vs/platform/telemetry/node/commonProperties'; +export const instanceStorageKey = 'telemetry.instanceId'; +export const currentSessionDateStorageKey = 'telemetry.currentSessionDate'; +export const firstSessionDateStorageKey = 'telemetry.firstSessionDate'; export const lastSessionDateStorageKey = 'telemetry.lastSessionDate'; export function resolveWorkbenchCommonProperties(storageService: IStorageService, commit: string, version: string, machineId: string, installSourcePath: string): Promise<{ [name: string]: string | undefined }> { return resolveCommonProperties(commit, version, machineId, installSourcePath).then(result => { - const instanceId = storageService.get('telemetry.instanceId', StorageScope.GLOBAL)!; - const firstSessionDate = storageService.get('telemetry.firstSessionDate', StorageScope.GLOBAL)!; + const instanceId = storageService.get(instanceStorageKey, StorageScope.GLOBAL)!; + const firstSessionDate = storageService.get(firstSessionDateStorageKey, StorageScope.GLOBAL)!; const lastSessionDate = storageService.get(lastSessionDateStorageKey, StorageScope.GLOBAL)!; // __GDPR__COMMON__ "common.version.shell" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" } diff --git a/src/vs/platform/windows/common/windows.ts b/src/vs/platform/windows/common/windows.ts index 7447410922f..e28ca55477a 100644 --- a/src/vs/platform/windows/common/windows.ts +++ b/src/vs/platform/windows/common/windows.ts @@ -6,7 +6,7 @@ import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { Event } from 'vs/base/common/event'; import { ITelemetryData } from 'vs/platform/telemetry/common/telemetry'; -import { IProcessEnvironment, isMacintosh } from 'vs/base/common/platform'; +import { IProcessEnvironment, isMacintosh, isLinux } from 'vs/base/common/platform'; import { ParsedArgs, IEnvironmentService } from 'vs/platform/environment/common/environment'; import { IWorkspaceIdentifier, ISingleFolderWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces'; import { IRecentlyOpened } from 'vs/platform/history/common/history'; @@ -277,12 +277,12 @@ export function getTitleBarStyle(configurationService: IConfigurationService, en } const style = configuration.titleBarStyle; - if (style === 'native') { - return 'native'; + if (style === 'native' || style === 'custom') { + return style; } } - return 'custom'; // default to custom on all OS + return isLinux ? 'native' : 'custom'; // default to custom on all macOS and Windows } export const enum OpenContext { diff --git a/src/vs/platform/workspaces/common/workspaces.ts b/src/vs/platform/workspaces/common/workspaces.ts index c704c3706e9..5271b8bae45 100644 --- a/src/vs/platform/workspaces/common/workspaces.ts +++ b/src/vs/platform/workspaces/common/workspaces.ts @@ -8,14 +8,14 @@ import { localize } from 'vs/nls'; import { Event } from 'vs/base/common/event'; import { IWorkspaceFolder, IWorkspace } from 'vs/platform/workspace/common/workspace'; import { URI, UriComponents } from 'vs/base/common/uri'; -import { isEqualOrParent, normalizeWithSlashes } from 'vs/base/common/extpath'; import { isWindows, isLinux, isMacintosh } from 'vs/base/common/platform'; -import { isAbsolute, relative, posix, resolve, extname } from 'vs/base/common/path'; -import { normalizeDriveLetter } from 'vs/base/common/labels'; -import { originalFSPath, dirname } from 'vs/base/common/resources'; -import { Schemas } from 'vs/base/common/network'; +import { extname } from 'vs/base/common/path'; +import { dirname, resolvePath, isEqualAuthority, isEqualOrParent, relativePath } from 'vs/base/common/resources'; import * as jsonEdit from 'vs/base/common/jsonEdit'; import * as json from 'vs/base/common/json'; +import { Schemas } from 'vs/base/common/network'; +import { normalizeDriveLetter } from 'vs/base/common/labels'; +import { toSlashes } from 'vs/base/common/extpath'; export const IWorkspacesMainService = createDecorator('workspacesMainService'); export const IWorkspacesService = createDecorator('workspacesService'); @@ -156,44 +156,48 @@ export function hasWorkspaceFileExtension(path: string) { const SLASH = '/'; /** - * Given the absolute path to a folder, massage it in a way that it fits - * into an existing set of workspace folders of a workspace. + * Given a folder URI and the workspace config folder, computes the IStoredWorkspaceFolder using +* a relative or absolute path or a uri. + * Undefined is returned if the folderURI and the targetConfigFolderURI don't have the same schema or authority * - * @param absoluteFolderPath the absolute path of a workspace folder - * @param targetConfigFolder the folder where the workspace is living in - * @param existingFolders a set of existing folders of the workspace + * @param folderURI a workspace folder + * @param folderName a workspace name + * @param targetConfigFolderURI the folder where the workspace is living in + * @param useSlashForPath if set, use forward slashes for file paths on windows */ -export function massageFolderPathForWorkspace(absoluteFolderPath: string, targetConfigFolderURI: URI, existingFolders: IStoredWorkspaceFolder[]): string { +export function getStoredWorkspaceFolder(folderURI: URI, folderName: string | undefined, targetConfigFolderURI: URI, useSlashForPath = !isWindows): IStoredWorkspaceFolder { - if (targetConfigFolderURI.scheme === Schemas.file) { - const targetFolderPath = originalFSPath(targetConfigFolderURI); - // Convert path to relative path if the target config folder - // is a parent of the path. - if (isEqualOrParent(absoluteFolderPath, targetFolderPath, !isLinux)) { - absoluteFolderPath = relative(targetFolderPath, absoluteFolderPath) || '.'; - } - - // Windows gets special treatment: - // - normalize all paths to get nice casing of drive letters - // - convert to slashes if we want to use slashes for paths - if (isWindows) { - if (isAbsolute(absoluteFolderPath)) { - if (shouldUseSlashForPath(existingFolders)) { - absoluteFolderPath = normalizeWithSlashes(absoluteFolderPath /* do not use OS path separator */); - } - - absoluteFolderPath = normalizeDriveLetter(absoluteFolderPath); - } else if (shouldUseSlashForPath(existingFolders)) { - absoluteFolderPath = absoluteFolderPath.replace(/[\\]/g, SLASH); - } - } - } else { - if (isEqualOrParent(absoluteFolderPath, targetConfigFolderURI.path)) { - absoluteFolderPath = posix.relative(absoluteFolderPath, targetConfigFolderURI.path) || '.'; - } + if (folderURI.scheme !== targetConfigFolderURI.scheme || !isEqualAuthority(folderURI.authority, targetConfigFolderURI.authority)) { + return { name: folderName, uri: folderURI.toString(true) }; } - return absoluteFolderPath; + let folderPath: string | undefined; + if (isEqualOrParent(folderURI, targetConfigFolderURI)) { + // use relative path + folderPath = relativePath(targetConfigFolderURI, folderURI) || '.'; // always uses forward slashes + if (isWindows && folderURI.scheme === Schemas.file && !useSlashForPath) { + // Windows gets special treatment: + // - use backslahes unless slash is used by other existing folders + folderPath = folderPath.replace(/\//g, '\\'); + } + } else { + // use absolute path + if (folderURI.scheme === Schemas.file) { + folderPath = folderURI.fsPath; + if (isWindows) { + // Windows gets special treatment: + // - normalize all paths to get nice casing of drive letters + // - use backslahes unless slash is used by other existing folders + folderPath = normalizeDriveLetter(folderPath); + if (useSlashForPath) { + folderPath = toSlashes(folderPath); + } + } + } else { + folderPath = folderURI.path; + } + } + return { name: folderName, path: folderPath }; } /** @@ -203,27 +207,24 @@ export function massageFolderPathForWorkspace(absoluteFolderPath: string, target export function rewriteWorkspaceFileForNewLocation(rawWorkspaceContents: string, configPathURI: URI, targetConfigPathURI: URI) { let storedWorkspace = doParseStoredWorkspace(configPathURI, rawWorkspaceContents); - const sourceConfigFolder = dirname(configPathURI)!; - const targetConfigFolder = dirname(targetConfigPathURI)!; + const sourceConfigFolder = dirname(configPathURI); + const targetConfigFolder = dirname(targetConfigPathURI); + + const rewrittenFolders: IStoredWorkspaceFolder[] = []; + const slashForPath = useSlashForPath(storedWorkspace.folders); // Rewrite absolute paths to relative paths if the target workspace folder // is a parent of the location of the workspace file itself. Otherwise keep // using absolute paths. for (const folder of storedWorkspace.folders) { - if (isRawFileWorkspaceFolder(folder)) { - if (sourceConfigFolder.scheme === Schemas.file) { - if (!isAbsolute(folder.path)) { - folder.path = resolve(originalFSPath(sourceConfigFolder), folder.path); // relative paths get resolved against the workspace location - } - folder.path = massageFolderPathForWorkspace(folder.path, targetConfigFolder, storedWorkspace.folders); - } - } + let folderURI = isRawFileWorkspaceFolder(folder) ? resolvePath(sourceConfigFolder, folder.path) : URI.parse(folder.uri); + rewrittenFolders.push(getStoredWorkspaceFolder(folderURI, folder.name, targetConfigFolder, slashForPath)); } // Preserve as much of the existing workspace as possible by using jsonEdit // and only changing the folders portion. let newRawWorkspaceContents = rawWorkspaceContents; - const edits = jsonEdit.setProperty(rawWorkspaceContents, ['folders'], storedWorkspace.folders, { insertSpaces: false, tabSize: 4, eol: (isLinux || isMacintosh) ? '\n' : '\r\n' }); + const edits = jsonEdit.setProperty(rawWorkspaceContents, ['folders'], rewrittenFolders, { insertSpaces: false, tabSize: 4, eol: (isLinux || isMacintosh) ? '\n' : '\r\n' }); edits.forEach(edit => { newRawWorkspaceContents = jsonEdit.applyEdit(rawWorkspaceContents, edit); }); @@ -248,19 +249,14 @@ function doParseStoredWorkspace(path: URI, contents: string): IStoredWorkspace { return storedWorkspace; } -function shouldUseSlashForPath(storedFolders: IStoredWorkspaceFolder[]): boolean { - - // Determine which path separator to use: - // - macOS/Linux: slash - // - Windows: use slash if already used in that file - let useSlashesForPath = !isWindows; +export function useSlashForPath(storedFolders: IStoredWorkspaceFolder[]): boolean { if (isWindows) { - storedFolders.forEach(folder => { - if (isRawFileWorkspaceFolder(folder) && !useSlashesForPath && folder.path.indexOf(SLASH) >= 0) { - useSlashesForPath = true; + for (const folder of storedFolders) { + if (isRawFileWorkspaceFolder(folder) && folder.path.indexOf(SLASH) >= 0) { + return true; } - }); + } + return false; } - - return useSlashesForPath; + return true; } \ No newline at end of file diff --git a/src/vs/platform/workspaces/electron-main/workspacesMainService.ts b/src/vs/platform/workspaces/electron-main/workspacesMainService.ts index e30bea97d8c..71775405d77 100644 --- a/src/vs/platform/workspaces/electron-main/workspacesMainService.ts +++ b/src/vs/platform/workspaces/electron-main/workspacesMainService.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IWorkspacesMainService, IWorkspaceIdentifier, hasWorkspaceFileExtension, UNTITLED_WORKSPACE_NAME, IResolvedWorkspace, IStoredWorkspaceFolder, isStoredWorkspaceFolder, IWorkspaceFolderCreationData, massageFolderPathForWorkspace, rewriteWorkspaceFileForNewLocation } from 'vs/platform/workspaces/common/workspaces'; +import { IWorkspacesMainService, IWorkspaceIdentifier, hasWorkspaceFileExtension, UNTITLED_WORKSPACE_NAME, IResolvedWorkspace, IStoredWorkspaceFolder, isStoredWorkspaceFolder, IWorkspaceFolderCreationData, rewriteWorkspaceFileForNewLocation, getStoredWorkspaceFolder } from 'vs/platform/workspaces/common/workspaces'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { join, dirname } from 'vs/base/common/path'; import { mkdirp, writeFile, readFile } from 'vs/base/node/pfs'; @@ -135,32 +135,15 @@ export class WorkspacesMainService extends Disposable implements IWorkspacesMain const untitledWorkspaceConfigFolder = joinPath(this.untitledWorkspacesHome, randomId); const untitledWorkspaceConfigPath = joinPath(untitledWorkspaceConfigFolder, UNTITLED_WORKSPACE_NAME); - const storedWorkspace: IStoredWorkspace = { - folders: folders.map(folder => { - const folderResource = folder.uri; - let storedWorkspace: IStoredWorkspaceFolder; + const storedWorkspaceFolder: IStoredWorkspaceFolder[] = []; - // File URI - if (folderResource.scheme === Schemas.file) { - storedWorkspace = { path: massageFolderPathForWorkspace(originalFSPath(folderResource), untitledWorkspaceConfigFolder, []) }; - } - - // Any URI - else { - storedWorkspace = { uri: folderResource.toString(true) }; - } - - if (folder.name) { - storedWorkspace.name = folder.name; - } - - return storedWorkspace; - }) - }; + for (const folder of folders) { + storedWorkspaceFolder.push(getStoredWorkspaceFolder(folder.uri, folder.name, untitledWorkspaceConfigFolder)); + } return { workspace: this.getWorkspaceIdentifier(untitledWorkspaceConfigPath), - storedWorkspace + storedWorkspace: { folders: storedWorkspaceFolder } }; } diff --git a/src/vs/platform/workspaces/test/electron-main/workspacesMainService.test.ts b/src/vs/platform/workspaces/test/electron-main/workspacesMainService.test.ts index 0c5e7f6f111..e5476a84dfd 100644 --- a/src/vs/platform/workspaces/test/electron-main/workspacesMainService.test.ts +++ b/src/vs/platform/workspaces/test/electron-main/workspacesMainService.test.ts @@ -111,15 +111,18 @@ suite('WorkspacesMainService', () => { }); test('createUntitledWorkspace (folders as other resource URIs)', () => { - return service.createUntitledWorkspace([{ uri: URI.from({ scheme: 'myScheme', path: process.cwd() }) }, { uri: URI.from({ scheme: 'myScheme', path: os.tmpdir() }) }]).then(workspace => { + const folder1URI = URI.parse('myscheme://server/work/p/f1'); + const folder2URI = URI.parse('myscheme://server/work/o/f3'); + + return service.createUntitledWorkspace([{ uri: folder1URI }, { uri: folder2URI }]).then(workspace => { assert.ok(workspace); assert.ok(fs.existsSync(workspace.configPath.fsPath)); assert.ok(service.isUntitledWorkspace(workspace)); const ws = JSON.parse(fs.readFileSync(workspace.configPath.fsPath).toString()) as IStoredWorkspace; assert.equal(ws.folders.length, 2); - assert.equal((ws.folders[0]).uri, URI.from({ scheme: 'myScheme', path: process.cwd() }).toString(true)); - assert.equal((ws.folders[1]).uri, URI.from({ scheme: 'myScheme', path: os.tmpdir() }).toString(true)); + assert.equal((ws.folders[0]).uri, folder1URI.toString(true)); + assert.equal((ws.folders[1]).uri, folder2URI.toString(true)); assert.ok(!(ws.folders[0]).name); assert.ok(!(ws.folders[1]).name); @@ -157,15 +160,18 @@ suite('WorkspacesMainService', () => { }); test('createUntitledWorkspaceSync (folders as other resource URIs)', () => { - const workspace = service.createUntitledWorkspaceSync([{ uri: URI.from({ scheme: 'myScheme', path: process.cwd() }) }, { uri: URI.from({ scheme: 'myScheme', path: os.tmpdir() }) }]); + const folder1URI = URI.parse('myscheme://server/work/p/f1'); + const folder2URI = URI.parse('myscheme://server/work/o/f3'); + + const workspace = service.createUntitledWorkspaceSync([{ uri: folder1URI }, { uri: folder2URI }]); assert.ok(workspace); assert.ok(fs.existsSync(workspace.configPath.fsPath)); assert.ok(service.isUntitledWorkspace(workspace)); const ws = JSON.parse(fs.readFileSync(workspace.configPath.fsPath).toString()) as IStoredWorkspace; assert.equal(ws.folders.length, 2); - assert.equal((ws.folders[0]).uri, URI.from({ scheme: 'myScheme', path: process.cwd() }).toString(true)); - assert.equal((ws.folders[1]).uri, URI.from({ scheme: 'myScheme', path: os.tmpdir() }).toString(true)); + assert.equal((ws.folders[0]).uri, folder1URI.toString(true)); + assert.equal((ws.folders[1]).uri, folder2URI.toString(true)); assert.ok(!(ws.folders[0]).name); assert.ok(!(ws.folders[1]).name); @@ -262,7 +268,7 @@ suite('WorkspacesMainService', () => { assert.equal(ws.folders.length, 3); assertPathEquals((ws.folders[0]).path, process.cwd()); // absolute path because outside of tmpdir assertPathEquals((ws.folders[1]).path, '.'); // relative path because inside of tmpdir - assertPathEquals((ws.folders[2]).path, path.relative(path.dirname(workspaceConfigPath), path.join(os.tmpdir(), 'somefolder'))); // relative + assertPathEquals((ws.folders[2]).path, 'somefolder'); // relative extfs.delSync(workspaceConfigPath); extfs.delSync(newWorkspaceConfigPath); diff --git a/src/vs/vscode.d.ts b/src/vs/vscode.d.ts index 978c31dd58c..b5a11afc39e 100644 --- a/src/vs/vscode.d.ts +++ b/src/vs/vscode.d.ts @@ -641,13 +641,22 @@ declare module 'vscode' { /** * The size in spaces a tab takes. This is used for two purposes: * - the rendering width of a tab character; - * - the number of spaces to insert when [insertSpaces](#TextEditorOptions.insertSpaces) is true. + * - the number of spaces to insert when [insertSpaces](#TextEditorOptions.insertSpaces) is true + * and `indentSize` is set to `"tab"`. * * When getting a text editor's options, this property will always be a number (resolved). * When setting a text editor's options, this property is optional and it can be a number or `"auto"`. */ tabSize?: number | string; + /** + * The number of spaces to insert when [insertSpaces](#TextEditorOptions.insertSpaces) is true. + * + * When getting a text editor's options, this property will always be a number (resolved). + * When setting a text editor's options, this property is optional and it can be a number or `"tabSize"`. + */ + indentSize?: number | string; + /** * When pressing Tab insert [n](#TextEditorOptions.tabSize) spaces. * When getting a text editor's options, this property will always be a boolean (resolved). diff --git a/src/vs/workbench/api/electron-browser/mainThreadConfiguration.ts b/src/vs/workbench/api/electron-browser/mainThreadConfiguration.ts index a219ce3dca3..495b36864ad 100644 --- a/src/vs/workbench/api/electron-browser/mainThreadConfiguration.ts +++ b/src/vs/workbench/api/electron-browser/mainThreadConfiguration.ts @@ -46,12 +46,12 @@ export class MainThreadConfiguration implements MainThreadConfigurationShape { this._configurationListener.dispose(); } - $updateConfigurationOption(target: ConfigurationTarget | null, key: string, value: any, resourceUriComponenets: UriComponents | null): Promise { + $updateConfigurationOption(target: ConfigurationTarget | null, key: string, value: any, resourceUriComponenets: UriComponents | undefined): Promise { const resource = resourceUriComponenets ? URI.revive(resourceUriComponenets) : null; return this.writeConfiguration(target, key, value, resource); } - $removeConfigurationOption(target: ConfigurationTarget | null, key: string, resourceUriComponenets: UriComponents | null): Promise { + $removeConfigurationOption(target: ConfigurationTarget | null, key: string, resourceUriComponenets: UriComponents | undefined): Promise { const resource = resourceUriComponenets ? URI.revive(resourceUriComponenets) : null; return this.writeConfiguration(target, key, undefined, resource); } diff --git a/src/vs/workbench/api/electron-browser/mainThreadEditor.ts b/src/vs/workbench/api/electron-browser/mainThreadEditor.ts index 672b53ed6b9..30639a78a94 100644 --- a/src/vs/workbench/api/electron-browser/mainThreadEditor.ts +++ b/src/vs/workbench/api/electron-browser/mainThreadEditor.ts @@ -84,6 +84,7 @@ export class MainThreadTextEditorProperties { return { insertSpaces: modelOptions.insertSpaces, tabSize: modelOptions.tabSize, + indentSize: modelOptions.indentSize, cursorStyle: cursorStyle, lineNumbers: lineNumbers }; @@ -166,6 +167,7 @@ export class MainThreadTextEditorProperties { } return ( a.tabSize === b.tabSize + && a.indentSize === b.indentSize && a.insertSpaces === b.insertSpaces && a.cursorStyle === b.cursorStyle && a.lineNumbers === b.lineNumbers @@ -321,10 +323,10 @@ export class MainThreadTextEditor { } private _setIndentConfiguration(newConfiguration: ITextEditorConfigurationUpdate): void { + let creationOpts = this._modelService.getCreationOptions(this._model.getLanguageIdentifier().language, this._model.uri, this._model.isForSimpleWidget); + if (newConfiguration.tabSize === 'auto' || newConfiguration.insertSpaces === 'auto') { // one of the options was set to 'auto' => detect indentation - - let creationOpts = this._modelService.getCreationOptions(this._model.getLanguageIdentifier().language, this._model.uri, this._model.isForSimpleWidget); let insertSpaces = creationOpts.insertSpaces; let tabSize = creationOpts.tabSize; @@ -347,6 +349,13 @@ export class MainThreadTextEditor { if (typeof newConfiguration.tabSize !== 'undefined') { newOpts.tabSize = newConfiguration.tabSize; } + if (typeof newConfiguration.indentSize !== 'undefined') { + if (newConfiguration.indentSize === 'tabSize') { + newOpts.indentSize = newOpts.tabSize || creationOpts.tabSize; + } else { + newOpts.indentSize = newConfiguration.indentSize; + } + } this._model.updateOptions(newOpts); } diff --git a/src/vs/workbench/api/node/extHost.protocol.ts b/src/vs/workbench/api/node/extHost.protocol.ts index 806dd35f10b..28e71142d2e 100644 --- a/src/vs/workbench/api/node/extHost.protocol.ts +++ b/src/vs/workbench/api/node/extHost.protocol.ts @@ -127,8 +127,8 @@ export interface MainThreadCommentsShape extends IDisposable { } export interface MainThreadConfigurationShape extends IDisposable { - $updateConfigurationOption(target: ConfigurationTarget, key: string, value: any, resource: UriComponents): Promise; - $removeConfigurationOption(target: ConfigurationTarget, key: string, resource: UriComponents): Promise; + $updateConfigurationOption(target: ConfigurationTarget | null, key: string, value: any, resource: UriComponents | undefined): Promise; + $removeConfigurationOption(target: ConfigurationTarget | null, key: string, resource: UriComponents | undefined): Promise; } export interface MainThreadDiagnosticsShape extends IDisposable { @@ -176,6 +176,7 @@ export interface MainThreadDocumentsShape extends IDisposable { export interface ITextEditorConfigurationUpdate { tabSize?: number | 'auto'; + indentSize?: number | 'tabSize'; insertSpaces?: boolean | 'auto'; cursorStyle?: TextEditorCursorStyle; lineNumbers?: TextEditorLineNumbersStyle; @@ -183,6 +184,7 @@ export interface ITextEditorConfigurationUpdate { export interface IResolvedTextEditorConfiguration { tabSize: number; + indentSize: number; insertSpaces: boolean; cursorStyle: TextEditorCursorStyle; lineNumbers: TextEditorLineNumbersStyle; diff --git a/src/vs/workbench/api/node/extHostConfiguration.ts b/src/vs/workbench/api/node/extHostConfiguration.ts index 88604967795..be43259afaf 100644 --- a/src/vs/workbench/api/node/extHostConfiguration.ts +++ b/src/vs/workbench/api/node/extHostConfiguration.ts @@ -64,7 +64,7 @@ export class ExtHostConfiguration implements ExtHostConfigurationShape { } $acceptConfigurationChanged(data: IConfigurationInitData, eventData: IWorkspaceConfigurationChangeEventData): void { - this._actual.$acceptConfigurationChanged(data, eventData); + this.getConfigProvider().then(provider => provider.$acceptConfigurationChanged(data, eventData)); } } @@ -129,7 +129,7 @@ export class ExtHostConfigProvider { } else { let clonedConfig = undefined; const cloneOnWriteProxy = (target: any, accessor: string): any => { - let clonedTarget = undefined; + let clonedTarget: any | undefined = undefined; const cloneTarget = () => { clonedConfig = clonedConfig ? clonedConfig : deepClone(config); clonedTarget = clonedTarget ? clonedTarget : lookUp(clonedConfig, accessor); @@ -153,17 +153,23 @@ export class ExtHostConfigProvider { }, set: (_target: any, property: string, value: any) => { cloneTarget(); - clonedTarget[property] = value; + if (clonedTarget) { + clonedTarget[property] = value; + } return true; }, deleteProperty: (_target: any, property: string) => { cloneTarget(); - delete clonedTarget[property]; + if (clonedTarget) { + delete clonedTarget[property]; + } return true; }, defineProperty: (_target: any, property: string, descriptor: any) => { cloneTarget(); - Object.defineProperty(clonedTarget, property, descriptor); + if (clonedTarget) { + Object.defineProperty(clonedTarget, property, descriptor); + } return true; } }) : target; @@ -181,7 +187,7 @@ export class ExtHostConfigProvider { return this._proxy.$removeConfigurationOption(target, key, resource); } }, - inspect: (key: string): ConfigurationInspect => { + inspect: (key: string): ConfigurationInspect | undefined => { key = section ? `${section}.${key}` : key; const config = deepClone(this._configuration.inspect(key, { resource }, this._extHostWorkspace.workspace)); if (config) { @@ -220,7 +226,7 @@ export class ExtHostConfigProvider { return readonlyProxy(result); } - private _validateConfigurationAccess(key: string, resource: URI | undefined, extensionId: ExtensionIdentifier): void { + private _validateConfigurationAccess(key: string, resource: URI | undefined, extensionId?: ExtensionIdentifier): void { const scope = OVERRIDE_PROPERTY_PATTERN.test(key) ? ConfigurationScope.RESOURCE : this._configurationScopes[key]; const extensionIdText = extensionId ? `[${extensionId.value}] ` : ''; if (ConfigurationScope.RESOURCE === scope) { diff --git a/src/vs/workbench/api/node/extHostLogService.ts b/src/vs/workbench/api/node/extHostLogService.ts index 85919c9d034..425a2329a6a 100644 --- a/src/vs/workbench/api/node/extHostLogService.ts +++ b/src/vs/workbench/api/node/extHostLogService.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { join } from 'vs/base/common/extpath'; +import { join } from 'vs/base/common/path'; import { ILogService, DelegatedLogService, LogLevel } from 'vs/platform/log/common/log'; import { createSpdLogService } from 'vs/platform/log/node/spdlogService'; import { ExtHostLogServiceShape } from 'vs/workbench/api/node/extHost.protocol'; @@ -11,7 +11,6 @@ import { ExtensionHostLogFileName } from 'vs/workbench/services/extensions/commo import { URI } from 'vs/base/common/uri'; import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; - export class ExtHostLogService extends DelegatedLogService implements ILogService, ExtHostLogServiceShape { private _logsPath: string; diff --git a/src/vs/workbench/api/node/extHostTextEditor.ts b/src/vs/workbench/api/node/extHostTextEditor.ts index 4c26b539193..67ddc6361cd 100644 --- a/src/vs/workbench/api/node/extHostTextEditor.ts +++ b/src/vs/workbench/api/node/extHostTextEditor.ts @@ -142,6 +142,7 @@ export class ExtHostTextEditorOptions implements vscode.TextEditorOptions { private _id: string; private _tabSize: number; + private _indentSize: number; private _insertSpaces: boolean; private _cursorStyle: TextEditorCursorStyle; private _lineNumbers: TextEditorLineNumbersStyle; @@ -154,6 +155,7 @@ export class ExtHostTextEditorOptions implements vscode.TextEditorOptions { public _accept(source: IResolvedTextEditorConfiguration): void { this._tabSize = source.tabSize; + this._indentSize = source.indentSize; this._insertSpaces = source.insertSpaces; this._cursorStyle = source.cursorStyle; this._lineNumbers = source.lineNumbers; @@ -200,6 +202,47 @@ export class ExtHostTextEditorOptions implements vscode.TextEditorOptions { })); } + public get indentSize(): number | string { + return this._indentSize; + } + + private _validateIndentSize(value: number | string): number | 'tabSize' | null { + if (value === 'tabSize') { + return 'tabSize'; + } + if (typeof value === 'number') { + let r = Math.floor(value); + return (r > 0 ? r : null); + } + if (typeof value === 'string') { + let r = parseInt(value, 10); + if (isNaN(r)) { + return null; + } + return (r > 0 ? r : null); + } + return null; + } + + public set indentSize(value: number | string) { + let indentSize = this._validateIndentSize(value); + if (indentSize === null) { + // ignore invalid call + return; + } + if (typeof indentSize === 'number') { + if (this._indentSize === indentSize) { + // nothing to do + return; + } + // reflect the new indentSize value immediately + this._indentSize = indentSize; + } + warnOnError(this._proxy.$trySetOptions(this._id, { + indentSize: indentSize + })); + } + public get insertSpaces(): boolean | string { return this._insertSpaces; } @@ -273,6 +316,19 @@ export class ExtHostTextEditorOptions implements vscode.TextEditorOptions { } } + if (typeof newOptions.indentSize !== 'undefined') { + let indentSize = this._validateIndentSize(newOptions.indentSize); + if (indentSize === 'tabSize') { + hasUpdate = true; + bulkConfigurationUpdate.indentSize = indentSize; + } else if (typeof indentSize === 'number' && this._indentSize !== indentSize) { + // reflect the new indentSize value immediately + this._indentSize = indentSize; + hasUpdate = true; + bulkConfigurationUpdate.indentSize = indentSize; + } + } + if (typeof newOptions.insertSpaces !== 'undefined') { let insertSpaces = this._validateInsertSpaces(newOptions.insertSpaces); if (insertSpaces === 'auto') { diff --git a/src/vs/workbench/api/node/extHostTypes.ts b/src/vs/workbench/api/node/extHostTypes.ts index c43279bc6cb..6645557f4d0 100644 --- a/src/vs/workbench/api/node/extHostTypes.ts +++ b/src/vs/workbench/api/node/extHostTypes.ts @@ -4,7 +4,6 @@ *--------------------------------------------------------------------------------------------*/ import * as crypto from 'crypto'; -import { relative } from 'vs/base/common/path'; import { coalesce, equals } from 'vs/base/common/arrays'; import { illegalArgument } from 'vs/base/common/errors'; import { IRelativePattern } from 'vs/base/common/glob'; @@ -2066,10 +2065,6 @@ export class RelativePattern implements IRelativePattern { this.pattern = pattern; } - - public pathToRelative(from: string, to: string): string { - return relative(from, to); - } } @es5ClassCompat diff --git a/src/vs/workbench/api/node/extHostWorkspace.ts b/src/vs/workbench/api/node/extHostWorkspace.ts index a0fec19ef9c..4cd66f5e7c6 100644 --- a/src/vs/workbench/api/node/extHostWorkspace.ts +++ b/src/vs/workbench/api/node/extHostWorkspace.ts @@ -56,7 +56,7 @@ interface MutableWorkspaceFolder extends vscode.WorkspaceFolder { class ExtHostWorkspaceImpl extends Workspace { - static toExtHostWorkspace(data: IWorkspaceData, previousConfirmedWorkspace?: ExtHostWorkspaceImpl, previousUnconfirmedWorkspace?: ExtHostWorkspaceImpl): { workspace: ExtHostWorkspaceImpl | null, added: vscode.WorkspaceFolder[], removed: vscode.WorkspaceFolder[] } { + static toExtHostWorkspace(data: IWorkspaceData | null, previousConfirmedWorkspace?: ExtHostWorkspaceImpl, previousUnconfirmedWorkspace?: ExtHostWorkspaceImpl): { workspace: ExtHostWorkspaceImpl | null, added: vscode.WorkspaceFolder[], removed: vscode.WorkspaceFolder[] } { if (!data) { return { workspace: null, added: [], removed: [] }; } @@ -198,7 +198,7 @@ export class ExtHostWorkspaceProvider { constructor( mainContext: IMainContext, - data: IWorkspaceData, + data: IWorkspaceData | null, private _logService: ILogService, private _requestIdProvider: Counter ) { diff --git a/src/vs/workbench/browser/media/style.css b/src/vs/workbench/browser/media/style.css index 81d4f95693c..8848f027b9d 100644 --- a/src/vs/workbench/browser/media/style.css +++ b/src/vs/workbench/browser/media/style.css @@ -23,6 +23,10 @@ .monaco-workbench.linux:lang(ja) { font-family: "Ubuntu", "Droid Sans", "Source Han Sans J", "Source Han Sans JP", "Source Han Sans", sans-serif; } .monaco-workbench.linux:lang(ko) { font-family: "Ubuntu", "Droid Sans", "Source Han Sans K", "Source Han Sans JR", "Source Han Sans", "UnDotum", "FBaekmuk Gulim", sans-serif; } +.monaco-workbench.mac { --monaco-monospace-font: Monaco, Menlo, Inconsolata, "Courier New", monospace; } +.monaco-workbench.windows { --monaco-monospace-font: Consolas, Inconsolata, "Courier New", monospace; } +.monaco-workbench.linux { --monaco-monospace-font: "Droid Sans Mono", Inconsolata, "Courier New", monospace, "Droid Sans Fallback"; } + /* Global Styles */ body { diff --git a/src/vs/workbench/browser/parts/editor/breadcrumbsPicker.ts b/src/vs/workbench/browser/parts/editor/breadcrumbsPicker.ts index 8729cb817d1..8d18052b55a 100644 --- a/src/vs/workbench/browser/parts/editor/breadcrumbsPicker.ts +++ b/src/vs/workbench/browser/parts/editor/breadcrumbsPicker.ts @@ -11,7 +11,7 @@ import { Emitter, Event } from 'vs/base/common/event'; import { createMatches, FuzzyScore } from 'vs/base/common/filters'; import * as glob from 'vs/base/common/glob'; import { dispose, IDisposable } from 'vs/base/common/lifecycle'; -import { join } from 'vs/base/common/extpath'; +import { posix } from 'vs/base/common/path'; import { basename, dirname, isEqual } from 'vs/base/common/resources'; import { URI } from 'vs/base/common/uri'; import 'vs/css!./media/breadcrumbscontrol'; @@ -299,7 +299,7 @@ class FileFilter implements ITreeFilter { continue; } let patternAbs = pattern.indexOf('**/') !== 0 - ? join(folder.uri.path, pattern) + ? posix.join(folder.uri.path, pattern) : pattern; adjustedConfig[patternAbs] = excludesConfig[pattern]; diff --git a/src/vs/workbench/browser/parts/editor/editorGroupView.ts b/src/vs/workbench/browser/parts/editor/editorGroupView.ts index 4040ae62226..403dae5c587 100644 --- a/src/vs/workbench/browser/parts/editor/editorGroupView.ts +++ b/src/vs/workbench/browser/parts/editor/editorGroupView.ts @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import 'vs/css!./media/editorgroupview'; + import { EditorGroup, IEditorOpenOptions, EditorCloseEvent, ISerializedEditorGroup, isSerializedEditorGroup } from 'vs/workbench/common/editor/editorGroup'; import { EditorInput, EditorOptions, GroupIdentifier, ConfirmResult, SideBySideEditorInput, CloseDirection, IEditorCloseEvent, EditorGroupActiveEditorDirtyContext, IEditor } from 'vs/workbench/common/editor'; import { Event, Emitter, Relay } from 'vs/base/common/event'; @@ -32,7 +33,7 @@ import { EventType as TouchEventType, GestureEvent } from 'vs/base/browser/touch import { TitleControl } from 'vs/workbench/browser/parts/editor/titleControl'; import { IEditorGroupsAccessor, IEditorGroupView, IEditorPartOptionsChangeEvent, getActiveTextEditorOptions, IEditorOpeningEvent } from 'vs/workbench/browser/parts/editor/editor'; import { IUntitledEditorService } from 'vs/workbench/services/untitled/common/untitledEditorService'; -import { join } from 'vs/base/common/extpath'; +import { join } from 'vs/base/common/path'; import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { ActionRunner, IAction, Action } from 'vs/base/common/actions'; diff --git a/src/vs/workbench/browser/parts/editor/editorStatus.ts b/src/vs/workbench/browser/parts/editor/editorStatus.ts index 6d36ad6d9e7..8e27e3a23fd 100644 --- a/src/vs/workbench/browser/parts/editor/editorStatus.ts +++ b/src/vs/workbench/browser/parts/editor/editorStatus.ts @@ -655,7 +655,7 @@ export class EditorStatus implements IStatusbarItem { const modelOpts = model.getOptions(); update.indentation = ( modelOpts.insertSpaces - ? nls.localize('spacesSize', "Spaces: {0}", modelOpts.tabSize) + ? nls.localize('spacesSize', "Spaces: {0}", modelOpts.indentSize) : nls.localize({ key: 'tabSize', comment: ['Tab corresponds to the tab key'] }, "Tab Size: {0}", modelOpts.tabSize) ); } diff --git a/src/vs/workbench/browser/parts/titlebar/menubarControl.ts b/src/vs/workbench/browser/parts/titlebar/menubarControl.ts index c6712c96605..12cdbfd61d1 100644 --- a/src/vs/workbench/browser/parts/titlebar/menubarControl.ts +++ b/src/vs/workbench/browser/parts/titlebar/menubarControl.ts @@ -13,7 +13,7 @@ import { IAction, Action } from 'vs/base/common/actions'; import { Separator } from 'vs/base/browser/ui/actionbar/actionbar'; import * as DOM from 'vs/base/browser/dom'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; -import { isMacintosh, isLinux } from 'vs/base/common/platform'; +import { isMacintosh, isLinux, AccessibilitySupport } from 'vs/base/common/platform'; import { IConfigurationService, IConfigurationChangeEvent } from 'vs/platform/configuration/common/configuration'; import { Event, Emitter } from 'vs/base/common/event'; import { Disposable } from 'vs/base/common/lifecycle'; @@ -32,6 +32,8 @@ import { MenuBar } from 'vs/base/browser/ui/menu/menubar'; import { SubmenuAction } from 'vs/base/browser/ui/menu/menu'; import { attachMenuStyler } from 'vs/platform/theme/common/styler'; import { assign } from 'vs/base/common/objects'; +import { mnemonicMenuLabel, unmnemonicLabel } from 'vs/base/common/labels'; +import { getAccessibilitySupport } from 'vs/base/browser/browser'; export class MenubarControl extends Disposable { @@ -131,7 +133,9 @@ export class MenubarControl extends Disposable { this.recentlyOpened = recentlyOpened; }); - this.detectAndRecommendCustomTitlebar(); + this.notifyExistingLinuxUser(); + + this.notifyUserOfCustomMenubarAccessibility(); this.registerListeners(); } @@ -191,8 +195,8 @@ export class MenubarControl extends Disposable { this.updateMenubar(); } - if (event.affectsConfiguration('window.menuBarVisibility')) { - this.detectAndRecommendCustomTitlebar(); + if (event.affectsConfiguration('editor.accessibilitySupport')) { + this.notifyUserOfCustomMenubarAccessibility(); } } @@ -203,39 +207,70 @@ export class MenubarControl extends Disposable { }); } - private detectAndRecommendCustomTitlebar(): void { + // TODO@sbatten remove after feb19 + private notifyExistingLinuxUser(): void { if (!isLinux) { return; } - if (!this.storageService.getBoolean('menubar/electronFixRecommended', StorageScope.GLOBAL, false)) { - if (this.currentMenubarVisibility === 'hidden' || this.currentTitlebarStyleSetting === 'custom') { - // Issue will not arise for user, abort notification - return; - } - - const message = nls.localize('menubar.electronFixRecommendation', "If you experience hard to read text in the menu bar, we recommend trying out the custom title bar."); - this.notificationService.prompt(Severity.Info, message, [ - { - label: nls.localize('goToSetting', "Open Settings"), - run: () => { - return this.preferencesService.openGlobalSettings(undefined, { query: 'window.titleBarStyle' }); - } - }, - { - label: nls.localize('moreInfo', "More Info"), - run: () => { - window.open('https://go.microsoft.com/fwlink/?linkid=2038566'); - } - }, - { - label: nls.localize('neverShowAgain', "Don't Show Again"), - run: () => { - this.storageService.store('menubar/electronFixRecommended', true, StorageScope.GLOBAL); - } - } - ]); + const isNewUser = !this.storageService.get('telemetry.lastSessionDate', StorageScope.GLOBAL); + const hasBeenNotified = this.storageService.getBoolean('menubar/linuxTitlebarRevertNotified', StorageScope.GLOBAL, false); + const titleBarConfiguration = this.configurationService.inspect('window.titleBarStyle'); + const customShown = getTitleBarStyle(this.configurationService, this.environmentService) === 'custom'; + if (isNewUser || hasBeenNotified || (titleBarConfiguration && titleBarConfiguration.user) || customShown) { + return; } + + const message = nls.localize('menubar.linuxTitlebarRevertNotification', "We have updated the default title bar on Linux to use the native setting. If you prefer, you can go back to the custom setting. More information is available in our [online documentation](https://go.microsoft.com/fwlink/?linkid=2074137)."); + this.notificationService.prompt(Severity.Info, message, [ + { + label: nls.localize('goToSetting', "Open Settings"), + run: () => { + return this.preferencesService.openGlobalSettings(undefined, { query: 'window.titleBarStyle' }); + } + }, + { + label: nls.localize('neverShowAgain', "Don't Show Again"), + run: () => { + this.storageService.store('menubar/linuxTitlebarRevertNotified', true, StorageScope.GLOBAL); + } + } + ]); + + this.storageService.store('menubar/linuxTitlebarRevertNotified', true, StorageScope.GLOBAL); + } + + private notifyUserOfCustomMenubarAccessibility(): void { + if (isMacintosh) { + return; + } + + const hasBeenNotified = this.storageService.getBoolean('menubar/accessibleMenubarNotified', StorageScope.GLOBAL, false); + const usingCustomMenubar = getTitleBarStyle(this.configurationService, this.environmentService) === 'custom'; + const detected = getAccessibilitySupport() === AccessibilitySupport.Enabled; + const config = this.configurationService.getValue('editor.accessibilitySupport'); + + if (hasBeenNotified || usingCustomMenubar || !(config === 'on' || (config === 'auto' && detected))) { + return; + } + + const message = nls.localize('menubar.customTitlebarAccessibilityNotification', "Accessibility support is enabled for you. For the most accessible experience, we recommend the custom title bar style."); + this.notificationService.prompt(Severity.Info, message, [ + { + label: nls.localize('goToSetting', "Open Settings"), + run: () => { + return this.preferencesService.openGlobalSettings(undefined, { query: 'window.titleBarStyle' }); + } + }, + { + label: nls.localize('neverShowAgain', "Don't Show Again"), + run: () => { + this.storageService.store('menubar/accessibleMenubarNotified', true, StorageScope.GLOBAL); + } + } + ]); + + this.storageService.store('menubar/accessibleMenubarNotified', true, StorageScope.GLOBAL); } private registerListeners(): void { @@ -341,6 +376,8 @@ export class MenubarControl extends Disposable { typeHint = 'file'; } + label = unmnemonicLabel(label); + const ret: IAction = new Action(commandId, label, undefined, undefined, (event) => { const openInNewWindow = event && ((!isMacintosh && (event.ctrlKey || event.shiftKey)) || (isMacintosh && (event.metaKey || event.altKey))); @@ -483,10 +520,10 @@ export class MenubarControl extends Disposable { const submenu = this.menuService.createMenu(action.item.submenu, this.contextKeyService); const submenuActions: SubmenuAction[] = []; updateActions(submenu, submenuActions); - target.push(new SubmenuAction(action.label, submenuActions)); + target.push(new SubmenuAction(mnemonicMenuLabel(action.label), submenuActions)); submenu.dispose(); } else { - action.label = this.calculateActionLabel(action); + action.label = mnemonicMenuLabel(this.calculateActionLabel(action)); target.push(action); } } @@ -503,7 +540,7 @@ export class MenubarControl extends Disposable { this._register(menu.onDidChange(() => { const actions = []; updateActions(menu, actions); - this.menubar.updateMenu({ actions: actions, label: this.topLevelTitles[title] }); + this.menubar.updateMenu({ actions: actions, label: mnemonicMenuLabel(this.topLevelTitles[title]) }); })); } @@ -511,9 +548,9 @@ export class MenubarControl extends Disposable { updateActions(menu, actions); if (!firstTime) { - this.menubar.updateMenu({ actions: actions, label: this.topLevelTitles[title] }); + this.menubar.updateMenu({ actions: actions, label: mnemonicMenuLabel(this.topLevelTitles[title]) }); } else { - this.menubar.push({ actions: actions, label: this.topLevelTitles[title] }); + this.menubar.push({ actions: actions, label: mnemonicMenuLabel(this.topLevelTitles[title]) }); } } } diff --git a/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts b/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts index 16482690836..3ab509fad99 100644 --- a/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts +++ b/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts @@ -65,6 +65,7 @@ export class TitlebarPart extends Part implements ITitleService, ISerializableVi private menubarPart: MenubarControl; private menubar: HTMLElement; private resizer: HTMLElement; + private lastLayoutDimensions: Dimension; private pendingTitle: string; private representedFileName: string; @@ -206,7 +207,9 @@ export class TitlebarPart extends Part implements ITitleService, ISerializableVi } if ((isWindows || isLinux) && this.title) { - this.adjustTitleMarginToCenter(); + if (this.lastLayoutDimensions) { + this.updateLayout(this.lastLayoutDimensions); + } } } @@ -558,6 +561,8 @@ export class TitlebarPart extends Part implements ITitleService, ISerializableVi } updateLayout(dimension: Dimension): void { + this.lastLayoutDimensions = dimension; + if (getTitleBarStyle(this.configurationService, this.environmentService) === 'custom') { // Only prevent zooming behavior on macOS or when the menubar is not visible if (isMacintosh || this.configurationService.getValue('window.menuBarVisibility') === 'hidden') { diff --git a/src/vs/workbench/contrib/codeEditor/electron-browser/textMate/inspectTMScopes.css b/src/vs/workbench/contrib/codeEditor/electron-browser/textMate/inspectTMScopes.css index 40993ec8ac2..234ad747b75 100644 --- a/src/vs/workbench/contrib/codeEditor/electron-browser/textMate/inspectTMScopes.css +++ b/src/vs/workbench/contrib/codeEditor/electron-browser/textMate/inspectTMScopes.css @@ -10,7 +10,7 @@ } .tm-token { - font-family: monospace; + font-family: var(--monaco-monospace-font); } .tm-metadata-separator { @@ -29,10 +29,10 @@ } .tm-metadata-value { - font-family: monospace; + font-family: var(--monaco-monospace-font); text-align: right; } .tm-theme-selector { - font-family: monospace; + font-family: var(--monaco-monospace-font); } diff --git a/src/vs/workbench/contrib/comments/electron-browser/media/panel.css b/src/vs/workbench/contrib/comments/electron-browser/media/panel.css index 43f2d98ff6b..1633bd41c7e 100644 --- a/src/vs/workbench/contrib/comments/electron-browser/media/panel.css +++ b/src/vs/workbench/contrib/comments/electron-browser/media/panel.css @@ -42,7 +42,7 @@ } .comments-panel .comments-panel-container .tree-container .comment-container .text code { - font-family: Menlo, Monaco, Consolas, "Droid Sans Mono", "Courier New", monospace, "Droid Sans Fallback"; + font-family: var(--monaco-monospace-font); } .comments-panel .comments-panel-container .message-box-container { diff --git a/src/vs/workbench/contrib/debug/browser/baseDebugView.ts b/src/vs/workbench/contrib/debug/browser/baseDebugView.ts index 0a79cb2a500..21ebc9acef2 100644 --- a/src/vs/workbench/contrib/debug/browser/baseDebugView.ts +++ b/src/vs/workbench/contrib/debug/browser/baseDebugView.ts @@ -141,20 +141,20 @@ export abstract class AbstractExpressionsRenderer implements ITreeRenderer { - data.name.style.display = 'none'; - data.value.style.display = 'none'; - data.inputBoxContainer.style.display = 'initial'; + const enableInputBox = (expression: IExpression, options: IInputBoxOptions) => { + name.style.display = 'none'; + value.style.display = 'none'; + inputBoxContainer.style.display = 'initial'; - const inputBox = new InputBox(data.inputBoxContainer, this.contextViewService, { + const inputBox = new InputBox(inputBoxContainer, this.contextViewService, { placeholder: options.placeholder, ariaLabel: options.ariaLabel }); @@ -165,7 +165,8 @@ export abstract class AbstractExpressionsRenderer implements ITreeRenderer { if (!disposed) { @@ -174,15 +175,15 @@ export abstract class AbstractExpressionsRenderer implements ITreeRenderer { + toDispose.push(dom.addStandardDisposableListener(inputBox.inputElement, 'keydown', (e: IKeyboardEvent) => { const isEscape = e.equals(KeyCode.Escape); const isEnter = e.equals(KeyCode.Enter); if (isEscape || isEnter) { @@ -191,17 +192,17 @@ export abstract class AbstractExpressionsRenderer implements ITreeRenderer { + toDispose.push(dom.addDisposableListener(inputBox.inputElement, 'blur', () => { wrapUp(true); })); - data.toDispose.push(dom.addDisposableListener(inputBox.inputElement, 'click', e => { + toDispose.push(dom.addDisposableListener(inputBox.inputElement, 'click', e => { // Do not expand / collapse selected elements e.preventDefault(); e.stopPropagation(); })); }; - return data; + return { expression, name, value, label, enableInputBox, inputBoxContainer, toDispose }; } renderElement(node: ITreeNode, index: number, data: IExpressionTemplateData): void { diff --git a/src/vs/workbench/contrib/debug/browser/loadedScriptsView.ts b/src/vs/workbench/contrib/debug/browser/loadedScriptsView.ts index 0eb47853e0f..eace5858bcf 100644 --- a/src/vs/workbench/contrib/debug/browser/loadedScriptsView.ts +++ b/src/vs/workbench/contrib/debug/browser/loadedScriptsView.ts @@ -555,9 +555,8 @@ class LoadedScriptsRenderer implements ITreeRenderer, index: number, data: ILoadedScriptsItemTemplateData): void { diff --git a/src/vs/workbench/contrib/debug/browser/media/debug.contribution.css b/src/vs/workbench/contrib/debug/browser/media/debug.contribution.css index a33316e6efe..516d47268bf 100644 --- a/src/vs/workbench/contrib/debug/browser/media/debug.contribution.css +++ b/src/vs/workbench/contrib/debug/browser/media/debug.contribution.css @@ -139,7 +139,7 @@ .monaco-workbench .monaco-list-row .expression { overflow: hidden; text-overflow: ellipsis; - font-family: Monaco, Menlo, Consolas, "Droid Sans Mono", "Inconsolata", "Courier New", monospace, "Droid Sans Fallback"; + font-family: var(--monaco-monospace-font); } .monaco-workbench.mac .monaco-list-row .expression { diff --git a/src/vs/workbench/contrib/debug/browser/media/debugViewlet.css b/src/vs/workbench/contrib/debug/browser/media/debugViewlet.css index 90390db77d7..8eea2828a7d 100644 --- a/src/vs/workbench/contrib/debug/browser/media/debugViewlet.css +++ b/src/vs/workbench/contrib/debug/browser/media/debugViewlet.css @@ -296,7 +296,7 @@ } .debug-viewlet .debug-watch .monaco-inputbox { - font-family: Monaco, Menlo, Consolas, "Droid Sans Mono", "Inconsolata", "Courier New", monospace, "Droid Sans Fallback"; + font-family: var(--monaco-monospace-font); } .debug-viewlet .monaco-inputbox > .wrapper { diff --git a/src/vs/workbench/contrib/debug/browser/media/exceptionWidget.css b/src/vs/workbench/contrib/debug/browser/media/exceptionWidget.css index 400a9188f2e..14e674738d5 100644 --- a/src/vs/workbench/contrib/debug/browser/media/exceptionWidget.css +++ b/src/vs/workbench/contrib/debug/browser/media/exceptionWidget.css @@ -19,7 +19,7 @@ .monaco-editor .zone-widget .zone-widget-container.exception-widget .description, .monaco-editor .zone-widget .zone-widget-container.exception-widget .stack-trace { - font-family: Monaco, Menlo, Consolas, "Droid Sans Mono", "Inconsolata", "Courier New", monospace, "Droid Sans Fallback"; + font-family: var(--monaco-monospace-font); } .monaco-editor .zone-widget .zone-widget-container.exception-widget .stack-trace { diff --git a/src/vs/workbench/contrib/debug/browser/media/repl.css b/src/vs/workbench/contrib/debug/browser/media/repl.css index f1c18dce896..afb6ca59a78 100644 --- a/src/vs/workbench/contrib/debug/browser/media/repl.css +++ b/src/vs/workbench/contrib/debug/browser/media/repl.css @@ -12,7 +12,7 @@ } .repl .surveyor { - font-family: Monaco, Menlo, Consolas, "Droid Sans Mono", "Inconsolata", "Courier New", monospace, "Droid Sans Fallback"; + font-family: var(--monaco-monospace-font); position: absolute; display: inline-block; width : auto; diff --git a/src/vs/workbench/contrib/debug/electron-browser/callStackView.ts b/src/vs/workbench/contrib/debug/electron-browser/callStackView.ts index 2810e1deb2b..ae804608a8c 100644 --- a/src/vs/workbench/contrib/debug/electron-browser/callStackView.ts +++ b/src/vs/workbench/contrib/debug/electron-browser/callStackView.ts @@ -365,14 +365,13 @@ class SessionsRenderer implements ITreeRenderer, index: number, data: ISessionTemplateData): void { @@ -435,16 +434,15 @@ class StackFramesRenderer implements ITreeRenderer, index: number, data: IStackFrameTemplateData): void { @@ -483,10 +481,9 @@ class ErrorsRenderer implements ITreeRenderer, index: number, data: IErrorTemplateData): void { @@ -509,10 +506,9 @@ class LoadMoreRenderer implements ITreeRenderer, index: number, data: ILabelTemplateData): void { @@ -532,10 +528,9 @@ class ShowMoreRenderer implements ITreeRenderer, index: number, data: ILabelTemplateData): void { diff --git a/src/vs/workbench/contrib/debug/electron-browser/electronDebugActions.ts b/src/vs/workbench/contrib/debug/electron-browser/electronDebugActions.ts index 6dbbe42f377..a89c54d9a0c 100644 --- a/src/vs/workbench/contrib/debug/electron-browser/electronDebugActions.ts +++ b/src/vs/workbench/contrib/debug/electron-browser/electronDebugActions.ts @@ -14,7 +14,7 @@ export class CopyValueAction extends Action { static readonly ID = 'workbench.debug.viewlet.action.copyValue'; static LABEL = nls.localize('copyValue', "Copy Value"); - constructor(id: string, label: string, private value: any, @IDebugService private readonly debugService: IDebugService) { + constructor(id: string, label: string, private value: any, private context: string, @IDebugService private readonly debugService: IDebugService) { super(id, label, 'debug-action copy-value'); this._enabled = typeof this.value === 'string' || (this.value instanceof Variable && !!this.value.evaluateName); } @@ -23,7 +23,7 @@ export class CopyValueAction extends Action { if (this.value instanceof Variable) { const frameId = this.debugService.getViewModel().focusedStackFrame.frameId; const session = this.debugService.getViewModel().focusedSession; - return session.evaluate(this.value.evaluateName, frameId).then(result => { + return session.evaluate(this.value.evaluateName, frameId, this.context).then(result => { clipboard.writeText(result.body.result); }, err => clipboard.writeText(this.value.value)); } diff --git a/src/vs/workbench/contrib/debug/electron-browser/repl.ts b/src/vs/workbench/contrib/debug/electron-browser/repl.ts index 2201c867c4a..b8c71e5aad5 100644 --- a/src/vs/workbench/contrib/debug/electron-browser/repl.ts +++ b/src/vs/workbench/contrib/debug/electron-browser/repl.ts @@ -518,7 +518,6 @@ interface ISimpleReplElementTemplateData { source: HTMLElement; getReplElementSource(): IReplElementSource; toDispose: IDisposable[]; - label: HighlightedLabel; } interface IRawObjectReplTemplateData { @@ -538,15 +537,14 @@ class ReplExpressionsRenderer implements ITreeRenderer, index: number, templateData: IExpressionTemplateData): void { @@ -639,17 +637,15 @@ class ReplRawObjectsRenderer implements ITreeRenderer, index: number, templateData: IRawObjectReplTemplateData): void { diff --git a/src/vs/workbench/contrib/debug/electron-browser/variablesView.ts b/src/vs/workbench/contrib/debug/electron-browser/variablesView.ts index 87fcd26abaa..a74a0b26659 100644 --- a/src/vs/workbench/contrib/debug/electron-browser/variablesView.ts +++ b/src/vs/workbench/contrib/debug/electron-browser/variablesView.ts @@ -138,9 +138,9 @@ export class VariablesView extends ViewletPanel { const element = e.element; if (element instanceof Variable && !!element.value) { const actions: IAction[] = []; - const variable = element; + const variable = element as Variable; actions.push(new SetValueAction(SetValueAction.ID, SetValueAction.LABEL, variable, this.debugService, this.keybindingService)); - actions.push(new CopyValueAction(CopyValueAction.ID, CopyValueAction.LABEL, variable, this.debugService)); + actions.push(new CopyValueAction(CopyValueAction.ID, CopyValueAction.LABEL, variable, 'variables', this.debugService)); actions.push(new CopyEvaluatePathAction(CopyEvaluatePathAction.ID, CopyEvaluatePathAction.LABEL, variable)); actions.push(new Separator()); actions.push(new AddToWatchExpressionsAction(AddToWatchExpressionsAction.ID, AddToWatchExpressionsAction.LABEL, variable, this.debugService, this.keybindingService)); @@ -210,11 +210,10 @@ class ScopesRenderer implements ITreeRenderer, index: number, templateData: IScopeTemplateData): void { diff --git a/src/vs/workbench/contrib/debug/electron-browser/watchExpressionsView.ts b/src/vs/workbench/contrib/debug/electron-browser/watchExpressionsView.ts index 052a012c7d3..b33ef666426 100644 --- a/src/vs/workbench/contrib/debug/electron-browser/watchExpressionsView.ts +++ b/src/vs/workbench/contrib/debug/electron-browser/watchExpressionsView.ts @@ -148,7 +148,7 @@ export class WatchExpressionsView extends ViewletPanel { actions.push(new AddWatchExpressionAction(AddWatchExpressionAction.ID, AddWatchExpressionAction.LABEL, this.debugService, this.keybindingService)); actions.push(new EditWatchExpressionAction(EditWatchExpressionAction.ID, EditWatchExpressionAction.LABEL, this.debugService, this.keybindingService)); if (!expression.hasChildren) { - actions.push(new CopyValueAction(CopyValueAction.ID, CopyValueAction.LABEL, expression.value, this.debugService)); + actions.push(new CopyValueAction(CopyValueAction.ID, CopyValueAction.LABEL, expression.value, 'watch', this.debugService)); } actions.push(new Separator()); @@ -157,9 +157,9 @@ export class WatchExpressionsView extends ViewletPanel { } else { actions.push(new AddWatchExpressionAction(AddWatchExpressionAction.ID, AddWatchExpressionAction.LABEL, this.debugService, this.keybindingService)); if (element instanceof Variable) { - const variable = element; + const variable = element as Variable; if (!variable.hasChildren) { - actions.push(new CopyValueAction(CopyValueAction.ID, CopyValueAction.LABEL, variable, this.debugService)); + actions.push(new CopyValueAction(CopyValueAction.ID, CopyValueAction.LABEL, variable, 'watch', this.debugService)); } actions.push(new Separator()); } diff --git a/src/vs/workbench/contrib/debug/node/debugAdapter.ts b/src/vs/workbench/contrib/debug/node/debugAdapter.ts index 7a5828acc53..aa7e83a4b7f 100644 --- a/src/vs/workbench/contrib/debug/node/debugAdapter.ts +++ b/src/vs/workbench/contrib/debug/node/debugAdapter.ts @@ -441,7 +441,7 @@ export class ExecutableDebugAdapter extends StreamDebugAdapter { const result: IDebuggerContribution = Object.create(null); if (contribution.runtime) { if (contribution.runtime.indexOf('./') === 0) { // TODO - result.runtime = extpath.join(extensionFolderPath, contribution.runtime); + result.runtime = extpath.joinWithSlashes(extensionFolderPath, contribution.runtime); } else { result.runtime = contribution.runtime; } @@ -451,7 +451,7 @@ export class ExecutableDebugAdapter extends StreamDebugAdapter { } if (contribution.program) { if (!path.isAbsolute(contribution.program)) { - result.program = extpath.join(extensionFolderPath, contribution.program); + result.program = extpath.joinWithSlashes(extensionFolderPath, contribution.program); } else { result.program = contribution.program; } diff --git a/src/vs/workbench/contrib/debug/node/debugger.ts b/src/vs/workbench/contrib/debug/node/debugger.ts index e97d34b077a..93237009cc0 100644 --- a/src/vs/workbench/contrib/debug/node/debugger.ts +++ b/src/vs/workbench/contrib/debug/node/debugger.ts @@ -7,6 +7,7 @@ import * as nls from 'vs/nls'; import { Client as TelemetryClient } from 'vs/base/parts/ipc/node/ipc.cp'; import * as strings from 'vs/base/common/strings'; import * as objects from 'vs/base/common/objects'; +import { isObject } from 'vs/base/common/types'; import { TelemetryAppenderClient } from 'vs/platform/telemetry/node/telemetryIpc'; import { IJSONSchema, IJSONSchemaSnippet } from 'vs/base/common/jsonSchema'; import { IWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; @@ -46,11 +47,40 @@ export class Debugger implements IDebugger { public merge(otherDebuggerContribution: IDebuggerContribution, extensionDescription: IExtensionDescription): void { + /** + * Copies all properties of source into destination. The optional parameter "overwrite" allows to control + * if existing non-structured properties on the destination should be overwritten or not. Defaults to true (overwrite). + */ + function mixin(destination: any, source: any, overwrite: boolean = true): any { + + if (!isObject(destination)) { + return source; + } + + if (isObject(source)) { + Object.keys(source).forEach(key => { + if (isObject(destination[key]) && isObject(source[key])) { + mixin(destination[key], source[key], overwrite); + } else { + if (key in destination) { + if (overwrite) { + destination[key] = source[key]; + } + } else { + destination[key] = source[key]; + } + } + }); + } + + return destination; + } + // remember all extensions that have been merged for this debugger this.mergedExtensionDescriptions.push(extensionDescription); - // merge new debugger contribution into existing contributions. - objects.mixin(this.debuggerContribution, otherDebuggerContribution, extensionDescription.isBuiltin); + // merge new debugger contribution into existing contributions (and don't overwrite values in built-in extensions) + mixin(this.debuggerContribution, otherDebuggerContribution, extensionDescription.isBuiltin); // remember the extension that is considered the "main" debugger contribution if (isDebuggerMainContribution(otherDebuggerContribution)) { diff --git a/src/vs/workbench/contrib/debug/test/node/debugger.test.ts b/src/vs/workbench/contrib/debug/test/node/debugger.test.ts index 76a855b4e21..08ec6867a17 100644 --- a/src/vs/workbench/contrib/debug/test/node/debugger.test.ts +++ b/src/vs/workbench/contrib/debug/test/node/debugger.test.ts @@ -144,7 +144,7 @@ suite('Debug - Debugger', () => { const ae = ExecutableDebugAdapter.platformAdapterExecutable([extensionDescriptor0], 'mock'); - assert.equal(ae!.command, extpath.join(extensionFolderPath, debuggerContribution.program)); + assert.equal(ae!.command, extpath.joinWithSlashes(extensionFolderPath, debuggerContribution.program)); assert.deepEqual(ae!.args, debuggerContribution.args); }); diff --git a/src/vs/workbench/contrib/extensions/common/extensions.ts b/src/vs/workbench/contrib/extensions/common/extensions.ts index 3d3ec28b199..331be4c3502 100644 --- a/src/vs/workbench/contrib/extensions/common/extensions.ts +++ b/src/vs/workbench/contrib/extensions/common/extensions.ts @@ -18,6 +18,8 @@ import { IExtensionManifest, ExtensionType } from 'vs/platform/extensions/common export const VIEWLET_ID = 'workbench.view.extensions'; export const VIEW_CONTAINER: ViewContainer = Registry.as(ViewContainerExtensions.ViewContainersRegistry).registerViewContainer(VIEWLET_ID); +export const EXTENSIONS_CONFIG = '.vscode/extensions.json'; + export interface IExtensionsViewlet extends IViewlet { search(text: string): void; } diff --git a/src/vs/workbench/contrib/extensions/electron-browser/extensionTipsService.ts b/src/vs/workbench/contrib/extensions/electron-browser/extensionTipsService.ts index 9170fbf6f62..5806f7edf30 100644 --- a/src/vs/workbench/contrib/extensions/electron-browser/extensionTipsService.ts +++ b/src/vs/workbench/contrib/extensions/electron-browser/extensionTipsService.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { localize } from 'vs/nls'; -import * as extpath from 'vs/base/common/extpath'; +import { join } from 'vs/base/common/path'; import { forEach } from 'vs/base/common/collections'; import { IDisposable, dispose, Disposable } from 'vs/base/common/lifecycle'; import { match } from 'vs/base/common/glob'; @@ -22,7 +22,7 @@ import { ShowRecommendedExtensionsAction, InstallWorkspaceRecommendedExtensionsA import Severity from 'vs/base/common/severity'; import { IWorkspaceContextService, IWorkspaceFolder, IWorkspace, IWorkspaceFoldersChangeEvent, WorkbenchState } from 'vs/platform/workspace/common/workspace'; import { IFileService } from 'vs/platform/files/common/files'; -import { IExtensionsConfiguration, ConfigurationKey, ShowRecommendationsOnlyOnDemandKey, IExtensionsViewlet, IExtensionsWorkbenchService } from 'vs/workbench/contrib/extensions/common/extensions'; +import { IExtensionsConfiguration, ConfigurationKey, ShowRecommendationsOnlyOnDemandKey, IExtensionsViewlet, IExtensionsWorkbenchService, EXTENSIONS_CONFIG } from 'vs/workbench/contrib/extensions/common/extensions'; import { IConfigurationService, ConfigurationTarget } from 'vs/platform/configuration/common/configuration'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import * as pfs from 'vs/base/node/pfs'; @@ -334,7 +334,7 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe * Parse the extensions.json files for given workspace folder and return the recommendations */ private resolveWorkspaceFolderExtensionConfig(workspaceFolder: IWorkspaceFolder): Promise { - const extensionsJsonUri = workspaceFolder.toResource(extpath.join('.vscode', 'extensions.json')); + const extensionsJsonUri = workspaceFolder.toResource(EXTENSIONS_CONFIG); return Promise.resolve(this.fileService.resolveFile(extensionsJsonUri) .then(() => this.fileService.resolveContent(extensionsJsonUri)) @@ -904,8 +904,8 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe .replace('%APPDATA%', process.env['APPDATA']!); promises.push(findExecutable(exeName, windowsPath)); } else { - promises.push(findExecutable(exeName, extpath.join('/usr/local/bin', exeName))); - promises.push(findExecutable(exeName, extpath.join(homeDir, exeName))); + promises.push(findExecutable(exeName, join('/usr/local/bin', exeName))); + promises.push(findExecutable(exeName, join(homeDir, exeName))); } }); diff --git a/src/vs/workbench/contrib/extensions/electron-browser/extensionsActions.ts b/src/vs/workbench/contrib/extensions/electron-browser/extensionsActions.ts index cbe2bb2dbb1..5c8039ed632 100644 --- a/src/vs/workbench/contrib/extensions/electron-browser/extensionsActions.ts +++ b/src/vs/workbench/contrib/extensions/electron-browser/extensionsActions.ts @@ -8,13 +8,12 @@ import { localize } from 'vs/nls'; import { IAction, Action } from 'vs/base/common/actions'; import { Throttler, Delayer } from 'vs/base/common/async'; import * as DOM from 'vs/base/browser/dom'; -import * as extpath from 'vs/base/common/extpath'; import { Event } from 'vs/base/common/event'; import * as json from 'vs/base/common/json'; import { ActionItem, Separator, IActionItemOptions } from 'vs/base/browser/ui/actionbar/actionbar'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { IDisposable, dispose, Disposable } from 'vs/base/common/lifecycle'; -import { IExtension, ExtensionState, IExtensionsWorkbenchService, VIEWLET_ID, IExtensionsViewlet, AutoUpdateConfigurationKey, IExtensionContainer } from 'vs/workbench/contrib/extensions/common/extensions'; +import { IExtension, ExtensionState, IExtensionsWorkbenchService, VIEWLET_ID, IExtensionsViewlet, AutoUpdateConfigurationKey, IExtensionContainer, EXTENSIONS_CONFIG } from 'vs/workbench/contrib/extensions/common/extensions'; import { ExtensionsConfigurationInitialContent } from 'vs/workbench/contrib/extensions/common/extensionsFileTemplate'; import { IExtensionEnablementService, IExtensionTipsService, EnablementState, ExtensionsLabel, IExtensionRecommendation, IGalleryExtension, IExtensionsConfigContent, IExtensionGalleryService, INSTALL_ERROR_MALICIOUS, INSTALL_ERROR_INCOMPATIBLE, IGalleryExtensionVersion, ILocalExtension } from 'vs/platform/extensionManagement/common/extensionManagement'; import { areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; @@ -2044,7 +2043,7 @@ export class ConfigureWorkspaceRecommendedExtensionsAction extends AbstractConfi public run(): Promise { switch (this.contextService.getWorkbenchState()) { case WorkbenchState.FOLDER: - return this.openExtensionsFile(this.contextService.getWorkspace().folders[0].toResource(extpath.join('.vscode', 'extensions.json'))); + return this.openExtensionsFile(this.contextService.getWorkspace().folders[0].toResource(EXTENSIONS_CONFIG)); case WorkbenchState.WORKSPACE: return this.openWorkspaceConfigurationFile(this.contextService.getWorkspace().configuration!); } @@ -2089,7 +2088,7 @@ export class ConfigureWorkspaceFolderRecommendedExtensionsAction extends Abstrac return Promise.resolve(pickFolderPromise) .then(workspaceFolder => { if (workspaceFolder) { - return this.openExtensionsFile(workspaceFolder.toResource(extpath.join('.vscode', 'extensions.json'))); + return this.openExtensionsFile(workspaceFolder.toResource(EXTENSIONS_CONFIG)); } return null; }); @@ -2142,7 +2141,7 @@ export class AddToWorkspaceFolderRecommendationsAction extends AbstractConfigure if (!workspaceFolder) { return Promise.resolve(); } - const configurationFile = workspaceFolder.toResource(extpath.join('.vscode', 'extensions.json')); + const configurationFile = workspaceFolder.toResource(EXTENSIONS_CONFIG); return this.getWorkspaceFolderExtensionsConfigContent(configurationFile).then(content => { const extensionIdLowerCase = extensionId.id.toLowerCase(); if (shouldRecommend) { diff --git a/src/vs/workbench/contrib/extensions/electron-browser/media/extensionEditor.css b/src/vs/workbench/contrib/extensions/electron-browser/media/extensionEditor.css index a5446fb5d46..88cd76efe63 100644 --- a/src/vs/workbench/contrib/extensions/electron-browser/media/extensionEditor.css +++ b/src/vs/workbench/contrib/extensions/electron-browser/media/extensionEditor.css @@ -276,7 +276,7 @@ } .extension-editor > .body > .content table code:not(:empty) { - font-family: Monaco, Menlo, Consolas, "Droid Sans Mono", "Inconsolata", "Courier New", monospace, "Droid Sans Fallback"; + font-family: var(--monaco-monospace-font); font-size: 90%; background-color: rgba(128, 128, 128, 0.17); border-radius: 4px; diff --git a/src/vs/workbench/contrib/feedback/electron-browser/media/feedback.css b/src/vs/workbench/contrib/feedback/electron-browser/media/feedback.css index 041ae4a8776..259665cc894 100644 --- a/src/vs/workbench/contrib/feedback/electron-browser/media/feedback.css +++ b/src/vs/workbench/contrib/feedback/electron-browser/media/feedback.css @@ -128,8 +128,7 @@ font-family: inherit; border: 1px solid transparent; } - - + .vs .monaco-workbench .feedback-form .cancel { background: url('close.svg') center center no-repeat; } diff --git a/src/vs/workbench/contrib/files/electron-browser/explorerService.ts b/src/vs/workbench/contrib/files/electron-browser/explorerService.ts index 800b5175456..aa8cdab2d36 100644 --- a/src/vs/workbench/contrib/files/electron-browser/explorerService.ts +++ b/src/vs/workbench/contrib/files/electron-browser/explorerService.ts @@ -48,7 +48,9 @@ export class ExplorerService implements IExplorerService { @IWorkspaceContextService private contextService: IWorkspaceContextService, @IClipboardService private clipboardService: IClipboardService, @IEditorService private editorService: IEditorService - ) { } + ) { + this._sortOrder = this.configurationService.getValue('explorer.sortOrder'); + } get roots(): ExplorerItem[] { return this.model.roots; diff --git a/src/vs/workbench/contrib/files/electron-browser/views/explorerView.ts b/src/vs/workbench/contrib/files/electron-browser/views/explorerView.ts index 75a80567887..67e96e37989 100644 --- a/src/vs/workbench/contrib/files/electron-browser/views/explorerView.ts +++ b/src/vs/workbench/contrib/files/electron-browser/views/explorerView.ts @@ -258,6 +258,7 @@ export class ExplorerView extends ViewletPanel { this.explorerService.select(this.getActiveFile(), reveal); } else { this.tree.setSelection([]); + this.tree.setFocus([]); } } } @@ -533,6 +534,7 @@ export class ExplorerView extends ViewletPanel { } this.tree.setFocus([fileStat]); + this.tree.setSelection([fileStat]); }); } diff --git a/src/vs/workbench/contrib/files/test/browser/fileEditorInput.test.ts b/src/vs/workbench/contrib/files/test/browser/fileEditorInput.test.ts index 208087e4cc9..b5dc3b0f3ce 100644 --- a/src/vs/workbench/contrib/files/test/browser/fileEditorInput.test.ts +++ b/src/vs/workbench/contrib/files/test/browser/fileEditorInput.test.ts @@ -5,7 +5,7 @@ import * as assert from 'assert'; import { URI } from 'vs/base/common/uri'; -import { join } from 'vs/base/common/extpath'; +import { toResource } from 'vs/base/test/common/utils'; import { FileEditorInput } from 'vs/workbench/contrib/files/common/editors/fileEditorInput'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { workbenchInstantiationService, TestTextFileService } from 'vs/workbench/test/workbenchTestServices'; @@ -17,10 +17,6 @@ import { TextFileEditorModel } from 'vs/workbench/services/textfile/common/textF import { IModelService } from 'vs/editor/common/services/modelService'; import { timeout } from 'vs/base/common/async'; -function toResource(self, path) { - return URI.file(join('C:\\', Buffer.from(self.test.fullTitle()).toString('base64'), path)); -} - class ServiceAccessor { constructor( @IEditorService public editorService: IEditorService, @@ -41,9 +37,9 @@ suite('Files - FileEditorInput', () => { }); test('Basics', function () { - let input = instantiationService.createInstance(FileEditorInput, toResource(this, '/foo/bar/file.js'), undefined); - const otherInput = instantiationService.createInstance(FileEditorInput, toResource(this, 'foo/bar/otherfile.js'), undefined); - const otherInputSame = instantiationService.createInstance(FileEditorInput, toResource(this, 'foo/bar/file.js'), undefined); + let input = instantiationService.createInstance(FileEditorInput, toResource.call(this, '/foo/bar/file.js'), undefined); + const otherInput = instantiationService.createInstance(FileEditorInput, toResource.call(this, 'foo/bar/otherfile.js'), undefined); + const otherInputSame = instantiationService.createInstance(FileEditorInput, toResource.call(this, 'foo/bar/file.js'), undefined); assert(input.matches(input)); assert(input.matches(otherInputSame)); @@ -55,13 +51,13 @@ suite('Files - FileEditorInput', () => { assert.strictEqual('file.js', input.getName()); - assert.strictEqual(toResource(this, '/foo/bar/file.js').fsPath, input.getResource().fsPath); + assert.strictEqual(toResource.call(this, '/foo/bar/file.js').fsPath, input.getResource().fsPath); assert(input.getResource() instanceof URI); - input = instantiationService.createInstance(FileEditorInput, toResource(this, '/foo/bar.html'), undefined); + input = instantiationService.createInstance(FileEditorInput, toResource.call(this, '/foo/bar.html'), undefined); - const inputToResolve: FileEditorInput = instantiationService.createInstance(FileEditorInput, toResource(this, '/foo/bar/file.js'), undefined); - const sameOtherInput: FileEditorInput = instantiationService.createInstance(FileEditorInput, toResource(this, '/foo/bar/file.js'), undefined); + const inputToResolve: FileEditorInput = instantiationService.createInstance(FileEditorInput, toResource.call(this, '/foo/bar/file.js'), undefined); + const sameOtherInput: FileEditorInput = instantiationService.createInstance(FileEditorInput, toResource.call(this, '/foo/bar/file.js'), undefined); return inputToResolve.resolve().then(resolved => { assert.ok(inputToResolve.isResolved()); @@ -100,10 +96,10 @@ suite('Files - FileEditorInput', () => { }); test('matches', function () { - const input1 = instantiationService.createInstance(FileEditorInput, toResource(this, '/foo/bar/updatefile.js'), undefined); - const input2 = instantiationService.createInstance(FileEditorInput, toResource(this, '/foo/bar/updatefile.js'), undefined); - const input3 = instantiationService.createInstance(FileEditorInput, toResource(this, '/foo/bar/other.js'), undefined); - const input2Upper = instantiationService.createInstance(FileEditorInput, toResource(this, '/foo/bar/UPDATEFILE.js'), undefined); + const input1 = instantiationService.createInstance(FileEditorInput, toResource.call(this, '/foo/bar/updatefile.js'), undefined); + const input2 = instantiationService.createInstance(FileEditorInput, toResource.call(this, '/foo/bar/updatefile.js'), undefined); + const input3 = instantiationService.createInstance(FileEditorInput, toResource.call(this, '/foo/bar/other.js'), undefined); + const input2Upper = instantiationService.createInstance(FileEditorInput, toResource.call(this, '/foo/bar/UPDATEFILE.js'), undefined); assert.strictEqual(input1.matches(null), false); assert.strictEqual(input1.matches(input1), true); @@ -114,7 +110,7 @@ suite('Files - FileEditorInput', () => { }); test('getEncoding/setEncoding', function () { - const input = instantiationService.createInstance(FileEditorInput, toResource(this, '/foo/bar/updatefile.js'), undefined); + const input = instantiationService.createInstance(FileEditorInput, toResource.call(this, '/foo/bar/updatefile.js'), undefined); input.setEncoding('utf16', EncodingMode.Encode); assert.equal(input.getEncoding(), 'utf16'); @@ -127,7 +123,7 @@ suite('Files - FileEditorInput', () => { }); test('save', function () { - const input = instantiationService.createInstance(FileEditorInput, toResource(this, '/foo/bar/updatefile.js'), undefined); + const input = instantiationService.createInstance(FileEditorInput, toResource.call(this, '/foo/bar/updatefile.js'), undefined); return input.resolve().then((resolved: TextFileEditorModel) => { resolved.textEditorModel.setValue('changed'); @@ -142,7 +138,7 @@ suite('Files - FileEditorInput', () => { }); test('revert', function () { - const input = instantiationService.createInstance(FileEditorInput, toResource(this, '/foo/bar/updatefile.js'), undefined); + const input = instantiationService.createInstance(FileEditorInput, toResource.call(this, '/foo/bar/updatefile.js'), undefined); return input.resolve().then((resolved: TextFileEditorModel) => { resolved.textEditorModel.setValue('changed'); @@ -157,7 +153,7 @@ suite('Files - FileEditorInput', () => { }); test('resolve handles binary files', function () { - const input = instantiationService.createInstance(FileEditorInput, toResource(this, '/foo/bar/updatefile.js'), undefined); + const input = instantiationService.createInstance(FileEditorInput, toResource.call(this, '/foo/bar/updatefile.js'), undefined); accessor.textFileService.setResolveTextContentErrorOnce(new FileOperationError('error', FileOperationResult.FILE_IS_BINARY)); @@ -169,7 +165,7 @@ suite('Files - FileEditorInput', () => { }); test('resolve handles too large files', function () { - const input = instantiationService.createInstance(FileEditorInput, toResource(this, '/foo/bar/updatefile.js'), undefined); + const input = instantiationService.createInstance(FileEditorInput, toResource.call(this, '/foo/bar/updatefile.js'), undefined); accessor.textFileService.setResolveTextContentErrorOnce(new FileOperationError('error', FileOperationResult.FILE_TOO_LARGE)); diff --git a/src/vs/workbench/contrib/files/test/browser/fileEditorTracker.test.ts b/src/vs/workbench/contrib/files/test/browser/fileEditorTracker.test.ts index 7b3b6c5a7f4..882f418c85f 100644 --- a/src/vs/workbench/contrib/files/test/browser/fileEditorTracker.test.ts +++ b/src/vs/workbench/contrib/files/test/browser/fileEditorTracker.test.ts @@ -5,8 +5,7 @@ import * as assert from 'assert'; import { FileEditorTracker } from 'vs/workbench/contrib/files/browser/editors/fileEditorTracker'; -import { URI } from 'vs/base/common/uri'; -import { join } from 'vs/base/common/extpath'; +import { toResource } from 'vs/base/test/common/utils'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { workbenchInstantiationService, TestTextFileService, TestFileService } from 'vs/workbench/test/workbenchTestServices'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; @@ -16,10 +15,6 @@ import { TextFileEditorModel } from 'vs/workbench/services/textfile/common/textF import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { timeout } from 'vs/base/common/async'; -function toResource(self: any, path: string) { - return URI.file(join('C:\\', Buffer.from(self.test.fullTitle()).toString('base64'), path)); -} - class ServiceAccessor { constructor( @IEditorService public editorService: IEditorService, @@ -43,7 +38,7 @@ suite('Files - FileEditorTracker', () => { test('file change event updates model', function () { const tracker = instantiationService.createInstance(FileEditorTracker); - const resource = toResource(this, '/path/index.txt'); + const resource = toResource.call(this, '/path/index.txt'); return accessor.textFileService.models.loadOrCreate(resource).then((model: TextFileEditorModel) => { model.textEditorModel.setValue('Super Good'); diff --git a/src/vs/workbench/contrib/files/test/electron-browser/explorerModel.test.ts b/src/vs/workbench/contrib/files/test/electron-browser/explorerModel.test.ts index 47ef46cfb31..cf94544bcbb 100644 --- a/src/vs/workbench/contrib/files/test/electron-browser/explorerModel.test.ts +++ b/src/vs/workbench/contrib/files/test/electron-browser/explorerModel.test.ts @@ -6,43 +6,36 @@ import * as assert from 'assert'; import { isLinux, isWindows } from 'vs/base/common/platform'; import { URI } from 'vs/base/common/uri'; -import { join } from 'vs/base/common/extpath'; +import { join } from 'vs/base/common/path'; import { validateFileName } from 'vs/workbench/contrib/files/electron-browser/fileActions'; import { ExplorerItem } from 'vs/workbench/contrib/files/common/explorerModel'; +import { toResource } from 'vs/base/test/common/utils'; -function createStat(path: string, name: string, isFolder: boolean, hasChildren: boolean, size: number, mtime: number): ExplorerItem { - return new ExplorerItem(toResource(path), null, isFolder, false, false, name, mtime); +function createStat(this: any, path: string, name: string, isFolder: boolean, hasChildren: boolean, size: number, mtime: number): ExplorerItem { + return new ExplorerItem(toResource.call(this, path), null, isFolder, false, false, name, mtime); } -function toResource(path) { - if (isWindows) { - return URI.file(join('C:\\', path)); - } else { - return URI.file(join('/home/john', path)); - } -} +suite('Files - View Model', function () { -suite('Files - View Model', () => { - - test('Properties', () => { + test('Properties', function () { const d = new Date().getTime(); - let s = createStat('/path/to/stat', 'sName', true, true, 8096, d); + let s = createStat.call(this, '/path/to/stat', 'sName', true, true, 8096, d); assert.strictEqual(s.isDirectoryResolved, false); - assert.strictEqual(s.resource.fsPath, toResource('/path/to/stat').fsPath); + assert.strictEqual(s.resource.fsPath, toResource.call(this, '/path/to/stat').fsPath); assert.strictEqual(s.name, 'sName'); assert.strictEqual(s.isDirectory, true); assert.strictEqual(s.mtime, new Date(d).getTime()); - s = createStat('/path/to/stat', 'sName', false, false, 8096, d); + s = createStat.call(this, '/path/to/stat', 'sName', false, false, 8096, d); }); test('Add and Remove Child, check for hasChild', function () { const d = new Date().getTime(); - const s = createStat('/path/to/stat', 'sName', true, false, 8096, d); + const s = createStat.call(this, '/path/to/stat', 'sName', true, false, 8096, d); - const child1 = createStat('/path/to/stat/foo', 'foo', true, false, 8096, d); - const child4 = createStat('/otherpath/to/other/otherbar.html', 'otherbar.html', false, false, 8096, d); + const child1 = createStat.call(this, '/path/to/stat/foo', 'foo', true, false, 8096, d); + const child4 = createStat.call(this, '/otherpath/to/other/otherbar.html', 'otherbar.html', false, false, 8096, d); s.addChild(child1); @@ -57,16 +50,16 @@ suite('Files - View Model', () => { // Assert that adding a child updates its path properly s.addChild(child4); - assert.strictEqual(child4.resource.fsPath, toResource('/path/to/stat/' + child4.name).fsPath); + assert.strictEqual(child4.resource.fsPath, toResource.call(this, '/path/to/stat/' + child4.name).fsPath); }); - test('Move', () => { + test('Move', function () { const d = new Date().getTime(); - const s1 = createStat('/', '/', true, false, 8096, d); - const s2 = createStat('/path', 'path', true, false, 8096, d); - const s3 = createStat('/path/to', 'to', true, false, 8096, d); - const s4 = createStat('/path/to/stat', 'stat', false, false, 8096, d); + const s1 = createStat.call(this, '/', '/', true, false, 8096, d); + const s2 = createStat.call(this, '/path', 'path', true, false, 8096, d); + const s3 = createStat.call(this, '/path/to', 'to', true, false, 8096, d); + const s4 = createStat.call(this, '/path/to/stat', 'stat', false, false, 8096, d); s1.addChild(s2); s2.addChild(s3); @@ -75,12 +68,12 @@ suite('Files - View Model', () => { s4.move(s1); // Assert the new path of the moved element - assert.strictEqual(s4.resource.fsPath, toResource('/' + s4.name).fsPath); + assert.strictEqual(s4.resource.fsPath, toResource.call(this, '/' + s4.name).fsPath); // Move a subtree with children - const leaf = createStat('/leaf', 'leaf', true, false, 8096, d); - const leafC1 = createStat('/leaf/folder', 'folder', true, false, 8096, d); - const leafCC2 = createStat('/leaf/folder/index.html', 'index.html', true, false, 8096, d); + const leaf = createStat.call(this, '/leaf', 'leaf', true, false, 8096, d); + const leafC1 = createStat.call(this, '/leaf/folder', 'folder', true, false, 8096, d); + const leafCC2 = createStat.call(this, '/leaf/folder/index.html', 'index.html', true, false, 8096, d); leaf.addChild(leafC1); leafC1.addChild(leafCC2); @@ -91,47 +84,47 @@ suite('Files - View Model', () => { assert.strictEqual(leafCC2.resource.fsPath, URI.file(leafC1.resource.fsPath + '/' + leafCC2.name).fsPath); }); - test('Rename', () => { + test('Rename', function () { const d = new Date().getTime(); - const s1 = createStat('/', '/', true, false, 8096, d); - const s2 = createStat('/path', 'path', true, false, 8096, d); - const s3 = createStat('/path/to', 'to', true, false, 8096, d); - const s4 = createStat('/path/to/stat', 'stat', true, false, 8096, d); + const s1 = createStat.call(this, '/', '/', true, false, 8096, d); + const s2 = createStat.call(this, '/path', 'path', true, false, 8096, d); + const s3 = createStat.call(this, '/path/to', 'to', true, false, 8096, d); + const s4 = createStat.call(this, '/path/to/stat', 'stat', true, false, 8096, d); s1.addChild(s2); s2.addChild(s3); s3.addChild(s4); assert.strictEqual(s1.getChild(s2.name), s2); - const s2renamed = createStat('/otherpath', 'otherpath', true, true, 8096, d); + const s2renamed = createStat.call(this, '/otherpath', 'otherpath', true, true, 8096, d); s2.rename(s2renamed); assert.strictEqual(s1.getChild(s2.name), s2); // Verify the paths have changed including children assert.strictEqual(s2.name, s2renamed.name); assert.strictEqual(s2.resource.fsPath, s2renamed.resource.fsPath); - assert.strictEqual(s3.resource.fsPath, toResource('/otherpath/to').fsPath); - assert.strictEqual(s4.resource.fsPath, toResource('/otherpath/to/stat').fsPath); + assert.strictEqual(s3.resource.fsPath, toResource.call(this, '/otherpath/to').fsPath); + assert.strictEqual(s4.resource.fsPath, toResource.call(this, '/otherpath/to/stat').fsPath); - const s4renamed = createStat('/otherpath/to/statother.js', 'statother.js', true, false, 8096, d); + const s4renamed = createStat.call(this, '/otherpath/to/statother.js', 'statother.js', true, false, 8096, d); s4.rename(s4renamed); assert.strictEqual(s3.getChild(s4.name), s4); assert.strictEqual(s4.name, s4renamed.name); assert.strictEqual(s4.resource.fsPath, s4renamed.resource.fsPath); }); - test('Find', () => { + test('Find', function () { const d = new Date().getTime(); - const s1 = createStat('/', '/', true, false, 8096, d); - const s2 = createStat('/path', 'path', true, false, 8096, d); - const s3 = createStat('/path/to', 'to', true, false, 8096, d); - const s4 = createStat('/path/to/stat', 'stat', true, false, 8096, d); - const s4Upper = createStat('/path/to/STAT', 'stat', true, false, 8096, d); + const s1 = createStat.call(this, '/', '/', true, false, 8096, d); + const s2 = createStat.call(this, '/path', 'path', true, false, 8096, d); + const s3 = createStat.call(this, '/path/to', 'to', true, false, 8096, d); + const s4 = createStat.call(this, '/path/to/stat', 'stat', true, false, 8096, d); + const s4Upper = createStat.call(this, '/path/to/STAT', 'stat', true, false, 8096, d); - const child1 = createStat('/path/to/stat/foo', 'foo', true, false, 8096, d); - const child2 = createStat('/path/to/stat/foo/bar.html', 'bar.html', false, false, 8096, d); + const child1 = createStat.call(this, '/path/to/stat/foo', 'foo', true, false, 8096, d); + const child2 = createStat.call(this, '/path/to/stat/foo/bar.html', 'bar.html', false, false, 8096, d); s1.addChild(s2); s2.addChild(s3); @@ -151,22 +144,22 @@ suite('Files - View Model', () => { assert.strictEqual(s1.find(s4Upper.resource), s4); } - assert.strictEqual(s1.find(toResource('foobar')), null); + assert.strictEqual(s1.find(toResource.call(this, 'foobar')), null); - assert.strictEqual(s1.find(toResource('/')), s1); - assert.strictEqual(s1.find(toResource('')), s1); + assert.strictEqual(s1.find(toResource.call(this, '/')), s1); + // assert.strictEqual(s1.find(toResource.call(this, '')), s1); //TODO@isidor this fails with proper paths usage }); test('Find with mixed case', function () { const d = new Date().getTime(); - const s1 = createStat('/', '/', true, false, 8096, d); - const s2 = createStat('/path', 'path', true, false, 8096, d); - const s3 = createStat('/path/to', 'to', true, false, 8096, d); - const s4 = createStat('/path/to/stat', 'stat', true, false, 8096, d); + const s1 = createStat.call(this, '/', '/', true, false, 8096, d); + const s2 = createStat.call(this, '/path', 'path', true, false, 8096, d); + const s3 = createStat.call(this, '/path/to', 'to', true, false, 8096, d); + const s4 = createStat.call(this, '/path/to/stat', 'stat', true, false, 8096, d); - const child1 = createStat('/path/to/stat/foo', 'foo', true, false, 8096, d); - const child2 = createStat('/path/to/stat/foo/bar.html', 'bar.html', false, false, 8096, d); + const child1 = createStat.call(this, '/path/to/stat/foo', 'foo', true, false, 8096, d); + const child2 = createStat.call(this, '/path/to/stat/foo/bar.html', 'bar.html', false, false, 8096, d); s1.addChild(s2); s2.addChild(s3); @@ -175,18 +168,18 @@ suite('Files - View Model', () => { child1.addChild(child2); if (isLinux) { // linux is case sensitive - assert.ok(!s1.find(toResource('/path/to/stat/Foo'))); - assert.ok(!s1.find(toResource('/Path/to/stat/foo/bar.html'))); + assert.ok(!s1.find(toResource.call(this, '/path/to/stat/Foo'))); + assert.ok(!s1.find(toResource.call(this, '/Path/to/stat/foo/bar.html'))); } else { - assert.ok(s1.find(toResource('/path/to/stat/Foo'))); - assert.ok(s1.find(toResource('/Path/to/stat/foo/bar.html'))); + assert.ok(s1.find(toResource.call(this, '/path/to/stat/Foo'))); + assert.ok(s1.find(toResource.call(this, '/Path/to/stat/foo/bar.html'))); } }); test('Validate File Name (For Create)', function () { const d = new Date().getTime(); - const s = createStat('/path/to/stat', 'sName', true, true, 8096, d); - const sChild = createStat('/path/to/stat/alles.klar', 'alles.klar', true, true, 8096, d); + const s = createStat.call(this, '/path/to/stat', 'sName', true, true, 8096, d); + const sChild = createStat.call(this, '/path/to/stat/alles.klar', 'alles.klar', true, true, 8096, d); s.addChild(sChild); assert(validateFileName(s, null!) !== null); @@ -210,8 +203,8 @@ suite('Files - View Model', () => { test('Validate File Name (For Rename)', function () { const d = new Date().getTime(); - const s = createStat('/path/to/stat', 'sName', true, true, 8096, d); - const sChild = createStat('/path/to/stat/alles.klar', 'alles.klar', true, true, 8096, d); + const s = createStat.call(this, '/path/to/stat', 'sName', true, true, 8096, d); + const sChild = createStat.call(this, '/path/to/stat/alles.klar', 'alles.klar', true, true, 8096, d); s.addChild(sChild); assert(validateFileName(s, 'alles.klar') === null); @@ -226,7 +219,7 @@ suite('Files - View Model', () => { test('Validate Multi-Path File Names', function () { const d = new Date().getTime(); - const wsFolder = createStat('/', 'workspaceFolder', true, false, 8096, d); + const wsFolder = createStat.call(this, '/', 'workspaceFolder', true, false, 8096, d); assert(validateFileName(wsFolder, 'foo/bar') === null); assert(validateFileName(wsFolder, 'foo\\bar') === null); @@ -235,13 +228,13 @@ suite('Files - View Model', () => { assert(validateFileName(wsFolder, '/slashAtBeginning') !== null); // attempting to add a child to a deeply nested file - const s1 = createStat('/path', 'path', true, false, 8096, d); - const s2 = createStat('/path/to', 'to', true, false, 8096, d); - const s3 = createStat('/path/to/stat', 'stat', true, false, 8096, d); + const s1 = createStat.call(this, '/path', 'path', true, false, 8096, d); + const s2 = createStat.call(this, '/path/to', 'to', true, false, 8096, d); + const s3 = createStat.call(this, '/path/to/stat', 'stat', true, false, 8096, d); wsFolder.addChild(s1); s1.addChild(s2); s2.addChild(s3); - const fileDeeplyNested = createStat('/path/to/stat/fileNested', 'fileNested', false, false, 8096, d); + const fileDeeplyNested = createStat.call(this, '/path/to/stat/fileNested', 'fileNested', false, false, 8096, d); s3.addChild(fileDeeplyNested); assert(validateFileName(wsFolder, '/path/to/stat/fileNested/aChild') !== null); diff --git a/src/vs/workbench/contrib/localizations/electron-browser/localizations.contribution.ts b/src/vs/workbench/contrib/localizations/electron-browser/localizations.contribution.ts index 46b0466d5c0..b545050f50f 100644 --- a/src/vs/workbench/contrib/localizations/electron-browser/localizations.contribution.ts +++ b/src/vs/workbench/contrib/localizations/electron-browser/localizations.contribution.ts @@ -21,7 +21,7 @@ import Severity from 'vs/base/common/severity'; import { IJSONEditingService } from 'vs/workbench/services/configuration/common/jsonEditing'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { URI } from 'vs/base/common/uri'; -import { join } from 'vs/base/common/extpath'; +import { join } from 'vs/base/common/path'; import { IWindowsService } from 'vs/platform/windows/common/windows'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; diff --git a/src/vs/workbench/contrib/localizations/electron-browser/localizationsActions.ts b/src/vs/workbench/contrib/localizations/electron-browser/localizationsActions.ts index 57295a1d3df..62f41d1a3fd 100644 --- a/src/vs/workbench/contrib/localizations/electron-browser/localizationsActions.ts +++ b/src/vs/workbench/contrib/localizations/electron-browser/localizationsActions.ts @@ -8,7 +8,7 @@ import { Action } from 'vs/base/common/actions'; import { IFileService } from 'vs/platform/files/common/files'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { IEditor } from 'vs/workbench/common/editor'; -import { join } from 'vs/base/common/extpath'; +import { join } from 'vs/base/common/path'; import { URI } from 'vs/base/common/uri'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { language } from 'vs/base/common/platform'; diff --git a/src/vs/workbench/contrib/logs/electron-browser/logs.contribution.ts b/src/vs/workbench/contrib/logs/electron-browser/logs.contribution.ts index 8ce70c2e4d4..bfeb0d54a1e 100644 --- a/src/vs/workbench/contrib/logs/electron-browser/logs.contribution.ts +++ b/src/vs/workbench/contrib/logs/electron-browser/logs.contribution.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as nls from 'vs/nls'; -import { join } from 'vs/base/common/extpath'; +import { join } from 'vs/base/common/path'; import { Registry } from 'vs/platform/registry/common/platform'; import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions, IWorkbenchContribution } from 'vs/workbench/common/contributions'; import { IOutputChannelRegistry, Extensions as OutputExt, } from 'vs/workbench/contrib/output/common/output'; diff --git a/src/vs/workbench/contrib/logs/electron-browser/logsActions.ts b/src/vs/workbench/contrib/logs/electron-browser/logsActions.ts index 32c879a6efb..faa6e33ec1e 100644 --- a/src/vs/workbench/contrib/logs/electron-browser/logsActions.ts +++ b/src/vs/workbench/contrib/logs/electron-browser/logsActions.ts @@ -5,7 +5,7 @@ import * as nls from 'vs/nls'; import { Action } from 'vs/base/common/actions'; -import * as extpath from 'vs/base/common/extpath'; +import { join } from 'vs/base/common/path'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { IWindowsService } from 'vs/platform/windows/common/windows'; import { ILogService, LogLevel, DEFAULT_LOG_LEVEL } from 'vs/platform/log/common/log'; @@ -24,7 +24,7 @@ export class OpenLogsFolderAction extends Action { } run(): Promise { - return this.windowsService.showItemInFolder(extpath.join(this.environmentService.logsPath, 'main.log')); + return this.windowsService.showItemInFolder(join(this.environmentService.logsPath, 'main.log')); } } diff --git a/src/vs/workbench/contrib/markers/electron-browser/markersFilterOptions.ts b/src/vs/workbench/contrib/markers/electron-browser/markersFilterOptions.ts index d60a281553c..ba24dc97c4e 100644 --- a/src/vs/workbench/contrib/markers/electron-browser/markersFilterOptions.ts +++ b/src/vs/workbench/contrib/markers/electron-browser/markersFilterOptions.ts @@ -5,8 +5,10 @@ import Messages from 'vs/workbench/contrib/markers/electron-browser/messages'; import { IFilter, matchesPrefix, matchesFuzzy, matchesFuzzy2 } from 'vs/base/common/filters'; -import { ParsedExpression, IExpression, splitGlobAware, getEmptyExpression, parse } from 'vs/base/common/glob'; +import { IExpression, splitGlobAware, getEmptyExpression } from 'vs/base/common/glob'; import * as strings from 'vs/base/common/strings'; +import { URI } from 'vs/base/common/uri'; +import { ResourceGlobMatcher } from 'vs/base/common/resources'; export class FilterOptions { @@ -16,19 +18,17 @@ export class FilterOptions { readonly filterErrors: boolean = false; readonly filterWarnings: boolean = false; readonly filterInfos: boolean = false; - readonly excludePattern: ParsedExpression | null = null; - readonly includePattern: ParsedExpression | null = null; readonly textFilter: string = ''; + readonly excludesMatcher: ResourceGlobMatcher; + readonly includesMatcher: ResourceGlobMatcher; - constructor(readonly filter: string = '', excludePatterns: IExpression = {}) { + constructor(readonly filter: string = '', filesExclude: { root: URI, expression: IExpression }[] | IExpression = []) { filter = filter.trim(); - for (const key of Object.keys(excludePatterns)) { - if (excludePatterns[key]) { - this.setPattern(excludePatterns, key); - } - delete excludePatterns[key]; - } - const includePatterns: IExpression = getEmptyExpression(); + + const filesExcludeByRoot = Array.isArray(filesExclude) ? filesExclude : []; + const excludesExpression: IExpression = Array.isArray(filesExclude) ? getEmptyExpression() : filesExclude; + + const includeExpression: IExpression = getEmptyExpression(); if (filter) { const filters = splitGlobAware(filter, ',').map(s => s.trim()).filter(s => !!s.length); for (const f of filters) { @@ -36,19 +36,16 @@ export class FilterOptions { this.filterWarnings = this.filterWarnings || this.matches(f, Messages.MARKERS_PANEL_FILTER_WARNINGS); this.filterInfos = this.filterInfos || this.matches(f, Messages.MARKERS_PANEL_FILTER_INFOS); if (strings.startsWith(f, '!')) { - this.setPattern(excludePatterns, strings.ltrim(f, '!')); + this.setPattern(excludesExpression, strings.ltrim(f, '!')); } else { - this.setPattern(includePatterns, f); + this.setPattern(includeExpression, f); this.textFilter += ` ${f}`; } } } - if (Object.keys(excludePatterns).length) { - this.excludePattern = parse(excludePatterns); - } - if (Object.keys(includePatterns).length) { - this.includePattern = parse(includePatterns); - } + + this.excludesMatcher = new ResourceGlobMatcher(excludesExpression, filesExcludeByRoot); + this.includesMatcher = new ResourceGlobMatcher(includeExpression, []); this.textFilter = this.textFilter.trim(); } diff --git a/src/vs/workbench/contrib/markers/electron-browser/markersPanel.ts b/src/vs/workbench/contrib/markers/electron-browser/markersPanel.ts index c7cb237a956..e8efccd8d4d 100644 --- a/src/vs/workbench/contrib/markers/electron-browser/markersPanel.ts +++ b/src/vs/workbench/contrib/markers/electron-browser/markersPanel.ts @@ -29,11 +29,9 @@ import { ITreeElement, ITreeNode, ITreeContextMenuEvent } from 'vs/base/browser/ import { Relay, Event, Emitter } from 'vs/base/common/event'; import { WorkbenchObjectTree, TreeResourceNavigator2 } from 'vs/platform/list/browser/listService'; import { FilterOptions } from 'vs/workbench/contrib/markers/electron-browser/markersFilterOptions'; -import { IExpression, getEmptyExpression } from 'vs/base/common/glob'; -import { mixin, deepClone } from 'vs/base/common/objects'; -import { IWorkspaceFolder, IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; -import { join } from 'vs/base/common/extpath'; -import { isAbsolute } from 'vs/base/common/path'; +import { IExpression } from 'vs/base/common/glob'; +import { deepClone } from 'vs/base/common/objects'; +import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { FilterData, Filter, VirtualDelegate, ResourceMarkersRenderer, MarkerRenderer, RelatedInformationRenderer, TreeElement, MarkersTreeAccessibilityProvider, MarkersViewModel, ResourceDragAndDrop } from 'vs/workbench/contrib/markers/electron-browser/markersTreeViewer'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { Separator, ActionItem } from 'vs/base/browser/ui/actionbar/actionbar'; @@ -248,8 +246,7 @@ export class MarkersPanel extends Panel implements IMarkerFilterController { private updateFilter() { this.cachedFilterStats = undefined; - const excludeExpression = this.getExcludeExpression(this.filterAction.useFilesExclude); - this.filter.options = new FilterOptions(this.filterAction.filterText, excludeExpression); + this.filter.options = new FilterOptions(this.filterAction.filterText, this.getFilesExcludeExpressions()); this.tree.refilter(); this._onDidFilter.fire(); @@ -258,44 +255,21 @@ export class MarkersPanel extends Panel implements IMarkerFilterController { this.renderMessage(); } - private getExcludeExpression(useFilesExclude: boolean): IExpression { - if (!useFilesExclude) { - return {}; + private getFilesExcludeExpressions(): { root: URI, expression: IExpression }[] | IExpression { + if (!this.filterAction.useFilesExclude) { + return []; } const workspaceFolders = this.workspaceContextService.getWorkspace().folders; - if (workspaceFolders.length) { - const result = getEmptyExpression(); - for (const workspaceFolder of workspaceFolders) { - mixin(result, this.getExcludesForFolder(workspaceFolder)); - } - return result; - } else { - return this.getFilesExclude(); - } - } - - private getExcludesForFolder(workspaceFolder: IWorkspaceFolder): IExpression { - const expression = this.getFilesExclude(workspaceFolder.uri); - return this.getAbsoluteExpression(expression, workspaceFolder.uri.fsPath); + return workspaceFolders.length + ? workspaceFolders.map(workspaceFolder => ({ root: workspaceFolder.uri, expression: this.getFilesExclude(workspaceFolder.uri) })) + : this.getFilesExclude(); } private getFilesExclude(resource?: URI): IExpression { return deepClone(this.configurationService.getValue('files.exclude', { resource })) || {}; } - private getAbsoluteExpression(expr: IExpression, root: string): IExpression { - return Object.keys(expr) - .reduce((absExpr: IExpression, key: string) => { - if (expr[key] && !isAbsolute(key)) { - const absPattern = join(root, key); - absExpr[absPattern] = expr[key]; - } - - return absExpr; - }, Object.create(null)); - } - private createMessageBox(parent: HTMLElement): void { this.messageBoxContainer = dom.append(parent, dom.$('.message-box-container')); this.messageBoxContainer.setAttribute('aria-labelledby', 'markers-panel-arialabel'); @@ -320,7 +294,7 @@ export class MarkersPanel extends Panel implements IMarkerFilterController { this.instantiationService.createInstance(MarkerRenderer, this.markersViewModel), this.instantiationService.createInstance(RelatedInformationRenderer) ]; - this.filter = new Filter(); + this.filter = new Filter(new FilterOptions()); const accessibilityProvider = this.instantiationService.createInstance(MarkersTreeAccessibilityProvider); const identityProvider = { diff --git a/src/vs/workbench/contrib/markers/electron-browser/markersTreeViewer.ts b/src/vs/workbench/contrib/markers/electron-browser/markersTreeViewer.ts index b666987f50f..0a8d4a38d84 100644 --- a/src/vs/workbench/contrib/markers/electron-browser/markersTreeViewer.ts +++ b/src/vs/workbench/contrib/markers/electron-browser/markersTreeViewer.ts @@ -406,7 +406,7 @@ export class RelatedInformationRenderer implements ITreeRenderer { - options = new FilterOptions(); + constructor(public options: FilterOptions) { } filter(element: TreeElement, parentVisibility: TreeVisibility): TreeFilterResult { if (element instanceof ResourceMarkers) { @@ -423,7 +423,7 @@ export class Filter implements ITreeFilter { return false; } - if (this.options.excludePattern && !!this.options.excludePattern(resourceMarkers.resource.fsPath)) { + if (this.options.excludesMatcher.matches(resourceMarkers.resource)) { return false; } @@ -433,7 +433,7 @@ export class Filter implements ITreeFilter { return { visibility: true, data: { type: FilterDataType.ResourceMarkers, uriMatches } }; } - if (this.options.includePattern && this.options.includePattern(resourceMarkers.resource.fsPath)) { + if (this.options.includesMatcher.matches(resourceMarkers.resource)) { return true; } diff --git a/src/vs/workbench/contrib/output/common/outputLinkComputer.ts b/src/vs/workbench/contrib/output/common/outputLinkComputer.ts index 10cb01e71bf..ee681d2dccf 100644 --- a/src/vs/workbench/contrib/output/common/outputLinkComputer.ts +++ b/src/vs/workbench/contrib/output/common/outputLinkComputer.ts @@ -8,10 +8,10 @@ import { ILink } from 'vs/editor/common/modes'; import { URI } from 'vs/base/common/uri'; import * as extpath from 'vs/base/common/extpath'; import * as resources from 'vs/base/common/resources'; -import * as path from 'vs/base/common/path'; import * as strings from 'vs/base/common/strings'; -import * as arrays from 'vs/base/common/arrays'; import { Range } from 'vs/editor/common/core/range'; +import { isWindows } from 'vs/base/common/platform'; +import { Schemas } from 'vs/base/common/network'; export interface ICreateData { workspaceFolders: string[]; @@ -87,11 +87,11 @@ export class OutputLinkComputer { public static createPatterns(workspaceFolder: URI): RegExp[] { const patterns: RegExp[] = []; - const workspaceFolderPath = workspaceFolder.scheme === 'file' ? workspaceFolder.fsPath : workspaceFolder.path; - const workspaceFolderVariants = arrays.distinct([ - path.normalize(workspaceFolderPath), - extpath.normalizeWithSlashes(workspaceFolderPath) - ]); + const workspaceFolderPath = workspaceFolder.scheme === Schemas.file ? workspaceFolder.fsPath : workspaceFolder.path; + const workspaceFolderVariants = [workspaceFolderPath]; + if (isWindows && workspaceFolder.scheme === Schemas.file) { + workspaceFolderVariants.push(extpath.toSlashes(workspaceFolderPath)); + } workspaceFolderVariants.forEach(workspaceFolderVariant => { const validPathCharacterPattern = '[^\\s\\(\\):<>"]'; diff --git a/src/vs/workbench/contrib/output/electron-browser/outputServices.ts b/src/vs/workbench/contrib/output/electron-browser/outputServices.ts index f661c95fff5..f511a8054d7 100644 --- a/src/vs/workbench/contrib/output/electron-browser/outputServices.ts +++ b/src/vs/workbench/contrib/output/electron-browser/outputServices.ts @@ -4,8 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as nls from 'vs/nls'; -import * as extpath from 'vs/base/common/extpath'; -import { dirname } from 'vs/base/common/path'; +import { join, dirname } from 'vs/base/common/path'; import * as strings from 'vs/base/common/strings'; import * as extfs from 'vs/base/node/extfs'; import { Event, Emitter } from 'vs/base/common/event'; @@ -185,7 +184,7 @@ class OutputChannelBackedByFile extends AbstractFileOutputChannel implements Out @IModeService modeService: IModeService, @ILogService logService: ILogService ) { - super({ ...outputChannelDescriptor, file: URI.file(extpath.join(outputDir, `${outputChannelDescriptor.id}.log`)) }, modelUri, fileService, modelService, modeService); + super({ ...outputChannelDescriptor, file: URI.file(join(outputDir, `${outputChannelDescriptor.id}.log`)) }, modelUri, fileService, modelService, modeService); // Use one rotating file to check for main file reset this.appender = new OutputAppender(this.id, this.file.fsPath); @@ -447,7 +446,7 @@ export class OutputService extends Disposable implements IOutputService, ITextMo ) { super(); this.activeChannelIdInStorage = this.storageService.get(OUTPUT_ACTIVE_CHANNEL_KEY, StorageScope.WORKSPACE, null); - this.outputDir = extpath.join(environmentService.logsPath, `output_${windowService.getCurrentWindowId()}_${toLocalISOString(new Date()).replace(/-|:|\.\d+Z$/g, '')}`); + this.outputDir = join(environmentService.logsPath, `output_${windowService.getCurrentWindowId()}_${toLocalISOString(new Date()).replace(/-|:|\.\d+Z$/g, '')}`); // Register as text model content provider for output textModelResolverService.registerTextModelContentProvider(OUTPUT_SCHEME, this); diff --git a/src/vs/workbench/contrib/output/test/outputLinkProvider.test.ts b/src/vs/workbench/contrib/output/test/outputLinkProvider.test.ts index 3d455d5db3b..0be1f3d9e48 100644 --- a/src/vs/workbench/contrib/output/test/outputLinkProvider.test.ts +++ b/src/vs/workbench/contrib/output/test/outputLinkProvider.test.ts @@ -5,7 +5,7 @@ import * as assert from 'assert'; import { URI } from 'vs/base/common/uri'; -import { isMacintosh, isLinux } from 'vs/base/common/platform'; +import { isMacintosh, isLinux, isWindows } from 'vs/base/common/platform'; import { OutputLinkComputer } from 'vs/workbench/contrib/output/common/outputLinkComputer'; import { TestContextService } from 'vs/workbench/test/workbenchTestServices'; @@ -20,32 +20,20 @@ function toOSPath(p: string): string { suite('Workbench - OutputWorker', () => { test('OutputWorker - Link detection', function () { - let patternsSlash = OutputLinkComputer.createPatterns( - URI.file('C:/Users/someone/AppData/Local/Temp/_monacodata_9888/workspaces/mankala') - ); + const rootFolder = isWindows ? URI.file('C:\\Users\\someone\\AppData\\Local\\Temp\\_monacodata_9888\\workspaces\\mankala') : + URI.file('C:/Users/someone/AppData/Local/Temp/_monacodata_9888/workspaces/mankala'); - let patternsBackSlash = OutputLinkComputer.createPatterns( - URI.file('C:\\Users\\someone\\AppData\\Local\\Temp\\_monacodata_9888\\workspaces\\mankala') - ); + let patterns = OutputLinkComputer.createPatterns(rootFolder); let contextService = new TestContextService(); let line = toOSPath('Foo bar'); - let result = OutputLinkComputer.detectLinks(line, 1, patternsSlash, contextService); - assert.equal(result.length, 0); - result = OutputLinkComputer.detectLinks(line, 1, patternsBackSlash, contextService); + let result = OutputLinkComputer.detectLinks(line, 1, patterns, contextService); assert.equal(result.length, 0); // Example: at C:\\Users\\someone\\AppData\\Local\\Temp\\_monacodata_9888\\workspaces\\mankala\\Game.ts line = toOSPath(' at C:\\Users\\someone\\AppData\\Local\\Temp\\_monacodata_9888\\workspaces\\mankala\\Game.ts in'); - result = OutputLinkComputer.detectLinks(line, 1, patternsSlash, contextService); - assert.equal(result.length, 1); - assert.equal(result[0].url, contextService.toResource('/Game.ts').toString()); - assert.equal(result[0].range.startColumn, 5); - assert.equal(result[0].range.endColumn, 84); - - line = toOSPath(' at C:\\Users\\someone\\AppData\\Local\\Temp\\_monacodata_9888\\workspaces\\mankala\\Game.ts in'); - result = OutputLinkComputer.detectLinks(line, 1, patternsBackSlash, contextService); + result = OutputLinkComputer.detectLinks(line, 1, patterns, contextService); assert.equal(result.length, 1); assert.equal(result[0].url, contextService.toResource('/Game.ts').toString()); assert.equal(result[0].range.startColumn, 5); @@ -53,14 +41,7 @@ suite('Workbench - OutputWorker', () => { // Example: at C:\\Users\\someone\\AppData\\Local\\Temp\\_monacodata_9888\\workspaces\\mankala\\Game.ts:336 line = toOSPath(' at C:\\Users\\someone\\AppData\\Local\\Temp\\_monacodata_9888\\workspaces\\mankala\\Game.ts:336 in'); - result = OutputLinkComputer.detectLinks(line, 1, patternsSlash, contextService); - assert.equal(result.length, 1); - assert.equal(result[0].url, contextService.toResource('/Game.ts').toString() + '#336'); - assert.equal(result[0].range.startColumn, 5); - assert.equal(result[0].range.endColumn, 88); - - line = toOSPath(' at C:\\Users\\someone\\AppData\\Local\\Temp\\_monacodata_9888\\workspaces\\mankala\\Game.ts:336 in'); - result = OutputLinkComputer.detectLinks(line, 1, patternsBackSlash, contextService); + result = OutputLinkComputer.detectLinks(line, 1, patterns, contextService); assert.equal(result.length, 1); assert.equal(result[0].url, contextService.toResource('/Game.ts').toString() + '#336'); assert.equal(result[0].range.startColumn, 5); @@ -68,26 +49,14 @@ suite('Workbench - OutputWorker', () => { // Example: at C:\\Users\\someone\\AppData\\Local\\Temp\\_monacodata_9888\\workspaces\\mankala\\Game.ts:336:9 line = toOSPath(' at C:\\Users\\someone\\AppData\\Local\\Temp\\_monacodata_9888\\workspaces\\mankala\\Game.ts:336:9 in'); - result = OutputLinkComputer.detectLinks(line, 1, patternsSlash, contextService); - assert.equal(result.length, 1); - assert.equal(result[0].url, contextService.toResource('/Game.ts').toString() + '#336,9'); - assert.equal(result[0].range.startColumn, 5); - assert.equal(result[0].range.endColumn, 90); - - result = OutputLinkComputer.detectLinks(line, 1, patternsBackSlash, contextService); + result = OutputLinkComputer.detectLinks(line, 1, patterns, contextService); assert.equal(result.length, 1); assert.equal(result[0].url, contextService.toResource('/Game.ts').toString() + '#336,9'); assert.equal(result[0].range.startColumn, 5); assert.equal(result[0].range.endColumn, 90); line = toOSPath(' at C:\\Users\\someone\\AppData\\Local\\Temp\\_monacodata_9888\\workspaces\\mankala\\Game.ts:336:9 in'); - result = OutputLinkComputer.detectLinks(line, 1, patternsSlash, contextService); - assert.equal(result.length, 1); - assert.equal(result[0].url, contextService.toResource('/Game.ts').toString() + '#336,9'); - assert.equal(result[0].range.startColumn, 5); - assert.equal(result[0].range.endColumn, 90); - - result = OutputLinkComputer.detectLinks(line, 1, patternsBackSlash, contextService); + result = OutputLinkComputer.detectLinks(line, 1, patterns, contextService); assert.equal(result.length, 1); assert.equal(result[0].url, contextService.toResource('/Game.ts').toString() + '#336,9'); assert.equal(result[0].range.startColumn, 5); @@ -95,7 +64,7 @@ suite('Workbench - OutputWorker', () => { // Example: at C:\\Users\\someone\\AppData\\Local\\Temp\\_monacodata_9888\\workspaces\\mankala\\Game.ts>dir line = toOSPath(' at C:\\Users\\someone\\AppData\\Local\\Temp\\_monacodata_9888\\workspaces\\mankala\\Game.ts>dir in'); - result = OutputLinkComputer.detectLinks(line, 1, patternsSlash, contextService); + result = OutputLinkComputer.detectLinks(line, 1, patterns, contextService); assert.equal(result.length, 1); assert.equal(result[0].url, contextService.toResource('/Game.ts').toString()); assert.equal(result[0].range.startColumn, 5); @@ -103,7 +72,7 @@ suite('Workbench - OutputWorker', () => { // Example: at [C:\\Users\\someone\\AppData\\Local\\Temp\\_monacodata_9888\\workspaces\\mankala\\Game.ts:336:9] line = toOSPath(' at C:\\Users\\someone\\AppData\\Local\\Temp\\_monacodata_9888\\workspaces\\mankala\\Game.ts:336:9] in'); - result = OutputLinkComputer.detectLinks(line, 1, patternsSlash, contextService); + result = OutputLinkComputer.detectLinks(line, 1, patterns, contextService); assert.equal(result.length, 1); assert.equal(result[0].url, contextService.toResource('/Game.ts').toString() + '#336,9'); assert.equal(result[0].range.startColumn, 5); @@ -111,19 +80,13 @@ suite('Workbench - OutputWorker', () => { // Example: at [C:\\Users\\someone\\AppData\\Local\\Temp\\_monacodata_9888\\workspaces\\mankala\\Game.ts] line = toOSPath(' at C:\\Users\\someone\\AppData\\Local\\Temp\\_monacodata_9888\\workspaces\\mankala\\Game.ts] in'); - result = OutputLinkComputer.detectLinks(line, 1, patternsSlash, contextService); + result = OutputLinkComputer.detectLinks(line, 1, patterns, contextService); assert.equal(result.length, 1); assert.equal(result[0].url, contextService.toResource('/Game.ts]').toString()); // Example: C:\Users\someone\AppData\Local\Temp\_monacodata_9888\workspaces\express\server.js on line 8 line = toOSPath('C:\\Users\\someone\\AppData\\Local\\Temp\\_monacodata_9888\\workspaces\\mankala\\Game.ts on line 8'); - result = OutputLinkComputer.detectLinks(line, 1, patternsSlash, contextService); - assert.equal(result.length, 1); - assert.equal(result[0].url, contextService.toResource('/Game.ts').toString() + '#8'); - assert.equal(result[0].range.startColumn, 1); - assert.equal(result[0].range.endColumn, 90); - - result = OutputLinkComputer.detectLinks(line, 1, patternsBackSlash, contextService); + result = OutputLinkComputer.detectLinks(line, 1, patterns, contextService); assert.equal(result.length, 1); assert.equal(result[0].url, contextService.toResource('/Game.ts').toString() + '#8'); assert.equal(result[0].range.startColumn, 1); @@ -131,34 +94,23 @@ suite('Workbench - OutputWorker', () => { // Example: C:\Users\someone\AppData\Local\Temp\_monacodata_9888\workspaces\express\server.js on line 8, column 13 line = toOSPath('C:\\Users\\someone\\AppData\\Local\\Temp\\_monacodata_9888\\workspaces\\mankala\\Game.ts on line 8, column 13'); - result = OutputLinkComputer.detectLinks(line, 1, patternsSlash, contextService); - assert.equal(result.length, 1); - assert.equal(result[0].url, contextService.toResource('/Game.ts').toString() + '#8,13'); - assert.equal(result[0].range.startColumn, 1); - assert.equal(result[0].range.endColumn, 101); - - result = OutputLinkComputer.detectLinks(line, 1, patternsBackSlash, contextService); + result = OutputLinkComputer.detectLinks(line, 1, patterns, contextService); assert.equal(result.length, 1); assert.equal(result[0].url, contextService.toResource('/Game.ts').toString() + '#8,13'); assert.equal(result[0].range.startColumn, 1); assert.equal(result[0].range.endColumn, 101); line = toOSPath('C:\\Users\\someone\\AppData\\Local\\Temp\\_monacodata_9888\\workspaces\\mankala\\Game.ts on LINE 8, COLUMN 13'); - result = OutputLinkComputer.detectLinks(line, 1, patternsSlash, contextService); + result = OutputLinkComputer.detectLinks(line, 1, patterns, contextService); assert.equal(result.length, 1); assert.equal(result[0].url, contextService.toResource('/Game.ts').toString() + '#8,13'); assert.equal(result[0].range.startColumn, 1); assert.equal(result[0].range.endColumn, 101); - result = OutputLinkComputer.detectLinks(line, 1, patternsBackSlash, contextService); - assert.equal(result.length, 1); - assert.equal(result[0].url, contextService.toResource('/Game.ts').toString() + '#8,13'); - assert.equal(result[0].range.startColumn, 1); - assert.equal(result[0].range.endColumn, 101); // Example: C:\Users\someone\AppData\Local\Temp\_monacodata_9888\workspaces\express\server.js:line 8 line = toOSPath('C:\\Users\\someone\\AppData\\Local\\Temp\\_monacodata_9888\\workspaces\\mankala\\Game.ts:line 8'); - result = OutputLinkComputer.detectLinks(line, 1, patternsSlash, contextService); + result = OutputLinkComputer.detectLinks(line, 1, patterns, contextService); assert.equal(result.length, 1); assert.equal(result[0].url, contextService.toResource('/Game.ts').toString() + '#8'); assert.equal(result[0].range.startColumn, 1); @@ -166,13 +118,7 @@ suite('Workbench - OutputWorker', () => { // Example: at File.put (C:/Users/someone/AppData/Local/Temp/_monacodata_9888/workspaces/mankala/Game.ts) line = toOSPath(' at File.put (C:/Users/someone/AppData/Local/Temp/_monacodata_9888/workspaces/mankala/Game.ts)'); - result = OutputLinkComputer.detectLinks(line, 1, patternsSlash, contextService); - assert.equal(result.length, 1); - assert.equal(result[0].url, contextService.toResource('/Game.ts').toString()); - assert.equal(result[0].range.startColumn, 15); - assert.equal(result[0].range.endColumn, 94); - - result = OutputLinkComputer.detectLinks(line, 1, patternsBackSlash, contextService); + result = OutputLinkComputer.detectLinks(line, 1, patterns, contextService); assert.equal(result.length, 1); assert.equal(result[0].url, contextService.toResource('/Game.ts').toString()); assert.equal(result[0].range.startColumn, 15); @@ -180,13 +126,7 @@ suite('Workbench - OutputWorker', () => { // Example: at File.put (C:/Users/someone/AppData/Local/Temp/_monacodata_9888/workspaces/mankala/Game.ts:278) line = toOSPath(' at File.put (C:/Users/someone/AppData/Local/Temp/_monacodata_9888/workspaces/mankala/Game.ts:278)'); - result = OutputLinkComputer.detectLinks(line, 1, patternsSlash, contextService); - assert.equal(result.length, 1); - assert.equal(result[0].url, contextService.toResource('/Game.ts').toString() + '#278'); - assert.equal(result[0].range.startColumn, 15); - assert.equal(result[0].range.endColumn, 98); - - result = OutputLinkComputer.detectLinks(line, 1, patternsBackSlash, contextService); + result = OutputLinkComputer.detectLinks(line, 1, patterns, contextService); assert.equal(result.length, 1); assert.equal(result[0].url, contextService.toResource('/Game.ts').toString() + '#278'); assert.equal(result[0].range.startColumn, 15); @@ -194,26 +134,14 @@ suite('Workbench - OutputWorker', () => { // Example: at File.put (C:/Users/someone/AppData/Local/Temp/_monacodata_9888/workspaces/mankala/Game.ts:278:34) line = toOSPath(' at File.put (C:/Users/someone/AppData/Local/Temp/_monacodata_9888/workspaces/mankala/Game.ts:278:34)'); - result = OutputLinkComputer.detectLinks(line, 1, patternsSlash, contextService); - assert.equal(result.length, 1); - assert.equal(result[0].url, contextService.toResource('/Game.ts').toString() + '#278,34'); - assert.equal(result[0].range.startColumn, 15); - assert.equal(result[0].range.endColumn, 101); - - result = OutputLinkComputer.detectLinks(line, 1, patternsBackSlash, contextService); + result = OutputLinkComputer.detectLinks(line, 1, patterns, contextService); assert.equal(result.length, 1); assert.equal(result[0].url, contextService.toResource('/Game.ts').toString() + '#278,34'); assert.equal(result[0].range.startColumn, 15); assert.equal(result[0].range.endColumn, 101); line = toOSPath(' at File.put (C:/Users/someone/AppData/Local/Temp/_monacodata_9888/workspaces/mankala/Game.ts:278:34)'); - result = OutputLinkComputer.detectLinks(line, 1, patternsSlash, contextService); - assert.equal(result.length, 1); - assert.equal(result[0].url, contextService.toResource('/Game.ts').toString() + '#278,34'); - assert.equal(result[0].range.startColumn, 15); - assert.equal(result[0].range.endColumn, 101); - - result = OutputLinkComputer.detectLinks(line, 1, patternsBackSlash, contextService); + result = OutputLinkComputer.detectLinks(line, 1, patterns, contextService); assert.equal(result.length, 1); assert.equal(result[0].url, contextService.toResource('/Game.ts').toString() + '#278,34'); assert.equal(result[0].range.startColumn, 15); @@ -221,13 +149,7 @@ suite('Workbench - OutputWorker', () => { // Example: C:/Users/someone/AppData/Local/Temp/_monacodata_9888/workspaces/mankala/Features.ts(45): error line = toOSPath('C:/Users/someone/AppData/Local/Temp/_monacodata_9888/workspaces/mankala/lib/something/Features.ts(45): error'); - result = OutputLinkComputer.detectLinks(line, 1, patternsSlash, contextService); - assert.equal(result.length, 1); - assert.equal(result[0].url, contextService.toResource('/lib/something/Features.ts').toString() + '#45'); - assert.equal(result[0].range.startColumn, 1); - assert.equal(result[0].range.endColumn, 102); - - result = OutputLinkComputer.detectLinks(line, 1, patternsBackSlash, contextService); + result = OutputLinkComputer.detectLinks(line, 1, patterns, contextService); assert.equal(result.length, 1); assert.equal(result[0].url, contextService.toResource('/lib/something/Features.ts').toString() + '#45'); assert.equal(result[0].range.startColumn, 1); @@ -235,13 +157,7 @@ suite('Workbench - OutputWorker', () => { // Example: C:/Users/someone/AppData/Local/Temp/_monacodata_9888/workspaces/mankala/Features.ts (45,18): error line = toOSPath('C:/Users/someone/AppData/Local/Temp/_monacodata_9888/workspaces/mankala/lib/something/Features.ts (45): error'); - result = OutputLinkComputer.detectLinks(line, 1, patternsSlash, contextService); - assert.equal(result.length, 1); - assert.equal(result[0].url, contextService.toResource('/lib/something/Features.ts').toString() + '#45'); - assert.equal(result[0].range.startColumn, 1); - assert.equal(result[0].range.endColumn, 103); - - result = OutputLinkComputer.detectLinks(line, 1, patternsBackSlash, contextService); + result = OutputLinkComputer.detectLinks(line, 1, patterns, contextService); assert.equal(result.length, 1); assert.equal(result[0].url, contextService.toResource('/lib/something/Features.ts').toString() + '#45'); assert.equal(result[0].range.startColumn, 1); @@ -249,26 +165,14 @@ suite('Workbench - OutputWorker', () => { // Example: C:/Users/someone/AppData/Local/Temp/_monacodata_9888/workspaces/mankala/Features.ts(45,18): error line = toOSPath('C:/Users/someone/AppData/Local/Temp/_monacodata_9888/workspaces/mankala/lib/something/Features.ts(45,18): error'); - result = OutputLinkComputer.detectLinks(line, 1, patternsSlash, contextService); - assert.equal(result.length, 1); - assert.equal(result[0].url, contextService.toResource('/lib/something/Features.ts').toString() + '#45,18'); - assert.equal(result[0].range.startColumn, 1); - assert.equal(result[0].range.endColumn, 105); - - result = OutputLinkComputer.detectLinks(line, 1, patternsBackSlash, contextService); + result = OutputLinkComputer.detectLinks(line, 1, patterns, contextService); assert.equal(result.length, 1); assert.equal(result[0].url, contextService.toResource('/lib/something/Features.ts').toString() + '#45,18'); assert.equal(result[0].range.startColumn, 1); assert.equal(result[0].range.endColumn, 105); line = toOSPath('C:/Users/someone/AppData/Local/Temp/_monacodata_9888/workspaces/mankala/lib/something/Features.ts(45,18): error'); - result = OutputLinkComputer.detectLinks(line, 1, patternsSlash, contextService); - assert.equal(result.length, 1); - assert.equal(result[0].url, contextService.toResource('/lib/something/Features.ts').toString() + '#45,18'); - assert.equal(result[0].range.startColumn, 1); - assert.equal(result[0].range.endColumn, 105); - - result = OutputLinkComputer.detectLinks(line, 1, patternsBackSlash, contextService); + result = OutputLinkComputer.detectLinks(line, 1, patterns, contextService); assert.equal(result.length, 1); assert.equal(result[0].url, contextService.toResource('/lib/something/Features.ts').toString() + '#45,18'); assert.equal(result[0].range.startColumn, 1); @@ -276,26 +180,14 @@ suite('Workbench - OutputWorker', () => { // Example: C:/Users/someone/AppData/Local/Temp/_monacodata_9888/workspaces/mankala/Features.ts (45,18): error line = toOSPath('C:/Users/someone/AppData/Local/Temp/_monacodata_9888/workspaces/mankala/lib/something/Features.ts (45,18): error'); - result = OutputLinkComputer.detectLinks(line, 1, patternsSlash, contextService); - assert.equal(result.length, 1); - assert.equal(result[0].url, contextService.toResource('/lib/something/Features.ts').toString() + '#45,18'); - assert.equal(result[0].range.startColumn, 1); - assert.equal(result[0].range.endColumn, 106); - - result = OutputLinkComputer.detectLinks(line, 1, patternsBackSlash, contextService); + result = OutputLinkComputer.detectLinks(line, 1, patterns, contextService); assert.equal(result.length, 1); assert.equal(result[0].url, contextService.toResource('/lib/something/Features.ts').toString() + '#45,18'); assert.equal(result[0].range.startColumn, 1); assert.equal(result[0].range.endColumn, 106); line = toOSPath('C:/Users/someone/AppData/Local/Temp/_monacodata_9888/workspaces/mankala/lib/something/Features.ts (45,18): error'); - result = OutputLinkComputer.detectLinks(line, 1, patternsSlash, contextService); - assert.equal(result.length, 1); - assert.equal(result[0].url, contextService.toResource('/lib/something/Features.ts').toString() + '#45,18'); - assert.equal(result[0].range.startColumn, 1); - assert.equal(result[0].range.endColumn, 106); - - result = OutputLinkComputer.detectLinks(line, 1, patternsBackSlash, contextService); + result = OutputLinkComputer.detectLinks(line, 1, patterns, contextService); assert.equal(result.length, 1); assert.equal(result[0].url, contextService.toResource('/lib/something/Features.ts').toString() + '#45,18'); assert.equal(result[0].range.startColumn, 1); @@ -303,13 +195,7 @@ suite('Workbench - OutputWorker', () => { // Example: C:/Users/someone/AppData/Local/Temp/_monacodata_9888/workspaces/mankala/Features.ts(45): error line = toOSPath('C:\\Users\\someone\\AppData\\Local\\Temp\\_monacodata_9888\\workspaces\\mankala\\lib\\something\\Features.ts(45): error'); - result = OutputLinkComputer.detectLinks(line, 1, patternsSlash, contextService); - assert.equal(result.length, 1); - assert.equal(result[0].url, contextService.toResource('/lib/something/Features.ts').toString() + '#45'); - assert.equal(result[0].range.startColumn, 1); - assert.equal(result[0].range.endColumn, 102); - - result = OutputLinkComputer.detectLinks(line, 1, patternsBackSlash, contextService); + result = OutputLinkComputer.detectLinks(line, 1, patterns, contextService); assert.equal(result.length, 1); assert.equal(result[0].url, contextService.toResource('/lib/something/Features.ts').toString() + '#45'); assert.equal(result[0].range.startColumn, 1); @@ -317,13 +203,7 @@ suite('Workbench - OutputWorker', () => { // Example: C:/Users/someone/AppData/Local/Temp/_monacodata_9888/workspaces/mankala/Features.ts (45,18): error line = toOSPath('C:\\Users\\someone\\AppData\\Local\\Temp\\_monacodata_9888\\workspaces\\mankala\\lib\\something\\Features.ts (45): error'); - result = OutputLinkComputer.detectLinks(line, 1, patternsSlash, contextService); - assert.equal(result.length, 1); - assert.equal(result[0].url, contextService.toResource('/lib/something/Features.ts').toString() + '#45'); - assert.equal(result[0].range.startColumn, 1); - assert.equal(result[0].range.endColumn, 103); - - result = OutputLinkComputer.detectLinks(line, 1, patternsBackSlash, contextService); + result = OutputLinkComputer.detectLinks(line, 1, patterns, contextService); assert.equal(result.length, 1); assert.equal(result[0].url, contextService.toResource('/lib/something/Features.ts').toString() + '#45'); assert.equal(result[0].range.startColumn, 1); @@ -331,26 +211,14 @@ suite('Workbench - OutputWorker', () => { // Example: C:/Users/someone/AppData/Local/Temp/_monacodata_9888/workspaces/mankala/Features.ts(45,18): error line = toOSPath('C:\\Users\\someone\\AppData\\Local\\Temp\\_monacodata_9888\\workspaces\\mankala\\lib\\something\\Features.ts(45,18): error'); - result = OutputLinkComputer.detectLinks(line, 1, patternsSlash, contextService); - assert.equal(result.length, 1); - assert.equal(result[0].url, contextService.toResource('/lib/something/Features.ts').toString() + '#45,18'); - assert.equal(result[0].range.startColumn, 1); - assert.equal(result[0].range.endColumn, 105); - - result = OutputLinkComputer.detectLinks(line, 1, patternsBackSlash, contextService); + result = OutputLinkComputer.detectLinks(line, 1, patterns, contextService); assert.equal(result.length, 1); assert.equal(result[0].url, contextService.toResource('/lib/something/Features.ts').toString() + '#45,18'); assert.equal(result[0].range.startColumn, 1); assert.equal(result[0].range.endColumn, 105); line = toOSPath('C:\\Users\\someone\\AppData\\Local\\Temp\\_monacodata_9888\\workspaces\\mankala\\lib\\something\\Features.ts(45,18): error'); - result = OutputLinkComputer.detectLinks(line, 1, patternsSlash, contextService); - assert.equal(result.length, 1); - assert.equal(result[0].url, contextService.toResource('/lib/something/Features.ts').toString() + '#45,18'); - assert.equal(result[0].range.startColumn, 1); - assert.equal(result[0].range.endColumn, 105); - - result = OutputLinkComputer.detectLinks(line, 1, patternsBackSlash, contextService); + result = OutputLinkComputer.detectLinks(line, 1, patterns, contextService); assert.equal(result.length, 1); assert.equal(result[0].url, contextService.toResource('/lib/something/Features.ts').toString() + '#45,18'); assert.equal(result[0].range.startColumn, 1); @@ -358,26 +226,14 @@ suite('Workbench - OutputWorker', () => { // Example: C:/Users/someone/AppData/Local/Temp/_monacodata_9888/workspaces/mankala/Features.ts (45,18): error line = toOSPath('C:\\Users\\someone\\AppData\\Local\\Temp\\_monacodata_9888\\workspaces\\mankala\\lib\\something\\Features.ts (45,18): error'); - result = OutputLinkComputer.detectLinks(line, 1, patternsSlash, contextService); - assert.equal(result.length, 1); - assert.equal(result[0].url, contextService.toResource('/lib/something/Features.ts').toString() + '#45,18'); - assert.equal(result[0].range.startColumn, 1); - assert.equal(result[0].range.endColumn, 106); - - result = OutputLinkComputer.detectLinks(line, 1, patternsBackSlash, contextService); + result = OutputLinkComputer.detectLinks(line, 1, patterns, contextService); assert.equal(result.length, 1); assert.equal(result[0].url, contextService.toResource('/lib/something/Features.ts').toString() + '#45,18'); assert.equal(result[0].range.startColumn, 1); assert.equal(result[0].range.endColumn, 106); line = toOSPath('C:\\Users\\someone\\AppData\\Local\\Temp\\_monacodata_9888\\workspaces\\mankala\\lib\\something\\Features.ts (45,18): error'); - result = OutputLinkComputer.detectLinks(line, 1, patternsSlash, contextService); - assert.equal(result.length, 1); - assert.equal(result[0].url, contextService.toResource('/lib/something/Features.ts').toString() + '#45,18'); - assert.equal(result[0].range.startColumn, 1); - assert.equal(result[0].range.endColumn, 106); - - result = OutputLinkComputer.detectLinks(line, 1, patternsBackSlash, contextService); + result = OutputLinkComputer.detectLinks(line, 1, patterns, contextService); assert.equal(result.length, 1); assert.equal(result[0].url, contextService.toResource('/lib/something/Features.ts').toString() + '#45,18'); assert.equal(result[0].range.startColumn, 1); @@ -385,13 +241,7 @@ suite('Workbench - OutputWorker', () => { // Example: C:\\Users\\someone\\AppData\\Local\\Temp\\_monacodata_9888\\workspaces\\mankala\\lib\\something\\Features Special.ts (45,18): error. line = toOSPath('C:\\Users\\someone\\AppData\\Local\\Temp\\_monacodata_9888\\workspaces\\mankala\\lib\\something\\Features Special.ts (45,18): error'); - result = OutputLinkComputer.detectLinks(line, 1, patternsSlash, contextService); - assert.equal(result.length, 1); - assert.equal(result[0].url, contextService.toResource('/lib/something/Features Special.ts').toString() + '#45,18'); - assert.equal(result[0].range.startColumn, 1); - assert.equal(result[0].range.endColumn, 114); - - result = OutputLinkComputer.detectLinks(line, 1, patternsBackSlash, contextService); + result = OutputLinkComputer.detectLinks(line, 1, patterns, contextService); assert.equal(result.length, 1); assert.equal(result[0].url, contextService.toResource('/lib/something/Features Special.ts').toString() + '#45,18'); assert.equal(result[0].range.startColumn, 1); @@ -399,7 +249,7 @@ suite('Workbench - OutputWorker', () => { // Example: at C:\\Users\\someone\\AppData\\Local\\Temp\\_monacodata_9888\\workspaces\\mankala\\Game.ts. line = toOSPath(' at C:\\Users\\someone\\AppData\\Local\\Temp\\_monacodata_9888\\workspaces\\mankala\\Game.ts. in'); - result = OutputLinkComputer.detectLinks(line, 1, patternsSlash, contextService); + result = OutputLinkComputer.detectLinks(line, 1, patterns, contextService); assert.equal(result.length, 1); assert.equal(result[0].url, contextService.toResource('/Game.ts').toString()); assert.equal(result[0].range.startColumn, 5); @@ -407,17 +257,17 @@ suite('Workbench - OutputWorker', () => { // Example: at C:\\Users\\someone\\AppData\\Local\\Temp\\_monacodata_9888\\workspaces\\mankala\\Game line = toOSPath(' at C:\\Users\\someone\\AppData\\Local\\Temp\\_monacodata_9888\\workspaces\\mankala\\Game in'); - result = OutputLinkComputer.detectLinks(line, 1, patternsSlash, contextService); + result = OutputLinkComputer.detectLinks(line, 1, patterns, contextService); assert.equal(result.length, 1); // Example: at C:\\Users\\someone\\AppData\\Local\\Temp\\_monacodata_9888\\workspaces\\mankala\\Game\\ line = toOSPath(' at C:\\Users\\someone\\AppData\\Local\\Temp\\_monacodata_9888\\workspaces\\mankala\\Game\\ in'); - result = OutputLinkComputer.detectLinks(line, 1, patternsSlash, contextService); + result = OutputLinkComputer.detectLinks(line, 1, patterns, contextService); assert.equal(result.length, 1); // Example: at "C:\\Users\\someone\\AppData\\Local\\Temp\\_monacodata_9888\\workspaces\\mankala\\Game.ts" line = toOSPath(' at "C:\\Users\\someone\\AppData\\Local\\Temp\\_monacodata_9888\\workspaces\\mankala\\Game.ts" in'); - result = OutputLinkComputer.detectLinks(line, 1, patternsSlash, contextService); + result = OutputLinkComputer.detectLinks(line, 1, patterns, contextService); assert.equal(result.length, 1); assert.equal(result[0].url, contextService.toResource('/Game.ts').toString()); assert.equal(result[0].range.startColumn, 6); @@ -425,7 +275,7 @@ suite('Workbench - OutputWorker', () => { // Example: at 'C:\\Users\\someone\\AppData\\Local\\Temp\\_monacodata_9888\\workspaces\\mankala\\Game.ts' line = toOSPath(' at \'C:\\Users\\someone\\AppData\\Local\\Temp\\_monacodata_9888\\workspaces\\mankala\\Game.ts\' in'); - result = OutputLinkComputer.detectLinks(line, 1, patternsSlash, contextService); + result = OutputLinkComputer.detectLinks(line, 1, patterns, contextService); assert.equal(result.length, 1); assert.equal(result[0].url, contextService.toResource('/Game.ts\'').toString()); assert.equal(result[0].range.startColumn, 6); diff --git a/src/vs/workbench/contrib/performance/electron-browser/perfviewEditor.ts b/src/vs/workbench/contrib/performance/electron-browser/perfviewEditor.ts index 2ec6c1bcf18..c04ad0f4647 100644 --- a/src/vs/workbench/contrib/performance/electron-browser/perfviewEditor.ts +++ b/src/vs/workbench/contrib/performance/electron-browser/perfviewEditor.ts @@ -153,7 +153,6 @@ class PerfModelContentProvider implements ITextModelContentProvider { table.push(['nls:start => nls:end', metrics.timers.ellapsedNlsGeneration, '[main]', `initial startup: ${metrics.initialStartup}`]); table.push(['require(main.bundle.js)', metrics.initialStartup ? perf.getDuration('willLoadMainBundle', 'didLoadMainBundle') : undefined, '[main]', `initial startup: ${metrics.initialStartup}`]); table.push(['app.isReady => window.loadUrl()', metrics.timers.ellapsedWindowLoad, '[main]', `initial startup: ${metrics.initialStartup}`]); - table.push(['require & init global storage', metrics.timers.ellapsedGlobalStorageInitMain, '[main]', `initial startup: ${metrics.initialStartup}`]); table.push(['window.loadUrl() => begin to require(workbench.main.js)', metrics.timers.ellapsedWindowLoadToRequire, '[main->renderer]', StartupKindToString(metrics.windowKind)]); table.push(['require(workbench.main.js)', metrics.timers.ellapsedRequire, '[renderer]', `cached data: ${(metrics.didUseCachedData ? 'YES' : 'NO')}${stats ? `, node_modules took ${stats.nodeRequireTotal}ms` : ''}`]); table.push(['require & init workspace storage', metrics.timers.ellapsedWorkspaceStorageInit, '[renderer]', undefined]); diff --git a/src/vs/workbench/contrib/preferences/browser/keybindingsEditorContribution.ts b/src/vs/workbench/contrib/preferences/browser/keybindingsEditorContribution.ts index eeac873c1d4..35f7916e436 100644 --- a/src/vs/workbench/contrib/preferences/browser/keybindingsEditorContribution.ts +++ b/src/vs/workbench/contrib/preferences/browser/keybindingsEditorContribution.ts @@ -265,13 +265,20 @@ export class KeybindingEditorDecorationsRenderer extends Disposable { return true; } - const [parsedA1, parsedA2] = KeybindingParser.parseUserBinding(a); - const [parsedB1, parsedB2] = KeybindingParser.parseUserBinding(b); + const aParts = KeybindingParser.parseUserBinding(a); + const bParts = KeybindingParser.parseUserBinding(b); - return ( - this._userBindingEquals(parsedA1, parsedB1) - && this._userBindingEquals(parsedA2, parsedB2) - ); + if (aParts.length !== bParts.length) { + return false; + } + + for (let i = 0, len = aParts.length; i < len; i++) { + if (!this._userBindingEquals(aParts[i], bParts[i])) { + return false; + } + } + + return true; } private static _userBindingEquals(a: SimpleKeybinding | ScanCodeBinding, b: SimpleKeybinding | ScanCodeBinding): boolean { diff --git a/src/vs/workbench/contrib/preferences/browser/media/keybindingsEditor.css b/src/vs/workbench/contrib/preferences/browser/media/keybindingsEditor.css index 4ba0ee5f72a..3e73bc7fbfb 100644 --- a/src/vs/workbench/contrib/preferences/browser/media/keybindingsEditor.css +++ b/src/vs/workbench/contrib/preferences/browser/media/keybindingsEditor.css @@ -167,7 +167,7 @@ } .keybindings-editor > .keybindings-body > .keybindings-list-container .monaco-list-row > .column > .code { - font-family: Monaco, Menlo, Consolas, "Droid Sans Mono", "Inconsolata", "Courier New", monospace, "Droid Sans Fallback"; + font-family: var(--monaco-monospace-font); font-size: 90%; opacity: 0.8; display: flex; diff --git a/src/vs/workbench/contrib/preferences/browser/media/settingsWidgets.css b/src/vs/workbench/contrib/preferences/browser/media/settingsWidgets.css index 8d341146a40..695926f9e33 100644 --- a/src/vs/workbench/contrib/preferences/browser/media/settingsWidgets.css +++ b/src/vs/workbench/contrib/preferences/browser/media/settingsWidgets.css @@ -16,7 +16,7 @@ .settings-editor > .settings-body > .settings-tree-container .setting-item.setting-item-exclude .setting-exclude-sibling { display: inline-block; line-height: 22px; - font-family: Menlo, Monaco, Consolas, "Droid Sans Mono", "Courier New", monospace, "Droid Sans Fallback"; + font-family: var(--monaco-monospace-font); } .settings-editor > .settings-body > .settings-tree-container .setting-item.setting-item-exclude .setting-exclude-sibling { diff --git a/src/vs/workbench/contrib/preferences/common/preferences.ts b/src/vs/workbench/contrib/preferences/common/preferences.ts index 538ce1a7fd8..26985c7b847 100644 --- a/src/vs/workbench/contrib/preferences/common/preferences.ts +++ b/src/vs/workbench/contrib/preferences/common/preferences.ts @@ -5,7 +5,6 @@ import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { RawContextKey } from 'vs/platform/contextkey/common/contextkey'; -import { join } from 'vs/base/common/extpath'; import { ISettingsEditorModel, ISearchResult } from 'vs/workbench/services/preferences/common/preferences'; import { IEditor } from 'vs/workbench/common/editor'; import { IKeybindingItemEntry } from 'vs/workbench/services/preferences/common/keybindingsEditorModel'; @@ -106,7 +105,6 @@ export const KEYBINDINGS_EDITOR_CLEAR_INPUT = 'keybindings.editor.showDefaultKey export const KEYBINDINGS_EDITOR_SHOW_DEFAULT_KEYBINDINGS = 'keybindings.editor.showDefaultKeybindings'; export const KEYBINDINGS_EDITOR_SHOW_USER_KEYBINDINGS = 'keybindings.editor.showUserKeybindings'; -export const FOLDER_SETTINGS_PATH = join('.vscode', 'settings.json'); export const DEFAULT_SETTINGS_EDITOR_SETTING = 'workbench.settings.openDefaultSettings'; export const MODIFIED_SETTING_TAG = 'modified'; diff --git a/src/vs/workbench/contrib/preferences/electron-browser/media/settingsEditor2.css b/src/vs/workbench/contrib/preferences/electron-browser/media/settingsEditor2.css index 3c84a5c7efe..07a0d93e5a8 100644 --- a/src/vs/workbench/contrib/preferences/electron-browser/media/settingsEditor2.css +++ b/src/vs/workbench/contrib/preferences/electron-browser/media/settingsEditor2.css @@ -268,6 +268,10 @@ cursor: default; } +.settings-editor > .settings-body .settings-tree-container .monaco-list-rows { + overflow: visible !important; /* Allow validation errors to flow out of the tree container. Override inline style from ScrollableElement. */ +} + .settings-editor > .settings-body .settings-tree-container .monaco-list-row .monaco-tl-contents { max-width: 1000px; margin: auto; @@ -398,7 +402,7 @@ .settings-editor > .settings-body > .settings-tree-container .setting-item-contents .setting-item-description-markdown code { line-height: 15px; /** For some reason, this is needed, otherwise will take up 20px height */ - font-family: Menlo, Monaco, Consolas, "Droid Sans Mono", "Courier New", monospace, "Droid Sans Fallback"; + font-family: var(--monaco-monospace-font); } .settings-editor > .settings-body > .settings-tree-container .setting-item-contents .setting-item-enumDescription { diff --git a/src/vs/workbench/contrib/search/browser/media/searchview.css b/src/vs/workbench/contrib/search/browser/media/searchview.css index fd591c75b55..9cd664e1b18 100644 --- a/src/vs/workbench/contrib/search/browser/media/searchview.css +++ b/src/vs/workbench/contrib/search/browser/media/searchview.css @@ -182,6 +182,7 @@ .search-view .linematch > .match { overflow: hidden; text-overflow: ellipsis; + white-space: pre; } .search-view .linematch .matchLineNum { diff --git a/src/vs/workbench/contrib/search/browser/searchActions.ts b/src/vs/workbench/contrib/search/browser/searchActions.ts index f0a3c4dd3f6..6e4f3b06376 100644 --- a/src/vs/workbench/contrib/search/browser/searchActions.ts +++ b/src/vs/workbench/contrib/search/browser/searchActions.ts @@ -17,18 +17,17 @@ import * as nls from 'vs/nls'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; import { ICommandHandler } from 'vs/platform/commands/common/commands'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { IEditorOptions } from 'vs/platform/editor/common/editor'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; -import { WorkbenchObjectTree } from 'vs/platform/list/browser/listService'; -import { ISearchConfiguration, ISearchHistoryService, VIEW_ID } from 'vs/workbench/services/search/common/search'; +import { getSelectionKeyboardEvent, WorkbenchObjectTree } from 'vs/platform/list/browser/listService'; import { SearchView } from 'vs/workbench/contrib/search/browser/searchView'; import * as Constants from 'vs/workbench/contrib/search/common/constants'; import { IReplaceService } from 'vs/workbench/contrib/search/common/replace'; -import { FileMatch, FileMatchOrMatch, FolderMatch, Match, RenderableMatch, searchMatchComparer, SearchResult, BaseFolderMatch } from 'vs/workbench/contrib/search/common/searchModel'; -import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { BaseFolderMatch, FileMatch, FileMatchOrMatch, FolderMatch, Match, RenderableMatch, searchMatchComparer, SearchResult } from 'vs/workbench/contrib/search/common/searchModel'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; +import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; +import { ISearchConfiguration, ISearchHistoryService, VIEW_ID } from 'vs/workbench/services/search/common/search'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; export function isSearchViewFocused(viewletService: IViewletService, panelService: IPanelService): boolean { @@ -470,7 +469,7 @@ export class RemoveAction extends AbstractSearchAndReplaceAction { if (nextFocusElement) { this.viewer.reveal(nextFocusElement); - this.viewer.setFocus([nextFocusElement], getKeyboardEventForEditorOpen()); + this.viewer.setFocus([nextFocusElement], getSelectionKeyboardEvent()); } this.element.parent().remove(this.element); @@ -507,7 +506,7 @@ export class ReplaceAllAction extends AbstractSearchAndReplaceAction { const nextFocusElement = this.getElementToFocusAfterRemoved(tree, this.fileMatch); return this.fileMatch.parent().replace(this.fileMatch).then(() => { if (nextFocusElement) { - tree.setFocus([nextFocusElement], getKeyboardEventForEditorOpen()); + tree.setFocus([nextFocusElement], getSelectionKeyboardEvent()); } tree.domFocus(); @@ -530,7 +529,7 @@ export class ReplaceAllInFolderAction extends AbstractSearchAndReplaceAction { const nextFocusElement = this.getElementToFocusAfterRemoved(this.viewer, this.folderMatch); return this.folderMatch.replaceAll().then(() => { if (nextFocusElement) { - this.viewer.setFocus([nextFocusElement], getKeyboardEventForEditorOpen()); + this.viewer.setFocus([nextFocusElement], getSelectionKeyboardEvent()); } this.viewer.domFocus(); }); @@ -555,7 +554,7 @@ export class ReplaceAction extends AbstractSearchAndReplaceAction { return this.element.parent().replace(this.element).then(() => { const elementToFocus = this.getElementToFocusAfterReplace(); if (elementToFocus) { - this.viewer.setFocus([elementToFocus], getKeyboardEventForEditorOpen()); + this.viewer.setFocus([elementToFocus], getSelectionKeyboardEvent()); } return this.getElementToShowReplacePreview(elementToFocus); @@ -748,13 +747,3 @@ export const focusSearchListCommand: ICommandHandler = accessor => { searchView.moveFocusToResults(); }); }; - -export function getKeyboardEventForEditorOpen(options: IEditorOptions = {}): KeyboardEvent { - const fakeKeyboardEvent = new KeyboardEvent('keydown'); - if (options.preserveFocus) { - // fake double click - (fakeKeyboardEvent).detail = 2; - } - - return fakeKeyboardEvent; -} diff --git a/src/vs/workbench/contrib/search/browser/searchView.ts b/src/vs/workbench/contrib/search/browser/searchView.ts index cec86d8859f..52e381f537b 100644 --- a/src/vs/workbench/contrib/search/browser/searchView.ts +++ b/src/vs/workbench/contrib/search/browser/searchView.ts @@ -32,7 +32,7 @@ import { IContextMenuService, IContextViewService } from 'vs/platform/contextvie import { IConfirmation, IDialogService } from 'vs/platform/dialogs/common/dialogs'; import { FileChangesEvent, FileChangeType, IFileService } from 'vs/platform/files/common/files'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { TreeResourceNavigator2, WorkbenchObjectTree } from 'vs/platform/list/browser/listService'; +import { TreeResourceNavigator2, WorkbenchObjectTree, getSelectionKeyboardEvent } from 'vs/platform/list/browser/listService'; import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; import { IProgressService } from 'vs/platform/progress/common/progress'; import { IPatternInfo, ISearchComplete, ISearchConfiguration, ISearchConfigurationProperties, ISearchHistoryService, ISearchHistoryValues, ITextQuery, SearchErrorCode, VIEW_ID } from 'vs/workbench/services/search/common/search'; @@ -48,7 +48,7 @@ import { IEditor } from 'vs/workbench/common/editor'; import { IPanel } from 'vs/workbench/common/panel'; import { IViewlet } from 'vs/workbench/common/viewlet'; import { ExcludePatternInputWidget, PatternInputWidget } from 'vs/workbench/contrib/search/browser/patternInputWidget'; -import { CancelSearchAction, ClearSearchResultsAction, CollapseDeepestExpandedLevelAction, getKeyboardEventForEditorOpen, RefreshAction } from 'vs/workbench/contrib/search/browser/searchActions'; +import { CancelSearchAction, ClearSearchResultsAction, CollapseDeepestExpandedLevelAction, RefreshAction } from 'vs/workbench/contrib/search/browser/searchActions'; import { FileMatchRenderer, FolderMatchRenderer, MatchRenderer, SearchAccessibilityProvider, SearchDelegate, SearchDND } from 'vs/workbench/contrib/search/browser/searchResultsView'; import { ISearchWidgetOptions, SearchWidget } from 'vs/workbench/contrib/search/browser/searchWidget'; import * as Constants from 'vs/workbench/contrib/search/common/constants'; @@ -699,7 +699,7 @@ export class SearchView extends Viewlet implements IViewlet, IPanel { selectCurrentMatch(): void { const focused = this.tree.getFocus()[0]; - const fakeKeyboardEvent = getKeyboardEventForEditorOpen({ preserveFocus: false }); + const fakeKeyboardEvent = getSelectionKeyboardEvent(undefined, false); this.tree.setSelection([focused], fakeKeyboardEvent); } @@ -734,8 +734,7 @@ export class SearchView extends Viewlet implements IViewlet, IPanel { // Reveal the newly selected element if (next) { - this.tree.setFocus([next]); - this.tree.setSelection([next]); + this.tree.setFocus([next], getSelectionKeyboardEvent(undefined, false)); this.tree.reveal(next); this.selectCurrentMatchEmitter.fire(undefined); } @@ -775,8 +774,7 @@ export class SearchView extends Viewlet implements IViewlet, IPanel { // Reveal the newly selected element if (prev) { - this.tree.setFocus([prev]); - this.tree.setSelection([prev]); + this.tree.setFocus([prev], getSelectionKeyboardEvent(undefined, false)); this.tree.reveal(prev); this.selectCurrentMatchEmitter.fire(undefined); } @@ -1171,7 +1169,8 @@ export class SearchView extends Viewlet implements IViewlet, IPanel { matchLines: 1, charsPerLine }, - isSmartCase: this.configurationService.getValue().search.smartCase + isSmartCase: this.configurationService.getValue().search.smartCase, + expandPatterns: true }; const folderResources = this.contextService.getWorkspace().folders; diff --git a/src/vs/workbench/contrib/search/common/queryBuilder.ts b/src/vs/workbench/contrib/search/common/queryBuilder.ts index 66991d572a8..465cab51dd8 100644 --- a/src/vs/workbench/contrib/search/common/queryBuilder.ts +++ b/src/vs/workbench/contrib/search/common/queryBuilder.ts @@ -38,7 +38,7 @@ export interface ISearchPathPattern { /** * A set of search paths and a set of glob expressions that should be applied. */ -export interface ISearchPathsResult { +export interface ISearchPathsInfo { searchPaths?: ISearchPathPattern[]; pattern?: glob.IExpression; } @@ -49,6 +49,9 @@ export interface ICommonQueryBuilderOptions { includePattern?: string; extraFileResources?: uri[]; + /** Parse the special ./ syntax supported by the searchview, and expand foo to ** /foo */ + expandPatterns?: boolean; + maxResults?: number; maxFileSize?: number; disregardIgnoreFiles?: boolean; @@ -141,23 +144,34 @@ export class QueryBuilder { } private commonQuery(folderResources: uri[] = [], options: ICommonQueryBuilderOptions = {}): ICommonQueryProps { - const { searchPaths, pattern: includePattern } = this.parseSearchPaths(options.includePattern || ''); - const excludePattern = this.parseSearchPaths(options.excludePattern || ''); + let includeSearchPathsInfo: ISearchPathsInfo = {}; + if (options.includePattern) { + includeSearchPathsInfo = options.expandPatterns ? + this.parseSearchPaths(options.includePattern) : + { pattern: patternListToIExpression(options.includePattern) }; + } + + let excludeSearchPathsInfo: ISearchPathsInfo = {}; + if (options.excludePattern) { + excludeSearchPathsInfo = options.expandPatterns ? + this.parseSearchPaths(options.excludePattern) : + { pattern: patternListToIExpression(options.excludePattern) }; + } // Build folderQueries from searchPaths, if given, otherwise folderResources - const folderQueries = (searchPaths && searchPaths.length ? - searchPaths.map(searchPath => this.getFolderQueryForSearchPath(searchPath, options, excludePattern)) : - folderResources.map(uri => this.getFolderQueryForRoot(uri, options, excludePattern))) + const folderQueries = (includeSearchPathsInfo.searchPaths && includeSearchPathsInfo.searchPaths.length ? + includeSearchPathsInfo.searchPaths.map(searchPath => this.getFolderQueryForSearchPath(searchPath, options, excludeSearchPathsInfo)) : + folderResources.map(uri => this.getFolderQueryForRoot(uri, options, excludeSearchPathsInfo))) .filter(query => !!query) as IFolderQuery[]; const queryProps: ICommonQueryProps = { _reason: options._reason, folderQueries, - usingSearchPaths: !!(searchPaths && searchPaths.length), + usingSearchPaths: !!(includeSearchPathsInfo.searchPaths && includeSearchPathsInfo.searchPaths.length), extraFileResources: options.extraFileResources, - excludePattern: excludePattern.pattern, - includePattern, + excludePattern: excludeSearchPathsInfo.pattern, + includePattern: includeSearchPathsInfo.pattern, maxResults: options.maxResults }; @@ -208,7 +222,7 @@ export class QueryBuilder { * * Public for test. */ - parseSearchPaths(pattern: string): ISearchPathsResult { + parseSearchPaths(pattern: string): ISearchPathsInfo { const isSearchPath = (segment: string) => { // A segment is a search path if it is an absolute path or starts with ./, ../, .\, or ..\ return path.isAbsolute(segment) || /^\.\.?[\/\\]/.test(segment); @@ -230,7 +244,7 @@ export class QueryBuilder { return expandGlobalGlob(p); }); - const result: ISearchPathsResult = {}; + const result: ISearchPathsInfo = {}; const searchPaths = this.expandSearchPathPatterns(groups.searchPaths || []); if (searchPaths && searchPaths.length) { result.searchPaths = searchPaths; @@ -365,7 +379,7 @@ export class QueryBuilder { return results; } - private getFolderQueryForSearchPath(searchPath: ISearchPathPattern, options: ICommonQueryBuilderOptions, searchPathExcludes: ISearchPathsResult): IFolderQuery | null { + private getFolderQueryForSearchPath(searchPath: ISearchPathPattern, options: ICommonQueryBuilderOptions, searchPathExcludes: ISearchPathsInfo): IFolderQuery | null { const rootConfig = this.getFolderQueryForRoot(searchPath.searchPath, options, searchPathExcludes); if (!rootConfig) { return null; @@ -379,7 +393,7 @@ export class QueryBuilder { }; } - private getFolderQueryForRoot(folder: uri, options: ICommonQueryBuilderOptions, searchPathExcludes: ISearchPathsResult): IFolderQuery | null { + private getFolderQueryForRoot(folder: uri, options: ICommonQueryBuilderOptions, searchPathExcludes: ISearchPathsInfo): IFolderQuery | null { let thisFolderExcludeSearchPathPattern: glob.IExpression | undefined; if (searchPathExcludes.searchPaths) { const thisFolderExcludeSearchPath = searchPathExcludes.searchPaths.filter(sp => isEqual(sp.searchPath, folder))[0]; diff --git a/src/vs/workbench/contrib/search/common/searchModel.ts b/src/vs/workbench/contrib/search/common/searchModel.ts index 03efb83863e..d2dfc6b190f 100644 --- a/src/vs/workbench/contrib/search/common/searchModel.ts +++ b/src/vs/workbench/contrib/search/common/searchModel.ts @@ -79,6 +79,7 @@ export class Match { after = this._oneLinePreviewText.substring(this._rangeInPreviewText.endColumn - 1); before = lcut(before, 26); + before = before.trimLeft(); let charsRemaining = Match.MAX_PREVIEW_CHARS - before.length; inside = inside.substr(0, charsRemaining); diff --git a/src/vs/workbench/contrib/search/test/common/queryBuilder.test.ts b/src/vs/workbench/contrib/search/test/common/queryBuilder.test.ts index 6fc562d90e6..c9ba2dae50a 100644 --- a/src/vs/workbench/contrib/search/test/common/queryBuilder.test.ts +++ b/src/vs/workbench/contrib/search/test/common/queryBuilder.test.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; import { IExpression } from 'vs/base/common/glob'; -import * as extpath from 'vs/base/common/extpath'; +import { join } from 'vs/base/common/path'; import { URI as uri } from 'vs/base/common/uri'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService'; @@ -12,7 +12,7 @@ import { IEnvironmentService } from 'vs/platform/environment/common/environment' import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; import { IFolderQuery, IPatternInfo, QueryType, ITextQuery, IFileQuery } from 'vs/workbench/services/search/common/search'; import { IWorkspaceContextService, toWorkspaceFolders, Workspace } from 'vs/platform/workspace/common/workspace'; -import { ISearchPathsResult, QueryBuilder } from 'vs/workbench/contrib/search/common/queryBuilder'; +import { ISearchPathsInfo, QueryBuilder } from 'vs/workbench/contrib/search/common/queryBuilder'; import { TestContextService, TestEnvironmentService } from 'vs/workbench/test/workbenchTestServices'; const DEFAULT_EDITOR_CONFIG = {}; @@ -86,7 +86,10 @@ suite('QueryBuilder', () => { assertEqualTextQueries( queryBuilder.text( PATTERN_INFO, - [ROOT_1_URI] + [ROOT_1_URI], + { + expandPatterns: true // verify that this doesn't affect patterns from configuration + } ), { contentPattern: PATTERN_INFO, @@ -108,7 +111,53 @@ suite('QueryBuilder', () => { queryBuilder.text( PATTERN_INFO, [ROOT_1_URI], - { includePattern: './bar' } + { + includePattern: 'bar', + expandPatterns: true + } + ), + { + contentPattern: PATTERN_INFO, + folderQueries: [{ + folder: ROOT_1_URI + }], + includePattern: { + '**/bar': true, + '**/bar/**': true + }, + type: QueryType.Text + }); + + assertEqualTextQueries( + queryBuilder.text( + PATTERN_INFO, + [ROOT_1_URI], + { + includePattern: 'bar' + } + ), + { + contentPattern: PATTERN_INFO, + folderQueries: [{ + folder: ROOT_1_URI + }], + includePattern: { + 'bar': true + }, + type: QueryType.Text + }); + }); + + test('simple include with ./ syntax', () => { + + assertEqualTextQueries( + queryBuilder.text( + PATTERN_INFO, + [ROOT_1_URI], + { + includePattern: './bar', + expandPatterns: true + } ), { contentPattern: PATTERN_INFO, @@ -126,7 +175,10 @@ suite('QueryBuilder', () => { queryBuilder.text( PATTERN_INFO, [ROOT_1_URI], - { includePattern: '.\\bar' } + { + includePattern: '.\\bar', + expandPatterns: true + } ), { contentPattern: PATTERN_INFO, @@ -156,7 +208,10 @@ suite('QueryBuilder', () => { queryBuilder.text( PATTERN_INFO, [ROOT_1_URI], - { includePattern: './foo' } + { + includePattern: './foo', + expandPatterns: true + } ), { contentPattern: PATTERN_INFO, @@ -217,7 +272,10 @@ suite('QueryBuilder', () => { queryBuilder.text( PATTERN_INFO, [ROOT_1_URI, ROOT_2_URI, ROOT_3_URI], - { includePattern: './root2/src' } + { + includePattern: './root2/src', + expandPatterns: true + } ), { contentPattern: PATTERN_INFO, @@ -243,7 +301,10 @@ suite('QueryBuilder', () => { queryBuilder.text( PATTERN_INFO, [ROOT_1_URI], - { excludePattern: 'foo' } + { + excludePattern: 'foo', + expandPatterns: true + } ), { contentPattern: PATTERN_INFO, @@ -274,7 +335,10 @@ suite('QueryBuilder', () => { queryBuilder.text( PATTERN_INFO, [ROOT_1_URI], - { excludePattern: './bar' } + { + excludePattern: './bar', + expandPatterns: true + } ), { contentPattern: PATTERN_INFO, @@ -289,7 +353,10 @@ suite('QueryBuilder', () => { queryBuilder.text( PATTERN_INFO, [ROOT_1_URI], - { excludePattern: './bar/**/*.ts' } + { + excludePattern: './bar/**/*.ts', + expandPatterns: true + } ), { contentPattern: PATTERN_INFO, @@ -304,7 +371,10 @@ suite('QueryBuilder', () => { queryBuilder.text( PATTERN_INFO, [ROOT_1_URI], - { excludePattern: '.\\bar\\**\\*.ts' } + { + excludePattern: '.\\bar\\**\\*.ts', + expandPatterns: true + } ), { contentPattern: PATTERN_INFO, @@ -338,7 +408,8 @@ suite('QueryBuilder', () => { [ROOT_1_URI], { extraFileResources: [getUri('/foo/bar.js')], - excludePattern: '*.js' + excludePattern: '*.js', + expandPatterns: true } ), { @@ -356,7 +427,8 @@ suite('QueryBuilder', () => { [ROOT_1_URI], { extraFileResources: [getUri('/foo/bar.js')], - includePattern: '*.txt' + includePattern: '*.txt', + expandPatterns: true } ), { @@ -390,19 +462,19 @@ suite('QueryBuilder', () => { ].forEach(([includePattern, expectedPatterns]) => testSimpleIncludes(includePattern, expectedPatterns)); }); - function testIncludes(includePattern: string, expectedResult: ISearchPathsResult): void { + function testIncludes(includePattern: string, expectedResult: ISearchPathsInfo): void { assertEqualSearchPathResults( queryBuilder.parseSearchPaths(includePattern), expectedResult, includePattern); } - function testIncludesDataItem([includePattern, expectedResult]: [string, ISearchPathsResult]): void { + function testIncludesDataItem([includePattern, expectedResult]: [string, ISearchPathsInfo]): void { testIncludes(includePattern, expectedResult); } test('absolute includes', () => { - const cases: [string, ISearchPathsResult][] = [ + const cases: [string, ISearchPathsInfo][] = [ [ fixPath('/foo/bar'), { @@ -472,7 +544,7 @@ suite('QueryBuilder', () => { test('includes with tilde', () => { const userHome = TestEnvironmentService.userHome; - const cases: [string, ISearchPathsResult][] = [ + const cases: [string, ISearchPathsInfo][] = [ [ '~/foo/bar', { @@ -497,7 +569,7 @@ suite('QueryBuilder', () => { }); test('relative includes w/single root folder', () => { - const cases: [string, ISearchPathsResult][] = [ + const cases: [string, ISearchPathsInfo][] = [ [ './a', { @@ -565,7 +637,7 @@ suite('QueryBuilder', () => { mockWorkspace.folders = toWorkspaceFolders([{ path: ROOT_1_URI.fsPath }, { path: getUri(ROOT_2).fsPath }]); mockWorkspace.configuration = uri.file(fixPath('config')); - const cases: [string, ISearchPathsResult][] = [ + const cases: [string, ISearchPathsInfo][] = [ [ './root1', { @@ -606,7 +678,7 @@ suite('QueryBuilder', () => { mockWorkspace.folders = toWorkspaceFolders([{ path: ROOT_1_URI.fsPath, name: ROOT_1_FOLDERNAME }, { path: getUri(ROOT_2).fsPath }]); mockWorkspace.configuration = uri.file(fixPath('config')); - const cases: [string, ISearchPathsResult][] = [ + const cases: [string, ISearchPathsInfo][] = [ [ './foldername', { @@ -634,7 +706,7 @@ suite('QueryBuilder', () => { mockWorkspace.folders = toWorkspaceFolders([{ path: ROOT_1_URI.fsPath }, { path: getUri(ROOT_2).fsPath }, { path: getUri(ROOT_3).fsPath }]); mockWorkspace.configuration = uri.file(fixPath('/config')); - const cases: [string, ISearchPathsResult][] = [ + const cases: [string, ISearchPathsInfo][] = [ [ '', { @@ -856,7 +928,7 @@ function assertEqualQueries(actual: ITextQuery | IFileQuery, expected: ITextQuer assert.deepEqual(actual, expected); } -function assertEqualSearchPathResults(actual: ISearchPathsResult, expected: ISearchPathsResult, message?: string): void { +function assertEqualSearchPathResults(actual: ISearchPathsInfo, expected: ISearchPathsInfo, message?: string): void { cleanUndefinedQueryValues(actual); assert.deepEqual(actual.pattern, expected.pattern, message); @@ -908,7 +980,7 @@ function fixPath(...slashPathParts: string[]): string { slashPathParts.unshift('c:'); } - return extpath.join(...slashPathParts); + return join(...slashPathParts); } function normalizeExpression(expression: IExpression | undefined): IExpression | undefined { diff --git a/src/vs/workbench/contrib/snippets/browser/configureSnippets.ts b/src/vs/workbench/contrib/snippets/browser/configureSnippets.ts index e2146dd406a..cc1f8bd6bc4 100644 --- a/src/vs/workbench/contrib/snippets/browser/configureSnippets.ts +++ b/src/vs/workbench/contrib/snippets/browser/configureSnippets.ts @@ -8,8 +8,7 @@ import { CommandsRegistry } from 'vs/platform/commands/common/commands'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { IModeService } from 'vs/editor/common/services/modeService'; import { IWindowService } from 'vs/platform/windows/common/windows'; -import { join } from 'vs/base/common/extpath'; -import { basename, dirname, extname } from 'vs/base/common/path'; +import { join, basename, dirname, extname } from 'vs/base/common/path'; import { MenuRegistry, MenuId } from 'vs/platform/actions/common/actions'; import { timeout } from 'vs/base/common/async'; import { IOpenerService } from 'vs/platform/opener/common/opener'; diff --git a/src/vs/workbench/contrib/snippets/browser/snippetsService.ts b/src/vs/workbench/contrib/snippets/browser/snippetsService.ts index 4269f654647..797c5f36c9a 100644 --- a/src/vs/workbench/contrib/snippets/browser/snippetsService.ts +++ b/src/vs/workbench/contrib/snippets/browser/snippetsService.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { join } from 'vs/base/common/extpath'; +import { join } from 'vs/base/common/path'; import { IJSONSchema } from 'vs/base/common/jsonSchema'; import { combinedDisposable, dispose, IDisposable } from 'vs/base/common/lifecycle'; import { values } from 'vs/base/common/map'; diff --git a/src/vs/workbench/contrib/splash/electron-browser/partsSplash.contribution.ts b/src/vs/workbench/contrib/splash/electron-browser/partsSplash.contribution.ts index 51631ae783d..b03134e1686 100644 --- a/src/vs/workbench/contrib/splash/electron-browser/partsSplash.contribution.ts +++ b/src/vs/workbench/contrib/splash/electron-browser/partsSplash.contribution.ts @@ -3,6 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { join } from 'vs/base/common/path'; import { onDidChangeFullscreen, isFullscreen } from 'vs/base/browser/browser'; import { getTotalHeight, getTotalWidth } from 'vs/base/browser/dom'; import { Color } from 'vs/base/common/color'; @@ -20,7 +21,6 @@ import { IPartService, Parts, Position } from 'vs/workbench/services/part/common import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { IFileService } from 'vs/platform/files/common/files'; import { URI } from 'vs/base/common/uri'; -import { join } from 'vs/base/common/extpath'; class PartsSplash { diff --git a/src/vs/workbench/contrib/tasks/common/problemMatcher.ts b/src/vs/workbench/contrib/tasks/common/problemMatcher.ts index f68a521b33a..62198ca45a0 100644 --- a/src/vs/workbench/contrib/tasks/common/problemMatcher.ts +++ b/src/vs/workbench/contrib/tasks/common/problemMatcher.ts @@ -8,7 +8,7 @@ import { localize } from 'vs/nls'; import * as Objects from 'vs/base/common/objects'; import * as Strings from 'vs/base/common/strings'; import * as Assert from 'vs/base/common/assert'; -import * as Extpath from 'vs/base/common/extpath'; +import { join } from 'vs/base/common/path'; import * as Types from 'vs/base/common/types'; import * as UUID from 'vs/base/common/uuid'; import * as Platform from 'vs/base/common/platform'; @@ -188,7 +188,7 @@ export function getResource(filename: string, matcher: ProblemMatcher): URI { if (kind === FileLocationKind.Absolute) { fullPath = filename; } else if ((kind === FileLocationKind.Relative) && matcher.filePrefix) { - fullPath = Extpath.join(matcher.filePrefix, filename); + fullPath = join(matcher.filePrefix, filename); } if (fullPath === undefined) { throw new Error('FileLocationKind is not actionable. Does the matcher have a filePrefix? This should never happen.'); diff --git a/src/vs/workbench/contrib/welcome/walkThrough/electron-browser/walkThroughPart.css b/src/vs/workbench/contrib/welcome/walkThrough/electron-browser/walkThroughPart.css index 401db65c7be..0d4122ccdff 100644 --- a/src/vs/workbench/contrib/welcome/walkThrough/electron-browser/walkThroughPart.css +++ b/src/vs/workbench/contrib/welcome/walkThrough/electron-browser/walkThroughPart.css @@ -98,7 +98,7 @@ .monaco-workbench .part.editor > .content .walkThroughContent code, .monaco-workbench .part.editor > .content .walkThroughContent .shortcut { - font-family: Menlo, Monaco, Consolas, "Droid Sans Mono", "Courier New", monospace, "Droid Sans Fallback"; + font-family: var(--monaco-monospace-font); font-size: 14px; line-height: 19px; } diff --git a/src/vs/workbench/electron-browser/actions/developerActions.ts b/src/vs/workbench/electron-browser/actions/developerActions.ts index 8b9c5485b63..3e5d7fbddda 100644 --- a/src/vs/workbench/electron-browser/actions/developerActions.ts +++ b/src/vs/workbench/electron-browser/actions/developerActions.ts @@ -15,6 +15,7 @@ import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { Context } from 'vs/platform/contextkey/browser/contextKeyService'; import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { timeout } from 'vs/base/common/async'; +import { IPartService } from 'vs/workbench/services/part/common/partService'; export class ToggleDevToolsAction extends Action { @@ -115,7 +116,12 @@ export class ToggleScreencastModeAction extends Action { static disposable: IDisposable | undefined; - constructor(id: string, label: string, @IKeybindingService private readonly keybindingService: IKeybindingService) { + constructor( + id: string, + label: string, + @IKeybindingService private readonly keybindingService: IKeybindingService, + @IPartService private readonly partService: IPartService + ) { super(id, label); } @@ -126,7 +132,9 @@ export class ToggleScreencastModeAction extends Action { return; } - const mouseMarker = append(document.body, $('div')); + const container = this.partService.getWorkbenchElement(); + + const mouseMarker = append(container, $('div')); mouseMarker.style.position = 'absolute'; mouseMarker.style.border = '2px solid red'; mouseMarker.style.borderRadius = '20px'; @@ -139,9 +147,9 @@ export class ToggleScreencastModeAction extends Action { mouseMarker.style.pointerEvents = 'none'; mouseMarker.style.display = 'none'; - const onMouseDown = domEvent(document.body, 'mousedown', true); - const onMouseUp = domEvent(document.body, 'mouseup', true); - const onMouseMove = domEvent(document.body, 'mousemove', true); + const onMouseDown = domEvent(container, 'mousedown', true); + const onMouseUp = domEvent(container, 'mouseup', true); + const onMouseMove = domEvent(container, 'mousemove', true); const mouseListener = onMouseDown(e => { mouseMarker.style.top = `${e.clientY - 10}px`; @@ -159,7 +167,7 @@ export class ToggleScreencastModeAction extends Action { }); }); - const keyboardMarker = append(document.body, $('div')); + const keyboardMarker = append(container, $('div')); keyboardMarker.style.position = 'absolute'; keyboardMarker.style.backgroundColor = 'rgba(0, 0, 0 ,0.5)'; keyboardMarker.style.width = '100%'; @@ -174,7 +182,7 @@ export class ToggleScreencastModeAction extends Action { keyboardMarker.style.fontSize = '56px'; keyboardMarker.style.display = 'none'; - const onKeyDown = domEvent(document.body, 'keydown', true); + const onKeyDown = domEvent(container, 'keydown', true); let keyboardTimeout: IDisposable = Disposable.None; const keyboardListener = onKeyDown(e => { @@ -204,7 +212,8 @@ export class ToggleScreencastModeAction extends Action { ToggleScreencastModeAction.disposable = toDisposable(() => { mouseListener.dispose(); keyboardListener.dispose(); - document.body.removeChild(mouseMarker); + mouseMarker.remove(); + keyboardMarker.remove(); }); } } diff --git a/src/vs/workbench/electron-browser/main.contribution.ts b/src/vs/workbench/electron-browser/main.contribution.ts index d7445124b53..fbd0dcd4a15 100644 --- a/src/vs/workbench/electron-browser/main.contribution.ts +++ b/src/vs/workbench/electron-browser/main.contribution.ts @@ -597,7 +597,7 @@ configurationRegistry.registerConfiguration({ 'window.titleBarStyle': { 'type': 'string', 'enum': ['native', 'custom'], - 'default': 'custom', + 'default': isLinux ? 'native' : 'custom', 'scope': ConfigurationScope.APPLICATION, 'description': nls.localize('titleBarStyle', "Adjust the appearance of the window title bar. Changes require a full restart to apply.") }, diff --git a/src/vs/workbench/electron-browser/main.ts b/src/vs/workbench/electron-browser/main.ts index bb8d71438ef..74d374d483b 100644 --- a/src/vs/workbench/electron-browser/main.ts +++ b/src/vs/workbench/electron-browser/main.ts @@ -3,6 +3,8 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import * as fs from 'fs'; +import { createHash } from 'crypto'; import * as nls from 'vs/nls'; import * as perf from 'vs/base/common/performance'; import { Workbench } from 'vs/workbench/electron-browser/workbench'; @@ -32,7 +34,6 @@ import { IURLService } from 'vs/platform/url/common/url'; import { WorkspacesChannelClient } from 'vs/platform/workspaces/node/workspacesIpc'; import { IWorkspacesService, ISingleFolderWorkspaceIdentifier, IWorkspaceInitializationPayload, IMultiFolderWorkspaceInitializationPayload, IEmptyWorkspaceInitializationPayload, ISingleFolderWorkspaceInitializationPayload, reviveWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces'; import { createSpdLogService } from 'vs/platform/log/node/spdlogService'; -import * as fs from 'fs'; import { ConsoleLogService, MultiplexLogService, ILogService } from 'vs/platform/log/common/log'; import { StorageService } from 'vs/platform/storage/node/storageService'; import { IssueChannelClient } from 'vs/platform/issue/node/issueIpc'; @@ -44,7 +45,6 @@ import { IMenubarService } from 'vs/platform/menubar/common/menubar'; import { Schemas } from 'vs/base/common/network'; import { sanitizeFilePath } from 'vs/base/node/extfs'; import { basename } from 'vs/base/common/path'; -import { createHash } from 'crypto'; import { IdleValue } from 'vs/base/common/async'; import { setGlobalLeakWarningThreshold } from 'vs/base/common/event'; import { GlobalStorageDatabaseChannelClient } from 'vs/platform/storage/node/storageIpc'; @@ -53,6 +53,7 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur import { IStorageService } from 'vs/platform/storage/common/storage'; import { InstantiationService } from 'vs/platform/instantiation/common/instantiationService'; import { Disposable } from 'vs/base/common/lifecycle'; +import { registerWindowDriver } from 'vs/platform/driver/electron-browser/driver'; export class CodeWindow extends Disposable { @@ -93,6 +94,15 @@ export class CodeWindow extends Disposable { collatorIsNumeric: collator.resolvedOptions().numeric }; })); + + // Inform user about loading issues from the loader + (self).require.config({ + onError: err => { + if (err.errorCode === 'load') { + onUnexpectedError(new Error(nls.localize('loaderErrorNative', "Failed to load a required file. Please restart the application to try again. Details: {0}", JSON.stringify(err)))); + } + } + }); } private reviveUris() { @@ -116,9 +126,9 @@ export class CodeWindow extends Disposable { } open(): Promise { - const mainProcessClient = this._register(new ElectronIPCClient(`window:${this.configuration.windowId}`)); + const electronMainClient = this._register(new ElectronIPCClient(`window:${this.configuration.windowId}`)); - return this.initServices(mainProcessClient).then(services => { + return this.initServices(electronMainClient).then(services => { return domContentLoaded().then(() => { perf.mark('willStartWorkbench'); @@ -130,8 +140,7 @@ export class CodeWindow extends Disposable { Workbench, document.body, this.configuration, - services, - mainProcessClient + services ); // Workbench Lifecycle @@ -144,49 +153,45 @@ export class CodeWindow extends Disposable { // Window this._register(instantiationService.createInstance(ElectronWindow)); - // Inform user about loading issues from the loader - (self).require.config({ - onError: err => { - if (err.errorCode === 'load') { - onUnexpectedError(new Error(nls.localize('loaderErrorNative', "Failed to load a required file. Please restart the application to try again. Details: {0}", JSON.stringify(err)))); - } - } - }); + // Driver + if (this.configuration.driverHandle) { + registerWindowDriver(electronMainClient, this.configuration.windowId, instantiationService).then(disposable => this._register(disposable)); + } }); }); } - private initServices(mainProcessClient: ElectronIPCClient): Promise { + private initServices(electronMainClient: ElectronIPCClient): Promise { const serviceCollection = new ServiceCollection(); - // Windows Channel - const windowsChannel = mainProcessClient.getChannel('windows'); + // Windows Service + const windowsChannel = electronMainClient.getChannel('windows'); serviceCollection.set(IWindowsService, new WindowsChannelClient(windowsChannel)); - // Update Channel - const updateChannel = mainProcessClient.getChannel('update'); + // Update Service + const updateChannel = electronMainClient.getChannel('update'); serviceCollection.set(IUpdateService, new SyncDescriptor(UpdateChannelClient, [updateChannel])); - // URL Channel - const urlChannel = mainProcessClient.getChannel('url'); + // URL Service + const urlChannel = electronMainClient.getChannel('url'); const mainUrlService = new URLServiceChannelClient(urlChannel); const urlService = new RelayURLService(mainUrlService); serviceCollection.set(IURLService, urlService); - // URLHandler Channel + // URLHandler Service const urlHandlerChannel = new URLHandlerChannel(urlService); - mainProcessClient.registerChannel('urlHandler', urlHandlerChannel); + electronMainClient.registerChannel('urlHandler', urlHandlerChannel); - // Issue Channel - const issueChannel = mainProcessClient.getChannel('issue'); + // Issue Service + const issueChannel = electronMainClient.getChannel('issue'); serviceCollection.set(IIssueService, new SyncDescriptor(IssueChannelClient, [issueChannel])); - // Menubar Channel - const menubarChannel = mainProcessClient.getChannel('menubar'); + // Menubar Service + const menubarChannel = electronMainClient.getChannel('menubar'); serviceCollection.set(IMenubarService, new SyncDescriptor(MenubarChannelClient, [menubarChannel])); - // Workspaces Channel - const workspacesChannel = mainProcessClient.getChannel('workspaces'); + // Workspaces Service + const workspacesChannel = electronMainClient.getChannel('workspaces'); serviceCollection.set(IWorkspacesService, new WorkspacesChannelClient(workspacesChannel)); // Environment @@ -194,7 +199,7 @@ export class CodeWindow extends Disposable { serviceCollection.set(IEnvironmentService, environmentService); // Log - const logService = this._register(this.createLogService(mainProcessClient, environmentService)); + const logService = this._register(this.createLogService(electronMainClient, environmentService)); serviceCollection.set(ILogService, logService); // Resolve a workspace payload that we can get the workspace ID from @@ -206,7 +211,7 @@ export class CodeWindow extends Disposable { this.createWorkspaceService(payload, environmentService, logService), // Create and initialize storage service - this.createStorageService(payload, environmentService, logService, mainProcessClient) + this.createStorageService(payload, environmentService, logService, electronMainClient) ]).then(services => { serviceCollection.set(IWorkspaceContextService, services[0]); serviceCollection.set(IConfigurationService, services[0]); diff --git a/src/vs/workbench/electron-browser/workbench.ts b/src/vs/workbench/electron-browser/workbench.ts index f4cfb45120b..63267bb3061 100644 --- a/src/vs/workbench/electron-browser/workbench.ts +++ b/src/vs/workbench/electron-browser/workbench.ts @@ -145,7 +145,7 @@ import { BackupFileService, InMemoryBackupFileService } from 'vs/workbench/servi import { WorkspaceService, DefaultConfigurationExportHelper } from 'vs/workbench/services/configuration/node/configurationService'; import { JSONEditingService } from 'vs/workbench/services/configuration/node/jsonEditingService'; import { WorkspaceEditingService } from 'vs/workbench/services/workspace/node/workspaceEditingService'; -import { IPCClient, getDelayedChannel } from 'vs/base/parts/ipc/node/ipc'; +import { getDelayedChannel } from 'vs/base/parts/ipc/node/ipc'; import { LogStorageAction } from 'vs/platform/storage/node/storageService'; import { HashService } from 'vs/workbench/services/hash/node/hashService'; import { connect as connectNet } from 'vs/base/parts/ipc/node/ipc.net'; @@ -173,7 +173,6 @@ import { RemoteFileService } from 'vs/workbench/services/files/electron-browser/ import { ClipboardService } from 'vs/platform/clipboard/electron-browser/clipboardService'; import { LifecycleService } from 'vs/platform/lifecycle/electron-browser/lifecycleService'; import { ToggleDevToolsAction } from 'vs/workbench/electron-browser/actions/developerActions'; -import { registerWindowDriver } from 'vs/platform/driver/electron-browser/driver'; import { IExtensionUrlHandler, ExtensionUrlHandler } from 'vs/workbench/services/extensions/electron-browser/inactiveExtensionUrlHandler'; import { WorkbenchThemeService } from 'vs/workbench/services/themes/browser/workbenchThemeService'; import { DialogService, FileDialogService } from 'vs/workbench/services/dialogs/electron-browser/dialogService'; @@ -187,11 +186,6 @@ import { TextResourcePropertiesService } from 'vs/workbench/services/textfile/el import { ITextMateService } from 'vs/workbench/services/textMate/electron-browser/textMateService'; import { TextMateService } from 'vs/workbench/services/textMate/electron-browser/TMSyntax'; -interface WorkbenchParams { - configuration: IWindowConfiguration; - serviceCollection: ServiceCollection; -} - interface IZenModeSettings { fullScreen: boolean; centerLayout: boolean; @@ -260,18 +254,17 @@ export class Workbench extends Disposable implements IPartService { private static readonly closeWhenEmptyConfigurationKey = 'window.closeWhenEmpty'; private static readonly fontAliasingConfigurationKey = 'workbench.fontAliasing'; + _serviceBrand: any; + private readonly _onShutdown = this._register(new Emitter()); get onShutdown(): Event { return this._onShutdown.event; } private readonly _onWillShutdown = this._register(new Emitter()); get onWillShutdown(): Event { return this._onWillShutdown.event; } - _serviceBrand: any; - private previousErrorValue: string; private previousErrorTime: number = 0; - private workbenchParams: WorkbenchParams; private workbench: HTMLElement; private workbenchStarted: boolean; private workbenchRestored: boolean; @@ -338,8 +331,7 @@ export class Workbench extends Disposable implements IPartService { constructor( private container: HTMLElement, private configuration: IWindowConfiguration, - serviceCollection: ServiceCollection, - private mainProcessClient: IPCClient, + private serviceCollection: ServiceCollection, @IInstantiationService private readonly instantiationService: IInstantiationService, @IWorkspaceContextService private readonly contextService: IWorkspaceContextService, @IStorageService private readonly storageService: IStorageService, @@ -350,8 +342,6 @@ export class Workbench extends Disposable implements IPartService { ) { super(); - this.workbenchParams = { configuration, serviceCollection }; - this.hasInitialFilesToOpen = !!( (configuration.filesToCreate && configuration.filesToCreate.length > 0) || (configuration.filesToOpen && configuration.filesToOpen.length > 0) || @@ -399,9 +389,9 @@ export class Workbench extends Disposable implements IPartService { } } - startup(): Promise { + startup(): void { try { - return this.doStartup().then(undefined, error => this.logService.error(toErrorMessage(error, true))); + this.doStartup().then(undefined, error => this.logService.error(toErrorMessage(error, true))); } catch (error) { this.logService.error(toErrorMessage(error, true)); @@ -432,7 +422,7 @@ export class Workbench extends Disposable implements IPartService { this.createGlobalActions(); // Services - this.initServices(); + this.initServices(this.serviceCollection); // Context Keys this.handleContextKeys(); @@ -452,11 +442,6 @@ export class Workbench extends Disposable implements IPartService { // Layout this.layout(); - // Driver - if (this.environmentService.driverHandle) { - registerWindowDriver(this.mainProcessClient, this.configuration.windowId, this.instantiationService).then(disposable => this._register(disposable)); - } - // Handle case where workbench is not starting up properly const timeoutHandle = setTimeout(() => { this.logService.warn('Workbench did not finish loading in 10 seconds, that might be a problem that should be reported.'); @@ -500,8 +485,7 @@ export class Workbench extends Disposable implements IPartService { } } - private initServices(): void { - const { serviceCollection } = this.workbenchParams; + private initServices(serviceCollection: ServiceCollection): void { // Parts serviceCollection.set(IPartService, this); @@ -731,8 +715,8 @@ export class Workbench extends Disposable implements IPartService { serviceCollection.set(IFileDialogService, new SyncDescriptor(FileDialogService)); // Backup File Service - if (this.workbenchParams.configuration.backupPath) { - this.backupFileService = this.instantiationService.createInstance(BackupFileService, this.workbenchParams.configuration.backupPath); + if (this.configuration.backupPath) { + this.backupFileService = this.instantiationService.createInstance(BackupFileService, this.configuration.backupPath); } else { this.backupFileService = new InMemoryBackupFileService(); } @@ -804,7 +788,7 @@ export class Workbench extends Disposable implements IPartService { this._register(this.editorPart.onDidActivateGroup(() => { if (this.editorHidden) { this.setEditorHidden(false); } })); // Listen to editor closing (if we run with --wait) - const filesToWait = this.workbenchParams.configuration.filesToWait; + const filesToWait = this.configuration.filesToWait; if (filesToWait) { const resourcesToWaitFor = filesToWait.paths.map(p => p.fileUri); const waitMarkerFile = URI.file(filesToWait.waitMarkerFilePath); @@ -955,11 +939,11 @@ export class Workbench extends Disposable implements IPartService { IsMacContext.bindTo(this.contextKeyService); IsLinuxContext.bindTo(this.contextKeyService); IsWindowsContext.bindTo(this.contextKeyService); + SupportsWorkspacesContext.bindTo(this.contextKeyService); const supportsOpenFileFolderContextKey = SupportsOpenFileFolderContext.bindTo(this.contextKeyService); - const supportsWorkspacesContextKey = SupportsWorkspacesContext.bindTo(this.contextKeyService); + SupportsWorkspacesContext.bindTo(this.contextKeyService); if (this.windowService.getConfiguration().remoteAuthority) { supportsOpenFileFolderContextKey.set(true); - supportsWorkspacesContextKey.set(false); } const sidebarVisibleContextRaw = new RawContextKey('sidebarVisible', false); @@ -1209,13 +1193,12 @@ export class Workbench extends Disposable implements IPartService { } private resolveEditorsToOpen(): Promise | IResourceEditor[] { - const config = this.workbenchParams.configuration; // Files to open, diff or create if (this.hasInitialFilesToOpen) { // Files to diff is exclusive - const filesToDiff = this.toInputs(config.filesToDiff, false); + const filesToDiff = this.toInputs(this.configuration.filesToDiff, false); if (filesToDiff && filesToDiff.length === 2) { return [{ leftResource: filesToDiff[0].resource, @@ -1225,8 +1208,8 @@ export class Workbench extends Disposable implements IPartService { }]; } - const filesToCreate = this.toInputs(config.filesToCreate, true); - const filesToOpen = this.toInputs(config.filesToOpen, false); + const filesToCreate = this.toInputs(this.configuration.filesToCreate, true); + const filesToOpen = this.toInputs(this.configuration.filesToOpen, false); // Otherwise: Open/Create files return [...filesToOpen, ...filesToCreate]; @@ -1754,29 +1737,16 @@ export class Workbench extends Disposable implements IPartService { } } - private gridHasView(view: View): boolean { - if (!(this.workbenchGrid instanceof Grid)) { - return false; - } - - try { - this.workbenchGrid.getViewSize(view); - return true; - } catch { - return false; - } - } - private updateGrid(): void { if (!(this.workbenchGrid instanceof Grid)) { return; } - let panelInGrid = this.gridHasView(this.panelPartView); - let sidebarInGrid = this.gridHasView(this.sidebarPartView); - let activityBarInGrid = this.gridHasView(this.activitybarPartView); - let statusBarInGrid = this.gridHasView(this.statusbarPartView); - let titlebarInGrid = this.gridHasView(this.titlebarPartView); + let panelInGrid = this.workbenchGrid.hasView(this.panelPartView); + let sidebarInGrid = this.workbenchGrid.hasView(this.sidebarPartView); + let activityBarInGrid = this.workbenchGrid.hasView(this.activitybarPartView); + let statusBarInGrid = this.workbenchGrid.hasView(this.statusbarPartView); + let titlebarInGrid = this.workbenchGrid.hasView(this.titlebarPartView); // Add parts to grid if (!statusBarInGrid) { diff --git a/src/vs/workbench/services/configuration/common/configurationModels.ts b/src/vs/workbench/services/configuration/common/configurationModels.ts index a943fab87fe..92a3834f1d9 100644 --- a/src/vs/workbench/services/configuration/common/configurationModels.ts +++ b/src/vs/workbench/services/configuration/common/configurationModels.ts @@ -256,7 +256,7 @@ export class AllKeysConfigurationChangeEvent extends AbstractConfigurationChange export class WorkspaceConfigurationChangeEvent implements IConfigurationChangeEvent { - constructor(private configurationChangeEvent: IConfigurationChangeEvent, private workspace: Workspace) { } + constructor(private configurationChangeEvent: IConfigurationChangeEvent, private workspace: Workspace | undefined) { } get changedConfiguration(): IConfigurationModel { return this.configurationChangeEvent.changedConfiguration; diff --git a/src/vs/workbench/services/configuration/node/configuration.ts b/src/vs/workbench/services/configuration/node/configuration.ts index df26c2f9309..47c177df136 100644 --- a/src/vs/workbench/services/configuration/node/configuration.ts +++ b/src/vs/workbench/services/configuration/node/configuration.ts @@ -5,7 +5,6 @@ import { URI } from 'vs/base/common/uri'; import { createHash } from 'crypto'; -import * as extpath from 'vs/base/common/extpath'; import * as resources from 'vs/base/common/resources'; import { Event, Emitter } from 'vs/base/common/event'; import * as pfs from 'vs/base/node/pfs'; @@ -22,7 +21,7 @@ import * as extfs from 'vs/base/node/extfs'; import { JSONEditingService } from 'vs/workbench/services/configuration/node/jsonEditingService'; import { WorkbenchState, IWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; import { ConfigurationScope } from 'vs/platform/configuration/common/configurationRegistry'; -import { extname } from 'vs/base/common/path'; +import { extname, join } from 'vs/base/common/path'; import { equals } from 'vs/base/common/objects'; import { Schemas } from 'vs/base/common/network'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; @@ -36,7 +35,7 @@ export interface IWorkspaceIdentifier { export class WorkspaceConfiguration extends Disposable { private readonly _cachedConfiguration: CachedWorkspaceConfiguration; - private _workspaceConfiguration: IWorkspaceConfiguration | null; + private _workspaceConfiguration: IWorkspaceConfiguration; private _workspaceIdentifier: IWorkspaceIdentifier | null = null; private _fileService: IFileService | null = null; @@ -126,7 +125,7 @@ export class WorkspaceConfiguration extends Disposable { private updateCache(): Promise { if (this._workspaceIdentifier && this._workspaceIdentifier.configPath.scheme !== Schemas.file && this._workspaceConfiguration instanceof FileServiceBasedWorkspaceConfiguration) { return this._workspaceConfiguration.load(this._workspaceIdentifier) - .then(() => this._cachedConfiguration.updateWorkspace(this._workspaceIdentifier, this._workspaceConfiguration.getConfigurationModel())); + .then(() => this._cachedConfiguration.updateWorkspace(this._workspaceIdentifier!, this._workspaceConfiguration.getConfigurationModel())); } return Promise.resolve(undefined); } @@ -215,7 +214,7 @@ class FileServiceBasedWorkspaceConfiguration extends AbstractWorkspaceConfigurat constructor(private fileService: IFileService, from?: AbstractWorkspaceConfiguration) { super(from); - this.workspaceConfig = from ? from.workspaceIdentifier.configPath : null; + this.workspaceConfig = from && from.workspaceIdentifier ? from.workspaceIdentifier.configPath : null; this._register(fileService.onFileChanges(e => this.handleWorkspaceFileEvents(e))); this.reloadConfigurationScheduler = this._register(new RunOnceScheduler(() => this._onDidChange.fire(), 50)); } @@ -269,7 +268,7 @@ class CachedWorkspaceConfiguration extends Disposable implements IWorkspaceConfi this._workspaceConfigurationModelParser = new WorkspaceConfigurationModelParser(this.cachedConfigurationPath); this._workspaceConfigurationModelParser.parse(contents.toString()); this._workspaceSettings = this._workspaceConfigurationModelParser.settingsModel.merge(this._workspaceConfigurationModelParser.launchModel); - }, () => null); + }, () => { }); } getConfigurationModel(): ConfigurationModel { @@ -307,8 +306,8 @@ class CachedWorkspaceConfiguration extends Disposable implements IWorkspaceConfi } private createPaths(workspaceIdentifier: IWorkspaceIdentifier) { - this.cachedWorkspacePath = extpath.join(this.environmentService.userDataPath, 'CachedConfigurations', 'workspaces', workspaceIdentifier.id); - this.cachedConfigurationPath = extpath.join(this.cachedWorkspacePath, 'workspace.json'); + this.cachedWorkspacePath = join(this.environmentService.userDataPath, 'CachedConfigurations', 'workspaces', workspaceIdentifier.id); + this.cachedConfigurationPath = join(this.cachedWorkspacePath, 'workspace.json'); } } @@ -470,7 +469,7 @@ export class FileServiceBasedFolderConfiguration extends AbstractFolderConfigura } private doLoadFolderConfigurationContents(): Promise> { - const workspaceFilePathToConfiguration: { [relativeWorkspacePath: string]: Promise } = Object.create(null); + const workspaceFilePathToConfiguration: { [relativeWorkspacePath: string]: Promise } = Object.create(null); const bulkContentFetchromise = Promise.resolve(this.fileService.resolveFile(this.folderConfigurationPath)) .then(stat => { if (stat.isDirectory && stat.children) { @@ -485,7 +484,7 @@ export class FileServiceBasedFolderConfiguration extends AbstractFolderConfigura } }).then(undefined, err => [] /* never fail this call */); - return bulkContentFetchromise.then(() => Promise.all(collections.values(workspaceFilePathToConfiguration))); + return bulkContentFetchromise.then(() => Promise.all(collections.values(workspaceFilePathToConfiguration))).then(contents => contents.filter(content => content !== undefined)); } private handleWorkspaceFileEvents(event: FileChangesEvent): void { @@ -528,11 +527,11 @@ export class FileServiceBasedFolderConfiguration extends AbstractFolderConfigura } } - private toFolderRelativePath(resource: URI): string | null { + private toFolderRelativePath(resource: URI): string | undefined { if (resources.isEqualOrParent(resource, this.folderConfigurationPath)) { return resources.relativePath(this.folderConfigurationPath, resource); } - return null; + return undefined; } } @@ -552,8 +551,8 @@ export class CachedFolderConfiguration extends Disposable implements IFolderConf configFolderRelativePath: string, environmentService: IEnvironmentService) { super(); - this.cachedFolderPath = extpath.join(environmentService.userDataPath, 'CachedConfigurations', 'folders', createHash('md5').update(extpath.join(folder.path, configFolderRelativePath)).digest('hex')); - this.cachedConfigurationPath = extpath.join(this.cachedFolderPath, 'configuration.json'); + this.cachedFolderPath = join(environmentService.userDataPath, 'CachedConfigurations', 'folders', createHash('md5').update(join(folder.path, configFolderRelativePath)).digest('hex')); + this.cachedConfigurationPath = join(this.cachedFolderPath, 'configuration.json'); this.configurationModel = new ConfigurationModel(); } diff --git a/src/vs/workbench/services/configuration/node/configurationService.ts b/src/vs/workbench/services/configuration/node/configurationService.ts index ca8a08e4842..3686e240aa5 100644 --- a/src/vs/workbench/services/configuration/node/configurationService.ts +++ b/src/vs/workbench/services/configuration/node/configurationService.ts @@ -22,7 +22,7 @@ import { Configuration, WorkspaceConfigurationChangeEvent, AllKeysConfigurationC import { IWorkspaceConfigurationService, FOLDER_CONFIG_FOLDER_NAME, defaultSettingsSchemaId, userSettingsSchemaId, workspaceSettingsSchemaId, folderSettingsSchemaId } from 'vs/workbench/services/configuration/common/configuration'; import { Registry } from 'vs/platform/registry/common/platform'; import { IConfigurationNode, IConfigurationRegistry, Extensions, IConfigurationPropertySchema, allSettings, windowSettings, resourceSettings, applicationSettings } from 'vs/platform/configuration/common/configurationRegistry'; -import { IWorkspaceIdentifier, isWorkspaceIdentifier, IStoredWorkspaceFolder, isStoredWorkspaceFolder, IWorkspaceFolderCreationData, ISingleFolderWorkspaceIdentifier, isSingleFolderWorkspaceIdentifier, IWorkspaceInitializationPayload, isSingleFolderWorkspaceInitializationPayload, ISingleFolderWorkspaceInitializationPayload, IEmptyWorkspaceInitializationPayload, massageFolderPathForWorkspace } from 'vs/platform/workspaces/common/workspaces'; +import { IWorkspaceIdentifier, isWorkspaceIdentifier, IStoredWorkspaceFolder, isStoredWorkspaceFolder, IWorkspaceFolderCreationData, ISingleFolderWorkspaceIdentifier, isSingleFolderWorkspaceIdentifier, IWorkspaceInitializationPayload, isSingleFolderWorkspaceInitializationPayload, ISingleFolderWorkspaceInitializationPayload, IEmptyWorkspaceInitializationPayload, useSlashForPath, getStoredWorkspaceFolder } from 'vs/platform/workspaces/common/workspaces'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { ICommandService } from 'vs/platform/commands/common/commands'; import product from 'vs/platform/node/product'; @@ -30,7 +30,6 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { ConfigurationEditingService } from 'vs/workbench/services/configuration/node/configurationEditingService'; import { WorkspaceConfiguration, FolderConfiguration } from 'vs/workbench/services/configuration/node/configuration'; import { JSONEditingService } from 'vs/workbench/services/configuration/node/jsonEditingService'; -import { Schemas } from 'vs/base/common/network'; import { UserConfiguration } from 'vs/platform/configuration/node/configuration'; import { IJSONSchema, IJSONSchemaMap } from 'vs/base/common/jsonSchema'; import { localize } from 'vs/nls'; @@ -161,6 +160,8 @@ export class WorkspaceService extends Disposable implements IWorkspaceConfigurat return !this.contains(foldersToRemove, currentWorkspaceFolders[index].uri); // keep entries which are unrelated }); + const slashForPath = useSlashForPath(newStoredFolders); + foldersHaveChanged = currentWorkspaceFolders.length !== newStoredFolders.length; // Add afterwards (if any) @@ -174,31 +175,11 @@ export class WorkspaceService extends Disposable implements IWorkspaceConfigurat const storedFoldersToAdd: IStoredWorkspaceFolder[] = []; foldersToAdd.forEach(folderToAdd => { - if (this.contains(currentWorkspaceFolderUris, folderToAdd.uri)) { + const folderURI = folderToAdd.uri; + if (this.contains(currentWorkspaceFolderUris, folderURI)) { return; // already existing } - - let storedFolder: IStoredWorkspaceFolder; - - // File resource: use "path" property - if (folderToAdd.uri.scheme === Schemas.file) { - storedFolder = { - path: massageFolderPathForWorkspace(folderToAdd.uri.fsPath, workspaceConfigFolder, newStoredFolders) - }; - } - - // Any other resource: use "uri" property - else { - storedFolder = { - uri: folderToAdd.uri.toString(true) - }; - } - - if (folderToAdd.name) { - storedFolder.name = folderToAdd.name; - } - - storedFoldersToAdd.push(storedFolder); + storedFoldersToAdd.push(getStoredWorkspaceFolder(folderURI, folderToAdd.name, workspaceConfigFolder, slashForPath)); }); // Apply to array of newStoredFolders diff --git a/src/vs/workbench/services/configuration/test/common/configurationModels.test.ts b/src/vs/workbench/services/configuration/test/common/configurationModels.test.ts index 775383ee17a..49adc81b759 100644 --- a/src/vs/workbench/services/configuration/test/common/configurationModels.test.ts +++ b/src/vs/workbench/services/configuration/test/common/configurationModels.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import { join } from 'vs/base/common/extpath'; +import { join } from 'vs/base/common/path'; import { Registry } from 'vs/platform/registry/common/platform'; import { FolderSettingsModelParser, WorkspaceConfigurationChangeEvent, StandaloneConfigurationModelParser, AllKeysConfigurationChangeEvent, Configuration } from 'vs/workbench/services/configuration/common/configurationModels'; import { Workspace, WorkspaceFolder } from 'vs/platform/workspace/common/workspace'; diff --git a/src/vs/workbench/services/dialogs/electron-browser/dialogService.ts b/src/vs/workbench/services/dialogs/electron-browser/dialogService.ts index 68d1589ebca..da79c6c2316 100644 --- a/src/vs/workbench/services/dialogs/electron-browser/dialogService.ts +++ b/src/vs/workbench/services/dialogs/electron-browser/dialogService.ts @@ -295,7 +295,9 @@ export class FileDialogService implements IFileDialogService { showSaveDialog(options: ISaveDialogOptions): Promise { const schema = this.getFileSystemSchema(options); if (schema !== Schemas.file) { - options.availableFileSystems = [schema, Schemas.file]; // always allow file as well + if (!options.availableFileSystems) { + options.availableFileSystems = [schema]; // by default only allow saving in the own file system + } return this.saveRemoteResource(options); } @@ -311,6 +313,9 @@ export class FileDialogService implements IFileDialogService { showOpenDialog(options: IOpenDialogOptions): Promise { const schema = this.getFileSystemSchema(options); if (schema !== Schemas.file) { + if (!options.availableFileSystems) { + options.availableFileSystems = [schema]; // by default only allow loading in the own file system + } return this.pickRemoteResource(options).then(urisToOpen => { return urisToOpen && urisToOpen.map(uto => uto.uri); }); diff --git a/src/vs/workbench/services/editor/test/browser/editorService.test.ts b/src/vs/workbench/services/editor/test/browser/editorService.test.ts index aef6e54e808..e794eef02c5 100644 --- a/src/vs/workbench/services/editor/test/browser/editorService.test.ts +++ b/src/vs/workbench/services/editor/test/browser/editorService.test.ts @@ -4,7 +4,6 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import * as extpath from 'vs/base/common/extpath'; import { IEditorModel } from 'vs/platform/editor/common/editor'; import { URI } from 'vs/base/common/uri'; import { BaseEditor } from 'vs/workbench/browser/parts/editor/baseEditor'; @@ -27,6 +26,7 @@ import { UntitledEditorInput } from 'vs/workbench/common/editor/untitledEditorIn import { EditorServiceImpl } from 'vs/workbench/browser/parts/editor/editor'; import { CancellationToken } from 'vs/base/common/cancellation'; import { timeout } from 'vs/base/common/async'; +import { toResource } from 'vs/base/test/common/utils'; export class TestEditorControl extends BaseEditor { @@ -180,11 +180,11 @@ suite('Editor service', () => { const service: EditorService = instantiationService.createInstance(EditorService); // Cached Input (Files) - const fileResource1 = toFileResource(this, '/foo/bar/cache1.js'); + const fileResource1 = toResource.call(this, '/foo/bar/cache1.js'); const fileInput1 = service.createInput({ resource: fileResource1 }); assert.ok(fileInput1); - const fileResource2 = toFileResource(this, '/foo/bar/cache2.js'); + const fileResource2 = toResource.call(this, '/foo/bar/cache2.js'); const fileInput2 = service.createInput({ resource: fileResource2 }); assert.ok(fileInput2); @@ -202,11 +202,11 @@ suite('Editor service', () => { assert.ok(!fileInput1AgainAndAgain.isDisposed()); // Cached Input (Resource) - const resource1 = toResource.call(this, '/foo/bar/cache1.js'); + const resource1 = URI.from({ scheme: 'custom', path: '/foo/bar/cache1.js' }); const input1 = service.createInput({ resource: resource1 }); assert.ok(input1); - const resource2 = toResource.call(this, '/foo/bar/cache2.js'); + const resource2 = URI.from({ scheme: 'custom', path: '/foo/bar/cache2.js' }); const input2 = service.createInput({ resource: resource2 }); assert.ok(input2); @@ -229,13 +229,13 @@ suite('Editor service', () => { const service: EditorService = instantiationService.createInstance(EditorService); // Untyped Input (file) - let input = service.createInput({ resource: toFileResource(this, '/index.html'), options: { selection: { startLineNumber: 1, startColumn: 1 } } }); + let input = service.createInput({ resource: toResource.call(this, '/index.html'), options: { selection: { startLineNumber: 1, startColumn: 1 } } }); assert(input instanceof FileEditorInput); let contentInput = input; - assert.strictEqual(contentInput.getResource().fsPath, toFileResource(this, '/index.html').fsPath); + assert.strictEqual(contentInput.getResource().fsPath, toResource.call(this, '/index.html').fsPath); // Untyped Input (file, encoding) - input = service.createInput({ resource: toFileResource(this, '/index.html'), encoding: 'utf16le', options: { selection: { startLineNumber: 1, startColumn: 1 } } }); + input = service.createInput({ resource: toResource.call(this, '/index.html'), encoding: 'utf16le', options: { selection: { startLineNumber: 1, startColumn: 1 } } }); assert(input instanceof FileEditorInput); contentInput = input; assert.equal(contentInput.getPreferredEncoding(), 'utf16le'); @@ -598,11 +598,3 @@ suite('Editor service', () => { assert.ok(!failingEditor); }); }); - -function toResource(path: string) { - return URI.from({ scheme: 'custom', path }); -} - -function toFileResource(self: any, path: string) { - return URI.file(extpath.join('C:\\', Buffer.from(self.test.fullTitle()).toString('base64'), path)); -} diff --git a/src/vs/workbench/services/extensions/node/proxyResolver.ts b/src/vs/workbench/services/extensions/node/proxyResolver.ts index 628b70814f8..dba03553536 100644 --- a/src/vs/workbench/services/extensions/node/proxyResolver.ts +++ b/src/vs/workbench/services/extensions/node/proxyResolver.ts @@ -31,14 +31,14 @@ export function connectProxyResolver( extHostLogService: ExtHostLogService, mainThreadTelemetry: MainThreadTelemetryShape ) { - const agents = createProxyAgents(extHostWorkspace, configProvider, extHostLogService, mainThreadTelemetry); - const lookup = createPatchedModules(configProvider, agents); + const resolveProxy = setupProxyResolution(extHostWorkspace, configProvider, extHostLogService, mainThreadTelemetry); + const lookup = createPatchedModules(configProvider, resolveProxy); return configureModuleLoading(extensionService, lookup); } const maxCacheEntries = 5000; // Cache can grow twice that much due to 'oldCache'. -function createProxyAgents( +function setupProxyResolution( extHostWorkspace: ExtHostWorkspaceProvider, configProvider: ExtHostConfigProvider, extHostLogService: ExtHostLogService, @@ -168,11 +168,7 @@ function createProxyAgents( }); } - const httpAgent: http.Agent = new ProxyAgent({ resolveProxy }); - (httpAgent).defaultPort = 80; - const httpsAgent: http.Agent = new ProxyAgent({ resolveProxy }); - (httpsAgent).defaultPort = 443; - return { http: httpAgent, https: httpsAgent }; + return resolveProxy; } function collectResult(results: ConnectionResult[], resolveProxy: string, connection: string, req: http.ClientRequest) { @@ -218,7 +214,7 @@ function proxyFromConfigURL(configURL: string) { return undefined; } -function createPatchedModules(configProvider: ExtHostConfigProvider, agents: { http: http.Agent; https: http.Agent; }) { +function createPatchedModules(configProvider: ExtHostConfigProvider, resolveProxy: ReturnType) { const setting = { config: configProvider.getConfiguration('http') .get('proxySupport') || 'off' @@ -230,23 +226,23 @@ function createPatchedModules(configProvider: ExtHostConfigProvider, agents: { h return { http: { - off: assign({}, http, patches(http, agents.http, agents.https, { config: 'off' }, true)), - on: assign({}, http, patches(http, agents.http, agents.https, { config: 'on' }, true)), - override: assign({}, http, patches(http, agents.http, agents.https, { config: 'override' }, true)), - onRequest: assign({}, http, patches(http, agents.http, agents.https, setting, true)), - default: assign(http, patches(http, agents.http, agents.https, setting, false)) // run last + off: assign({}, http, patches(http, resolveProxy, { config: 'off' }, true)), + on: assign({}, http, patches(http, resolveProxy, { config: 'on' }, true)), + override: assign({}, http, patches(http, resolveProxy, { config: 'override' }, true)), + onRequest: assign({}, http, patches(http, resolveProxy, setting, true)), + default: assign(http, patches(http, resolveProxy, setting, false)) // run last }, https: { - off: assign({}, https, patches(https, agents.https, agents.http, { config: 'off' }, true)), - on: assign({}, https, patches(https, agents.https, agents.http, { config: 'on' }, true)), - override: assign({}, https, patches(https, agents.https, agents.http, { config: 'override' }, true)), - onRequest: assign({}, https, patches(https, agents.https, agents.http, setting, true)), - default: assign(https, patches(https, agents.https, agents.http, setting, false)) // run last + off: assign({}, https, patches(https, resolveProxy, { config: 'off' }, true)), + on: assign({}, https, patches(https, resolveProxy, { config: 'on' }, true)), + override: assign({}, https, patches(https, resolveProxy, { config: 'override' }, true)), + onRequest: assign({}, https, patches(https, resolveProxy, setting, true)), + default: assign(https, patches(https, resolveProxy, setting, false)) // run last } }; } -function patches(originals: typeof http | typeof https, agent: http.Agent, otherAgent: http.Agent, setting: { config: string; }, onRequest: boolean) { +function patches(originals: typeof http | typeof https, resolveProxy: ReturnType, setting: { config: string; }, onRequest: boolean) { return { get: patch(originals.get), request: patch(originals.request) @@ -270,7 +266,7 @@ function patches(originals: typeof http | typeof https, agent: http.Agent, other return original.apply(null, arguments as unknown as any[]); } - if (!options.socketPath && (config === 'override' || config === 'on' && !options.agent) && options.agent !== agent && options.agent !== otherAgent) { + if (!options.socketPath && (config === 'override' || config === 'on' && !options.agent) && !(options.agent instanceof ProxyAgent)) { if (url) { const parsed = typeof url === 'string' ? new nodeurl.URL(url) : url; const urlOptions = { @@ -286,7 +282,11 @@ function patches(originals: typeof http | typeof https, agent: http.Agent, other } else { options = { ...options }; } - options.agent = agent; + options.agent = new ProxyAgent({ + resolveProxy, + defaultPort: originals === https ? 443 : 80, + originalAgent: options.agent + }); return original(options, callback); } diff --git a/src/vs/workbench/services/keybinding/common/keybindingIO.ts b/src/vs/workbench/services/keybinding/common/keybindingIO.ts index ce6b029f4df..4a32a43c9dc 100644 --- a/src/vs/workbench/services/keybinding/common/keybindingIO.ts +++ b/src/vs/workbench/services/keybinding/common/keybindingIO.ts @@ -5,15 +5,13 @@ import { SimpleKeybinding } from 'vs/base/common/keyCodes'; import { KeybindingParser } from 'vs/base/common/keybindingParser'; -import { OperatingSystem } from 'vs/base/common/platform'; import { ScanCodeBinding } from 'vs/base/common/scanCode'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { IUserFriendlyKeybinding } from 'vs/platform/keybinding/common/keybinding'; import { ResolvedKeybindingItem } from 'vs/platform/keybinding/common/resolvedKeybindingItem'; export interface IUserKeybindingItem { - firstPart: SimpleKeybinding | ScanCodeBinding | null; - chordPart: SimpleKeybinding | ScanCodeBinding | null; + parts: (SimpleKeybinding | ScanCodeBinding)[]; command: string | null; commandArgs?: any; when: ContextKeyExpr | null; @@ -21,7 +19,7 @@ export interface IUserKeybindingItem { export class KeybindingIO { - public static writeKeybindingItem(out: OutputBuilder, item: ResolvedKeybindingItem, OS: OperatingSystem): void { + public static writeKeybindingItem(out: OutputBuilder, item: ResolvedKeybindingItem): void { if (!item.resolvedKeybinding) { return; } @@ -41,14 +39,13 @@ export class KeybindingIO { out.write('}'); } - public static readUserKeybindingItem(input: IUserFriendlyKeybinding, OS: OperatingSystem): IUserKeybindingItem { - const [firstPart, chordPart] = (typeof input.key === 'string' ? KeybindingParser.parseUserBinding(input.key) : [null, null]); + public static readUserKeybindingItem(input: IUserFriendlyKeybinding): IUserKeybindingItem { + const parts = (typeof input.key === 'string' ? KeybindingParser.parseUserBinding(input.key) : []); const when = (typeof input.when === 'string' ? ContextKeyExpr.deserialize(input.when) : null); const command = (typeof input.command === 'string' ? input.command : null); const commandArgs = (typeof input.args !== 'undefined' ? input.args : undefined); return { - firstPart: firstPart, - chordPart: chordPart, + parts: parts, command: command, commandArgs: commandArgs, when: when diff --git a/src/vs/workbench/services/keybinding/common/keyboardMapper.ts b/src/vs/workbench/services/keybinding/common/keyboardMapper.ts index 36c9f6ca888..373700f973f 100644 --- a/src/vs/workbench/services/keybinding/common/keyboardMapper.ts +++ b/src/vs/workbench/services/keybinding/common/keyboardMapper.ts @@ -11,7 +11,7 @@ export interface IKeyboardMapper { dumpDebugInfo(): string; resolveKeybinding(keybinding: Keybinding): ResolvedKeybinding[]; resolveKeyboardEvent(keyboardEvent: IKeyboardEvent): ResolvedKeybinding; - resolveUserBinding(firstPart: SimpleKeybinding | ScanCodeBinding | null, chordPart: SimpleKeybinding | ScanCodeBinding | null): ResolvedKeybinding[]; + resolveUserBinding(firstPart: (SimpleKeybinding | ScanCodeBinding)[]): ResolvedKeybinding[]; } export class CachedKeyboardMapper implements IKeyboardMapper { @@ -43,7 +43,7 @@ export class CachedKeyboardMapper implements IKeyboardMapper { return this._actual.resolveKeyboardEvent(keyboardEvent); } - public resolveUserBinding(firstPart: SimpleKeybinding | ScanCodeBinding, chordPart: SimpleKeybinding | ScanCodeBinding): ResolvedKeybinding[] { - return this._actual.resolveUserBinding(firstPart, chordPart); + public resolveUserBinding(parts: (SimpleKeybinding | ScanCodeBinding)[]): ResolvedKeybinding[] { + return this._actual.resolveUserBinding(parts); } } diff --git a/src/vs/workbench/services/keybinding/common/macLinuxFallbackKeyboardMapper.ts b/src/vs/workbench/services/keybinding/common/macLinuxFallbackKeyboardMapper.ts index a052a184b6e..23e3521e20f 100644 --- a/src/vs/workbench/services/keybinding/common/macLinuxFallbackKeyboardMapper.ts +++ b/src/vs/workbench/services/keybinding/common/macLinuxFallbackKeyboardMapper.ts @@ -9,6 +9,7 @@ import { IMMUTABLE_CODE_TO_KEY_CODE, ScanCode, ScanCodeBinding } from 'vs/base/c import { IKeyboardEvent } from 'vs/platform/keybinding/common/keybinding'; import { USLayoutResolvedKeybinding } from 'vs/platform/keybinding/common/usLayoutResolvedKeybinding'; import { IKeyboardMapper } from 'vs/workbench/services/keybinding/common/keyboardMapper'; +import { removeElementsAfterNulls } from 'vs/platform/keybinding/common/resolvedKeybindingItem'; /** * A keyboard mapper to be used when reading the keymap from the OS fails. @@ -117,16 +118,8 @@ export class MacLinuxFallbackKeyboardMapper implements IKeyboardMapper { return new SimpleKeybinding(binding.ctrlKey, binding.shiftKey, binding.altKey, binding.metaKey, keyCode); } - public resolveUserBinding(firstPart: SimpleKeybinding | ScanCodeBinding | null, chordPart: SimpleKeybinding | ScanCodeBinding | null): ResolvedKeybinding[] { - const _firstPart = this._resolveSimpleUserBinding(firstPart); - const _chordPart = this._resolveSimpleUserBinding(chordPart); - let parts: SimpleKeybinding[] = []; - if (_firstPart) { - parts.push(_firstPart); - } - if (_chordPart) { - parts.push(_chordPart); - } + public resolveUserBinding(input: (SimpleKeybinding | ScanCodeBinding)[]): ResolvedKeybinding[] { + const parts: SimpleKeybinding[] = removeElementsAfterNulls(input.map(keybinding => this._resolveSimpleUserBinding(keybinding))); if (parts.length > 0) { return [new USLayoutResolvedKeybinding(new ChordKeybinding(parts), this._OS)]; } diff --git a/src/vs/workbench/services/keybinding/common/macLinuxKeyboardMapper.ts b/src/vs/workbench/services/keybinding/common/macLinuxKeyboardMapper.ts index 37afe210ef7..16481ba5b63 100644 --- a/src/vs/workbench/services/keybinding/common/macLinuxKeyboardMapper.ts +++ b/src/vs/workbench/services/keybinding/common/macLinuxKeyboardMapper.ts @@ -4,11 +4,12 @@ *--------------------------------------------------------------------------------------------*/ import { CharCode } from 'vs/base/common/charCode'; -import { KeyCode, KeyCodeUtils, Keybinding, ResolvedKeybinding, SimpleKeybinding, BaseResolvedKeybinding } from 'vs/base/common/keyCodes'; +import { KeyCode, KeyCodeUtils, Keybinding, ResolvedKeybinding, SimpleKeybinding } from 'vs/base/common/keyCodes'; import { OperatingSystem } from 'vs/base/common/platform'; import { IMMUTABLE_CODE_TO_KEY_CODE, IMMUTABLE_KEY_CODE_TO_CODE, ScanCode, ScanCodeBinding, ScanCodeUtils } from 'vs/base/common/scanCode'; import { IKeyboardEvent } from 'vs/platform/keybinding/common/keybinding'; import { IKeyboardMapper } from 'vs/workbench/services/keybinding/common/keyboardMapper'; +import { BaseResolvedKeybinding } from 'vs/platform/keybinding/common/baseResolvedKeybinding'; export interface IMacLinuxKeyMapping { value: string; @@ -1052,14 +1053,8 @@ export class MacLinuxKeyboardMapper implements IKeyboardMapper { return this.simpleKeybindingToScanCodeBinding(binding); } - public resolveUserBinding(_firstPart: SimpleKeybinding | ScanCodeBinding | null, _chordPart: SimpleKeybinding | ScanCodeBinding | null): ResolvedKeybinding[] { - let parts: ScanCodeBinding[][] = []; - if (_firstPart) { - parts.push(this._resolveSimpleUserBinding(_firstPart)); - } - if (_chordPart) { - parts.push(this._resolveSimpleUserBinding(_chordPart)); - } + public resolveUserBinding(input: (SimpleKeybinding | ScanCodeBinding)[]): ResolvedKeybinding[] { + const parts: ScanCodeBinding[][] = input.map(keybinding => this._resolveSimpleUserBinding(keybinding)); let result: NativeResolvedKeybinding[] = []; this._generateResolvedKeybindings(parts, 0, [], result); return result; diff --git a/src/vs/workbench/services/keybinding/common/windowsKeyboardMapper.ts b/src/vs/workbench/services/keybinding/common/windowsKeyboardMapper.ts index 38d534fcebb..ee848b09d0f 100644 --- a/src/vs/workbench/services/keybinding/common/windowsKeyboardMapper.ts +++ b/src/vs/workbench/services/keybinding/common/windowsKeyboardMapper.ts @@ -4,12 +4,14 @@ *--------------------------------------------------------------------------------------------*/ import { CharCode } from 'vs/base/common/charCode'; -import { KeyCode, KeyCodeUtils, Keybinding, ResolvedKeybinding, SimpleKeybinding, BaseResolvedKeybinding } from 'vs/base/common/keyCodes'; +import { KeyCode, KeyCodeUtils, Keybinding, ResolvedKeybinding, SimpleKeybinding } from 'vs/base/common/keyCodes'; import { UILabelProvider } from 'vs/base/common/keybindingLabels'; import { OperatingSystem } from 'vs/base/common/platform'; import { IMMUTABLE_CODE_TO_KEY_CODE, ScanCode, ScanCodeBinding, ScanCodeUtils } from 'vs/base/common/scanCode'; import { IKeyboardEvent } from 'vs/platform/keybinding/common/keybinding'; import { IKeyboardMapper } from 'vs/workbench/services/keybinding/common/keyboardMapper'; +import { BaseResolvedKeybinding } from 'vs/platform/keybinding/common/baseResolvedKeybinding'; +import { removeElementsAfterNulls } from 'vs/platform/keybinding/common/resolvedKeybindingItem'; export interface IWindowsKeyMapping { vkey: string; @@ -463,16 +465,8 @@ export class WindowsKeyboardMapper implements IKeyboardMapper { return new SimpleKeybinding(binding.ctrlKey, binding.shiftKey, binding.altKey, binding.metaKey, keyCode); } - public resolveUserBinding(firstPart: SimpleKeybinding | ScanCodeBinding | null, chordPart: SimpleKeybinding | ScanCodeBinding | null): ResolvedKeybinding[] { - const _firstPart = this._resolveSimpleUserBinding(firstPart); - const _chordPart = this._resolveSimpleUserBinding(chordPart); - let parts: SimpleKeybinding[] = []; - if (_firstPart) { - parts.push(_firstPart); - } - if (_chordPart) { - parts.push(_chordPart); - } + public resolveUserBinding(input: (SimpleKeybinding | ScanCodeBinding)[]): ResolvedKeybinding[] { + const parts: SimpleKeybinding[] = removeElementsAfterNulls(input.map(keybinding => this._resolveSimpleUserBinding(keybinding))); if (parts.length > 0) { return [new WindowsNativeResolvedKeybinding(this, parts)]; } diff --git a/src/vs/workbench/services/keybinding/electron-browser/keybindingService.ts b/src/vs/workbench/services/keybinding/electron-browser/keybindingService.ts index 1ae7af41a36..cd9e0c23fd4 100644 --- a/src/vs/workbench/services/keybinding/electron-browser/keybindingService.ts +++ b/src/vs/workbench/services/keybinding/electron-browser/keybindingService.ts @@ -38,6 +38,8 @@ import { MacLinuxFallbackKeyboardMapper } from 'vs/workbench/services/keybinding import { IMacLinuxKeyboardMapping, MacLinuxKeyboardMapper, macLinuxKeyboardMappingEquals } from 'vs/workbench/services/keybinding/common/macLinuxKeyboardMapper'; import { IWindowsKeyboardMapping, WindowsKeyboardMapper, windowsKeyboardMappingEquals } from 'vs/workbench/services/keybinding/common/windowsKeyboardMapper'; import { IWindowService } from 'vs/platform/windows/common/windows'; +import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; +import { MenuRegistry } from 'vs/platform/actions/common/actions'; export class KeyboardMapperFactory { public static readonly INSTANCE = new KeyboardMapperFactory(); @@ -276,7 +278,8 @@ export class WorkbenchKeybindingService extends AbstractKeybindingService { @IEnvironmentService environmentService: IEnvironmentService, @IStatusbarService statusBarService: IStatusbarService, @IConfigurationService configurationService: IConfigurationService, - @IWindowService private readonly windowService: IWindowService + @IWindowService private readonly windowService: IWindowService, + @IExtensionService extensionService: IExtensionService ) { super(contextKeyService, commandService, telemetryService, notificationService, statusBarService); @@ -316,6 +319,9 @@ export class WorkbenchKeybindingService extends AbstractKeybindingService { this.updateResolver({ source: KeybindingSource.Default }); }); + updateSchema(); + this._register(extensionService.onDidRegisterExtensions(() => updateSchema())); + this._register(this.userKeybindings.onDidUpdateConfiguration(event => this.updateResolver({ source: KeybindingSource.User, keybindings: event.config @@ -407,13 +413,12 @@ export class WorkbenchKeybindingService extends AbstractKeybindingService { let result: ResolvedKeybindingItem[] = [], resultLen = 0; for (const item of items) { const when = (item.when ? item.when.normalize() : null); - const firstPart = item.firstPart; - const chordPart = item.chordPart; - if (!firstPart) { + const parts = item.parts; + if (parts.length === 0) { // This might be a removal keybinding item in user settings => accept it result[resultLen++] = new ResolvedKeybindingItem(null, item.command, item.commandArgs, when, isDefault); } else { - const resolvedKeybindings = this._keyboardMapper.resolveUserBinding(firstPart, chordPart); + const resolvedKeybindings = this._keyboardMapper.resolveUserBinding(parts); for (const resolvedKeybinding of resolvedKeybindings) { result[resultLen++] = new ResolvedKeybindingItem(resolvedKeybinding, item.command, item.commandArgs, when, isDefault); } @@ -438,7 +443,7 @@ export class WorkbenchKeybindingService extends AbstractKeybindingService { }); } - return extraUserKeybindings.map((k) => KeybindingIO.readUserKeybindingItem(k, OS)); + return extraUserKeybindings.map((k) => KeybindingIO.readUserKeybindingItem(k)); } public resolveKeybinding(kb: Keybinding): ResolvedKeybinding[] { @@ -450,8 +455,8 @@ export class WorkbenchKeybindingService extends AbstractKeybindingService { } public resolveUserBinding(userBinding: string): ResolvedKeybinding[] { - const [firstPart, chordPart] = KeybindingParser.parseUserBinding(userBinding); - return this._keyboardMapper.resolveUserBinding(firstPart, chordPart); + const parts = KeybindingParser.parseUserBinding(userBinding); + return this._keyboardMapper.resolveUserBinding(parts); } private _handleKeybindingsExtensionPointUser(isBuiltin: boolean, keybindings: ContributedKeyBinding | ContributedKeyBinding[], collector: ExtensionMessageCollector, result: IKeybindingRule2[]): void { @@ -531,7 +536,7 @@ export class WorkbenchKeybindingService extends AbstractKeybindingService { let lastIndex = defaultKeybindings.length - 1; defaultKeybindings.forEach((k, index) => { - KeybindingIO.writeKeybindingItem(out, k, OS); + KeybindingIO.writeKeybindingItem(out, k); if (index !== lastIndex) { out.writeLine(','); } else { @@ -572,6 +577,8 @@ export class WorkbenchKeybindingService extends AbstractKeybindingService { let schemaId = 'vscode://schemas/keybindings'; let commandsSchemas: IJSONSchema[] = []; +let commandsEnum: string[] = []; +let commandsEnumDescriptions: (string | null | undefined)[] = []; let schema: IJSONSchema = { 'id': schemaId, 'type': 'array', @@ -605,6 +612,8 @@ let schema: IJSONSchema = { }, 'command': { 'type': 'string', + 'enum': commandsEnum, + 'enumDescriptions': commandsEnumDescriptions, 'description': nls.localize('keybindings.json.command', "Name of the command to execute"), }, 'when': { @@ -623,14 +632,38 @@ let schemaRegistry = Registry.as(Extensions.JSONContr schemaRegistry.registerSchema(schemaId, schema); function updateSchema() { + commandsSchemas.length = 0; + commandsEnum.length = 0; + commandsEnumDescriptions.length = 0; + + const knownCommands = new Set(); + const addKnownCommand = (commandId: string, description?: string | null) => { + if (!/^_/.test(commandId)) { + if (!knownCommands.has(commandId)) { + knownCommands.add(commandId); + + commandsEnum.push(commandId); + commandsEnumDescriptions.push(description); + + // Also add the negative form for keybinding removal + commandsEnum.push(`-${commandId}`); + commandsEnumDescriptions.push(description); + } + } + }; + const allCommands = CommandsRegistry.getCommands(); for (let commandId in allCommands) { const commandDescription = allCommands[commandId].description; + + addKnownCommand(commandId, commandDescription && commandDescription.description); + if (!commandDescription || !commandDescription.args || commandDescription.args.length !== 1 || !commandDescription.args[0].schema) { continue; } const argsSchema = commandDescription.args[0].schema; + const argsRequired = Array.isArray(argsSchema.required) && argsSchema.required.length > 0; const addition = { 'if': { 'properties': { @@ -638,6 +671,7 @@ function updateSchema() { } }, 'then': { + 'required': ([]).concat(argsRequired ? ['args'] : []), 'properties': { 'args': argsSchema } @@ -646,6 +680,12 @@ function updateSchema() { commandsSchemas.push(addition); } + + const menuCommands = MenuRegistry.getCommands(); + for (let commandId in menuCommands) { + addKnownCommand(commandId); + } + } const configurationRegistry = Registry.as(ConfigExtensions.Configuration); diff --git a/src/vs/workbench/services/keybinding/test/keybindingIO.test.ts b/src/vs/workbench/services/keybinding/test/keybindingIO.test.ts index 4a3ae322028..99a2ef11a08 100644 --- a/src/vs/workbench/services/keybinding/test/keybindingIO.test.ts +++ b/src/vs/workbench/services/keybinding/test/keybindingIO.test.ts @@ -5,7 +5,7 @@ import * as assert from 'assert'; import { KeyChord, KeyCode, KeyMod, SimpleKeybinding, createKeybinding } from 'vs/base/common/keyCodes'; import { KeybindingParser } from 'vs/base/common/keybindingParser'; -import { OS, OperatingSystem } from 'vs/base/common/platform'; +import { OperatingSystem } from 'vs/base/common/platform'; import { ScanCode, ScanCodeBinding } from 'vs/base/common/scanCode'; import { IUserFriendlyKeybinding } from 'vs/platform/keybinding/common/keybinding'; import { USLayoutResolvedKeybinding } from 'vs/platform/keybinding/common/usLayoutResolvedKeybinding'; @@ -126,37 +126,35 @@ suite('keybindingIO', () => { test('issue #10452 - invalid command', () => { let strJSON = `[{ "key": "ctrl+k ctrl+f", "command": ["firstcommand", "seccondcommand"] }]`; let userKeybinding = JSON.parse(strJSON)[0]; - let keybindingItem = KeybindingIO.readUserKeybindingItem(userKeybinding, OS); + let keybindingItem = KeybindingIO.readUserKeybindingItem(userKeybinding); assert.equal(keybindingItem.command, null); }); test('issue #10452 - invalid when', () => { let strJSON = `[{ "key": "ctrl+k ctrl+f", "command": "firstcommand", "when": [] }]`; let userKeybinding = JSON.parse(strJSON)[0]; - let keybindingItem = KeybindingIO.readUserKeybindingItem(userKeybinding, OS); + let keybindingItem = KeybindingIO.readUserKeybindingItem(userKeybinding); assert.equal(keybindingItem.when, null); }); test('issue #10452 - invalid key', () => { let strJSON = `[{ "key": [], "command": "firstcommand" }]`; let userKeybinding = JSON.parse(strJSON)[0]; - let keybindingItem = KeybindingIO.readUserKeybindingItem(userKeybinding, OS); - assert.equal(keybindingItem.firstPart, null); - assert.equal(keybindingItem.chordPart, null); + let keybindingItem = KeybindingIO.readUserKeybindingItem(userKeybinding); + assert.deepEqual(keybindingItem.parts, []); }); test('issue #10452 - invalid key 2', () => { let strJSON = `[{ "key": "", "command": "firstcommand" }]`; let userKeybinding = JSON.parse(strJSON)[0]; - let keybindingItem = KeybindingIO.readUserKeybindingItem(userKeybinding, OS); - assert.equal(keybindingItem.firstPart, null); - assert.equal(keybindingItem.chordPart, null); + let keybindingItem = KeybindingIO.readUserKeybindingItem(userKeybinding); + assert.deepEqual(keybindingItem.parts, []); }); test('test commands args', () => { let strJSON = `[{ "key": "ctrl+k ctrl+f", "command": "firstcommand", "when": [], "args": { "text": "theText" } }]`; let userKeybinding = JSON.parse(strJSON)[0]; - let keybindingItem = KeybindingIO.readUserKeybindingItem(userKeybinding, OS); + let keybindingItem = KeybindingIO.readUserKeybindingItem(userKeybinding); assert.equal(keybindingItem.commandArgs.text, 'theText'); }); }); diff --git a/src/vs/workbench/services/keybinding/test/keyboardMapperTestUtils.ts b/src/vs/workbench/services/keybinding/test/keyboardMapperTestUtils.ts index 185688a524a..8c8b209a002 100644 --- a/src/vs/workbench/services/keybinding/test/keyboardMapperTestUtils.ts +++ b/src/vs/workbench/services/keybinding/test/keyboardMapperTestUtils.ts @@ -44,8 +44,8 @@ export function assertResolveKeyboardEvent(mapper: IKeyboardMapper, keyboardEven assert.deepEqual(actual, expected); } -export function assertResolveUserBinding(mapper: IKeyboardMapper, firstPart: SimpleKeybinding | ScanCodeBinding, chordPart: SimpleKeybinding | ScanCodeBinding | null, expected: IResolvedKeybinding[]): void { - let actual: IResolvedKeybinding[] = mapper.resolveUserBinding(firstPart, chordPart).map(toIResolvedKeybinding); +export function assertResolveUserBinding(mapper: IKeyboardMapper, parts: (SimpleKeybinding | ScanCodeBinding)[], expected: IResolvedKeybinding[]): void { + let actual: IResolvedKeybinding[] = mapper.resolveUserBinding(parts).map(toIResolvedKeybinding); assert.deepEqual(actual, expected); } diff --git a/src/vs/workbench/services/keybinding/test/macLinuxFallbackKeyboardMapper.test.ts b/src/vs/workbench/services/keybinding/test/macLinuxFallbackKeyboardMapper.test.ts index e4fd593bdac..00fe9173b49 100644 --- a/src/vs/workbench/services/keybinding/test/macLinuxFallbackKeyboardMapper.test.ts +++ b/src/vs/workbench/services/keybinding/test/macLinuxFallbackKeyboardMapper.test.ts @@ -72,9 +72,10 @@ suite('keyboardMapper - MAC fallback', () => { test('resolveUserBinding Cmd+[Comma] Cmd+/', () => { assertResolveUserBinding( - mapper, - new ScanCodeBinding(false, false, false, true, ScanCode.Comma), - new SimpleKeybinding(false, false, false, true, KeyCode.US_SLASH), + mapper, [ + new ScanCodeBinding(false, false, false, true, ScanCode.Comma), + new SimpleKeybinding(false, false, false, true, KeyCode.US_SLASH), + ], [{ label: '⌘, ⌘/', ariaLabel: 'Command+, Command+/', @@ -174,9 +175,10 @@ suite('keyboardMapper - LINUX fallback', () => { test('resolveUserBinding Ctrl+[Comma] Ctrl+/', () => { assertResolveUserBinding( - mapper, - new ScanCodeBinding(true, false, false, false, ScanCode.Comma), - new SimpleKeybinding(true, false, false, false, KeyCode.US_SLASH), + mapper, [ + new ScanCodeBinding(true, false, false, false, ScanCode.Comma), + new SimpleKeybinding(true, false, false, false, KeyCode.US_SLASH), + ], [{ label: 'Ctrl+, Ctrl+/', ariaLabel: 'Control+, Control+/', @@ -191,9 +193,9 @@ suite('keyboardMapper - LINUX fallback', () => { test('resolveUserBinding Ctrl+[Comma]', () => { assertResolveUserBinding( - mapper, - new ScanCodeBinding(true, false, false, false, ScanCode.Comma), - null, + mapper, [ + new ScanCodeBinding(true, false, false, false, ScanCode.Comma), + ], [{ label: 'Ctrl+,', ariaLabel: 'Control+,', diff --git a/src/vs/workbench/services/keybinding/test/macLinuxKeyboardMapper.test.ts b/src/vs/workbench/services/keybinding/test/macLinuxKeyboardMapper.test.ts index ec0096039cd..d9967e815df 100644 --- a/src/vs/workbench/services/keybinding/test/macLinuxKeyboardMapper.test.ts +++ b/src/vs/workbench/services/keybinding/test/macLinuxKeyboardMapper.test.ts @@ -307,8 +307,10 @@ suite('keyboardMapper - MAC de_ch', () => { test('resolveUserBinding Cmd+[Comma] Cmd+/', () => { assertResolveUserBinding( mapper, - new ScanCodeBinding(false, false, false, true, ScanCode.Comma), - new SimpleKeybinding(false, false, false, true, KeyCode.US_SLASH), + [ + new ScanCodeBinding(false, false, false, true, ScanCode.Comma), + new SimpleKeybinding(false, false, false, true, KeyCode.US_SLASH), + ], [{ label: '⌘, ⇧⌘7', ariaLabel: 'Command+, Shift+Command+7', @@ -384,8 +386,10 @@ suite('keyboardMapper - MAC en_us', () => { test('resolveUserBinding Cmd+[Comma] Cmd+/', () => { assertResolveUserBinding( mapper, - new ScanCodeBinding(false, false, false, true, ScanCode.Comma), - new SimpleKeybinding(false, false, false, true, KeyCode.US_SLASH), + [ + new ScanCodeBinding(false, false, false, true, ScanCode.Comma), + new SimpleKeybinding(false, false, false, true, KeyCode.US_SLASH), + ], [{ label: '⌘, ⌘/', ariaLabel: 'Command+, Command+/', @@ -732,9 +736,10 @@ suite('keyboardMapper - LINUX de_ch', () => { test('resolveUserBinding Ctrl+[Comma] Ctrl+/', () => { assertResolveUserBinding( - mapper, - new ScanCodeBinding(true, false, false, false, ScanCode.Comma), - new SimpleKeybinding(true, false, false, false, KeyCode.US_SLASH), + mapper, [ + new ScanCodeBinding(true, false, false, false, ScanCode.Comma), + new SimpleKeybinding(true, false, false, false, KeyCode.US_SLASH), + ], [{ label: 'Ctrl+, Ctrl+Shift+7', ariaLabel: 'Control+, Control+Shift+7', @@ -1108,9 +1113,10 @@ suite('keyboardMapper - LINUX en_us', () => { test('resolveUserBinding Ctrl+[Comma] Ctrl+/', () => { assertResolveUserBinding( - mapper, - new ScanCodeBinding(true, false, false, false, ScanCode.Comma), - new SimpleKeybinding(true, false, false, false, KeyCode.US_SLASH), + mapper, [ + new ScanCodeBinding(true, false, false, false, ScanCode.Comma), + new SimpleKeybinding(true, false, false, false, KeyCode.US_SLASH), + ], [{ label: 'Ctrl+, Ctrl+/', ariaLabel: 'Control+, Control+/', @@ -1125,9 +1131,9 @@ suite('keyboardMapper - LINUX en_us', () => { test('resolveUserBinding Ctrl+[Comma]', () => { assertResolveUserBinding( - mapper, - new ScanCodeBinding(true, false, false, false, ScanCode.Comma), - null, + mapper, [ + new ScanCodeBinding(true, false, false, false, ScanCode.Comma) + ], [{ label: 'Ctrl+,', ariaLabel: 'Control+,', diff --git a/src/vs/workbench/services/keybinding/test/windowsKeyboardMapper.test.ts b/src/vs/workbench/services/keybinding/test/windowsKeyboardMapper.test.ts index 22bc65c18bd..5592512f9b1 100644 --- a/src/vs/workbench/services/keybinding/test/windowsKeyboardMapper.test.ts +++ b/src/vs/workbench/services/keybinding/test/windowsKeyboardMapper.test.ts @@ -272,9 +272,10 @@ suite('keyboardMapper - WINDOWS de_ch', () => { test('resolveUserBinding Ctrl+[Comma] Ctrl+/', () => { assertResolveUserBinding( - mapper, - new ScanCodeBinding(true, false, false, false, ScanCode.Comma), - new SimpleKeybinding(true, false, false, false, KeyCode.US_SLASH), + mapper, [ + new ScanCodeBinding(true, false, false, false, ScanCode.Comma), + new SimpleKeybinding(true, false, false, false, KeyCode.US_SLASH), + ], [{ label: 'Ctrl+, Ctrl+§', ariaLabel: 'Control+, Control+§', @@ -341,9 +342,10 @@ suite('keyboardMapper - WINDOWS en_us', () => { test('resolveUserBinding Ctrl+[Comma] Ctrl+/', () => { assertResolveUserBinding( - mapper, - new ScanCodeBinding(true, false, false, false, ScanCode.Comma), - new SimpleKeybinding(true, false, false, false, KeyCode.US_SLASH), + mapper, [ + new ScanCodeBinding(true, false, false, false, ScanCode.Comma), + new SimpleKeybinding(true, false, false, false, KeyCode.US_SLASH), + ], [{ label: 'Ctrl+, Ctrl+/', ariaLabel: 'Control+, Control+/', @@ -358,9 +360,9 @@ suite('keyboardMapper - WINDOWS en_us', () => { test('resolveUserBinding Ctrl+[Comma]', () => { assertResolveUserBinding( - mapper, - new ScanCodeBinding(true, false, false, false, ScanCode.Comma), - null!, + mapper, [ + new ScanCodeBinding(true, false, false, false, ScanCode.Comma), + ], [{ label: 'Ctrl+,', ariaLabel: 'Control+,', diff --git a/src/vs/workbench/services/preferences/common/preferences.ts b/src/vs/workbench/services/preferences/common/preferences.ts index 01a55393bc5..c69cfc9d412 100644 --- a/src/vs/workbench/services/preferences/common/preferences.ts +++ b/src/vs/workbench/services/preferences/common/preferences.ts @@ -5,7 +5,6 @@ import { IStringDictionary } from 'vs/base/common/collections'; import { Event } from 'vs/base/common/event'; -import { join } from 'vs/base/common/extpath'; import { URI } from 'vs/base/common/uri'; import { IRange } from 'vs/editor/common/core/range'; import { ITextModel } from 'vs/editor/common/model'; @@ -230,6 +229,6 @@ export function getSettingsTargetName(target: ConfigurationTarget, resource: URI return ''; } -export const FOLDER_SETTINGS_PATH = join('.vscode', 'settings.json'); +export const FOLDER_SETTINGS_PATH = '.vscode/settings.json'; export const DEFAULT_SETTINGS_EDITOR_SETTING = 'workbench.settings.openDefaultSettings'; export const USE_SPLIT_JSON_SETTING = 'workbench.settings.useSplitJSON'; diff --git a/src/vs/workbench/services/textfile/common/textFileEditorModel.ts b/src/vs/workbench/services/textfile/common/textFileEditorModel.ts index 41d0ff63316..e7f9af79cf3 100644 --- a/src/vs/workbench/services/textfile/common/textFileEditorModel.ts +++ b/src/vs/workbench/services/textfile/common/textFileEditorModel.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as extpath from 'vs/base/common/extpath'; +import { join } from 'vs/base/common/path'; import * as nls from 'vs/nls'; import { Event, Emitter } from 'vs/base/common/event'; import { guessMimeTypes } from 'vs/base/common/mime'; @@ -784,12 +784,12 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil } // Check for locale file - if (isEqual(this.resource, URI.file(extpath.join(this.environmentService.appSettingsHome, 'locale.json')), !isLinux)) { + if (isEqual(this.resource, URI.file(join(this.environmentService.appSettingsHome, 'locale.json')), !isLinux)) { return 'locale'; } // Check for snippets - if (isEqualOrParent(this.resource, URI.file(extpath.join(this.environmentService.appSettingsHome, 'snippets')))) { + if (isEqualOrParent(this.resource, URI.file(join(this.environmentService.appSettingsHome, 'snippets')))) { return 'snippets'; } diff --git a/src/vs/workbench/services/textfile/common/textFileService.ts b/src/vs/workbench/services/textfile/common/textFileService.ts index c261d86bf61..99b7cdbed06 100644 --- a/src/vs/workbench/services/textfile/common/textFileService.ts +++ b/src/vs/workbench/services/textfile/common/textFileService.ts @@ -5,7 +5,6 @@ import * as nls from 'vs/nls'; import { URI } from 'vs/base/common/uri'; -import * as extpath from 'vs/base/common/extpath'; import * as errors from 'vs/base/common/errors'; import * as objects from 'vs/base/common/objects'; import { Event, Emitter } from 'vs/base/common/event'; @@ -908,7 +907,7 @@ export class TextFileService extends Disposable implements ITextFileService { // Otherwise a parent folder of the source is being moved, so we need // to compute the target resource based on that else { - targetModelResource = sourceModelResource.with({ path: extpath.join(target.path, sourceModelResource.path.substr(source.path.length + 1)) }); + targetModelResource = sourceModelResource.with({ path: joinPath(target, sourceModelResource.path.substr(source.path.length + 1)).path }); } // Remember as dirty target model to load after the operation diff --git a/src/vs/workbench/services/textfile/test/textFileEditorModelManager.test.ts b/src/vs/workbench/services/textfile/test/textFileEditorModelManager.test.ts index 7be9315676a..18c7d0e3f0a 100644 --- a/src/vs/workbench/services/textfile/test/textFileEditorModelManager.test.ts +++ b/src/vs/workbench/services/textfile/test/textFileEditorModelManager.test.ts @@ -7,12 +7,12 @@ import * as assert from 'assert'; import { URI } from 'vs/base/common/uri'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { TextFileEditorModelManager } from 'vs/workbench/services/textfile/common/textFileEditorModelManager'; -import { join } from 'vs/base/common/extpath'; import { workbenchInstantiationService, TestFileService } from 'vs/workbench/test/workbenchTestServices'; import { TextFileEditorModel } from 'vs/workbench/services/textfile/common/textFileEditorModel'; import { IFileService, FileChangesEvent, FileChangeType } from 'vs/platform/files/common/files'; import { IModelService } from 'vs/editor/common/services/modelService'; import { timeout } from 'vs/base/common/async'; +import { toResource } from 'vs/base/test/common/utils'; export class TestTextFileEditorModelManager extends TextFileEditorModelManager { @@ -29,10 +29,6 @@ class ServiceAccessor { } } -function toResource(path: string): URI { - return URI.file(join('C:\\', path)); -} - suite('Files - TextFileEditorModelManager', () => { let instantiationService: IInstantiationService; @@ -46,9 +42,9 @@ suite('Files - TextFileEditorModelManager', () => { test('add, remove, clear, get, getAll', function () { const manager: TestTextFileEditorModelManager = instantiationService.createInstance(TestTextFileEditorModelManager); - const model1: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource('/path/random1.txt'), 'utf8'); - const model2: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource('/path/random2.txt'), 'utf8'); - const model3: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource('/path/random3.txt'), 'utf8'); + const model1: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/random1.txt'), 'utf8'); + const model2: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/random2.txt'), 'utf8'); + const model3: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/random3.txt'), 'utf8'); manager.add(URI.file('/test.html'), model1); manager.add(URI.file('/some/other.html'), model2); @@ -126,9 +122,9 @@ suite('Files - TextFileEditorModelManager', () => { test('removed from cache when model disposed', function () { const manager: TestTextFileEditorModelManager = instantiationService.createInstance(TestTextFileEditorModelManager); - const model1: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource('/path/random1.txt'), 'utf8'); - const model2: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource('/path/random2.txt'), 'utf8'); - const model3: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource('/path/random3.txt'), 'utf8'); + const model1: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/random1.txt'), 'utf8'); + const model2: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/random2.txt'), 'utf8'); + const model3: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/random3.txt'), 'utf8'); manager.add(URI.file('/test.html'), model1); manager.add(URI.file('/some/other.html'), model2); @@ -143,14 +139,14 @@ suite('Files - TextFileEditorModelManager', () => { model3.dispose(); }); - test('events', () => { + test('events', function () { TextFileEditorModel.DEFAULT_CONTENT_CHANGE_BUFFER_DELAY = 0; TextFileEditorModel.DEFAULT_ORPHANED_CHANGE_BUFFER_DELAY = 0; const manager: TestTextFileEditorModelManager = instantiationService.createInstance(TestTextFileEditorModelManager); - const resource1 = toResource('/path/index.txt'); - const resource2 = toResource('/path/other.txt'); + const resource1 = toResource.call(this, '/path/index.txt'); + const resource2 = toResource.call(this, '/path/other.txt'); let dirtyCounter = 0; let revertedCounter = 0; @@ -235,8 +231,8 @@ suite('Files - TextFileEditorModelManager', () => { test('events debounced', function () { const manager: TestTextFileEditorModelManager = instantiationService.createInstance(TestTextFileEditorModelManager); - const resource1 = toResource('/path/index.txt'); - const resource2 = toResource('/path/other.txt'); + const resource1 = toResource.call(this, '/path/index.txt'); + const resource2 = toResource.call(this, '/path/other.txt'); let dirtyCounter = 0; let revertedCounter = 0; @@ -293,7 +289,7 @@ suite('Files - TextFileEditorModelManager', () => { test('disposing model takes it out of the manager', function () { const manager: TestTextFileEditorModelManager = instantiationService.createInstance(TestTextFileEditorModelManager); - const resource = toResource('/path/index_something.txt'); + const resource = toResource.call(this, '/path/index_something.txt'); return manager.loadOrCreate(resource, { encoding: 'utf8' }).then(model => { model.dispose(); @@ -308,7 +304,7 @@ suite('Files - TextFileEditorModelManager', () => { test('dispose prevents dirty model from getting disposed', function () { const manager: TestTextFileEditorModelManager = instantiationService.createInstance(TestTextFileEditorModelManager); - const resource = toResource('/path/index_something.txt'); + const resource = toResource.call(this, '/path/index_something.txt'); return manager.loadOrCreate(resource, { encoding: 'utf8' }).then(model => { model.textEditorModel.setValue('make dirty'); diff --git a/src/vs/workbench/services/timer/electron-browser/timerService.ts b/src/vs/workbench/services/timer/electron-browser/timerService.ts index be1ba98ee64..422995dd65b 100644 --- a/src/vs/workbench/services/timer/electron-browser/timerService.ts +++ b/src/vs/workbench/services/timer/electron-browser/timerService.ts @@ -53,7 +53,6 @@ export interface IMemoryInfo { "timers.ellapsedExtensions" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true }, "timers.ellapsedExtensionsReady" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true }, "timers.ellapsedRequire" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true }, - "timers.ellapsedGlobalStorageInitMain" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true }, "timers.ellapsedWorkspaceStorageInit" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true }, "timers.ellapsedWorkspaceServiceInit" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true }, "timers.ellapsedViewletRestore" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true }, @@ -195,15 +194,6 @@ export interface IStartupMetrics { */ readonly ellapsedWindowLoadToRequire: number; - /** - * The time it took to require the global storage DB, connect to it - * and load the initial set of values. - * - * * Happens in the main-process - * * Measured with the `main:willInitGlobalStorage` and `main:didInitGlobalStorage` performance marks. - */ - readonly ellapsedGlobalStorageInitMain: number; - /** * The time it took to require the workspace storage DB, connect to it * and load the initial set of values. @@ -399,7 +389,6 @@ class TimerService implements ITimerService { ellapsedWindowLoad: initialStartup ? perf.getDuration('main:appReady', 'main:loadWindow') : undefined, ellapsedWindowLoadToRequire: perf.getDuration('main:loadWindow', 'willLoadWorkbenchMain'), ellapsedRequire: perf.getDuration('willLoadWorkbenchMain', 'didLoadWorkbenchMain'), - ellapsedGlobalStorageInitMain: perf.getDuration('main:willInitGlobalStorage', 'main:didInitGlobalStorage'), ellapsedWorkspaceStorageInit: perf.getDuration('willInitWorkspaceStorage', 'didInitWorkspaceStorage'), ellapsedWorkspaceServiceInit: perf.getDuration('willInitWorkspaceService', 'didInitWorkspaceService'), ellapsedExtensions: perf.getDuration('willLoadExtensions', 'didLoadExtensions'), diff --git a/src/vs/workbench/test/common/editor/untitledEditor.test.ts b/src/vs/workbench/test/common/editor/untitledEditor.test.ts index a710ecd7bff..5ce1d30ace9 100644 --- a/src/vs/workbench/test/common/editor/untitledEditor.test.ts +++ b/src/vs/workbench/test/common/editor/untitledEditor.test.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { URI } from 'vs/base/common/uri'; import * as assert from 'assert'; -import { join } from 'vs/base/common/extpath'; +import { join } from 'vs/base/common/path'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IUntitledEditorService, UntitledEditorService } from 'vs/workbench/services/untitled/common/untitledEditorService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; diff --git a/src/vs/workbench/test/electron-browser/api/extHostConfiguration.test.ts b/src/vs/workbench/test/electron-browser/api/extHostConfiguration.test.ts index 25213f7c845..e8d1abc034c 100644 --- a/src/vs/workbench/test/electron-browser/api/extHostConfiguration.test.ts +++ b/src/vs/workbench/test/electron-browser/api/extHostConfiguration.test.ts @@ -56,7 +56,7 @@ suite('ExtHostConfiguration', function () { assert.equal(extHostConfig.getConfiguration('search.exclude')['**/node_modules'], true); assert.equal(extHostConfig.getConfiguration('search.exclude').get('**/node_modules'), true); - assert.equal(extHostConfig.getConfiguration('search').get('exclude')['**/node_modules'], true); + assert.equal(extHostConfig.getConfiguration('search').get('exclude')!['**/node_modules'], true); assert.equal(extHostConfig.getConfiguration('search.exclude').has('**/node_modules'), true); assert.equal(extHostConfig.getConfiguration('search').has('exclude.**/node_modules'), true); @@ -167,7 +167,7 @@ suite('ExtHostConfiguration', function () { }); const testObject = all.getConfiguration(); - let actual = testObject.get('farboo'); + let actual: any = testObject.get('farboo'); assert.deepEqual(JSON.stringify({ 'config0': true, 'nested': { @@ -190,7 +190,7 @@ suite('ExtHostConfiguration', function () { 'config4': '' }), JSON.stringify(actual)); - actual = testObject.get('workbench')['colorCustomizations']!; + actual = testObject.get('workbench')!['colorCustomizations']!; actual['statusBar.background'] = 'anothervalue'; assert.deepEqual(JSON.stringify({ 'statusBar.foreground': 'somevalue', @@ -241,7 +241,7 @@ suite('ExtHostConfiguration', function () { } }); - let testObject = all.getConfiguration(); + let testObject: any = all.getConfiguration(); try { testObject['get'] = null; diff --git a/src/vs/workbench/test/electron-browser/api/extHostTextEditor.test.ts b/src/vs/workbench/test/electron-browser/api/extHostTextEditor.test.ts index 87f42a87e02..b7cdcfcf6e9 100644 --- a/src/vs/workbench/test/electron-browser/api/extHostTextEditor.test.ts +++ b/src/vs/workbench/test/electron-browser/api/extHostTextEditor.test.ts @@ -19,7 +19,7 @@ suite('ExtHostTextEditor', () => { ], '\n', 'text', 1, false); setup(() => { - editor = new ExtHostTextEditor(null!, 'fake', doc, [], { cursorStyle: 0, insertSpaces: true, lineNumbers: 1, tabSize: 4 }, [], 1); + editor = new ExtHostTextEditor(null!, 'fake', doc, [], { cursorStyle: 0, insertSpaces: true, lineNumbers: 1, tabSize: 4, indentSize: 4 }, [], 1); }); test('disposed editor', () => { @@ -45,7 +45,7 @@ suite('ExtHostTextEditor', () => { applyCount += 1; return Promise.resolve(true); } - }, 'edt1', doc, [], { cursorStyle: 0, insertSpaces: true, lineNumbers: 1, tabSize: 4 }, [], 1); + }, 'edt1', doc, [], { cursorStyle: 0, insertSpaces: true, lineNumbers: 1, tabSize: 4, indentSize: 4 }, [], 1); await editor.edit(edit => { }); assert.equal(applyCount, 0); @@ -88,6 +88,7 @@ suite('ExtHostTextEditorOptions', () => { }; opts = new ExtHostTextEditorOptions(mockProxy, '1', { tabSize: 4, + indentSize: 4, insertSpaces: false, cursorStyle: TextEditorCursorStyle.Line, lineNumbers: TextEditorLineNumbersStyle.On @@ -102,6 +103,7 @@ suite('ExtHostTextEditorOptions', () => { function assertState(opts: ExtHostTextEditorOptions, expected: IResolvedTextEditorConfiguration): void { let actual = { tabSize: opts.tabSize, + indentSize: opts.indentSize, insertSpaces: opts.insertSpaces, cursorStyle: opts.cursorStyle, lineNumbers: opts.lineNumbers @@ -113,6 +115,7 @@ suite('ExtHostTextEditorOptions', () => { opts.tabSize = 4; assertState(opts, { tabSize: 4, + indentSize: 4, insertSpaces: false, cursorStyle: TextEditorCursorStyle.Line, lineNumbers: TextEditorLineNumbersStyle.On @@ -124,6 +127,7 @@ suite('ExtHostTextEditorOptions', () => { opts.tabSize = 1; assertState(opts, { tabSize: 1, + indentSize: 4, insertSpaces: false, cursorStyle: TextEditorCursorStyle.Line, lineNumbers: TextEditorLineNumbersStyle.On @@ -135,6 +139,7 @@ suite('ExtHostTextEditorOptions', () => { opts.tabSize = 2.3; assertState(opts, { tabSize: 2, + indentSize: 4, insertSpaces: false, cursorStyle: TextEditorCursorStyle.Line, lineNumbers: TextEditorLineNumbersStyle.On @@ -146,6 +151,7 @@ suite('ExtHostTextEditorOptions', () => { opts.tabSize = '2'; assertState(opts, { tabSize: 2, + indentSize: 4, insertSpaces: false, cursorStyle: TextEditorCursorStyle.Line, lineNumbers: TextEditorLineNumbersStyle.On @@ -157,6 +163,7 @@ suite('ExtHostTextEditorOptions', () => { opts.tabSize = 'auto'; assertState(opts, { tabSize: 4, + indentSize: 4, insertSpaces: false, cursorStyle: TextEditorCursorStyle.Line, lineNumbers: TextEditorLineNumbersStyle.On @@ -168,6 +175,7 @@ suite('ExtHostTextEditorOptions', () => { opts.tabSize = null!; assertState(opts, { tabSize: 4, + indentSize: 4, insertSpaces: false, cursorStyle: TextEditorCursorStyle.Line, lineNumbers: TextEditorLineNumbersStyle.On @@ -179,6 +187,7 @@ suite('ExtHostTextEditorOptions', () => { opts.tabSize = -5; assertState(opts, { tabSize: 4, + indentSize: 4, insertSpaces: false, cursorStyle: TextEditorCursorStyle.Line, lineNumbers: TextEditorLineNumbersStyle.On @@ -190,6 +199,7 @@ suite('ExtHostTextEditorOptions', () => { opts.tabSize = 'hello'; assertState(opts, { tabSize: 4, + indentSize: 4, insertSpaces: false, cursorStyle: TextEditorCursorStyle.Line, lineNumbers: TextEditorLineNumbersStyle.On @@ -201,6 +211,127 @@ suite('ExtHostTextEditorOptions', () => { opts.tabSize = '-17'; assertState(opts, { tabSize: 4, + indentSize: 4, + insertSpaces: false, + cursorStyle: TextEditorCursorStyle.Line, + lineNumbers: TextEditorLineNumbersStyle.On + }); + assert.deepEqual(calls, []); + }); + + test('can set indentSize to the same value', () => { + opts.indentSize = 4; + assertState(opts, { + tabSize: 4, + indentSize: 4, + insertSpaces: false, + cursorStyle: TextEditorCursorStyle.Line, + lineNumbers: TextEditorLineNumbersStyle.On + }); + assert.deepEqual(calls, []); + }); + + test('can change indentSize to positive integer', () => { + opts.indentSize = 1; + assertState(opts, { + tabSize: 4, + indentSize: 1, + insertSpaces: false, + cursorStyle: TextEditorCursorStyle.Line, + lineNumbers: TextEditorLineNumbersStyle.On + }); + assert.deepEqual(calls, [{ indentSize: 1 }]); + }); + + test('can change indentSize to positive float', () => { + opts.indentSize = 2.3; + assertState(opts, { + tabSize: 4, + indentSize: 2, + insertSpaces: false, + cursorStyle: TextEditorCursorStyle.Line, + lineNumbers: TextEditorLineNumbersStyle.On + }); + assert.deepEqual(calls, [{ indentSize: 2 }]); + }); + + test('can change indentSize to a string number', () => { + opts.indentSize = '2'; + assertState(opts, { + tabSize: 4, + indentSize: 2, + insertSpaces: false, + cursorStyle: TextEditorCursorStyle.Line, + lineNumbers: TextEditorLineNumbersStyle.On + }); + assert.deepEqual(calls, [{ indentSize: 2 }]); + }); + + test('indentSize can request to use tabSize', () => { + opts.indentSize = 'tabSize'; + assertState(opts, { + tabSize: 4, + indentSize: 4, + insertSpaces: false, + cursorStyle: TextEditorCursorStyle.Line, + lineNumbers: TextEditorLineNumbersStyle.On + }); + assert.deepEqual(calls, [{ indentSize: 'tabSize' }]); + }); + + test('indentSize cannot request indentation detection', () => { + opts.indentSize = 'auto'; + assertState(opts, { + tabSize: 4, + indentSize: 4, + insertSpaces: false, + cursorStyle: TextEditorCursorStyle.Line, + lineNumbers: TextEditorLineNumbersStyle.On + }); + assert.deepEqual(calls, []); + }); + + test('ignores invalid indentSize 1', () => { + opts.indentSize = null; + assertState(opts, { + tabSize: 4, + indentSize: 4, + insertSpaces: false, + cursorStyle: TextEditorCursorStyle.Line, + lineNumbers: TextEditorLineNumbersStyle.On + }); + assert.deepEqual(calls, []); + }); + + test('ignores invalid indentSize 2', () => { + opts.indentSize = -5; + assertState(opts, { + tabSize: 4, + indentSize: 4, + insertSpaces: false, + cursorStyle: TextEditorCursorStyle.Line, + lineNumbers: TextEditorLineNumbersStyle.On + }); + assert.deepEqual(calls, []); + }); + + test('ignores invalid indentSize 3', () => { + opts.indentSize = 'hello'; + assertState(opts, { + tabSize: 4, + indentSize: 4, + insertSpaces: false, + cursorStyle: TextEditorCursorStyle.Line, + lineNumbers: TextEditorLineNumbersStyle.On + }); + assert.deepEqual(calls, []); + }); + + test('ignores invalid indentSize 4', () => { + opts.indentSize = '-17'; + assertState(opts, { + tabSize: 4, + indentSize: 4, insertSpaces: false, cursorStyle: TextEditorCursorStyle.Line, lineNumbers: TextEditorLineNumbersStyle.On @@ -212,6 +343,7 @@ suite('ExtHostTextEditorOptions', () => { opts.insertSpaces = false; assertState(opts, { tabSize: 4, + indentSize: 4, insertSpaces: false, cursorStyle: TextEditorCursorStyle.Line, lineNumbers: TextEditorLineNumbersStyle.On @@ -223,6 +355,7 @@ suite('ExtHostTextEditorOptions', () => { opts.insertSpaces = true; assertState(opts, { tabSize: 4, + indentSize: 4, insertSpaces: true, cursorStyle: TextEditorCursorStyle.Line, lineNumbers: TextEditorLineNumbersStyle.On @@ -234,6 +367,7 @@ suite('ExtHostTextEditorOptions', () => { opts.insertSpaces = 'false'; assertState(opts, { tabSize: 4, + indentSize: 4, insertSpaces: false, cursorStyle: TextEditorCursorStyle.Line, lineNumbers: TextEditorLineNumbersStyle.On @@ -245,6 +379,7 @@ suite('ExtHostTextEditorOptions', () => { opts.insertSpaces = 'hello'; assertState(opts, { tabSize: 4, + indentSize: 4, insertSpaces: true, cursorStyle: TextEditorCursorStyle.Line, lineNumbers: TextEditorLineNumbersStyle.On @@ -256,6 +391,7 @@ suite('ExtHostTextEditorOptions', () => { opts.insertSpaces = 'auto'; assertState(opts, { tabSize: 4, + indentSize: 4, insertSpaces: false, cursorStyle: TextEditorCursorStyle.Line, lineNumbers: TextEditorLineNumbersStyle.On @@ -267,6 +403,7 @@ suite('ExtHostTextEditorOptions', () => { opts.cursorStyle = TextEditorCursorStyle.Line; assertState(opts, { tabSize: 4, + indentSize: 4, insertSpaces: false, cursorStyle: TextEditorCursorStyle.Line, lineNumbers: TextEditorLineNumbersStyle.On @@ -278,6 +415,7 @@ suite('ExtHostTextEditorOptions', () => { opts.cursorStyle = TextEditorCursorStyle.Block; assertState(opts, { tabSize: 4, + indentSize: 4, insertSpaces: false, cursorStyle: TextEditorCursorStyle.Block, lineNumbers: TextEditorLineNumbersStyle.On @@ -289,6 +427,7 @@ suite('ExtHostTextEditorOptions', () => { opts.lineNumbers = TextEditorLineNumbersStyle.On; assertState(opts, { tabSize: 4, + indentSize: 4, insertSpaces: false, cursorStyle: TextEditorCursorStyle.Line, lineNumbers: TextEditorLineNumbersStyle.On @@ -300,6 +439,7 @@ suite('ExtHostTextEditorOptions', () => { opts.lineNumbers = TextEditorLineNumbersStyle.Off; assertState(opts, { tabSize: 4, + indentSize: 4, insertSpaces: false, cursorStyle: TextEditorCursorStyle.Line, lineNumbers: TextEditorLineNumbersStyle.Off @@ -316,6 +456,7 @@ suite('ExtHostTextEditorOptions', () => { }); assertState(opts, { tabSize: 4, + indentSize: 4, insertSpaces: false, cursorStyle: TextEditorCursorStyle.Line, lineNumbers: TextEditorLineNumbersStyle.On @@ -330,6 +471,7 @@ suite('ExtHostTextEditorOptions', () => { }); assertState(opts, { tabSize: 4, + indentSize: 4, insertSpaces: true, cursorStyle: TextEditorCursorStyle.Line, lineNumbers: TextEditorLineNumbersStyle.On @@ -344,6 +486,7 @@ suite('ExtHostTextEditorOptions', () => { }); assertState(opts, { tabSize: 3, + indentSize: 4, insertSpaces: false, cursorStyle: TextEditorCursorStyle.Line, lineNumbers: TextEditorLineNumbersStyle.On @@ -358,6 +501,7 @@ suite('ExtHostTextEditorOptions', () => { }); assertState(opts, { tabSize: 4, + indentSize: 4, insertSpaces: false, cursorStyle: TextEditorCursorStyle.Block, lineNumbers: TextEditorLineNumbersStyle.Relative diff --git a/src/vs/workbench/test/workbenchTestServices.ts b/src/vs/workbench/test/workbenchTestServices.ts index 6f8dcf7c270..33d9287af94 100644 --- a/src/vs/workbench/test/workbenchTestServices.ts +++ b/src/vs/workbench/test/workbenchTestServices.ts @@ -6,7 +6,7 @@ import 'vs/workbench/contrib/files/electron-browser/files.contribution'; // load our contribution into the test import { FileEditorInput } from 'vs/workbench/contrib/files/common/editors/fileEditorInput'; import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; -import * as extpath from 'vs/base/common/extpath'; +import { join } from 'vs/base/common/path'; import * as resources from 'vs/base/common/resources'; import { URI } from 'vs/base/common/uri'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; @@ -163,7 +163,7 @@ export class TestContextService implements IWorkspaceContextService { } public toResource(workspaceRelativePath: string): URI { - return URI.file(extpath.join('C:\\', workspaceRelativePath)); + return URI.file(join('C:\\', workspaceRelativePath)); } public isCurrentWorkspace(workspaceIdentifier: ISingleFolderWorkspaceIdentifier | IWorkspaceIdentifier): boolean { @@ -1472,9 +1472,8 @@ export class TestViewletService implements IViewletService { getViewlets(): ViewletDescriptor[] { return []; } getProgressIndicator(_id: string): IProgressService | null { return null; } - } export function getRandomTestPath(tmpdir: string, ...segments: string[]): string { - return extpath.join(tmpdir, ...segments, generateUuid()); + return join(tmpdir, ...segments, generateUuid()); } diff --git a/yarn.lock b/yarn.lock index 3fdec48c164..7f320a02b0b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9408,10 +9408,10 @@ vscode-nsfw@1.1.1: lodash.isundefined "^3.0.1" nan "^2.10.0" -vscode-proxy-agent@0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/vscode-proxy-agent/-/vscode-proxy-agent-0.3.0.tgz#b5c8bea5046761966e1fa71f89d9cef11c457894" - integrity sha512-R6qz8Sc0ocNfeFPOp3k6QLP/Y8HzK1yqXwfgB1f0GakVzUGMDmniRe8RLxIiCAqlxGaWMn2yqpTSNUYZ1obPsQ== +vscode-proxy-agent@0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/vscode-proxy-agent/-/vscode-proxy-agent-0.4.0.tgz#574833e65405c6333f350f1b9fef9909deccb6b5" + integrity sha512-L+WKjDOXRPxpq31Uj1Wr3++jaNNmhykn8JnGoYcwepbTnUwJKCbyyXRgb/hlBx0LXsF+k3BsnXt+r+5Q8rm97g== dependencies: debug "3.1.0" http-proxy-agent "2.1.0" @@ -9603,11 +9603,6 @@ windows-process-tree@0.2.3: dependencies: nan "^2.10.0" -winreg@^1.2.4: - version "1.2.4" - resolved "https://registry.yarnpkg.com/winreg/-/winreg-1.2.4.tgz#ba065629b7a925130e15779108cf540990e98d1b" - integrity sha1-ugZWKbepJRMOFXeRCM9UCZDpjRs= - wordwrap@0.0.2: version "0.0.2" resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-0.0.2.tgz#b79669bb42ecb409f83d583cad52ca17eaa1643f"