diff --git a/extensions/configuration-editing/src/extension.ts b/extensions/configuration-editing/src/extension.ts index 5aa5ebed1eb4116eee85e64e2521b0bbba543e12..ee653a7d17fbca932fb8beac14a73c376ec10b07 100644 --- a/extensions/configuration-editing/src/extension.ts +++ b/extensions/configuration-editing/src/extension.ts @@ -27,6 +27,12 @@ export function activate(context: vscode.ExtensionContext): void { //extensions suggestions context.subscriptions.push(...registerExtensionsCompletions()); + // launch.json variable suggestions + context.subscriptions.push(registerVariableCompletions('**/launch.json')); + + // task.json variable suggestions + context.subscriptions.push(registerVariableCompletions('**/tasks.json')); + // launch.json decorations context.subscriptions.push(vscode.window.onDidChangeActiveTextEditor(editor => updateLaunchJsonDecorations(editor), null, context.subscriptions)); context.subscriptions.push(vscode.workspace.onDidChangeTextDocument(event => { @@ -108,6 +114,27 @@ function registerSettingsCompletions(): vscode.Disposable { }); } +function registerVariableCompletions(pattern: string): vscode.Disposable { + return vscode.languages.registerCompletionItemProvider({ language: 'jsonc', pattern }, { + provideCompletionItems(document, position, token) { + const location = getLocation(document.getText(), document.offsetAt(position)); + if (!location.isAtPropertyKey && location.previousNode && location.previousNode.type === 'string') { + return [{ label: 'workspaceFolder', detail: localize('workspaceFolder', "The path of the folder opened in VS Code") }, { label: 'workspaceFolderBasename', detail: localize('workspaceFolderBasename', "The name of the folder opened in VS Code without any slashes (/)") }, + { label: 'relativeFile', detail: localize('relativeFile', "The current opened file relative to ${workspaceFolder}") }, { label: 'file', detail: localize('file', "The current opened file") }, { label: 'cwd', detail: localize('cwd', "The task runner's current working directory on startup") }, + { label: 'lineNumber', detail: localize('lineNumber', "The current selected line number in the active file") }, { label: 'selectedText', detail: localize('selectedText', "The current selected text in the active file") }, + { label: 'fileDirname', detail: localize('fileDirname', "The current opened file's dirname") }, { label: 'fileExtname', detail: localize('fileExtname', "The current opened file's extension") }, { label: 'fileBasename', detail: localize('fileBasename', "The current opened file's basename") }, + { label: 'fileBasenameNoExtension', detail: localize('fileBasenameNoExtension', "The current opened file's basename with no file extension") }].map(variable => ({ + label: '${' + variable.label + '}', + range: new vscode.Range(position, position), + detail: variable.detail + })); + } + + return []; + } + }); +} + interface IExtensionsContent { recommendations: string[]; } diff --git a/package.json b/package.json index 2af87ab9098a0aa28f1fc065ff1d8c0b8878ff14..958610a6490410fb62ec31c692ee9887ab31b830 100644 --- a/package.json +++ b/package.json @@ -42,14 +42,14 @@ "node-pty": "0.7.4", "semver": "^5.5.0", "spdlog": "0.6.0", - "sudo-prompt": "^8.0.0", + "sudo-prompt": "8.2.0", "v8-inspect-profiler": "^0.0.8", "vscode-chokidar": "1.6.2", "vscode-debugprotocol": "1.28.0", "vscode-nsfw": "1.0.17", "vscode-ripgrep": "^0.8.1", "vscode-textmate": "^3.3.3", - "vscode-xterm": "3.5.0-beta1", + "vscode-xterm": "3.4.0-beta3", "yauzl": "^2.9.1" }, "devDependencies": { @@ -136,4 +136,4 @@ "windows-mutex": "^0.2.0", "windows-process-tree": "0.2.2" } -} +} \ No newline at end of file diff --git a/src/vs/code/electron-main/window.ts b/src/vs/code/electron-main/window.ts index af71a291e7b173f55c564fd517a97ddc75548eac..338e1a3a6bec1114384914e1c93aaa9d73174537 100644 --- a/src/vs/code/electron-main/window.ts +++ b/src/vs/code/electron-main/window.ts @@ -367,7 +367,7 @@ export class CodeWindow implements ICodeWindow { } // To prevent flashing, we set the window visible after the page has finished to load but before Code is loaded - if (!this._win.isVisible()) { + if (this._win && !this._win.isVisible()) { if (this.windowState.mode === WindowMode.Maximized) { this._win.maximize(); } diff --git a/src/vs/editor/contrib/colorPicker/colorPickerWidget.ts b/src/vs/editor/contrib/colorPicker/colorPickerWidget.ts index 5a8f2a463a7ad66583932dd4919814119ce1b613..5e462f6d9d463a609fd5d9f519eb8fa82b247328 100644 --- a/src/vs/editor/contrib/colorPicker/colorPickerWidget.ts +++ b/src/vs/editor/contrib/colorPicker/colorPickerWidget.ts @@ -57,7 +57,7 @@ export class ColorPickerHeader extends Disposable { } private onDidChangePresentation(): void { - this.pickedColorNode.textContent = this.model.presentation.label; + this.pickedColorNode.textContent = this.model.presentation ? this.model.presentation.label : ''; } } diff --git a/src/vs/platform/actions/browser/menuItemActionItem.ts b/src/vs/platform/actions/browser/menuItemActionItem.ts index b93c8334898e6700bd6798481db245ddc5f84610..03fe83a4cc06a20333044b3990528d09a652328d 100644 --- a/src/vs/platform/actions/browser/menuItemActionItem.ts +++ b/src/vs/platform/actions/browser/menuItemActionItem.ts @@ -26,6 +26,7 @@ class AlternativeKeyEmitter extends Emitter { private _subscriptions: IDisposable[] = []; private _isPressed: boolean; private static instance: AlternativeKeyEmitter; + private _suppressAltKeyUp: boolean = false; private constructor(contextMenuService: IContextMenuService) { super(); @@ -33,7 +34,16 @@ class AlternativeKeyEmitter extends Emitter { this._subscriptions.push(domEvent(document.body, 'keydown')(e => { this.isPressed = e.altKey || ((isWindows || isLinux) && e.shiftKey); })); - this._subscriptions.push(domEvent(document.body, 'keyup')(e => this.isPressed = false)); + this._subscriptions.push(domEvent(document.body, 'keyup')(e => { + if (this.isPressed) { + if (this._suppressAltKeyUp) { + e.preventDefault(); + } + } + + this._suppressAltKeyUp = false; + this.isPressed = false; + })); this._subscriptions.push(domEvent(document.body, 'mouseleave')(e => this.isPressed = false)); this._subscriptions.push(domEvent(document.body, 'blur')(e => this.isPressed = false)); // Workaround since we do not get any events while a context menu is shown @@ -49,6 +59,12 @@ class AlternativeKeyEmitter extends Emitter { this.fire(this._isPressed); } + suppressAltKeyUp() { + // Sometimes the native alt behavior needs to be suppresed since the alt was already used as an alternative key + // Example: windows behavior to toggle tha top level menu #44396 + this._suppressAltKeyUp = true; + } + static getInstance(contextMenuService: IContextMenuService) { if (!AlternativeKeyEmitter.instance) { AlternativeKeyEmitter.instance = new AlternativeKeyEmitter(contextMenuService); @@ -149,6 +165,11 @@ export class MenuItemActionItem extends ActionItem { event.preventDefault(); event.stopPropagation(); + const altKey = AlternativeKeyEmitter.getInstance(this._contextMenuService); + if (altKey.isPressed) { + altKey.suppressAltKeyUp(); + } + this.actionRunner.run(this._commandAction) .done(undefined, err => this._notificationService.error(err)); } diff --git a/src/vs/platform/extensionManagement/node/extensionManagementService.ts b/src/vs/platform/extensionManagement/node/extensionManagementService.ts index 8dacc38fcf8c37d6bd192179f9b4c2615fe301a1..243399ea1989ad95a35354155fca453041dbfa5d 100644 --- a/src/vs/platform/extensionManagement/node/extensionManagementService.ts +++ b/src/vs/platform/extensionManagement/node/extensionManagementService.ts @@ -102,6 +102,11 @@ interface InstallableExtension { metadata?: IGalleryMetadata; } +enum Operation { + Install = 1, + Update +} + export class ExtensionManagementService extends Disposable implements IExtensionManagementService { _serviceBrand: any; @@ -227,19 +232,21 @@ export class ExtensionManagementService extends Disposable implements IExtension installFromGallery(extension: IGalleryExtension): TPromise { this.onInstallExtensions([extension]); - return this.collectExtensionsToInstall(extension) - .then( - extensionsToInstall => { - if (extensionsToInstall.length > 1) { - this.onInstallExtensions(extensionsToInstall.slice(1)); - } - return this.downloadAndInstallExtensions(extensionsToInstall) - .then( - locals => this.onDidInstallExtensions(extensionsToInstall, locals, []) - .then(() => locals.filter(l => areSameExtensions({ id: getGalleryExtensionIdFromLocal(l), uuid: l.identifier.uuid }, extension.identifier)[0])), - errors => this.onDidInstallExtensions(extensionsToInstall, [], errors)); - }, - error => this.onDidInstallExtensions([extension], [], [error])); + return this.getInstalled(LocalExtensionType.User) + .then(installed => this.collectExtensionsToInstall(extension) + .then( + extensionsToInstall => { + if (extensionsToInstall.length > 1) { + this.onInstallExtensions(extensionsToInstall.slice(1)); + } + const operataions: Operation[] = extensionsToInstall.map(e => this.getOperation(e, installed)); + return this.downloadAndInstallExtensions(extensionsToInstall) + .then( + locals => this.onDidInstallExtensions(extensionsToInstall, locals, operataions, []) + .then(() => locals.filter(l => areSameExtensions({ id: getGalleryExtensionIdFromLocal(l), uuid: l.identifier.uuid }, extension.identifier)[0])), + errors => this.onDidInstallExtensions(extensionsToInstall, [], operataions, errors)); + }, + error => this.onDidInstallExtensions([extension], [], [this.getOperation(extension, installed)], [error]))); } reinstallFromGallery(extension: ILocalExtension): TPromise { @@ -259,6 +266,10 @@ export class ExtensionManagementService extends Disposable implements IExtension }); } + private getOperation(extensionToInstall: IGalleryExtension, installed: ILocalExtension[]): Operation { + return installed.some(i => areSameExtensions({ id: getGalleryExtensionIdFromLocal(i), uuid: i.identifier.uuid }, extensionToInstall.identifier)) ? Operation.Update : Operation.Install; + } + private collectExtensionsToInstall(extension: IGalleryExtension): TPromise { return this.galleryService.loadCompatibleVersion(extension) .then(compatible => { @@ -340,7 +351,7 @@ export class ExtensionManagementService extends Disposable implements IExtension } } - private onDidInstallExtensions(extensions: IGalleryExtension[], locals: ILocalExtension[], errors: Error[]): TPromise { + private onDidInstallExtensions(extensions: IGalleryExtension[], locals: ILocalExtension[], operations: Operation[], errors: Error[]): TPromise { extensions.forEach((gallery, index) => { const identifier = { id: getLocalExtensionIdFromGallery(gallery, gallery.version), uuid: gallery.identifier.uuid }; const local = locals[index]; @@ -354,7 +365,7 @@ export class ExtensionManagementService extends Disposable implements IExtension this._onDidInstallExtension.fire({ identifier, gallery, error: errorCode }); } const startTime = this.installationStartTime.get(gallery.identifier.id); - this.reportTelemetry('extensionGallery:install', getGalleryExtensionTelemetryData(gallery), startTime ? new Date().getTime() - startTime : void 0, error); + this.reportTelemetry(operations[index] === Operation.Install ? 'extensionGallery:install' : 'extensionGallery:update', getGalleryExtensionTelemetryData(gallery), startTime ? new Date().getTime() - startTime : void 0, error); this.installationStartTime.delete(gallery.identifier.id); }); return errors.length ? TPromise.wrapError(this.joinErrors(errors)) : TPromise.as(null); @@ -892,6 +903,16 @@ export class ExtensionManagementService extends Disposable implements IExtension ] } */ + /* __GDPR__ + "extensionGallery:update" : { + "success": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true }, + "duration" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true }, + "errorcode": { "classification": "CallstackOrException", "purpose": "PerformanceAndHealth" }, + "${include}": [ + "${GalleryExtensionTelemetryData}" + ] + } + */ this.telemetryService.publicLog(eventName, assign(extensionData, { success: !error, duration, errorcode })); } } diff --git a/src/vs/platform/quickOpen/common/quickOpen.ts b/src/vs/platform/quickOpen/common/quickOpen.ts index c6484ef828e1a23bc1e3253f12666aabea3b5c5d..e5e1bffd9d41d85e7d25f6a0353c2ee6fb681271 100644 --- a/src/vs/platform/quickOpen/common/quickOpen.ts +++ b/src/vs/platform/quickOpen/common/quickOpen.ts @@ -33,7 +33,6 @@ export interface IPickOpenEntry { run?: (context: IEntryRunContext) => void; action?: IAction; payload?: any; - picked?: boolean; } export interface IPickOpenItem { @@ -85,46 +84,6 @@ export interface IPickOptions { * a context key to set when this picker is active */ contextKey?: string; - - /** - * an optional flag to make this picker multi-select (honoured by extension API) - */ - canPickMany?: boolean; -} - -export interface IInputOptions { - - /** - * the value to prefill in the input box - */ - value?: string; - - /** - * the selection of value, default to the whole word - */ - valueSelection?: [number, number]; - - /** - * the text to display underneath the input box - */ - prompt?: string; - - /** - * an optional string to show as place holder in the input box to guide the user what to type - */ - placeHolder?: string; - - /** - * set to true to show a password prompt that will not show the typed value - */ - password?: boolean; - - ignoreFocusLost?: boolean; - - /** - * an optional function that is used to validate user input. - */ - validateInput?: (input: string) => TPromise; } export interface IShowOptions { diff --git a/src/vs/platform/quickinput/common/quickInput.ts b/src/vs/platform/quickinput/common/quickInput.ts index ec9fde4de40f6973d6ec19288863d6ef3c5ca0ee..b8aa997bdba6b4d83febd42d663b669773abd5ab 100644 --- a/src/vs/platform/quickinput/common/quickInput.ts +++ b/src/vs/platform/quickinput/common/quickInput.ts @@ -6,8 +6,83 @@ import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { TPromise } from 'vs/base/common/winjs.base'; -import { IPickOptions, IPickOpenEntry, IInputOptions } from 'vs/platform/quickOpen/common/quickOpen'; import { CancellationToken } from 'vs/base/common/cancellation'; +import { ResolvedKeybinding } from 'vs/base/common/keyCodes'; + +export interface IPickOpenEntry { + id?: string; + label: string; + description?: string; + detail?: string; + picked?: boolean; +} + +export interface IQuickNavigateConfiguration { + keybindings: ResolvedKeybinding[]; +} + +export interface IPickOptions { + + /** + * an optional string to show as place holder in the input box to guide the user what she picks on + */ + placeHolder?: string; + + /** + * an optional flag to include the description when filtering the picks + */ + matchOnDescription?: boolean; + + /** + * an optional flag to include the detail when filtering the picks + */ + matchOnDetail?: boolean; + + /** + * an optional flag to not close the picker on focus lost + */ + ignoreFocusLost?: boolean; + + /** + * an optional flag to make this picker multi-select + */ + canPickMany?: boolean; +} + +export interface IInputOptions { + + /** + * the value to prefill in the input box + */ + value?: string; + + /** + * the selection of value, default to the whole word + */ + valueSelection?: [number, number]; + + /** + * the text to display underneath the input box + */ + prompt?: string; + + /** + * an optional string to show as place holder in the input box to guide the user what to type + */ + placeHolder?: string; + + /** + * set to true to show a password prompt that will not show the typed value + */ + password?: boolean; + + ignoreFocusLost?: boolean; + + /** + * an optional function that is used to validate user input. + */ + validateInput?: (input: string) => TPromise; +} export const IQuickInputService = createDecorator('quickInputService'); @@ -29,7 +104,7 @@ export interface IQuickInputService { toggle(): void; - navigate(next: boolean): void; + navigate(next: boolean, quickNavigate?: IQuickNavigateConfiguration): void; accept(): TPromise; diff --git a/src/vs/workbench/api/electron-browser/mainThreadQuickOpen.ts b/src/vs/workbench/api/electron-browser/mainThreadQuickOpen.ts index 03730ce66ac592fa4ef15b2db1d8f62448952cfd..0d6947e576eb4ea8ffd2e1b2ffeaedb6fe8bda3e 100644 --- a/src/vs/workbench/api/electron-browser/mainThreadQuickOpen.ts +++ b/src/vs/workbench/api/electron-browser/mainThreadQuickOpen.ts @@ -6,8 +6,7 @@ import { TPromise } from 'vs/base/common/winjs.base'; import { asWinJsPromise } from 'vs/base/common/async'; -import { IPickOptions, IInputOptions } from 'vs/platform/quickOpen/common/quickOpen'; -import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; +import { IPickOptions, IInputOptions, IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; import { InputBoxOptions } from 'vscode'; import { ExtHostContext, MainThreadQuickOpenShape, ExtHostQuickOpenShape, MyQuickPickItems, MainContext, IExtHostContext } from '../node/extHost.protocol'; import { extHostNamedCustomer } from 'vs/workbench/api/electron-browser/extHostCustomers'; diff --git a/src/vs/workbench/api/node/extHost.protocol.ts b/src/vs/workbench/api/node/extHost.protocol.ts index 56897f732baecf5f93b4ba59479d1b10ab59d0d9..22d0278a20384cd7217965846e3b5db2d74c8753 100644 --- a/src/vs/workbench/api/node/extHost.protocol.ts +++ b/src/vs/workbench/api/node/extHost.protocol.ts @@ -26,7 +26,7 @@ import * as modes from 'vs/editor/common/modes'; import { IConfigurationData, ConfigurationTarget, IConfigurationModel } from 'vs/platform/configuration/common/configuration'; import { IConfig, IAdapterExecutable, ITerminalSettings } from 'vs/workbench/parts/debug/common/debug'; -import { IPickOpenEntry, IPickOptions } from 'vs/platform/quickOpen/common/quickOpen'; +import { IPickOpenEntry, IPickOptions } from 'vs/platform/quickinput/common/quickInput'; import { SaveReason } from 'vs/workbench/services/textfile/common/textfiles'; import { TextEditorCursorStyle } from 'vs/editor/common/config/editorOptions'; import { EndOfLine, TextEditorLineNumbersStyle } from 'vs/workbench/api/node/extHostTypes'; diff --git a/src/vs/workbench/api/node/extHostQuickOpen.ts b/src/vs/workbench/api/node/extHostQuickOpen.ts index 1b27e118913bf18668b8003e4c99d4fcc8f328ff..275f124a1e3f7f3e565d34ce4dafcaa639966db5 100644 --- a/src/vs/workbench/api/node/extHostQuickOpen.ts +++ b/src/vs/workbench/api/node/extHostQuickOpen.ts @@ -40,7 +40,6 @@ export class ExtHostQuickOpen implements ExtHostQuickOpenShape { const itemsPromise = >TPromise.wrap(itemsOrItemsPromise); const quickPickWidget = this._proxy.$show({ - autoFocus: { autoFocusFirstEntry: true }, placeHolder: options && options.placeHolder, matchOnDescription: options && options.matchOnDescription, matchOnDetail: options && options.matchOnDetail, diff --git a/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts b/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts index fa7b31c58e5bd737afc196aeafec1de1d3459f61..338c2a65dee90d65f4eec059cf1d86ddfdbe940d 100644 --- a/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts +++ b/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts @@ -74,7 +74,6 @@ export class ActivitybarPart extends Part { this.globalActivityIdToActions = Object.create(null); - this.placeholderComposites = JSON.parse(this.storageService.get(ActivitybarPart.PLACEHOLDER_VIEWLETS, StorageScope.GLOBAL, '[]')); this.compositeActions = Object.create(null); this.compositeBar = this.instantiationService.createInstance(CompositeBar, { icon: true, @@ -91,6 +90,8 @@ export class ActivitybarPart extends Part { colors: ActivitybarPart.COLORS, overflowActionSize: ActivitybarPart.ACTION_HEIGHT }); + const previousState = this.storageService.get(ActivitybarPart.PLACEHOLDER_VIEWLETS, StorageScope.GLOBAL, void 0); + this.placeholderComposites = previousState ? JSON.parse(previousState) : this.compositeBar.getCompositesFromStorage().map(id => ({ id, iconUrl: void 0 })); this.registerListeners(); this.updateCompositebar(); @@ -325,7 +326,6 @@ export class ActivitybarPart extends Part { public shutdown(): void { const state = this.viewletService.getViewlets().filter(viewlet => this.hasRegisteredViews(viewlet)).map(viewlet => ({ id: viewlet.id, iconUrl: viewlet.iconUrl })); this.storageService.store(ActivitybarPart.PLACEHOLDER_VIEWLETS, JSON.stringify(state), StorageScope.GLOBAL); - this.compositeBar.shutdown(); super.shutdown(); } @@ -355,7 +355,7 @@ class PlaceHolderViewletActivityAction extends ViewletActivityAction { super({ id, name: id, cssClass: `extensionViewlet-placeholder-${id.replace(/\./g, '-')}` }, viewletService, partService, telemetryService); // Generate Placeholder CSS to show the icon in the activity bar const iconClass = `.monaco-workbench > .activitybar .monaco-action-bar .action-label.${this.class}`; - createCSSRule(iconClass, `-webkit-mask: url('${iconUrl}') no-repeat 50% 50%`); + createCSSRule(iconClass, `-webkit-mask: url('${iconUrl || ''}') no-repeat 50% 50%`); this.enabled = false; } diff --git a/src/vs/workbench/browser/parts/compositebar/compositeBar.ts b/src/vs/workbench/browser/parts/compositebar/compositeBar.ts index af799c58c8fc6fa6560171b252f4e8dc6ec1818e..57176fe9b29b7d828a02a1cef8e77d0573e2f83e 100644 --- a/src/vs/workbench/browser/parts/compositebar/compositeBar.ts +++ b/src/vs/workbench/browser/parts/compositebar/compositeBar.ts @@ -63,6 +63,10 @@ export class CompositeBar extends Widget implements ICompositeBar { this.compositeSizeInBar = new Map(); } + public getCompositesFromStorage(): string[] { + return this.storedState.map(s => s.id); + } + public create(parent: HTMLElement): HTMLElement { const actionBarDiv = parent.appendChild($('.composite-bar')); this.compositeSwitcherBar = this._register(new ActionBar(actionBarDiv, { @@ -192,8 +196,6 @@ export class CompositeBar extends Widget implements ICompositeBar { public pin(compositeId: string, open?: boolean): void { if (this.model.setPinned(compositeId, true)) { this.updateCompositeSwitcher(); - // Persist - this.saveCompositeItems(); if (open) { this.options.openComposite(compositeId) @@ -206,8 +208,6 @@ export class CompositeBar extends Widget implements ICompositeBar { if (this.model.setPinned(compositeId, false)) { this.updateCompositeSwitcher(); - // Persist - this.saveCompositeItems(); const defaultCompositeId = this.options.getDefaultCompositeId(); @@ -250,11 +250,7 @@ export class CompositeBar extends Widget implements ICompositeBar { public move(compositeId: string, toCompositeId: string): void { if (this.model.move(compositeId, toCompositeId)) { // timeout helps to prevent artifacts from showing up - setTimeout(() => { - this.updateCompositeSwitcher(); - // Persist - this.saveCompositeItems(); - }, 0); + setTimeout(() => this.updateCompositeSwitcher(), 0); } } @@ -395,6 +391,9 @@ export class CompositeBar extends Widget implements ICompositeBar { this.compositeSwitcherBar.push(this.compositeOverflowAction, { label: false, icon: true }); } + + // Persist + this.saveCompositeItems(); } private getOverflowingComposites(): { id: string, name: string }[] { @@ -448,10 +447,6 @@ export class CompositeBar extends Widget implements ICompositeBar { this.storedState = this.model.toJSON(); this.storageService.store(this.options.storageId, JSON.stringify(this.storedState), StorageScope.GLOBAL); } - - public shutdown(): void { - this.saveCompositeItems(); - } } interface ISerializedCompositeBarItem { diff --git a/src/vs/workbench/browser/parts/editor/editorGroupsControl.ts b/src/vs/workbench/browser/parts/editor/editorGroupsControl.ts index 4947c7fa85a564b902fc8fd27b2cb67ab4c75865..eca86db064dfca2e18a5dc2694a24c4e88177cfa 100644 --- a/src/vs/workbench/browser/parts/editor/editorGroupsControl.ts +++ b/src/vs/workbench/browser/parts/editor/editorGroupsControl.ts @@ -1074,7 +1074,7 @@ export class EditorGroupsControl extends Themable implements IEditorGroupsContro this.centeredEditorLeftMarginRatio = 0.5; // Restore centered layout position and size - const centeredLayoutDataString = this.storageService.get(EditorGroupsControl.CENTERED_EDITOR_LAYOUT_DATA_STORAGE_KEY, StorageScope.GLOBAL); + const centeredLayoutDataString = this.storageService.get(EditorGroupsControl.CENTERED_EDITOR_LAYOUT_DATA_STORAGE_KEY, StorageScope.WORKSPACE); if (centeredLayoutDataString) { const centeredLayout = JSON.parse(centeredLayoutDataString); this.centeredEditorLeftMarginRatio = centeredLayout.leftMarginRatio; @@ -1985,7 +1985,7 @@ export class EditorGroupsControl extends Themable implements IEditorGroupsContro leftMarginRatio: this.centeredEditorLeftMarginRatio, size: this.centeredEditorSize }; - this.storageService.store(EditorGroupsControl.CENTERED_EDITOR_LAYOUT_DATA_STORAGE_KEY, JSON.stringify(data), StorageScope.GLOBAL); + this.storageService.store(EditorGroupsControl.CENTERED_EDITOR_LAYOUT_DATA_STORAGE_KEY, JSON.stringify(data), StorageScope.WORKSPACE); } public getVerticalSashTop(sash: Sash): number { @@ -2230,7 +2230,7 @@ export class EditorGroupsControl extends Themable implements IEditorGroupsContro if (layout) { this.layoutContainers(); } - this.storageService.remove(EditorGroupsControl.CENTERED_EDITOR_LAYOUT_DATA_STORAGE_KEY, StorageScope.GLOBAL); + this.storageService.remove(EditorGroupsControl.CENTERED_EDITOR_LAYOUT_DATA_STORAGE_KEY, StorageScope.WORKSPACE); } public getInstantiationService(position: Position): IInstantiationService { diff --git a/src/vs/workbench/browser/parts/panel/panelPart.ts b/src/vs/workbench/browser/parts/panel/panelPart.ts index 21552f02b5e57b3c267e96b04ee1ee2d1f6a27e2..c22eec7af839f8a9d87d87c7b165f5030218e279 100644 --- a/src/vs/workbench/browser/parts/panel/panelPart.ts +++ b/src/vs/workbench/browser/parts/panel/panelPart.ts @@ -237,11 +237,6 @@ export class PanelPart extends CompositePart implements IPanelService { return sizes; } - public shutdown(): void { - this.compositeBar.shutdown(); - super.shutdown(); - } - private layoutCompositeBar(): void { if (this.dimension) { let availableWidth = this.dimension.width - 40; // take padding into account diff --git a/src/vs/workbench/browser/parts/quickinput/quickInput.css b/src/vs/workbench/browser/parts/quickinput/quickInput.css index 1965f7a8913163107d8b664c46ed56ed3db18609..e1fb74ff1cc215fc6a46c2d970aa1393f85b2198 100644 --- a/src/vs/workbench/browser/parts/quickinput/quickInput.css +++ b/src/vs/workbench/browser/parts/quickinput/quickInput.css @@ -65,35 +65,35 @@ height: 2px; } -.quick-input-checkbox-list { +.quick-input-list { line-height: 22px; } -.quick-input-checkbox-list .monaco-list { +.quick-input-list .monaco-list { overflow: hidden; max-height: calc(20 * 22px); } -.quick-input-checkbox-list .quick-input-checkbox-list-entry { +.quick-input-list .quick-input-list-entry { overflow: hidden; display: flex; height: 100%; padding: 0 6px; } -.quick-input-checkbox-list .quick-input-checkbox-list-label { +.quick-input-list .quick-input-list-label { overflow: hidden; display: flex; height: 100%; flex: 1; } -.quick-input-checkbox-list .quick-input-checkbox-list-checkbox { +.quick-input-list .quick-input-list-checkbox { align-self: center; margin: 0; } -.quick-input-checkbox-list .quick-input-checkbox-list-rows { +.quick-input-list .quick-input-list-rows { overflow: hidden; text-overflow: ellipsis; display: flex; @@ -103,20 +103,20 @@ margin-left: 5px; } -.quick-input-widget[data-type=pickMany] .quick-input-checkbox-list .quick-input-checkbox-list-rows { +.quick-input-widget[data-type=pickMany] .quick-input-list .quick-input-list-rows { margin-left: 10px; } -.quick-input-checkbox-list .quick-input-checkbox-list-rows > .quick-input-checkbox-list-row { +.quick-input-list .quick-input-list-rows > .quick-input-list-row { display: flex; align-items: center; } -.quick-input-checkbox-list .quick-input-checkbox-list-rows .monaco-highlighted-label span { +.quick-input-list .quick-input-list-rows .monaco-highlighted-label span { opacity: 1; } -.quick-input-checkbox-list .quick-input-checkbox-list-label-meta { +.quick-input-list .quick-input-list-label-meta { opacity: 0.7; line-height: normal; } diff --git a/src/vs/workbench/browser/parts/quickinput/quickInput.ts b/src/vs/workbench/browser/parts/quickinput/quickInput.ts index 3d77ec3f87b468e160a93cc80441320970920b8d..83314d3a8ed9d8cccae2e0a5eb47567906341f33 100644 --- a/src/vs/workbench/browser/parts/quickinput/quickInput.ts +++ b/src/vs/workbench/browser/parts/quickinput/quickInput.ts @@ -7,17 +7,17 @@ import 'vs/css!./quickInput'; import { Component } from 'vs/workbench/common/component'; -import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; +import { IQuickInputService, IPickOpenEntry, IPickOptions, IInputOptions, IQuickNavigateConfiguration } from 'vs/platform/quickinput/common/quickInput'; import { IPartService } from 'vs/workbench/services/part/common/partService'; import * as dom from 'vs/base/browser/dom'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { contrastBorder, widgetShadow } from 'vs/platform/theme/common/colorRegistry'; import { SIDE_BAR_BACKGROUND, SIDE_BAR_FOREGROUND } from 'vs/workbench/common/theme'; -import { IQuickOpenService, IPickOpenEntry, IPickOptions, IInputOptions } from 'vs/platform/quickOpen/common/quickOpen'; +import { IQuickOpenService } from 'vs/platform/quickOpen/common/quickOpen'; import { TPromise } from 'vs/base/common/winjs.base'; import { CancellationToken } from 'vs/base/common/cancellation'; -import { QuickInputCheckboxList } from './quickInputCheckboxList'; +import { QuickInputList } from './quickInputList'; import { QuickInputBox } from './quickInputBox'; import { KeyCode } from 'vs/base/common/keyCodes'; import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; @@ -73,11 +73,12 @@ export interface TextInputParameters extends BaseInputParameters { } interface QuickInputUI { + container: HTMLElement; checkAll: HTMLInputElement; inputBox: QuickInputBox; count: CountBadge; message: HTMLElement; - checkboxList: QuickInputCheckboxList; + list: QuickInputList; close: (ok?: true | Thenable) => void; } @@ -89,53 +90,54 @@ interface InputController { } class PickOneController implements InputController { - public showUI = { inputBox: true, checkboxList: true }; + public showUI = { inputBox: true, list: true }; public result: TPromise; public ready: TPromise; public resolve: (ok?: true | Thenable) => void; public progress: (value: T) => void; private closed = false; + private quickNavigate = false; private disposables: IDisposable[] = []; - constructor(ui: QuickInputUI, parameters: PickOneParameters) { + constructor(private ui: QuickInputUI, parameters: PickOneParameters) { this.result = new TPromise((resolve, reject, progress) => { - this.resolve = ok => resolve(ok === true ? ui.checkboxList.getFocusedElements()[0] : ok); + this.resolve = ok => resolve(ok === true ? ui.list.getFocusedElements()[0] : ok); this.progress = progress; }); this.result.then(() => this.dispose()); ui.inputBox.value = ''; ui.inputBox.setPlaceholder(parameters.placeHolder || ''); - ui.checkboxList.matchOnDescription = parameters.matchOnDescription; - ui.checkboxList.matchOnDetail = parameters.matchOnDetail; - ui.checkboxList.setElements([]); + ui.list.matchOnDescription = parameters.matchOnDescription; + ui.list.matchOnDetail = parameters.matchOnDetail; + ui.list.setElements([]); this.ready = parameters.picks.then(elements => { if (this.closed) { return; } - ui.checkboxList.setElements(elements); - ui.checkboxList.filter(ui.inputBox.value); - ui.checkboxList.focus('First'); + ui.list.setElements(elements); + ui.list.filter(ui.inputBox.value); + ui.list.focus('First'); this.disposables.push( - ui.checkboxList.onSelectionChange(elements => { + ui.list.onSelectionChange(elements => { if (elements[0]) { ui.close(true); } }), ui.inputBox.onDidChange(value => { - ui.checkboxList.filter(value); - ui.checkboxList.focus('First'); + ui.list.filter(value); + ui.list.focus('First'); }), ui.inputBox.onKeyDown(event => { switch (event.keyCode) { case KeyCode.DownArrow: - ui.checkboxList.focus('Next'); + ui.list.focus('Next'); break; case KeyCode.UpArrow: - ui.checkboxList.focus('Previous'); + ui.list.focus('Previous'); break; } }) @@ -143,6 +145,53 @@ class PickOneController implements InputController }); } + configureQuickNavigate(quickNavigate: IQuickNavigateConfiguration) { + if (this.quickNavigate) { + return; + } + this.quickNavigate = true; + + this.disposables.push(dom.addDisposableListener(this.ui.container, dom.EventType.KEY_UP, (e: KeyboardEvent) => { + const keyboardEvent: StandardKeyboardEvent = new StandardKeyboardEvent(e as KeyboardEvent); + const keyCode = keyboardEvent.keyCode; + + // Select element when keys are pressed that signal it + const quickNavKeys = quickNavigate.keybindings; + const wasTriggerKeyPressed = keyCode === KeyCode.Enter || quickNavKeys.some(k => { + const [firstPart, chordPart] = k.getParts(); + if (chordPart) { + return false; + } + + if (firstPart.shiftKey && keyCode === KeyCode.Shift) { + if (keyboardEvent.ctrlKey || keyboardEvent.altKey || keyboardEvent.metaKey) { + return false; // this is an optimistic check for the shift key being used to navigate back in quick open + } + + return true; + } + + if (firstPart.altKey && keyCode === KeyCode.Alt) { + return true; + } + + if (firstPart.ctrlKey && keyCode === KeyCode.Ctrl) { + return true; + } + + if (firstPart.metaKey && keyCode === KeyCode.Meta) { + return true; + } + + return false; + }); + + if (wasTriggerKeyPressed) { + this.ui.close(true); + } + })); + } + private dispose() { this.closed = true; this.disposables = dispose(this.disposables); @@ -150,7 +199,7 @@ class PickOneController implements InputController } class PickManyController implements InputController { - public showUI = { checkAll: true, inputBox: true, count: true, ok: true, checkboxList: true }; + public showUI = { checkAll: true, inputBox: true, count: true, ok: true, list: true }; public result: TPromise; public ready: TPromise; public resolve: (ok?: true | Thenable) => void; @@ -160,42 +209,42 @@ class PickManyController implements InputController) { this.result = new TPromise((resolve, reject, progress) => { - this.resolve = ok => resolve(ok === true ? ui.checkboxList.getCheckedElements() : ok); + this.resolve = ok => resolve(ok === true ? ui.list.getCheckedElements() : ok); this.progress = progress; }); this.result.then(() => this.dispose()); ui.inputBox.value = ''; ui.inputBox.setPlaceholder(parameters.placeHolder || ''); - ui.checkboxList.matchOnDescription = parameters.matchOnDescription; - ui.checkboxList.matchOnDetail = parameters.matchOnDetail; - ui.checkboxList.setElements([]); - ui.checkAll.checked = ui.checkboxList.getAllVisibleChecked(); - ui.count.setCount(ui.checkboxList.getCheckedCount()); + ui.list.matchOnDescription = parameters.matchOnDescription; + ui.list.matchOnDetail = parameters.matchOnDetail; + ui.list.setElements([]); + ui.checkAll.checked = ui.list.getAllVisibleChecked(); + ui.count.setCount(ui.list.getCheckedCount()); this.ready = parameters.picks.then(elements => { if (this.closed) { return; } - ui.checkboxList.setElements(elements, true); - ui.checkboxList.filter(ui.inputBox.value); - ui.checkAll.checked = ui.checkboxList.getAllVisibleChecked(); - ui.count.setCount(ui.checkboxList.getCheckedCount()); + ui.list.setElements(elements, true); + ui.list.filter(ui.inputBox.value); + ui.checkAll.checked = ui.list.getAllVisibleChecked(); + ui.count.setCount(ui.list.getCheckedCount()); this.disposables.push( ui.inputBox.onDidChange(value => { - ui.checkboxList.filter(value); + ui.list.filter(value); }), ui.inputBox.onKeyDown(event => { switch (event.keyCode) { case KeyCode.DownArrow: - ui.checkboxList.focus('First'); - ui.checkboxList.domFocus(); + ui.list.focus('First'); + ui.list.domFocus(); break; case KeyCode.UpArrow: - ui.checkboxList.focus('Last'); - ui.checkboxList.domFocus(); + ui.list.focus('Last'); + ui.list.domFocus(); break; } }) @@ -301,7 +350,6 @@ export class QuickInputService extends Component implements IQuickInputService { private static readonly MAX_WIDTH = 600; // Max total width of quick open widget private layoutDimensions: dom.Dimension; - private container: HTMLElement; private filterContainer: HTMLElement; private countContainer: HTMLElement; private okContainer: HTMLElement; @@ -348,22 +396,22 @@ export class QuickInputService extends Component implements IQuickInputService { } private create() { - if (this.container) { + if (this.ui) { return; } const workbench = document.getElementById(this.partService.getWorkbenchElementId()); - this.container = dom.append(workbench, $('.quick-input-widget')); - this.container.tabIndex = -1; - this.container.style.display = 'none'; + const container = dom.append(workbench, $('.quick-input-widget')); + container.tabIndex = -1; + container.style.display = 'none'; - const headerContainer = dom.append(this.container, $('.quick-input-header')); + const headerContainer = dom.append(container, $('.quick-input-header')); const checkAll = dom.append(headerContainer, $('input.quick-input-check-all')); checkAll.type = 'checkbox'; this.toUnbind.push(dom.addStandardDisposableListener(checkAll, dom.EventType.CHANGE, e => { const checked = checkAll.checked; - checkboxList.setAllVisibleChecked(checked); + list.setAllVisibleChecked(checked); })); this.toUnbind.push(dom.addDisposableListener(checkAll, dom.EventType.CLICK, e => { if (e.x || e.y) { // Avoid 'click' triggered by 'space'... @@ -390,29 +438,29 @@ export class QuickInputService extends Component implements IQuickInputService { } })); - const message = dom.append(this.container, $('.quick-input-message')); + const message = dom.append(container, $('.quick-input-message')); - this.progressBar = new ProgressBar(this.container); + this.progressBar = new ProgressBar(container); dom.addClass(this.progressBar.getContainer(), 'quick-input-progress'); this.toUnbind.push(attachProgressBarStyler(this.progressBar, this.themeService)); - const checkboxList = this.instantiationService.createInstance(QuickInputCheckboxList, this.container); - this.toUnbind.push(checkboxList); - this.toUnbind.push(checkboxList.onAllVisibleCheckedChanged(checked => { + const list = this.instantiationService.createInstance(QuickInputList, container); + this.toUnbind.push(list); + this.toUnbind.push(list.onAllVisibleCheckedChanged(checked => { checkAll.checked = checked; })); - this.toUnbind.push(checkboxList.onCheckedCountChanged(c => { + this.toUnbind.push(list.onCheckedCountChanged(c => { count.setCount(c); })); - this.toUnbind.push(checkboxList.onLeave(() => { + this.toUnbind.push(list.onLeave(() => { // Defer to avoid the input field reacting to the triggering key. setTimeout(() => { inputBox.setFocus(); - checkboxList.clearFocus(); + list.clearFocus(); }, 0); })); this.toUnbind.push( - chain(checkboxList.onFocusChange) + chain(list.onFocusChange) .map(e => e[0]) .filter(e => !!e) .latch() @@ -424,13 +472,13 @@ export class QuickInputService extends Component implements IQuickInputService { }) ); - this.toUnbind.push(dom.addDisposableListener(this.container, 'focusout', (e: FocusEvent) => { - if (e.relatedTarget === this.container) { + this.toUnbind.push(dom.addDisposableListener(container, 'focusout', (e: FocusEvent) => { + if (e.relatedTarget === container) { (e.target).focus(); return; } for (let element = e.relatedTarget; element; element = element.parentElement) { - if (element === this.container) { + if (element === container) { return; } } @@ -438,7 +486,7 @@ export class QuickInputService extends Component implements IQuickInputService { this.close(undefined, true); } })); - this.toUnbind.push(dom.addDisposableListener(this.container, dom.EventType.KEY_DOWN, (e: KeyboardEvent) => { + this.toUnbind.push(dom.addDisposableListener(container, dom.EventType.KEY_DOWN, (e: KeyboardEvent) => { const event = new StandardKeyboardEvent(e); switch (event.keyCode) { case KeyCode.Enter: @@ -453,7 +501,7 @@ export class QuickInputService extends Component implements IQuickInputService { break; case KeyCode.Tab: if (!event.altKey && !event.ctrlKey && !event.metaKey) { - const inputs = [].slice.call(this.container.querySelectorAll('input')) + const inputs = [].slice.call(container.querySelectorAll('input')) .filter(input => input.style.display !== 'none'); if (event.shiftKey && event.target === inputs[0]) { dom.EventHelper.stop(e, true); @@ -469,7 +517,7 @@ export class QuickInputService extends Component implements IQuickInputService { this.toUnbind.push(this.quickOpenService.onShow(() => this.close())); - this.ui = { checkAll, inputBox, count, message, checkboxList, close: ok => this.close(ok) }; + this.ui = { container, checkAll, inputBox, count, message, list, close: ok => this.close(ok) }; this.updateStyles(); } @@ -483,7 +531,7 @@ export class QuickInputService extends Component implements IQuickInputService { const result = resolved .then(() => { this.inQuickOpen('quickInput', false); - this.container.style.display = 'none'; + this.ui.container.style.display = 'none'; if (!focusLost) { this.restoreFocus(); } @@ -493,7 +541,7 @@ export class QuickInputService extends Component implements IQuickInputService { } } this.inQuickOpen('quickInput', false); - this.container.style.display = 'none'; + this.ui.container.style.display = 'none'; if (!focusLost) { this.restoreFocus(); } @@ -540,7 +588,7 @@ export class QuickInputService extends Component implements IQuickInputService { this.controller.resolve(); } - this.container.setAttribute('data-type', parameters.type); + this.ui.container.setAttribute('data-type', parameters.type); this.ignoreFocusLost = parameters.ignoreFocusLost; @@ -554,12 +602,12 @@ export class QuickInputService extends Component implements IQuickInputService { this.countContainer.style.display = this.controller.showUI.count ? '' : 'none'; this.okContainer.style.display = this.controller.showUI.ok ? '' : 'none'; this.ui.message.style.display = this.controller.showUI.message ? '' : 'none'; - this.ui.checkboxList.display(this.controller.showUI.checkboxList); + this.ui.list.display(this.controller.showUI.list); - if (this.container.style.display === 'none') { + if (this.ui.container.style.display === 'none') { this.inQuickOpen('quickInput', true); } - this.container.style.display = ''; + this.ui.container.style.display = ''; this.updateLayout(); this.ui.inputBox.setFocus(); @@ -604,13 +652,16 @@ export class QuickInputService extends Component implements IQuickInputService { toggle() { if (this.isDisplayed() && this.controller instanceof PickManyController) { - this.ui.checkboxList.toggleCheckbox(); + this.ui.list.toggleCheckbox(); } } - navigate(next: boolean) { - if (this.isDisplayed() && this.ui.checkboxList.isDisplayed()) { - this.ui.checkboxList.focus(next ? 'Next' : 'Previous'); + navigate(next: boolean, quickNavigate?: IQuickNavigateConfiguration) { + if (this.isDisplayed() && this.ui.list.isDisplayed()) { + this.ui.list.focus(next ? 'Next' : 'Previous'); + if (quickNavigate && this.controller instanceof PickOneController) { + this.controller.configureQuickNavigate(quickNavigate); + } } } @@ -628,17 +679,17 @@ export class QuickInputService extends Component implements IQuickInputService { } private updateLayout() { - if (this.layoutDimensions && this.container) { + if (this.layoutDimensions && this.ui) { const titlebarOffset = this.partService.getTitleBarOffset(); - this.container.style.top = `${titlebarOffset}px`; + this.ui.container.style.top = `${titlebarOffset}px`; - const style = this.container.style; + const style = this.ui.container.style; const width = Math.min(this.layoutDimensions.width * 0.62 /* golden cut */, QuickInputService.MAX_WIDTH); style.width = width + 'px'; style.marginLeft = '-' + (width / 2) + 'px'; this.ui.inputBox.layout(); - this.ui.checkboxList.layout(); + this.ui.list.layout(); } } @@ -646,22 +697,21 @@ export class QuickInputService extends Component implements IQuickInputService { const theme = this.themeService.getTheme(); if (this.ui) { this.ui.inputBox.style(theme); - this.ui.checkboxList.style(theme); } - if (this.container) { + if (this.ui) { const sideBarBackground = theme.getColor(SIDE_BAR_BACKGROUND); - this.container.style.backgroundColor = sideBarBackground ? sideBarBackground.toString() : undefined; + this.ui.container.style.backgroundColor = sideBarBackground ? sideBarBackground.toString() : undefined; const sideBarForeground = theme.getColor(SIDE_BAR_FOREGROUND); - this.container.style.color = sideBarForeground ? sideBarForeground.toString() : undefined; + this.ui.container.style.color = sideBarForeground ? sideBarForeground.toString() : undefined; const contrastBorderColor = theme.getColor(contrastBorder); - this.container.style.border = contrastBorderColor ? `1px solid ${contrastBorderColor}` : undefined; + this.ui.container.style.border = contrastBorderColor ? `1px solid ${contrastBorderColor}` : undefined; const widgetShadowColor = theme.getColor(widgetShadow); - this.container.style.boxShadow = widgetShadowColor ? `0 5px 8px ${widgetShadowColor}` : undefined; + this.ui.container.style.boxShadow = widgetShadowColor ? `0 5px 8px ${widgetShadowColor}` : undefined; } } private isDisplayed() { - return this.container && this.container.style.display !== 'none'; + return this.ui && this.ui.container.style.display !== 'none'; } } diff --git a/src/vs/workbench/browser/parts/quickinput/quickInputCheckboxList.ts b/src/vs/workbench/browser/parts/quickinput/quickInputList.ts similarity index 79% rename from src/vs/workbench/browser/parts/quickinput/quickInputCheckboxList.ts rename to src/vs/workbench/browser/parts/quickinput/quickInputList.ts index f8a2f6774a60d73a7a86248abd20eeafdb7fbb76..ffc74cdf34ce10799c736623cfe9689d91d41e56 100644 --- a/src/vs/workbench/browser/parts/quickinput/quickInputCheckboxList.ts +++ b/src/vs/workbench/browser/parts/quickinput/quickInputList.ts @@ -11,7 +11,7 @@ import * as dom from 'vs/base/browser/dom'; import { dispose, IDisposable } from 'vs/base/common/lifecycle'; import { WorkbenchList } from 'vs/platform/list/browser/listService'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { IPickOpenEntry } from 'vs/platform/quickOpen/common/quickOpen'; +import { IPickOpenEntry } from 'vs/platform/quickinput/common/quickInput'; import { IMatch } from 'vs/base/common/filters'; import { matchesFuzzyOcticonAware, parseOcticons } from 'vs/base/common/octicon'; import { compareAnything } from 'vs/base/common/comparers'; @@ -25,24 +25,24 @@ import { memoize } from 'vs/base/common/decorators'; import { range } from 'vs/base/common/arrays'; import * as platform from 'vs/base/common/platform'; import { listFocusBackground } from 'vs/platform/theme/common/colorRegistry'; -import { ITheme } from 'vs/platform/theme/common/themeService'; +import { registerThemingParticipant } from 'vs/platform/theme/common/themeService'; const $ = dom.$; -interface ICheckableElement { +interface IListElement { index: number; item: IPickOpenEntry; - checked: boolean; + checked?: boolean; } -class CheckableElement implements ICheckableElement { +class ListElement implements IListElement { index: number; item: IPickOpenEntry; shouldAlwaysShow = false; hidden = false; private _onChecked = new Emitter(); onChecked = this._onChecked.event; - _checked: boolean; + _checked?: boolean; get checked() { return this._checked; } @@ -56,59 +56,59 @@ class CheckableElement implements ICheckableElement { descriptionHighlights?: IMatch[]; detailHighlights?: IMatch[]; - constructor(init: ICheckableElement) { + constructor(init: IListElement) { assign(this, init); } } -interface ICheckableElementTemplateData { +interface IListElementTemplateData { checkbox: HTMLInputElement; label: IconLabel; detail: HighlightedLabel; - element: CheckableElement; + element: ListElement; toDisposeElement: IDisposable[]; toDisposeTemplate: IDisposable[]; } -class CheckableElementRenderer implements IRenderer { +class ListElementRenderer implements IRenderer { - static readonly ID = 'checkableelement'; + static readonly ID = 'listelement'; get templateId() { - return CheckableElementRenderer.ID; + return ListElementRenderer.ID; } - renderTemplate(container: HTMLElement): ICheckableElementTemplateData { - const data: ICheckableElementTemplateData = Object.create(null); + renderTemplate(container: HTMLElement): IListElementTemplateData { + const data: IListElementTemplateData = Object.create(null); data.toDisposeElement = []; data.toDisposeTemplate = []; - const entry = dom.append(container, $('.quick-input-checkbox-list-entry')); + const entry = dom.append(container, $('.quick-input-list-entry')); // Checkbox - const label = dom.append(entry, $('label.quick-input-checkbox-list-label')); - data.checkbox = dom.append(label, $('input.quick-input-checkbox-list-checkbox')); + const label = dom.append(entry, $('label.quick-input-list-label')); + data.checkbox = dom.append(label, $('input.quick-input-list-checkbox')); data.checkbox.type = 'checkbox'; data.toDisposeTemplate.push(dom.addStandardDisposableListener(data.checkbox, dom.EventType.CHANGE, e => { data.element.checked = data.checkbox.checked; })); // Rows - const rows = dom.append(label, $('.quick-input-checkbox-list-rows')); - const row1 = dom.append(rows, $('.quick-input-checkbox-list-row')); - const row2 = dom.append(rows, $('.quick-input-checkbox-list-row')); + const rows = dom.append(label, $('.quick-input-list-rows')); + const row1 = dom.append(rows, $('.quick-input-list-row')); + const row2 = dom.append(rows, $('.quick-input-list-row')); // Label data.label = new IconLabel(row1, { supportHighlights: true, supportDescriptionHighlights: true }); // Detail - const detailContainer = dom.append(row2, $('.quick-input-checkbox-list-label-meta')); + const detailContainer = dom.append(row2, $('.quick-input-list-label-meta')); data.detail = new HighlightedLabel(detailContainer); return data; } - renderElement(element: CheckableElement, index: number, data: ICheckableElementTemplateData): void { + renderElement(element: ListElement, index: number, data: IListElementTemplateData): void { data.toDisposeElement = dispose(data.toDisposeElement); data.element = element; if (element.checked === undefined) { @@ -116,8 +116,8 @@ class CheckableElementRenderer implements IRenderer data.checkbox.checked = checked)); } - data.toDisposeElement.push(element.onChecked(checked => data.checkbox.checked = checked)); const { labelHighlights, descriptionHighlights, detailHighlights } = element; @@ -132,28 +132,28 @@ class CheckableElementRenderer implements IRenderer { +class ListElementDelegate implements IDelegate { - getHeight(element: CheckableElement): number { + getHeight(element: ListElement): number { return element.item.detail ? 44 : 22; } - getTemplateId(element: CheckableElement): string { - return CheckableElementRenderer.ID; + getTemplateId(element: ListElement): string { + return ListElementRenderer.ID; } } -export class QuickInputCheckboxList { +export class QuickInputList { private container: HTMLElement; - private list: WorkbenchList; - private elements: CheckableElement[] = []; + private list: WorkbenchList; + private elements: ListElement[] = []; matchOnDescription = false; matchOnDetail = false; private _onAllVisibleCheckedChanged = new Emitter(); // TODO: Debounce @@ -170,12 +170,12 @@ export class QuickInputCheckboxList { private parent: HTMLElement, @IInstantiationService private instantiationService: IInstantiationService ) { - this.container = dom.append(this.parent, $('.quick-input-checkbox-list')); - const delegate = new CheckableElementDelegate(); - this.list = this.instantiationService.createInstance(WorkbenchList, this.container, delegate, [new CheckableElementRenderer()], { + this.container = dom.append(this.parent, $('.quick-input-list')); + const delegate = new ListElementDelegate(); + this.list = this.instantiationService.createInstance(WorkbenchList, this.container, delegate, [new ListElementRenderer()], { identityProvider: element => element.label, multipleSelectionSupport: false - }) as WorkbenchList; + }) as WorkbenchList; this.disposables.push(this.list); this.disposables.push(this.list.onKeyDown(e => { const event = new StandardKeyboardEvent(e); @@ -228,7 +228,7 @@ export class QuickInputCheckboxList { return this.allVisibleChecked(this.elements, false); } - private allVisibleChecked(elements: CheckableElement[], whenNoneVisible = true) { + private allVisibleChecked(elements: ListElement[], whenNoneVisible = true) { for (let i = 0, n = elements.length; i < n; i++) { const element = elements[i]; if (!element.hidden) { @@ -269,12 +269,14 @@ export class QuickInputCheckboxList { setElements(elements: IPickOpenEntry[], canCheck = false): void { this.elementDisposables = dispose(this.elementDisposables); - this.elements = elements.map((item, index) => new CheckableElement({ + this.elements = elements.map((item, index) => new ListElement({ index, item, checked: canCheck ? !!item.picked : undefined })); - this.elementDisposables.push(...this.elements.map(element => element.onChecked(() => this.fireCheckedEvents()))); + if (canCheck) { + this.elementDisposables.push(...this.elements.map(element => element.onChecked(() => this.fireCheckedEvents()))); + } this.list.splice(0, this.list.length, this.elements); this.list.setFocus([]); } @@ -381,12 +383,6 @@ export class QuickInputCheckboxList { } } - style(theme: ITheme) { - this.list.style({ - listInactiveFocusBackground: theme.getColor(listFocusBackground), - }); - } - display(display: boolean) { this.container.style.display = display ? '' : 'none'; } @@ -408,7 +404,7 @@ export class QuickInputCheckboxList { } } -function compareEntries(elementA: CheckableElement, elementB: CheckableElement, lookFor: string): number { +function compareEntries(elementA: ListElement, elementB: ListElement, lookFor: string): number { const labelHighlightsA = elementA.labelHighlights || []; const labelHighlightsB = elementB.labelHighlights || []; @@ -421,4 +417,13 @@ function compareEntries(elementA: CheckableElement, elementB: CheckableElement, } return compareAnything(elementA.item.label, elementB.item.label, lookFor); -} \ No newline at end of file +} + +registerThemingParticipant((theme, collector) => { + // Override inactive focus background with active focus background for single-pick case. + const listInactiveFocusBackground = theme.getColor(listFocusBackground); + if (listInactiveFocusBackground) { + collector.addRule(`.quick-input-list .monaco-list .monaco-list-row.focused { background-color: ${listInactiveFocusBackground}; }`); + collector.addRule(`.quick-input-list .monaco-list .monaco-list-row.focused:hover { background-color: ${listInactiveFocusBackground}; }`); + } +}); diff --git a/src/vs/workbench/browser/parts/quickopen/quickopen.ts b/src/vs/workbench/browser/parts/quickopen/quickopen.ts index 2e0b2dc8988c5c22edb21c871fe4f74dc8edc592..a13daae1be5558f2d29df29fd8aeef203b7d20e5 100644 --- a/src/vs/workbench/browser/parts/quickopen/quickopen.ts +++ b/src/vs/workbench/browser/parts/quickopen/quickopen.ts @@ -58,7 +58,7 @@ export class BaseQuickOpenNavigateAction extends Action { const quickNavigate = this.quickNavigate ? { keybindings: keys } : void 0; this.quickOpenService.navigate(this.next, quickNavigate); - this.quickInputService.navigate(this.next); + this.quickInputService.navigate(this.next, quickNavigate); return TPromise.as(true); } @@ -74,7 +74,7 @@ export function getQuickNavigateHandler(id: string, next?: boolean): ICommandHan const quickNavigate = { keybindings: keys }; quickOpenService.navigate(next, quickNavigate); - quickInputService.navigate(next); + quickInputService.navigate(next, quickNavigate); }; } diff --git a/src/vs/workbench/electron-browser/workbench.ts b/src/vs/workbench/electron-browser/workbench.ts index 700f322ad420946059106379d479faee9b7aa76b..b3b532dd3b7fcfba09d5ac9fa061e580e9c4467f 100644 --- a/src/vs/workbench/electron-browser/workbench.ts +++ b/src/vs/workbench/electron-browser/workbench.ts @@ -677,7 +677,7 @@ export class Workbench extends Disposable implements IPartService { } // Restore Forced Editor Center Mode - if (this.storageService.getBoolean(Workbench.centeredEditorLayoutActiveStorageKey, StorageScope.GLOBAL, false)) { + if (this.storageService.getBoolean(Workbench.centeredEditorLayoutActiveStorageKey, StorageScope.WORKSPACE, false)) { this.centeredEditorLayoutActive = true; } @@ -1244,7 +1244,7 @@ export class Workbench extends Disposable implements IPartService { // - IEditorInput.supportsCenteredEditorLayout() no longer supported centerEditorLayout(active: boolean, skipLayout?: boolean): void { this.centeredEditorLayoutActive = active; - this.storageService.store(Workbench.centeredEditorLayoutActiveStorageKey, this.centeredEditorLayoutActive, StorageScope.GLOBAL); + this.storageService.store(Workbench.centeredEditorLayoutActiveStorageKey, this.centeredEditorLayoutActive, StorageScope.WORKSPACE); // Enter Centered Editor Layout if (active) { diff --git a/src/vs/workbench/parts/debug/browser/baseDebugView.ts b/src/vs/workbench/parts/debug/browser/baseDebugView.ts index a15f7d2bb52dffd702d8bf7b960c62fc10c3fd35..c5d6c8985354727bcc3771ff9d9d593f29314ca1 100644 --- a/src/vs/workbench/parts/debug/browser/baseDebugView.ts +++ b/src/vs/workbench/parts/debug/browser/baseDebugView.ts @@ -66,6 +66,9 @@ export function renderExpressionValue(expressionOrValue: IExpression | string, c if (value !== Expression.DEFAULT_VALUE) { dom.addClass(container, 'error'); } + } else if (options.showChanged && (expressionOrValue).valueChanged && value !== Expression.DEFAULT_VALUE) { + // value changed color has priority over other colors. + container.className = 'value changed'; } if (options.colorize && typeof expressionOrValue !== 'string') { @@ -80,11 +83,6 @@ export function renderExpressionValue(expressionOrValue: IExpression | string, c } } - if (options.showChanged && (expressionOrValue).valueChanged && value !== Expression.DEFAULT_VALUE) { - // value changed color has priority over other colors. - container.className = 'value changed'; - } - if (options.maxValueLength && value.length > options.maxValueLength) { value = value.substr(0, options.maxValueLength) + '...'; } @@ -106,7 +104,7 @@ export function renderVariable(variable: Variable, data: IVariableTemplateData, } if (variable.value) { - data.name.textContent += variable.name ? ':' : ''; + data.name.textContent += (typeof variable.name === 'string') ? ':' : ''; renderExpressionValue(variable, data.value, { showChanged, maxValueLength: MAX_VALUE_RENDER_LENGTH_IN_VIEWLET, diff --git a/src/vs/workbench/parts/extensions/node/extensionsWorkbenchService.ts b/src/vs/workbench/parts/extensions/node/extensionsWorkbenchService.ts index 89fc28f9bf0b228789bd045054a06eb803c5c4ab..bc61f8e7578a7e6df0c0dc5c6f76d64abe1d0fd5 100644 --- a/src/vs/workbench/parts/extensions/node/extensionsWorkbenchService.ts +++ b/src/vs/workbench/parts/extensions/node/extensionsWorkbenchService.ts @@ -799,18 +799,17 @@ export class ExtensionsWorkbenchService implements IExtensionsWorkbenchService, const extension: Extension = installingExtension ? installingExtension : zipPath ? new Extension(this.galleryService, this.stateProvider, null, null, this.telemetryService) : null; if (extension) { this.installing = installingExtension ? this.installing.filter(e => e !== installingExtension) : this.installing; - + const installed = this.installed.filter(e => e.id === extension.id)[0]; if (!error) { extension.local = local; - const installed = this.installed.filter(e => e.id === extension.id)[0]; if (installed) { installed.local = local; } else { this.installed.push(extension); } } - if (extension.gallery) { - // Report telemetry only for gallery extensions + if (extension.gallery && !installed) { + // Report recommendation telemetry only for gallery extensions that are first time installs this.reportExtensionRecommendationsTelemetry(installingExtension); } } diff --git a/src/vs/workbench/parts/files/common/explorerModel.ts b/src/vs/workbench/parts/files/common/explorerModel.ts index bc440771caca974ad9ab6607445ef5c7817d9c26..391611ce204827816427b485ef9b6ba2659737c6 100644 --- a/src/vs/workbench/parts/files/common/explorerModel.ts +++ b/src/vs/workbench/parts/files/common/explorerModel.ts @@ -126,7 +126,7 @@ export class ExplorerItem { } public get isRoot(): boolean { - return this.resource.toString() === this.root.resource.toString(); + return this === this.root; } public static create(raw: IFileStat, root: ExplorerItem, resolveTo?: URI[]): ExplorerItem { diff --git a/src/vs/workbench/parts/files/electron-browser/fileActions.ts b/src/vs/workbench/parts/files/electron-browser/fileActions.ts index 76a6aacb07fe8eac96a9b69e5ac0408b37f64401..1195fcf4de41c9e8629cf7a76f6d88f5e9610645 100644 --- a/src/vs/workbench/parts/files/electron-browser/fileActions.ts +++ b/src/vs/workbench/parts/files/electron-browser/fileActions.ts @@ -1492,7 +1492,6 @@ export function getWellFormedFileName(filename: string): string { // Remove trailing dots, slashes, and spaces filename = strings.rtrim(filename, '.'); - filename = strings.rtrim(filename, ' '); filename = strings.rtrim(filename, '/'); filename = strings.rtrim(filename, '\\'); diff --git a/src/vs/workbench/parts/output/common/outputLinkComputer.ts b/src/vs/workbench/parts/output/common/outputLinkComputer.ts index 6e5d56cac1457fc7c2aaa8871e708fe1c1debf6b..ad1811607ce5b42196a5488d56c45c5c84acca94 100644 --- a/src/vs/workbench/parts/output/common/outputLinkComputer.ts +++ b/src/vs/workbench/parts/output/common/outputLinkComputer.ts @@ -23,11 +23,11 @@ export interface IResourceCreator { export class OutputLinkComputer { private ctx: IWorkerContext; - private patterns: Map; + private patterns: Map; constructor(ctx: IWorkerContext, createData: ICreateData) { this.ctx = ctx; - this.patterns = new Map(); + this.patterns = new Map(); this.computePatterns(createData); } @@ -40,7 +40,7 @@ export class OutputLinkComputer { const workspaceFolders = createData.workspaceFolders.map(r => URI.parse(r)); workspaceFolders.forEach(workspaceFolder => { const patterns = OutputLinkComputer.createPatterns(workspaceFolder); - this.patterns.set(workspaceFolder.fsPath, patterns); + this.patterns.set(workspaceFolder, patterns); }); } @@ -66,11 +66,11 @@ export class OutputLinkComputer { const lines = model.getValue().split(/\r\n|\r|\n/); // For each workspace root patterns - this.patterns.forEach((folderPatterns, folderPath) => { + this.patterns.forEach((folderPatterns, folderUri) => { const resourceCreator: IResourceCreator = { toResource: (folderRelativePath: string): URI => { if (typeof folderRelativePath === 'string') { - return URI.file(paths.join(folderPath, folderRelativePath)); + return folderUri.with({ path: paths.join(folderUri.path, folderRelativePath) }); } return null; @@ -88,9 +88,10 @@ 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([ - paths.normalize(workspaceFolder.fsPath, true), - paths.normalize(workspaceFolder.fsPath, false) + paths.normalize(workspaceFolderPath, true), + paths.normalize(workspaceFolderPath, false) ]); workspaceFolderVariants.forEach(workspaceFolderVariant => { @@ -181,7 +182,3 @@ export class OutputLinkComputer { return links; } } - -export function create(ctx: IWorkerContext, createData: ICreateData): OutputLinkComputer { - return new OutputLinkComputer(ctx, createData); -} diff --git a/src/vs/workbench/parts/preferences/browser/media/settingsEditor2.css b/src/vs/workbench/parts/preferences/browser/media/settingsEditor2.css index 3142accd6e845d2c78b56fd826070906f23a48bd..8187670b8b75fded62d477d3d44cb9286db00257 100644 --- a/src/vs/workbench/parts/preferences/browser/media/settingsEditor2.css +++ b/src/vs/workbench/parts/preferences/browser/media/settingsEditor2.css @@ -237,7 +237,7 @@ - Make up for that space with a negative margin on the settings-body This is risky, consider a different approach - */ +*/ .settings-editor .settings-list-container .monaco-list-row { overflow: visible; } @@ -251,6 +251,38 @@ margin-left: -15px; } -/* .settings-editor .settings-list-container .monaco-scrollable-element > .shadow.top { - width: calc(100% - 3px); -} */ \ No newline at end of file +.settings-editor > .settings-body > .settings-list-container .settings-group-title-expanded, +.settings-editor > .settings-body > .settings-list-container .settings-group-title-collapsed { + cursor: pointer; +} + +.vs-dark .settings-editor > .settings-body > .settings-list-container .settings-group-title-collapsed::before { + background-image: url(collapsed-dark.svg); +} + +.settings-editor > .settings-body > .settings-list-container .settings-group-title-collapsed::before { + display: inline-block; + background-image: url(collapsed.svg); +} + +.vs-dark .settings-editor > .settings-body > .settings-list-container .settings-group-title-expanded::before { + background-image: url(expanded-dark.svg); +} + +.settings-editor > .settings-body > .settings-list-container .settings-group-title-expanded::before { + display: inline-block; + background-image: url(expanded.svg); +} + +.settings-editor > .settings-body > .settings-list-container .settings-group-title-collapsed::before, +.settings-editor > .settings-body > .settings-list-container .settings-group-title-expanded::before { + background-size: 16px; + background-position: 50% 50%; + background-repeat: no-repeat; + width: 16px; + height: 22px; + position: absolute; + content: ' '; + left: -19px; + top: 14px; +} \ No newline at end of file diff --git a/src/vs/workbench/parts/preferences/browser/settingsEditor2.ts b/src/vs/workbench/parts/preferences/browser/settingsEditor2.ts index 82051ef58ca88e3b0916999b12f700fbbe8ee9fe..f114fcefdeda5b4c7a22e015b9e24b907bcf2173 100644 --- a/src/vs/workbench/parts/preferences/browser/settingsEditor2.ts +++ b/src/vs/workbench/parts/preferences/browser/settingsEditor2.ts @@ -61,8 +61,15 @@ interface ISettingItemEntry extends IListEntry { enum?: string[]; } +enum ExpandState { + Expanded, + Collapsed, + NA +} + interface IGroupTitleEntry extends IListEntry { title: string; + expandState: ExpandState; } interface IButtonRowEntry extends IListEntry { @@ -113,6 +120,8 @@ export class SettingsEditor2 extends BaseEditor { private searchResultModel: SearchResultModel; private pendingSettingModifiedReport: { key: string, value: any }; + private groupExpanded = new Map(); + constructor( @ITelemetryService telemetryService: ITelemetryService, @IConfigurationService private configurationService: IConfigurationService, @@ -250,11 +259,18 @@ export class SettingsEditor2 extends BaseEditor { const buttonItemRenderer = new ButtonRowRenderer(); this._register(buttonItemRenderer.onDidClick(e => this.onShowAllSettingsClicked())); + const groupTitleRenderer = new GroupTitleRenderer(); + this._register(groupTitleRenderer.onDidClickGroup(e => { + const isExpanded = !!this.groupExpanded.get(e); + this.groupExpanded.set(e, !isExpanded); + this.renderEntries(); + })); + this.settingsList = this._register(this.instantiationService.createInstance( WorkbenchList, this.settingsListContainer, new SettingItemDelegate(), - [settingItemRenderer, new GroupTitleRenderer(), buttonItemRenderer], + [settingItemRenderer, groupTitleRenderer, buttonItemRenderer], { identityProvider: e => e.id, ariaLabel: localize('settingsListLabel', "Settings"), @@ -530,21 +546,30 @@ export class SettingsEditor2 extends BaseEditor { } const group = this.defaultSettingsEditorModel.settingsGroups[groupIdx]; + const isExpanded = groupIdx === 0 || this.groupExpanded.get(group.id); + const groupEntries = []; - for (const section of group.sections) { - for (const setting of section.settings) { - const entry = this.settingToEntry(setting); - if (!this.showConfiguredSettingsOnly || entry.isConfigured) { - groupEntries.push(entry); + if (isExpanded) { + for (const section of group.sections) { + for (const setting of section.settings) { + const entry = this.settingToEntry(setting); + if (!this.showConfiguredSettingsOnly || entry.isConfigured) { + groupEntries.push(entry); + } } } } - if (groupEntries.length) { + if (!isExpanded || groupEntries.length) { + const expandState = groupIdx === 0 ? ExpandState.NA : + isExpanded ? ExpandState.Expanded : + ExpandState.Collapsed; + entries.push({ id: group.id, templateId: SETTINGS_GROUP_ENTRY_TEMPLATE_ID, - title: group.title + title: group.title, + expandState }); entries.push(...groupEntries); @@ -623,9 +648,12 @@ class SettingItemDelegate implements IDelegate { } } -interface ISettingItemTemplate { - parent: HTMLElement; +interface IDisposableTemplate { toDispose: IDisposable[]; +} + +interface ISettingItemTemplate extends IDisposableTemplate { + parent: HTMLElement; containerElement: HTMLElement; categoryElement: HTMLElement; @@ -635,14 +663,14 @@ interface ISettingItemTemplate { overridesElement: HTMLElement; } -interface IGroupTitleTemplate { +interface IGroupTitleTemplate extends IDisposableTemplate { + context?: IGroupTitleEntry; parent: HTMLElement; labelElement: HTMLElement; } -interface IButtonRowTemplate { +interface IButtonRowTemplate extends IDisposableTemplate { parent: HTMLElement; - toDispose: IDisposable[]; button: Button; entry?: IButtonRowEntry; @@ -701,18 +729,25 @@ class SettingItemRenderer implements IRenderer { class GroupTitleRenderer implements IRenderer { + private static readonly EXPANDED_CLASS = 'settings-group-title-expanded'; + private static readonly COLLAPSED_CLASS = 'settings-group-title-collapsed'; + + private readonly _onDidClickGroup: Emitter = new Emitter(); + public readonly onDidClickGroup: Event = this._onDidClickGroup.event; + get templateId(): string { return SETTINGS_GROUP_ENTRY_TEMPLATE_ID; } renderTemplate(parent: HTMLElement): IGroupTitleTemplate { DOM.addClass(parent, 'group-title'); - const labelElement = DOM.append(parent, $('h2.group-title-label')); - return { + const labelElement = DOM.append(parent, $('h2.settings-group-title-label')); + + const toDispose = []; + const template: IGroupTitleTemplate = { parent: parent, - labelElement + labelElement, + toDispose }; + + toDispose.push(DOM.addDisposableListener(labelElement, 'click', () => { + if (template.context) { + this._onDidClickGroup.fire(template.context.id); + } + })); + + return template; } renderElement(entry: IGroupTitleEntry, index: number, template: IGroupTitleTemplate): void { + template.context = entry; template.labelElement.textContent = entry.title; + + template.labelElement.classList.remove(GroupTitleRenderer.EXPANDED_CLASS); + template.labelElement.classList.remove(GroupTitleRenderer.COLLAPSED_CLASS); + + if (entry.expandState === ExpandState.Expanded) { + template.labelElement.classList.add(GroupTitleRenderer.EXPANDED_CLASS); + } else if (entry.expandState === ExpandState.Collapsed) { + template.labelElement.classList.add(GroupTitleRenderer.COLLAPSED_CLASS); + } } disposeTemplate(template: IGroupTitleTemplate): void { + dispose(template.toDispose); } } diff --git a/src/vs/workbench/parts/search/browser/searchActions.ts b/src/vs/workbench/parts/search/browser/searchActions.ts index 17a6209022f8d36e3273f06dfaee11ada6afea23..30ad61a997aa397e70db8f486283275ca7db71c4 100644 --- a/src/vs/workbench/parts/search/browser/searchActions.ts +++ b/src/vs/workbench/parts/search/browser/searchActions.ts @@ -209,6 +209,47 @@ export class ShowPreviousSearchTermAction extends Action { return TPromise.as(null); } } +export class ShowNextReplaceTermAction extends Action { + + public static readonly ID = 'search.replaceHistory.showNext'; + public static readonly LABEL = nls.localize('nextReplaceTerm', "Show Next Search Replace Term"); + + constructor(id: string, label: string, + @IViewletService private viewletService: IViewletService, + @IContextKeyService private contextKeyService: IContextKeyService, + @IPanelService private panelService: IPanelService + ) { + super(id, label); + this.enabled = this.contextKeyService.contextMatchesRules(Constants.SearchViewVisibleKey); + } + + public run(): TPromise { + const searchView = getSearchView(this.viewletService, this.panelService); + searchView.searchAndReplaceWidget.showNextReplaceTerm(); + return TPromise.as(null); + } +} + +export class ShowPreviousReplaceTermAction extends Action { + + public static readonly ID = 'search.replaceHistory.showPrevious'; + public static readonly LABEL = nls.localize('previousReplaceTerm', "Show Previous Search Replace Term"); + + constructor(id: string, label: string, + @IViewletService private viewletService: IViewletService, + @IContextKeyService private contextKeyService: IContextKeyService, + @IPanelService private panelService: IPanelService + ) { + super(id, label); + this.enabled = this.contextKeyService.contextMatchesRules(Constants.SearchViewVisibleKey); + } + + public run(): TPromise { + const searchView = getSearchView(this.viewletService, this.panelService); + searchView.searchAndReplaceWidget.showPreviousReplaceTerm(); + return TPromise.as(null); + } +} export class FocusNextInputAction extends Action { diff --git a/src/vs/workbench/parts/search/browser/searchView.ts b/src/vs/workbench/parts/search/browser/searchView.ts index 9de89c56f91d5a22e50839eea2dfb87695faeb18..5c763261753288163c317926394fbb3f92b87ab0 100644 --- a/src/vs/workbench/parts/search/browser/searchView.ts +++ b/src/vs/workbench/parts/search/browser/searchView.ts @@ -331,13 +331,15 @@ export class SearchView extends Viewlet implements IViewlet, IPanel { let isWholeWords = this.viewletSettings['query.wholeWords'] === true; let isCaseSensitive = this.viewletSettings['query.caseSensitive'] === true; let searchHistory = this.viewletSettings['query.searchHistory'] || []; + let replaceHistory = this.viewletSettings['query.replaceHistory'] || []; this.searchWidget = this.instantiationService.createInstance(SearchWidget, builder, { value: contentPattern, isRegex: isRegex, isCaseSensitive: isCaseSensitive, isWholeWords: isWholeWords, - history: searchHistory, + searchHistory: searchHistory, + replaceHistory: replaceHistory, historyLimit: SearchView.MAX_HISTORY_ITEMS }); @@ -1519,13 +1521,15 @@ export class SearchView extends Viewlet implements IViewlet, IPanel { const patternExcludes = this.inputPatternExcludes.getValue().trim(); const patternIncludes = this.inputPatternIncludes.getValue().trim(); const useExcludesAndIgnoreFiles = this.inputPatternExcludes.useExcludesAndIgnoreFiles(); - const searchHistory = this.searchWidget.getHistory(); + const searchHistory = this.searchWidget.getSearchHistory(); + const replaceHistory = this.searchWidget.getReplaceHistory(); const patternExcludesHistory = this.inputPatternExcludes.getHistory(); const patternIncludesHistory = this.inputPatternIncludes.getHistory(); // store memento this.viewletSettings['query.contentPattern'] = contentPattern; this.viewletSettings['query.searchHistory'] = searchHistory; + this.viewletSettings['query.replaceHistory'] = replaceHistory; this.viewletSettings['query.regex'] = isRegex; this.viewletSettings['query.wholeWords'] = isWholeWords; this.viewletSettings['query.caseSensitive'] = isCaseSensitive; diff --git a/src/vs/workbench/parts/search/browser/searchWidget.ts b/src/vs/workbench/parts/search/browser/searchWidget.ts index 0b79c602d5bc1730d36b7ad4625d4dc9d6c31cca..c2ae258908a1d7cdbfc908365509bba80b3d3dda 100644 --- a/src/vs/workbench/parts/search/browser/searchWidget.ts +++ b/src/vs/workbench/parts/search/browser/searchWidget.ts @@ -38,8 +38,9 @@ export interface ISearchWidgetOptions { isRegex?: boolean; isCaseSensitive?: boolean; isWholeWords?: boolean; - history?: string[]; + searchHistory?: string[]; historyLimit?: number; + replaceHistory?: string[]; } class ReplaceAllAction extends Action { @@ -96,6 +97,7 @@ export class SearchWidget extends Widget { private replaceActionBar: ActionBar; private searchHistory: HistoryNavigator; + private replaceHistory: HistoryNavigator; private ignoreGlobalFindBufferOnNextFocus = false; private previousGlobalFindBufferValue: string; @@ -128,7 +130,8 @@ export class SearchWidget extends Widget { @IConfigurationService private configurationService: IConfigurationService ) { super(); - this.searchHistory = new HistoryNavigator(options.history, options.historyLimit); + this.searchHistory = new HistoryNavigator(options.searchHistory, options.historyLimit); + this.replaceHistory = new HistoryNavigator(options.replaceHistory, options.historyLimit); this.replaceActive = Constants.ReplaceActiveKey.bindTo(this.keyBindingService); this.searchInputBoxFocused = Constants.SearchInputBoxFocusedKey.bindTo(this.keyBindingService); this.replaceInputBoxFocused = Constants.ReplaceInputBoxFocusedKey.bindTo(this.keyBindingService); @@ -176,10 +179,14 @@ export class SearchWidget extends Widget { } } - public getHistory(): string[] { + public getSearchHistory(): string[] { return this.searchHistory.getHistory(); } + public getReplaceHistory(): string[] { + return this.replaceHistory.getHistory(); + } + public clearHistory(): void { this.searchHistory.clear(); } @@ -204,6 +211,26 @@ export class SearchWidget extends Widget { } } + public showNextReplaceTerm() { + let next = this.replaceHistory.next(); + if (next) { + this.replaceInput.value = next; + } + } + + public showPreviousReplaceTerm() { + let previous; + if (this.replaceInput.value.length === 0) { + previous = this.replaceHistory.current(); + } else { + this.replaceHistory.addIfNotPresent(this.replaceInput.value); + previous = this.replaceHistory.previous(); + } + if (previous) { + this.replaceInput.value = previous; + } + } + public searchInputHasFocus(): boolean { return this.searchInputBoxFocused.get(); } @@ -256,6 +283,12 @@ export class SearchWidget extends Widget { this.searchHistory.add(this.searchInput.getValue()); })); + this._register(this.onReplaceValueChanged(() => { + if ((this.replaceHistory.current() !== this.replaceInput.value) && (this.replaceInput.value)) { + this.replaceHistory.add(this.replaceInput.value); + } + })); + this.searchInputFocusTracker = this._register(dom.trackFocus(this.searchInput.inputBox.inputElement)); this._register(this.searchInputFocusTracker.onDidFocus(() => { this.searchInputBoxFocused.set(true); diff --git a/src/vs/workbench/parts/search/electron-browser/search.contribution.ts b/src/vs/workbench/parts/search/electron-browser/search.contribution.ts index 6f1d4ed9b57bc87910296404cf483123693c3db7..a14b8b8df4306c74cdcbda48aeb08866a6f96724 100644 --- a/src/vs/workbench/parts/search/electron-browser/search.contribution.ts +++ b/src/vs/workbench/parts/search/electron-browser/search.contribution.ts @@ -53,7 +53,7 @@ import { getMultiSelectedResources } from 'vs/workbench/parts/files/browser/file import { Schemas } from 'vs/base/common/network'; import { PanelRegistry, Extensions as PanelExtensions, PanelDescriptor } from 'vs/workbench/browser/panel'; import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; -import { openSearchView, getSearchView, ReplaceAllInFolderAction, ReplaceAllAction, CloseReplaceAction, FocusNextInputAction, FocusPreviousInputAction, FocusNextSearchResultAction, FocusPreviousSearchResultAction, ReplaceInFilesAction, FindInFilesAction, FocusActiveEditorCommand, toggleCaseSensitiveCommand, ShowNextSearchTermAction, ShowPreviousSearchTermAction, toggleRegexCommand, ShowPreviousSearchIncludeAction, ShowNextSearchIncludeAction, CollapseDeepestExpandedLevelAction, toggleWholeWordCommand, RemoveAction, ReplaceAction, ClearSearchResultsAction, copyPathCommand, copyMatchCommand, copyAllCommand, ShowNextSearchExcludeAction, ShowPreviousSearchExcludeAction, clearHistoryCommand } from 'vs/workbench/parts/search/browser/searchActions'; +import { openSearchView, getSearchView, ReplaceAllInFolderAction, ReplaceAllAction, CloseReplaceAction, FocusNextInputAction, FocusPreviousInputAction, FocusNextSearchResultAction, FocusPreviousSearchResultAction, ReplaceInFilesAction, FindInFilesAction, FocusActiveEditorCommand, toggleCaseSensitiveCommand, ShowNextSearchTermAction, ShowPreviousSearchTermAction, toggleRegexCommand, ShowPreviousSearchIncludeAction, ShowNextSearchIncludeAction, CollapseDeepestExpandedLevelAction, toggleWholeWordCommand, RemoveAction, ReplaceAction, ClearSearchResultsAction, copyPathCommand, copyMatchCommand, copyAllCommand, ShowNextSearchExcludeAction, ShowPreviousSearchExcludeAction, clearHistoryCommand, ShowNextReplaceTermAction, ShowPreviousReplaceTermAction } from 'vs/workbench/parts/search/browser/searchActions'; import { VIEW_ID, ISearchConfigurationProperties } from 'vs/platform/search/common/search'; import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; @@ -493,6 +493,9 @@ KeybindingsRegistry.registerCommandAndKeybindingRule(objects.assign({ registry.registerWorkbenchAction(new SyncActionDescriptor(ShowNextSearchTermAction, ShowNextSearchTermAction.ID, ShowNextSearchTermAction.LABEL, ShowNextFindTermKeybinding, ContextKeyExpr.and(Constants.SearchViewVisibleKey, Constants.SearchInputBoxFocusedKey)), 'Search: Show Next Search Term', category); registry.registerWorkbenchAction(new SyncActionDescriptor(ShowPreviousSearchTermAction, ShowPreviousSearchTermAction.ID, ShowPreviousSearchTermAction.LABEL, ShowPreviousFindTermKeybinding, ContextKeyExpr.and(Constants.SearchViewVisibleKey, Constants.SearchInputBoxFocusedKey)), 'Search: Show Previous Search Term', category); +registry.registerWorkbenchAction(new SyncActionDescriptor(ShowNextReplaceTermAction, ShowNextReplaceTermAction.ID, ShowNextReplaceTermAction.LABEL, ShowNextFindTermKeybinding, ContextKeyExpr.and(Constants.SearchViewVisibleKey, Constants.ReplaceInputBoxFocusedKey)), 'Search: Show Next Search Replace Term', category); +registry.registerWorkbenchAction(new SyncActionDescriptor(ShowPreviousReplaceTermAction, ShowPreviousReplaceTermAction.ID, ShowPreviousReplaceTermAction.LABEL, ShowPreviousFindTermKeybinding, ContextKeyExpr.and(Constants.SearchViewVisibleKey, Constants.ReplaceInputBoxFocusedKey)), 'Search: Show Previous Search Replace Term', category); + registry.registerWorkbenchAction(new SyncActionDescriptor(ShowNextSearchIncludeAction, ShowNextSearchIncludeAction.ID, ShowNextSearchIncludeAction.LABEL, ShowNextFindTermKeybinding, ContextKeyExpr.and(Constants.SearchViewVisibleKey, Constants.PatternIncludesFocusedKey)), 'Search: Show Next Search Include Pattern', category); registry.registerWorkbenchAction(new SyncActionDescriptor(ShowPreviousSearchIncludeAction, ShowPreviousSearchIncludeAction.ID, ShowPreviousSearchIncludeAction.LABEL, ShowPreviousFindTermKeybinding, ContextKeyExpr.and(Constants.SearchViewVisibleKey, Constants.PatternIncludesFocusedKey)), 'Search: Show Previous Search Include Pattern', category); diff --git a/src/vs/workbench/parts/terminal/browser/terminalTab.ts b/src/vs/workbench/parts/terminal/browser/terminalTab.ts index ff96195e5bcfeb30463a3acd698c72a6e699dc0d..c14c3d449d7a003aa0da2bed8bb577e3200fd4bf 100644 --- a/src/vs/workbench/parts/terminal/browser/terminalTab.ts +++ b/src/vs/workbench/parts/terminal/browser/terminalTab.ts @@ -446,6 +446,7 @@ export class TerminalTab extends Disposable implements ITerminalTab { const isHorizontal = (direction === Direction.Left || direction === Direction.Right); const font = this._terminalService.configHelper.getFont(); + // TODO: Support letter spacing and line height const amount = isHorizontal ? font.charWidth : font.charHeight; if (amount) { this._splitPaneContainer.resizePane(this._activeInstanceIndex, direction, amount); diff --git a/src/vs/workbench/parts/terminal/common/terminal.ts b/src/vs/workbench/parts/terminal/common/terminal.ts index c10e3c4a734cf69360a507350e5374374d3b166c..f8fdd316c399c794674274b880a90bad8ec59c4e 100644 --- a/src/vs/workbench/parts/terminal/common/terminal.ts +++ b/src/vs/workbench/parts/terminal/common/terminal.ts @@ -45,6 +45,10 @@ export const TerminalCursorStyle = { export const TERMINAL_CONFIG_SECTION = 'terminal.integrated'; +export const DEFAULT_LETTER_SPACING = 0; +export const MINIMUM_LETTER_SPACING = -5; +export const DEFAULT_LINE_HEIGHT = 1.0; + export type FontWeight = 'normal' | 'bold' | '100' | '200' | '300' | '400' | '500' | '600' | '700' | '800' | '900'; export interface ITerminalConfiguration { @@ -67,6 +71,7 @@ export interface ITerminalConfiguration { fontWeightBold: FontWeight; // fontLigatures: boolean; fontSize: number; + letterSpacing: number; lineHeight: number; setLocaleVariables: boolean; scrollback: number; @@ -97,6 +102,7 @@ export interface ITerminalConfigHelper { export interface ITerminalFont { fontFamily: string; fontSize: number; + letterSpacing: number; lineHeight: number; charWidth?: number; charHeight?: number; diff --git a/src/vs/workbench/parts/terminal/electron-browser/terminal.contribution.ts b/src/vs/workbench/parts/terminal/electron-browser/terminal.contribution.ts index d148d55861be1effcfb15e912cbc4c7119330a92..42fb54a9fd536f62ac59f13d6b540af7d4b76f4d 100644 --- a/src/vs/workbench/parts/terminal/electron-browser/terminal.contribution.ts +++ b/src/vs/workbench/parts/terminal/electron-browser/terminal.contribution.ts @@ -13,7 +13,7 @@ import * as panel from 'vs/workbench/browser/panel'; import * as platform from 'vs/base/common/platform'; import * as terminalCommands from 'vs/workbench/parts/terminal/common/terminalCommands'; import { Extensions, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry'; -import { ITerminalService, KEYBINDING_CONTEXT_TERMINAL_FOCUS, KEYBINDING_CONTEXT_TERMINAL_FIND_WIDGET_INPUT_FOCUSED, KEYBINDING_CONTEXT_TERMINAL_TEXT_SELECTED, TERMINAL_PANEL_ID, KEYBINDING_CONTEXT_TERMINAL_FIND_WIDGET_VISIBLE, TerminalCursorStyle, KEYBINDING_CONTEXT_TERMINAL_FIND_WIDGET_NOT_VISIBLE } from 'vs/workbench/parts/terminal/common/terminal'; +import { ITerminalService, KEYBINDING_CONTEXT_TERMINAL_FOCUS, KEYBINDING_CONTEXT_TERMINAL_FIND_WIDGET_INPUT_FOCUSED, KEYBINDING_CONTEXT_TERMINAL_TEXT_SELECTED, TERMINAL_PANEL_ID, KEYBINDING_CONTEXT_TERMINAL_FIND_WIDGET_VISIBLE, TerminalCursorStyle, KEYBINDING_CONTEXT_TERMINAL_FIND_WIDGET_NOT_VISIBLE, DEFAULT_LINE_HEIGHT, DEFAULT_LETTER_SPACING } from 'vs/workbench/parts/terminal/common/terminal'; import { getTerminalDefaultShellUnixLike, getTerminalDefaultShellWindows } from 'vs/workbench/parts/terminal/node/terminal'; import { IWorkbenchActionRegistry, Extensions as ActionExtensions } from 'vs/workbench/common/actions'; import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; @@ -140,10 +140,15 @@ configurationRegistry.registerConfiguration({ 'type': 'number', 'default': EDITOR_FONT_DEFAULTS.fontSize }, + 'terminal.integrated.letterSpacing': { + 'description': nls.localize('terminal.integrated.letterSpacing', "Controls the letter spacing of the terminal, this is an integer value which represents the amount of additional pixels to add between characters."), + 'type': 'number', + 'default': DEFAULT_LETTER_SPACING + }, 'terminal.integrated.lineHeight': { 'description': nls.localize('terminal.integrated.lineHeight', "Controls the line height of the terminal, this number is multiplied by the terminal font size to get the actual line-height in pixels."), 'type': 'number', - 'default': 1 + 'default': DEFAULT_LINE_HEIGHT }, 'terminal.integrated.fontWeight': { 'type': 'string', diff --git a/src/vs/workbench/parts/terminal/electron-browser/terminalConfigHelper.ts b/src/vs/workbench/parts/terminal/electron-browser/terminalConfigHelper.ts index a8e3f9ecbae15c58486995a64cabc8e37c40147c..f74ed02cf9cea712b5969769f0870596433d57ab 100644 --- a/src/vs/workbench/parts/terminal/electron-browser/terminalConfigHelper.ts +++ b/src/vs/workbench/parts/terminal/electron-browser/terminalConfigHelper.ts @@ -10,14 +10,12 @@ import { EDITOR_FONT_DEFAULTS, IEditorOptions } from 'vs/editor/common/config/ed import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IWorkspaceConfigurationService } from 'vs/workbench/services/configuration/common/configuration'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; -import { ITerminalConfiguration, ITerminalConfigHelper, ITerminalFont, IShellLaunchConfig, IS_WORKSPACE_SHELL_ALLOWED_STORAGE_KEY, TERMINAL_CONFIG_SECTION } from 'vs/workbench/parts/terminal/common/terminal'; +import { ITerminalConfiguration, ITerminalConfigHelper, ITerminalFont, IShellLaunchConfig, IS_WORKSPACE_SHELL_ALLOWED_STORAGE_KEY, TERMINAL_CONFIG_SECTION, DEFAULT_LETTER_SPACING, DEFAULT_LINE_HEIGHT, MINIMUM_LETTER_SPACING } from 'vs/workbench/parts/terminal/common/terminal'; import Severity from 'vs/base/common/severity'; import { isFedora } from 'vs/workbench/parts/terminal/node/terminal'; import { Terminal as XTermTerminal } from 'vscode-xterm'; import { INotificationService } from 'vs/platform/notification/common/notification'; -const DEFAULT_LINE_HEIGHT = 1.0; - const MINIMUM_FONT_SIZE = 6; const MAXIMUM_FONT_SIZE = 25; @@ -50,7 +48,7 @@ export class TerminalConfigHelper implements ITerminalConfigHelper { this.config = this._configurationService.getValue(TERMINAL_CONFIG_SECTION); } - private _measureFont(fontFamily: string, fontSize: number, lineHeight: number): ITerminalFont { + private _measureFont(fontFamily: string, fontSize: number, letterSpacing: number, lineHeight: number): ITerminalFont { // Create charMeasureElement if it hasn't been created or if it was orphaned by its parent if (!this._charMeasureElement || !this._charMeasureElement.parentElement) { this._charMeasureElement = document.createElement('div'); @@ -74,6 +72,7 @@ export class TerminalConfigHelper implements ITerminalConfigHelper { this._lastFontMeasurement = { fontFamily, fontSize, + letterSpacing, lineHeight, charWidth: rect.width, charHeight: Math.ceil(rect.height) @@ -98,12 +97,14 @@ export class TerminalConfigHelper implements ITerminalConfigHelper { } let fontSize = this._toInteger(this.config.fontSize, MINIMUM_FONT_SIZE, MAXIMUM_FONT_SIZE, EDITOR_FONT_DEFAULTS.fontSize); + const letterSpacing = this.config.letterSpacing ? Math.max(Math.floor(this.config.letterSpacing), MINIMUM_LETTER_SPACING) : DEFAULT_LETTER_SPACING; const lineHeight = this.config.lineHeight ? Math.max(this.config.lineHeight, 1) : DEFAULT_LINE_HEIGHT; if (excludeDimensions) { return { fontFamily, fontSize, + letterSpacing, lineHeight }; } @@ -114,6 +115,7 @@ export class TerminalConfigHelper implements ITerminalConfigHelper { return { fontFamily, fontSize, + letterSpacing, lineHeight, charHeight: xterm.charMeasure.height, charWidth: xterm.charMeasure.width @@ -122,7 +124,7 @@ export class TerminalConfigHelper implements ITerminalConfigHelper { } // Fall back to measuring the font ourselves - return this._measureFont(fontFamily, fontSize, lineHeight); + return this._measureFont(fontFamily, fontSize, letterSpacing, lineHeight); } public setWorkspaceShellAllowed(isAllowed: boolean): void { diff --git a/src/vs/workbench/parts/terminal/electron-browser/terminalInstance.ts b/src/vs/workbench/parts/terminal/electron-browser/terminalInstance.ts index 621227d54631b266e012769cb57226fc4362a7df..987ab501d2d2fd8eb760e441c8d5e73d88e59cb9 100644 --- a/src/vs/workbench/parts/terminal/electron-browser/terminalInstance.ts +++ b/src/vs/workbench/parts/terminal/electron-browser/terminalInstance.ts @@ -185,7 +185,7 @@ export class TerminalInstance implements ITerminalInstance { // order to be precise. font.charWidth/charHeight alone as insufficient // when window.devicePixelRatio changes. const scaledWidthAvailable = dimension.width * window.devicePixelRatio; - const scaledCharWidth = Math.floor(font.charWidth * window.devicePixelRatio); + const scaledCharWidth = Math.floor(font.charWidth * window.devicePixelRatio) + font.letterSpacing; this._cols = Math.max(Math.floor(scaledWidthAvailable / scaledCharWidth), 1); const scaledHeightAvailable = dimension.height * window.devicePixelRatio; @@ -256,6 +256,7 @@ export class TerminalInstance implements ITerminalInstance { fontWeight: this._configHelper.config.fontWeight, fontWeightBold: this._configHelper.config.fontWeightBold, fontSize: font.fontSize, + letterSpacing: font.letterSpacing, lineHeight: font.lineHeight, bellStyle: this._configHelper.config.enableBell ? 'sound' : 'none', screenReaderMode: accessibilitySupport === 'on', @@ -848,6 +849,9 @@ export class TerminalInstance implements ITerminalInstance { // Only apply these settings when the terminal is visible so that // the characters are measured correctly. if (this._isVisible) { + if (this._xterm.getOption('letterSpacing') !== font.letterSpacing) { + this._xterm.setOption('letterSpacing', font.letterSpacing); + } if (this._xterm.getOption('lineHeight') !== font.lineHeight) { this._xterm.setOption('lineHeight', font.lineHeight); } diff --git a/src/vs/workbench/parts/update/electron-browser/update.ts b/src/vs/workbench/parts/update/electron-browser/update.ts index 07ce8b15539f1ea6c3f05a824cca384911c75a94..204f5399f942a153055c97bf778142f1056948aa 100644 --- a/src/vs/workbench/parts/update/electron-browser/update.ts +++ b/src/vs/workbench/parts/update/electron-browser/update.ts @@ -25,7 +25,7 @@ import { IStorageService, StorageScope } from 'vs/platform/storage/common/storag import { IUpdateService, State as UpdateState, StateType, IUpdate } from 'vs/platform/update/common/update'; import * as semver from 'semver'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; -import { INotificationService } from 'vs/platform/notification/common/notification'; +import { INotificationService, INotificationHandle } from 'vs/platform/notification/common/notification'; import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; import { IWindowService } from 'vs/platform/windows/common/windows'; import { ReleaseNotesManager } from './releaseNotesEditor'; @@ -150,10 +150,10 @@ class NeverShowAgain { private readonly key: string; - readonly action = new Action(`neverShowAgain:${this.key}`, nls.localize('neveragain', "Don't Show Again"), undefined, true, (notification: IDisposable) => { + readonly action = new Action(`neverShowAgain:${this.key}`, nls.localize('neveragain', "Don't Show Again"), undefined, true, (notification: INotificationHandle) => { // Hide notification - notification.dispose(); + notification.close(); return TPromise.wrap(this.storageService.store(this.key, true, StorageScope.GLOBAL)); }); @@ -194,14 +194,14 @@ export class Win3264BitContribution implements IWorkbenchContribution { ? Win3264BitContribution.INSIDER_URL : Win3264BitContribution.URL; - notificationService.prompt( + const handle = notificationService.prompt( severity.Info, nls.localize('64bitisavailable', "{0} for 64-bit Windows is now available! Click [here]({1}) to learn more.", product.nameShort, url), [{ label: nls.localize('neveragain', "Don't Show Again"), isSecondary: true, run: () => { - neverShowAgain.action.run(); + neverShowAgain.action.run(handle); neverShowAgain.action.dispose(); } }] @@ -384,14 +384,14 @@ export class UpdateContribution implements IGlobalActivity { return; } - this.notificationService.prompt( + const handle = this.notificationService.prompt( severity.Info, nls.localize('updateInstalling', "{0} {1} is being installed in the background, we'll let you know when it's done.", product.nameLong, update.productVersion), [{ label: nls.localize('neveragain', "Don't Show Again"), isSecondary: true, run: () => { - neverShowAgain.action.run(); + neverShowAgain.action.run(handle); neverShowAgain.action.dispose(); } }] diff --git a/src/vs/workbench/services/configuration/common/configurationExtensionPoint.ts b/src/vs/workbench/services/configuration/common/configurationExtensionPoint.ts index ddc4e0db248f8616e1e1fd1174003100412a550b..5bb46155fc99e2cb0e31edbac52ebd32340cad77 100644 --- a/src/vs/workbench/services/configuration/common/configurationExtensionPoint.ts +++ b/src/vs/workbench/services/configuration/common/configurationExtensionPoint.ts @@ -11,6 +11,7 @@ import { ExtensionsRegistry, IExtensionPointUser } from 'vs/workbench/services/e import { IConfigurationNode, IConfigurationRegistry, Extensions, editorConfigurationSchemaId, IDefaultConfigurationExtension, validateProperty, ConfigurationScope } from 'vs/platform/configuration/common/configurationRegistry'; import { IJSONContributionRegistry, Extensions as JSONExtensions } from 'vs/platform/jsonschemas/common/jsonContributionRegistry'; import { workspaceSettingsSchemaId, launchSchemaId } from 'vs/workbench/services/configuration/common/configuration'; +import { isObject } from 'vs/base/common/types'; const configurationRegistry = Registry.as(Extensions.Configuration); @@ -131,7 +132,17 @@ function validateProperties(configuration: IConfigurationNode, extension: IExten } for (let key in properties) { const message = validateProperty(key); + if (message) { + delete properties[key]; + extension.collector.warn(message); + continue; + } const propertyConfiguration = configuration.properties[key]; + if (!isObject(propertyConfiguration)) { + delete properties[key]; + extension.collector.error(nls.localize('invalid.property', "'configuration.property' must be an object")); + continue; + } if (propertyConfiguration.scope) { if (propertyConfiguration.scope.toString() === 'application') { propertyConfiguration.scope = ConfigurationScope.APPLICATION; @@ -144,10 +155,6 @@ function validateProperties(configuration: IConfigurationNode, extension: IExten propertyConfiguration.scope = ConfigurationScope.WINDOW; } propertyConfiguration.notMultiRootAdopted = !(extension.description.isBuiltin || (Array.isArray(extension.description.keywords) && extension.description.keywords.indexOf('multi-root ready') !== -1)); - if (message) { - extension.collector.warn(message); - delete properties[key]; - } } } let subNodes = configuration.allOf; diff --git a/test/smoke/src/areas/quickinput/quickinput.ts b/test/smoke/src/areas/quickinput/quickinput.ts new file mode 100644 index 0000000000000000000000000000000000000000..c13b308c0d912da6839373ba27fb25ecda51c183 --- /dev/null +++ b/test/smoke/src/areas/quickinput/quickinput.ts @@ -0,0 +1,28 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Code } from '../../vscode/code'; + +export class QuickInput { + + static QUICK_INPUT = '.quick-input-widget'; + static QUICK_INPUT_INPUT = `${QuickInput.QUICK_INPUT} .quick-input-box input`; + static QUICK_INPUT_FOCUSED_ELEMENT = `${QuickInput.QUICK_INPUT} .quick-open-tree .monaco-tree-row.focused .monaco-highlighted-label`; + + constructor(private code: Code) { } + + async closeQuickInput(): Promise { + await this.code.dispatchKeybinding('escape'); + await this.waitForQuickInputClosed(); + } + + async waitForQuickInputOpened(retryCount?: number): Promise { + await this.code.waitForActiveElement(QuickInput.QUICK_INPUT_INPUT, retryCount); + } + + private async waitForQuickInputClosed(): Promise { + await this.code.waitForElement(QuickInput.QUICK_INPUT, r => !!r && r.attributes.style.indexOf('display: none;') !== -1); + } +} diff --git a/test/smoke/src/areas/statusbar/statusbar.test.ts b/test/smoke/src/areas/statusbar/statusbar.test.ts index 9fcd47bf9b563696222eda658373c747873dce24..dfb1d7550d383113413b140e0ed06865bdff1f5f 100644 --- a/test/smoke/src/areas/statusbar/statusbar.test.ts +++ b/test/smoke/src/areas/statusbar/statusbar.test.ts @@ -30,8 +30,8 @@ export function setup() { const app = this.app as Application; await app.workbench.statusbar.clickOn(StatusBarElement.BRANCH_STATUS); - await app.workbench.quickopen.waitForQuickOpenOpened(); - await app.workbench.quickopen.closeQuickOpen(); + await app.workbench.quickinput.waitForQuickInputOpened(); + await app.workbench.quickinput.closeQuickInput(); await app.workbench.quickopen.openFile('app.js'); await app.workbench.statusbar.clickOn(StatusBarElement.INDENTATION_STATUS); diff --git a/test/smoke/src/areas/workbench/workbench.ts b/test/smoke/src/areas/workbench/workbench.ts index 1e219bda869e8766aa182a6e45f0f6de89898b5a..661e0699a4da924c1365dc658c6ac7617ae58a9a 100644 --- a/test/smoke/src/areas/workbench/workbench.ts +++ b/test/smoke/src/areas/workbench/workbench.ts @@ -6,6 +6,7 @@ import { Explorer } from '../explorer/explorer'; import { ActivityBar } from '../activitybar/activityBar'; import { QuickOpen } from '../quickopen/quickopen'; +import { QuickInput } from '../quickinput/quickinput'; import { Extensions } from '../extensions/extensions'; import { Search } from '../search/search'; import { Editor } from '../editor/editor'; @@ -26,6 +27,7 @@ export interface Commands { export class Workbench { readonly quickopen: QuickOpen; + readonly quickinput: QuickInput; readonly editors: Editors; readonly explorer: Explorer; readonly activitybar: ActivityBar; @@ -43,6 +45,7 @@ export class Workbench { constructor(code: Code, userDataPath: string) { this.editors = new Editors(code); this.quickopen = new QuickOpen(code, this.editors); + this.quickinput = new QuickInput(code); this.explorer = new Explorer(code, this.editors); this.activitybar = new ActivityBar(code); this.search = new Search(code); diff --git a/yarn.lock b/yarn.lock index 9d7d6dc7458e05358c06a8556afe7cb12c183f1b..baf9dd6a6618fb021c663aa4d2ddc4dfbf9cb7ae 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5380,9 +5380,9 @@ strip-json-comments@~2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" -sudo-prompt@^8.0.0: - version "8.0.0" - resolved "https://registry.yarnpkg.com/sudo-prompt/-/sudo-prompt-8.0.0.tgz#a7b4a1ca6cbcca0e705b90a89dfc81d11034cba9" +sudo-prompt@8.2.0: + version "8.2.0" + resolved "https://registry.yarnpkg.com/sudo-prompt/-/sudo-prompt-8.2.0.tgz#bcd4aaacdb367b77b4bffcce1c658c2b1dd327f3" sumchecker@^2.0.1: version "2.0.2" @@ -5997,9 +5997,9 @@ vscode-textmate@^3.3.3: fast-plist "^0.1.2" oniguruma "^6.0.1" -vscode-xterm@3.5.0-beta1: - version "3.5.0-beta1" - resolved "https://registry.yarnpkg.com/vscode-xterm/-/vscode-xterm-3.5.0-beta1.tgz#90b45c71aa188d0c6e1acbec18f587203d18dc2a" +vscode-xterm@3.4.0-beta3: + version "3.4.0-beta3" + resolved "https://registry.yarnpkg.com/vscode-xterm/-/vscode-xterm-3.4.0-beta3.tgz#349db387bd3669ad4d6044a6ea6d699e6d649fb5" vso-node-api@^6.1.2-preview: version "6.1.2-preview"