From 0d09b73da112a715449db7ca7939f4630c83bb5c Mon Sep 17 00:00:00 2001 From: svipben Date: Thu, 29 Mar 2018 20:18:44 +0300 Subject: [PATCH 01/49] fixes #45479: different border styles for HC --- .../editor/browser/widget/diffEditorWidget.ts | 18 ++++---- .../editor/common/view/editorColorRegistry.ts | 19 +++++---- src/vs/editor/contrib/find/findWidget.ts | 36 ++++++++-------- .../wordHighlighter/wordHighlighter.ts | 42 ++++++++----------- .../parts/search/browser/searchView.ts | 6 +-- 5 files changed, 62 insertions(+), 59 deletions(-) diff --git a/src/vs/editor/browser/widget/diffEditorWidget.ts b/src/vs/editor/browser/widget/diffEditorWidget.ts index 8bd844fdf8e..294e27a714c 100644 --- a/src/vs/editor/browser/widget/diffEditorWidget.ts +++ b/src/vs/editor/browser/widget/diffEditorWidget.ts @@ -2032,27 +2032,31 @@ function createFakeLinesDiv(): HTMLElement { } registerThemingParticipant((theme, collector) => { - let added = theme.getColor(diffInserted); + const added = theme.getColor(diffInserted); if (added) { collector.addRule(`.monaco-editor .line-insert, .monaco-editor .char-insert { background-color: ${added}; }`); collector.addRule(`.monaco-diff-editor .line-insert, .monaco-diff-editor .char-insert { background-color: ${added}; }`); collector.addRule(`.monaco-editor .inline-added-margin-view-zone { background-color: ${added}; }`); } - let removed = theme.getColor(diffRemoved); + + const removed = theme.getColor(diffRemoved); if (removed) { collector.addRule(`.monaco-editor .line-delete, .monaco-editor .char-delete { background-color: ${removed}; }`); collector.addRule(`.monaco-diff-editor .line-delete, .monaco-diff-editor .char-delete { background-color: ${removed}; }`); collector.addRule(`.monaco-editor .inline-deleted-margin-view-zone { background-color: ${removed}; }`); } - let addedOutline = theme.getColor(diffInsertedOutline); + + const addedOutline = theme.getColor(diffInsertedOutline); if (addedOutline) { - collector.addRule(`.monaco-editor .line-insert, .monaco-editor .char-insert { border: 1px dashed ${addedOutline}; }`); + collector.addRule(`.monaco-editor .line-insert, .monaco-editor .char-insert { border: 1px ${theme.type === 'hc' ? 'dashed' : 'solid'} ${addedOutline}; }`); } - let removedOutline = theme.getColor(diffRemovedOutline); + + const removedOutline = theme.getColor(diffRemovedOutline); if (removedOutline) { - collector.addRule(`.monaco-editor .line-delete, .monaco-editor .char-delete { border: 1px dashed ${removedOutline}; }`); + collector.addRule(`.monaco-editor .line-delete, .monaco-editor .char-delete { border: 1px ${theme.type === 'hc' ? 'dashed' : 'solid'} ${removedOutline}; }`); } - let shadow = theme.getColor(scrollbarShadow); + + const shadow = theme.getColor(scrollbarShadow); if (shadow) { collector.addRule(`.monaco-diff-editor.side-by-side .editor.modified { box-shadow: -6px 0 5px -5px ${shadow}; }`); } diff --git a/src/vs/editor/common/view/editorColorRegistry.ts b/src/vs/editor/common/view/editorColorRegistry.ts index 86cfc23d51a..9bafc0f45c2 100644 --- a/src/vs/editor/common/view/editorColorRegistry.ts +++ b/src/vs/editor/common/view/editorColorRegistry.ts @@ -56,27 +56,32 @@ export const overviewRulerInfo = registerColor('editorOverviewRuler.infoForegrou // contains all color rules that used to defined in editor/browser/widget/editor.css registerThemingParticipant((theme, collector) => { - let background = theme.getColor(editorBackground); + const background = theme.getColor(editorBackground); if (background) { collector.addRule(`.monaco-editor, .monaco-editor-background, .monaco-editor .inputarea.ime-input { background-color: ${background}; }`); } - let foreground = theme.getColor(editorForeground); + + const foreground = theme.getColor(editorForeground); if (foreground) { collector.addRule(`.monaco-editor, .monaco-editor .inputarea.ime-input { color: ${foreground}; }`); } - let gutter = theme.getColor(editorGutter); + + const gutter = theme.getColor(editorGutter); if (gutter) { collector.addRule(`.monaco-editor .margin { background-color: ${gutter}; }`); } - let rangeHighlight = theme.getColor(editorRangeHighlight); + + const rangeHighlight = theme.getColor(editorRangeHighlight); if (rangeHighlight) { collector.addRule(`.monaco-editor .rangeHighlight { background-color: ${rangeHighlight}; }`); } - let rangeHighlightBorder = theme.getColor(editorRangeHighlightBorder); + + const rangeHighlightBorder = theme.getColor(editorRangeHighlightBorder); if (rangeHighlightBorder) { - collector.addRule(`.monaco-editor .rangeHighlight { border: 1px dotted ${rangeHighlightBorder}; }`); + collector.addRule(`.monaco-editor .rangeHighlight { border: 1px ${theme.type === 'hc' ? 'dotted' : 'solid'} ${rangeHighlightBorder}; }`); } - let invisibles = theme.getColor(editorWhitespaces); + + const invisibles = theme.getColor(editorWhitespaces); if (invisibles) { collector.addRule(`.vs-whitespace { color: ${invisibles} !important; }`); } diff --git a/src/vs/editor/contrib/find/findWidget.ts b/src/vs/editor/contrib/find/findWidget.ts index a852de93cbc..83ff2daa50a 100644 --- a/src/vs/editor/contrib/find/findWidget.ts +++ b/src/vs/editor/contrib/find/findWidget.ts @@ -1066,48 +1066,50 @@ export class SimpleButton extends Widget { // theming registerThemingParticipant((theme, collector) => { - function addBackgroundColorRule(selector: string, color: Color): void { + const addBackgroundColorRule = (selector: string, color: Color): void => { if (color) { collector.addRule(`.monaco-editor ${selector} { background-color: ${color}; }`); } - } + }; addBackgroundColorRule('.findMatch', theme.getColor(editorFindMatchHighlight)); addBackgroundColorRule('.currentFindMatch', theme.getColor(editorFindMatch)); addBackgroundColorRule('.findScope', theme.getColor(editorFindRangeHighlight)); - let widgetBackground = theme.getColor(editorWidgetBackground); + const widgetBackground = theme.getColor(editorWidgetBackground); addBackgroundColorRule('.find-widget', widgetBackground); - let widgetShadowColor = theme.getColor(widgetShadow); + const widgetShadowColor = theme.getColor(widgetShadow); if (widgetShadowColor) { collector.addRule(`.monaco-editor .find-widget { box-shadow: 0 2px 8px ${widgetShadowColor}; }`); } - let findMatchHighlightBorder = theme.getColor(editorFindMatchHighlightBorder); + const findMatchHighlightBorder = theme.getColor(editorFindMatchHighlightBorder); if (findMatchHighlightBorder) { - collector.addRule(`.monaco-editor .findMatch { border: 1px dotted ${findMatchHighlightBorder}; -moz-box-sizing: border-box; box-sizing: border-box; }`); - } - let findMatchBorder = theme.getColor(editorFindMatchBorder); - if (findMatchBorder) { - collector.addRule(`.monaco-editor .currentFindMatch { border: 2px solid ${findMatchBorder}; padding: 1px; -moz-box-sizing: border-box; box-sizing: border-box; }`); - } - let findRangeHighlightBorder = theme.getColor(editorFindRangeHighlightBorder); - if (findRangeHighlightBorder) { - collector.addRule(`.monaco-editor .findScope { border: 1px dashed ${findRangeHighlightBorder}; }`); + collector.addRule(`.monaco-editor .findMatch { border: 1px ${theme.type === 'hc' ? 'dotted' : 'solid'} ${findMatchHighlightBorder}; box-sizing: border-box; }`); } - let hcBorder = theme.getColor(contrastBorder); + const findMatchBorder = theme.getColor(editorFindMatchBorder); + if (findMatchBorder) { + collector.addRule(`.monaco-editor .currentFindMatch { border: 2px solid ${findMatchBorder}; padding: 1px; box-sizing: border-box; }`); + } + + const findRangeHighlightBorder = theme.getColor(editorFindRangeHighlightBorder); + if (findRangeHighlightBorder) { + collector.addRule(`.monaco-editor .findScope { border: 1px ${theme.type === 'hc' ? 'dashed' : 'solid'} ${findRangeHighlightBorder}; }`); + } + + const hcBorder = theme.getColor(contrastBorder); if (hcBorder) { collector.addRule(`.monaco-editor .find-widget { border: 2px solid ${hcBorder}; }`); } - let error = theme.getColor(errorForeground); + const error = theme.getColor(errorForeground); if (error) { collector.addRule(`.monaco-editor .find-widget.no-results .matchesCount { color: ${error}; }`); } - let border = theme.getColor(editorWidgetBorder); + const border = theme.getColor(editorWidgetBorder); if (border) { collector.addRule(`.monaco-editor .find-widget .monaco-sash { background-color: ${border}; width: 3px !important; margin-left: -4px;}`); } diff --git a/src/vs/editor/contrib/wordHighlighter/wordHighlighter.ts b/src/vs/editor/contrib/wordHighlighter/wordHighlighter.ts index 00c39b26a49..dd664d73fb7 100644 --- a/src/vs/editor/contrib/wordHighlighter/wordHighlighter.ts +++ b/src/vs/editor/contrib/wordHighlighter/wordHighlighter.ts @@ -504,42 +504,34 @@ registerEditorAction(NextWordHighlightAction); registerEditorAction(PrevWordHighlightAction); registerThemingParticipant((theme, collector) => { - let selectionHighlight = theme.getColor(editorSelectionHighlight); + const selectionHighlight = theme.getColor(editorSelectionHighlight); if (selectionHighlight) { collector.addRule(`.monaco-editor .focused .selectionHighlight { background-color: ${selectionHighlight}; }`); collector.addRule(`.monaco-editor .selectionHighlight { background-color: ${selectionHighlight.transparent(0.5)}; }`); } - let wordHighlight = theme.getColor(editorWordHighlight); + + const wordHighlight = theme.getColor(editorWordHighlight); if (wordHighlight) { collector.addRule(`.monaco-editor .wordHighlight { background-color: ${wordHighlight}; }`); } - let wordHighlightStrong = theme.getColor(editorWordHighlightStrong); + + const wordHighlightStrong = theme.getColor(editorWordHighlightStrong); if (wordHighlightStrong) { collector.addRule(`.monaco-editor .wordHighlightStrong { background-color: ${wordHighlightStrong}; }`); } - let selectionHighlightBorder = theme.getColor(editorSelectionHighlightBorder); + + const selectionHighlightBorder = theme.getColor(editorSelectionHighlightBorder); if (selectionHighlightBorder) { - if (theme.type === 'hc') { - collector.addRule(`.monaco-editor .selectionHighlight { border: 1px dotted ${selectionHighlightBorder}; box-sizing: border-box; }`); - } else { - collector.addRule(`.monaco-editor .selectionHighlight { border: 1px solid ${selectionHighlightBorder}; box-sizing: border-box; }`); - } - } - let wordHighlightBorder = theme.getColor(editorWordHighlightBorder); - if (wordHighlightBorder) { - if (theme.type === 'hc') { - collector.addRule(`.monaco-editor .wordHighlight { border: 1px dashed ${wordHighlightBorder}; box-sizing: border-box; }`); - } else { - collector.addRule(`.monaco-editor .wordHighlight { border: 1px solid ${wordHighlightBorder}; box-sizing: border-box; }`); - } - } - let wordHighlightStrongBorder = theme.getColor(editorWordHighlightStrongBorder); - if (wordHighlightStrongBorder) { - if (theme.type === 'hc') { - collector.addRule(`.monaco-editor .wordHighlightStrong { border: 1px dashed ${wordHighlightStrongBorder}; box-sizing: border-box; }`); - } else { - collector.addRule(`.monaco-editor .wordHighlightStrong { border: 1px solid ${wordHighlightStrongBorder}; box-sizing: border-box; }`); - } + collector.addRule(`.monaco-editor .selectionHighlight { border: 1px ${theme.type === 'hc' ? 'dotted' : 'solid'} ${selectionHighlightBorder}; box-sizing: border-box; }`); } + const wordHighlightBorder = theme.getColor(editorWordHighlightBorder); + if (wordHighlightBorder) { + collector.addRule(`.monaco-editor .wordHighlight { border: 1px ${theme.type === 'hc' ? 'dashed' : 'solid'} ${wordHighlightBorder}; box-sizing: border-box; }`); + } + + const wordHighlightStrongBorder = theme.getColor(editorWordHighlightStrongBorder); + if (wordHighlightStrongBorder) { + collector.addRule(`.monaco-editor .wordHighlightStrong { border: 1px ${theme.type === 'hc' ? 'dashed' : 'solid'} ${wordHighlightStrongBorder}; box-sizing: border-box; }`); + } }); diff --git a/src/vs/workbench/parts/search/browser/searchView.ts b/src/vs/workbench/parts/search/browser/searchView.ts index d40610f8601..ff5008ac4c9 100644 --- a/src/vs/workbench/parts/search/browser/searchView.ts +++ b/src/vs/workbench/parts/search/browser/searchView.ts @@ -1489,16 +1489,16 @@ registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => { const diffInsertedOutlineColor = theme.getColor(diffInsertedOutline); if (diffInsertedOutlineColor) { - collector.addRule(`.monaco-workbench .search-view .replaceMatch:not(:empty) { border: 1px dashed ${diffInsertedOutlineColor}; }`); + collector.addRule(`.monaco-workbench .search-view .replaceMatch:not(:empty) { border: 1px ${theme.type === 'hc' ? 'dashed' : 'solid'} ${diffInsertedOutlineColor}; }`); } const diffRemovedOutlineColor = theme.getColor(diffRemovedOutline); if (diffRemovedOutlineColor) { - collector.addRule(`.monaco-workbench .search-view .replace.findInFileMatch { border: 1px dashed ${diffRemovedOutlineColor}; }`); + collector.addRule(`.monaco-workbench .search-view .replace.findInFileMatch { border: 1px ${theme.type === 'hc' ? 'dashed' : 'solid'} ${diffRemovedOutlineColor}; }`); } const findMatchHighlightBorder = theme.getColor(editorFindMatchHighlightBorder); if (findMatchHighlightBorder) { - collector.addRule(`.monaco-workbench .search-view .findInFileMatch { border: 1px dashed ${findMatchHighlightBorder}; }`); + collector.addRule(`.monaco-workbench .search-view .findInFileMatch { border: 1px ${theme.type === 'hc' ? 'dashed' : 'solid'} ${findMatchHighlightBorder}; }`); } }); From 1f9184254c5c6f53d68f5840b5a6b61bee8264d1 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Tue, 3 Apr 2018 17:21:24 +0200 Subject: [PATCH 02/49] Fix #47147 --- src/vs/workbench/api/node/extHostTreeViews.ts | 6 +++--- .../api/extHostTreeViews.test.ts | 17 ++++++++++++++++- 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/src/vs/workbench/api/node/extHostTreeViews.ts b/src/vs/workbench/api/node/extHostTreeViews.ts index b6ddcfa97a0..9cadef91101 100644 --- a/src/vs/workbench/api/node/extHostTreeViews.ts +++ b/src/vs/workbench/api/node/extHostTreeViews.ts @@ -152,7 +152,7 @@ class ExtHostTreeView extends Disposable { private resolveTreeNode(element: T, parent?: TreeNode): TPromise { return asWinJsPromise(() => this.dataProvider.getTreeItem(element)) - .then(extTreeItem => this.createHandle(element, extTreeItem, parent)) + .then(extTreeItem => this.createHandle(element, extTreeItem, parent, true)) .then(handle => this.getChildren(parent ? parent.item.handle : null) .then(() => { const cachedElement = this.getExtensionElement(handle); @@ -303,7 +303,7 @@ class ExtHostTreeView extends Disposable { return item; } - private createHandle(element: T, { id, label, resourceUri }: vscode.TreeItem, parent?: TreeNode): TreeItemHandle { + private createHandle(element: T, { id, label, resourceUri }: vscode.TreeItem, parent: TreeNode, first?: boolean): TreeItemHandle { if (id) { return `${ExtHostTreeView.ID_HANDLE_PREFIX}/${id}`; } @@ -316,7 +316,7 @@ class ExtHostTreeView extends Disposable { for (let counter = 0; counter <= childrenNodes.length; counter++) { const handle = `${prefix}/${counter}:${elementId}`; - if (!this.elements.has(handle) || existingHandle === handle) { + if (first || !this.elements.has(handle) || existingHandle === handle) { return handle; } } diff --git a/src/vs/workbench/test/electron-browser/api/extHostTreeViews.test.ts b/src/vs/workbench/test/electron-browser/api/extHostTreeViews.test.ts index 9b95fc29698..969cac30359 100644 --- a/src/vs/workbench/test/electron-browser/api/extHostTreeViews.test.ts +++ b/src/vs/workbench/test/electron-browser/api/extHostTreeViews.test.ts @@ -423,7 +423,7 @@ suite('ExtHostTreeView', function () { }); }); - test('reveal will return parents array for an element', () => { + test('reveal will return parents array for an element when hierarchy is not loaded', () => { const revealTarget = sinon.spy(target, '$reveal'); const treeView = testObject.createTreeView('treeDataProvider', { treeDataProvider: aCompleteNodeTreeDataProvider() }); return treeView.reveal({ key: 'aa' }) @@ -436,6 +436,21 @@ suite('ExtHostTreeView', function () { }); }); + test('reveal will return parents array for an element when hierarchy is loaded', () => { + const revealTarget = sinon.spy(target, '$reveal'); + const treeView = testObject.createTreeView('treeDataProvider', { treeDataProvider: aCompleteNodeTreeDataProvider() }); + return testObject.$getChildren('treeDataProvider') + .then(() => testObject.$getChildren('treeDataProvider', '0/0:a')) + .then(() => treeView.reveal({ key: 'aa' }) + .then(() => { + assert.ok(revealTarget.calledOnce); + assert.deepEqual('treeDataProvider', revealTarget.args[0][0]); + assert.deepEqual({ handle: '0/0:a/0:aa', label: 'aa', collapsibleState: TreeItemCollapsibleState.None, parentHandle: '0/0:a' }, removeUnsetKeys(revealTarget.args[0][1])); + assert.deepEqual([{ handle: '0/0:a', label: 'a', collapsibleState: TreeItemCollapsibleState.Collapsed }], (>revealTarget.args[0][2]).map(arg => removeUnsetKeys(arg))); + assert.equal(void 0, revealTarget.args[0][3]); + })); + }); + test('reveal will return parents array for deeper element with no selection', () => { tree = { 'b': { From 6ef45d9332ded9bac37c0ec43a9dd70d165c9acc Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Tue, 3 Apr 2018 18:04:09 +0200 Subject: [PATCH 03/49] notifications - more use of prompt() API --- .../extensions/browser/extensionsActions.ts | 123 ++++++------------ .../electron-browser/extensionsActions.ts | 16 +-- .../parts/update/electron-browser/update.ts | 7 +- 3 files changed, 44 insertions(+), 102 deletions(-) diff --git a/src/vs/workbench/parts/extensions/browser/extensionsActions.ts b/src/vs/workbench/parts/extensions/browser/extensionsActions.ts index 0d5e207078e..afc0d173ab5 100644 --- a/src/vs/workbench/parts/extensions/browser/extensionsActions.ts +++ b/src/vs/workbench/parts/extensions/browser/extensionsActions.ts @@ -48,31 +48,23 @@ import { mnemonicButtonLabel } from 'vs/base/common/labels'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { IQuickOpenService, IPickOpenEntry } from 'vs/platform/quickOpen/common/quickOpen'; -class DownloadExtensionAction extends Action { - - constructor( - private extension: IExtension, - @IOpenerService private openerService: IOpenerService, - @INotificationService private notificationService: INotificationService, - @IInstantiationService private instantiationService: IInstantiationService - ) { - super('extensions.download', localize('download', "Download Manually"), '', true); - } - - run(): TPromise { - return this.openerService.open(URI.parse(this.extension.downloadUrl)).then(() => { - const action = this.instantiationService.createInstance(InstallVSIXAction, InstallVSIXAction.ID, InstallVSIXAction.LABEL); - const handle = this.notificationService.notify({ - severity: Severity.Info, - message: localize('install vsix', 'Once downloaded, please manually install the downloaded VSIX of \'{0}\'.', this.extension.id), - actions: { - primary: [action] - } +const promptDownloadManually = (extension: IExtension, message: string, instantiationService: IInstantiationService, notificationService: INotificationService, openerService: IOpenerService) => { + notificationService.prompt(Severity.Error, message, [localize('download', "Download Manually")]).done(choice => { + if (choice === 0) { + openerService.open(URI.parse(extension.downloadUrl)).then(() => { + const action = instantiationService.createInstance(InstallVSIXAction, InstallVSIXAction.ID, InstallVSIXAction.LABEL); + const handle = notificationService.notify({ + severity: Severity.Info, + message: localize('install vsix', 'Once downloaded, please manually install the downloaded VSIX of \'{0}\'.', extension.id), + actions: { + primary: [action] + } + }); + once(handle.onDidDispose)(() => action.dispose()); }); - once(handle.onDidDispose)(() => action.dispose()); - }); - } -} + } + }); +}; export class InstallAction extends Action { @@ -90,7 +82,8 @@ export class InstallAction extends Action { constructor( @IExtensionsWorkbenchService private extensionsWorkbenchService: IExtensionsWorkbenchService, @IInstantiationService private instantiationService: IInstantiationService, - @INotificationService private notificationService: INotificationService + @INotificationService private notificationService: INotificationService, + @IOpenerService private openerService: IOpenerService ) { super('extensions.install', InstallAction.InstallLabel, InstallAction.Class, false); @@ -133,15 +126,7 @@ export class InstallAction extends Action { console.error(err); - const action = this.instantiationService.createInstance(DownloadExtensionAction, extension); - const handle = this.notificationService.notify({ - severity: Severity.Error, - message: localize('failedToInstall', "Failed to install \'{0}\'.", extension.id), - actions: { - primary: [action] - } - }); - once(handle.onDidDispose)(() => action.dispose()); + promptDownloadManually(extension, localize('failedToInstall', "Failed to install \'{0}\'.", extension.id), this.instantiationService, this.notificationService, this.openerService); }); } @@ -306,7 +291,8 @@ export class UpdateAction extends Action { constructor( @IExtensionsWorkbenchService private extensionsWorkbenchService: IExtensionsWorkbenchService, @IInstantiationService private instantiationService: IInstantiationService, - @INotificationService private notificationService: INotificationService + @INotificationService private notificationService: INotificationService, + @IOpenerService private openerService: IOpenerService ) { super('extensions.update', UpdateAction.Label, UpdateAction.DisabledClass, false); @@ -348,15 +334,8 @@ export class UpdateAction extends Action { } console.error(err); - const action = this.instantiationService.createInstance(DownloadExtensionAction, extension); - const handle = this.notificationService.notify({ - severity: Severity.Error, - message: localize('failedToUpdate', "Failed to update \'{0}\'.", extension.id), - actions: { - primary: [action] - } - }); - once(handle.onDidDispose)(() => action.dispose()); + + promptDownloadManually(extension, localize('failedToUpdate', "Failed to update \'{0}\'.", extension.id), this.instantiationService, this.notificationService, this.openerService); }); } @@ -833,7 +812,8 @@ export class UpdateAllAction extends Action { label = UpdateAllAction.LABEL, @IExtensionsWorkbenchService private extensionsWorkbenchService: IExtensionsWorkbenchService, @INotificationService private notificationService: INotificationService, - @IInstantiationService private instantiationService: IInstantiationService + @IInstantiationService private instantiationService: IInstantiationService, + @IOpenerService private openerService: IOpenerService ) { super(id, label, '', false); @@ -860,15 +840,8 @@ export class UpdateAllAction extends Action { } console.error(err); - const action = this.instantiationService.createInstance(DownloadExtensionAction, extension); - const handle = this.notificationService.notify({ - severity: Severity.Error, - message: localize('failedToUpdate', "Failed to update \'{0}\'.", extension.id), - actions: { - primary: [action] - } - }); - once(handle.onDidDispose)(() => action.dispose()); + + promptDownloadManually(extension, localize('failedToUpdate', "Failed to update \'{0}\'.", extension.id), this.instantiationService, this.notificationService, this.openerService); }); } @@ -1205,7 +1178,8 @@ export class InstallWorkspaceRecommendedExtensionsAction extends Action { @IExtensionsWorkbenchService private extensionsWorkbenchService: IExtensionsWorkbenchService, @IExtensionTipsService private extensionTipsService: IExtensionTipsService, @INotificationService private notificationService: INotificationService, - @IInstantiationService private instantiationService: IInstantiationService + @IInstantiationService private instantiationService: IInstantiationService, + @IOpenerService private openerService: IOpenerService ) { super(id, label, 'extension-action'); this.extensionsWorkbenchService.onChange(() => this.update(), this, this.disposables); @@ -1269,15 +1243,8 @@ export class InstallWorkspaceRecommendedExtensionsAction extends Action { } console.error(err); - const action = this.instantiationService.createInstance(DownloadExtensionAction, extension); - const handle = this.notificationService.notify({ - severity: Severity.Error, - message: localize('failedToInstall', "Failed to install \'{0}\'.", extension.id), - actions: { - primary: [action] - } - }); - once(handle.onDidDispose)(() => action.dispose()); + + promptDownloadManually(extension, localize('failedToInstall', "Failed to install \'{0}\'.", extension.id), this.instantiationService, this.notificationService, this.openerService); }); } @@ -1300,7 +1267,8 @@ export class InstallRecommendedExtensionAction extends Action { @IViewletService private viewletService: IViewletService, @IExtensionsWorkbenchService private extensionsWorkbenchService: IExtensionsWorkbenchService, @INotificationService private notificationService: INotificationService, - @IInstantiationService private instantiationService: IInstantiationService + @IInstantiationService private instantiationService: IInstantiationService, + @IOpenerService private openerService: IOpenerService ) { super(InstallRecommendedExtensionAction.ID, InstallRecommendedExtensionAction.LABEL, null); this.extensionId = extensionId; @@ -1338,15 +1306,8 @@ export class InstallRecommendedExtensionAction extends Action { } console.error(err); - const action = this.instantiationService.createInstance(DownloadExtensionAction, extension); - const handle = this.notificationService.notify({ - severity: Severity.Error, - message: localize('failedToInstall', "Failed to install \'{0}\'.", extension.id), - actions: { - primary: [action] - } - }); - once(handle.onDidDispose)(() => action.dispose()); + + promptDownloadManually(extension, localize('failedToInstall', "Failed to install \'{0}\'.", extension.id), this.instantiationService, this.notificationService, this.openerService); }); } @@ -1994,17 +1955,9 @@ export class ReinstallAction extends Action { private reinstallExtension(extension: IExtension): TPromise { return this.extensionsWorkbenchService.reinstall(extension) .then(() => { - this.notificationService.notify({ - message: localize('ReinstallAction.success', "Successfully reinstalled the extension."), - severity: Severity.Info, - actions: { - primary: [{ - id: 'reload', - label: localize('ReinstallAction.reloadNow', "Reload Now"), - enabled: true, - run: () => this.windowService.reloadWindow(), - dispose: () => null - }] + this.notificationService.prompt(Severity.Info, localize('ReinstallAction.success', "Successfully reinstalled the extension."), [localize('ReinstallAction.reloadNow', "Reload Now")]).done(choice => { + if (choice === 0) { + this.windowService.reloadWindow(); } }); }, error => this.notificationService.error(error)); diff --git a/src/vs/workbench/parts/extensions/electron-browser/extensionsActions.ts b/src/vs/workbench/parts/extensions/electron-browser/extensionsActions.ts index aa3773bbe87..0b284cca2bd 100644 --- a/src/vs/workbench/parts/extensions/electron-browser/extensionsActions.ts +++ b/src/vs/workbench/parts/extensions/electron-browser/extensionsActions.ts @@ -5,7 +5,7 @@ import { localize } from 'vs/nls'; import { TPromise } from 'vs/base/common/winjs.base'; -import { Action, IAction } from 'vs/base/common/actions'; +import { Action } from 'vs/base/common/actions'; import * as paths from 'vs/base/common/paths'; import { IExtensionsWorkbenchService, IExtension } from 'vs/workbench/parts/extensions/common/extensions'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; @@ -131,17 +131,9 @@ export class ReinstallAction extends Action { private reinstallExtension(extension: IExtension): TPromise { return this.extensionsWorkbenchService.reinstall(extension) .then(() => { - this.notificationService.notify({ - message: localize('ReinstallAction.success', "Successfully reinstalled the extension."), - severity: Severity.Info, - actions: { - primary: [{ - id: 'reload', - label: localize('ReinstallAction.reloadNow', "Reload Now"), - enabled: true, - run: () => this.windowService.reloadWindow(), - dispose: () => null - }] + this.notificationService.prompt(Severity.Info, localize('ReinstallAction.success', "Successfully reinstalled the extension."), [localize('ReinstallAction.reloadNow', "Reload Now")]).done(choice => { + if (choice === 0) { + this.windowService.reloadWindow(); } }); }, error => this.notificationService.error(error)); diff --git a/src/vs/workbench/parts/update/electron-browser/update.ts b/src/vs/workbench/parts/update/electron-browser/update.ts index 93cf12eeb33..4a8dad69dbc 100644 --- a/src/vs/workbench/parts/update/electron-browser/update.ts +++ b/src/vs/workbench/parts/update/electron-browser/update.ts @@ -143,10 +143,7 @@ export class ProductContribution implements IWorkbenchContribution { // should we show the new license? if (product.licenseUrl && lastVersion && semver.satisfies(lastVersion, '<1.0.0') && semver.satisfies(pkg.version, '>=1.0.0')) { - notificationService.notify({ - severity: severity.Info, - message: nls.localize('licenseChanged', "Our license terms have changed, please click [here]({0}) to go through them.", product.licenseUrl), - }); + notificationService.info(nls.localize('licenseChanged', "Our license terms have changed, please click [here]({0}) to go through them.", product.licenseUrl)); } storageService.store(ProductContribution.KEY, pkg.version, StorageScope.GLOBAL); @@ -392,7 +389,7 @@ export class UpdateContribution implements IGlobalActivity { message: nls.localize('updateAvailableAfterRestart', "Restart {0} to apply the latest update.", product.nameLong), actions: { primary: [applyUpdateAction, NotNowAction, releaseNotesAction] } }); - once(handle.onDidDispose)(() => applyUpdateAction, releaseNotesAction); + once(handle.onDidDispose)(() => dispose(applyUpdateAction, releaseNotesAction)); } private shouldShowNotification(): boolean { From 3bc2728340325c0e2215c998db17f069c1d91719 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Tue, 3 Apr 2018 19:51:27 +0200 Subject: [PATCH 04/49] fix test --debug after electron adoption --- test/electron/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/electron/index.js b/test/electron/index.js index aa054f553ca..770bb853d4c 100644 --- a/test/electron/index.js +++ b/test/electron/index.js @@ -132,7 +132,7 @@ app.on('ready', () => { win.webContents.on('did-finish-load', () => { if (argv.debug) { win.show(); - win.webContents.openDevTools('right'); + win.webContents.openDevTools({ mode: 'right' }); } win.webContents.send('run', argv); }); From 691c789ce35a42b78c7c64c87380152c3c5c6f80 Mon Sep 17 00:00:00 2001 From: Rachel Macfarlane Date: Mon, 2 Apr 2018 16:12:14 -0700 Subject: [PATCH 05/49] Get GPU information in issue reporter, #46960 --- .../issue/issueReporterMain.ts | 6 +++- .../issue/issueReporterModel.ts | 6 +++- .../issue/test/testReporterModel.test.ts | 30 +++++++++++++++++++ src/vs/code/electron-main/diagnostics.ts | 14 +++++++-- 4 files changed, 52 insertions(+), 4 deletions(-) diff --git a/src/vs/code/electron-browser/issue/issueReporterMain.ts b/src/vs/code/electron-browser/issue/issueReporterMain.ts index d87232cf697..47f1ca1038f 100644 --- a/src/vs/code/electron-browser/issue/issueReporterMain.ts +++ b/src/vs/code/electron-browser/issue/issueReporterMain.ts @@ -771,10 +771,14 @@ export class IssueReporter extends Disposable { const target = document.querySelector('.block-system .block-info'); let tableHtml = ''; Object.keys(state.systemInfo).forEach(k => { + const data = typeof state.systemInfo[k] === 'object' + ? Object.keys(state.systemInfo[k]).map(key => `${key}: ${state.systemInfo[k][key]}`).join('
') + : state.systemInfo[k]; + tableHtml += ` ${k} - ${state.systemInfo[k]} + ${data} `; }); target.innerHTML = `${tableHtml}
`; diff --git a/src/vs/code/electron-browser/issue/issueReporterModel.ts b/src/vs/code/electron-browser/issue/issueReporterModel.ts index 43d581ea6a3..e6a7a1aa1f0 100644 --- a/src/vs/code/electron-browser/issue/issueReporterModel.ts +++ b/src/vs/code/electron-browser/issue/issueReporterModel.ts @@ -142,7 +142,11 @@ ${this.getInfos()} `; Object.keys(this._data.systemInfo).forEach(k => { - md += `|${k}|${this._data.systemInfo[k]}|\n`; + const data = typeof this._data.systemInfo[k] === 'object' + ? Object.keys(this._data.systemInfo[k]).map(key => `${key}: ${this._data.systemInfo[k][key]}`).join('
') + : this._data.systemInfo[k]; + + md += `|${k}|${data}|\n`; }); md += '\n'; diff --git a/src/vs/code/electron-browser/issue/test/testReporterModel.test.ts b/src/vs/code/electron-browser/issue/test/testReporterModel.test.ts index 34c7c07e8a5..c08ab806bf3 100644 --- a/src/vs/code/electron-browser/issue/test/testReporterModel.test.ts +++ b/src/vs/code/electron-browser/issue/test/testReporterModel.test.ts @@ -35,6 +35,36 @@ VS Code version: undefined OS version: undefined +`); + }); + + test('serializes GPU information when data is provided', () => { + const issueReporterModel = new IssueReporterModel({ + issueType: 0, + systemInfo: { + 'GPU Status': { + '2d_canvas': 'enabled', + 'checker_imaging': 'disabled_off' + } + } + }); + assert.equal(issueReporterModel.serialize(), + ` +Issue Type: Bug + +undefined + +VS Code version: undefined +OS version: undefined + +
+System Info + +|Item|Value| +|---|---| +|GPU Status|2d_canvas: enabled
checker_imaging: disabled_off| + +
Extensions: none `); }); diff --git a/src/vs/code/electron-main/diagnostics.ts b/src/vs/code/electron-main/diagnostics.ts index 8de2d49423c..c2d5a003f9f 100644 --- a/src/vs/code/electron-main/diagnostics.ts +++ b/src/vs/code/electron-main/diagnostics.ts @@ -29,6 +29,7 @@ export interface SystemInfo { VM: string; 'Screen Reader': string; 'Process Argv': string; + 'GPU Status': Electron.GPUFeatureStatus; } export interface ProcessInfo { @@ -92,7 +93,8 @@ export function getSystemInfo(info: IMainProcessInfo): SystemInfo { 'Memory (System)': `${(os.totalmem() / GB).toFixed(2)}GB (${(os.freemem() / GB).toFixed(2)}GB free)`, VM: `${Math.round((virtualMachineHint.value() * 100))}%`, 'Screen Reader': `${app.isAccessibilitySupportEnabled() ? 'yes' : 'no'}`, - 'Process Argv': `${info.mainArguments.join(' ')}` + 'Process Argv': `${info.mainArguments.join(' ')}`, + 'GPU Status': app.getGPUFeatureStatus() }; const cpus = os.cpus(); @@ -208,7 +210,14 @@ function formatLaunchConfigs(configs: WorkspaceStatItem[]): string { return output.join('\n'); } -function formatEnvironment(info: IMainProcessInfo): string { +function expandGPUFeatures(): string { + const gpuFeatures = app.getGPUFeatureStatus(); + const longestFeatureName = Math.max(...Object.keys(gpuFeatures).map(feature => feature.length)); + // Make columns aligned by adding spaces after feature name + return Object.keys(gpuFeatures).map(feature => `${feature}: ${repeat(' ', longestFeatureName - feature.length)} ${gpuFeatures[feature]}`).join('\n '); +} + +export function formatEnvironment(info: IMainProcessInfo): string { const MB = 1024 * 1024; const GB = 1024 * MB; @@ -226,6 +235,7 @@ function formatEnvironment(info: IMainProcessInfo): string { output.push(`VM: ${Math.round((virtualMachineHint.value() * 100))}%`); output.push(`Screen Reader: ${app.isAccessibilitySupportEnabled() ? 'yes' : 'no'}`); output.push(`Process Argv: ${info.mainArguments.join(' ')}`); + output.push(`GPU Status: ${expandGPUFeatures()}`); return output.join('\n'); } From 244c39c94ef440168e0a565f9a82b1e6f47cd182 Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Tue, 3 Apr 2018 11:09:49 -0700 Subject: [PATCH 06/49] Fix #47155 --- src/vs/workbench/parts/search/browser/searchResultsView.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/parts/search/browser/searchResultsView.ts b/src/vs/workbench/parts/search/browser/searchResultsView.ts index 1d78469272a..1577ee03f22 100644 --- a/src/vs/workbench/parts/search/browser/searchResultsView.ts +++ b/src/vs/workbench/parts/search/browser/searchResultsView.ts @@ -344,10 +344,12 @@ export class SearchAccessibilityProvider implements IAccessibilityProvider { const replace = searchModel.isReplaceActive() && !!searchModel.replaceString; const matchString = match.getMatchString(); const range = match.range(); + const matchText = match.text().substr(0, range.endColumn + 150); if (replace) { - return nls.localize('replacePreviewResultAria', "Replace term {0} with {1} at column position {2} in line with text {3}", matchString, match.replaceString, range.startColumn + 1, match.text()); + return nls.localize('replacePreviewResultAria', "Replace term {0} with {1} at column position {2} in line with text {3}", matchString, match.replaceString, range.startColumn + 1, matchText); } - return nls.localize('searchResultAria', "Found term {0} at column position {1} in line with text {2}", matchString, range.startColumn + 1, match.text()); + + return nls.localize('searchResultAria', "Found term {0} at column position {1} in line with text {2}", matchString, range.startColumn + 1, matchText); } return undefined; } From cf87ba36c0d62c3a9d77da5f96a5c24c9d87a122 Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Tue, 3 Apr 2018 11:35:29 -0700 Subject: [PATCH 07/49] Fix #47157 - Focus include/exclude box when toggling open --- src/vs/workbench/parts/search/browser/searchView.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/parts/search/browser/searchView.ts b/src/vs/workbench/parts/search/browser/searchView.ts index 1626a1441f3..67002a97b86 100644 --- a/src/vs/workbench/parts/search/browser/searchView.ts +++ b/src/vs/workbench/parts/search/browser/searchView.ts @@ -191,13 +191,13 @@ export class SearchView extends Viewlet implements IViewlet, IPanel { builder.div({ 'class': 'more', 'tabindex': 0, 'role': 'button', 'title': nls.localize('moreSearch', "Toggle Search Details") }) .on(dom.EventType.CLICK, (e) => { dom.EventHelper.stop(e); - this.toggleQueryDetails(true); + this.toggleQueryDetails(); }).on(dom.EventType.KEY_UP, (e: KeyboardEvent) => { let event = new StandardKeyboardEvent(e); if (event.equals(KeyCode.Enter) || event.equals(KeyCode.Space)) { dom.EventHelper.stop(e); - this.toggleQueryDetails(); + this.toggleQueryDetails(false); } }); @@ -879,7 +879,7 @@ export class SearchView extends Viewlet implements IViewlet, IPanel { this.onQueryChanged(true, true); } - public toggleQueryDetails(moveFocus?: boolean, show?: boolean, skipLayout?: boolean): void { + public toggleQueryDetails(moveFocus = true, show?: boolean, skipLayout?: boolean): void { let cls = 'more'; show = typeof show === 'undefined' ? !dom.hasClass(this.queryDetails, cls) : Boolean(show); this.viewletSettings['query.queryDetailsExpanded'] = show; From a01215580c5cafb215c2cb1669bf7a616a92eab6 Mon Sep 17 00:00:00 2001 From: Rachel Macfarlane Date: Tue, 3 Apr 2018 11:48:49 -0700 Subject: [PATCH 08/49] Show notification when running with extensions disabled, fixes #46817 --- .../services/extensions/electron-browser/extensionService.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/vs/workbench/services/extensions/electron-browser/extensionService.ts b/src/vs/workbench/services/extensions/electron-browser/extensionService.ts index 0423ebbecc4..b4ec2e94433 100644 --- a/src/vs/workbench/services/extensions/electron-browser/extensionService.ts +++ b/src/vs/workbench/services/extensions/electron-browser/extensionService.ts @@ -299,6 +299,10 @@ export class ExtensionService extends Disposable implements IExtensionService { this._extensionHostProcessManager = null; this.startDelayed(lifecycleService); + + if (this._environmentService.disableExtensions) { + this._notificationService.info(nls.localize('extensionsDisabled', "All extensions are disabled.")); + } } private startDelayed(lifecycleService: ILifecycleService): void { From 21ec23c075a563775789597b1c8e6378e2c20c02 Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Tue, 3 Apr 2018 22:02:59 +0200 Subject: [PATCH 09/49] [css] enable strict on server --- .../server/src/cssServerMain.ts | 49 +++++++++---------- .../server/src/test/completion.test.ts | 2 +- .../server/src/test/emmet.test.ts | 10 ++-- .../server/src/utils/errors.ts | 33 ------------- .../server/src/utils/runner.ts | 47 ++++++++++++++++++ .../server/tsconfig.json | 3 +- 6 files changed, 79 insertions(+), 65 deletions(-) delete mode 100644 extensions/css-language-features/server/src/utils/errors.ts create mode 100644 extensions/css-language-features/server/src/utils/runner.ts diff --git a/extensions/css-language-features/server/src/cssServerMain.ts b/extensions/css-language-features/server/src/cssServerMain.ts index 75223f91b78..aba96f5ff6a 100644 --- a/extensions/css-language-features/server/src/cssServerMain.ts +++ b/extensions/css-language-features/server/src/cssServerMain.ts @@ -5,15 +5,14 @@ 'use strict'; import { - createConnection, IConnection, TextDocuments, InitializeParams, InitializeResult, ServerCapabilities, - ConfigurationRequest, WorkspaceFolder, DocumentColorRequest, ColorPresentationRequest + createConnection, IConnection, TextDocuments, InitializeParams, InitializeResult, ServerCapabilities, ConfigurationRequest, WorkspaceFolder } from 'vscode-languageserver'; import { TextDocument, CompletionList } from 'vscode-languageserver-types'; import { getCSSLanguageService, getSCSSLanguageService, getLESSLanguageService, LanguageSettings, LanguageService, Stylesheet } from 'vscode-css-languageservice'; import { getLanguageModelCache } from './languageModelCache'; -import { formatError, runSafe } from './utils/errors'; +import { formatError, runSafe } from './utils/runner'; import URI from 'vscode-uri'; import { getPathCompletionParticipant } from './pathCompletion'; import { FoldingProviderServerCapabilities, FoldingRangesRequest } from 'vscode-languageserver-protocol-foldingprovider'; @@ -182,7 +181,7 @@ function validateTextDocument(textDocument: TextDocument): void { }); } -connection.onCompletion(textDocumentPosition => { +connection.onCompletion((textDocumentPosition, token) => { return runSafe(() => { let document = documents.get(textDocumentPosition.textDocument.uri); const cssLS = getLanguageService(document); @@ -196,58 +195,58 @@ connection.onCompletion(textDocumentPosition => { isIncomplete: result.isIncomplete, items: [...pathCompletionList.items, ...result.items] }; - }, null, `Error while computing completions for ${textDocumentPosition.textDocument.uri}`); + }, null, `Error while computing completions for ${textDocumentPosition.textDocument.uri}`, token); }); -connection.onHover(textDocumentPosition => { +connection.onHover((textDocumentPosition, token) => { return runSafe(() => { let document = documents.get(textDocumentPosition.textDocument.uri); let styleSheet = stylesheets.get(document); - return getLanguageService(document).doHover(document, textDocumentPosition.position, styleSheet)!; /* TODO: remove ! once LS has null annotations */ - }, null, `Error while computing hover for ${textDocumentPosition.textDocument.uri}`); + return getLanguageService(document).doHover(document, textDocumentPosition.position, styleSheet); + }, null, `Error while computing hover for ${textDocumentPosition.textDocument.uri}`, token); }); -connection.onDocumentSymbol(documentSymbolParams => { +connection.onDocumentSymbol((documentSymbolParams, token) => { return runSafe(() => { let document = documents.get(documentSymbolParams.textDocument.uri); let stylesheet = stylesheets.get(document); return getLanguageService(document).findDocumentSymbols(document, stylesheet); - }, [], `Error while computing document symbols for ${documentSymbolParams.textDocument.uri}`); + }, [], `Error while computing document symbols for ${documentSymbolParams.textDocument.uri}`, token); }); -connection.onDefinition(documentSymbolParams => { +connection.onDefinition((documentSymbolParams, token) => { return runSafe(() => { let document = documents.get(documentSymbolParams.textDocument.uri); let stylesheet = stylesheets.get(document); return getLanguageService(document).findDefinition(document, documentSymbolParams.position, stylesheet); - }, null, `Error while computing definitions for ${documentSymbolParams.textDocument.uri}`); + }, null, `Error while computing definitions for ${documentSymbolParams.textDocument.uri}`, token); }); -connection.onDocumentHighlight(documentSymbolParams => { +connection.onDocumentHighlight((documentSymbolParams, token) => { return runSafe(() => { let document = documents.get(documentSymbolParams.textDocument.uri); let stylesheet = stylesheets.get(document); return getLanguageService(document).findDocumentHighlights(document, documentSymbolParams.position, stylesheet); - }, [], `Error while computing document highlights for ${documentSymbolParams.textDocument.uri}`); + }, [], `Error while computing document highlights for ${documentSymbolParams.textDocument.uri}`, token); }); -connection.onReferences(referenceParams => { +connection.onReferences((referenceParams, token) => { return runSafe(() => { let document = documents.get(referenceParams.textDocument.uri); let stylesheet = stylesheets.get(document); return getLanguageService(document).findReferences(document, referenceParams.position, stylesheet); - }, [], `Error while computing references for ${referenceParams.textDocument.uri}`); + }, [], `Error while computing references for ${referenceParams.textDocument.uri}`, token); }); -connection.onCodeAction(codeActionParams => { +connection.onCodeAction((codeActionParams, token) => { return runSafe(() => { let document = documents.get(codeActionParams.textDocument.uri); let stylesheet = stylesheets.get(document); return getLanguageService(document).doCodeActions(document, codeActionParams.range, codeActionParams.context, stylesheet); - }, [], `Error while computing code actions for ${codeActionParams.textDocument.uri}`); + }, [], `Error while computing code actions for ${codeActionParams.textDocument.uri}`, token); }); -connection.onRequest(DocumentColorRequest.type, params => { +connection.onDocumentColor((params, token) => { return runSafe(() => { let document = documents.get(params.textDocument.uri); if (document) { @@ -255,10 +254,10 @@ connection.onRequest(DocumentColorRequest.type, params => { return getLanguageService(document).findDocumentColors(document, stylesheet); } return []; - }, [], `Error while computing document colors for ${params.textDocument.uri}`); + }, [], `Error while computing document colors for ${params.textDocument.uri}`, token); }); -connection.onRequest(ColorPresentationRequest.type, params => { +connection.onColorPresentation((params, token) => { return runSafe(() => { let document = documents.get(params.textDocument.uri); if (document) { @@ -266,15 +265,15 @@ connection.onRequest(ColorPresentationRequest.type, params => { return getLanguageService(document).getColorPresentations(document, stylesheet, params.color, params.range); } return []; - }, [], `Error while computing color presentations for ${params.textDocument.uri}`); + }, [], `Error while computing color presentations for ${params.textDocument.uri}`, token); }); -connection.onRenameRequest(renameParameters => { +connection.onRenameRequest((renameParameters, token) => { return runSafe(() => { let document = documents.get(renameParameters.textDocument.uri); let stylesheet = stylesheets.get(document); return getLanguageService(document).doRename(document, renameParameters.position, renameParameters.newName, stylesheet); - }, null, `Error while computing renames for ${renameParameters.textDocument.uri}`); + }, null, `Error while computing renames for ${renameParameters.textDocument.uri}`, token); }); connection.onRequest(FoldingRangesRequest.type, (params, token) => { @@ -282,7 +281,7 @@ connection.onRequest(FoldingRangesRequest.type, (params, token) => { let document = documents.get(params.textDocument.uri); let stylesheet = stylesheets.get(document); return getLanguageService(document).findFoldingRegions(document, stylesheet); - }, null, `Error while computing folding ranges for ${params.textDocument.uri}`); + }, null, `Error while computing folding ranges for ${params.textDocument.uri}`, token); }); // Listen on the connection diff --git a/extensions/css-language-features/server/src/test/completion.test.ts b/extensions/css-language-features/server/src/test/completion.test.ts index 86f57ca523d..23fc5e0e3b1 100644 --- a/extensions/css-language-features/server/src/test/completion.test.ts +++ b/extensions/css-language-features/server/src/test/completion.test.ts @@ -48,7 +48,7 @@ suite('Completions', () => { cssLanguageService.setCompletionParticipants([getPathCompletionParticipant(document, workspaceFolders, participantResult)]); const stylesheet = cssLanguageService.parseStylesheet(document); - let list = cssLanguageService.doComplete!(document, position, stylesheet); + let list = cssLanguageService.doComplete(document, position, stylesheet)!; list.items = list.items.concat(participantResult.items); if (expected.count) { diff --git a/extensions/css-language-features/server/src/test/emmet.test.ts b/extensions/css-language-features/server/src/test/emmet.test.ts index 7c850fe2697..e7fe76d7be3 100644 --- a/extensions/css-language-features/server/src/test/emmet.test.ts +++ b/extensions/css-language-features/server/src/test/emmet.test.ts @@ -15,7 +15,7 @@ suite('CSS Emmet Support', () => { const cssLanguageService = getCSSLanguageService(); const scssLanguageService = getSCSSLanguageService(); - function assertCompletions(syntax: string, value: string, expectedProposal: string, expectedProposalDoc: string): void { + function assertCompletions(syntax: string, value: string, expectedProposal: string | null, expectedProposalDoc: string | null): void { const offset = value.indexOf('|'); value = value.substr(0, offset) + value.substr(offset + 1); @@ -23,12 +23,12 @@ suite('CSS Emmet Support', () => { const position = document.positionAt(offset); const emmetCompletionList: CompletionList = { isIncomplete: true, - items: undefined + items: [] }; const languageService = syntax === 'scss' ? scssLanguageService : cssLanguageService; languageService.setCompletionParticipants([getEmmetCompletionParticipants(document, position, document.languageId, {}, emmetCompletionList)]); const stylesheet = languageService.parseStylesheet(document); - const list = languageService.doComplete!(document, position, stylesheet); + const list = languageService.doComplete(document, position, stylesheet); assert.ok(list); assert.ok(emmetCompletionList); @@ -43,7 +43,7 @@ suite('CSS Emmet Support', () => { } } - test('Css Emmet Completions', function (): any { + test('Css Emmet Completions', function (this: any): any { this.skip(); // disabled again (see #29113) assertCompletions('css', '.foo { display: none; m10| }', 'margin: 10px;', 'margin: 10px;'); @@ -56,7 +56,7 @@ suite('CSS Emmet Support', () => { assertCompletions('css', '.foo { display: none; -m-m10| }', 'margin: 10px;', '-moz-margin: 10px;\nmargin: 10px;'); }); - test('Scss Emmet Completions', function (): any { + test('Scss Emmet Completions', function (this: any): any { this.skip(); // disabled again (see #29113) assertCompletions('scss', '.foo { display: none; .bar { m10| } }', 'margin: 10px;', 'margin: 10px;'); diff --git a/extensions/css-language-features/server/src/utils/errors.ts b/extensions/css-language-features/server/src/utils/errors.ts deleted file mode 100644 index d5a0c8e7d05..00000000000 --- a/extensions/css-language-features/server/src/utils/errors.ts +++ /dev/null @@ -1,33 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -'use strict'; - -export function formatError(message: string, err: any): string { - if (err instanceof Error) { - let error = err; - return `${message}: ${error.message}\n${error.stack}`; - } else if (typeof err === 'string') { - return `${message}: ${err}`; - } else if (err) { - return `${message}: ${err.toString()}`; - } - return message; -} - -export function runSafe(func: () => Thenable | T, errorVal: T, errorMessage: string): Thenable | T { - try { - let t = func(); - if (t instanceof Promise) { - return t.then(void 0, e => { - console.error(formatError(errorMessage, e)); - return errorVal; - }); - } - return t; - } catch (e) { - console.error(formatError(errorMessage, e)); - return errorVal; - } -} \ No newline at end of file diff --git a/extensions/css-language-features/server/src/utils/runner.ts b/extensions/css-language-features/server/src/utils/runner.ts new file mode 100644 index 00000000000..273194ce431 --- /dev/null +++ b/extensions/css-language-features/server/src/utils/runner.ts @@ -0,0 +1,47 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +'use strict'; + +import { ResponseError, ErrorCodes, CancellationToken } from 'vscode-languageserver'; + +export function formatError(message: string, err: any): string { + if (err instanceof Error) { + let error = err; + return `${message}: ${error.message}\n${error.stack}`; + } else if (typeof err === 'string') { + return `${message}: ${err}`; + } else if (err) { + return `${message}: ${err.toString()}`; + } + return message; +} + +export function runSafe(func: () => T, errorVal: T, errorMessage: string, token: CancellationToken): Thenable> { + return new Promise>((resolve, reject) => { + setImmediate(() => { + if (token.isCancellationRequested) { + resolve(cancelValue()); + } else { + try { + let result = func(); + if (token.isCancellationRequested) { + resolve(cancelValue()); + return; + } else { + resolve(result); + } + + } catch (e) { + console.error(formatError(errorMessage, e)); + resolve(errorVal); + } + } + }); + }); +} + +function cancelValue() { + return new ResponseError(ErrorCodes.RequestCancelled, 'Request cancelled'); +} diff --git a/extensions/css-language-features/server/tsconfig.json b/extensions/css-language-features/server/tsconfig.json index dc12dad6cf0..7f8b647d04b 100644 --- a/extensions/css-language-features/server/tsconfig.json +++ b/extensions/css-language-features/server/tsconfig.json @@ -6,7 +6,8 @@ "noUnusedLocals": true, "lib": [ "es5", "es2015.promise" - ] + ], + "strict": true }, "include": [ "src/**/*" From e8125cf26754ba7354e6bc5bb17fdd8eed9c8b46 Mon Sep 17 00:00:00 2001 From: Rachel Macfarlane Date: Tue, 3 Apr 2018 15:32:07 -0700 Subject: [PATCH 10/49] Fixes #46844 --- src/vs/code/electron-browser/issue/issueReporterMain.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/code/electron-browser/issue/issueReporterMain.ts b/src/vs/code/electron-browser/issue/issueReporterMain.ts index 47f1ca1038f..fe645528c44 100644 --- a/src/vs/code/electron-browser/issue/issueReporterMain.ts +++ b/src/vs/code/electron-browser/issue/issueReporterMain.ts @@ -447,7 +447,7 @@ export class IssueReporter extends Disposable { } private searchVSCodeIssues(title: string, issueDescription: string): void { - if (title || issueDescription) { + if (title) { this.searchDuplicates(title, issueDescription); } else { this.clearSearchResults(); @@ -578,7 +578,7 @@ export class IssueReporter extends Disposable { similarIssues.appendChild(issues); } else { const message = $('div.list-title'); - message.textContent = localize('noResults', "No results found"); + message.textContent = localize('noSimilarIssues', "No similar issues found"); similarIssues.appendChild(message); } } From 2880f8e173b748ebc81efe97fe3b634ea74af336 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andre=CC=81=20Weinand?= Date: Wed, 4 Apr 2018 00:54:49 +0200 Subject: [PATCH 11/49] node-debug@1.23.1 --- build/builtInExtensions.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/builtInExtensions.json b/build/builtInExtensions.json index ccd151d7d3d..4bd1d55b0b7 100644 --- a/build/builtInExtensions.json +++ b/build/builtInExtensions.json @@ -1,7 +1,7 @@ [ { "name": "ms-vscode.node-debug", - "version": "1.22.13", + "version": "1.23.1", "repo": "https://github.com/Microsoft/vscode-node-debug" }, { From de6653a997242ce0baa7d2f59f0a8e5c73d8d5a0 Mon Sep 17 00:00:00 2001 From: Ramya Achutha Rao Date: Tue, 3 Apr 2018 16:40:58 -0700 Subject: [PATCH 12/49] Retain paths starting from node_modules and match escaped backslashes --- .../telemetry/common/telemetryService.ts | 7 +- .../electron-browser/telemetryService.test.ts | 82 ++++++++++++++++--- 2 files changed, 74 insertions(+), 15 deletions(-) diff --git a/src/vs/platform/telemetry/common/telemetryService.ts b/src/vs/platform/telemetry/common/telemetryService.ts index 2f713dd11af..992f3e36a96 100644 --- a/src/vs/platform/telemetry/common/telemetryService.ts +++ b/src/vs/platform/telemetry/common/telemetryService.ts @@ -130,15 +130,16 @@ export class TelemetryService implements ITelemetryService { } } - const fileRegex = /(file:\/\/)?([a-z,A-Z]:)?([\\\/]\w+)+/g; + const nodeModulesRegex = /^[\\\/]?(node_modules|node_modules\.asar)[\\\/]/; + const fileRegex = /(file:\/\/)?([a-zA-Z]:(\\\\|\\|\/)|(\\\\|\\|\/))?([\w-\._]+(\\\\|\\|\/))+[\w-\._]*/g; let updatedStack = stack; while (true) { const result = fileRegex.exec(stack); if (!result) { break; } - // Anoynimize user file paths that do not need cleanup. - if (cleanUpIndexes.every(([x, y]) => result.index < x || result.index >= y)) { + // Anoynimize user file paths that do not need to be retained or cleaned up. + if (!nodeModulesRegex.test(result[0]) && cleanUpIndexes.every(([x, y]) => result.index < x || result.index >= y)) { updatedStack = updatedStack.slice(0, result.index) + result[0].replace(/./g, 'a') + updatedStack.slice(fileRegex.lastIndex); } } diff --git a/src/vs/platform/telemetry/test/electron-browser/telemetryService.test.ts b/src/vs/platform/telemetry/test/electron-browser/telemetryService.test.ts index d3062bc7ff2..39fe577f0e5 100644 --- a/src/vs/platform/telemetry/test/electron-browser/telemetryService.test.ts +++ b/src/vs/platform/telemetry/test/electron-browser/telemetryService.test.ts @@ -50,8 +50,10 @@ class ErrorTestingSettings { public noSuchFilePrefix: string; public noSuchFileMessage: string; public stack: string[]; - public randomUserFile: string = 'a/path/that/doesnt/contain/code/names'; - public anonymizedRandomUserFile: string = 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'; + public randomUserFile: string = 'a/path/that/doe_snt/con-tain/code/names.js'; + public anonymizedRandomUserFile: string = 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'; + public nodeModulePathToRetain: string = 'node_modules/path/that/shouldbe/retained/names.js:14:15854'; + public nodeModuleAsarPathToRetain: string = 'node_modules.asar/path/that/shouldbe/retained/names.js:14:12354'; constructor() { this.personalInfo = 'DANGEROUS/PATH'; @@ -66,16 +68,16 @@ class ErrorTestingSettings { this.noSuchFilePrefix = 'ENOENT: no such file or directory'; this.noSuchFileMessage = this.noSuchFilePrefix + ' \'' + this.personalInfo + '\''; - this.stack = [`at e._modelEvents (${this.randomUserFile}.js:11:7309)`, - ` at t.AllWorkers (${this.randomUserFile}.js:6:8844)`, - ` at e.(anonymous function) [as _modelEvents] (${this.randomUserFile}.js:5:29552)`, - ` at Function. (${this.randomUserFile}.js:6:8272)`, - ` at e.dispatch (${this.randomUserFile}.js:5:26931)`, - ` at e.request (${this.randomUserFile}.js:14:1745)`, - ' at t._handleMessage (another/path/that/doesnt/contain/code/names.js:14:17447)', - ' at t._onmessage (another/path/that/doesnt/contain/code/names.js:14:16976)', - ' at t.onmessage (another/path/that/doesnt/contain/code/names.js:14:15854)', - ' at DedicatedWorkerGlobalScope.self.onmessage', + this.stack = [`at e._modelEvents (${this.randomUserFile}:11:7309)`, + ` at t.AllWorkers (${this.randomUserFile}:6:8844)`, + ` at e.(anonymous function) [as _modelEvents] (${this.randomUserFile}:5:29552)`, + ` at Function. (${this.randomUserFile}:6:8272)`, + ` at e.dispatch (${this.randomUserFile}:5:26931)`, + ` at e.request (/${this.nodeModuleAsarPathToRetain})`, + ` at t._handleMessage (${this.nodeModuleAsarPathToRetain})`, + ` at t._onmessage (/${this.nodeModulePathToRetain})`, + ` at t.onmessage (${this.nodeModulePathToRetain})`, + ` at DedicatedWorkerGlobalScope.self.onmessage`, this.dangerousPathWithImportantInfo, this.dangerousPathWithoutImportantInfo, this.missingModelMessage, @@ -461,6 +463,62 @@ suite('TelemetryService', () => { service.dispose(); })); + test('Unexpected Error Telemetry removes PII but preserves Code file path with node modules', sinon.test(function (this: any) { + + let origErrorHandler = Errors.errorHandler.getUnexpectedErrorHandler(); + Errors.setUnexpectedErrorHandler(() => { }); + + try { + let settings = new ErrorTestingSettings(); + let testAppender = new TestTelemetryAppender(); + let service = new TelemetryService({ appender: testAppender }, undefined); + const errorTelemetry = new ErrorTelemetry(service); + + let dangerousPathWithImportantInfoError: any = new Error(settings.dangerousPathWithImportantInfo); + dangerousPathWithImportantInfoError.stack = settings.stack; + + + Errors.onUnexpectedError(dangerousPathWithImportantInfoError); + this.clock.tick(ErrorTelemetry.ERROR_FLUSH_TIMEOUT); + + assert.notEqual(testAppender.events[0].data.stack.indexOf('(' + settings.nodeModuleAsarPathToRetain), -1); + assert.notEqual(testAppender.events[0].data.stack.indexOf('(' + settings.nodeModulePathToRetain), -1); + assert.notEqual(testAppender.events[0].data.stack.indexOf('(/' + settings.nodeModuleAsarPathToRetain), -1); + assert.notEqual(testAppender.events[0].data.stack.indexOf('(/' + settings.nodeModulePathToRetain), -1); + + errorTelemetry.dispose(); + service.dispose(); + } + finally { + Errors.setUnexpectedErrorHandler(origErrorHandler); + } + })); + + test('Uncaught Error Telemetry removes PII but preserves Code file path', sinon.test(function (this: any) { + let errorStub = sinon.stub(); + window.onerror = errorStub; + let settings = new ErrorTestingSettings(); + let testAppender = new TestTelemetryAppender(); + let service = new TelemetryService({ appender: testAppender }, undefined); + const errorTelemetry = new ErrorTelemetry(service); + + let dangerousPathWithImportantInfoError: any = new Error('dangerousPathWithImportantInfo'); + dangerousPathWithImportantInfoError.stack = settings.stack; + (window.onerror)(settings.dangerousPathWithImportantInfo, 'test.js', 2, 42, dangerousPathWithImportantInfoError); + this.clock.tick(ErrorTelemetry.ERROR_FLUSH_TIMEOUT); + + assert.equal(errorStub.callCount, 1); + + assert.notEqual(testAppender.events[0].data.stack.indexOf('(' + settings.nodeModuleAsarPathToRetain), -1); + assert.notEqual(testAppender.events[0].data.stack.indexOf('(' + settings.nodeModulePathToRetain), -1); + assert.notEqual(testAppender.events[0].data.stack.indexOf('(/' + settings.nodeModuleAsarPathToRetain), -1); + assert.notEqual(testAppender.events[0].data.stack.indexOf('(/' + settings.nodeModulePathToRetain), -1); + + errorTelemetry.dispose(); + service.dispose(); + })); + + test('Unexpected Error Telemetry removes PII but preserves Code file path when PIIPath is configured', sinon.test(function (this: any) { let origErrorHandler = Errors.errorHandler.getUnexpectedErrorHandler(); From 1dcb420e69d5c1cefc003045e80d6b7ca6610f2c Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Tue, 3 Apr 2018 15:10:37 -0700 Subject: [PATCH 13/49] #8594 - implement copy path context menu item --- .../parts/search/browser/searchActions.ts | 12 +++++++++ .../parts/search/browser/searchResultsView.ts | 4 ++- .../parts/search/common/constants.ts | 2 ++ .../electron-browser/search.contribution.ts | 27 ++++++++++++++++--- 4 files changed, 41 insertions(+), 4 deletions(-) diff --git a/src/vs/workbench/parts/search/browser/searchActions.ts b/src/vs/workbench/parts/search/browser/searchActions.ts index 868a64b10a7..94a6efb8886 100644 --- a/src/vs/workbench/parts/search/browser/searchActions.ts +++ b/src/vs/workbench/parts/search/browser/searchActions.ts @@ -22,6 +22,10 @@ import { OS } from 'vs/base/common/platform'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; import { VIEW_ID } from 'vs/platform/search/common/search'; +import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; +import { ICommandHandler } from 'vs/platform/commands/common/commands'; +import { Schemas } from 'vs/base/common/network'; +import { getPathLabel } from 'vs/base/common/labels'; export function isSearchViewFocused(viewletService: IViewletService, panelService: IPanelService): boolean { let searchView = getSearchView(viewletService, panelService); @@ -630,3 +634,11 @@ export class ReplaceAction extends AbstractSearchAndReplaceAction { return false; } } + +export const copyPathCommand: ICommandHandler = (accessor, fileMatch: FileMatch) => { + const clipboardService = accessor.get(IClipboardService); + + const resource = fileMatch.resource(); + const text = resource.scheme === Schemas.file ? getPathLabel(resource) : resource.toString(); + clipboardService.writeText(text); +}; diff --git a/src/vs/workbench/parts/search/browser/searchResultsView.ts b/src/vs/workbench/parts/search/browser/searchResultsView.ts index 1577ee03f22..4515d7d943d 100644 --- a/src/vs/workbench/parts/search/browser/searchResultsView.ts +++ b/src/vs/workbench/parts/search/browser/searchResultsView.ts @@ -388,7 +388,9 @@ export class SearchTreeController extends WorkbenchTreeController { const actions: IAction[] = []; fillInActions(this.contextMenu, { shouldForwardArgs: true }, actions, this.contextMenuService); return TPromise.as(actions); - } + }, + + getActionsContext: () => element }); return true; diff --git a/src/vs/workbench/parts/search/common/constants.ts b/src/vs/workbench/parts/search/common/constants.ts index fe9c22e0725..a2e93246ec6 100644 --- a/src/vs/workbench/parts/search/common/constants.ts +++ b/src/vs/workbench/parts/search/common/constants.ts @@ -12,6 +12,8 @@ export const FocusSearchFromResults = 'search.action.focusSearchFromResults'; export const OpenMatchToSide = 'search.action.openResultToSide'; export const CancelActionId = 'search.action.cancel'; export const RemoveActionId = 'search.action.remove'; +export const CopyPathCommandId = 'search.action.copyPath'; +export const CopyMatchCommandId = 'search.action.copyMatch'; export const ReplaceActionId = 'search.action.replace'; export const ReplaceAllInFileActionId = 'search.action.replaceAllInFile'; export const ReplaceAllInFolderActionId = 'search.action.replaceAllInFolder'; 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 e26158e086f..1715c84ce87 100644 --- a/src/vs/workbench/parts/search/electron-browser/search.contribution.ts +++ b/src/vs/workbench/parts/search/electron-browser/search.contribution.ts @@ -53,12 +53,12 @@ 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 } 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 } 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'; import { SearchViewLocationUpdater } from 'vs/workbench/parts/search/browser/searchViewLocationUpdater'; -import { IConfigurationService } from '../../../../platform/configuration/common/configuration'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; registerSingleton(ISearchWorkbenchService, SearchWorkbenchService); replaceContributions(); @@ -235,6 +235,27 @@ MenuRegistry.appendMenuItem(MenuId.SearchContext, { order: 2 }); +KeybindingsRegistry.registerCommandAndKeybindingRule({ + id: Constants.CopyPathCommandId, + weight: KeybindingsRegistry.WEIGHT.workbenchContrib(), + when: Constants.FileFocusKey, + primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.KEY_C, + win: { + primary: KeyMod.Shift | KeyMod.Alt | KeyCode.KEY_C + }, + handler: copyPathCommand +}); + +MenuRegistry.appendMenuItem(MenuId.SearchContext, { + command: { + id: Constants.CopyPathCommandId, + title: nls.localize('copyPathLabel', "Copy Path") + }, + when: Constants.FileFocusKey, + group: 'search', + order: 3 +}); + CommandsRegistry.registerCommand({ id: Constants.ToggleSearchViewPositionCommandId, handler: (accessor) => { @@ -254,7 +275,7 @@ MenuRegistry.appendMenuItem(MenuId.SearchContext, { }, when: Constants.SearchViewVisibleKey, group: 'search_2', - order: 3 + order: 1 }); const FIND_IN_FOLDER_ID = 'filesExplorer.findInFolder'; From c8a6ce937b6efd31343c03f3e8eb48f9a005c599 Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Tue, 3 Apr 2018 16:45:21 -0700 Subject: [PATCH 14/49] Fix #42120 - Implement Copy --- .../parts/search/browser/searchActions.ts | 58 ++++++++++++++++++- .../electron-browser/search.contribution.ts | 26 +++++++-- 2 files changed, 77 insertions(+), 7 deletions(-) diff --git a/src/vs/workbench/parts/search/browser/searchActions.ts b/src/vs/workbench/parts/search/browser/searchActions.ts index 94a6efb8886..8d421e4391b 100644 --- a/src/vs/workbench/parts/search/browser/searchActions.ts +++ b/src/vs/workbench/parts/search/browser/searchActions.ts @@ -18,7 +18,7 @@ import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/edi import { ResolvedKeybinding, createKeybinding } from 'vs/base/common/keyCodes'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; -import { OS } from 'vs/base/common/platform'; +import { OS, isWindows } from 'vs/base/common/platform'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; import { VIEW_ID } from 'vs/platform/search/common/search'; @@ -635,10 +635,62 @@ export class ReplaceAction extends AbstractSearchAndReplaceAction { } } +function fileMatchUriToString(fileMatch: FileMatch): string { + const resource = fileMatch.resource(); + return resource.scheme === Schemas.file ? getPathLabel(resource) : resource.toString(); +} + export const copyPathCommand: ICommandHandler = (accessor, fileMatch: FileMatch) => { const clipboardService = accessor.get(IClipboardService); - const resource = fileMatch.resource(); - const text = resource.scheme === Schemas.file ? getPathLabel(resource) : resource.toString(); + const text = fileMatchUriToString(fileMatch); clipboardService.writeText(text); }; + +function matchToString(match: Match): string { + return `${match.range().startLineNumber},${match.range().startColumn}: ${match.text()}`; +} + +const lineDelimiter = isWindows ? '\r\n' : '\n'; +function fileMatchToString(fileMatch: FileMatch, maxMatches: number): { text: string, count: number } { + const matchTextRows = fileMatch.matches() + .slice(0, maxMatches) + .map(matchToString) + .map(matchText => ' ' + matchText); + + return { + text: `${fileMatchUriToString(fileMatch)}${lineDelimiter}${matchTextRows.join(lineDelimiter)}`, + count: matchTextRows.length + }; +} + +function folderMatchToString(folderMatch: FolderMatch, maxMatches: number): string { + const fileResults: string[] = []; + let numMatches = 0; + + for (let i = 0; i < folderMatch.fileCount() && numMatches < maxMatches; i++) { + const fileResult = fileMatchToString(folderMatch.matches()[i], maxMatches - numMatches); + numMatches += fileResult.count; + fileResults.push(fileResult.text); + } + + return fileResults.join(lineDelimiter + lineDelimiter); +} + +const maxClipboardMatches = 1e4; +export const copyMatchCommand: ICommandHandler = (accessor, match: RenderableMatch) => { + const clipboardService = accessor.get(IClipboardService); + + let text: string; + if (match instanceof Match) { + text = matchToString(match); + } else if (match instanceof FileMatch) { + text = fileMatchToString(match, maxClipboardMatches).text; + } else if (match instanceof FolderMatch) { + text = folderMatchToString(match, maxClipboardMatches); + } + + if (text) { + clipboardService.writeText(text); + } +}; 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 1715c84ce87..8443056e9c3 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 } 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 } 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'; @@ -235,6 +235,24 @@ MenuRegistry.appendMenuItem(MenuId.SearchContext, { order: 2 }); +KeybindingsRegistry.registerCommandAndKeybindingRule({ + id: Constants.CopyMatchCommandId, + weight: KeybindingsRegistry.WEIGHT.workbenchContrib(), + when: Constants.FileMatchOrMatchFocusKey, + primary: KeyMod.CtrlCmd | KeyCode.KEY_C, + handler: copyMatchCommand +}); + +MenuRegistry.appendMenuItem(MenuId.SearchContext, { + command: { + id: Constants.CopyMatchCommandId, + title: nls.localize('copyMatchLabel', "Copy") + }, + when: Constants.FileMatchOrMatchFocusKey, + group: 'search_2', + order: 3 +}); + KeybindingsRegistry.registerCommandAndKeybindingRule({ id: Constants.CopyPathCommandId, weight: KeybindingsRegistry.WEIGHT.workbenchContrib(), @@ -252,8 +270,8 @@ MenuRegistry.appendMenuItem(MenuId.SearchContext, { title: nls.localize('copyPathLabel', "Copy Path") }, when: Constants.FileFocusKey, - group: 'search', - order: 3 + group: 'search_2', + order: 4 }); CommandsRegistry.registerCommand({ @@ -274,7 +292,7 @@ MenuRegistry.appendMenuItem(MenuId.SearchContext, { title: toggleSearchViewPositionLabel }, when: Constants.SearchViewVisibleKey, - group: 'search_2', + group: 'search_9', order: 1 }); From e9262fb5a888d621c88e97e5e7f4f5d4a7bda5c9 Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Tue, 3 Apr 2018 17:27:28 -0700 Subject: [PATCH 15/49] #8594 - Fix Copy Path on folder nodes --- src/vs/workbench/parts/search/browser/searchActions.ts | 10 +++++----- src/vs/workbench/parts/search/browser/searchView.ts | 6 ++++-- src/vs/workbench/parts/search/common/constants.ts | 3 ++- .../search/electron-browser/search.contribution.ts | 4 ++-- 4 files changed, 13 insertions(+), 10 deletions(-) diff --git a/src/vs/workbench/parts/search/browser/searchActions.ts b/src/vs/workbench/parts/search/browser/searchActions.ts index 8d421e4391b..29af3475e47 100644 --- a/src/vs/workbench/parts/search/browser/searchActions.ts +++ b/src/vs/workbench/parts/search/browser/searchActions.ts @@ -26,6 +26,7 @@ import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService import { ICommandHandler } from 'vs/platform/commands/common/commands'; import { Schemas } from 'vs/base/common/network'; import { getPathLabel } from 'vs/base/common/labels'; +import URI from 'vs/base/common/uri'; export function isSearchViewFocused(viewletService: IViewletService, panelService: IPanelService): boolean { let searchView = getSearchView(viewletService, panelService); @@ -635,15 +636,14 @@ export class ReplaceAction extends AbstractSearchAndReplaceAction { } } -function fileMatchUriToString(fileMatch: FileMatch): string { - const resource = fileMatch.resource(); +function uriToClipboardString(resource: URI): string { return resource.scheme === Schemas.file ? getPathLabel(resource) : resource.toString(); } -export const copyPathCommand: ICommandHandler = (accessor, fileMatch: FileMatch) => { +export const copyPathCommand: ICommandHandler = (accessor, fileMatch: FileMatch | FolderMatch) => { const clipboardService = accessor.get(IClipboardService); - const text = fileMatchUriToString(fileMatch); + const text = uriToClipboardString(fileMatch.resource()); clipboardService.writeText(text); }; @@ -659,7 +659,7 @@ function fileMatchToString(fileMatch: FileMatch, maxMatches: number): { text: st .map(matchText => ' ' + matchText); return { - text: `${fileMatchUriToString(fileMatch)}${lineDelimiter}${matchTextRows.join(lineDelimiter)}`, + text: `${uriToClipboardString(fileMatch.resource())}${lineDelimiter}${matchTextRows.join(lineDelimiter)}`, count: matchTextRows.length }; } diff --git a/src/vs/workbench/parts/search/browser/searchView.ts b/src/vs/workbench/parts/search/browser/searchView.ts index 67002a97b86..b216b8bb235 100644 --- a/src/vs/workbench/parts/search/browser/searchView.ts +++ b/src/vs/workbench/parts/search/browser/searchView.ts @@ -82,6 +82,7 @@ export class SearchView extends Viewlet implements IViewlet, IPanel { private inputPatternIncludesFocused: IContextKey; private firstMatchFocused: IContextKey; private fileMatchOrMatchFocused: IContextKey; + private fileMatchOrFolderMatchFocus: IContextKey; private fileMatchFocused: IContextKey; private folderMatchFocused: IContextKey; private matchFocused: IContextKey; @@ -137,6 +138,7 @@ export class SearchView extends Viewlet implements IViewlet, IPanel { this.inputPatternIncludesFocused = Constants.PatternIncludesFocusedKey.bindTo(this.contextKeyService); this.firstMatchFocused = Constants.FirstMatchFocusKey.bindTo(contextKeyService); this.fileMatchOrMatchFocused = Constants.FileMatchOrMatchFocusKey.bindTo(contextKeyService); + this.fileMatchOrFolderMatchFocus = Constants.FileMatchOrFolderMatchFocusKey.bindTo(contextKeyService); this.fileMatchFocused = Constants.FileFocusKey.bindTo(contextKeyService); this.folderMatchFocused = Constants.FolderFocusKey.bindTo(contextKeyService); this.matchFocused = Constants.MatchFocusKey.bindTo(this.contextKeyService); @@ -535,6 +537,7 @@ export class SearchView extends Viewlet implements IViewlet, IPanel { this.fileMatchFocused.set(focus instanceof FileMatch); this.folderMatchFocused.set(focus instanceof FolderMatch); this.matchFocused.set(focus instanceof Match); + this.fileMatchOrFolderMatchFocus.set(focus instanceof FileMatch || focus instanceof FolderMatch); } })); @@ -545,9 +548,8 @@ export class SearchView extends Viewlet implements IViewlet, IPanel { this.fileMatchFocused.reset(); this.folderMatchFocused.reset(); this.matchFocused.reset(); + this.fileMatchOrFolderMatchFocus.reset(); })); - - }); } diff --git a/src/vs/workbench/parts/search/common/constants.ts b/src/vs/workbench/parts/search/common/constants.ts index a2e93246ec6..78eb76c2af5 100644 --- a/src/vs/workbench/parts/search/common/constants.ts +++ b/src/vs/workbench/parts/search/common/constants.ts @@ -34,7 +34,8 @@ export const ReplaceActiveKey = new RawContextKey('replaceActive', fals export const HasSearchResults = new RawContextKey('hasSearchResult', false); export const FirstMatchFocusKey = new RawContextKey('firstMatchFocus', false); -export const FileMatchOrMatchFocusKey = new RawContextKey('fileMatchOrMatchFocus', false); +export const FileMatchOrMatchFocusKey = new RawContextKey('fileMatchOrMatchFocus', false); // This is actually, Match or File or Folder +export const FileMatchOrFolderMatchFocusKey = new RawContextKey('fileMatchOrFolderMatchFocus', false); export const FileFocusKey = new RawContextKey('fileMatchFocus', false); export const FolderFocusKey = new RawContextKey('folderMatchFocus', false); export const MatchFocusKey = new RawContextKey('matchFocus', false); 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 8443056e9c3..30341762f4c 100644 --- a/src/vs/workbench/parts/search/electron-browser/search.contribution.ts +++ b/src/vs/workbench/parts/search/electron-browser/search.contribution.ts @@ -256,7 +256,7 @@ MenuRegistry.appendMenuItem(MenuId.SearchContext, { KeybindingsRegistry.registerCommandAndKeybindingRule({ id: Constants.CopyPathCommandId, weight: KeybindingsRegistry.WEIGHT.workbenchContrib(), - when: Constants.FileFocusKey, + when: Constants.FileMatchOrFolderMatchFocusKey, primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.KEY_C, win: { primary: KeyMod.Shift | KeyMod.Alt | KeyCode.KEY_C @@ -269,7 +269,7 @@ MenuRegistry.appendMenuItem(MenuId.SearchContext, { id: Constants.CopyPathCommandId, title: nls.localize('copyPathLabel', "Copy Path") }, - when: Constants.FileFocusKey, + when: Constants.FileMatchOrFolderMatchFocusKey, group: 'search_2', order: 4 }); From c7b37f59157e6f228c837299a3c5f31d9880d91d Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Tue, 3 Apr 2018 17:53:56 -0700 Subject: [PATCH 16/49] #42120 - Implement Copy All --- .../parts/search/browser/searchActions.ts | 38 +++++++++++++++++-- .../parts/search/common/constants.ts | 1 + .../electron-browser/search.contribution.ts | 21 ++++++++-- 3 files changed, 53 insertions(+), 7 deletions(-) diff --git a/src/vs/workbench/parts/search/browser/searchActions.ts b/src/vs/workbench/parts/search/browser/searchActions.ts index 29af3475e47..675c13c025d 100644 --- a/src/vs/workbench/parts/search/browser/searchActions.ts +++ b/src/vs/workbench/parts/search/browser/searchActions.ts @@ -11,7 +11,7 @@ import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; import { ITree } from 'vs/base/parts/tree/browser/tree'; import { INavigator } from 'vs/base/common/iterator'; import { SearchView } from 'vs/workbench/parts/search/browser/searchView'; -import { Match, FileMatch, FileMatchOrMatch, FolderMatch, RenderableMatch } from 'vs/workbench/parts/search/common/searchModel'; +import { Match, FileMatch, FileMatchOrMatch, FolderMatch, RenderableMatch, SearchResult } from 'vs/workbench/parts/search/common/searchModel'; import { IReplaceService } from 'vs/workbench/parts/search/common/replace'; import * as Constants from 'vs/workbench/parts/search/common/constants'; import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService'; @@ -664,7 +664,7 @@ function fileMatchToString(fileMatch: FileMatch, maxMatches: number): { text: st }; } -function folderMatchToString(folderMatch: FolderMatch, maxMatches: number): string { +function folderMatchToString(folderMatch: FolderMatch, maxMatches: number): { text: string, count: number } { const fileResults: string[] = []; let numMatches = 0; @@ -674,7 +674,10 @@ function folderMatchToString(folderMatch: FolderMatch, maxMatches: number): stri fileResults.push(fileResult.text); } - return fileResults.join(lineDelimiter + lineDelimiter); + return { + text: fileResults.join(lineDelimiter + lineDelimiter), + count: numMatches + }; } const maxClipboardMatches = 1e4; @@ -687,10 +690,37 @@ export const copyMatchCommand: ICommandHandler = (accessor, match: RenderableMat } else if (match instanceof FileMatch) { text = fileMatchToString(match, maxClipboardMatches).text; } else if (match instanceof FolderMatch) { - text = folderMatchToString(match, maxClipboardMatches); + text = folderMatchToString(match, maxClipboardMatches).text; } if (text) { clipboardService.writeText(text); } }; + +function allFolderMatchesToString(folderMatches: FolderMatch[], maxMatches: number): string { + const folderResults: string[] = []; + let numMatches = 0; + + for (let i = 0; i < folderMatches.length && numMatches < maxMatches; i++) { + const folderResult = folderMatchToString(folderMatches[i], maxMatches - numMatches); + if (folderResult.count) { + numMatches += folderResult.count; + folderResults.push(folderResult.text); + } + } + + return folderResults.join(lineDelimiter + lineDelimiter); +} + +export const copyAllCommand: ICommandHandler = (accessor) => { + const viewletService = accessor.get(IViewletService); + const panelService = accessor.get(IPanelService); + const clipboardService = accessor.get(IClipboardService); + + const searchView = getSearchView(viewletService, panelService); + const root: SearchResult = searchView.getControl().getInput(); + + const text = allFolderMatchesToString(root.folderMatches(), maxClipboardMatches); + clipboardService.writeText(text); +}; diff --git a/src/vs/workbench/parts/search/common/constants.ts b/src/vs/workbench/parts/search/common/constants.ts index 78eb76c2af5..b8ca4a9aa84 100644 --- a/src/vs/workbench/parts/search/common/constants.ts +++ b/src/vs/workbench/parts/search/common/constants.ts @@ -14,6 +14,7 @@ export const CancelActionId = 'search.action.cancel'; export const RemoveActionId = 'search.action.remove'; export const CopyPathCommandId = 'search.action.copyPath'; export const CopyMatchCommandId = 'search.action.copyMatch'; +export const CopyAllCommandId = 'search.action.copyAll'; export const ReplaceActionId = 'search.action.replace'; export const ReplaceAllInFileActionId = 'search.action.replaceAllInFile'; export const ReplaceAllInFolderActionId = 'search.action.replaceAllInFolder'; 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 30341762f4c..9de9d564d94 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 } 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 } 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'; @@ -250,7 +250,7 @@ MenuRegistry.appendMenuItem(MenuId.SearchContext, { }, when: Constants.FileMatchOrMatchFocusKey, group: 'search_2', - order: 3 + order: 1 }); KeybindingsRegistry.registerCommandAndKeybindingRule({ @@ -271,7 +271,22 @@ MenuRegistry.appendMenuItem(MenuId.SearchContext, { }, when: Constants.FileMatchOrFolderMatchFocusKey, group: 'search_2', - order: 4 + order: 2 +}); + +MenuRegistry.appendMenuItem(MenuId.SearchContext, { + command: { + id: Constants.CopyAllCommandId, + title: nls.localize('copyAllLabel', "Copy All") + }, + when: Constants.HasSearchResults, + group: 'search_2', + order: 3 +}); + +CommandsRegistry.registerCommand({ + id: Constants.CopyAllCommandId, + handler: copyAllCommand }); CommandsRegistry.registerCommand({ From dd21d3520a3708360c264d12864173fadd71e142 Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Tue, 3 Apr 2018 18:25:22 -0700 Subject: [PATCH 17/49] Add webview restoration api proposal (#46380) Adds a proposed webiew serialization api that allows webviews to be restored automatically when vscode restarts --- .../markdown-language-features/package.json | 3 +- .../src/features/preview.ts | 116 +++++++++--- .../src/features/previewManager.ts | 48 ++++- src/vs/vscode.proposed.d.ts | 45 ++++- .../api/electron-browser/mainThreadWebview.ts | 165 ++++++++++++----- src/vs/workbench/api/node/extHost.api.impl.ts | 3 + src/vs/workbench/api/node/extHost.protocol.ts | 8 +- src/vs/workbench/api/node/extHostWebview.ts | 60 +++++- .../electron-browser/releaseNotesEditor.ts | 69 +++---- .../electron-browser/webview.contribution.ts | 14 +- .../webview/electron-browser/webviewEditor.ts | 32 ++-- .../webview/electron-browser/webviewInput.ts | 74 ++++---- .../electron-browser/webviewInputFactory.ts | 54 ++++++ .../electron-browser/webviewService.ts | 175 ++++++++++++++++++ 14 files changed, 690 insertions(+), 176 deletions(-) create mode 100644 src/vs/workbench/parts/webview/electron-browser/webviewInputFactory.ts create mode 100644 src/vs/workbench/parts/webview/electron-browser/webviewService.ts diff --git a/extensions/markdown-language-features/package.json b/extensions/markdown-language-features/package.json index d3fda69b63f..6fa682e351d 100644 --- a/extensions/markdown-language-features/package.json +++ b/extensions/markdown-language-features/package.json @@ -22,7 +22,8 @@ "onCommand:markdown.showPreviewToSide", "onCommand:markdown.showLockedPreviewToSide", "onCommand:markdown.showSource", - "onCommand:markdown.showPreviewSecuritySelector" + "onCommand:markdown.showPreviewSecuritySelector", + "onView:markdown.preview" ], "contributes": { "commands": [ diff --git a/extensions/markdown-language-features/src/features/preview.ts b/extensions/markdown-language-features/src/features/preview.ts index 1002bcf0fc5..5a3ca7443d4 100644 --- a/extensions/markdown-language-features/src/features/preview.ts +++ b/extensions/markdown-language-features/src/features/preview.ts @@ -18,37 +18,85 @@ const localize = nls.loadMessageBundle(); export class MarkdownPreview { - public static previewViewType = 'markdown.preview'; + public static viewType = 'markdown.preview'; private readonly webview: vscode.Webview; private throttleTimer: any; - private initialLine: number | undefined = undefined; + private line: number | undefined = undefined; private readonly disposables: vscode.Disposable[] = []; private firstUpdate = true; private currentVersion?: { resource: vscode.Uri, version: number }; private forceUpdate = false; private isScrolling = false; - constructor( - private _resource: vscode.Uri, + public static revive( + webview: vscode.Webview, + state: any, + contentProvider: MarkdownContentProvider, + previewConfigurations: MarkdownPreviewConfigurationManager, + logger: Logger, + topmostLineMonitor: MarkdownFileTopmostLineMonitor + ): MarkdownPreview { + const resource = vscode.Uri.parse(state.resource); + const locked = state.locked; + const line = state.line; + + const preview = new MarkdownPreview( + webview, + resource, + locked, + contentProvider, + previewConfigurations, + logger, + topmostLineMonitor); + + if (!isNaN(line)) { + preview.line = line; + } + return preview; + } + + public static create( + resource: vscode.Uri, previewColumn: vscode.ViewColumn, - public locked: boolean, - private readonly contentProvider: MarkdownContentProvider, - private readonly previewConfigurations: MarkdownPreviewConfigurationManager, - private readonly logger: Logger, + locked: boolean, + contentProvider: MarkdownContentProvider, + previewConfigurations: MarkdownPreviewConfigurationManager, + logger: Logger, topmostLineMonitor: MarkdownFileTopmostLineMonitor, - private readonly contributions: MarkdownContributions - ) { - this.webview = vscode.window.createWebview( - MarkdownPreview.previewViewType, - this.getPreviewTitle(this._resource), + contributions: MarkdownContributions + ): MarkdownPreview { + const webview = vscode.window.createWebview( + MarkdownPreview.viewType, + MarkdownPreview.getPreviewTitle(resource, locked), previewColumn, { enableScripts: true, enableCommandUris: true, enableFindWidget: true, - localResourceRoots: this.getLocalResourceRoots(_resource) + localResourceRoots: MarkdownPreview.getLocalResourceRoots(resource, contributions) }); + return new MarkdownPreview( + webview, + resource, + locked, + contentProvider, + previewConfigurations, + logger, + topmostLineMonitor); + } + + private constructor( + webview: vscode.Webview, + private _resource: vscode.Uri, + public locked: boolean, + private readonly contentProvider: MarkdownContentProvider, + private readonly previewConfigurations: MarkdownPreviewConfigurationManager, + private readonly logger: Logger, + topmostLineMonitor: MarkdownFileTopmostLineMonitor + ) { + this.webview = webview; + this.webview.onDidDispose(() => { this.dispose(); }, null, this.disposables); @@ -111,6 +159,14 @@ export class MarkdownPreview { return this._resource; } + public get state() { + return { + resource: this.resource.toString(), + locked: this.locked, + line: this.line + }; + } + public dispose() { this._onDisposeEmitter.fire(); @@ -124,9 +180,7 @@ export class MarkdownPreview { public update(resource: vscode.Uri) { const editor = vscode.window.activeTextEditor; if (editor && editor.document.uri.fsPath === resource.fsPath) { - this.initialLine = getVisibleLine(editor); - } else { - this.initialLine = undefined; + this.line = getVisibleLine(editor); } // If we have changed resources, cancel any pending updates @@ -169,6 +223,10 @@ export class MarkdownPreview { return this._resource.fsPath === resource.fsPath; } + public isWebviewOf(webview: vscode.Webview): boolean { + return this.webview === webview; + } + public matchesResource( otherResource: vscode.Uri, otherViewColumn: vscode.ViewColumn | undefined, @@ -195,11 +253,11 @@ export class MarkdownPreview { public toggleLock() { this.locked = !this.locked; - this.webview.title = this.getPreviewTitle(this._resource); + this.webview.title = MarkdownPreview.getPreviewTitle(this._resource, this.locked); } - private getPreviewTitle(resource: vscode.Uri): string { - return this.locked + private static getPreviewTitle(resource: vscode.Uri, locked: boolean): string { + return locked ? localize('lockedPreviewTitle', '[Preview] {0}', path.basename(resource.fsPath)) : localize('previewTitle', 'Preview {0}', path.basename(resource.fsPath)); } @@ -216,7 +274,7 @@ export class MarkdownPreview { if (typeof topLine === 'number') { this.logger.log('updateForView', { markdownFile: resource }); - this.initialLine = topLine; + this.line = topLine; this.webview.postMessage({ type: 'updateView', line: topLine, @@ -233,25 +291,28 @@ export class MarkdownPreview { const document = await vscode.workspace.openTextDocument(resource); if (!this.forceUpdate && this.currentVersion && this.currentVersion.resource.fsPath === resource.fsPath && this.currentVersion.version === document.version) { - if (this.initialLine) { - this.updateForView(resource, this.initialLine); + if (this.line) { + this.updateForView(resource, this.line); } return; } this.forceUpdate = false; this.currentVersion = { resource, version: document.version }; - this.contentProvider.provideTextDocumentContent(document, this.previewConfigurations, this.initialLine) + this.contentProvider.provideTextDocumentContent(document, this.previewConfigurations, this.line) .then(content => { if (this._resource === resource) { - this.webview.title = this.getPreviewTitle(this._resource); + this.webview.title = MarkdownPreview.getPreviewTitle(this._resource, this.locked); this.webview.html = content; } }); } - private getLocalResourceRoots(resource: vscode.Uri): vscode.Uri[] { - const baseRoots = this.contributions.previewResourceRoots; + private static getLocalResourceRoots( + resource: vscode.Uri, + contributions: MarkdownContributions + ): vscode.Uri[] { + const baseRoots = contributions.previewResourceRoots; const folder = vscode.workspace.getWorkspaceFolder(resource); if (folder) { @@ -266,6 +327,7 @@ export class MarkdownPreview { } private onDidScrollPreview(line: number) { + this.line = line; for (const editor of vscode.window.visibleTextEditors) { if (!this.isPreviewOf(editor.document.uri)) { continue; diff --git a/extensions/markdown-language-features/src/features/previewManager.ts b/extensions/markdown-language-features/src/features/previewManager.ts index 61fee517b43..d9a2752edd3 100644 --- a/extensions/markdown-language-features/src/features/previewManager.ts +++ b/extensions/markdown-language-features/src/features/previewManager.ts @@ -14,7 +14,7 @@ import { isMarkdownFile } from '../util/file'; import { MarkdownPreviewConfigurationManager } from './previewConfig'; import { MarkdownContributions } from '../markdownExtensions'; -export class MarkdownPreviewManager { +export class MarkdownPreviewManager implements vscode.WebviewSerializer { private static readonly markdownPreviewActiveContextKey = 'markdownPreviewFocus'; private readonly topmostLineMonitor = new MarkdownFileTopmostLineMonitor(); @@ -29,15 +29,14 @@ export class MarkdownPreviewManager { private readonly contributions: MarkdownContributions ) { vscode.window.onDidChangeActiveTextEditor(editor => { - if (editor) { - if (isMarkdownFile(editor.document)) { - for (const preview of this.previews.filter(preview => !preview.locked)) { - preview.update(editor.document.uri); - } + if (editor && isMarkdownFile(editor.document)) { + for (const preview of this.previews.filter(preview => !preview.locked)) { + preview.update(editor.document.uri); } } }, null, this.disposables); + this.disposables.push(vscode.window.registerWebviewSerializer(MarkdownPreview.viewType, this)); } public dispose(): void { @@ -66,7 +65,6 @@ export class MarkdownPreviewManager { preview.reveal(previewSettings.previewColumn); } else { preview = this.createNewPreview(resource, previewSettings); - this.previews.push(preview); } preview.update(resource); @@ -90,6 +88,30 @@ export class MarkdownPreviewManager { } } + public async deserializeWebview( + webview: vscode.Webview, + state: any + ): Promise { + const preview = MarkdownPreview.revive( + webview, + state, + this.contentProvider, + this.previewConfigurations, + this.logger, + this.topmostLineMonitor); + + this.registerPreview(preview); + preview.refresh(); + return true; + } + + public async serializeWebview( + webview: vscode.Webview, + ): Promise { + const preview = this.previews.find(preview => preview.isWebviewOf(webview)); + return preview ? preview.state : undefined; + } + private getExistingPreview( resource: vscode.Uri, previewSettings: PreviewSettings @@ -101,8 +123,8 @@ export class MarkdownPreviewManager { private createNewPreview( resource: vscode.Uri, previewSettings: PreviewSettings - ) { - const preview = new MarkdownPreview( + ): MarkdownPreview { + const preview = MarkdownPreview.create( resource, previewSettings.previewColumn, previewSettings.locked, @@ -112,6 +134,14 @@ export class MarkdownPreviewManager { this.topmostLineMonitor, this.contributions); + return this.registerPreview(preview); + } + + private registerPreview( + preview: MarkdownPreview + ): MarkdownPreview { + this.previews.push(preview); + preview.onDispose(() => { const existing = this.previews.indexOf(preview!); if (existing >= 0) { diff --git a/src/vs/vscode.proposed.d.ts b/src/vs/vscode.proposed.d.ts index 5d0f2e25d86..33c125d8467 100644 --- a/src/vs/vscode.proposed.d.ts +++ b/src/vs/vscode.proposed.d.ts @@ -568,7 +568,7 @@ declare module 'vscode' { */ export interface Webview { /** - * The type of the webview, such as `'markdownw.preview'` + * The type of the webview, such as `'markdown.preview'` */ readonly viewType: string; @@ -636,16 +636,57 @@ declare module 'vscode' { dispose(): any; } + /** + * Save and restore webviews that have been persisted when vscode shuts down. + */ + interface WebviewSerializer { + /** + * Save a webview's `state`. + * + * Called before shutdown. Webview may or may not be visible. + * + * @param webview Webview to serialize. + * + * @returns JSON serializable state blob. + */ + serializeWebview(webview: Webview): Thenable; + + /** + * Restore a webview from its `state`. + * + * Called when a serialized webview first becomes active. + * + * @param webview Webview to restore. The serializer should take ownership of this webview. + * @param state Persisted state. + * + * @return Was deserialization successful? + */ + deserializeWebview(webview: Webview, state: any): Thenable; + } + namespace window { /** * Create and show a new webview. * - * @param viewType Identifier the type of the webview. + * @param viewType Identifies the type of the webview. * @param title Title of the webview. * @param column Editor column to show the new webview in. * @param options Content settings for the webview. */ export function createWebview(viewType: string, title: string, column: ViewColumn, options: WebviewOptions): Webview; + + /** + * Registers a webview serializer. + * + * Extensions that support reviving should have an `"onView:viewType"` activation method and + * make sure that `registerWebviewSerializer` is called during activation. + * + * Only a single serializer may be registered at a time for a given `viewType`. + * + * @param viewType Type of the webview that can be serialized. + * @param reviver Webview serializer. + */ + export function registerWebviewSerializer(viewType: string, reviver: WebviewSerializer): Disposable; } //#endregion diff --git a/src/vs/workbench/api/electron-browser/mainThreadWebview.ts b/src/vs/workbench/api/electron-browser/mainThreadWebview.ts index aba0f0c1621..323f9e9db0e 100644 --- a/src/vs/workbench/api/electron-browser/mainThreadWebview.ts +++ b/src/vs/workbench/api/electron-browser/mainThreadWebview.ts @@ -2,44 +2,58 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ - +import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import * as map from 'vs/base/common/map'; -import { MainThreadWebviewsShape, MainContext, IExtHostContext, ExtHostContext, ExtHostWebviewsShape, WebviewHandle } from 'vs/workbench/api/node/extHost.protocol'; -import { dispose, Disposable } from 'vs/base/common/lifecycle'; -import { extHostNamedCustomer } from './extHostCustomers'; -import { Position } from 'vs/platform/editor/common/editor'; -import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService'; -import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; -import { IPartService } from 'vs/workbench/services/part/common/partService'; -import { IOpenerService } from 'vs/platform/opener/common/opener'; -import * as vscode from 'vscode'; -import { IEditorGroupService } from 'vs/workbench/services/group/common/groupService'; import URI from 'vs/base/common/uri'; -import { WebviewInput } from 'vs/workbench/parts/webview/electron-browser/webviewInput'; +import { TPromise } from 'vs/base/common/winjs.base'; +import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { Position } from 'vs/platform/editor/common/editor'; +import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle'; +import { IOpenerService } from 'vs/platform/opener/common/opener'; +import { ExtHostContext, ExtHostWebviewsShape, IExtHostContext, MainContext, MainThreadWebviewsShape, WebviewHandle } from 'vs/workbench/api/node/extHost.protocol'; import { WebviewEditor } from 'vs/workbench/parts/webview/electron-browser/webviewEditor'; - +import { WebviewEditorInput } from 'vs/workbench/parts/webview/electron-browser/webviewInput'; +import { IWebviewService, WebviewInputOptions, WebviewReviver } from 'vs/workbench/parts/webview/electron-browser/webviewService'; +import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; +import { IEditorGroupService } from 'vs/workbench/services/group/common/groupService'; +import { extHostNamedCustomer } from './extHostCustomers'; @extHostNamedCustomer(MainContext.MainThreadWebviews) -export class MainThreadWebviews implements MainThreadWebviewsShape { +export class MainThreadWebviews implements MainThreadWebviewsShape, WebviewReviver { + + private static readonly viewType = 'mainThreadWebview'; + private static readonly standardSupportedLinkSchemes = ['http', 'https', 'mailto']; - private _toDispose: Disposable[] = []; + private static revivalPool = 0; + + private _toDispose: IDisposable[] = []; private readonly _proxy: ExtHostWebviewsShape; - private readonly _webviews = new Map(); + private readonly _webviews = new Map(); + private readonly _revivers = new Set(); - private _activeWebview: WebviewInput | undefined = undefined; + private _activeWebview: WebviewEditorInput | undefined = undefined; constructor( context: IExtHostContext, - @IContextKeyService _contextKeyService: IContextKeyService, - @IPartService private readonly _partService: IPartService, + @IContextKeyService contextKeyService: IContextKeyService, + @IEditorGroupService editorGroupService: IEditorGroupService, + @ILifecycleService lifecycleService: ILifecycleService, @IWorkbenchEditorService private readonly _editorService: IWorkbenchEditorService, - @IEditorGroupService private readonly _editorGroupService: IEditorGroupService, - @IOpenerService private readonly _openerService: IOpenerService + @IWebviewService private readonly _webviewService: IWebviewService, + @IOpenerService private readonly _openerService: IOpenerService, + @IExtensionService private readonly _extensionService: IExtensionService, + ) { this._proxy = context.getProxy(ExtHostContext.ExtHostWebviews); - _editorGroupService.onEditorsChanged(this.onEditorsChanged, this, this._toDispose); + editorGroupService.onEditorsChanged(this.onEditorsChanged, this, this._toDispose); + + _webviewService.registerReviver(MainThreadWebviews.viewType, this); + this._toDispose.push(lifecycleService.onWillShutdown(e => { + e.veto(this._onWillShutdown()); + })); } dispose(): void { @@ -51,30 +65,31 @@ export class MainThreadWebviews implements MainThreadWebviewsShape { viewType: string, title: string, column: Position, - options: vscode.WebviewOptions, + options: WebviewInputOptions, extensionFolderPath: string ): void { - const webviewInput = new WebviewInput(title, options, '', { + const webview = this._webviewService.createWebview(MainThreadWebviews.viewType, title, column, options, extensionFolderPath, { + onDidClickLink: uri => this.onDidClickLink(uri, webview.options), onMessage: message => this._proxy.$onMessage(handle, message), onDidChangePosition: position => this._proxy.$onDidChangePosition(handle, position), onDispose: () => { this._proxy.$onDidDisposeWeview(handle).then(() => { this._webviews.delete(handle); }); - }, - onDidClickLink: (link, options) => this.onDidClickLink(link, options) - }, this._partService); + } + }); - this._webviews.set(handle, webviewInput); + webview.state = { + viewType: viewType, + state: undefined + }; - this._editorService.openEditor(webviewInput, { pinned: true }, column); + this._webviews.set(handle, webview); } $disposeWebview(handle: WebviewHandle): void { const webview = this.getWebview(handle); - if (webview) { - this._editorService.closeEditor(webview.position, webview); - } + webview.dispose(); } $setTitle(handle: WebviewHandle, value: string): void { @@ -84,24 +99,20 @@ export class MainThreadWebviews implements MainThreadWebviewsShape { $setHtml(handle: WebviewHandle, value: string): void { const webview = this.getWebview(handle); - webview.setHtml(value); + webview.html = value; } $reveal(handle: WebviewHandle, column: Position): void { - const webviewInput = this.getWebview(handle); - if (webviewInput.position === column) { - this._editorService.openEditor(webviewInput, { preserveFocus: true }, column); - } else { - this._editorGroupService.moveEditor(webviewInput, webviewInput.position, column, { preserveFocus: true }); - } + const webview = this.getWebview(handle); + this._webviewService.revealWebview(webview, column); } async $sendMessage(handle: WebviewHandle, message: any): Promise { - const webviewInput = this.getWebview(handle); + const webview = this.getWebview(handle); const editors = this._editorService.getVisibleEditors() .filter(e => e instanceof WebviewEditor) .map(e => e as WebviewEditor) - .filter(e => e.input.matches(webviewInput)); + .filter(e => e.input.matches(webview)); for (const editor of editors) { editor.sendMessage(message); @@ -110,18 +121,74 @@ export class MainThreadWebviews implements MainThreadWebviewsShape { return (editors.length > 0); } - private getWebview(handle: number): WebviewInput { - const webviewInput = this._webviews.get(handle); - if (!webviewInput) { + $registerSerializer(viewType: string): void { + this._revivers.add(viewType); + } + + $unregisterSerializer(viewType: string): void { + this._revivers.delete(viewType); + } + + reviveWebview(webview: WebviewEditorInput) { + this._extensionService.activateByEvent(`onView:${webview.state.viewType}`).then(() => { + const handle = 'revival-' + MainThreadWebviews.revivalPool++; + this._webviews.set(handle, webview); + + webview._events = { + onDidClickLink: uri => this.onDidClickLink(uri, webview.options), + onMessage: message => this._proxy.$onMessage(handle, message), + onDidChangePosition: position => this._proxy.$onDidChangePosition(handle, position), + onDispose: () => { + this._proxy.$onDidDisposeWeview(handle).then(() => { + this._webviews.delete(handle); + }); + } + }; + + this._proxy.$deserializeWebview(handle, webview.state.viewType, webview.state.state, webview.position, webview.options); + }); + } + + canRevive(webview: WebviewEditorInput): boolean { + return this._revivers.has(webview.viewType) || webview.reviver !== null; + } + + private _onWillShutdown(): TPromise { + const toRevive: WebviewHandle[] = []; + this._webviews.forEach((view, key) => { + if (this.canRevive(view)) { + toRevive.push(key); + } + }); + + const reviveResponses = toRevive.map(handle => + this._proxy.$serializeWebview(handle).then(state => ({ handle, state }))); + + return TPromise.join(reviveResponses).then(results => { + for (const result of results) { + if (result.state) { + const view = this._webviews.get(result.handle); + if (view) { + view.state.state = result.state; + } + } + } + return false; // Don't veto shutdown + }); + } + + private getWebview(handle: WebviewHandle): WebviewEditorInput { + const webview = this._webviews.get(handle); + if (!webview) { throw new Error('Unknown webview handle:' + handle); } - return webviewInput; + return webview; } private onEditorsChanged() { const activeEditor = this._editorService.getActiveEditor(); - let newActiveWebview: { input: WebviewInput, handle: WebviewHandle } | undefined = undefined; - if (activeEditor && activeEditor.input instanceof WebviewInput) { + let newActiveWebview: { input: WebviewEditorInput, handle: WebviewHandle } | undefined = undefined; + if (activeEditor && activeEditor.input instanceof WebviewEditorInput) { for (const handle of map.keys(this._webviews)) { const input = this._webviews.get(handle); if (input.matches(activeEditor.input)) { @@ -132,7 +199,7 @@ export class MainThreadWebviews implements MainThreadWebviewsShape { } if (newActiveWebview) { - if (!this._activeWebview || !newActiveWebview.input.matches(this._activeWebview)) { + if (!this._activeWebview || newActiveWebview.input !== this._activeWebview) { this._proxy.$onDidChangeActiveWeview(newActiveWebview.handle); this._activeWebview = newActiveWebview.input; } @@ -144,7 +211,7 @@ export class MainThreadWebviews implements MainThreadWebviewsShape { } } - private onDidClickLink(link: URI, options: vscode.WebviewOptions): void { + private onDidClickLink(link: URI, options: WebviewInputOptions): void { if (!link) { return; } diff --git a/src/vs/workbench/api/node/extHost.api.impl.ts b/src/vs/workbench/api/node/extHost.api.impl.ts index de10ae5c3cf..6757325af76 100644 --- a/src/vs/workbench/api/node/extHost.api.impl.ts +++ b/src/vs/workbench/api/node/extHost.api.impl.ts @@ -418,6 +418,9 @@ export function createApiFactory( }), createWebview: proposedApiFunction(extension, (viewType: string, title: string, column: vscode.ViewColumn, options: vscode.WebviewOptions) => { return extHostWebviews.createWebview(viewType, title, column, options, extension.extensionFolderPath); + }), + registerWebviewSerializer: proposedApiFunction(extension, (viewType: string, serializer: vscode.WebviewSerializer) => { + return extHostWebviews.registerWebviewSerializer(viewType, serializer); }) }; diff --git a/src/vs/workbench/api/node/extHost.protocol.ts b/src/vs/workbench/api/node/extHost.protocol.ts index fe1dfcdb6ed..2a53c50b8ec 100644 --- a/src/vs/workbench/api/node/extHost.protocol.ts +++ b/src/vs/workbench/api/node/extHost.protocol.ts @@ -347,7 +347,7 @@ export interface MainThreadTelemetryShape extends IDisposable { $publicLog(eventName: string, data?: any): void; } -export type WebviewHandle = number; +export type WebviewHandle = string; export interface MainThreadWebviewsShape extends IDisposable { $createWebview(handle: WebviewHandle, viewType: string, title: string, column: EditorPosition, options: vscode.WebviewOptions, extensionFolderPath: string): void; @@ -356,12 +356,18 @@ export interface MainThreadWebviewsShape extends IDisposable { $setTitle(handle: WebviewHandle, value: string): void; $setHtml(handle: WebviewHandle, value: string): void; $sendMessage(handle: WebviewHandle, value: any): Thenable; + + $registerSerializer(viewType: string): void; + $unregisterSerializer(viewType: string): void; } + export interface ExtHostWebviewsShape { $onMessage(handle: WebviewHandle, message: any): void; $onDidChangeActiveWeview(handle: WebviewHandle | undefined): void; $onDidDisposeWeview(handle: WebviewHandle): Thenable; $onDidChangePosition(handle: WebviewHandle, newPosition: EditorPosition): void; + $deserializeWebview(newWebviewHandle: WebviewHandle, viewType: string, state: any, position: EditorPosition, options: vscode.WebviewOptions): void; + $serializeWebview(webviewHandle: WebviewHandle): Thenable; } export interface MainThreadWorkspaceShape extends IDisposable { diff --git a/src/vs/workbench/api/node/extHostWebview.ts b/src/vs/workbench/api/node/extHostWebview.ts index 27f69d0ec1d..5e3a1b38126 100644 --- a/src/vs/workbench/api/node/extHostWebview.ts +++ b/src/vs/workbench/api/node/extHostWebview.ts @@ -9,6 +9,7 @@ import { Event, Emitter } from 'vs/base/common/event'; import * as typeConverters from 'vs/workbench/api/node/extHostTypeConverters'; import { Position } from 'vs/platform/editor/common/editor'; import { TPromise } from 'vs/base/common/winjs.base'; +import { Disposable } from './extHostTypes'; export class ExtHostWebview implements vscode.Webview { @@ -19,6 +20,7 @@ export class ExtHostWebview implements vscode.Webview { private _isDisposed: boolean = false; private _viewColumn: vscode.ViewColumn; private _active: boolean; + private _state: any; public readonly onMessageEmitter = new Emitter(); public readonly onDidReceiveMessage: Event = this.onMessageEmitter.event; @@ -85,6 +87,11 @@ export class ExtHostWebview implements vscode.Webview { } } + get state(): any { + this.assertNotDisposed(); + return this._state; + } + get options(): vscode.WebviewOptions { this.assertNotDisposed(); return this._options; @@ -128,11 +135,12 @@ export class ExtHostWebview implements vscode.Webview { } export class ExtHostWebviews implements ExtHostWebviewsShape { - private static handlePool = 1; + private static webviewHandlePool = 1; private readonly _proxy: MainThreadWebviewsShape; private readonly _webviews = new Map(); + private readonly _serializers = new Map(); private _activeWebview: ExtHostWebview | undefined; @@ -149,7 +157,7 @@ export class ExtHostWebviews implements ExtHostWebviewsShape { options: vscode.WebviewOptions, extensionFolderPath: string ): vscode.Webview { - const handle = ExtHostWebviews.handlePool++; + const handle = ExtHostWebviews.webviewHandlePool++ + ''; this._proxy.$createWebview(handle, viewType, title, typeConverters.fromViewColumn(viewColumn), options, extensionFolderPath); const webview = new ExtHostWebview(handle, this._proxy, viewType, viewColumn, options); @@ -157,6 +165,23 @@ export class ExtHostWebviews implements ExtHostWebviewsShape { return webview; } + registerWebviewSerializer( + viewType: string, + serializer: vscode.WebviewSerializer + ): vscode.Disposable { + if (this._serializers.has(viewType)) { + throw new Error(`Serializer for '${viewType}' already registered`); + } + + this._serializers.set(viewType, serializer); + this._proxy.$registerSerializer(viewType); + + return new Disposable(() => { + this._serializers.delete(viewType); + this._proxy.$unregisterSerializer(viewType); + }); + } + $onMessage(handle: WebviewHandle, message: any): void { const webview = this.getWebview(handle); if (webview) { @@ -206,8 +231,35 @@ export class ExtHostWebviews implements ExtHostWebviewsShape { } } - private readonly _onDidChangeActiveWebview = new Emitter(); - public readonly onDidChangeActiveWebview = this._onDidChangeActiveWebview.event; + $deserializeWebview( + webviewHandle: WebviewHandle, + viewType: string, + state: any, + position: Position, + options: vscode.WebviewOptions + ): void { + const serializer = this._serializers.get(viewType); + if (!serializer) { + return; + } + + const revivedWebview = new ExtHostWebview(webviewHandle, this._proxy, viewType, typeConverters.toViewColumn(position), options); + this._webviews.set(webviewHandle, revivedWebview); + serializer.deserializeWebview(revivedWebview, state); + } + + $serializeWebview( + webviewHandle: WebviewHandle + ): Thenable { + const webview = this.getWebview(webviewHandle); + + const serialzer = this._serializers.get(webview.viewType); + if (!serialzer) { + return TPromise.as(undefined); + } + + return serialzer.serializeWebview(webview); + } private getWebview(handle: WebviewHandle) { return this._webviews.get(handle); diff --git a/src/vs/workbench/parts/update/electron-browser/releaseNotesEditor.ts b/src/vs/workbench/parts/update/electron-browser/releaseNotesEditor.ts index bf6f7c9feca..12978917cf6 100644 --- a/src/vs/workbench/parts/update/electron-browser/releaseNotesEditor.ts +++ b/src/vs/workbench/parts/update/electron-browser/releaseNotesEditor.ts @@ -5,29 +5,29 @@ 'use strict'; -import { TPromise } from 'vs/base/common/winjs.base'; +import { onUnexpectedError } from 'vs/base/common/errors'; import { marked } from 'vs/base/common/marked/marked'; -import { IModeService } from 'vs/editor/common/services/modeService'; -import { tokenizeToString } from 'vs/editor/common/modes/textToHtmlTokenizer'; +import { OS } from 'vs/base/common/platform'; +import URI from 'vs/base/common/uri'; +import { TPromise } from 'vs/base/common/winjs.base'; +import { asText } from 'vs/base/node/request'; import { IMode, TokenizationRegistry } from 'vs/editor/common/modes'; import { generateTokensCSSForColorMap } from 'vs/editor/common/modes/supports/tokenization'; -import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService'; -import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; -import { KeybindingIO } from 'vs/workbench/services/keybinding/common/keybindingIO'; -import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; -import { IRequestService } from 'vs/platform/request/node/request'; -import { IEnvironmentService } from 'vs/platform/environment/common/environment'; -import { IPartService } from 'vs/workbench/services/part/common/partService'; -import { IOpenerService } from 'vs/platform/opener/common/opener'; -import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; -import { WebviewInput } from 'vs/workbench/parts/webview/electron-browser/webviewInput'; -import { onUnexpectedError } from 'vs/base/common/errors'; -import { addGAParameters } from 'vs/platform/telemetry/node/telemetryNodeUtils'; -import URI from 'vs/base/common/uri'; -import { asText } from 'vs/base/node/request'; +import { tokenizeToString } from 'vs/editor/common/modes/textToHtmlTokenizer'; +import { IModeService } from 'vs/editor/common/services/modeService'; import * as nls from 'vs/nls'; -import { OS } from 'vs/base/common/platform'; -import { IEditorGroupService } from 'vs/workbench/services/group/common/groupService'; +import { IEnvironmentService } from 'vs/platform/environment/common/environment'; +import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; +import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; +import { IOpenerService } from 'vs/platform/opener/common/opener'; +import { IRequestService } from 'vs/platform/request/node/request'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; +import { addGAParameters } from 'vs/platform/telemetry/node/telemetryNodeUtils'; +import { IWebviewService } from 'vs/workbench/parts/webview/electron-browser/webviewService'; +import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { KeybindingIO } from 'vs/workbench/services/keybinding/common/keybindingIO'; +import { Position } from 'vs/platform/editor/common/editor'; +import { WebviewEditorInput } from 'vs/workbench/parts/webview/electron-browser/webviewInput'; function renderBody( body: string, @@ -51,18 +51,17 @@ export class ReleaseNotesManager { private _releaseNotesCache: { [version: string]: TPromise; } = Object.create(null); - private _currentReleaseNotes: WebviewInput | undefined = undefined; + private _currentReleaseNotes: WebviewEditorInput | undefined = undefined; public constructor( - @IEditorGroupService private readonly _editorGroupService: IEditorGroupService, @IEnvironmentService private readonly _environmentService: IEnvironmentService, @IKeybindingService private readonly _keybindingService: IKeybindingService, @IModeService private readonly _modeService: IModeService, @IOpenerService private readonly _openerService: IOpenerService, - @IPartService private readonly _partService: IPartService, @IRequestService private readonly _requestService: IRequestService, @ITelemetryService private readonly _telemetryService: ITelemetryService, @IWorkbenchEditorService private readonly _editorService: IWorkbenchEditorService, + @IWebviewService private readonly _webviewService: IWebviewService, ) { } public async show( @@ -73,21 +72,23 @@ export class ReleaseNotesManager { const html = await this.renderBody(releaseNoteText); const title = nls.localize('releaseNotesInputName', "Release Notes: {0}", version); + const activeEditor = this._editorService.getActiveEditor(); if (this._currentReleaseNotes) { this._currentReleaseNotes.setName(title); - this._currentReleaseNotes.setHtml(html); - const activeEditor = this._editorService.getActiveEditor(); - if (activeEditor && activeEditor.position !== this._currentReleaseNotes.position) { - this._editorGroupService.moveEditor(this._currentReleaseNotes, this._currentReleaseNotes.position, activeEditor.position, { preserveFocus: true }); - } else { - this._editorService.openEditor(this._currentReleaseNotes, { preserveFocus: true }); - } + this._currentReleaseNotes.html = html; + this._webviewService.revealWebview(this._currentReleaseNotes, activeEditor ? activeEditor.position : undefined); } else { - this._currentReleaseNotes = new WebviewInput(title, { tryRestoreScrollPosition: true, enableFindWidget: true }, html, { - onDidClickLink: uri => this.onDidClickLink(uri), - onDispose: () => { this._currentReleaseNotes = undefined; } - }, this._partService); - await this._editorService.openEditor(this._currentReleaseNotes, { pinned: true }); + this._currentReleaseNotes = this._webviewService.createWebview( + 'releaseNotes', + title, + activeEditor ? activeEditor.position : Position.ONE, + { tryRestoreScrollPosition: true, enableFindWidget: true }, + undefined, { + onDidClickLink: uri => this.onDidClickLink(uri), + onDispose: () => { this._currentReleaseNotes = undefined; } + }); + + this._currentReleaseNotes.html = html; } return true; diff --git a/src/vs/workbench/parts/webview/electron-browser/webview.contribution.ts b/src/vs/workbench/parts/webview/electron-browser/webview.contribution.ts index 4203fa20dff..2b98fd80e6d 100644 --- a/src/vs/workbench/parts/webview/electron-browser/webview.contribution.ts +++ b/src/vs/workbench/parts/webview/electron-browser/webview.contribution.ts @@ -7,11 +7,21 @@ import { IEditorRegistry, EditorDescriptor, Extensions as EditorExtensions } fro import { WebviewEditor } from './webviewEditor'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { Registry } from 'vs/platform/registry/common/platform'; -import { WebviewInput } from './webviewInput'; +import { WebviewEditorInput } from './webviewInput'; import { localize } from 'vs/nls'; +import { IEditorInputFactoryRegistry, Extensions as EditorInputExtensions } from 'vs/workbench/common/editor'; +import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { IWebviewService, WebviewService } from './webviewService'; +import { WebviewInputFactory } from 'vs/workbench/parts/webview/electron-browser/webviewInputFactory'; (Registry.as(EditorExtensions.Editors)).registerEditor(new EditorDescriptor( WebviewEditor, WebviewEditor.ID, localize('webview.editor.label', "webview editor")), - [new SyncDescriptor(WebviewInput)]); \ No newline at end of file + [new SyncDescriptor(WebviewEditorInput)]); + +Registry.as(EditorInputExtensions.EditorInputFactories).registerEditorInputFactory( + WebviewInputFactory.ID, + WebviewInputFactory); + +registerSingleton(IWebviewService, WebviewService); diff --git a/src/vs/workbench/parts/webview/electron-browser/webviewEditor.ts b/src/vs/workbench/parts/webview/electron-browser/webviewEditor.ts index b3d963f146b..d4e8afff2b5 100644 --- a/src/vs/workbench/parts/webview/electron-browser/webviewEditor.ts +++ b/src/vs/workbench/parts/webview/electron-browser/webviewEditor.ts @@ -19,7 +19,7 @@ import { IEnvironmentService } from 'vs/platform/environment/common/environment' import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import * as DOM from 'vs/base/browser/dom'; import { Event, Emitter } from 'vs/base/common/event'; -import { WebviewInput } from 'vs/workbench/parts/webview/electron-browser/webviewInput'; +import { WebviewEditorInput } from 'vs/workbench/parts/webview/electron-browser/webviewInput'; import URI from 'vs/base/common/uri'; export class WebviewEditor extends BaseWebviewEditor { @@ -29,10 +29,12 @@ export class WebviewEditor extends BaseWebviewEditor { private editorFrame: HTMLElement; private content: HTMLElement; private webviewContent: HTMLElement | undefined; - private readonly _onDidFocusWebview: Emitter; + private _webviewFocusTracker?: DOM.IFocusTracker; private _webviewFocusListenerDisposable?: IDisposable; + private readonly _onDidFocusWebview = new Emitter(); + constructor( @ITelemetryService telemetryService: ITelemetryService, @IThemeService themeService: IThemeService, @@ -43,8 +45,6 @@ export class WebviewEditor extends BaseWebviewEditor { @IWorkspaceContextService private readonly _contextService: IWorkspaceContextService ) { super(WebviewEditor.ID, telemetryService, themeService, _contextKeyService); - - this._onDidFocusWebview = new Emitter(); } protected createEditor(parent: Builder): void { @@ -54,7 +54,7 @@ export class WebviewEditor extends BaseWebviewEditor { } private doUpdateContainer() { - const webviewContainer = this.input && (this.input as WebviewInput).container; + const webviewContainer = this.input && (this.input as WebviewEditorInput).container; if (webviewContainer && webviewContainer.parentElement) { const frameRect = this.editorFrame.getBoundingClientRect(); const containerRect = webviewContainer.parentElement.getBoundingClientRect(); @@ -103,14 +103,14 @@ export class WebviewEditor extends BaseWebviewEditor { } protected setEditorVisible(visible: boolean, position?: Position): void { - if (this.input && this.input instanceof WebviewInput) { + if (this.input && this.input instanceof WebviewEditorInput) { if (visible) { this.input.claimWebview(this); } else { this.input.releaseWebview(this); } - this.updateWebview(this.input as WebviewInput); + this.updateWebview(this.input as WebviewEditorInput); } if (this.webviewContent) { @@ -126,7 +126,7 @@ export class WebviewEditor extends BaseWebviewEditor { } public clearInput() { - if (this.input && this.input instanceof WebviewInput) { + if (this.input && this.input instanceof WebviewEditorInput) { this.input.releaseWebview(this); } @@ -136,24 +136,24 @@ export class WebviewEditor extends BaseWebviewEditor { super.clearInput(); } - async setInput(input: WebviewInput, options: EditorOptions): TPromise { + async setInput(input: WebviewEditorInput, options: EditorOptions): TPromise { if (this.input && this.input.matches(input)) { return undefined; } if (this.input) { - (this.input as WebviewInput).releaseWebview(this); + (this.input as WebviewEditorInput).releaseWebview(this); this._webview = undefined; this.webviewContent = undefined; } await super.setInput(input, options); - input.onDidChangePosition(this.position); + input.onBecameActive(this.position); this.updateWebview(input); } - private updateWebview(input: WebviewInput) { + private updateWebview(input: WebviewEditorInput) { const webview = this.getWebview(input); input.claimWebview(this); webview.options = { @@ -163,7 +163,7 @@ export class WebviewEditor extends BaseWebviewEditor { useSameOriginForRoot: false, localResourceRoots: input.options.localResourceRoots || this.getDefaultLocalResourceRoots() }; - input.setHtml(input.html); + input.html = input.html; if (this.webviewContent) { this.webviewContent.style.visibility = 'visible'; @@ -174,13 +174,13 @@ export class WebviewEditor extends BaseWebviewEditor { private getDefaultLocalResourceRoots(): URI[] { const rootPaths = this._contextService.getWorkspace().folders.map(x => x.uri); - if ((this.input as WebviewInput).extensionFolderPath) { - rootPaths.push((this.input as WebviewInput).extensionFolderPath); + if ((this.input as WebviewEditorInput).extensionFolderPath) { + rootPaths.push((this.input as WebviewEditorInput).extensionFolderPath); } return rootPaths; } - private getWebview(input: WebviewInput): Webview { + private getWebview(input: WebviewEditorInput): Webview { if (this._webview) { return this._webview; } diff --git a/src/vs/workbench/parts/webview/electron-browser/webviewInput.ts b/src/vs/workbench/parts/webview/electron-browser/webviewInput.ts index 0d925f614b7..1bd9535aeaf 100644 --- a/src/vs/workbench/parts/webview/electron-browser/webviewInput.ts +++ b/src/vs/workbench/parts/webview/electron-browser/webviewInput.ts @@ -5,69 +5,61 @@ 'use strict'; -import { TPromise } from 'vs/base/common/winjs.base'; import { IDisposable, dispose } from 'vs/base/common/lifecycle'; +import URI from 'vs/base/common/uri'; +import { TPromise } from 'vs/base/common/winjs.base'; +import { IEditorInput, IEditorModel, Position } from 'vs/platform/editor/common/editor'; import { EditorInput, EditorModel } from 'vs/workbench/common/editor'; -import { IEditorModel, Position, IEditorInput } from 'vs/platform/editor/common/editor'; import { Webview } from 'vs/workbench/parts/html/electron-browser/webview'; import { IPartService, Parts } from 'vs/workbench/services/part/common/partService'; -import * as vscode from 'vscode'; -import URI from 'vs/base/common/uri'; +import { WebviewEvents, WebviewInputOptions, WebviewReviver } from './webviewService'; -export interface WebviewEvents { - onMessage?(message: any): void; - onDidChangePosition?(newPosition: Position): void; - onDispose?(): void; - onDidClickLink?(link: URI, options: vscode.WebviewOptions): void; -} -export interface WebviewInputOptions extends vscode.WebviewOptions { - tryRestoreScrollPosition?: boolean; -} - -export class WebviewInput extends EditorInput { +export class WebviewEditorInput extends EditorInput { private static handlePool = 0; + public static readonly typeId = 'workbench.editors.webviewInput'; + private _name: string; private _options: WebviewInputOptions; - private _html: string; + private _html: string = ''; private _currentWebviewHtml: string = ''; - private _events: WebviewEvents | undefined; + public _events: WebviewEvents | undefined; private _container: HTMLElement; private _webview: Webview | undefined; private _webviewOwner: any; private _webviewDisposables: IDisposable[] = []; private _position?: Position; private _scrollYPercentage: number = 0; + private _state: any; + + private _revived: boolean = false; + public readonly extensionFolderPath: URI | undefined; constructor( + public readonly viewType: string, name: string, options: WebviewInputOptions, - html: string, + state: any, events: WebviewEvents, - partService: IPartService, - extensionFolderPath?: string + extensionFolderPath: string | undefined, + public readonly reviver: WebviewReviver | undefined, + @IPartService private readonly _partService: IPartService, ) { super(); this._name = name; this._options = options; - this._html = html; this._events = events; + this._state = state; if (extensionFolderPath) { this.extensionFolderPath = URI.file(extensionFolderPath); } - - const id = WebviewInput.handlePool++; - this._container = document.createElement('div'); - this._container.id = `webview-${id}`; - - partService.getContainer(Parts.EDITOR_PART).appendChild(this._container); } public getTypeId(): string { - return 'webview'; + return WebviewEditorInput.typeId; } public dispose() { @@ -119,7 +111,7 @@ export class WebviewInput extends EditorInput { return this._html; } - public setHtml(value: string): void { + public set html(value: string) { if (value === this._currentWebviewHtml) { return; } @@ -132,6 +124,14 @@ export class WebviewInput extends EditorInput { } } + public get state(): any { + return this._state; + } + + public set state(value: any) { + this._state = value; + } + public get options(): WebviewInputOptions { return this._options; } @@ -149,6 +149,12 @@ export class WebviewInput extends EditorInput { } public get container(): HTMLElement { + if (!this._container) { + const id = WebviewEditorInput.handlePool++; + this._container = document.createElement('div'); + this._container.id = `webview-${id}`; + this._partService.getContainer(Parts.EDITOR_PART).appendChild(this._container); + } return this._container; } @@ -215,10 +221,16 @@ export class WebviewInput extends EditorInput { this._currentWebviewHtml = ''; } - public onDidChangePosition(position: Position) { + public onBecameActive(position: Position) { + this._position = position; + if (this._events && this._events.onDidChangePosition) { this._events.onDidChangePosition(position); } - this._position = position; + + if (this.reviver && !this._revived) { + this._revived = true; + this.reviver.reviveWebview(this); + } } } diff --git a/src/vs/workbench/parts/webview/electron-browser/webviewInputFactory.ts b/src/vs/workbench/parts/webview/electron-browser/webviewInputFactory.ts new file mode 100644 index 00000000000..824907dca95 --- /dev/null +++ b/src/vs/workbench/parts/webview/electron-browser/webviewInputFactory.ts @@ -0,0 +1,54 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +'use strict'; + +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { IEditorInputFactory } from 'vs/workbench/common/editor'; +import { IWebviewService, WebviewInputOptions } from './webviewService'; +import { WebviewEditorInput } from './webviewInput'; + +interface SerializedWebview { + readonly viewType: string; + readonly title: string; + readonly options: WebviewInputOptions; + readonly extensionFolderPath: string; + readonly state: any; +} + +export class WebviewInputFactory implements IEditorInputFactory { + + public static readonly ID = WebviewEditorInput.typeId; + + public constructor( + @IWebviewService private readonly _webviewService: IWebviewService + ) { } + + public serialize( + input: WebviewEditorInput + ): string { + // Only attempt revival if we may have a reviver + if (!this._webviewService.canRevive(input) && !input.reviver) { + return null; + } + + const data: SerializedWebview = { + viewType: input.viewType, + title: input.getName(), + options: input.options, + extensionFolderPath: input.extensionFolderPath.fsPath, + state: input.state + }; + return JSON.stringify(data); + } + + public deserialize( + instantiationService: IInstantiationService, + serializedEditorInput: string + ): WebviewEditorInput { + const data: SerializedWebview = JSON.parse(serializedEditorInput); + return this._webviewService.createRevivableWebview(data.viewType, data.title, data.state, data.options, data.extensionFolderPath); + } +} diff --git a/src/vs/workbench/parts/webview/electron-browser/webviewService.ts b/src/vs/workbench/parts/webview/electron-browser/webviewService.ts new file mode 100644 index 00000000000..e9bead7bb42 --- /dev/null +++ b/src/vs/workbench/parts/webview/electron-browser/webviewService.ts @@ -0,0 +1,175 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +import { IDisposable, toDisposable } from 'vs/base/common/lifecycle'; +import URI from 'vs/base/common/uri'; +import { Position } from 'vs/platform/editor/common/editor'; +import { IInstantiationService, createDecorator } from 'vs/platform/instantiation/common/instantiation'; +import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { IEditorGroupService } from 'vs/workbench/services/group/common/groupService'; +import * as vscode from 'vscode'; +import { WebviewEditorInput } from './webviewInput'; + +export const IWebviewService = createDecorator('webviewService'); + +export interface IWebviewService { + _serviceBrand: any; + + createWebview( + viewType: string, + title: string, + column: Position, + options: WebviewInputOptions, + extensionFolderPath: string, + events: WebviewEvents + ): WebviewEditorInput; + + createRevivableWebview( + viewType: string, + title: string, + state: any, + options: WebviewInputOptions, + extensionFolderPath: string + ): WebviewEditorInput; + + revealWebview( + webview: WebviewEditorInput, + column: Position | undefined + ): void; + + registerReviver( + viewType: string, + reviver: WebviewReviver + ): IDisposable; + + canRevive( + input: WebviewEditorInput + ): boolean; +} + +export interface WebviewReviver { + canRevive( + webview: WebviewEditorInput + ): boolean; + + reviveWebview( + webview: WebviewEditorInput + ): void; +} + +export interface WebviewEvents { + onMessage?(message: any): void; + onDidChangePosition?(newPosition: Position): void; + onDispose?(): void; + onDidClickLink?(link: URI, options: vscode.WebviewOptions): void; +} + +export interface WebviewInputOptions extends vscode.WebviewOptions { + tryRestoreScrollPosition?: boolean; +} + +export class WebviewService implements IWebviewService { + _serviceBrand: any; + + private readonly _revivers = new Map(); + private readonly _needingRevival = new Map(); + + constructor( + @IWorkbenchEditorService private readonly _editorService: IWorkbenchEditorService, + @IInstantiationService private readonly _instantiationService: IInstantiationService, + @IEditorGroupService private readonly _editorGroupService: IEditorGroupService, + ) { } + + createWebview( + viewType: string, + title: string, + column: Position, + options: vscode.WebviewOptions, + extensionFolderPath: string, + events: WebviewEvents + ): WebviewEditorInput { + const webviewInput = this._instantiationService.createInstance(WebviewEditorInput, viewType, title, options, {}, events, extensionFolderPath, undefined); + this._editorService.openEditor(webviewInput, { pinned: true }, column); + return webviewInput; + } + + revealWebview( + webview: WebviewEditorInput, + column: Position | undefined + ): void { + if (typeof column === 'undefined') { + column = webview.position; + } + + if (webview.position === column) { + this._editorService.openEditor(webview, { preserveFocus: true }, column); + } else { + this._editorGroupService.moveEditor(webview, webview.position, column, { preserveFocus: true }); + } + } + + createRevivableWebview( + viewType: string, + title: string, + state: any, + options: WebviewInputOptions, + extensionFolderPath: string + ): WebviewEditorInput { + const webviewInput = this._instantiationService.createInstance(WebviewEditorInput, viewType, title, options, state, {}, extensionFolderPath, { + canRevive: (webview) => { + return true; + }, + reviveWebview: (webview) => { + if (!this._needingRevival.has(viewType)) { + this._needingRevival.set(viewType, []); + } + this._needingRevival.get(viewType).push(webviewInput); + this.tryRevive(viewType); + } + }); + + return webviewInput; + } + + registerReviver( + viewType: string, + reviver: WebviewReviver + ): IDisposable { + if (this._revivers.has(viewType)) { + throw new Error(`Reveriver for 'viewType' already registered`); + } + + this._revivers.set(viewType, reviver); + this.tryRevive(viewType); + + return toDisposable(() => { + this._revivers.delete(viewType); + }); + } + + canRevive( + webview: WebviewEditorInput + ): boolean { + const viewType = webview.viewType; + return this._revivers.has(viewType) && this._revivers.get(viewType).canRevive(webview); + } + + tryRevive( + viewType: string + ) { + const reviver = this._revivers.get(viewType); + if (!reviver) { + return; + } + + const toRevive = this._needingRevival.get(viewType); + if (!toRevive) { + return; + } + + for (const webview of toRevive) { + reviver.reviveWebview(webview); + } + } +} \ No newline at end of file From 62bc9143c14d10e3462de110dc26aa32755b95e9 Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Tue, 3 Apr 2018 19:19:42 -0700 Subject: [PATCH 18/49] Bump node2, fix Microsoft/vscode-internalbacklog#181 --- build/builtInExtensions.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/builtInExtensions.json b/build/builtInExtensions.json index 4bd1d55b0b7..696a6d05383 100644 --- a/build/builtInExtensions.json +++ b/build/builtInExtensions.json @@ -6,7 +6,7 @@ }, { "name": "ms-vscode.node-debug2", - "version": "1.22.6", + "version": "1.22.8", "repo": "https://github.com/Microsoft/vscode-node-debug2" } ] From 17ed2280597e68fefa00b43c8d28d0442891ea22 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Wed, 4 Apr 2018 07:35:27 +0200 Subject: [PATCH 19/49] debt - make notification#prompt() nicer to use --- .../standalone/browser/simpleServices.ts | 6 +- .../integrity/node/integrityServiceImpl.ts | 36 ++- .../common/abstractKeybindingService.test.ts | 6 +- .../notification/common/notification.ts | 37 +-- src/vs/workbench/common/notifications.ts | 2 +- .../extensions/browser/extensionsActions.ts | 65 +++-- .../electron-browser/extensionTipsService.ts | 271 ++++++++++-------- .../extensions.contribution.ts | 3 +- .../electron-browser/extensionsActions.ts | 32 ++- .../electron-browser/extensionsUtils.ts | 93 +++--- .../electron-browser/extensionsViewlet.ts | 15 +- .../node/extensionsWorkbenchService.ts | 19 +- .../extensionsTipsService.test.ts | 6 +- .../files/electron-browser/fileActions.ts | 11 +- .../browser/localizations.contribution.ts | 22 +- .../languageSurveys.contribution.ts | 32 ++- .../electron-browser/nps.contribution.ts | 31 +- .../electron-browser/task.contribution.ts | 49 ++-- .../electron-browser/terminalConfigHelper.ts | 22 +- .../electron-browser/terminalService.ts | 27 +- ...supportedWorkspaceSettings.contribution.ts | 25 +- .../parts/update/electron-browser/update.ts | 152 ++++++---- .../electron-browser/telemetryOptOut.ts | 11 +- .../page/electron-browser/welcomePage.ts | 21 +- .../node/configurationEditingService.ts | 68 ++--- .../electron-browser/extensionHost.ts | 11 +- .../electron-browser/extensionService.ts | 33 ++- .../files/electron-browser/fileService.ts | 50 ++-- .../common/notificationService.ts | 74 ++--- .../workspace/node/workspaceEditingService.ts | 28 +- .../api/extHostMessagerService.test.ts | 8 +- .../workbench/test/workbenchTestServices.ts | 6 +- 32 files changed, 672 insertions(+), 600 deletions(-) diff --git a/src/vs/editor/standalone/browser/simpleServices.ts b/src/vs/editor/standalone/browser/simpleServices.ts index 3a45cc198a9..a09429f1137 100644 --- a/src/vs/editor/standalone/browser/simpleServices.ts +++ b/src/vs/editor/standalone/browser/simpleServices.ts @@ -38,7 +38,7 @@ import { ResolvedKeybindingItem } from 'vs/platform/keybinding/common/resolvedKe import { OS } from 'vs/base/common/platform'; import { IRange } from 'vs/editor/common/core/range'; import { ITextModel } from 'vs/editor/common/model'; -import { INotificationService, INotification, INotificationHandle, NoOpNotification, PromptOption } from 'vs/platform/notification/common/notification'; +import { INotificationService, INotification, INotificationHandle, NoOpNotification, IPromptChoice } from 'vs/platform/notification/common/notification'; import { IConfirmation, IConfirmationResult, IDialogService, IDialogOptions } from 'vs/platform/dialogs/common/dialogs'; import { IPosition, Position as Pos } from 'vs/editor/common/core/position'; @@ -297,8 +297,8 @@ export class SimpleNotificationService implements INotificationService { return SimpleNotificationService.NO_OP; } - public prompt(severity: Severity, message: string, choices: PromptOption[]): TPromise { - return TPromise.as(0); + public prompt(severity: Severity, message: string, choices: IPromptChoice[], onCancel?: () => void): INotificationHandle { + return SimpleNotificationService.NO_OP; } } diff --git a/src/vs/platform/integrity/node/integrityServiceImpl.ts b/src/vs/platform/integrity/node/integrityServiceImpl.ts index 34fd1e44a4f..5a45065e013 100644 --- a/src/vs/platform/integrity/node/integrityServiceImpl.ts +++ b/src/vs/platform/integrity/node/integrityServiceImpl.ts @@ -14,7 +14,7 @@ import URI from 'vs/base/common/uri'; import Severity from 'vs/base/common/severity'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; import { ILifecycleService, LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; -import { INotificationService, PromptOption } from 'vs/platform/notification/common/notification'; +import { INotificationService } from 'vs/platform/notification/common/notification'; interface IStorageData { dontShowPrompt: boolean; @@ -82,26 +82,24 @@ export class IntegrityServiceImpl implements IIntegrityService { private _prompt(): void { const storedData = this._storage.get(); if (storedData && storedData.dontShowPrompt && storedData.commit === product.commit) { - // Do not prompt - return; + return; // Do not prompt } - const choices: PromptOption[] = [nls.localize('integrity.moreInformation', "More Information"), { label: nls.localize('integrity.dontShowAgain', "Don't Show Again") }]; - - this.notificationService.prompt(Severity.Warning, nls.localize('integrity.prompt', "Your {0} installation appears to be corrupt. Please reinstall.", product.nameShort), choices).then(choice => { - switch (choice) { - case 0 /* More Information */: - const uri = URI.parse(product.checksumFailMoreInfoUrl); - window.open(uri.toString(true)); - break; - case 1 /* Do not show again */: - this._storage.set({ - dontShowPrompt: true, - commit: product.commit - }); - break; - } - }); + this.notificationService.prompt( + Severity.Warning, + nls.localize('integrity.prompt', "Your {0} installation appears to be corrupt. Please reinstall.", product.nameShort), + [ + { + label: nls.localize('integrity.moreInformation', "More Information"), + run: () => window.open(URI.parse(product.checksumFailMoreInfoUrl).toString(true)) + }, + { + label: nls.localize('integrity.dontShowAgain', "Don't Show Again"), + isSecondary: true, + run: () => this._storage.set({ dontShowPrompt: true, commit: product.commit }) + } + ] + ); } public isPure(): Thenable { diff --git a/src/vs/platform/keybinding/test/common/abstractKeybindingService.test.ts b/src/vs/platform/keybinding/test/common/abstractKeybindingService.test.ts index 6c00827eac5..44ea5de8c5d 100644 --- a/src/vs/platform/keybinding/test/common/abstractKeybindingService.test.ts +++ b/src/vs/platform/keybinding/test/common/abstractKeybindingService.test.ts @@ -19,7 +19,7 @@ import { ResolvedKeybindingItem } from 'vs/platform/keybinding/common/resolvedKe import { OS } from 'vs/base/common/platform'; import { IKeyboardEvent } from 'vs/platform/keybinding/common/keybinding'; import { NullTelemetryService } from 'vs/platform/telemetry/common/telemetryUtils'; -import { INotificationService, NoOpNotification, INotification } from 'vs/platform/notification/common/notification'; +import { INotificationService, NoOpNotification, INotification, IPromptChoice } from 'vs/platform/notification/common/notification'; function createContext(ctx: any) { return { @@ -139,8 +139,8 @@ suite('AbstractKeybindingService', () => { showMessageCalls.push({ sev: Severity.Error, message }); return new NoOpNotification(); }, - prompt: () => { - return TPromise.as(0); + prompt(severity: Severity, message: string, choices: IPromptChoice[], onCancel?: () => void) { + throw new Error('not implemented'); } }; diff --git a/src/vs/platform/notification/common/notification.ts b/src/vs/platform/notification/common/notification.ts index 62b7e6751cd..7f1c3d4c507 100644 --- a/src/vs/platform/notification/common/notification.ts +++ b/src/vs/platform/notification/common/notification.ts @@ -10,7 +10,6 @@ import { createDecorator } from 'vs/platform/instantiation/common/instantiation' import { IDisposable } from 'vs/base/common/lifecycle'; import { IAction } from 'vs/base/common/actions'; import { Event, Emitter } from 'vs/base/common/event'; -import { TPromise } from 'vs/base/common/winjs.base'; export import Severity = BaseSeverity; @@ -121,26 +120,30 @@ export interface INotificationHandle extends IDisposable { updateActions(actions?: INotificationActions): void; } +export interface IPromptChoice { -/** - * Primary choices show up as buttons in the notification below the message. - */ -export type PrimaryPromptChoice = string; - -/** - * Secondary choices show up under the gear icon in the header of the notification. - */ -export interface SecondaryPromptChoice { + /** + * Label to show for the choice to the user. + */ label: string; /** - * Wether to keep the notification open after the secondary choice was selected + * Primary choices show up as buttons in the notification below the message. + * Secondary choices show up under the gear icon in the header of the notification. + */ + isSecondary?: boolean; + + /** + * Wether to keep the notification open after the choice was selected * by the user. By default, will close the notification upon click. */ keepOpen?: boolean; -} -export type PromptOption = PrimaryPromptChoice | SecondaryPromptChoice; + /** + * Triggered when the user selects the choice. + */ + run: () => void; +} /** * A service to bring up notifications and non-modal prompts. @@ -185,10 +188,12 @@ export interface INotificationService { * Shows a prompt in the notification area with the provided choices. The prompt * is non-modal. If you want to show a modal dialog instead, use `IDialogService`. * - * @returns a promise that will resolve to the index of the choice that was picked. - * The promise can be cancelled to hide the notification prompt. + * @param onCancel will be called if the user closed the notification without picking + * any of the provided choices. + * + * @returns a handle on the notification to e.g. hide it or update message, buttons, etc. */ - prompt(severity: Severity, message: string, choices: PromptOption[]): TPromise; + prompt(severity: Severity, message: string, choices: IPromptChoice[], onCancel?: () => void): INotificationHandle; } export class NoOpNotification implements INotificationHandle { diff --git a/src/vs/workbench/common/notifications.ts b/src/vs/workbench/common/notifications.ts index 41b614cd182..88fd2bc2e44 100644 --- a/src/vs/workbench/common/notifications.ts +++ b/src/vs/workbench/common/notifications.ts @@ -582,7 +582,7 @@ export class NotificationViewItem implements INotificationViewItem { } for (let i = 0; i < primaryActions.length; i++) { - if (primaryActions[i].id !== otherPrimaryActions[i].id) { + if ((primaryActions[i].id + primaryActions[i].label) !== (otherPrimaryActions[i].id + otherPrimaryActions[i].label)) { return false; } } diff --git a/src/vs/workbench/parts/extensions/browser/extensionsActions.ts b/src/vs/workbench/parts/extensions/browser/extensionsActions.ts index afc0d173ab5..df3e59bcee6 100644 --- a/src/vs/workbench/parts/extensions/browser/extensionsActions.ts +++ b/src/vs/workbench/parts/extensions/browser/extensionsActions.ts @@ -10,7 +10,7 @@ import { IAction, Action } from 'vs/base/common/actions'; import { Throttler } from 'vs/base/common/async'; import * as DOM from 'vs/base/browser/dom'; import * as paths from 'vs/base/common/paths'; -import { Event, once } from 'vs/base/common/event'; +import { Event } from 'vs/base/common/event'; import * as json from 'vs/base/common/json'; import { ActionItem, IActionItem, Separator } from 'vs/base/browser/ui/actionbar/actionbar'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; @@ -49,21 +49,23 @@ import { IEnvironmentService } from 'vs/platform/environment/common/environment' import { IQuickOpenService, IPickOpenEntry } from 'vs/platform/quickOpen/common/quickOpen'; const promptDownloadManually = (extension: IExtension, message: string, instantiationService: IInstantiationService, notificationService: INotificationService, openerService: IOpenerService) => { - notificationService.prompt(Severity.Error, message, [localize('download', "Download Manually")]).done(choice => { - if (choice === 0) { - openerService.open(URI.parse(extension.downloadUrl)).then(() => { - const action = instantiationService.createInstance(InstallVSIXAction, InstallVSIXAction.ID, InstallVSIXAction.LABEL); - const handle = notificationService.notify({ - severity: Severity.Info, - message: localize('install vsix', 'Once downloaded, please manually install the downloaded VSIX of \'{0}\'.', extension.id), - actions: { - primary: [action] + notificationService.prompt(Severity.Error, message, [{ + label: localize('download', "Download Manually"), + run: () => openerService.open(URI.parse(extension.downloadUrl)).then(() => { + notificationService.prompt( + Severity.Info, + localize('install vsix', 'Once downloaded, please manually install the downloaded VSIX of \'{0}\'.', extension.id), + [{ + label: InstallVSIXAction.LABEL, + run: () => { + const action = instantiationService.createInstance(InstallVSIXAction, InstallVSIXAction.ID, InstallVSIXAction.LABEL); + action.run(); + action.dispose(); } - }); - once(handle.onDidDispose)(() => action.dispose()); - }); - } - }); + }] + ); + }) + }]); }; export class InstallAction extends Action { @@ -1882,13 +1884,13 @@ export class InstallVSIXAction extends Action { label = InstallVSIXAction.LABEL, @IExtensionsWorkbenchService private extensionsWorkbenchService: IExtensionsWorkbenchService, @INotificationService private notificationService: INotificationService, - @IWindowService private windowsService: IWindowService + @IWindowService private windowService: IWindowService ) { super(id, label, 'extension-action install-vsix', true); } run(): TPromise { - return this.windowsService.showOpenDialog({ + return this.windowService.showOpenDialog({ title: localize('installFromVSIX', "Install from VSIX"), filters: [{ name: 'VSIX Extensions', extensions: ['vsix'] }], properties: ['openFile'], @@ -1899,19 +1901,19 @@ export class InstallVSIXAction extends Action { } return TPromise.join(result.map(vsix => this.extensionsWorkbenchService.install(vsix))).then(() => { - return this.notificationService.prompt(Severity.Info, localize('InstallVSIXAction.success', "Successfully installed the extension. Reload to enable it."), [localize('InstallVSIXAction.reloadNow', "Reload Now")]).then(choice => { - if (choice === 0) { - return this.windowsService.reloadWindow(); - } - - return TPromise.as(undefined); - }); + this.notificationService.prompt( + Severity.Info, + localize('InstallVSIXAction.success', "Successfully installed the extension. Reload to enable it."), + [{ + label: localize('InstallVSIXAction.reloadNow', "Reload Now"), + run: () => this.windowService.reloadWindow() + }] + ); }); }); } } - export class ReinstallAction extends Action { static readonly ID = 'workbench.extensions.action.reinstall'; @@ -1955,11 +1957,14 @@ export class ReinstallAction extends Action { private reinstallExtension(extension: IExtension): TPromise { return this.extensionsWorkbenchService.reinstall(extension) .then(() => { - this.notificationService.prompt(Severity.Info, localize('ReinstallAction.success', "Successfully reinstalled the extension."), [localize('ReinstallAction.reloadNow', "Reload Now")]).done(choice => { - if (choice === 0) { - this.windowService.reloadWindow(); - } - }); + this.notificationService.prompt( + Severity.Info, + localize('ReinstallAction.success', "Successfully reinstalled the extension."), + [{ + label: localize('ReinstallAction.reloadNow', "Reload Now"), + run: () => this.windowService.reloadWindow() + }] + ); }, error => this.notificationService.error(error)); } } diff --git a/src/vs/workbench/parts/extensions/electron-browser/extensionTipsService.ts b/src/vs/workbench/parts/extensions/electron-browser/extensionTipsService.ts index 0b7b540ca90..93215f58bfd 100644 --- a/src/vs/workbench/parts/extensions/electron-browser/extensionTipsService.ts +++ b/src/vs/workbench/parts/extensions/electron-browser/extensionTipsService.ts @@ -36,7 +36,7 @@ import { asJson } from 'vs/base/node/request'; import { isNumber } from 'vs/base/common/types'; import { language, LANGUAGE_DEFAULT } from 'vs/base/common/platform'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; -import { INotificationService, PromptOption } from 'vs/platform/notification/common/notification'; +import { INotificationService } from 'vs/platform/notification/common/notification'; interface IExtensionsContent { recommendations: string[]; @@ -160,15 +160,13 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe if (!pager || !pager.firstPage || !pager.firstPage.length) { return; } - const message = localize('showLanguagePackExtensions', "The Marketplace has extensions that can help localizing VS Code to '{0}' locale", language); - const options: PromptOption[] = [ - searchMarketplace, - { label: choiceNever } - ]; - this.notificationService.prompt(Severity.Info, message, options).done(choice => { - switch (choice) { - case 0 /* Search Marketplace */: + this.notificationService.prompt( + Severity.Info, + localize('showLanguagePackExtensions', "The Marketplace has extensions that can help localizing VS Code to '{0}' locale", language), + [{ + label: searchMarketplace, + run: () => { /* __GDPR__ "languagePackSuggestion:popup" : { "userReaction" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, @@ -182,8 +180,12 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe viewlet.search(`tag:lp-${language}`); viewlet.focus(); }); - break; - case 1 /* Never show again */: + } + }, + { + label: choiceNever, + isSecondary: true, + run: () => { languagePackSuggestionIgnoreList.push(language); this.storageService.store( 'extensionsAssistant/languagePackSuggestionIgnore', @@ -197,17 +199,18 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe } */ this.telemetryService.publicLog('languagePackSuggestion:popup', { userReaction: 'neverShowAgain', language }); - break; - } - }, () => { - /* __GDPR__ - "languagePackSuggestion:popup" : { - "userReaction" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, - "language": { "classification": "PublicNonPersonalData", "purpose": "FeatureInsight" } } - */ - this.telemetryService.publicLog('languagePackSuggestion:popup', { userReaction: 'cancelled', language }); - }); + }], + () => { + /* __GDPR__ + "languagePackSuggestion:popup" : { + "userReaction" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "language": { "classification": "PublicNonPersonalData", "purpose": "FeatureInsight" } + } + */ + this.telemetryService.publicLog('languagePackSuggestion:popup', { userReaction: 'cancelled', language }); + } + ); }); }); } @@ -487,26 +490,25 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe message = localize('reallyRecommendedExtensionPack', "The '{0}' extension pack is recommended for this file type.", name); } - const recommendationsAction = this.instantiationService.createInstance(ShowRecommendedExtensionsAction, ShowRecommendedExtensionsAction.ID, localize('showRecommendations', "Show Recommendations")); - const installAction = this.instantiationService.createInstance(InstallRecommendedExtensionAction, id); - const options: PromptOption[] = [ - localize('install', 'Install'), - recommendationsAction.label, - { label: choiceNever } - ]; - - this.notificationService.prompt(Severity.Info, message, options).done(choice => { - switch (choice) { - case 0 /* Install */: + this.notificationService.prompt(Severity.Info, message, + [{ + label: localize('install', 'Install'), + run: () => { /* __GDPR__ - "extensionRecommendations:popup" : { - "userReaction" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, - "extensionId": { "classification": "PublicNonPersonalData", "purpose": "FeatureInsight" } - } + "extensionRecommendations:popup" : { + "userReaction" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "extensionId": { "classification": "PublicNonPersonalData", "purpose": "FeatureInsight" } + } */ this.telemetryService.publicLog('extensionRecommendations:popup', { userReaction: 'install', extensionId: name }); - return installAction.run(); - case 1 /* Show Recommendations */: + + const installAction = this.instantiationService.createInstance(InstallRecommendedExtensionAction, id); + installAction.run(); + installAction.dispose(); + } + }, { + label: localize('showRecommendations', "Show Recommendations"), + run: () => { /* __GDPR__ "extensionRecommendations:popup" : { "userReaction" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, @@ -514,8 +516,15 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe } */ this.telemetryService.publicLog('extensionRecommendations:popup', { userReaction: 'show', extensionId: name }); - return recommendationsAction.run(); - case 2 /* Never show again */: + + const recommendationsAction = this.instantiationService.createInstance(ShowRecommendedExtensionsAction, ShowRecommendedExtensionsAction.ID, localize('showRecommendations', "Show Recommendations")); + recommendationsAction.run(); + recommendationsAction.dispose(); + } + }, { + label: choiceNever, + isSecondary: true, + run: () => { importantRecommendationsIgnoreList.push(id); this.storageService.store( 'extensionsAssistant/importantRecommendationsIgnore', @@ -529,17 +538,19 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe } */ this.telemetryService.publicLog('extensionRecommendations:popup', { userReaction: 'neverShowAgain', extensionId: name }); - return this.ignoreExtensionRecommendations(); - } - }, () => { - /* __GDPR__ - "extensionRecommendations:popup" : { - "userReaction" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, - "extensionId": { "classification": "PublicNonPersonalData", "purpose": "FeatureInsight" } + this.ignoreExtensionRecommendations(); } - */ - this.telemetryService.publicLog('extensionRecommendations:popup', { userReaction: 'cancelled', extensionId: name }); - }); + }], + () => { + /* __GDPR__ + "extensionRecommendations:popup" : { + "userReaction" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "extensionId": { "classification": "PublicNonPersonalData", "purpose": "FeatureInsight" } + } + */ + this.telemetryService.publicLog('extensionRecommendations:popup', { userReaction: 'cancelled', extensionId: name }); + } + ); }); const mimeTypesPromise = this.getMimeTypes(uri.fsPath); @@ -568,15 +579,12 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe return; } - const message = localize('showLanguageExtensions', "The Marketplace has extensions that can help with '.{0}' files", fileExtension); - const options: PromptOption[] = [ - searchMarketplace, - { label: choiceNever } - ]; - - this.notificationService.prompt(Severity.Info, message, options).done(choice => { - switch (choice) { - case 0 /* Search Marketplace */: + this.notificationService.prompt( + Severity.Info, + localize('showLanguageExtensions', "The Marketplace has extensions that can help with '.{0}' files", fileExtension), + [{ + label: searchMarketplace, + run: () => { /* __GDPR__ "fileExtensionSuggestion:popup" : { "userReaction" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, @@ -590,8 +598,11 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe viewlet.search(`ext:${fileExtension}`); viewlet.focus(); }); - break; - case 1 /* Never show again */: + } + }, { + label: choiceNever, + isSecondary: true, + run: () => { fileExtensionSuggestionIgnoreList.push(fileExtension); this.storageService.store( 'extensionsAssistant/fileExtensionsSuggestionIgnore', @@ -605,17 +616,18 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe } */ this.telemetryService.publicLog('fileExtensionSuggestion:popup', { userReaction: 'neverShowAgain', fileExtension: fileExtension }); - break; - } - }, () => { - /* __GDPR__ - "fileExtensionSuggestion:popup" : { - "userReaction" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, - "fileExtension": { "classification": "PublicNonPersonalData", "purpose": "FeatureInsight" } } - */ - this.telemetryService.publicLog('fileExtensionSuggestion:popup', { userReaction: 'cancelled', fileExtension: fileExtension }); - }); + }], + () => { + /* __GDPR__ + "fileExtensionSuggestion:popup" : { + "userReaction" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "fileExtension": { "classification": "PublicNonPersonalData", "purpose": "FeatureInsight" } + } + */ + this.telemetryService.publicLog('fileExtensionSuggestion:popup', { userReaction: 'cancelled', fileExtension: fileExtension }); + } + ); }); }); }); @@ -635,73 +647,88 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe .filter(id => local.every(local => `${local.manifest.publisher.toLowerCase()}.${local.manifest.name.toLowerCase()}` !== id)); if (!recommendations.length) { - return; + return TPromise.as(void 0); } - const message = localize('workspaceRecommended', "This workspace has extension recommendations."); - const showAction = this.instantiationService.createInstance(ShowRecommendedExtensionsAction, ShowRecommendedExtensionsAction.ID, localize('showRecommendations', "Show Recommendations")); - const installAllAction = this.instantiationService.createInstance(InstallWorkspaceRecommendedExtensionsAction, InstallWorkspaceRecommendedExtensionsAction.ID, localize('installAll', "Install All")); + return new TPromise(c => { + this.notificationService.prompt( + Severity.Info, + localize('workspaceRecommended', "This workspace has extension recommendations."), + [{ + label: localize('installAll', "Install All"), + run: () => { + /* __GDPR__ + "extensionRecommendations:popup" : { + "userReaction" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" } + } + */ + this.telemetryService.publicLog('extensionWorkspaceRecommendations:popup', { userReaction: 'install' }); - const options: PromptOption[] = [ - installAllAction.label, - showAction.label, - { label: choiceNever } - ]; + const installAllAction = this.instantiationService.createInstance(InstallWorkspaceRecommendedExtensionsAction, InstallWorkspaceRecommendedExtensionsAction.ID, localize('installAll', "Install All")); + installAllAction.run(); + installAllAction.dispose(); - return this.notificationService.prompt(Severity.Info, message, options).done(choice => { - switch (choice) { - case 0 /* Install */: + c(void 0); + } + }, { + label: localize('showRecommendations', "Show Recommendations"), + run: () => { + /* __GDPR__ + "extensionRecommendations:popup" : { + "userReaction" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" } + } + */ + this.telemetryService.publicLog('extensionWorkspaceRecommendations:popup', { userReaction: 'show' }); + + const showAction = this.instantiationService.createInstance(ShowRecommendedExtensionsAction, ShowRecommendedExtensionsAction.ID, localize('showRecommendations', "Show Recommendations")); + showAction.run(); + showAction.dispose(); + + c(void 0); + } + }, { + label: choiceNever, + isSecondary: true, + run: () => { + /* __GDPR__ + "extensionRecommendations:popup" : { + "userReaction" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" } + } + */ + this.telemetryService.publicLog('extensionWorkspaceRecommendations:popup', { userReaction: 'neverShowAgain' }); + this.storageService.store(storageKey, true, StorageScope.WORKSPACE); + + c(void 0); + } + }], + () => { /* __GDPR__ "extensionRecommendations:popup" : { "userReaction" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" } } */ - this.telemetryService.publicLog('extensionWorkspaceRecommendations:popup', { userReaction: 'install' }); - return installAllAction.run(); - case 1 /* Show Recommendations */: - /* __GDPR__ - "extensionRecommendations:popup" : { - "userReaction" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" } - } - */ - this.telemetryService.publicLog('extensionWorkspaceRecommendations:popup', { userReaction: 'show' }); - return showAction.run(); - case 2 /* Never show again */: - /* __GDPR__ - "extensionRecommendations:popup" : { - "userReaction" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" } - } - */ - this.telemetryService.publicLog('extensionWorkspaceRecommendations:popup', { userReaction: 'neverShowAgain' }); - return this.storageService.store(storageKey, true, StorageScope.WORKSPACE); - } - }, () => { - /* __GDPR__ - "extensionRecommendations:popup" : { - "userReaction" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" } + this.telemetryService.publicLog('extensionWorkspaceRecommendations:popup', { userReaction: 'cancelled' }); + + c(void 0); } - */ - this.telemetryService.publicLog('extensionWorkspaceRecommendations:popup', { userReaction: 'cancelled' }); + ); }); }); }); } private ignoreExtensionRecommendations() { - const message = localize('ignoreExtensionRecommendations', "Do you want to ignore all extension recommendations?"); - const options = [ - localize('ignoreAll', "Yes, Ignore All"), - localize('no', "No") - ]; - - this.notificationService.prompt(Severity.Info, message, options).done(choice => { - switch (choice) { - case 0: // If the user ignores the current message and selects different file type - return this.setIgnoreRecommendationsConfig(true); - case 1: - return this.setIgnoreRecommendationsConfig(false); - } - }); + this.notificationService.prompt( + Severity.Info, + localize('ignoreExtensionRecommendations', "Do you want to ignore all extension recommendations?"), + [{ + label: localize('ignoreAll', "Yes, Ignore All"), + run: () => this.setIgnoreRecommendationsConfig(true) + }, { + label: localize('no', "No"), + run: () => this.setIgnoreRecommendationsConfig(false) + }] + ); } private _suggestBasedOnExecutables(): TPromise { diff --git a/src/vs/workbench/parts/extensions/electron-browser/extensions.contribution.ts b/src/vs/workbench/parts/extensions/electron-browser/extensions.contribution.ts index 6cdd37b47c3..9fcd5142a61 100644 --- a/src/vs/workbench/parts/extensions/electron-browser/extensions.contribution.ts +++ b/src/vs/workbench/parts/extensions/electron-browser/extensions.contribution.ts @@ -34,7 +34,7 @@ import * as jsonContributionRegistry from 'vs/platform/jsonschemas/common/jsonCo import { ExtensionsConfigurationSchema, ExtensionsConfigurationSchemaId } from 'vs/workbench/parts/extensions/common/extensionsFileTemplate'; import { CommandsRegistry } from 'vs/platform/commands/common/commands'; import { ServicesAccessor, IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { KeymapExtensions, BetterMergeDisabled } from 'vs/workbench/parts/extensions/electron-browser/extensionsUtils'; +import { KeymapExtensions } from 'vs/workbench/parts/extensions/electron-browser/extensionsUtils'; import { adoptToGalleryExtensionId } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; import { GalleryExtensionsHandler, ExtensionsHandler } from 'vs/workbench/parts/extensions/browser/extensionsQuickOpen'; import { EditorDescriptor, IEditorRegistry, Extensions as EditorExtensions } from 'vs/workbench/browser/editor'; @@ -54,7 +54,6 @@ workbenchRegistry.registerWorkbenchContribution(StatusUpdater, LifecyclePhase.Ru workbenchRegistry.registerWorkbenchContribution(MaliciousExtensionChecker, LifecyclePhase.Eventually); workbenchRegistry.registerWorkbenchContribution(ConfigureRecommendedExtensionsCommandsContributor, LifecyclePhase.Eventually); workbenchRegistry.registerWorkbenchContribution(KeymapExtensions, LifecyclePhase.Running); -workbenchRegistry.registerWorkbenchContribution(BetterMergeDisabled, LifecyclePhase.Running); Registry.as(OutputExtensions.OutputChannels) .registerChannel(ExtensionsChannelId, ExtensionsLabel); diff --git a/src/vs/workbench/parts/extensions/electron-browser/extensionsActions.ts b/src/vs/workbench/parts/extensions/electron-browser/extensionsActions.ts index 0b284cca2bd..06d3286942b 100644 --- a/src/vs/workbench/parts/extensions/electron-browser/extensionsActions.ts +++ b/src/vs/workbench/parts/extensions/electron-browser/extensionsActions.ts @@ -59,13 +59,13 @@ export class InstallVSIXAction extends Action { label = InstallVSIXAction.LABEL, @IExtensionsWorkbenchService private extensionsWorkbenchService: IExtensionsWorkbenchService, @INotificationService private notificationService: INotificationService, - @IWindowService private windowsService: IWindowService + @IWindowService private windowService: IWindowService ) { super(id, label, 'extension-action install-vsix', true); } run(): TPromise { - return this.windowsService.showOpenDialog({ + return this.windowService.showOpenDialog({ title: localize('installFromVSIX', "Install from VSIX"), filters: [{ name: 'VSIX Extensions', extensions: ['vsix'] }], properties: ['openFile'], @@ -76,13 +76,14 @@ export class InstallVSIXAction extends Action { } return TPromise.join(result.map(vsix => this.extensionsWorkbenchService.install(vsix))).then(() => { - return this.notificationService.prompt(Severity.Info, localize('InstallVSIXAction.success', "Successfully installed the extension. Reload to enable it."), [localize('InstallVSIXAction.reloadNow', "Reload Now")]).then(choice => { - if (choice === 0) { - return this.windowsService.reloadWindow(); - } - - return TPromise.as(undefined); - }); + this.notificationService.prompt( + Severity.Info, + localize('InstallVSIXAction.success', "Successfully installed the extension. Reload to enable it."), + [{ + label: localize('InstallVSIXAction.reloadNow', "Reload Now"), + run: () => this.windowService.reloadWindow() + }] + ); }); }); } @@ -131,11 +132,14 @@ export class ReinstallAction extends Action { private reinstallExtension(extension: IExtension): TPromise { return this.extensionsWorkbenchService.reinstall(extension) .then(() => { - this.notificationService.prompt(Severity.Info, localize('ReinstallAction.success', "Successfully reinstalled the extension."), [localize('ReinstallAction.reloadNow', "Reload Now")]).done(choice => { - if (choice === 0) { - this.windowService.reloadWindow(); - } - }); + this.notificationService.prompt( + Severity.Info, + localize('ReinstallAction.success', "Successfully reinstalled the extension."), + [{ + label: localize('ReinstallAction.reloadNow', "Reload Now"), + run: () => this.windowService.reloadWindow() + }] + ); }, error => this.notificationService.error(error)); } } \ No newline at end of file diff --git a/src/vs/workbench/parts/extensions/electron-browser/extensionsUtils.ts b/src/vs/workbench/parts/extensions/electron-browser/extensionsUtils.ts index 42f909ce560..104644f1c7f 100644 --- a/src/vs/workbench/parts/extensions/electron-browser/extensionsUtils.ts +++ b/src/vs/workbench/parts/extensions/electron-browser/extensionsUtils.ts @@ -8,17 +8,15 @@ import * as arrays from 'vs/base/common/arrays'; import { localize } from 'vs/nls'; import { Event, chain, anyEvent, debounceEvent } from 'vs/base/common/event'; -import { onUnexpectedError, canceled } from 'vs/base/common/errors'; +import { onUnexpectedError } from 'vs/base/common/errors'; import { TPromise } from 'vs/base/common/winjs.base'; import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; -import { IExtensionManagementService, ILocalExtension, IExtensionEnablementService, IExtensionTipsService, LocalExtensionType, IExtensionIdentifier, EnablementState } from 'vs/platform/extensionManagement/common/extensionManagement'; -import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; +import { IExtensionManagementService, ILocalExtension, IExtensionEnablementService, IExtensionTipsService, IExtensionIdentifier, EnablementState } from 'vs/platform/extensionManagement/common/extensionManagement'; import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle'; import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; import { ServicesAccessor, IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; -import { BetterMergeDisabledNowKey, BetterMergeId, areSameExtensions, adoptToGalleryExtensionId } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; +import { areSameExtensions, adoptToGalleryExtensionId } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; import { getIdAndVersionFromLocalExtensionId } from 'vs/platform/extensionManagement/node/extensionManagementUtil'; import { Severity, INotificationService } from 'vs/platform/notification/common/notification'; @@ -63,36 +61,37 @@ export class KeymapExtensions implements IWorkbenchContribution { }); } - private promptForDisablingOtherKeymaps(newKeymap: IExtensionStatus, oldKeymaps: IExtensionStatus[]): TPromise { - const message = localize('disableOtherKeymapsConfirmation', "Disable other keymaps ({0}) to avoid conflicts between keybindings?", oldKeymaps.map(k => `'${k.local.manifest.displayName}'`).join(', ')); - const options = [ - localize('yes', "Yes"), - localize('no', "No") - ]; - return this.notificationService.prompt(Severity.Info, message, options) - .then(value => { - const confirmed = value === 0; - const telemetryData: { [key: string]: any; } = { - newKeymap: newKeymap.identifier, - oldKeymaps: oldKeymaps.map(k => k.identifier), - confirmed - }; - /* __GDPR__ - "disableOtherKeymaps" : { - "newKeymap": { "${inline}": [ "${ExtensionIdentifier}" ] }, - "oldKeymaps": { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, - "confirmed" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true } - } - */ - this.telemetryService.publicLog('disableOtherKeymaps', telemetryData); - if (confirmed) { - return TPromise.join(oldKeymaps.map(keymap => { - return this.extensionEnablementService.setEnablement(keymap.local, EnablementState.Disabled); - })); + private promptForDisablingOtherKeymaps(newKeymap: IExtensionStatus, oldKeymaps: IExtensionStatus[]): void { + const onPrompt = (confirmed: boolean) => { + const telemetryData: { [key: string]: any; } = { + newKeymap: newKeymap.identifier, + oldKeymaps: oldKeymaps.map(k => k.identifier), + confirmed + }; + /* __GDPR__ + "disableOtherKeymaps" : { + "newKeymap": { "${inline}": [ "${ExtensionIdentifier}" ] }, + "oldKeymaps": { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "confirmed" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true } } - return undefined; - }, error => TPromise.wrapError(canceled())) - .then(() => { /* drop resolved value */ }); + */ + this.telemetryService.publicLog('disableOtherKeymaps', telemetryData); + if (confirmed) { + TPromise.join(oldKeymaps.map(keymap => { + return this.extensionEnablementService.setEnablement(keymap.local, EnablementState.Disabled); + })); + } + }; + + this.notificationService.prompt(Severity.Info, localize('disableOtherKeymapsConfirmation', "Disable other keymaps ({0}) to avoid conflicts between keybindings?", oldKeymaps.map(k => `'${k.local.manifest.displayName}'`).join(', ')), + [{ + label: localize('yes', "Yes"), + run: () => onPrompt(true) + }, { + label: localize('no', "No"), + run: () => onPrompt(false) + }] + ); } dispose(): void { @@ -143,29 +142,3 @@ export function isKeymapExtension(tipsService: IExtensionTipsService, extension: function stripVersion(id: string): string { return getIdAndVersionFromLocalExtensionId(id).id; } - -export class BetterMergeDisabled implements IWorkbenchContribution { - - constructor( - @IStorageService storageService: IStorageService, - @INotificationService notificationService: INotificationService, - @IExtensionService extensionService: IExtensionService, - @IExtensionManagementService extensionManagementService: IExtensionManagementService, - @ITelemetryService telemetryService: ITelemetryService, - ) { - extensionService.whenInstalledExtensionsRegistered().then(() => { - if (storageService.getBoolean(BetterMergeDisabledNowKey, StorageScope.GLOBAL, false)) { - storageService.remove(BetterMergeDisabledNowKey, StorageScope.GLOBAL); - - notificationService.prompt(Severity.Info, localize('betterMergeDisabled', "The Better Merge extension is now built-in, the installed extension was disabled and can be uninstalled."), [localize('uninstall', "Uninstall")]).then(choice => { - if (choice === 0) { - extensionManagementService.getInstalled(LocalExtensionType.User).then(extensions => { - return Promise.all(extensions.filter(e => stripVersion(e.identifier.id) === BetterMergeId) - .map(e => extensionManagementService.uninstall(e, true))); - }); - } - }); - } - }); - } -} diff --git a/src/vs/workbench/parts/extensions/electron-browser/extensionsViewlet.ts b/src/vs/workbench/parts/extensions/electron-browser/extensionsViewlet.ts index 516a1d76d6c..ca6ef161be3 100644 --- a/src/vs/workbench/parts/extensions/electron-browser/extensionsViewlet.ts +++ b/src/vs/workbench/parts/extensions/electron-browser/extensionsViewlet.ts @@ -535,13 +535,14 @@ export class MaliciousExtensionChecker implements IWorkbenchContribution { if (maliciousExtensions.length) { return TPromise.join(maliciousExtensions.map(e => this.extensionsManagementService.uninstall(e, true).then(() => { - return this.notificationService.prompt(Severity.Warning, localize('malicious warning', "We have uninstalled '{0}' which was reported to be problematic.", getGalleryExtensionIdFromLocal(e)), [localize('reloadNow', "Reload Now")]).then(choice => { - if (choice === 0) { - return this.windowService.reloadWindow(); - } - - return TPromise.as(undefined); - }); + this.notificationService.prompt( + Severity.Warning, + localize('malicious warning', "We have uninstalled '{0}' which was reported to be problematic.", getGalleryExtensionIdFromLocal(e)), + [{ + label: localize('reloadNow', "Reload Now"), + run: () => this.windowService.reloadWindow() + }] + ); }))); } else { return TPromise.as(null); diff --git a/src/vs/workbench/parts/extensions/node/extensionsWorkbenchService.ts b/src/vs/workbench/parts/extensions/node/extensionsWorkbenchService.ts index 77f8b5b09b5..cabf01e5411 100644 --- a/src/vs/workbench/parts/extensions/node/extensionsWorkbenchService.ts +++ b/src/vs/workbench/parts/extensions/node/extensionsWorkbenchService.ts @@ -1003,17 +1003,14 @@ export class ExtensionsWorkbenchService implements IExtensionsWorkbenchService, return this.windowService.show().then(() => { return this.open(extension).then(() => { - const message = nls.localize('installConfirmation', "Would you like to install the '{0}' extension?", extension.displayName, extension.publisher); - const options = [ - nls.localize('install', "Install") - ]; - return this.notificationService.prompt(Severity.Info, message, options).then(value => { - if (value === 0) { - return this.install(extension); - } - - return TPromise.as(null); - }); + this.notificationService.prompt( + Severity.Info, + nls.localize('installConfirmation', "Would you like to install the '{0}' extension?", extension.displayName, extension.publisher), + [{ + label: nls.localize('install', "Install"), + run: () => this.install(extension).done(undefined, error => this.onError(error)) + }] + ); }); }); }); diff --git a/src/vs/workbench/parts/extensions/test/electron-browser/extensionsTipsService.test.ts b/src/vs/workbench/parts/extensions/test/electron-browser/extensionsTipsService.test.ts index fd2f7e41c28..98b711a62e8 100644 --- a/src/vs/workbench/parts/extensions/test/electron-browser/extensionsTipsService.test.ts +++ b/src/vs/workbench/parts/extensions/test/electron-browser/extensionsTipsService.test.ts @@ -45,7 +45,7 @@ import product from 'vs/platform/node/product'; import { ITextModel } from 'vs/editor/common/model'; import { IModelService } from 'vs/editor/common/services/modelService'; import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle'; -import { INotificationService } from 'vs/platform/notification/common/notification'; +import { INotificationService, Severity, IPromptChoice } from 'vs/platform/notification/common/notification'; import { URLService } from 'vs/platform/url/common/urlService'; const mockExtensionGallery: IGalleryExtension[] = [ @@ -223,9 +223,9 @@ suite('ExtensionsTipsService Test', () => { prompted = false; class TestNotificationService2 extends TestNotificationService { - public prompt() { + public prompt(severity: Severity, message: string, choices: IPromptChoice[], onCancel?: () => void) { prompted = true; - return TPromise.as(3); + return null; } } diff --git a/src/vs/workbench/parts/files/electron-browser/fileActions.ts b/src/vs/workbench/parts/files/electron-browser/fileActions.ts index 0056fa9eb60..7832bc03939 100644 --- a/src/vs/workbench/parts/files/electron-browser/fileActions.ts +++ b/src/vs/workbench/parts/files/electron-browser/fileActions.ts @@ -103,11 +103,12 @@ export class BaseErrorReportingAction extends Action { } protected onErrorWithRetry(error: any, retry: () => TPromise): void { - this._notificationService.prompt(Severity.Error, toErrorMessage(error, false), [nls.localize('retry', "Retry")]).then(choice => { - if (choice === 0) { - retry(); - } - }); + this._notificationService.prompt(Severity.Error, toErrorMessage(error, false), + [{ + label: nls.localize('retry', "Retry"), + run: () => retry() + }] + ); } } diff --git a/src/vs/workbench/parts/localizations/browser/localizations.contribution.ts b/src/vs/workbench/parts/localizations/browser/localizations.contribution.ts index 4d111bf50d3..1c89acd7c6f 100644 --- a/src/vs/workbench/parts/localizations/browser/localizations.contribution.ts +++ b/src/vs/workbench/parts/localizations/browser/localizations.contribution.ts @@ -64,17 +64,25 @@ export class LocalizationWorkbenchContribution extends Disposable implements IWo if (!this.storageService.getBoolean(donotAskUpdateKey) && e.local && e.local.manifest.contributes && e.local.manifest.contributes.localizations && e.local.manifest.contributes.localizations.length) { const locale = e.local.manifest.contributes.localizations[0].languageId; if (language !== locale) { - const updateLocaleMessage = localize('updateLocale', "Would you like to change VS Code's UI language to {0} and restart?", e.local.manifest.contributes.localizations[0].languageName || e.local.manifest.contributes.localizations[0].languageId); - this.notificationService.prompt(Severity.Info, updateLocaleMessage, [localize('yes', "Yes"), localize('no', "No"), localize('doNotAskAgain', "Do not ask me again")]) - .then(option => { - if (option === 0) { + this.notificationService.prompt( + Severity.Info, + localize('updateLocale', "Would you like to change VS Code's UI language to {0} and restart?", e.local.manifest.contributes.localizations[0].languageName || e.local.manifest.contributes.localizations[0].languageId), + [{ + label: localize('yes', "Yes"), + run: () => { const file = URI.file(join(this.environmentService.appSettingsHome, 'locale.json')); this.jsonEditingService.write(file, { key: 'locale', value: locale }, true) .then(() => this.windowsService.relaunch({}), e => this.notificationService.error(e)); - } else if (option === 2) { - this.storageService.store(donotAskUpdateKey, true); } - }); + }, { + label: localize('no', "No"), + run: () => { } + }, { + label: localize('neverAgain', "Don't Show Again"), + isSecondary: true, + run: () => this.storageService.store(donotAskUpdateKey, true) + }] + ); } } } diff --git a/src/vs/workbench/parts/surveys/electron-browser/languageSurveys.contribution.ts b/src/vs/workbench/parts/surveys/electron-browser/languageSurveys.contribution.ts index e4f90adef38..22c74d78dc7 100644 --- a/src/vs/workbench/parts/surveys/electron-browser/languageSurveys.contribution.ts +++ b/src/vs/workbench/parts/surveys/electron-browser/languageSurveys.contribution.ts @@ -17,7 +17,7 @@ import { IStorageService, StorageScope } from 'vs/platform/storage/common/storag import pkg from 'vs/platform/node/package'; import product, { ISurveyData } from 'vs/platform/node/product'; import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; -import { Severity, INotificationService, PromptOption } from 'vs/platform/notification/common/notification'; +import { Severity, INotificationService } from 'vs/platform/notification/common/notification'; class LanguageSurvey { @@ -87,30 +87,36 @@ class LanguageSurvey { // __GDPR__TODO__ Need to move away from dynamic event names as those cannot be registered statically telemetryService.publicLog(`${data.surveyId}.survey/userAsked`); - const choices: PromptOption[] = [nls.localize('takeShortSurvey', "Take Short Survey"), nls.localize('remindLater', "Remind Me later"), { label: nls.localize('neverAgain', "Don't Show Again") }]; - notificationService.prompt(Severity.Info, nls.localize('helpUs', "Help us improve our support for {0}", data.languageId), choices).then(choice => { - switch (choice) { - case 0 /* Take Survey */: + notificationService.prompt( + Severity.Info, + nls.localize('helpUs', "Help us improve our support for {0}", data.languageId), + [{ + label: nls.localize('takeShortSurvey', "Take Short Survey"), + run: () => { telemetryService.publicLog(`${data.surveyId}.survey/takeShortSurvey`); telemetryService.getTelemetryInfo().then(info => { window.open(`${data.surveyUrl}?o=${encodeURIComponent(process.platform)}&v=${encodeURIComponent(pkg.version)}&m=${encodeURIComponent(info.machineId)}`); storageService.store(IS_CANDIDATE_KEY, false, StorageScope.GLOBAL); storageService.store(SKIP_VERSION_KEY, pkg.version, StorageScope.GLOBAL); }); - break; - case 1 /* Remind Later */: + } + }, { + label: nls.localize('remindLater', "Remind Me later"), + run: () => { telemetryService.publicLog(`${data.surveyId}.survey/remindMeLater`); storageService.store(SESSION_COUNT_KEY, sessionCount - 3, StorageScope.GLOBAL); - break; - case 2 /* Never show again */: + } + }, { + label: nls.localize('neverAgain', "Don't Show Again"), + isSecondary: true, + run: () => { telemetryService.publicLog(`${data.surveyId}.survey/dontShowAgain`); storageService.store(IS_CANDIDATE_KEY, false, StorageScope.GLOBAL); storageService.store(SKIP_VERSION_KEY, pkg.version, StorageScope.GLOBAL); - break; - } - }); + } + }] + ); } - } class LanguageSurveysContribution implements IWorkbenchContribution { diff --git a/src/vs/workbench/parts/surveys/electron-browser/nps.contribution.ts b/src/vs/workbench/parts/surveys/electron-browser/nps.contribution.ts index 74d76dfd627..5313485e412 100644 --- a/src/vs/workbench/parts/surveys/electron-browser/nps.contribution.ts +++ b/src/vs/workbench/parts/surveys/electron-browser/nps.contribution.ts @@ -15,7 +15,7 @@ import { IStorageService, StorageScope } from 'vs/platform/storage/common/storag import pkg from 'vs/platform/node/package'; import product from 'vs/platform/node/product'; import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; -import { Severity, INotificationService, PromptOption } from 'vs/platform/notification/common/notification'; +import { Severity, INotificationService } from 'vs/platform/notification/common/notification'; const PROBABILITY = 0.15; const SESSION_COUNT_KEY = 'nps/sessionCount'; @@ -62,25 +62,30 @@ class NPSContribution implements IWorkbenchContribution { return; } - const choices: PromptOption[] = [nls.localize('takeSurvey', "Take Survey"), nls.localize('remindLater', "Remind Me later"), { label: nls.localize('neverAgain', "Don't Show Again") }]; - notificationService.prompt(Severity.Info, nls.localize('surveyQuestion', "Do you mind taking a quick feedback survey?"), choices).then(choice => { - switch (choice) { - case 0 /* Take Survey */: + notificationService.prompt( + Severity.Info, + nls.localize('surveyQuestion', "Do you mind taking a quick feedback survey?"), + [{ + label: nls.localize('takeSurvey', "Take Survey"), + run: () => { telemetryService.getTelemetryInfo().then(info => { window.open(`${product.npsSurveyUrl}?o=${encodeURIComponent(process.platform)}&v=${encodeURIComponent(pkg.version)}&m=${encodeURIComponent(info.machineId)}`); storageService.store(IS_CANDIDATE_KEY, false, StorageScope.GLOBAL); storageService.store(SKIP_VERSION_KEY, pkg.version, StorageScope.GLOBAL); }); - break; - case 1 /* Remind Later */: - storageService.store(SESSION_COUNT_KEY, sessionCount - 3, StorageScope.GLOBAL); - break; - case 2 /* Never show again */: + } + }, { + label: nls.localize('remindLater', "Remind Me later"), + run: () => storageService.store(SESSION_COUNT_KEY, sessionCount - 3, StorageScope.GLOBAL) + }, { + label: nls.localize('neverAgain', "Don't Show Again"), + isSecondary: true, + run: () => { storageService.store(IS_CANDIDATE_KEY, false, StorageScope.GLOBAL); storageService.store(SKIP_VERSION_KEY, pkg.version, StorageScope.GLOBAL); - break; - } - }); + } + }] + ); } } diff --git a/src/vs/workbench/parts/tasks/electron-browser/task.contribution.ts b/src/vs/workbench/parts/tasks/electron-browser/task.contribution.ts index 7bf86924378..03a0b389ef4 100644 --- a/src/vs/workbench/parts/tasks/electron-browser/task.contribution.ts +++ b/src/vs/workbench/parts/tasks/electron-browser/task.contribution.ts @@ -498,14 +498,17 @@ class TaskService implements ITaskService { let folderSetup = this.computeWorkspaceFolderSetup(); if (this.executionEngine !== folderSetup[2]) { if (this._taskSystem && this._taskSystem.getActiveTasks().length > 0) { - this.notificationService.prompt(Severity.Info, nls.localize( - 'TaskSystem.noHotSwap', - 'Changing the task execution engine with an active task running requires to reload the Window' - ), [nls.localize('reloadWindow', "Reload Window")]).then(choice => { - if (choice === 0) { - this._windowService.reloadWindow(); - } - }); + this.notificationService.prompt( + Severity.Info, + nls.localize( + 'TaskSystem.noHotSwap', + 'Changing the task execution engine with an active task running requires to reload the Window' + ), + [{ + label: nls.localize('reloadWindow', "Reload Window"), + run: () => this._windowService.reloadWindow() + }] + ); return; } else { this.disposeTaskSystemListeners(); @@ -1850,24 +1853,18 @@ class TaskService implements ITaskService { return TPromise.as(undefined); } - const action = new Action('dontShowAgain', nls.localize('TaskService.notAgain', 'Don\'t Show Again'), null, true, (notification: IDisposable) => { - this.storageService.store(TaskService.IgnoreTask010DonotShowAgain_key, true, StorageScope.WORKSPACE); - this.__showIgnoreMessage = false; - - // Hide notification - notification.dispose(); - - return TPromise.as(true); - }); - - const handle = this.notificationService.notify({ - severity: Severity.Info, - message: nls.localize('TaskService.ignoredFolder', 'The following workspace folders are ignored since they use task version 0.1.0: {0}', this.ignoredWorkspaceFolders.map(f => f.name).join(', ')), - actions: { - secondary: [action] - } - }); - once(handle.onDidDispose)(() => action.dispose()); + this.notificationService.prompt( + Severity.Info, + nls.localize('TaskService.ignoredFolder', 'The following workspace folders are ignored since they use task version 0.1.0: {0}', this.ignoredWorkspaceFolders.map(f => f.name).join(', ')), + [{ + label: nls.localize('TaskService.notAgain', 'Don\'t Show Again'), + isSecondary: true, + run: () => { + this.storageService.store(TaskService.IgnoreTask010DonotShowAgain_key, true, StorageScope.WORKSPACE); + this.__showIgnoreMessage = false; + } + }] + ); return TPromise.as(undefined); } diff --git a/src/vs/workbench/parts/terminal/electron-browser/terminalConfigHelper.ts b/src/vs/workbench/parts/terminal/electron-browser/terminalConfigHelper.ts index 314511a4e90..243f7094819 100644 --- a/src/vs/workbench/parts/terminal/electron-browser/terminalConfigHelper.ts +++ b/src/vs/workbench/parts/terminal/electron-browser/terminalConfigHelper.ts @@ -163,18 +163,16 @@ export class TerminalConfigHelper implements ITerminalConfigHelper { } else { // if (shellArgsConfigValue.workspace !== undefined) changeString = `shellArgs: ${argsString}`; } - const message = nls.localize('terminal.integrated.allowWorkspaceShell', "Do you allow {0} (defined as a workspace setting) to be launched in the terminal?", changeString); - const options = [nls.localize('allow', "Allow"), nls.localize('disallow', "Disallow")]; - this._notificationService.prompt(Severity.Info, message, options).then(choice => { - switch (choice) { - case 0: /* Allow */ - this._storageService.store(IS_WORKSPACE_SHELL_ALLOWED_STORAGE_KEY, true, StorageScope.WORKSPACE); - break; - case 1: /* Disallow */ - this._storageService.store(IS_WORKSPACE_SHELL_ALLOWED_STORAGE_KEY, false, StorageScope.WORKSPACE); - break; - } - }); + this._notificationService.prompt(Severity.Info, nls.localize('terminal.integrated.allowWorkspaceShell', "Do you allow {0} (defined as a workspace setting) to be launched in the terminal?", changeString), + [{ + label: nls.localize('allow', "Allow"), + run: () => this._storageService.store(IS_WORKSPACE_SHELL_ALLOWED_STORAGE_KEY, true, StorageScope.WORKSPACE) + }, + { + label: nls.localize('disallow', "Disallow"), + run: () => this._storageService.store(IS_WORKSPACE_SHELL_ALLOWED_STORAGE_KEY, false, StorageScope.WORKSPACE) + }] + ); } shell.executable = (isWorkspaceShellAllowed ? shellConfigValue.value : shellConfigValue.user) || shellConfigValue.default; diff --git a/src/vs/workbench/parts/terminal/electron-browser/terminalService.ts b/src/vs/workbench/parts/terminal/electron-browser/terminalService.ts index 95f9e18ddf5..a09efd1925d 100644 --- a/src/vs/workbench/parts/terminal/electron-browser/terminalService.ts +++ b/src/vs/workbench/parts/terminal/electron-browser/terminalService.ts @@ -23,7 +23,7 @@ import { getTerminalDefaultShellWindows } from 'vs/workbench/parts/terminal/elec import { TerminalPanel } from 'vs/workbench/parts/terminal/electron-browser/terminalPanel'; import { TerminalTab } from 'vs/workbench/parts/terminal/electron-browser/terminalTab'; import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; -import { INotificationService, PromptOption } from 'vs/platform/notification/common/notification'; +import { INotificationService } from 'vs/platform/notification/common/notification'; import { ipcRenderer as ipc } from 'electron'; import { IOpenFileRequest } from 'vs/platform/windows/common/windows'; @@ -142,11 +142,12 @@ export class TerminalService extends AbstractTerminalService implements ITermina return; } - const message = nls.localize('terminal.integrated.chooseWindowsShellInfo', "You can change the default terminal shell by selecting the customize button."); - const options: PromptOption[] = [nls.localize('customize', "Customize"), { label: nls.localize('never again', "Don't Show Again") }]; - this._notificationService.prompt(Severity.Info, message, options).then(choice => { - switch (choice) { - case 0 /* Customize */: + this._notificationService.prompt( + Severity.Info, + nls.localize('terminal.integrated.chooseWindowsShellInfo', "You can change the default terminal shell by selecting the customize button."), + [{ + label: nls.localize('customize', "Customize"), + run: () => { this.selectDefaultWindowsShell().then(shell => { if (!shell) { return TPromise.as(null); @@ -161,12 +162,14 @@ export class TerminalService extends AbstractTerminalService implements ITermina } return TPromise.as(null); }); - break; - case 1 /* Do not show again */: - this._storageService.store(NEVER_SUGGEST_SELECT_WINDOWS_SHELL_STORAGE_KEY, true); - break; - } - }); + } + }, + { + label: nls.localize('never again', "Don't Show Again"), + isSecondary: true, + run: () => this._storageService.store(NEVER_SUGGEST_SELECT_WINDOWS_SHELL_STORAGE_KEY, true) + }] + ); } public selectDefaultWindowsShell(): TPromise { diff --git a/src/vs/workbench/parts/trust/electron-browser/unsupportedWorkspaceSettings.contribution.ts b/src/vs/workbench/parts/trust/electron-browser/unsupportedWorkspaceSettings.contribution.ts index 7d2c890eafc..904c597c9f6 100644 --- a/src/vs/workbench/parts/trust/electron-browser/unsupportedWorkspaceSettings.contribution.ts +++ b/src/vs/workbench/parts/trust/electron-browser/unsupportedWorkspaceSettings.contribution.ts @@ -14,7 +14,7 @@ import { IPreferencesService } from 'vs/workbench/parts/preferences/common/prefe import { IWorkspaceConfigurationService } from 'vs/workbench/services/configuration/common/configuration'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; -import { Severity, INotificationService, PromptOption } from 'vs/platform/notification/common/notification'; +import { Severity, INotificationService } from 'vs/platform/notification/common/notification'; class UnsupportedWorkspaceSettingsContribution implements IWorkbenchContribution { @@ -60,18 +60,21 @@ class UnsupportedWorkspaceSettingsContribution implements IWorkbenchContribution } private showWarning(unsupportedKeys: string[]): void { - const choices: PromptOption[] = [nls.localize('openWorkspaceSettings', 'Open Workspace Settings'), { label: nls.localize('dontShowAgain', 'Don\'t Show Again') }]; - this.notificationService.prompt(Severity.Warning, nls.localize('unsupportedWorkspaceSettings', 'This Workspace contains settings that can only be set in User Settings ({0}). Click [here]({1}) to learn more.', unsupportedKeys.join(', '), 'https://go.microsoft.com/fwlink/?linkid=839878'), choices).then(choice => { - switch (choice) { - case 0 /* Open Workspace Settings */: + this.notificationService.prompt( + Severity.Warning, + nls.localize('unsupportedWorkspaceSettings', 'This Workspace contains settings that can only be set in User Settings ({0}). Click [here]({1}) to learn more.', unsupportedKeys.join(', '), 'https://go.microsoft.com/fwlink/?linkid=839878'), + [{ + label: nls.localize('openWorkspaceSettings', 'Open Workspace Settings'), + run: () => { this.rememberWarningWasShown(); this.preferencesService.openWorkspaceSettings(); - break; - case 1 /* Never show again */: - this.rememberWarningWasShown(); - break; - } - }); + } + }, { + label: nls.localize('dontShowAgain', 'Don\'t Show Again'), + isSecondary: true, + run: () => this.rememberWarningWasShown() + }] + ); } } diff --git a/src/vs/workbench/parts/update/electron-browser/update.ts b/src/vs/workbench/parts/update/electron-browser/update.ts index 4a8dad69dbc..8c1ab586a7b 100644 --- a/src/vs/workbench/parts/update/electron-browser/update.ts +++ b/src/vs/workbench/parts/update/electron-browser/update.ts @@ -29,17 +29,8 @@ import { INotificationService } from 'vs/platform/notification/common/notificati import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; import { IWindowService } from 'vs/platform/windows/common/windows'; import { ReleaseNotesManager } from './releaseNotesEditor'; -import { once } from 'vs/base/common/event'; import { isWindows } from 'vs/base/common/platform'; -const NotNowAction = new Action( - 'update.later', - nls.localize('later', "Later"), - null, - true, - () => TPromise.as(true) -); - let releaseNotesManager: ReleaseNotesManager | undefined = undefined; function showReleaseNotes(instantiationService: IInstantiationService, version: string) { @@ -123,7 +114,8 @@ export class ProductContribution implements IWorkbenchContribution { @IInstantiationService instantiationService: IInstantiationService, @INotificationService notificationService: INotificationService, @IWorkbenchEditorService editorService: IWorkbenchEditorService, - @IEnvironmentService environmentService: IEnvironmentService + @IEnvironmentService environmentService: IEnvironmentService, + @IOpenerService openerService: IOpenerService ) { const lastVersion = storageService.get(ProductContribution.KEY, StorageScope.GLOBAL, ''); @@ -131,13 +123,17 @@ export class ProductContribution implements IWorkbenchContribution { if (!environmentService.skipReleaseNotes && product.releaseNotesUrl && lastVersion && pkg.version !== lastVersion) { showReleaseNotes(instantiationService, lastVersion) .then(undefined, () => { - const action = instantiationService.createInstance(OpenLatestReleaseNotesInBrowserAction); - const handle = notificationService.notify({ - severity: severity.Info, - message: nls.localize('read the release notes', "Welcome to {0} v{1}! Would you like to read the Release Notes?", product.nameLong, pkg.version), - actions: { primary: [action] } - }); - once(handle.onDidDispose)(() => action.dispose()); + notificationService.prompt( + severity.Info, + nls.localize('read the release notes', "Welcome to {0} v{1}! Would you like to read the Release Notes?", product.nameLong, pkg.version), + [{ + label: nls.localize('releaseNotes', "Release Notes"), + run: () => { + const uri = URI.parse(product.releaseNotesUrl); + openerService.open(uri); + } + }] + ); }); } @@ -180,7 +176,7 @@ export class Win3264BitContribution implements IWorkbenchContribution { constructor( @IStorageService storageService: IStorageService, @IInstantiationService instantiationService: IInstantiationService, - @INotificationService private notificationService: INotificationService, + @INotificationService notificationService: INotificationService, @IWorkbenchEditorService editorService: IWorkbenchEditorService, @IEnvironmentService environmentService: IEnvironmentService ) { @@ -198,12 +194,18 @@ export class Win3264BitContribution implements IWorkbenchContribution { ? Win3264BitContribution.INSIDER_URL : Win3264BitContribution.URL; - const handle = this.notificationService.notify({ - severity: severity.Info, - message: nls.localize('64bitisavailable', "{0} for 64-bit Windows is now available! Click [here]({1}) to learn more.", product.nameShort, url), - actions: { secondary: [neverShowAgain.action] } - }); - once(handle.onDidDispose)(() => neverShowAgain.action.dispose()); + 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.dispose(); + } + }] + ); } } @@ -328,16 +330,24 @@ export class UpdateContribution implements IGlobalActivity { return; } - const releaseNotesAction = this.instantiationService.createInstance(ShowReleaseNotesAction, update.productVersion); - const downloadAction = new Action('update.downloadNow', nls.localize('download now', "Download Now"), null, true, () => - this.updateService.downloadUpdate()); - - const handle = this.notificationService.notify({ - severity: severity.Info, - message: nls.localize('thereIsUpdateAvailable', "There is an available update."), - actions: { primary: [downloadAction, NotNowAction, releaseNotesAction] } - }); - once(handle.onDidDispose)(() => dispose(releaseNotesAction, downloadAction)); + this.notificationService.prompt( + severity.Info, + nls.localize('thereIsUpdateAvailable', "There is an available update."), + [{ + label: nls.localize('download now', "Download Now"), + run: () => this.updateService.downloadUpdate() + }, { + label: nls.localize('later', "Later"), + run: () => { } + }, { + label: nls.localize('releaseNotes', "Release Notes"), + run: () => { + const action = this.instantiationService.createInstance(ShowReleaseNotesAction, update.productVersion); + action.run(); + action.dispose(); + } + }] + ); } // windows fast updates @@ -346,16 +356,24 @@ export class UpdateContribution implements IGlobalActivity { return; } - const releaseNotesAction = this.instantiationService.createInstance(ShowReleaseNotesAction, update.productVersion); - const installUpdateAction = new Action('update.applyUpdate', nls.localize('installUpdate', "Install Update"), undefined, true, () => - this.updateService.applyUpdate()); - - const handle = this.notificationService.notify({ - severity: severity.Info, - message: nls.localize('updateAvailable', "There's an update available: {0} {1}", product.nameLong, update.productVersion), - actions: { primary: [installUpdateAction, NotNowAction, releaseNotesAction] } - }); - once(handle.onDidDispose)(() => dispose(installUpdateAction, releaseNotesAction)); + this.notificationService.prompt( + severity.Info, + nls.localize('updateAvailable', "There's an update available: {0} {1}", product.nameLong, update.productVersion), + [{ + label: nls.localize('installUpdate', "Install Update"), + run: () => this.updateService.applyUpdate() + }, { + label: nls.localize('later', "Later"), + run: () => { } + }, { + label: nls.localize('releaseNotes', "Release Notes"), + run: () => { + const action = this.instantiationService.createInstance(ShowReleaseNotesAction, update.productVersion); + action.run(); + action.dispose(); + } + }] + ); } // windows fast updates @@ -366,12 +384,18 @@ export class UpdateContribution implements IGlobalActivity { return; } - const handle = this.notificationService.notify({ - severity: severity.Info, - message: nls.localize('updateInstalling', "{0} {1} is being installed in the background, we'll let you know when it's done.", product.nameLong, update.productVersion), - actions: { secondary: [neverShowAgain.action] } - }); - once(handle.onDidDispose)(() => neverShowAgain.action.dispose()); + 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.dispose(); + } + }] + ); } // windows and mac @@ -380,16 +404,24 @@ export class UpdateContribution implements IGlobalActivity { return; } - const releaseNotesAction = this.instantiationService.createInstance(ShowReleaseNotesAction, update.productVersion); - const applyUpdateAction = new Action('update.applyUpdate', nls.localize('updateNow', "Update Now"), undefined, true, () => - this.updateService.quitAndInstall()); - - const handle = this.notificationService.notify({ - severity: severity.Info, - message: nls.localize('updateAvailableAfterRestart', "Restart {0} to apply the latest update.", product.nameLong), - actions: { primary: [applyUpdateAction, NotNowAction, releaseNotesAction] } - }); - once(handle.onDidDispose)(() => dispose(applyUpdateAction, releaseNotesAction)); + this.notificationService.prompt( + severity.Info, + nls.localize('updateAvailableAfterRestart', "Restart {0} to apply the latest update.", product.nameLong), + [{ + label: nls.localize('updateNow', "Update Now"), + run: () => this.updateService.quitAndInstall() + }, { + label: nls.localize('later', "Later"), + run: () => { } + }, { + label: nls.localize('releaseNotes', "Release Notes"), + run: () => { + const action = this.instantiationService.createInstance(ShowReleaseNotesAction, update.productVersion); + action.run(); + action.dispose(); + } + }] + ); } private shouldShowNotification(): boolean { diff --git a/src/vs/workbench/parts/welcome/gettingStarted/electron-browser/telemetryOptOut.ts b/src/vs/workbench/parts/welcome/gettingStarted/electron-browser/telemetryOptOut.ts index 9dea022290c..49a9d309bbf 100644 --- a/src/vs/workbench/parts/welcome/gettingStarted/electron-browser/telemetryOptOut.ts +++ b/src/vs/workbench/parts/welcome/gettingStarted/electron-browser/telemetryOptOut.ts @@ -43,8 +43,15 @@ export class TelemetryOptOut implements IWorkbenchContribution { const privacyUrl = product.privacyStatementUrl || product.telemetryOptOutUrl; const optOutNotice = localize('telemetryOptOut.optOutNotice', "Help improve VS Code by allowing Microsoft to collect usage data. Read our [privacy statement]({0}) and learn how to [opt out]({1}).", privacyUrl, optOutUrl); const optInNotice = localize('telemetryOptOut.optInNotice', "Help improve VS Code by allowing Microsoft to collect usage data. Read our [privacy statement]({0}) and learn how to [opt in]({1}).", privacyUrl, optOutUrl); - return notificationService.prompt(Severity.Info, telemetryService.isOptedIn ? optOutNotice : optInNotice, [localize('telemetryOptOut.readMore', "Read More")]) - .then(() => openerService.open(URI.parse(optOutUrl))); + + notificationService.prompt( + Severity.Info, + telemetryService.isOptedIn ? optOutNotice : optInNotice, + [{ + label: localize('telemetryOptOut.readMore', "Read More"), + run: () => openerService.open(URI.parse(optOutUrl)) + }] + ); }) .then(null, onUnexpectedError); } diff --git a/src/vs/workbench/parts/welcome/page/electron-browser/welcomePage.ts b/src/vs/workbench/parts/welcome/page/electron-browser/welcomePage.ts index 290a2b95ac3..21f183d2458 100644 --- a/src/vs/workbench/parts/welcome/page/electron-browser/welcomePage.ts +++ b/src/vs/workbench/parts/welcome/page/electron-browser/welcomePage.ts @@ -427,9 +427,12 @@ class WelcomePage { }); }); - this.notificationService.prompt(Severity.Info, strings.reloadAfterInstall.replace('{0}', extensionSuggestion.name), [localize('ok', "OK"), localize('details', "Details")]).then(choice => { - switch (choice) { - case 0 /* OK */: + this.notificationService.prompt( + Severity.Info, + strings.reloadAfterInstall.replace('{0}', extensionSuggestion.name), + [{ + label: localize('ok', "OK"), + run: () => { const messageDelay = TPromise.timeout(300); messageDelay.then(() => { this.notificationService.info(strings.installing.replace('{0}', extensionSuggestion.name)); @@ -491,8 +494,10 @@ class WelcomePage { }); this.notificationService.error(err); }); - break; - case 1 /* Details */: + } + }, { + label: localize('details', "Details"), + run: () => { /* __GDPR__FRAGMENT__ "WelcomePageDetails-1" : { "from" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, @@ -506,9 +511,9 @@ class WelcomePage { this.extensionsWorkbenchService.queryGallery({ names: [extensionSuggestion.id] }) .then(result => this.extensionsWorkbenchService.open(result.firstPage[0])) .then(null, onUnexpectedError); - break; - } - }); + } + }] + ); }).then(null, err => { /* __GDPR__FRAGMENT__ "WelcomePageInstalled-6" : { diff --git a/src/vs/workbench/services/configuration/node/configurationEditingService.ts b/src/vs/workbench/services/configuration/node/configurationEditingService.ts index b242c7188c6..065e6601dd6 100644 --- a/src/vs/workbench/services/configuration/node/configurationEditingService.ts +++ b/src/vs/workbench/services/configuration/node/configurationEditingService.ts @@ -192,19 +192,19 @@ export class ConfigurationEditingService { : operation.workspaceStandAloneConfigurationKey === LAUNCH_CONFIGURATION_KEY ? nls.localize('openLaunchConfiguration', "Open Launch Configuration") : null; if (openStandAloneConfigurationActionLabel) { - this.notificationService.prompt(Severity.Error, error.message, [openStandAloneConfigurationActionLabel]) - .then(option => { - if (option === 0) { - this.openFile(operation.resource); - } - }); + this.notificationService.prompt(Severity.Error, error.message, + [{ + label: openStandAloneConfigurationActionLabel, + run: () => this.openFile(operation.resource) + }] + ); } else { - this.notificationService.prompt(Severity.Error, error.message, [nls.localize('open', "Open Settings")]) - .then(option => { - if (option === 0) { - this.openSettings(operation); - } - }); + this.notificationService.prompt(Severity.Error, error.message, + [{ + label: nls.localize('open', "Open Settings"), + run: () => this.openSettings(operation) + }] + ); } } @@ -213,30 +213,30 @@ export class ConfigurationEditingService { : operation.workspaceStandAloneConfigurationKey === LAUNCH_CONFIGURATION_KEY ? nls.localize('openLaunchConfiguration', "Open Launch Configuration") : null; if (openStandAloneConfigurationActionLabel) { - this.notificationService.prompt(Severity.Error, error.message, [nls.localize('saveAndRetry', "Save and Retry"), openStandAloneConfigurationActionLabel]) - .then(option => { - switch (option) { - case 0 /* Save & Retry */: - const key = operation.key ? `${operation.workspaceStandAloneConfigurationKey}.${operation.key}` : operation.workspaceStandAloneConfigurationKey; - this.writeConfiguration(operation.target, { key, value: operation.value }, { force: true, scopes }); - break; - case 1 /* Open Config */: - this.openFile(operation.resource); - break; + this.notificationService.prompt(Severity.Error, error.message, + [{ + label: nls.localize('saveAndRetry', "Save and Retry"), + run: () => { + const key = operation.key ? `${operation.workspaceStandAloneConfigurationKey}.${operation.key}` : operation.workspaceStandAloneConfigurationKey; + this.writeConfiguration(operation.target, { key, value: operation.value }, { force: true, scopes }); } - }); + }, + { + label: openStandAloneConfigurationActionLabel, + run: () => this.openFile(operation.resource) + }] + ); } else { - this.notificationService.prompt(Severity.Error, error.message, [nls.localize('saveAndRetry', "Save and Retry"), nls.localize('open', "Open Settings")]) - .then(option => { - switch (option) { - case 0 /* Save and Retry */: - this.writeConfiguration(operation.target, { key: operation.key, value: operation.value }, { force: true, scopes }); - break; - case 1 /* Open Settings */: - this.openSettings(operation); - break; - } - }); + this.notificationService.prompt(Severity.Error, error.message, + [{ + label: nls.localize('saveAndRetry', "Save and Retry"), + run: () => this.writeConfiguration(operation.target, { key: operation.key, value: operation.value }, { force: true, scopes }) + }, + { + label: nls.localize('open', "Open Settings"), + run: () => this.openSettings(operation) + }] + ); } } diff --git a/src/vs/workbench/services/extensions/electron-browser/extensionHost.ts b/src/vs/workbench/services/extensions/electron-browser/extensionHost.ts index d91ed2b458c..c57272a6b22 100644 --- a/src/vs/workbench/services/extensions/electron-browser/extensionHost.ts +++ b/src/vs/workbench/services/extensions/electron-browser/extensionHost.ts @@ -235,11 +235,12 @@ export class ExtensionHostProcessWorker { ? nls.localize('extensionHostProcess.startupFailDebug', "Extension host did not start in 10 seconds, it might be stopped on the first line and needs a debugger to continue.") : nls.localize('extensionHostProcess.startupFail', "Extension host did not start in 10 seconds, that might be a problem."); - this._notificationService.prompt(Severity.Warning, msg, [nls.localize('reloadWindow', "Reload Window")]).then(choice => { - if (choice === 0) { - this._windowService.reloadWindow(); - } - }); + this._notificationService.prompt(Severity.Warning, msg, + [{ + label: nls.localize('reloadWindow', "Reload Window"), + run: () => this._windowService.reloadWindow() + }] + ); }, 10000); } diff --git a/src/vs/workbench/services/extensions/electron-browser/extensionService.ts b/src/vs/workbench/services/extensions/electron-browser/extensionService.ts index b4ec2e94433..4d64bdc8415 100644 --- a/src/vs/workbench/services/extensions/electron-browser/extensionService.ts +++ b/src/vs/workbench/services/extensions/electron-browser/extensionService.ts @@ -386,16 +386,16 @@ export class ExtensionService extends Disposable implements IExtensionService { message = nls.localize('extensionHostProcess.unresponsiveCrash', "Extension host terminated because it was not responsive."); } - this._notificationService.prompt(Severity.Error, message, [nls.localize('devTools', "Developer Tools"), nls.localize('restart', "Restart Extension Host")]).then(choice => { - switch (choice) { - case 0 /* Open Dev Tools */: - this._windowService.openDevTools(); - break; - case 1 /* Restart Extension Host */: - this._startExtensionHostProcess(Object.keys(this._allRequestedActivateEvents)); - break; - } - }); + this._notificationService.prompt(Severity.Error, message, + [{ + label: nls.localize('devTools', "Open Developer Tools"), + run: () => this._windowService.openDevTools() + }, + { + label: nls.localize('restart', "Restart Extension Host"), + run: () => this._startExtensionHostProcess(Object.keys(this._allRequestedActivateEvents)) + }] + ); } // ---- begin IExtensionService @@ -638,11 +638,14 @@ export class ExtensionService extends Disposable implements IExtensionService { console.error(err); } - notificationService.prompt(Severity.Error, nls.localize('extensionCache.invalid', "Extensions have been modified on disk. Please reload the window."), [nls.localize('reloadWindow', "Reload Window")]).then(choice => { - if (choice === 0) { - windowService.reloadWindow(); - } - }); + notificationService.prompt( + Severity.Error, + nls.localize('extensionCache.invalid', "Extensions have been modified on disk. Please reload the window."), + [{ + label: nls.localize('reloadWindow', "Reload Window"), + run: () => windowService.reloadWindow() + }] + ); } private static async _readExtensionCache(environmentService: IEnvironmentService, cacheKey: string): TPromise { diff --git a/src/vs/workbench/services/files/electron-browser/fileService.ts b/src/vs/workbench/services/files/electron-browser/fileService.ts index 7adb118fced..cb2fa1e771f 100644 --- a/src/vs/workbench/services/files/electron-browser/fileService.ts +++ b/src/vs/workbench/services/files/electron-browser/fileService.ts @@ -24,7 +24,7 @@ import { ITextResourceConfigurationService } from 'vs/editor/common/services/res import { isMacintosh, isWindows } from 'vs/base/common/platform'; import product from 'vs/platform/node/product'; import { Schemas } from 'vs/base/common/network'; -import { Severity, INotificationService, PromptOption } from 'vs/platform/notification/common/notification'; +import { Severity, INotificationService } from 'vs/platform/notification/common/notification'; import { WORKSPACE_EXTENSION } from 'vs/platform/workspaces/common/workspaces'; export class FileService implements IFileService { @@ -109,32 +109,36 @@ export class FileService implements IFileService { // Detect if we run < .NET Framework 4.5 (TODO@ben remove with new watcher impl) if (msg.indexOf(FileService.NET_VERSION_ERROR) >= 0 && !this.storageService.getBoolean(FileService.NET_VERSION_ERROR_IGNORE_KEY, StorageScope.WORKSPACE)) { - const choices: PromptOption[] = [nls.localize('installNet', "Download .NET Framework 4.5"), { label: nls.localize('neverShowAgain', "Don't Show Again") }]; - this.notificationService.prompt(Severity.Warning, nls.localize('netVersionError', "The Microsoft .NET Framework 4.5 is required. Please follow the link to install it."), choices).then(choice => { - switch (choice) { - case 0 /* Read More */: - window.open('https://go.microsoft.com/fwlink/?LinkId=786533'); - break; - case 1 /* Never show again */: - this.storageService.store(FileService.NET_VERSION_ERROR_IGNORE_KEY, true, StorageScope.WORKSPACE); - break; - } - }); + this.notificationService.prompt( + Severity.Warning, + nls.localize('netVersionError', "The Microsoft .NET Framework 4.5 is required. Please follow the link to install it."), + [{ + label: nls.localize('installNet', "Download .NET Framework 4.5"), + run: () => window.open('https://go.microsoft.com/fwlink/?LinkId=786533') + }, + { + label: nls.localize('neverShowAgain', "Don't Show Again"), + isSecondary: true, + run: () => this.storageService.store(FileService.NET_VERSION_ERROR_IGNORE_KEY, true, StorageScope.WORKSPACE) + }] + ); } // Detect if we run into ENOSPC issues if (msg.indexOf(FileService.ENOSPC_ERROR) >= 0 && !this.storageService.getBoolean(FileService.ENOSPC_ERROR_IGNORE_KEY, StorageScope.WORKSPACE)) { - const choices: PromptOption[] = [nls.localize('learnMore', "Instructions"), { label: nls.localize('neverShowAgain', "Don't Show Again") }]; - this.notificationService.prompt(Severity.Warning, nls.localize('enospcError', "{0} is unable to watch for file changes in this large workspace. Please follow the instructions link to resolve this issue.", product.nameLong), choices).then(choice => { - switch (choice) { - case 0 /* Read More */: - window.open('https://go.microsoft.com/fwlink/?linkid=867693'); - break; - case 1 /* Never show again */: - this.storageService.store(FileService.ENOSPC_ERROR_IGNORE_KEY, true, StorageScope.WORKSPACE); - break; - } - }); + this.notificationService.prompt( + Severity.Warning, + nls.localize('enospcError', "{0} is unable to watch for file changes in this large workspace. Please follow the instructions link to resolve this issue.", product.nameLong), + [{ + label: nls.localize('learnMore', "Instructions"), + run: () => window.open('https://go.microsoft.com/fwlink/?linkid=867693') + }, + { + label: nls.localize('neverShowAgain', "Don't Show Again"), + isSecondary: true, + run: () => this.storageService.store(FileService.ENOSPC_ERROR_IGNORE_KEY, true, StorageScope.WORKSPACE) + }] + ); } } diff --git a/src/vs/workbench/services/notification/common/notificationService.ts b/src/vs/workbench/services/notification/common/notificationService.ts index 815873ed0ff..d2cd39a8132 100644 --- a/src/vs/workbench/services/notification/common/notificationService.ts +++ b/src/vs/workbench/services/notification/common/notificationService.ts @@ -5,7 +5,7 @@ 'use strict'; -import { INotificationService, INotification, INotificationHandle, Severity, NotificationMessage, PromptOption, INotificationActions } from 'vs/platform/notification/common/notification'; +import { INotificationService, INotification, INotificationHandle, Severity, NotificationMessage, INotificationActions, IPromptChoice } from 'vs/platform/notification/common/notification'; import { INotificationsModel, NotificationsModel } from 'vs/workbench/common/notifications'; import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import { TPromise } from 'vs/base/common/winjs.base'; @@ -65,61 +65,49 @@ export class NotificationService implements INotificationService { return this.model.notify(notification); } - public prompt(severity: Severity, message: string, choices: PromptOption[]): TPromise { + public prompt(severity: Severity, message: string, choices: IPromptChoice[], onCancel?: () => void): INotificationHandle { let handle: INotificationHandle; + let choiceClicked = false; - const promise = new TPromise(c => { + // Convert choices into primary/secondary actions + const actions: INotificationActions = { primary: [], secondary: [] }; + choices.forEach((choice, index) => { + const action = new Action(`workbench.dialog.choice.${index}`, choice.label, null, true, () => { + choiceClicked = true; - // Complete promise with index of action that was picked - const callback = (index: number, closeNotification: boolean) => () => { - c(index); + // Pass to runner + choice.run(); - if (closeNotification) { + // Close notification unless we are told to keep open + if (!choice.keepOpen) { handle.dispose(); } return TPromise.as(void 0); - }; - - // Convert choices into primary/secondary actions - const actions: INotificationActions = { - primary: [], - secondary: [] - }; - - choices.forEach((choice, index) => { - let isPrimary = true; - let label: string; - let closeNotification = false; - - if (typeof choice === 'string') { - label = choice; - } else { - isPrimary = false; - label = choice.label; - closeNotification = !choice.keepOpen; - } - - const action = new Action(`workbench.dialog.choice.${index}`, label, null, true, callback(index, closeNotification)); - if (isPrimary) { - actions.primary.push(action); - } else { - actions.secondary.push(action); - } }); - // Show notification with actions - handle = this.notify({ severity, message, actions }); + if (!choice.isSecondary) { + actions.primary.push(action); + } else { + actions.secondary.push(action); + } + }); - // Cancel promise and cleanup when notification gets disposed - once(handle.onDidDispose)(() => { - dispose(...actions.primary, ...actions.secondary); - promise.cancel(); - }); + // Show notification with actions + handle = this.notify({ severity, message, actions }); - }, () => handle.dispose()); + once(handle.onDidDispose)(() => { - return promise; + // Cleanup when notification gets disposed + dispose(...actions.primary, ...actions.secondary); + + // Indicate cancellation to the outside if no action was executed + if (!choiceClicked && typeof onCancel === 'function') { + onCancel(); + } + }); + + return handle; } public dispose(): void { diff --git a/src/vs/workbench/services/workspace/node/workspaceEditingService.ts b/src/vs/workbench/services/workspace/node/workspaceEditingService.ts index f5acaa9c5ba..5e8f0fb6ca9 100644 --- a/src/vs/workbench/services/workspace/node/workspaceEditingService.ts +++ b/src/vs/workbench/services/workspace/node/workspaceEditingService.ts @@ -155,31 +155,33 @@ export class WorkspaceEditingService implements IWorkspaceEditingService { private handleWorkspaceConfigurationEditingError(error: JSONEditingError): TPromise { switch (error.code) { case JSONEditingErrorCode.ERROR_INVALID_FILE: - return this.onInvalidWorkspaceConfigurationFileError(); + this.onInvalidWorkspaceConfigurationFileError(); + return TPromise.as(void 0); case JSONEditingErrorCode.ERROR_FILE_DIRTY: - return this.onWorkspaceConfigurationFileDirtyError(); + this.onWorkspaceConfigurationFileDirtyError(); + return TPromise.as(void 0); } this.notificationService.error(error.message); return TPromise.as(void 0); } - private onInvalidWorkspaceConfigurationFileError(): TPromise { + private onInvalidWorkspaceConfigurationFileError(): void { const message = nls.localize('errorInvalidTaskConfiguration', "Unable to write into workspace configuration file. Please open the file to correct errors/warnings in it and try again."); - return this.askToOpenWorkspaceConfigurationFile(message); + this.askToOpenWorkspaceConfigurationFile(message); } - private onWorkspaceConfigurationFileDirtyError(): TPromise { + private onWorkspaceConfigurationFileDirtyError(): void { const message = nls.localize('errorWorkspaceConfigurationFileDirty', "Unable to write into workspace configuration file because the file is dirty. Please save it and try again."); - return this.askToOpenWorkspaceConfigurationFile(message); + this.askToOpenWorkspaceConfigurationFile(message); } - private askToOpenWorkspaceConfigurationFile(message: string): TPromise { - return this.notificationService.prompt(Severity.Error, message, [nls.localize('openWorkspaceConfigurationFile', "Open Workspace Configuration")]) - .then(option => { - if (option === 0) { - this.commandService.executeCommand('workbench.action.openWorkspaceConfigFile'); - } - }); + private askToOpenWorkspaceConfigurationFile(message: string): void { + this.notificationService.prompt(Severity.Error, message, + [{ + label: nls.localize('openWorkspaceConfigurationFile', "Open Workspace Configuration"), + run: () => this.commandService.executeCommand('workbench.action.openWorkspaceConfigFile') + }] + ); } private doEnterWorkspace(mainSidePromise: () => TPromise): TPromise { diff --git a/src/vs/workbench/test/electron-browser/api/extHostMessagerService.test.ts b/src/vs/workbench/test/electron-browser/api/extHostMessagerService.test.ts index a7711e888f6..feeab0b2895 100644 --- a/src/vs/workbench/test/electron-browser/api/extHostMessagerService.test.ts +++ b/src/vs/workbench/test/electron-browser/api/extHostMessagerService.test.ts @@ -9,7 +9,7 @@ import * as assert from 'assert'; import { MainThreadMessageService } from 'vs/workbench/api/electron-browser/mainThreadMessageService'; import { TPromise as Promise, TPromise } from 'vs/base/common/winjs.base'; import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; -import { INotificationService, INotification, NoOpNotification, INotificationHandle, PromptOption, Severity } from 'vs/platform/notification/common/notification'; +import { INotificationService, INotification, NoOpNotification, INotificationHandle, Severity, IPromptChoice } from 'vs/platform/notification/common/notification'; import { ICommandService } from 'vs/platform/commands/common/commands'; const emptyDialogService = new class implements IDialogService { @@ -45,7 +45,7 @@ const emptyNotificationService = new class implements INotificationService { error(...args: any[]): never { throw new Error('not implemented'); } - prompt(severity: Severity, message: string, choices: PromptOption[]): TPromise { + prompt(severity: Severity, message: string, choices: IPromptChoice[], onCancel?: () => void): INotificationHandle { throw new Error('not implemented'); } }; @@ -71,8 +71,8 @@ class EmptyNotificationService implements INotificationService { error(message: any): void { throw new Error('Method not implemented.'); } - prompt(severity: Severity, message: string, choices: PromptOption[]): Promise { - throw new Error('Method not implemented.'); + prompt(severity: Severity, message: string, choices: IPromptChoice[], onCancel?: () => void): INotificationHandle { + throw new Error('not implemented'); } } diff --git a/src/vs/workbench/test/workbenchTestServices.ts b/src/vs/workbench/test/workbenchTestServices.ts index 220a4d8e75a..7fb344f6341 100644 --- a/src/vs/workbench/test/workbenchTestServices.ts +++ b/src/vs/workbench/test/workbenchTestServices.ts @@ -63,7 +63,7 @@ import { MockContextKeyService } from 'vs/platform/keybinding/test/common/mockKe import { ITextBufferFactory, DefaultEndOfLine, EndOfLinePreference } from 'vs/editor/common/model'; import { Range } from 'vs/editor/common/core/range'; import { IConfirmation, IConfirmationResult, IDialogService, IDialogOptions } from 'vs/platform/dialogs/common/dialogs'; -import { INotificationService, INotificationHandle, INotification, NoOpNotification, PromptOption } from 'vs/platform/notification/common/notification'; +import { INotificationService, INotificationHandle, INotification, NoOpNotification, IPromptChoice } from 'vs/platform/notification/common/notification'; export function createFileInput(instantiationService: IInstantiationService, resource: URI): FileEditorInput { return instantiationService.createInstance(FileEditorInput, resource, void 0); @@ -328,8 +328,8 @@ export class TestNotificationService implements INotificationService { return TestNotificationService.NO_OP; } - public prompt(severity: Severity, message: string, choices: PromptOption[]): TPromise { - return TPromise.as(0); + public prompt(severity: Severity, message: string, choices: IPromptChoice[], onCancel?: () => void): INotificationHandle { + return TestNotificationService.NO_OP; } } From 9a273dfc845455315c7a78f1ffaa954c1cd52cd1 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Wed, 4 Apr 2018 07:54:58 +0200 Subject: [PATCH 20/49] :lipstick: --- .../electron-browser/task.contribution.ts | 38 ++++++------------- 1 file changed, 11 insertions(+), 27 deletions(-) diff --git a/src/vs/workbench/parts/tasks/electron-browser/task.contribution.ts b/src/vs/workbench/parts/tasks/electron-browser/task.contribution.ts index 03a0b389ef4..9dae269eac1 100644 --- a/src/vs/workbench/parts/tasks/electron-browser/task.contribution.ts +++ b/src/vs/workbench/parts/tasks/electron-browser/task.contribution.ts @@ -17,7 +17,7 @@ import { IStringDictionary } from 'vs/base/common/collections'; import { Action } from 'vs/base/common/actions'; import * as Dom from 'vs/base/browser/dom'; import { IDisposable, dispose } from 'vs/base/common/lifecycle'; -import { Event, Emitter, once } from 'vs/base/common/event'; +import { Event, Emitter } from 'vs/base/common/event'; import * as Builder from 'vs/base/browser/builder'; import * as Types from 'vs/base/common/types'; import { KeyMod, KeyCode } from 'vs/base/common/keyCodes'; @@ -1621,15 +1621,6 @@ class TaskService implements ITaskService { }; } - private configureBuildTask(): Action { - let run = () => { this.runConfigureTasks(); return TPromise.as(undefined); }; - return new class extends Action { - constructor() { - super(ConfigureTaskAction.ID, ConfigureTaskAction.TEXT, undefined, true, run); - } - }; - } - public beforeShutdown(): boolean | TPromise { if (!this._taskSystem) { return false; @@ -1689,15 +1680,6 @@ class TaskService implements ITaskService { }); } - private getConfigureAction(code: TaskErrors): Action { - switch (code) { - case TaskErrors.NoBuildTask: - return this.configureBuildTask(); - default: - return this.configureAction(); - } - } - private handleError(err: any): void { let showOutput = true; if (err instanceof TaskError) { @@ -1705,14 +1687,16 @@ class TaskService implements ITaskService { let needsConfig = buildError.code === TaskErrors.NotConfigured || buildError.code === TaskErrors.NoBuildTask || buildError.code === TaskErrors.NoTestTask; let needsTerminate = buildError.code === TaskErrors.RunningTask; if (needsConfig || needsTerminate) { - let action: Action = needsConfig - ? this.getConfigureAction(buildError.code) - : new Action( - 'workbench.action.tasks.terminate', - nls.localize('TerminateAction.label', "Terminate Task"), - undefined, true, () => { this.runTerminateCommand(); return TPromise.wrap(undefined); }); - let handle = this.notificationService.notify({ severity: buildError.severity, message: buildError.message, actions: { primary: [action] } }); - once(handle.onDidDispose)(() => action.dispose()); + this.notificationService.prompt(buildError.severity, buildError.message, [{ + label: needsConfig ? ConfigureTaskAction.TEXT : nls.localize('TerminateAction.label', "Terminate Task"), + run: () => { + if (needsConfig) { + this.runConfigureTasks(); + } else { + this.runTerminateCommand(); + } + } + }]); } else { this.notificationService.notify({ severity: buildError.severity, message: buildError.message }); } From b393ca69ac3551814ac7fd0804c0cb386658d215 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Wed, 4 Apr 2018 10:08:57 +0200 Subject: [PATCH 21/49] debt - further reduce Builder usage --- src/vs/base/browser/builder.css | 8 +-- src/vs/base/browser/dom.ts | 32 +++++++++ src/vs/base/test/browser/builder.test.ts | 1 - src/vs/workbench/browser/composite.ts | 9 ++- src/vs/workbench/browser/layout.ts | 68 +++++++++---------- src/vs/workbench/browser/panel.ts | 2 +- src/vs/workbench/browser/part.ts | 25 ++++--- .../parts/activitybar/activitybarPart.ts | 8 +-- .../workbench/browser/parts/compositePart.ts | 14 ++-- .../browser/parts/editor/baseEditor.ts | 11 ++- .../browser/parts/editor/binaryEditor.ts | 6 +- .../parts/editor/editorGroupsControl.ts | 42 ++++++------ .../browser/parts/editor/editorPart.ts | 30 ++++---- .../browser/parts/editor/resourceViewer.ts | 12 ++-- .../browser/parts/editor/sideBySideEditor.ts | 14 ++-- .../browser/parts/editor/textDiffEditor.ts | 5 +- .../browser/parts/editor/textEditor.ts | 11 ++- .../browser/parts/panel/panelPart.ts | 10 +-- .../browser/parts/sidebar/sidebarPart.ts | 3 +- .../browser/parts/statusbar/statusbarPart.ts | 16 ++--- .../browser/parts/titlebar/titlebarPart.ts | 13 ++-- .../browser/parts/views/customView.ts | 5 +- .../browser/parts/views/panelViewlet.ts | 10 ++- .../browser/parts/views/viewsViewlet.ts | 9 ++- src/vs/workbench/browser/viewlet.ts | 2 +- .../workbench/electron-browser/workbench.ts | 33 ++++----- .../parts/debug/browser/debugActionsWidget.ts | 7 +- .../parts/debug/browser/debugViewlet.ts | 6 +- .../parts/debug/electron-browser/repl.ts | 5 +- .../electron-browser/extensionEditor.ts | 7 +- .../electron-browser/extensionsViewlet.ts | 9 ++- .../runtimeExtensionsEditor.ts | 9 +-- .../files/electron-browser/explorerViewlet.ts | 6 +- .../electron-browser/views/explorerView.ts | 7 +- .../html/electron-browser/htmlPreviewPart.ts | 5 +- .../markers/electron-browser/markersPanel.ts | 7 +- .../parts/output/browser/outputPanel.ts | 5 +- .../preferences/browser/keybindingsEditor.ts | 9 +-- .../preferences/browser/preferencesEditor.ts | 18 +++-- .../parts/scm/electron-browser/scmViewlet.ts | 7 +- .../parts/search/browser/searchActions.ts | 2 +- .../parts/search/browser/searchView.ts | 8 +-- .../electron-browser/task.contribution.ts | 29 ++++---- .../electron-browser/terminalPanel.ts | 7 +- .../webview/electron-browser/webviewEditor.ts | 7 +- .../electron-browser/walkThroughPart.ts | 13 ++-- src/vs/workbench/test/browser/part.test.ts | 41 ++++++----- 47 files changed, 300 insertions(+), 313 deletions(-) diff --git a/src/vs/base/browser/builder.css b/src/vs/base/browser/builder.css index 09e124826a6..e440d5cf994 100644 --- a/src/vs/base/browser/builder.css +++ b/src/vs/base/browser/builder.css @@ -5,10 +5,4 @@ .monaco-builder-hidden { display: none !important; - visibility: hidden !important; -} - -.monaco-builder-visible { - display: inherit; - visibility: visible; -} +} \ No newline at end of file diff --git a/src/vs/base/browser/dom.ts b/src/vs/base/browser/dom.ts index 75248d70562..55b001b91b4 100644 --- a/src/vs/base/browser/dom.ts +++ b/src/vs/base/browser/dom.ts @@ -590,6 +590,36 @@ export interface IDomNodePagePosition { height: number; } +export function size(element: HTMLElement, width: number, height: number): void { + if (typeof width === 'number') { + element.style.width = `${width}px`; + } + + if (typeof height === 'number') { + element.style.height = `${height}px`; + } +} + +export function position(element: HTMLElement, top: number, right?: number, bottom?: number, left?: number, position: string = 'absolute'): void { + if (typeof top === 'number') { + element.style.top = `${top}px`; + } + + if (typeof right === 'number') { + element.style.right = `${right}px`; + } + + if (typeof bottom === 'number') { + element.style.bottom = `${bottom}px`; + } + + if (typeof left === 'number') { + element.style.left = `${left}px`; + } + + element.style.position = position; +} + /** * Returns the position of a dom node relative to the entire page. */ @@ -994,12 +1024,14 @@ export function join(nodes: Node[], separator: Node | string): Node[] { export function show(...elements: HTMLElement[]): void { for (let element of elements) { element.style.display = ''; + element.removeAttribute('aria-hidden'); } } export function hide(...elements: HTMLElement[]): void { for (let element of elements) { element.style.display = 'none'; + element.setAttribute('aria-hidden', 'true'); } } diff --git a/src/vs/base/test/browser/builder.test.ts b/src/vs/base/test/browser/builder.test.ts index 9184eff6ac1..d46dff04c97 100644 --- a/src/vs/base/test/browser/builder.test.ts +++ b/src/vs/base/test/browser/builder.test.ts @@ -636,7 +636,6 @@ suite('Builder', () => { assert(!b.isHidden()); b.hide(); assert(b.isHidden()); - assert(!b.hasClass('monaco-builder-visible')); b.show(); b.hide(); assert(b.hasClass('monaco-builder-hidden')); diff --git a/src/vs/workbench/browser/composite.ts b/src/vs/workbench/browser/composite.ts index e540070611e..04e72f4dfa8 100644 --- a/src/vs/workbench/browser/composite.ts +++ b/src/vs/workbench/browser/composite.ts @@ -4,7 +4,6 @@ *--------------------------------------------------------------------------------------------*/ import { TPromise } from 'vs/base/common/winjs.base'; -import { Builder } from 'vs/base/browser/builder'; import { IAction, IActionRunner, ActionRunner } from 'vs/base/common/actions'; import { IActionItem } from 'vs/base/browser/ui/actionbar/actionbar'; import { Component } from 'vs/workbench/common/component'; @@ -35,7 +34,7 @@ export abstract class Composite extends Component implements IComposite { private _focusListenerDisposable?: IDisposable; private visible: boolean; - private parent: Builder; + private parent: HTMLElement; protected actionRunner: IActionRunner; @@ -75,7 +74,7 @@ export abstract class Composite extends Component implements IComposite { * Note that DOM-dependent calculations should be performed from the setVisible() * call. Only then the composite will be part of the DOM. */ - public create(parent: Builder): TPromise { + public create(parent: HTMLElement): TPromise { this.parent = parent; return TPromise.as(null); @@ -88,12 +87,12 @@ export abstract class Composite extends Component implements IComposite { /** * Returns the container this composite is being build in. */ - public getContainer(): Builder { + public getContainer(): HTMLElement { return this.parent; } public get onDidFocus(): Event { - this._focusTracker = trackFocus(this.getContainer().getHTMLElement()); + this._focusTracker = trackFocus(this.getContainer()); this._focusListenerDisposable = this._focusTracker.onDidFocus(() => { this._onDidFocus.fire(); }); diff --git a/src/vs/workbench/browser/layout.ts b/src/vs/workbench/browser/layout.ts index 516f874935a..e8dcb1d830b 100644 --- a/src/vs/workbench/browser/layout.ts +++ b/src/vs/workbench/browser/layout.ts @@ -22,7 +22,7 @@ import { IThemeService } from 'vs/platform/theme/common/themeService'; import { memoize } from 'vs/base/common/decorators'; import { NotificationsCenter } from 'vs/workbench/browser/parts/notifications/notificationsCenter'; import { NotificationsToasts } from 'vs/workbench/browser/parts/notifications/notificationsToasts'; -import { Dimension, getClientArea } from 'vs/base/browser/dom'; +import { Dimension, getClientArea, size, position, hide, show } from 'vs/base/browser/dom'; const MIN_SIDEBAR_PART_WIDTH = 170; const DEFAULT_SIDEBAR_PART_WIDTH = 300; @@ -578,14 +578,8 @@ export class WorkbenchLayout implements IVerticalSashLayoutProvider, IHorizontal } // Workbench - this.workbenchContainer.style.top = '0px'; - this.workbenchContainer.style.right = '0px'; - this.workbenchContainer.style.bottom = '0px'; - this.workbenchContainer.style.left = '0px'; - this.workbenchContainer.style.position = 'relative'; - - this.workbenchContainer.style.width = `${this.workbenchSize.width}px`; - this.workbenchContainer.style.height = `${this.workbenchSize.height}px`; + position(this.workbenchContainer, 0, 0, 0, 0, 'relative'); + size(this.workbenchContainer, this.workbenchSize.width, this.workbenchSize.height); // Bug on Chrome: Sometimes Chrome wants to scroll the workbench container on layout changes. The fix is to reset scrolling in this case. const workbenchContainer = this.workbenchContainer; @@ -597,64 +591,70 @@ export class WorkbenchLayout implements IVerticalSashLayoutProvider, IHorizontal } // Title Part + const titleContainer = this.titlebar.getContainer(); if (isTitlebarHidden) { - this.titlebar.getContainer().hide(); + hide(titleContainer); } else { - this.titlebar.getContainer().show(); + show(titleContainer); } // Editor Part and Panel part - this.editor.getContainer().size(editorSize.width, editorSize.height); - this.panel.getContainer().size(panelDimension.width, panelDimension.height); + const editorContainer = this.editor.getContainer(); + const panelContainer = this.panel.getContainer(); + size(editorContainer, editorSize.width, editorSize.height); + size(panelContainer, panelDimension.width, panelDimension.height); if (panelPosition === Position.BOTTOM) { if (sidebarPosition === Position.LEFT) { - this.editor.getContainer().position(this.titlebarHeight, 0, this.statusbarHeight + panelDimension.height, sidebarSize.width + activityBarSize.width); - this.panel.getContainer().position(editorSize.height + this.titlebarHeight, 0, this.statusbarHeight, sidebarSize.width + activityBarSize.width); + position(editorContainer, this.titlebarHeight, 0, this.statusbarHeight + panelDimension.height, sidebarSize.width + activityBarSize.width); + position(panelContainer, editorSize.height + this.titlebarHeight, 0, this.statusbarHeight, sidebarSize.width + activityBarSize.width); } else { - this.editor.getContainer().position(this.titlebarHeight, sidebarSize.width, this.statusbarHeight + panelDimension.height, 0); - this.panel.getContainer().position(editorSize.height + this.titlebarHeight, sidebarSize.width, this.statusbarHeight, 0); + position(editorContainer, this.titlebarHeight, sidebarSize.width, this.statusbarHeight + panelDimension.height, 0); + position(panelContainer, editorSize.height + this.titlebarHeight, sidebarSize.width, this.statusbarHeight, 0); } } else { if (sidebarPosition === Position.LEFT) { - this.editor.getContainer().position(this.titlebarHeight, panelDimension.width, this.statusbarHeight, sidebarSize.width + activityBarSize.width); - this.panel.getContainer().position(this.titlebarHeight, 0, this.statusbarHeight, sidebarSize.width + activityBarSize.width + editorSize.width); + position(editorContainer, this.titlebarHeight, panelDimension.width, this.statusbarHeight, sidebarSize.width + activityBarSize.width); + position(panelContainer, this.titlebarHeight, 0, this.statusbarHeight, sidebarSize.width + activityBarSize.width + editorSize.width); } else { - this.editor.getContainer().position(this.titlebarHeight, sidebarSize.width + activityBarSize.width + panelWidth, this.statusbarHeight, 0); - this.panel.getContainer().position(this.titlebarHeight, sidebarSize.width + activityBarSize.width, this.statusbarHeight, editorSize.width); + position(editorContainer, this.titlebarHeight, sidebarSize.width + activityBarSize.width + panelWidth, this.statusbarHeight, 0); + position(panelContainer, this.titlebarHeight, sidebarSize.width + activityBarSize.width, this.statusbarHeight, editorSize.width); } } // Activity Bar Part - this.activitybar.getContainer().size(null, activityBarSize.height); + const activitybarContainer = this.activitybar.getContainer(); + size(activitybarContainer, null, activityBarSize.height); if (sidebarPosition === Position.LEFT) { - this.activitybar.getContainer().getHTMLElement().style.right = ''; - this.activitybar.getContainer().position(this.titlebarHeight, null, 0, 0); + this.activitybar.getContainer().style.right = ''; + position(activitybarContainer, this.titlebarHeight, null, 0, 0); } else { - this.activitybar.getContainer().getHTMLElement().style.left = ''; - this.activitybar.getContainer().position(this.titlebarHeight, 0, 0, null); + this.activitybar.getContainer().style.left = ''; + position(activitybarContainer, this.titlebarHeight, 0, 0, null); } if (isActivityBarHidden) { - this.activitybar.getContainer().hide(); + hide(activitybarContainer); } else { - this.activitybar.getContainer().show(); + show(activitybarContainer); } // Sidebar Part - this.sidebar.getContainer().size(sidebarSize.width, sidebarSize.height); + const sidebarContainer = this.sidebar.getContainer(); + size(sidebarContainer, sidebarSize.width, sidebarSize.height); const editorAndPanelWidth = editorSize.width + (panelPosition === Position.RIGHT ? panelWidth : 0); if (sidebarPosition === Position.LEFT) { - this.sidebar.getContainer().position(this.titlebarHeight, editorAndPanelWidth, this.statusbarHeight, activityBarSize.width); + position(sidebarContainer, this.titlebarHeight, editorAndPanelWidth, this.statusbarHeight, activityBarSize.width); } else { - this.sidebar.getContainer().position(this.titlebarHeight, activityBarSize.width, this.statusbarHeight, editorAndPanelWidth); + position(sidebarContainer, this.titlebarHeight, activityBarSize.width, this.statusbarHeight, editorAndPanelWidth); } // Statusbar Part - this.statusbar.getContainer().position(this.workbenchSize.height - this.statusbarHeight); + const statusbarContainer = this.statusbar.getContainer(); + position(statusbarContainer, this.workbenchSize.height - this.statusbarHeight); if (isStatusbarHidden) { - this.statusbar.getContainer().hide(); + hide(statusbarContainer); } else { - this.statusbar.getContainer().show(); + show(statusbarContainer); } // Quick open diff --git a/src/vs/workbench/browser/panel.ts b/src/vs/workbench/browser/panel.ts index a44f1f6c2e2..8b05f420b0b 100644 --- a/src/vs/workbench/browser/panel.ts +++ b/src/vs/workbench/browser/panel.ts @@ -95,7 +95,7 @@ export abstract class TogglePanelAction extends Action { const activePanel = this.panelService.getActivePanel(); const activeElement = document.activeElement; - return activePanel && activeElement && DOM.isAncestor(activeElement, (activePanel).getContainer().getHTMLElement()); + return activePanel && activeElement && DOM.isAncestor(activeElement, (activePanel).getContainer()); } } diff --git a/src/vs/workbench/browser/part.ts b/src/vs/workbench/browser/part.ts index 2fa675fe4c7..8f147ff99bc 100644 --- a/src/vs/workbench/browser/part.ts +++ b/src/vs/workbench/browser/part.ts @@ -6,10 +6,9 @@ 'use strict'; import 'vs/css!./media/part'; -import { Builder } from 'vs/base/browser/builder'; import { Component } from 'vs/workbench/common/component'; import { IThemeService, ITheme } from 'vs/platform/theme/common/themeService'; -import { Dimension } from 'vs/base/browser/dom'; +import { Dimension, size } from 'vs/base/browser/dom'; export interface IPartOptions { hasTitle?: boolean; @@ -21,9 +20,9 @@ export interface IPartOptions { * and mandatory content area to show content. */ export abstract class Part extends Component { - private parent: Builder; - private titleArea: Builder; - private contentArea: Builder; + private parent: HTMLElement; + private titleArea: HTMLElement; + private contentArea: HTMLElement; private partLayout: PartLayout; constructor( @@ -48,7 +47,7 @@ export abstract class Part extends Component { * * Called to create title and content area of the part. */ - public create(parent: Builder): void { + public create(parent: HTMLElement): void { this.parent = parent; this.titleArea = this.createTitleArea(parent); this.contentArea = this.createContentArea(parent); @@ -61,35 +60,35 @@ export abstract class Part extends Component { /** * Returns the overall part container. */ - public getContainer(): Builder { + public getContainer(): HTMLElement { return this.parent; } /** * Subclasses override to provide a title area implementation. */ - protected createTitleArea(parent: Builder): Builder { + protected createTitleArea(parent: HTMLElement): HTMLElement { return null; } /** * Returns the title area container. */ - protected getTitleArea(): Builder { + protected getTitleArea(): HTMLElement { return this.titleArea; } /** * Subclasses override to provide a content area implementation. */ - protected createContentArea(parent: Builder): Builder { + protected createContentArea(parent: HTMLElement): HTMLElement { return null; } /** * Returns the content area container. */ - protected getContentArea(): Builder { + protected getContentArea(): HTMLElement { return this.contentArea; } @@ -105,7 +104,7 @@ const TITLE_HEIGHT = 35; export class PartLayout { - constructor(container: Builder, private options: IPartOptions, titleArea: Builder, private contentArea: Builder) { } + constructor(container: HTMLElement, private options: IPartOptions, titleArea: HTMLElement, private contentArea: HTMLElement) { } public layout(dimension: Dimension): Dimension[] { const { width, height } = dimension; @@ -133,7 +132,7 @@ export class PartLayout { // Content if (this.contentArea) { - this.contentArea.size(contentSize.width, contentSize.height); + size(this.contentArea, contentSize.width, contentSize.height); } return sizes; diff --git a/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts b/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts index db68639288e..298b44a0160 100644 --- a/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts +++ b/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts @@ -9,7 +9,7 @@ import 'vs/css!./media/activitybarpart'; import * as nls from 'vs/nls'; import { TPromise } from 'vs/base/common/winjs.base'; import { illegalArgument } from 'vs/base/common/errors'; -import { Builder, $ } from 'vs/base/browser/builder'; +import { $ } from 'vs/base/browser/builder'; import { Action } from 'vs/base/common/actions'; import { ActionsOrientation, ActionBar, Separator } from 'vs/base/browser/ui/actionbar/actionbar'; import { GlobalActivityExtensions, IGlobalActivityRegistry } from 'vs/workbench/common/activity'; @@ -125,7 +125,7 @@ export class ActivitybarPart extends Part { return toDisposable(() => action.setBadge(undefined)); } - public createContentArea(parent: Builder): Builder { + public createContentArea(parent: HTMLElement): HTMLElement { const $el = $(parent); const $result = $('.content').appendTo($el); @@ -156,14 +156,14 @@ export class ActivitybarPart extends Part { }); } - return $result; + return $result.getHTMLElement(); } public updateStyles(): void { super.updateStyles(); // Part container - const container = this.getContainer(); + const container = $(this.getContainer()); const background = this.getColor(ACTIVITY_BAR_BACKGROUND); container.style('background-color', background); diff --git a/src/vs/workbench/browser/parts/compositePart.ts b/src/vs/workbench/browser/parts/compositePart.ts index da608b7abb9..e9b89555eea 100644 --- a/src/vs/workbench/browser/parts/compositePart.ts +++ b/src/vs/workbench/browser/parts/compositePart.ts @@ -224,7 +224,7 @@ export abstract class CompositePart extends Part { 'class': ['composite', this.compositeCSSClass], id: composite.getId() }, div => { - createCompositePromise = composite.create(div).then(() => { + createCompositePromise = composite.create(div.getHTMLElement()).then(() => { composite.updateStyles(); }); }); @@ -401,7 +401,7 @@ export abstract class CompositePart extends Part { }); } - public createTitleArea(parent: Builder): Builder { + public createTitleArea(parent: HTMLElement): HTMLElement { // Title Area Container const titleArea = $(parent).div({ @@ -411,7 +411,7 @@ export abstract class CompositePart extends Part { $(titleArea).on(EventType.CONTEXT_MENU, (e: MouseEvent) => this.onTitleAreaContextMenu(new StandardMouseEvent(e))); // Left Title Label - this.titleLabel = this.createTitleLabel(titleArea); + this.titleLabel = this.createTitleLabel(titleArea.getHTMLElement()); // Right Actions Container $(titleArea).div({ @@ -426,10 +426,10 @@ export abstract class CompositePart extends Part { }); }); - return titleArea; + return titleArea.getHTMLElement(); } - protected createTitleLabel(parent: Builder): ICompositeTitleLabel { + protected createTitleLabel(parent: HTMLElement): ICompositeTitleLabel { let titleLabel: Builder; $(parent).div({ 'class': 'title-label' @@ -486,14 +486,14 @@ export abstract class CompositePart extends Part { return undefined; } - public createContentArea(parent: Builder): Builder { + public createContentArea(parent: HTMLElement): HTMLElement { return $(parent).div({ 'class': 'content' }, div => { this.progressBar = new ProgressBar(div.getHTMLElement()); this.toUnbind.push(attachProgressBarStyler(this.progressBar, this.themeService)); this.progressBar.hide(); - }); + }).getHTMLElement(); } private onError(error: any): void { diff --git a/src/vs/workbench/browser/parts/editor/baseEditor.ts b/src/vs/workbench/browser/parts/editor/baseEditor.ts index b38ca7a0638..e7809135889 100644 --- a/src/vs/workbench/browser/parts/editor/baseEditor.ts +++ b/src/vs/workbench/browser/parts/editor/baseEditor.ts @@ -5,7 +5,6 @@ 'use strict'; import { TPromise } from 'vs/base/common/winjs.base'; -import { Builder } from 'vs/base/browser/builder'; import { Panel } from 'vs/workbench/browser/panel'; import { EditorInput, EditorOptions } from 'vs/workbench/common/editor'; import { IEditor, Position } from 'vs/platform/editor/common/editor'; @@ -64,9 +63,9 @@ export abstract class BaseEditor extends Panel implements IEditor { this._options = null; } - public create(parent: Builder): void; // create is sync for editors - public create(parent: Builder): TPromise; - public create(parent: Builder): TPromise { + public create(parent: HTMLElement): void; // create is sync for editors + public create(parent: HTMLElement): TPromise; + public create(parent: HTMLElement): TPromise { const res = super.create(parent); // Create Editor @@ -76,9 +75,9 @@ export abstract class BaseEditor extends Panel implements IEditor { } /** - * Called to create the editor in the parent builder. + * Called to create the editor in the parent HTMLElement. */ - protected abstract createEditor(parent: Builder): void; + protected abstract createEditor(parent: HTMLElement): void; /** * Overload this function to allow for passing in a position argument. diff --git a/src/vs/workbench/browser/parts/editor/binaryEditor.ts b/src/vs/workbench/browser/parts/editor/binaryEditor.ts index e50946ef3ad..77d4fbf3b0c 100644 --- a/src/vs/workbench/browser/parts/editor/binaryEditor.ts +++ b/src/vs/workbench/browser/parts/editor/binaryEditor.ts @@ -60,7 +60,7 @@ export abstract class BaseBinaryResourceEditor extends BaseEditor { return this.input ? this.input.getName() : nls.localize('binaryEditor', "Binary Viewer"); } - protected createEditor(parent: Builder): void { + protected createEditor(parent: HTMLElement): void { // Container for Binary const binaryContainerElement = document.createElement('div'); @@ -71,7 +71,7 @@ export abstract class BaseBinaryResourceEditor extends BaseEditor { // Custom Scrollbars this.scrollbar = new DomScrollableElement(binaryContainerElement, { horizontal: ScrollbarVisibility.Auto, vertical: ScrollbarVisibility.Auto }); - parent.getHTMLElement().appendChild(this.scrollbar.getDomNode()); + parent.appendChild(this.scrollbar.getDomNode()); } public setInput(input: EditorInput, options?: EditorOptions): TPromise { @@ -99,7 +99,7 @@ export abstract class BaseBinaryResourceEditor extends BaseEditor { // Render Input this.resourceViewerContext = ResourceViewer.show( { name: model.getName(), resource: model.getResource(), size: model.getSize(), etag: model.getETag(), mime: model.getMime() }, - this.binaryContainer, + this.binaryContainer.getHTMLElement(), this.scrollbar, resource => this.callbacks.openInternal(input, options), resource => this.callbacks.openExternal(resource), diff --git a/src/vs/workbench/browser/parts/editor/editorGroupsControl.ts b/src/vs/workbench/browser/parts/editor/editorGroupsControl.ts index 26779caaa6e..f578a8d5170 100644 --- a/src/vs/workbench/browser/parts/editor/editorGroupsControl.ts +++ b/src/vs/workbench/browser/parts/editor/editorGroupsControl.ts @@ -118,7 +118,7 @@ export class EditorGroupsControl extends Themable implements IEditorGroupsContro private stacks: IEditorStacksModel; - private parent: Builder; + private parent: HTMLElement; private dimension: DOM.Dimension; private dragging: boolean; @@ -168,7 +168,7 @@ export class EditorGroupsControl extends Themable implements IEditorGroupsContro private transfer = LocalSelectionTransfer.getInstance(); constructor( - parent: Builder, + parent: HTMLElement, groupOrientation: GroupOrientation, @IWorkbenchEditorService private editorService: IWorkbenchEditorService, @IEditorGroupService private editorGroupService: IEditorGroupService, @@ -367,8 +367,8 @@ export class EditorGroupsControl extends Themable implements IEditorGroupsContro this.trackFocus(editor, position); // Find target container and build into - const target = this.silos[position].child(); - editor.getContainer().build(target); + const target = this.silos[position].child().getHTMLElement(); + target.appendChild(editor.getContainer()); // Adjust layout according to provided ratios (used when restoring multiple editors at once) if (ratio && (ratio.length === 2 || ratio.length === 3)) { @@ -440,7 +440,7 @@ export class EditorGroupsControl extends Themable implements IEditorGroupsContro } // Show editor container - editor.getContainer().show(); + DOM.show(editor.getContainer()); } private getVisibleEditorCount(): number { @@ -552,7 +552,11 @@ export class EditorGroupsControl extends Themable implements IEditorGroupsContro this.clearPosition(position); // Take editor container offdom and hide - editor.getContainer().offDOM().hide(); + const editorContainer = editor.getContainer(); + if (editorContainer.parentNode) { + editorContainer.parentNode.removeChild(editorContainer); + } + DOM.hide(editorContainer); // Adjust layout and rochade if instructed to do so if (layoutAndRochade) { @@ -778,10 +782,10 @@ export class EditorGroupsControl extends Themable implements IEditorGroupsContro this.layoutVertically = (orientation !== 'horizontal'); // Editor Layout - const verticalLayouting = this.parent.hasClass('vertical-layout'); + const verticalLayouting = DOM.hasClass(this.parent, 'vertical-layout'); if (verticalLayouting !== this.layoutVertically) { - this.parent.removeClass('vertical-layout', 'horizontal-layout'); - this.parent.addClass(this.layoutVertically ? 'vertical-layout' : 'horizontal-layout'); + DOM.removeClasses(this.parent, 'vertical-layout', 'horizontal-layout'); + DOM.addClass(this.parent, this.layoutVertically ? 'vertical-layout' : 'horizontal-layout'); this.sashOne.setOrientation(this.layoutVertically ? Orientation.VERTICAL : Orientation.HORIZONTAL); this.sashTwo.setOrientation(this.layoutVertically ? Orientation.VERTICAL : Orientation.HORIZONTAL); @@ -966,16 +970,16 @@ export class EditorGroupsControl extends Themable implements IEditorGroupsContro private create(): void { // Store layout as class property - this.parent.addClass(this.layoutVertically ? 'vertical-layout' : 'horizontal-layout'); + DOM.addClass(this.parent, this.layoutVertically ? 'vertical-layout' : 'horizontal-layout'); // Allow to drop into container to open - this.enableDropTarget(this.parent.getHTMLElement()); + this.enableDropTarget(this.parent); // Silo One this.silos[Position.ONE] = $(this.parent).div({ class: 'one-editor-silo editor-one' }); // Sash One - this.sashOne = new Sash(this.parent.getHTMLElement(), this, { baseSize: 5, orientation: this.layoutVertically ? Orientation.VERTICAL : Orientation.HORIZONTAL }); + this.sashOne = new Sash(this.parent, this, { baseSize: 5, orientation: this.layoutVertically ? Orientation.VERTICAL : Orientation.HORIZONTAL }); this.toUnbind.push(this.sashOne.onDidStart(() => this.onSashOneDragStart())); this.toUnbind.push(this.sashOne.onDidChange((e: ISashEvent) => this.onSashOneDrag(e))); this.toUnbind.push(this.sashOne.onDidEnd(() => this.onSashOneDragEnd())); @@ -986,7 +990,7 @@ export class EditorGroupsControl extends Themable implements IEditorGroupsContro this.silos[Position.TWO] = $(this.parent).div({ class: 'one-editor-silo editor-two' }); // Sash Two - this.sashTwo = new Sash(this.parent.getHTMLElement(), this, { baseSize: 5, orientation: this.layoutVertically ? Orientation.VERTICAL : Orientation.HORIZONTAL }); + this.sashTwo = new Sash(this.parent, this, { baseSize: 5, orientation: this.layoutVertically ? Orientation.VERTICAL : Orientation.HORIZONTAL }); this.toUnbind.push(this.sashTwo.onDidStart(() => this.onSashTwoDragStart())); this.toUnbind.push(this.sashTwo.onDidChange((e: ISashEvent) => this.onSashTwoDrag(e))); this.toUnbind.push(this.sashTwo.onDidEnd(() => this.onSashTwoDragEnd())); @@ -1282,7 +1286,7 @@ export class EditorGroupsControl extends Themable implements IEditorGroupsContro if (!overlay) { const containers = $this.visibleEditors.filter(e => !!e).map(e => e.getContainer()); containers.forEach((container, index) => { - if (container && DOM.isAncestor(target, container.getHTMLElement())) { + if (container && DOM.isAncestor(target, container)) { const activeContrastBorderColor = $this.getColor(activeContrastBorder); overlay = $('div').style({ top: $this.tabOptions.showTabs ? `${EditorGroupsControl.EDITOR_TITLE_HEIGHT}px` : 0, @@ -1625,11 +1629,11 @@ export class EditorGroupsControl extends Themable implements IEditorGroupsContro let borderColor = null; if (isDragging) { - this.parent.addClass('dragging'); + DOM.addClass(this.parent, 'dragging'); silo.addClass('dragging'); borderColor = this.getColor(EDITOR_GROUP_BORDER) || this.getColor(contrastBorder); } else { - this.parent.removeClass('dragging'); + DOM.removeClass(this.parent, 'dragging'); silo.removeClass('dragging'); } @@ -2201,9 +2205,9 @@ export class EditorGroupsControl extends Themable implements IEditorGroupsContro } const editorContainer = editor.getContainer(); - editorContainer.style('margin-left', this.centeredEditorActive ? `${editorPosition}px` : null); - editorContainer.style('width', this.centeredEditorActive ? `${editorWidth}px` : null); - editorContainer.style('border-color', this.centeredEditorActive ? this.getColor(EDITOR_GROUP_BORDER) || this.getColor(contrastBorder) : null); + editorContainer.style.marginLeft = this.centeredEditorActive ? `${editorPosition}px` : null; + editorContainer.style.width = this.centeredEditorActive ? `${editorWidth}px` : null; + editorContainer.style.borderColor = this.centeredEditorActive ? this.getColor(EDITOR_GROUP_BORDER) || this.getColor(contrastBorder) : null; editor.layout(new DOM.Dimension(editorWidth, editorHeight)); } } diff --git a/src/vs/workbench/browser/parts/editor/editorPart.ts b/src/vs/workbench/browser/parts/editor/editorPart.ts index 97e25171b1a..246dc838d0e 100644 --- a/src/vs/workbench/browser/parts/editor/editorPart.ts +++ b/src/vs/workbench/browser/parts/editor/editorPart.ts @@ -9,7 +9,6 @@ import 'vs/css!./media/editorpart'; import 'vs/workbench/browser/parts/editor/editor.contribution'; import { TPromise } from 'vs/base/common/winjs.base'; import { Registry } from 'vs/platform/registry/common/platform'; -import { Builder, $ } from 'vs/base/browser/builder'; import * as nls from 'vs/nls'; import * as strings from 'vs/base/common/strings'; import * as arrays from 'vs/base/common/arrays'; @@ -40,7 +39,7 @@ import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/c import { IThemeService } from 'vs/platform/theme/common/themeService'; import { editorBackground } from 'vs/platform/theme/common/colorRegistry'; import { EDITOR_GROUP_BACKGROUND } from 'vs/workbench/common/theme'; -import { createCSSRule, Dimension } from 'vs/base/browser/dom'; +import { createCSSRule, Dimension, addClass, removeClass } from 'vs/base/browser/dom'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { join } from 'vs/base/common/paths'; import { IEditorDescriptor, IEditorRegistry, Extensions as EditorExtensions } from 'vs/workbench/browser/editor'; @@ -470,11 +469,12 @@ export class EditorPart extends Part implements IEditorPart, IEditorGroupService // Create editor as needed if (!editor.getContainer()) { - editor.create($().div({ - 'class': 'editor-container', - 'role': 'tabpanel', - id: descriptor.getId() - })); + const editorContainer = document.createElement('div'); + editorContainer.id = descriptor.getId(); + addClass(editorContainer, 'editor-container'); + editorContainer.setAttribute('role', 'tabpanel'); + + editor.create(editorContainer); } return editor; @@ -1134,12 +1134,12 @@ export class EditorPart extends Part implements IEditorPart, IEditorGroupService return this.editorGroupsControl.getGroupOrientation(); } - public createContentArea(parent: Builder): Builder { + public createContentArea(parent: HTMLElement): HTMLElement { // Content Container - const contentArea = $(parent) - .div() - .addClass('content'); + const contentArea = document.createElement('div'); + addClass(contentArea, 'content'); + parent.appendChild(contentArea); // get settings this.memento = this.getMemento(this.storageService, MementoScope.WORKSPACE); @@ -1157,19 +1157,19 @@ export class EditorPart extends Part implements IEditorPart, IEditorGroupService // Part container const container = this.getContainer(); - container.style('background-color', this.getColor(editorBackground)); + container.style.backgroundColor = this.getColor(editorBackground); // Content area const content = this.getContentArea(); const groupCount = this.stacks.groups.length; if (groupCount > 1) { - content.addClass('multiple-groups'); + addClass(content, 'multiple-groups'); } else { - content.removeClass('multiple-groups'); + removeClass(content, 'multiple-groups'); } - content.style('background-color', groupCount > 0 ? this.getColor(EDITOR_GROUP_BACKGROUND) : null); + content.style.backgroundColor = groupCount > 0 ? this.getColor(EDITOR_GROUP_BACKGROUND) : null; } private onGroupFocusChanged(): void { diff --git a/src/vs/workbench/browser/parts/editor/resourceViewer.ts b/src/vs/workbench/browser/parts/editor/resourceViewer.ts index 973506ff5c7..02e222d2456 100644 --- a/src/vs/workbench/browser/parts/editor/resourceViewer.ts +++ b/src/vs/workbench/browser/parts/editor/resourceViewer.ts @@ -132,7 +132,7 @@ export class ResourceViewer { public static show( descriptor: IResourceDescriptor, - container: Builder, + container: HTMLElement, scrollbar: DomScrollableElement, openInternalClb: (uri: URI) => void, openExternalClb: (uri: URI) => void, @@ -184,7 +184,7 @@ class ImageView { private static readonly BASE64_MARKER = 'base64,'; public static create( - container: Builder, + container: HTMLElement, descriptor: IResourceDescriptor, scrollbar: DomScrollableElement, openExternalClb: (uri: URI) => void, @@ -221,7 +221,7 @@ class ImageView { class LargeImageView { public static create( - container: Builder, + container: HTMLElement, descriptor: IResourceDescriptor, openExternalClb: (uri: URI) => void ) { @@ -247,7 +247,7 @@ class LargeImageView { class FileTooLargeFileView { public static create( - container: Builder, + container: HTMLElement, descriptor: IResourceDescriptor, scrollbar: DomScrollableElement, metadataClb: (meta: string) => void @@ -270,7 +270,7 @@ class FileTooLargeFileView { class FileSeemsBinaryFileView { public static create( - container: Builder, + container: HTMLElement, descriptor: IResourceDescriptor, scrollbar: DomScrollableElement, openInternalClb: (uri: URI) => void, @@ -434,7 +434,7 @@ class InlineImageView { private static readonly imageStateCache = new LRUCache(100); public static create( - container: Builder, + container: HTMLElement, descriptor: IResourceDescriptor, scrollbar: DomScrollableElement, metadataClb: (meta: string) => void diff --git a/src/vs/workbench/browser/parts/editor/sideBySideEditor.ts b/src/vs/workbench/browser/parts/editor/sideBySideEditor.ts index d2e082c1d86..ea554cb2cc4 100644 --- a/src/vs/workbench/browser/parts/editor/sideBySideEditor.ts +++ b/src/vs/workbench/browser/parts/editor/sideBySideEditor.ts @@ -5,7 +5,6 @@ import { TPromise } from 'vs/base/common/winjs.base'; import * as DOM from 'vs/base/browser/dom'; -import { Builder } from 'vs/base/browser/builder'; import { Registry } from 'vs/platform/registry/common/platform'; import { EditorInput, EditorOptions, SideBySideEditorInput } from 'vs/workbench/common/editor'; import { BaseEditor } from 'vs/workbench/browser/parts/editor/baseEditor'; @@ -39,10 +38,9 @@ export class SideBySideEditor extends BaseEditor { super(SideBySideEditor.ID, telemetryService, themeService); } - protected createEditor(parent: Builder): void { - const parentElement = parent.getHTMLElement(); - DOM.addClass(parentElement, 'side-by-side-editor'); - this.createSash(parentElement); + protected createEditor(parent: HTMLElement): void { + DOM.addClass(parent, 'side-by-side-editor'); + this.createSash(parent); } public setInput(newInput: SideBySideEditorInput, options?: EditorOptions): TPromise { @@ -135,7 +133,7 @@ export class SideBySideEditor extends BaseEditor { const descriptor = Registry.as(EditorExtensions.Editors).getEditor(editorInput); const editor = descriptor.instantiate(this.instantiationService); - editor.create(new Builder(container)); + editor.create(container); editor.setVisible(this.isVisible(), this.position); return editor; @@ -149,7 +147,7 @@ export class SideBySideEditor extends BaseEditor { } private createEditorContainers(): void { - const parentElement = this.getContainer().getHTMLElement(); + const parentElement = this.getContainer(); this.detailsEditorContainer = DOM.append(parentElement, DOM.$('.details-editor-container')); this.detailsEditorContainer.style.position = 'absolute'; this.masterEditorContainer = DOM.append(parentElement, DOM.$('.master-editor-container')); @@ -191,7 +189,7 @@ export class SideBySideEditor extends BaseEditor { } private disposeEditors(): void { - const parentContainer = this.getContainer().getHTMLElement(); + const parentContainer = this.getContainer(); if (this.detailsEditor) { this.detailsEditor.dispose(); this.detailsEditor = null; diff --git a/src/vs/workbench/browser/parts/editor/textDiffEditor.ts b/src/vs/workbench/browser/parts/editor/textDiffEditor.ts index 0046ce5ab25..169c1c9b94a 100644 --- a/src/vs/workbench/browser/parts/editor/textDiffEditor.ts +++ b/src/vs/workbench/browser/parts/editor/textDiffEditor.ts @@ -9,7 +9,6 @@ import 'vs/css!./media/textdiffeditor'; import { TPromise } from 'vs/base/common/winjs.base'; import * as nls from 'vs/nls'; import * as objects from 'vs/base/common/objects'; -import { Builder } from 'vs/base/browser/builder'; import { Action, IAction } from 'vs/base/common/actions'; import { onUnexpectedError } from 'vs/base/common/errors'; import * as types from 'vs/base/common/types'; @@ -82,7 +81,7 @@ export class TextDiffEditor extends BaseTextEditor { return nls.localize('textDiffEditor', "Text Diff Editor"); } - public createEditorControl(parent: Builder, configuration: IEditorOptions): IDiffEditor { + public createEditorControl(parent: HTMLElement, configuration: IEditorOptions): IDiffEditor { // Actions this.nextDiffAction = new NavigateAction(this, true); @@ -122,7 +121,7 @@ export class TextDiffEditor extends BaseTextEditor { // Create a special child of instantiator that will delegate all calls to openEditor() to the same diff editor if the input matches with the modified one const diffEditorInstantiator = this.instantiationService.createChild(new ServiceCollection([IWorkbenchEditorService, delegatingEditorService])); - return diffEditorInstantiator.createInstance(DiffEditorWidget, parent.getHTMLElement(), configuration); + return diffEditorInstantiator.createInstance(DiffEditorWidget, parent, configuration); } public setInput(input: EditorInput, options?: EditorOptions): TPromise { diff --git a/src/vs/workbench/browser/parts/editor/textEditor.ts b/src/vs/workbench/browser/parts/editor/textEditor.ts index afec6d1b42f..bb7aae0c4f6 100644 --- a/src/vs/workbench/browser/parts/editor/textEditor.ts +++ b/src/vs/workbench/browser/parts/editor/textEditor.ts @@ -8,7 +8,6 @@ import * as nls from 'vs/nls'; import { TPromise } from 'vs/base/common/winjs.base'; import URI from 'vs/base/common/uri'; -import { Builder } from 'vs/base/browser/builder'; import * as objects from 'vs/base/common/objects'; import * as types from 'vs/base/common/types'; import * as errors from 'vs/base/common/errors'; @@ -43,7 +42,7 @@ export interface IEditorConfiguration { */ export abstract class BaseTextEditor extends BaseEditor { private editorControl: IEditor; - private _editorContainer: Builder; + private _editorContainer: HTMLElement; private hasPendingConfigurationChange: boolean; private lastAppliedEditorOptions: IEditorOptions; @@ -123,7 +122,7 @@ export abstract class BaseTextEditor extends BaseEditor { return overrides; } - protected createEditor(parent: Builder): void { + protected createEditor(parent: HTMLElement): void { // Editor for Text this._editorContainer = parent; @@ -177,10 +176,10 @@ export abstract class BaseTextEditor extends BaseEditor { * * The passed in configuration object should be passed to the editor control when creating it. */ - protected createEditorControl(parent: Builder, configuration: IEditorOptions): IEditor { + protected createEditorControl(parent: HTMLElement, configuration: IEditorOptions): IEditor { // Use a getter for the instantiation service since some subclasses might use scoped instantiation services - return this.instantiationService.createInstance(CodeEditor, parent.getHTMLElement(), configuration); + return this.instantiationService.createInstance(CodeEditor, parent, configuration); } public setInput(input: EditorInput, options?: EditorOptions): TPromise { @@ -189,7 +188,7 @@ export abstract class BaseTextEditor extends BaseEditor { // Update editor options after having set the input. We do this because there can be // editor input specific options (e.g. an ARIA label depending on the input showing) this.updateEditorConfiguration(); - this._editorContainer.getHTMLElement().setAttribute('aria-label', this.computeAriaLabel()); + this._editorContainer.setAttribute('aria-label', this.computeAriaLabel()); }); } diff --git a/src/vs/workbench/browser/parts/panel/panelPart.ts b/src/vs/workbench/browser/parts/panel/panelPart.ts index 90d74fd857f..852757046ca 100644 --- a/src/vs/workbench/browser/parts/panel/panelPart.ts +++ b/src/vs/workbench/browser/parts/panel/panelPart.ts @@ -7,7 +7,7 @@ import 'vs/css!./media/panelpart'; import { TPromise } from 'vs/base/common/winjs.base'; import { IAction, Action } from 'vs/base/common/actions'; import { Event } from 'vs/base/common/event'; -import { Builder } from 'vs/base/browser/builder'; +import { $ } from 'vs/base/browser/builder'; import { Registry } from 'vs/platform/registry/common/platform'; import { ActionsOrientation } from 'vs/base/browser/ui/actionbar/actionbar'; import { IPanel } from 'vs/workbench/common/panel'; @@ -124,11 +124,11 @@ export class PanelPart extends CompositePart implements IPanelService { public updateStyles(): void { super.updateStyles(); - const container = this.getContainer(); + const container = $(this.getContainer()); container.style('background-color', this.getColor(PANEL_BACKGROUND)); container.style('border-left-color', this.getColor(PANEL_BORDER) || this.getColor(contrastBorder)); - const title = this.getTitleArea(); + const title = $(this.getTitleArea()); title.style('border-top-color', this.getColor(PANEL_BORDER) || this.getColor(contrastBorder)); } @@ -208,8 +208,8 @@ export class PanelPart extends CompositePart implements IPanelService { return this.hideActiveComposite().then(composite => void 0); } - protected createTitleLabel(parent: Builder): ICompositeTitleLabel { - const titleArea = this.compositeBar.create(parent.getHTMLElement()); + protected createTitleLabel(parent: HTMLElement): ICompositeTitleLabel { + const titleArea = this.compositeBar.create(parent); titleArea.classList.add('panel-switcher-container'); return { diff --git a/src/vs/workbench/browser/parts/sidebar/sidebarPart.ts b/src/vs/workbench/browser/parts/sidebar/sidebarPart.ts index 4ee3bbdd603..172dfe24ff2 100644 --- a/src/vs/workbench/browser/parts/sidebar/sidebarPart.ts +++ b/src/vs/workbench/browser/parts/sidebar/sidebarPart.ts @@ -27,6 +27,7 @@ import { contrastBorder } from 'vs/platform/theme/common/colorRegistry'; import { SIDE_BAR_TITLE_FOREGROUND, SIDE_BAR_BACKGROUND, SIDE_BAR_FOREGROUND, SIDE_BAR_BORDER } from 'vs/workbench/common/theme'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { Dimension } from 'vs/base/browser/dom'; +import { $ } from 'vs/base/browser/builder'; export class SidebarPart extends CompositePart { @@ -79,7 +80,7 @@ export class SidebarPart extends CompositePart { super.updateStyles(); // Part container - const container = this.getContainer(); + const container = $(this.getContainer()); container.style('background-color', this.getColor(SIDE_BAR_BACKGROUND)); container.style('color', this.getColor(SIDE_BAR_FOREGROUND)); diff --git a/src/vs/workbench/browser/parts/statusbar/statusbarPart.ts b/src/vs/workbench/browser/parts/statusbar/statusbarPart.ts index 1004fac2c9b..73592ff0f2b 100644 --- a/src/vs/workbench/browser/parts/statusbar/statusbarPart.ts +++ b/src/vs/workbench/browser/parts/statusbar/statusbarPart.ts @@ -10,7 +10,7 @@ import * as nls from 'vs/nls'; import { toErrorMessage } from 'vs/base/common/errorMessage'; import { TPromise } from 'vs/base/common/winjs.base'; import { dispose, IDisposable } from 'vs/base/common/lifecycle'; -import { Builder, $ } from 'vs/base/browser/builder'; +import { $ } from 'vs/base/browser/builder'; import { OcticonLabel } from 'vs/base/browser/ui/octiconLabel/octiconLabel'; import { Registry } from 'vs/platform/registry/common/platform'; import { ICommandService } from 'vs/platform/commands/common/commands'; @@ -39,7 +39,7 @@ export class StatusbarPart extends Part implements IStatusbarService { private static readonly PRIORITY_PROP = 'priority'; private static readonly ALIGNMENT_PROP = 'alignment'; - private statusItemsContainer: Builder; + private statusItemsContainer: HTMLElement; private statusMsgDispose: IDisposable; private styleElement: HTMLStyleElement; @@ -67,7 +67,7 @@ export class StatusbarPart extends Part implements IStatusbarService { const toDispose = item.render(el); // Insert according to priority - const container = this.statusItemsContainer.getHTMLElement(); + const container = this.statusItemsContainer; const neighbours = this.getEntries(alignment); let inserted = false; for (let i = 0; i < neighbours.length; i++) { @@ -101,7 +101,7 @@ export class StatusbarPart extends Part implements IStatusbarService { private getEntries(alignment: StatusbarAlignment): HTMLElement[] { const entries: HTMLElement[] = []; - const container = this.statusItemsContainer.getHTMLElement(); + const container = this.statusItemsContainer; const children = container.children; for (let i = 0; i < children.length; i++) { const childElement = children.item(i); @@ -113,8 +113,8 @@ export class StatusbarPart extends Part implements IStatusbarService { return entries; } - public createContentArea(parent: Builder): Builder { - this.statusItemsContainer = $(parent); + public createContentArea(parent: HTMLElement): HTMLElement { + this.statusItemsContainer = parent; // Fill in initial items that were contributed from the registry const registry = Registry.as(Extensions.Statusbar); @@ -129,7 +129,7 @@ export class StatusbarPart extends Part implements IStatusbarService { const el = this.doCreateStatusItem(descriptor.alignment, descriptor.priority); const dispose = item.render(el); - this.statusItemsContainer.append(el); + this.statusItemsContainer.appendChild(el); return dispose; })); @@ -140,7 +140,7 @@ export class StatusbarPart extends Part implements IStatusbarService { protected updateStyles(): void { super.updateStyles(); - const container = this.getContainer(); + const container = $(this.getContainer()); // Background colors const backgroundColor = this.getColor(this.contextService.getWorkbenchState() !== WorkbenchState.EMPTY ? STATUS_BAR_BACKGROUND : STATUS_BAR_NO_FOLDER_BACKGROUND); diff --git a/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts b/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts index cef65a843f3..4fe96407a85 100644 --- a/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts +++ b/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts @@ -226,7 +226,7 @@ export class TitlebarPart extends Part implements ITitleService { }); } - public createContentArea(parent: Builder): Builder { + public createContentArea(parent: HTMLElement): HTMLElement { this.titleContainer = $(parent); // Title @@ -262,20 +262,19 @@ export class TitlebarPart extends Part implements ITitleService { }, 0 /* need a timeout because we are in capture phase */); }, void 0, true /* use capture to know the currently active element properly */); - return this.titleContainer; + return this.titleContainer.getHTMLElement(); } protected updateStyles(): void { super.updateStyles(); // Part container - const container = this.getContainer(); - if (container) { - container.style('color', this.getColor(this.isInactive ? TITLE_BAR_INACTIVE_FOREGROUND : TITLE_BAR_ACTIVE_FOREGROUND)); - container.style('background-color', this.getColor(this.isInactive ? TITLE_BAR_INACTIVE_BACKGROUND : TITLE_BAR_ACTIVE_BACKGROUND)); + if (this.titleContainer) { + this.titleContainer.style('color', this.getColor(this.isInactive ? TITLE_BAR_INACTIVE_FOREGROUND : TITLE_BAR_ACTIVE_FOREGROUND)); + this.titleContainer.style('background-color', this.getColor(this.isInactive ? TITLE_BAR_INACTIVE_BACKGROUND : TITLE_BAR_ACTIVE_BACKGROUND)); const titleBorder = this.getColor(TITLE_BAR_BORDER); - container.style('border-bottom', titleBorder ? `1px solid ${titleBorder}` : null); + this.titleContainer.style('border-bottom', titleBorder ? `1px solid ${titleBorder}` : null); } } diff --git a/src/vs/workbench/browser/parts/views/customView.ts b/src/vs/workbench/browser/parts/views/customView.ts index 0399b1e7df4..c064cc0bccd 100644 --- a/src/vs/workbench/browser/parts/views/customView.ts +++ b/src/vs/workbench/browser/parts/views/customView.ts @@ -9,7 +9,6 @@ import { IDisposable, Disposable, dispose } from 'vs/base/common/lifecycle'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { TPromise } from 'vs/base/common/winjs.base'; import * as DOM from 'vs/base/browser/dom'; -import { $ } from 'vs/base/browser/builder'; import { LIGHT, FileThemeIcon, FolderThemeIcon } from 'vs/platform/theme/common/themeService'; import { ITree, IDataSource, IRenderer, ContextMenuEvent } from 'vs/base/parts/tree/browser/tree'; import { TreeItemCollapsibleState, ITreeItem, ITreeViewer, ICustomViewsService, ITreeViewDataProvider, ViewsRegistry, IViewDescriptor, TreeViewItemHandleArg, ICustomViewDescriptor, IViewsViewlet } from 'vs/workbench/common/views'; @@ -171,9 +170,9 @@ class CustomTreeViewer extends Disposable implements ITreeViewer { if (this.tree) { if (this.isVisible) { - $(this.tree.getHTMLElement()).show(); + DOM.show(this.tree.getHTMLElement()); } else { - $(this.tree.getHTMLElement()).hide(); // make sure the tree goes out of the tabindex world by hiding it + DOM.hide(this.tree.getHTMLElement()); // make sure the tree goes out of the tabindex world by hiding it } if (this.isVisible) { diff --git a/src/vs/workbench/browser/parts/views/panelViewlet.ts b/src/vs/workbench/browser/parts/views/panelViewlet.ts index 28f0881979c..0fe47e54d6b 100644 --- a/src/vs/workbench/browser/parts/views/panelViewlet.ts +++ b/src/vs/workbench/browser/parts/views/panelViewlet.ts @@ -10,8 +10,7 @@ import { Event, Emitter, filterEvent } from 'vs/base/common/event'; import { ColorIdentifier, contrastBorder } from 'vs/platform/theme/common/colorRegistry'; import { attachStyler, IColorMapping } from 'vs/platform/theme/common/styler'; import { SIDE_BAR_DRAG_AND_DROP_BACKGROUND, SIDE_BAR_SECTION_HEADER_FOREGROUND, SIDE_BAR_SECTION_HEADER_BACKGROUND } from 'vs/workbench/common/theme'; -import { Builder } from 'vs/base/browser/builder'; -import { append, $, trackFocus, toggleClass, EventType, isAncestor, Dimension } from 'vs/base/browser/dom'; +import { append, $, trackFocus, toggleClass, EventType, isAncestor, Dimension, addDisposableListener } from 'vs/base/browser/dom'; import { IDisposable, combinedDisposable } from 'vs/base/common/lifecycle'; import { firstIndex } from 'vs/base/common/arrays'; import { IAction, IActionRunner } from 'vs/base/common/actions'; @@ -166,13 +165,12 @@ export class PanelViewlet extends Viewlet { super(id, partService, telemetryService, themeService); } - async create(parent: Builder): TPromise { + async create(parent: HTMLElement): TPromise { super.create(parent); - const container = parent.getHTMLElement(); - this.panelview = this._register(new PanelView(container, this.options)); + this.panelview = this._register(new PanelView(parent, this.options)); this._register(this.panelview.onDidDrop(({ from, to }) => this.movePanel(from as ViewletPanel, to as ViewletPanel))); - this._register(parent.on(EventType.CONTEXT_MENU, (e: MouseEvent) => this.showContextMenu(new StandardMouseEvent(e)))); + this._register(addDisposableListener(parent, EventType.CONTEXT_MENU, (e: MouseEvent) => this.showContextMenu(new StandardMouseEvent(e)))); } private showContextMenu(event: StandardMouseEvent): void { diff --git a/src/vs/workbench/browser/parts/views/viewsViewlet.ts b/src/vs/workbench/browser/parts/views/viewsViewlet.ts index a9790f253c6..dbc78516337 100644 --- a/src/vs/workbench/browser/parts/views/viewsViewlet.ts +++ b/src/vs/workbench/browser/parts/views/viewsViewlet.ts @@ -6,7 +6,6 @@ import { TPromise } from 'vs/base/common/winjs.base'; import * as errors from 'vs/base/common/errors'; import * as DOM from 'vs/base/browser/dom'; -import { $, Builder } from 'vs/base/browser/builder'; import { Scope } from 'vs/workbench/common/memento'; import { dispose, IDisposable } from 'vs/base/common/lifecycle'; import { IAction, IActionRunner } from 'vs/base/common/actions'; @@ -137,9 +136,9 @@ export abstract class TreeViewsViewletPanel extends ViewsViewletPanel { } if (isVisible) { - $(tree.getHTMLElement()).show(); + DOM.show(tree.getHTMLElement()); } else { - $(tree.getHTMLElement()).hide(); // make sure the tree goes out of the tabindex world by hiding it + DOM.hide(tree.getHTMLElement()); // make sure the tree goes out of the tabindex world by hiding it } if (isVisible) { @@ -215,7 +214,7 @@ export class ViewsViewlet extends PanelViewlet implements IViewsViewlet { this.viewletSettings = this.getMemento(storageService, Scope.WORKSPACE); } - async create(parent: Builder): TPromise { + async create(parent: HTMLElement): TPromise { await super.create(parent); this._register(this.onDidSashChange(() => this.snapshotViewsStates())); @@ -644,7 +643,7 @@ export class PersistentViewsViewlet extends ViewsViewlet { this._register(this.onDidChangeViewVisibilityState(id => this.onViewVisibilityChanged(id))); } - create(parent: Builder): TPromise { + create(parent: HTMLElement): TPromise { this.loadViewsStates(); return super.create(parent); } diff --git a/src/vs/workbench/browser/viewlet.ts b/src/vs/workbench/browser/viewlet.ts index 1d5419d0e5e..e6ed31632fe 100644 --- a/src/vs/workbench/browser/viewlet.ts +++ b/src/vs/workbench/browser/viewlet.ts @@ -154,7 +154,7 @@ export class ToggleViewletAction extends Action { const activeViewlet = this.viewletService.getActiveViewlet(); const activeElement = document.activeElement; - return activeViewlet && activeElement && DOM.isAncestor(activeElement, (activeViewlet).getContainer().getHTMLElement()); + return activeViewlet && activeElement && DOM.isAncestor(activeElement, (activeViewlet).getContainer()); } } diff --git a/src/vs/workbench/electron-browser/workbench.ts b/src/vs/workbench/electron-browser/workbench.ts index e2a5c184d8d..e4e9cc79e7f 100644 --- a/src/vs/workbench/electron-browser/workbench.ts +++ b/src/vs/workbench/electron-browser/workbench.ts @@ -721,7 +721,7 @@ export class Workbench implements IPartService { } public getContainer(part: Parts): HTMLElement { - let container: Builder = null; + let container: HTMLElement = null; switch (part) { case Parts.TITLEBAR_PART: container = this.titlebarPart.getContainer(); @@ -742,7 +742,8 @@ export class Workbench implements IPartService { container = this.statusbarPart.getContainer(); break; } - return container && container.getHTMLElement(); + + return container; } public isVisible(part: Parts): boolean { @@ -942,10 +943,10 @@ export class Workbench implements IPartService { this.sideBarPosition = position; // Adjust CSS - this.activitybarPart.getContainer().removeClass(oldPositionValue); - this.sidebarPart.getContainer().removeClass(oldPositionValue); - this.activitybarPart.getContainer().addClass(newPositionValue); - this.sidebarPart.getContainer().addClass(newPositionValue); + DOM.removeClass(this.activitybarPart.getContainer(), oldPositionValue); + DOM.removeClass(this.sidebarPart.getContainer(), oldPositionValue); + DOM.addClass(this.activitybarPart.getContainer(), newPositionValue); + DOM.addClass(this.sidebarPart.getContainer(), newPositionValue); // Update Styles this.activitybarPart.updateStyles(); @@ -967,8 +968,8 @@ export class Workbench implements IPartService { this.storageService.store(Workbench.panelPositionStorageKey, Position[this.panelPosition].toLowerCase(), StorageScope.WORKSPACE); // Adjust CSS - this.panelPart.getContainer().removeClass(oldPositionValue); - this.panelPart.getContainer().addClass(newPositionValue); + DOM.removeClass(this.panelPart.getContainer(), oldPositionValue); + DOM.addClass(this.panelPart.getContainer(), newPositionValue); // Update Styles this.panelPart.updateStyles(); @@ -1108,10 +1109,10 @@ export class Workbench implements IPartService { const editorContainer = this.editorPart.getContainer(); if (visibleEditors === 0) { this.editorsVisibleContext.reset(); - this.editorBackgroundDelayer.trigger(() => editorContainer.addClass('empty')); + this.editorBackgroundDelayer.trigger(() => DOM.addClass(editorContainer, 'empty')); } else { this.editorsVisibleContext.set(true); - this.editorBackgroundDelayer.trigger(() => editorContainer.removeClass('empty')); + this.editorBackgroundDelayer.trigger(() => DOM.removeClass(editorContainer, 'empty')); } } @@ -1218,7 +1219,7 @@ export class Workbench implements IPartService { role: 'contentinfo' }); - this.titlebarPart.create(titlebarContainer); + this.titlebarPart.create(titlebarContainer.getHTMLElement()); } private createActivityBarPart(): void { @@ -1229,7 +1230,7 @@ export class Workbench implements IPartService { role: 'navigation' }); - this.activitybarPart.create(activitybarPartContainer); + this.activitybarPart.create(activitybarPartContainer.getHTMLElement()); } private createSidebarPart(): void { @@ -1240,7 +1241,7 @@ export class Workbench implements IPartService { role: 'complementary' }); - this.sidebarPart.create(sidebarPartContainer); + this.sidebarPart.create(sidebarPartContainer.getHTMLElement()); } private createPanelPart(): void { @@ -1251,7 +1252,7 @@ export class Workbench implements IPartService { role: 'complementary' }); - this.panelPart.create(panelPartContainer); + this.panelPart.create(panelPartContainer.getHTMLElement()); } private createEditorPart(): void { @@ -1262,7 +1263,7 @@ export class Workbench implements IPartService { role: 'main' }); - this.editorPart.create(editorContainer); + this.editorPart.create(editorContainer.getHTMLElement()); } private createStatusbarPart(): void { @@ -1272,7 +1273,7 @@ export class Workbench implements IPartService { role: 'contentinfo' }); - this.statusbarPart.create(statusbarContainer); + this.statusbarPart.create(statusbarContainer.getHTMLElement()); } private createNotificationsHandlers(): void { diff --git a/src/vs/workbench/parts/debug/browser/debugActionsWidget.ts b/src/vs/workbench/parts/debug/browser/debugActionsWidget.ts index bbf67306fc4..dcdbd5ae3d9 100644 --- a/src/vs/workbench/parts/debug/browser/debugActionsWidget.ts +++ b/src/vs/workbench/parts/debug/browser/debugActionsWidget.ts @@ -7,7 +7,7 @@ import 'vs/css!./media/debugActionsWidget'; import * as errors from 'vs/base/common/errors'; import * as strings from 'vs/base/common/strings'; import * as browser from 'vs/base/browser/browser'; -import * as builder from 'vs/base/browser/builder'; +import { $, Builder } from 'vs/base/browser/builder'; import * as dom from 'vs/base/browser/dom'; import * as arrays from 'vs/base/common/arrays'; import { StandardMouseEvent } from 'vs/base/browser/mouseEvent'; @@ -31,7 +31,6 @@ import { IContextViewService } from 'vs/platform/contextview/browser/contextView import { INotificationService } from 'vs/platform/notification/common/notification'; import { RunOnceScheduler } from 'vs/base/common/async'; -const $ = builder.$; const DEBUG_ACTIONS_WIDGET_POSITION_KEY = 'debug.actionswidgetposition'; export const debugToolBarBackground = registerColor('debugToolBar.background', { @@ -47,8 +46,8 @@ export const debugToolBarBorder = registerColor('debugToolBar.border', { export class DebugActionsWidget extends Themable implements IWorkbenchContribution { - private $el: builder.Builder; - private dragArea: builder.Builder; + private $el: Builder; + private dragArea: Builder; private actionBar: ActionBar; private allActions: AbstractDebugAction[]; private activeActions: AbstractDebugAction[]; diff --git a/src/vs/workbench/parts/debug/browser/debugViewlet.ts b/src/vs/workbench/parts/debug/browser/debugViewlet.ts index b3bcf05b04c..5470e587f06 100644 --- a/src/vs/workbench/parts/debug/browser/debugViewlet.ts +++ b/src/vs/workbench/parts/debug/browser/debugViewlet.ts @@ -5,7 +5,6 @@ import 'vs/css!./media/debugViewlet'; import * as nls from 'vs/nls'; -import { Builder } from 'vs/base/browser/builder'; import { Action, IAction } from 'vs/base/common/actions'; import * as DOM from 'vs/base/browser/dom'; import { TPromise } from 'vs/base/common/winjs.base'; @@ -56,11 +55,10 @@ export class DebugViewlet extends PersistentViewsViewlet { this._register(this.contextService.onDidChangeWorkbenchState(() => this.updateTitleArea())); } - async create(parent: Builder): TPromise { + async create(parent: HTMLElement): TPromise { await super.create(parent); - const el = parent.getHTMLElement(); - DOM.addClass(el, 'debug-viewlet'); + DOM.addClass(parent, 'debug-viewlet'); } public focus(): void { diff --git a/src/vs/workbench/parts/debug/electron-browser/repl.ts b/src/vs/workbench/parts/debug/electron-browser/repl.ts index a3551f72a7a..f11e1016db6 100644 --- a/src/vs/workbench/parts/debug/electron-browser/repl.ts +++ b/src/vs/workbench/parts/debug/electron-browser/repl.ts @@ -10,7 +10,6 @@ import { wireCancellationToken } from 'vs/base/common/async'; import { TPromise } from 'vs/base/common/winjs.base'; import * as errors from 'vs/base/common/errors'; import { IAction } from 'vs/base/common/actions'; -import { Builder } from 'vs/base/browser/builder'; import * as dom from 'vs/base/browser/dom'; import { isMacintosh } from 'vs/base/common/platform'; import { CancellationToken } from 'vs/base/common/cancellation'; @@ -127,9 +126,9 @@ export class Repl extends Panel implements IPrivateReplService { } } - public create(parent: Builder): TPromise { + public create(parent: HTMLElement): TPromise { super.create(parent); - this.container = dom.append(parent.getHTMLElement(), $('.repl')); + this.container = dom.append(parent, $('.repl')); this.treeContainer = dom.append(this.container, $('.repl-tree')); this.createReplInput(this.container); diff --git a/src/vs/workbench/parts/extensions/electron-browser/extensionEditor.ts b/src/vs/workbench/parts/extensions/electron-browser/extensionEditor.ts index 08780638219..5f42b7941f4 100644 --- a/src/vs/workbench/parts/extensions/electron-browser/extensionEditor.ts +++ b/src/vs/workbench/parts/extensions/electron-browser/extensionEditor.ts @@ -17,7 +17,6 @@ import Cache from 'vs/base/common/cache'; import { Action } from 'vs/base/common/actions'; import { isPromiseCanceledError } from 'vs/base/common/errors'; import { IDisposable, dispose, toDisposable } from 'vs/base/common/lifecycle'; -import { Builder } from 'vs/base/browser/builder'; import { domEvent } from 'vs/base/browser/event'; import { append, $, addClass, removeClass, finalHandler, join, toggleClass } from 'vs/base/browser/dom'; import { BaseEditor } from 'vs/workbench/browser/parts/editor/baseEditor'; @@ -206,10 +205,8 @@ export class ExtensionEditor extends BaseEditor { this.findInputFocusContextKey = KEYBINDING_CONTEXT_EXTENSIONEDITOR_FIND_WIDGET_INPUT_FOCUSED.bindTo(this.contextKeyService); } - createEditor(parent: Builder): void { - const container = parent.getHTMLElement(); - - const root = append(container, $('.extension-editor')); + createEditor(parent: HTMLElement): void { + const root = append(parent, $('.extension-editor')); this.header = append(root, $('.header')); this.icon = append(this.header, $('img.icon', { draggable: false })); diff --git a/src/vs/workbench/parts/extensions/electron-browser/extensionsViewlet.ts b/src/vs/workbench/parts/extensions/electron-browser/extensionsViewlet.ts index ca6ef161be3..1a3afdd8357 100644 --- a/src/vs/workbench/parts/extensions/electron-browser/extensionsViewlet.ts +++ b/src/vs/workbench/parts/extensions/electron-browser/extensionsViewlet.ts @@ -12,7 +12,6 @@ import { TPromise } from 'vs/base/common/winjs.base'; import { isPromiseCanceledError, onUnexpectedError, create as createError } from 'vs/base/common/errors'; import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; import { IDisposable, dispose } from 'vs/base/common/lifecycle'; -import { Builder } from 'vs/base/browser/builder'; import { Event as EventOf, mapEvent, chain } from 'vs/base/common/event'; import { IAction } from 'vs/base/common/actions'; import { domEvent } from 'vs/base/browser/event'; @@ -249,9 +248,9 @@ export class ExtensionsViewlet extends PersistentViewsViewlet implements IExtens }; } - async create(parent: Builder): TPromise { - parent.addClass('extensions-viewlet'); - this.root = parent.getHTMLElement(); + async create(parent: HTMLElement): TPromise { + addClass(parent, 'extensions-viewlet'); + this.root = parent; const header = append(this.root, $('.header')); @@ -278,7 +277,7 @@ export class ExtensionsViewlet extends PersistentViewsViewlet implements IExtens this.onSearchChange = mapEvent(onSearchInput, e => e.target.value); - await super.create(new Builder(this.extensionsBox)); + await super.create(this.extensionsBox); const installed = await this.extensionManagementService.getInstalled(LocalExtensionType.User); diff --git a/src/vs/workbench/parts/extensions/electron-browser/runtimeExtensionsEditor.ts b/src/vs/workbench/parts/extensions/electron-browser/runtimeExtensionsEditor.ts index 76dae04de2d..802cc4d1615 100644 --- a/src/vs/workbench/parts/extensions/electron-browser/runtimeExtensionsEditor.ts +++ b/src/vs/workbench/parts/extensions/electron-browser/runtimeExtensionsEditor.ts @@ -14,7 +14,6 @@ import { EditorInput } from 'vs/workbench/common/editor'; import pkg from 'vs/platform/node/package'; import { TPromise } from 'vs/base/common/winjs.base'; import { Action, IAction } from 'vs/base/common/actions'; -import { Builder } from 'vs/base/browser/builder'; import { BaseEditor } from 'vs/workbench/browser/parts/editor/baseEditor'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IInstantiationService, createDecorator } from 'vs/platform/instantiation/common/instantiation'; @@ -212,10 +211,8 @@ export class RuntimeExtensionsEditor extends BaseEditor { return result; } - protected createEditor(parent: Builder): void { - const container = parent.getHTMLElement(); - - addClass(container, 'runtime-extensions-editor'); + protected createEditor(parent: HTMLElement): void { + addClass(parent, 'runtime-extensions-editor'); const TEMPLATE_ID = 'runtimeExtensionElementTemplate'; @@ -362,7 +359,7 @@ export class RuntimeExtensionsEditor extends BaseEditor { } }; - this._list = this._instantiationService.createInstance(WorkbenchList, container, delegate, [renderer], { + this._list = this._instantiationService.createInstance(WorkbenchList, parent, delegate, [renderer], { multipleSelectionSupport: false }) as WorkbenchList; diff --git a/src/vs/workbench/parts/files/electron-browser/explorerViewlet.ts b/src/vs/workbench/parts/files/electron-browser/explorerViewlet.ts index 714e365c394..fe8a56d8938 100644 --- a/src/vs/workbench/parts/files/electron-browser/explorerViewlet.ts +++ b/src/vs/workbench/parts/files/electron-browser/explorerViewlet.ts @@ -10,7 +10,6 @@ import { localize } from 'vs/nls'; import { IActionRunner } from 'vs/base/common/actions'; import { TPromise } from 'vs/base/common/winjs.base'; import * as DOM from 'vs/base/browser/dom'; -import { Builder } from 'vs/base/browser/builder'; import { VIEWLET_ID, ExplorerViewletVisibleContext, IFilesConfiguration, OpenEditorsVisibleContext, OpenEditorsVisibleCondition, IExplorerViewlet } from 'vs/workbench/parts/files/common/files'; import { PersistentViewsViewlet, IViewletViewOptions, ViewsViewletPanel } from 'vs/workbench/browser/parts/views/viewsViewlet'; import { IConfigurationService, IConfigurationChangeEvent } from 'vs/platform/configuration/common/configuration'; @@ -171,11 +170,10 @@ export class ExplorerViewlet extends PersistentViewsViewlet implements IExplorer this._register(this.contextService.onDidChangeWorkspaceName(e => this.updateTitleArea())); } - async create(parent: Builder): TPromise { + async create(parent: HTMLElement): TPromise { await super.create(parent); - const el = parent.getHTMLElement(); - DOM.addClass(el, 'explorer-viewlet'); + DOM.addClass(parent, 'explorer-viewlet'); } private isOpenEditorsVisible(): boolean { diff --git a/src/vs/workbench/parts/files/electron-browser/views/explorerView.ts b/src/vs/workbench/parts/files/electron-browser/views/explorerView.ts index c98732c93e7..9a9c28d97ae 100644 --- a/src/vs/workbench/parts/files/electron-browser/views/explorerView.ts +++ b/src/vs/workbench/parts/files/electron-browser/views/explorerView.ts @@ -6,7 +6,6 @@ import * as nls from 'vs/nls'; import { TPromise } from 'vs/base/common/winjs.base'; -import { Builder, $ } from 'vs/base/browser/builder'; import URI from 'vs/base/common/uri'; import { ThrottledDelayer, Delayer } from 'vs/base/common/async'; import * as errors from 'vs/base/common/errors'; @@ -160,7 +159,7 @@ export class ExplorerView extends TreeViewsViewletPanel implements IExplorerView public renderBody(container: HTMLElement): void { this.treeContainer = DOM.append(container, DOM.$('.explorer-folders-view')); - this.tree = this.createViewer($(this.treeContainer)); + this.tree = this.createViewer(this.treeContainer); if (this.toolbar) { this.toolbar.setActions(this.getActions(), this.getSecondaryActions())(); @@ -394,7 +393,7 @@ export class ExplorerView extends TreeViewsViewletPanel implements IExplorerView return model; } - private createViewer(container: Builder): WorkbenchTree { + private createViewer(container: HTMLElement): WorkbenchTree { const dataSource = this.instantiationService.createInstance(FileDataSource); const renderer = this.instantiationService.createInstance(FileRenderer, this.viewletState); const controller = this.instantiationService.createInstance(FileController); @@ -406,7 +405,7 @@ export class ExplorerView extends TreeViewsViewletPanel implements IExplorerView const dnd = this.instantiationService.createInstance(FileDragAndDrop); const accessibilityProvider = this.instantiationService.createInstance(FileAccessibilityProvider); - this.explorerViewer = this.instantiationService.createInstance(FileIconThemableWorkbenchTree, container.getHTMLElement(), { + this.explorerViewer = this.instantiationService.createInstance(FileIconThemableWorkbenchTree, container, { dataSource, renderer, controller, diff --git a/src/vs/workbench/parts/html/electron-browser/htmlPreviewPart.ts b/src/vs/workbench/parts/html/electron-browser/htmlPreviewPart.ts index 4a9472862ee..a4fa6c015c1 100644 --- a/src/vs/workbench/parts/html/electron-browser/htmlPreviewPart.ts +++ b/src/vs/workbench/parts/html/electron-browser/htmlPreviewPart.ts @@ -8,7 +8,6 @@ import { localize } from 'vs/nls'; import { TPromise } from 'vs/base/common/winjs.base'; import { ITextModel } from 'vs/editor/common/model'; -import { Builder } from 'vs/base/browser/builder'; import { empty as EmptyDisposable, IDisposable, dispose, IReference } from 'vs/base/common/lifecycle'; import { EditorOptions, EditorInput } from 'vs/workbench/common/editor'; import { Position } from 'vs/platform/editor/common/editor'; @@ -78,11 +77,11 @@ export class HtmlPreviewPart extends BaseWebviewEditor { super.dispose(); } - protected createEditor(parent: Builder): void { + protected createEditor(parent: HTMLElement): void { this._content = document.createElement('div'); this._content.style.position = 'absolute'; this._content.classList.add(HtmlPreviewPart.class); - parent.getHTMLElement().appendChild(this._content); + parent.appendChild(this._content); } private get webview(): Webview { diff --git a/src/vs/workbench/parts/markers/electron-browser/markersPanel.ts b/src/vs/workbench/parts/markers/electron-browser/markersPanel.ts index 31136c190fc..e5b909416af 100644 --- a/src/vs/workbench/parts/markers/electron-browser/markersPanel.ts +++ b/src/vs/workbench/parts/markers/electron-browser/markersPanel.ts @@ -10,7 +10,6 @@ import URI from 'vs/base/common/uri'; import { TPromise } from 'vs/base/common/winjs.base'; import { Delayer } from 'vs/base/common/async'; import * as dom from 'vs/base/browser/dom'; -import * as builder from 'vs/base/browser/builder'; import { IAction, Action } from 'vs/base/common/actions'; import { IActionItem } from 'vs/base/browser/ui/actionbar/actionbar'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; @@ -68,15 +67,15 @@ export class MarkersPanel extends Panel { this.autoExpanded = new Set(); } - public create(parent: builder.Builder): TPromise { + public create(parent: HTMLElement): TPromise { super.create(parent); this.rangeHighlightDecorations = this.instantiationService.createInstance(RangeHighlightDecorations); this.toUnbind.push(this.rangeHighlightDecorations); - dom.addClass(parent.getHTMLElement(), 'markers-panel'); + dom.addClass(parent, 'markers-panel'); - let container = dom.append(parent.getHTMLElement(), dom.$('.markers-panel-container')); + let container = dom.append(parent, dom.$('.markers-panel-container')); this.createMessageBox(container); this.createTree(container); diff --git a/src/vs/workbench/parts/output/browser/outputPanel.ts b/src/vs/workbench/parts/output/browser/outputPanel.ts index 36673ad91e0..35afc6d3e48 100644 --- a/src/vs/workbench/parts/output/browser/outputPanel.ts +++ b/src/vs/workbench/parts/output/browser/outputPanel.ts @@ -7,7 +7,6 @@ import 'vs/css!./media/output'; import * as nls from 'vs/nls'; import { TPromise } from 'vs/base/common/winjs.base'; import { Action, IAction } from 'vs/base/common/actions'; -import { Builder } from 'vs/base/browser/builder'; import { IActionItem } from 'vs/base/browser/ui/actionbar/actionbar'; import { IEditorOptions } from 'vs/editor/common/config/editorOptions'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; @@ -121,9 +120,9 @@ export class OutputPanel extends AbstractTextResourceEditor { super.clearInput(); } - protected createEditor(parent: Builder): void { + protected createEditor(parent: HTMLElement): void { // First create the scoped instantation service and only then construct the editor using the scoped service - const scopedContextKeyService = this.contextKeyService.createScoped(parent.getHTMLElement()); + const scopedContextKeyService = this.contextKeyService.createScoped(parent); this.toUnbind.push(scopedContextKeyService); this.scopedInstantiationService = this.instantiationService.createChild(new ServiceCollection([IContextKeyService, scopedContextKeyService])); super.createEditor(parent); diff --git a/src/vs/workbench/parts/preferences/browser/keybindingsEditor.ts b/src/vs/workbench/parts/preferences/browser/keybindingsEditor.ts index ec96d8d0f85..ea9696016b3 100644 --- a/src/vs/workbench/parts/preferences/browser/keybindingsEditor.ts +++ b/src/vs/workbench/parts/preferences/browser/keybindingsEditor.ts @@ -11,7 +11,6 @@ import * as DOM from 'vs/base/browser/dom'; import { OS } from 'vs/base/common/platform'; import { dispose } from 'vs/base/common/lifecycle'; import { Checkbox } from 'vs/base/browser/ui/checkbox/checkbox'; -import { Builder } from 'vs/base/browser/builder'; import { HighlightedLabel } from 'vs/base/browser/ui/highlightedlabel/highlightedLabel'; import { KeybindingLabel } from 'vs/base/browser/ui/keybindingLabel/keybindingLabel'; import { IAction } from 'vs/base/common/actions'; @@ -122,16 +121,14 @@ export class KeybindingsEditor extends BaseEditor implements IKeybindingsEditor this.delayedFilterLogging = new Delayer(1000); } - createEditor(parent: Builder): void { - const parentElement = parent.getHTMLElement(); - - const keybindingsEditorElement = DOM.append(parentElement, $('div', { class: 'keybindings-editor' })); + createEditor(parent: HTMLElement): void { + const keybindingsEditorElement = DOM.append(parent, $('div', { class: 'keybindings-editor' })); this.createOverlayContainer(keybindingsEditorElement); this.createHeader(keybindingsEditorElement); this.createBody(keybindingsEditorElement); - const focusTracker = this._register(DOM.trackFocus(parentElement)); + const focusTracker = this._register(DOM.trackFocus(parent)); this._register(focusTracker.onDidFocus(() => this.keybindingsEditorContextKey.set(true))); this._register(focusTracker.onDidBlur(() => this.keybindingsEditorContextKey.reset())); } diff --git a/src/vs/workbench/parts/preferences/browser/preferencesEditor.ts b/src/vs/workbench/parts/preferences/browser/preferencesEditor.ts index fad5c212b78..70c4761e517 100644 --- a/src/vs/workbench/parts/preferences/browser/preferencesEditor.ts +++ b/src/vs/workbench/parts/preferences/browser/preferencesEditor.ts @@ -11,7 +11,6 @@ import { onUnexpectedError, isPromiseCanceledError, getErrorMessage } from 'vs/b import * as DOM from 'vs/base/browser/dom'; import { Delayer, ThrottledDelayer } from 'vs/base/common/async'; import * as arrays from 'vs/base/common/arrays'; -import { Builder } from 'vs/base/browser/builder'; import { ArrayNavigator } from 'vs/base/common/iterator'; import { Disposable, IDisposable, dispose } from 'vs/base/common/lifecycle'; import { KeyMod, KeyCode } from 'vs/base/common/keyCodes'; @@ -138,11 +137,10 @@ export class PreferencesEditor extends BaseEditor { this.remoteSearchThrottle = new ThrottledDelayer(200); } - public createEditor(parent: Builder): void { - const parentElement = parent.getHTMLElement(); - DOM.addClass(parentElement, 'preferences-editor'); + public createEditor(parent: HTMLElement): void { + DOM.addClass(parent, 'preferences-editor'); - this.headerContainer = DOM.append(parentElement, DOM.$('.preferences-header')); + this.headerContainer = DOM.append(parent, DOM.$('.preferences-header')); this.searchWidget = this._register(this.instantiationService.createInstance(SearchWidget, this.headerContainer, { ariaLabel: nls.localize('SearchSettingsWidget.AriaLabel', "Search settings"), @@ -154,7 +152,7 @@ export class PreferencesEditor extends BaseEditor { this._register(this.searchWidget.onFocus(() => this.lastFocusedWidget = this.searchWidget)); this.lastFocusedWidget = this.searchWidget; - const editorsContainer = DOM.append(parentElement, DOM.$('.preferences-editors-container')); + const editorsContainer = DOM.append(parent, DOM.$('.preferences-editors-container')); this.sideBySidePreferencesWidget = this._register(this.instantiationService.createInstance(SideBySidePreferencesWidget, editorsContainer)); this._register(this.sideBySidePreferencesWidget.onFocus(() => this.lastFocusedWidget = this.sideBySidePreferencesWidget)); this._register(this.sideBySidePreferencesWidget.onDidSettingsTargetChange(target => this.switchSettings(target))); @@ -817,7 +815,7 @@ class SideBySidePreferencesWidget extends Widget { this.defaultPreferencesHeader.textContent = nls.localize('defaultSettings', "Default Settings"); this.defaultPreferencesEditor = this._register(this.instantiationService.createInstance(DefaultPreferencesEditor)); - this.defaultPreferencesEditor.create(new Builder(this.defaultPreferencesEditorContainer)); + this.defaultPreferencesEditor.create(this.defaultPreferencesEditorContainer); this.defaultPreferencesEditor.setVisible(true); (this.defaultPreferencesEditor.getControl()).onDidFocusEditor(() => this.lastFocusedEditor = this.defaultPreferencesEditor); @@ -902,7 +900,7 @@ class SideBySidePreferencesWidget extends Widget { const descriptor = Registry.as(EditorExtensions.Editors).getEditor(editorInput); const editor = descriptor.instantiate(this.instantiationService); this.editablePreferencesEditor = editor; - this.editablePreferencesEditor.create(new Builder(this.editablePreferencesEditorContainer)); + this.editablePreferencesEditor.create(this.editablePreferencesEditorContainer); this.editablePreferencesEditor.setVisible(true); (this.editablePreferencesEditor.getControl()).onDidFocusEditor(() => this.lastFocusedEditor = this.editablePreferencesEditor); this.lastFocusedEditor = this.editablePreferencesEditor; @@ -990,8 +988,8 @@ export class DefaultPreferencesEditor extends BaseTextEditor { super(DefaultPreferencesEditor.ID, telemetryService, instantiationService, storageService, configurationService, themeService, textFileService, editorGroupService); } - public createEditorControl(parent: Builder, configuration: IEditorOptions): editorCommon.IEditor { - const editor = this.instantiationService.createInstance(DefaultPreferencesCodeEditor, parent.getHTMLElement(), configuration); + public createEditorControl(parent: HTMLElement, configuration: IEditorOptions): editorCommon.IEditor { + const editor = this.instantiationService.createInstance(DefaultPreferencesCodeEditor, parent, configuration); // Inform user about editor being readonly if user starts type this.toUnbind.push(editor.onDidType(() => this.showReadonlyHint(editor))); diff --git a/src/vs/workbench/parts/scm/electron-browser/scmViewlet.ts b/src/vs/workbench/parts/scm/electron-browser/scmViewlet.ts index 3a7e31b081d..46d44ddbc61 100644 --- a/src/vs/workbench/parts/scm/electron-browser/scmViewlet.ts +++ b/src/vs/workbench/parts/scm/electron-browser/scmViewlet.ts @@ -13,7 +13,6 @@ import { domEvent, stop } from 'vs/base/browser/event'; import { basename } from 'vs/base/common/paths'; import { onUnexpectedError } from 'vs/base/common/errors'; import { IDisposable, dispose, combinedDisposable, empty as EmptyDisposable, toDisposable } from 'vs/base/common/lifecycle'; -import { Builder } from 'vs/base/browser/builder'; import { PanelViewlet, ViewletPanel } from 'vs/workbench/browser/parts/views/panelViewlet'; import { append, $, addClass, toggleClass, trackFocus, Dimension } from 'vs/base/browser/dom'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; @@ -1073,13 +1072,13 @@ export class SCMViewlet extends PanelViewlet implements IViewModel { this.menus.onDidChangeTitle(this.updateTitleArea, this, this.disposables); } - async create(parent: Builder): TPromise { + async create(parent: HTMLElement): TPromise { await super.create(parent); - this.el = parent.getHTMLElement(); + this.el = parent; addClass(this.el, 'scm-viewlet'); addClass(this.el, 'empty'); - append(parent.getHTMLElement(), $('div.empty-message', null, localize('no open repo', "There are no active source control providers."))); + append(parent, $('div.empty-message', null, localize('no open repo', "There are no active source control providers."))); this.scmService.onDidAddRepository(this.onDidAddRepository, this, this.disposables); this.scmService.onDidRemoveRepository(this.onDidRemoveRepository, this, this.disposables); diff --git a/src/vs/workbench/parts/search/browser/searchActions.ts b/src/vs/workbench/parts/search/browser/searchActions.ts index 675c13c025d..6c6b5d03b53 100644 --- a/src/vs/workbench/parts/search/browser/searchActions.ts +++ b/src/vs/workbench/parts/search/browser/searchActions.ts @@ -31,7 +31,7 @@ import URI from 'vs/base/common/uri'; export function isSearchViewFocused(viewletService: IViewletService, panelService: IPanelService): boolean { let searchView = getSearchView(viewletService, panelService); let activeElement = document.activeElement; - return searchView && activeElement && DOM.isAncestor(activeElement, searchView.getContainer().getHTMLElement()); + return searchView && activeElement && DOM.isAncestor(activeElement, searchView.getContainer()); } export function appendKeyBindingLabel(label: string, keyBinding: number | ResolvedKeybinding, keyBindingService2: IKeybindingService): string { diff --git a/src/vs/workbench/parts/search/browser/searchView.ts b/src/vs/workbench/parts/search/browser/searchView.ts index b216b8bb235..054283ed6c5 100644 --- a/src/vs/workbench/parts/search/browser/searchView.ts +++ b/src/vs/workbench/parts/search/browser/searchView.ts @@ -164,12 +164,12 @@ export class SearchView extends Viewlet implements IViewlet, IPanel { } } - public create(parent: Builder): TPromise { + public create(parent: HTMLElement): TPromise { super.create(parent); this.viewModel = this.searchWorkbenchService.searchModel; let builder: Builder; - parent.div({ + $(parent).div({ 'class': 'search-view' }, (div) => { builder = div; @@ -754,9 +754,9 @@ export class SearchView extends Viewlet implements IViewlet, IPanel { } if (this.size.width >= SearchView.WIDE_VIEW_SIZE) { - this.getContainer().addClass(SearchView.WIDE_CLASS_NAME); + dom.addClass(this.getContainer(), SearchView.WIDE_CLASS_NAME); } else { - this.getContainer().removeClass(SearchView.WIDE_CLASS_NAME); + dom.removeClass(this.getContainer(), SearchView.WIDE_CLASS_NAME); } this.searchWidget.setWidth(this.size.width - 28 /* container margin */); diff --git a/src/vs/workbench/parts/tasks/electron-browser/task.contribution.ts b/src/vs/workbench/parts/tasks/electron-browser/task.contribution.ts index 9dae269eac1..28ee56fb5a6 100644 --- a/src/vs/workbench/parts/tasks/electron-browser/task.contribution.ts +++ b/src/vs/workbench/parts/tasks/electron-browser/task.contribution.ts @@ -18,7 +18,6 @@ import { Action } from 'vs/base/common/actions'; import * as Dom from 'vs/base/browser/dom'; import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import { Event, Emitter } from 'vs/base/common/event'; -import * as Builder from 'vs/base/browser/builder'; import * as Types from 'vs/base/common/types'; import { KeyMod, KeyCode } from 'vs/base/common/keyCodes'; import { TerminateResponseCode } from 'vs/base/common/processes'; @@ -88,7 +87,6 @@ import { QuickOpenActionContributor } from '../browser/quickOpen'; import { Themable, STATUS_BAR_FOREGROUND, STATUS_BAR_NO_FOLDER_FOREGROUND } from 'vs/workbench/common/theme'; import { IThemeService } from 'vs/platform/theme/common/themeService'; -let $ = Builder.$; let tasksCategory = nls.localize('tasksCategory', "Tasks"); namespace ConfigureTaskAction { @@ -175,17 +173,16 @@ class BuildStatusBarItem extends Themable implements IStatusbarItem { Dom.addClass(infoIcon, 'mask-icon'); label.appendChild(infoIcon); this.icons.push(infoIcon); - $(infoIcon).hide(); + Dom.hide(infoIcon); Dom.addClass(info, 'task-statusbar-item-label-counter'); label.appendChild(info); - $(info).hide(); + Dom.hide(info); Dom.addClass(building, 'task-statusbar-item-building'); element.appendChild(building); building.innerHTML = nls.localize('building', 'Building...'); - $(building).hide(); - + Dom.hide(building); callOnDispose.push(Dom.addDisposableListener(label, 'click', (e: MouseEvent) => { const panel = this.panelService.getActivePanel(); @@ -206,11 +203,11 @@ class BuildStatusBarItem extends Themable implements IStatusbarItem { if (stats.infos > 0) { info.innerHTML = packNumber(stats.infos); info.title = infoIcon.title = infoTitle(stats.infos); - $(info).show(); - $(infoIcon).show(); + Dom.show(info); + Dom.show(infoIcon); } else { - $(info).hide(); - $(infoIcon).hide(); + Dom.hide(info); + Dom.hide(infoIcon); } }; @@ -226,7 +223,7 @@ class BuildStatusBarItem extends Themable implements IStatusbarItem { case TaskEventKind.Active: this.activeCount++; if (this.activeCount === 1) { - $(building).show(); + Dom.show(building); } break; case TaskEventKind.Inactive: @@ -235,13 +232,13 @@ class BuildStatusBarItem extends Themable implements IStatusbarItem { if (this.activeCount > 0) { this.activeCount--; if (this.activeCount === 0) { - $(building).hide(); + Dom.hide(building); } } break; case TaskEventKind.Terminated: if (this.activeCount !== 0) { - $(building).hide(); + Dom.hide(building); this.activeCount = 0; } break; @@ -299,7 +296,7 @@ class TaskStatusBarItem extends Themable implements IStatusbarItem { let label = new OcticonLabel(labelElement); label.title = nls.localize('runningTasks', "Show Running Tasks"); - $(element).hide(); + Dom.hide(element); callOnDispose.push(Dom.addDisposableListener(labelElement, 'click', (e: MouseEvent) => { (this.taskService as TaskService).runShowTasks(); @@ -308,10 +305,10 @@ class TaskStatusBarItem extends Themable implements IStatusbarItem { let updateStatus = (): void => { this.taskService.getActiveTasks().then(tasks => { if (tasks.length === 0) { - $(element).hide(); + Dom.hide(element); } else { label.text = `$(tools) ${tasks.length}`; - $(element).show(); + Dom.show(element); } }); }; diff --git a/src/vs/workbench/parts/terminal/electron-browser/terminalPanel.ts b/src/vs/workbench/parts/terminal/electron-browser/terminalPanel.ts index c41c318e81b..336d07d9a95 100644 --- a/src/vs/workbench/parts/terminal/electron-browser/terminalPanel.ts +++ b/src/vs/workbench/parts/terminal/electron-browser/terminalPanel.ts @@ -9,7 +9,6 @@ import * as dom from 'vs/base/browser/dom'; import * as nls from 'vs/nls'; import * as platform from 'vs/base/common/platform'; import { Action, IAction } from 'vs/base/common/actions'; -import { Builder } from 'vs/base/browser/builder'; import { IActionItem, Separator } from 'vs/base/browser/ui/actionbar/actionbar'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; @@ -53,9 +52,9 @@ export class TerminalPanel extends Panel { super(TERMINAL_PANEL_ID, telemetryService, themeService); } - public create(parent: Builder): TPromise { + public create(parent: HTMLElement): TPromise { super.create(parent); - this._parentDomElement = parent.getHTMLElement(); + this._parentDomElement = parent; dom.addClass(this._parentDomElement, 'integrated-terminal'); this._themeStyleElement = document.createElement('style'); this._fontStyleElement = document.createElement('style'); @@ -72,7 +71,7 @@ export class TerminalPanel extends Panel { this._attachEventListeners(); - this._terminalService.setContainers(this.getContainer().getHTMLElement(), this._terminalContainer); + this._terminalService.setContainers(this.getContainer(), this._terminalContainer); this._register(this.themeService.onThemeChange(theme => this._updateTheme(theme))); this._register(this._configurationService.onDidChangeConfiguration(e => { diff --git a/src/vs/workbench/parts/webview/electron-browser/webviewEditor.ts b/src/vs/workbench/parts/webview/electron-browser/webviewEditor.ts index d4e8afff2b5..bfc40a60fff 100644 --- a/src/vs/workbench/parts/webview/electron-browser/webviewEditor.ts +++ b/src/vs/workbench/parts/webview/electron-browser/webviewEditor.ts @@ -8,7 +8,6 @@ import { IDisposable, } from 'vs/base/common/lifecycle'; import { EditorOptions } from 'vs/workbench/common/editor'; import { Position } from 'vs/platform/editor/common/editor'; import { BaseWebviewEditor as BaseWebviewEditor, KEYBINDING_CONTEXT_WEBVIEWEDITOR_FOCUS, KEYBINDING_CONTEXT_WEBVIEWEDITOR_FIND_WIDGET_INPUT_FOCUSED, KEYBINDING_CONTEXT_WEBVIEW_FIND_WIDGET_VISIBLE } from 'vs/workbench/parts/html/electron-browser/baseWebviewEditor'; -import { Builder } from 'vs/base/browser/builder'; import { Webview } from 'vs/workbench/parts/html/electron-browser/webview'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; @@ -47,10 +46,10 @@ export class WebviewEditor extends BaseWebviewEditor { super(WebviewEditor.ID, telemetryService, themeService, _contextKeyService); } - protected createEditor(parent: Builder): void { - this.editorFrame = parent.getHTMLElement(); + protected createEditor(parent: HTMLElement): void { + this.editorFrame = parent; this.content = document.createElement('div'); - parent.append(this.content); + parent.appendChild(this.content); } private doUpdateContainer() { diff --git a/src/vs/workbench/parts/welcome/walkThrough/electron-browser/walkThroughPart.ts b/src/vs/workbench/parts/welcome/walkThrough/electron-browser/walkThroughPart.ts index 1d32c0c1670..5e1223341a8 100644 --- a/src/vs/workbench/parts/welcome/walkThrough/electron-browser/walkThroughPart.ts +++ b/src/vs/workbench/parts/welcome/walkThrough/electron-browser/walkThroughPart.ts @@ -11,7 +11,6 @@ import { ScrollbarVisibility } from 'vs/base/common/scrollable'; import * as strings from 'vs/base/common/strings'; import URI from 'vs/base/common/uri'; import { TPromise } from 'vs/base/common/winjs.base'; -import { $, Builder } from 'vs/base/browser/builder'; import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import { EditorOptions } from 'vs/workbench/common/editor'; import { BaseEditor } from 'vs/workbench/browser/parts/editor/baseEditor'; @@ -40,7 +39,7 @@ import { UILabelProvider } from 'vs/base/common/keybindingLabels'; import { OS, OperatingSystem } from 'vs/base/common/platform'; import { deepClone } from 'vs/base/common/objects'; import { INotificationService } from 'vs/platform/notification/common/notification'; -import { Dimension } from 'vs/base/browser/dom'; +import { Dimension, size } from 'vs/base/browser/dom'; export const WALK_THROUGH_FOCUS = new RawContextKey('interactivePlaygroundFocus', false); @@ -109,9 +108,7 @@ export class WalkThroughPart extends BaseEditor { this.editorFocus = WALK_THROUGH_FOCUS.bindTo(this.contextKeyService); } - createEditor(parent: Builder): void { - const container = parent.getHTMLElement(); - + createEditor(container: HTMLElement): void { this.content = document.createElement('div'); this.content.tabIndex = 0; this.content.style.outlineStyle = 'none'; @@ -215,9 +212,9 @@ export class WalkThroughPart extends BaseEditor { return uri.with({ query: JSON.stringify(query) }); } - layout(size: Dimension): void { - this.size = size; - $(this.content).style({ height: `${size.height}px`, width: `${size.width}px` }); + layout(dimension: Dimension): void { + this.size = dimension; + size(this.content, dimension.width, dimension.height); this.updateSizeClasses(); this.contentDisposables.forEach(disposable => { if (disposable instanceof CodeEditor) { diff --git a/src/vs/workbench/test/browser/part.test.ts b/src/vs/workbench/test/browser/part.test.ts index 27dea733652..4ba14edc657 100644 --- a/src/vs/workbench/test/browser/part.test.ts +++ b/src/vs/workbench/test/browser/part.test.ts @@ -6,7 +6,7 @@ 'use strict'; import * as assert from 'assert'; -import { Builder } from 'vs/base/browser/builder'; +import { Builder, $ } from 'vs/base/browser/builder'; import { Part } from 'vs/workbench/browser/part'; import * as Types from 'vs/base/common/types'; import { IStorageService } from 'vs/platform/storage/common/storage'; @@ -16,16 +16,16 @@ import { TestWorkspace } from 'vs/platform/workspace/test/common/testWorkspace'; class MyPart extends Part { - constructor(private expectedParent: Builder) { + constructor(private expectedParent: HTMLElement) { super('myPart', { hasTitle: true }, new TestThemeService()); } - public createTitleArea(parent: Builder): Builder { + public createTitleArea(parent: HTMLElement): HTMLElement { assert.strictEqual(parent, this.expectedParent); return super.createTitleArea(parent); } - public createContentArea(parent: Builder): Builder { + public createContentArea(parent: HTMLElement): HTMLElement { assert.strictEqual(parent, this.expectedParent); return super.createContentArea(parent); } @@ -41,22 +41,22 @@ class MyPart2 extends Part { super('myPart2', { hasTitle: true }, new TestThemeService()); } - public createTitleArea(parent: Builder): Builder { - return parent.div(function (div) { + public createTitleArea(parent: HTMLElement): HTMLElement { + return $(parent).div(function (div) { div.span({ id: 'myPart.title', innerHtml: 'Title' }); - }); + }).getHTMLElement(); } - public createContentArea(parent: Builder): Builder { - return parent.div(function (div) { + public createContentArea(parent: HTMLElement): HTMLElement { + return $(parent).div(function (div) { div.span({ id: 'myPart.content', innerHtml: 'Content' }); - }); + }).getHTMLElement(); } } @@ -66,17 +66,17 @@ class MyPart3 extends Part { super('myPart2', { hasTitle: false }, new TestThemeService()); } - public createTitleArea(parent: Builder): Builder { + public createTitleArea(parent: HTMLElement): HTMLElement { return null; } - public createContentArea(parent: Builder): Builder { - return parent.div(function (div) { + public createContentArea(parent: HTMLElement): HTMLElement { + return $(parent).div(function (div) { div.span({ id: 'myPart.content', innerHtml: 'Content' }); - }); + }).getHTMLElement(); } } @@ -100,11 +100,10 @@ suite('Workbench Part', () => { let b = new Builder(document.getElementById(fixtureId)); b.div().hide(); - let part = new MyPart(b); - part.create(b); + let part = new MyPart(b.getHTMLElement()); + part.create(b.getHTMLElement()); assert.strictEqual(part.getId(), 'myPart'); - assert.strictEqual(part.getContainer(), b); // Memento let memento = part.getMemento(storage); @@ -115,7 +114,7 @@ suite('Workbench Part', () => { part.shutdown(); // Re-Create to assert memento contents - part = new MyPart(b); + part = new MyPart(b.getHTMLElement()); memento = part.getMemento(storage); assert(memento); @@ -127,7 +126,7 @@ suite('Workbench Part', () => { delete memento.bar; part.shutdown(); - part = new MyPart(b); + part = new MyPart(b.getHTMLElement()); memento = part.getMemento(storage); assert(memento); assert.strictEqual(Types.isEmptyObject(memento), true); @@ -138,7 +137,7 @@ suite('Workbench Part', () => { b.div().hide(); let part = new MyPart2(); - part.create(b); + part.create(b.getHTMLElement()); assert(document.getElementById('myPart.title')); assert(document.getElementById('myPart.content')); @@ -149,7 +148,7 @@ suite('Workbench Part', () => { b.div().hide(); let part = new MyPart3(); - part.create(b); + part.create(b.getHTMLElement()); assert(!document.getElementById('myPart.title')); assert(document.getElementById('myPart.content')); From 6caf60fa704d8df191ffc0bedf5fe8f4eb9bcd1e Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Wed, 4 Apr 2018 10:15:18 +0200 Subject: [PATCH 22/49] fix #43429 --- .../editor/contrib/suggest/suggestMemory.ts | 24 ++++++++++++------- .../suggest/test/suggestMemory.test.ts | 18 ++++++++++++-- 2 files changed, 31 insertions(+), 11 deletions(-) diff --git a/src/vs/editor/contrib/suggest/suggestMemory.ts b/src/vs/editor/contrib/suggest/suggestMemory.ts index 5256dd4bce4..29c6afc906d 100644 --- a/src/vs/editor/contrib/suggest/suggestMemory.ts +++ b/src/vs/editor/contrib/suggest/suggestMemory.ts @@ -66,18 +66,24 @@ export class LRUMemory extends Memory { // in order of completions, select the first // that has been used in the past let { word } = model.getWordUntilPosition(pos); + if (word.length !== 0) { + return 0; + } + + let lineSuffix = model.getLineContent(pos.lineNumber).substr(pos.column - 10, pos.column - 1); + if (/\s$/.test(lineSuffix)) { + return 0; + } let res = 0; let seq = -1; - if (word.length === 0) { - for (let i = 0; i < items.length; i++) { - const { suggestion } = items[i]; - const key = `${model.getLanguageIdentifier().language}/${suggestion.label}`; - const item = this._cache.get(key); - if (item && item.touch > seq && item.type === suggestion.type && item.insertText === suggestion.insertText) { - seq = item.touch; - res = i; - } + for (let i = 0; i < items.length; i++) { + const { suggestion } = items[i]; + const key = `${model.getLanguageIdentifier().language}/${suggestion.label}`; + const item = this._cache.get(key); + if (item && item.touch > seq && item.type === suggestion.type && item.insertText === suggestion.insertText) { + seq = item.touch; + res = i; } } return res; diff --git a/src/vs/editor/contrib/suggest/test/suggestMemory.test.ts b/src/vs/editor/contrib/suggest/test/suggestMemory.test.ts index 9d69016057f..4025fcd221e 100644 --- a/src/vs/editor/contrib/suggest/test/suggestMemory.test.ts +++ b/src/vs/editor/contrib/suggest/test/suggestMemory.test.ts @@ -21,7 +21,7 @@ suite('SuggestMemories', function () { setup(function () { pos = { lineNumber: 1, column: 1 }; - buffer = TextModel.createFromString('This is some text'); + buffer = TextModel.createFromString('This is some text.\nthis.\nfoo: ,'); items = [ createSuggestItem('foo', 0), createSuggestItem('bar', 0) @@ -39,7 +39,9 @@ suite('SuggestMemories', function () { mem.memorize(buffer, pos, null); }); - test('ShyMemories', function () { + test('LRUMemory', function () { + + pos = { lineNumber: 2, column: 6 }; const mem = new LRUMemory(); mem.memorize(buffer, pos, items[1]); @@ -59,7 +61,19 @@ suite('SuggestMemories', function () { createSuggestItem('new1', 0), createSuggestItem('new2', 0) ]), 0); + }); + test('intellisense is not showing top options first #43429', function () { + // ensure we don't memorize for whitespace prefixes + + pos = { lineNumber: 2, column: 6 }; + const mem = new LRUMemory(); + + mem.memorize(buffer, pos, items[1]); + assert.equal(mem.select(buffer, pos, items), 1); + + assert.equal(mem.select(buffer, { lineNumber: 3, column: 5 }, items), 0); // foo: |, + assert.equal(mem.select(buffer, { lineNumber: 3, column: 6 }, items), 1); // foo: ,| }); test('PrefixMemory', function () { From 5c0cee98bfbd5f174e63bb1950bea7a6cb0bac88 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Wed, 4 Apr 2018 10:53:50 +0200 Subject: [PATCH 23/49] Fix #46951 --- .../common/extensionEnablementService.ts | 8 +++++++- .../common/extensionEnablementService.test.ts | 19 +++++++++++++++++-- 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/src/vs/platform/extensionManagement/common/extensionEnablementService.ts b/src/vs/platform/extensionManagement/common/extensionEnablementService.ts index 945ae66a841..28c6ce6805f 100644 --- a/src/vs/platform/extensionManagement/common/extensionEnablementService.ts +++ b/src/vs/platform/extensionManagement/common/extensionEnablementService.ts @@ -78,7 +78,13 @@ export class ExtensionEnablementService implements IExtensionEnablementService { } canChangeEnablement(extension: ILocalExtension): boolean { - return !this.environmentService.disableExtensions && !(extension.manifest && extension.manifest.contributes && extension.manifest.contributes.localizations && extension.manifest.contributes.localizations.length); + if (extension.manifest && extension.manifest.contributes && extension.manifest.contributes.localizations && extension.manifest.contributes.localizations.length) { + return false; + } + if (extension.type === LocalExtensionType.User && this.environmentService.disableExtensions) { + return false; + } + return true; } setEnablement(arg: ILocalExtension | IExtensionIdentifier, newState: EnablementState): TPromise { diff --git a/src/vs/platform/extensionManagement/test/common/extensionEnablementService.test.ts b/src/vs/platform/extensionManagement/test/common/extensionEnablementService.test.ts index f4feeba2973..717a17dd3b0 100644 --- a/src/vs/platform/extensionManagement/test/common/extensionEnablementService.test.ts +++ b/src/vs/platform/extensionManagement/test/common/extensionEnablementService.test.ts @@ -6,7 +6,7 @@ import * as assert from 'assert'; import * as sinon from 'sinon'; -import { IExtensionManagementService, IExtensionEnablementService, DidUninstallExtensionEvent, EnablementState, IExtensionContributions, ILocalExtension } from 'vs/platform/extensionManagement/common/extensionManagement'; +import { IExtensionManagementService, IExtensionEnablementService, DidUninstallExtensionEvent, EnablementState, IExtensionContributions, ILocalExtension, LocalExtensionType } from 'vs/platform/extensionManagement/common/extensionManagement'; import { ExtensionEnablementService } from 'vs/platform/extensionManagement/common/extensionEnablementService'; import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; import { Emitter } from 'vs/base/common/event'; @@ -324,6 +324,20 @@ suite('ExtensionEnablementService Test', () => { test('test canChangeEnablement return false for language packs', () => { assert.equal(testObject.canChangeEnablement(aLocalExtension('pub.a', { localizations: [{ languageId: 'gr', translations: [{ id: 'vscode', path: 'path' }] }] })), false); }); + + test('test canChangeEnablement return false when extensions are disabled in environment', () => { + instantiationService.stub(IEnvironmentService, { disableExtensions: true } as IEnvironmentService); + testObject = new TestExtensionEnablementService(instantiationService); + assert.equal(testObject.canChangeEnablement(aLocalExtension('pub.a')), false); + }); + + test('test canChangeEnablement return true for system extensions when extensions are disabled in environment', () => { + instantiationService.stub(IEnvironmentService, { disableExtensions: true } as IEnvironmentService); + testObject = new TestExtensionEnablementService(instantiationService); + const extension = aLocalExtension('pub.a'); + extension.type = LocalExtensionType.System; + assert.equal(testObject.canChangeEnablement(extension), true); + }); }); function aLocalExtension(id: string, contributes?: IExtensionContributions): ILocalExtension { @@ -334,6 +348,7 @@ function aLocalExtension(id: string, contributes?: IExtensionContributions): ILo name, publisher, contributes - } + }, + type: LocalExtensionType.User }); } From e2d003c41f702616faa494245ca203e0fc95d231 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Wed, 4 Apr 2018 11:09:37 +0200 Subject: [PATCH 24/49] Increase retry time to 30s --- .../extensionManagement/node/extensionManagementService.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/platform/extensionManagement/node/extensionManagementService.ts b/src/vs/platform/extensionManagement/node/extensionManagementService.ts index 21960eff79c..7418a9e3985 100644 --- a/src/vs/platform/extensionManagement/node/extensionManagementService.ts +++ b/src/vs/platform/extensionManagement/node/extensionManagementService.ts @@ -419,7 +419,7 @@ export class ExtensionManagementService extends Disposable implements IExtension private extractAndRename(id: string, zipPath: string, extractPath: string, renamePath: string): TPromise { return this.extract(id, zipPath, extractPath) - .then(() => this.rename(id, extractPath, renamePath, Date.now() + (20 * 1000) /* Retry for 20 seconds */) + .then(() => this.rename(id, extractPath, renamePath, Date.now() + (30 * 1000) /* Retry for 30 seconds */) .then( () => this.logService.info('Renamed to', renamePath), e => { From 6289e7095ed65187a3de2d0f232649d9b0d81e42 Mon Sep 17 00:00:00 2001 From: Alex Dima Date: Wed, 4 Apr 2018 10:23:14 +0200 Subject: [PATCH 25/49] Make readFromTextArea a static method --- src/vs/editor/browser/controller/textAreaInput.ts | 4 ++-- src/vs/editor/browser/controller/textAreaState.ts | 2 +- src/vs/editor/test/browser/controller/textAreaState.test.ts | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/vs/editor/browser/controller/textAreaInput.ts b/src/vs/editor/browser/controller/textAreaInput.ts index a96c80dbbf2..fd40e350b99 100644 --- a/src/vs/editor/browser/controller/textAreaInput.ts +++ b/src/vs/editor/browser/controller/textAreaInput.ts @@ -147,7 +147,7 @@ export class TextAreaInput extends Disposable { */ const deduceInputFromTextAreaValue = (couldBeEmojiInput: boolean): [TextAreaState, ITypeData] => { const oldState = this._textAreaState; - const newState = this._textAreaState.readFromTextArea(this._textArea); + const newState = TextAreaState.readFromTextArea(this._textArea); return [newState, TextAreaState.deduceInput(oldState, newState, couldBeEmojiInput)]; }; @@ -223,7 +223,7 @@ export class TextAreaInput extends Disposable { // Due to isEdgeOrIE (where the textarea was not cleared initially) and isChrome (the textarea is not updated correctly when composition ends) // we cannot assume the text at the end consists only of the composited text if (browser.isEdgeOrIE || browser.isChrome) { - this._textAreaState = this._textAreaState.readFromTextArea(this._textArea); + this._textAreaState = TextAreaState.readFromTextArea(this._textArea); } if (!this._isDoingComposition) { diff --git a/src/vs/editor/browser/controller/textAreaState.ts b/src/vs/editor/browser/controller/textAreaState.ts index c46ad756789..a9b1b7f6f30 100644 --- a/src/vs/editor/browser/controller/textAreaState.ts +++ b/src/vs/editor/browser/controller/textAreaState.ts @@ -51,7 +51,7 @@ export class TextAreaState { return '[ <' + this.value + '>, selectionStart: ' + this.selectionStart + ', selectionEnd: ' + this.selectionEnd + ']'; } - public readFromTextArea(textArea: ITextAreaWrapper): TextAreaState { + public static readFromTextArea(textArea: ITextAreaWrapper): TextAreaState { return new TextAreaState(textArea.getValue(), textArea.getSelectionStart(), textArea.getSelectionEnd(), null, null); } diff --git a/src/vs/editor/test/browser/controller/textAreaState.test.ts b/src/vs/editor/test/browser/controller/textAreaState.test.ts index fce77b0211d..087af746c69 100644 --- a/src/vs/editor/test/browser/controller/textAreaState.test.ts +++ b/src/vs/editor/test/browser/controller/textAreaState.test.ts @@ -82,7 +82,7 @@ suite('TextAreaState', () => { textArea._value = 'Hello world!'; textArea._selectionStart = 1; textArea._selectionEnd = 12; - let actual = TextAreaState.EMPTY.readFromTextArea(textArea); + let actual = TextAreaState.readFromTextArea(textArea); assertTextAreaState(actual, 'Hello world!', 1, 12); assert.equal(actual.value, 'Hello world!'); @@ -132,7 +132,7 @@ suite('TextAreaState', () => { textArea._selectionStart = selectionStart; textArea._selectionEnd = selectionEnd; - let newState = prevState.readFromTextArea(textArea); + let newState = TextAreaState.readFromTextArea(textArea); let actual = TextAreaState.deduceInput(prevState, newState, true); assert.equal(actual.text, expected); From 7335b0cf8857599e8a41b87b5a5799edd940b303 Mon Sep 17 00:00:00 2001 From: Alex Dima Date: Wed, 4 Apr 2018 11:23:03 +0200 Subject: [PATCH 26/49] Handle the case where typing occurs at offset 0 after a focus gain (fixes #42251) --- .../browser/controller/textAreaInput.ts | 51 +++++++++-- .../browser/controller/textAreaState.ts | 16 +++- .../browser/controller/textAreaState.test.ts | 84 +++++++++++-------- 3 files changed, 107 insertions(+), 44 deletions(-) diff --git a/src/vs/editor/browser/controller/textAreaInput.ts b/src/vs/editor/browser/controller/textAreaInput.ts index fd40e350b99..d3dd81fac4e 100644 --- a/src/vs/editor/browser/controller/textAreaInput.ts +++ b/src/vs/editor/browser/controller/textAreaInput.ts @@ -42,6 +42,19 @@ export interface ITextAreaInputHost { deduceModelPosition(viewAnchorPosition: Position, deltaOffset: number, lineFeedCnt: number): Position; } +const enum TextAreaInputEventType { + none, + compositionstart, + compositionupdate, + compositionend, + input, + cut, + copy, + paste, + focus, + blur +} + /** * Writes screen reader content to the textarea and is able to analyze its input events to generate: * - onCut @@ -89,6 +102,7 @@ export class TextAreaInput extends Disposable { private readonly _host: ITextAreaInputHost; private readonly _textArea: TextAreaWrapper; + private _lastTextAreaEvent: TextAreaInputEventType; private readonly _asyncTriggerCut: RunOnceScheduler; private _textAreaState: TextAreaState; @@ -101,6 +115,7 @@ export class TextAreaInput extends Disposable { super(); this._host = host; this._textArea = this._register(new TextAreaWrapper(textArea)); + this._lastTextAreaEvent = TextAreaInputEventType.none; this._asyncTriggerCut = this._register(new RunOnceScheduler(() => this._onCut.fire(), 0)); this._textAreaState = TextAreaState.EMPTY; @@ -129,6 +144,8 @@ export class TextAreaInput extends Disposable { })); this._register(dom.addDisposableListener(textArea.domNode, 'compositionstart', (e: CompositionEvent) => { + this._lastTextAreaEvent = TextAreaInputEventType.compositionstart; + if (this._isDoingComposition) { return; } @@ -145,10 +162,10 @@ export class TextAreaInput extends Disposable { /** * Deduce the typed input from a text area's value and the last observed state. */ - const deduceInputFromTextAreaValue = (couldBeEmojiInput: boolean): [TextAreaState, ITypeData] => { + const deduceInputFromTextAreaValue = (couldBeEmojiInput: boolean, couldBeTypingAtOffset0: boolean): [TextAreaState, ITypeData] => { const oldState = this._textAreaState; const newState = TextAreaState.readFromTextArea(this._textArea); - return [newState, TextAreaState.deduceInput(oldState, newState, couldBeEmojiInput)]; + return [newState, TextAreaState.deduceInput(oldState, newState, couldBeEmojiInput, couldBeTypingAtOffset0)]; }; /** @@ -185,6 +202,8 @@ export class TextAreaInput extends Disposable { }; this._register(dom.addDisposableListener(textArea.domNode, 'compositionupdate', (e: CompositionEvent) => { + this._lastTextAreaEvent = TextAreaInputEventType.compositionupdate; + if (browser.isChromev56) { // See https://github.com/Microsoft/monaco-editor/issues/320 // where compositionupdate .data is broken in Chrome v55 and v56 @@ -195,7 +214,7 @@ export class TextAreaInput extends Disposable { } if (compositionDataInValid(e.locale)) { - const [newState, typeInput] = deduceInputFromTextAreaValue(/*couldBeEmojiInput*/false); + const [newState, typeInput] = deduceInputFromTextAreaValue(/*couldBeEmojiInput*/false, /*couldBeTypingAtOffset0*/false); this._textAreaState = newState; this._onType.fire(typeInput); this._onCompositionUpdate.fire(e); @@ -209,9 +228,11 @@ export class TextAreaInput extends Disposable { })); this._register(dom.addDisposableListener(textArea.domNode, 'compositionend', (e: CompositionEvent) => { + this._lastTextAreaEvent = TextAreaInputEventType.compositionend; + if (compositionDataInValid(e.locale)) { // https://github.com/Microsoft/monaco-editor/issues/339 - const [newState, typeInput] = deduceInputFromTextAreaValue(/*couldBeEmojiInput*/false); + const [newState, typeInput] = deduceInputFromTextAreaValue(/*couldBeEmojiInput*/false, /*couldBeTypingAtOffset0*/false); this._textAreaState = newState; this._onType.fire(typeInput); } else { @@ -235,6 +256,10 @@ export class TextAreaInput extends Disposable { })); this._register(dom.addDisposableListener(textArea.domNode, 'input', () => { + // We want to find out if this is the first `input` after a `focus`. + const previousEventWasFocus = (this._lastTextAreaEvent === TextAreaInputEventType.focus); + this._lastTextAreaEvent = TextAreaInputEventType.input; + // Pretend here we touched the text area, as the `input` event will most likely // result in a `selectionchange` event which we want to ignore this._textArea.setIgnoreSelectionChangeTime('received input event'); @@ -254,7 +279,7 @@ export class TextAreaInput extends Disposable { return; } - const [newState, typeInput] = deduceInputFromTextAreaValue(/*couldBeEmojiInput*/platform.isMacintosh); + const [newState, typeInput] = deduceInputFromTextAreaValue(/*couldBeEmojiInput*/platform.isMacintosh, /*couldBeTypingAtOffset0*/previousEventWasFocus && platform.isMacintosh); if (typeInput.replaceCharCnt === 0 && typeInput.text.length === 1 && strings.isHighSurrogate(typeInput.text.charCodeAt(0))) { // Ignore invalid input but keep it around for next time return; @@ -279,6 +304,8 @@ export class TextAreaInput extends Disposable { // --- Clipboard operations this._register(dom.addDisposableListener(textArea.domNode, 'cut', (e: ClipboardEvent) => { + this._lastTextAreaEvent = TextAreaInputEventType.cut; + // Pretend here we touched the text area, as the `cut` event will most likely // result in a `selectionchange` event which we want to ignore this._textArea.setIgnoreSelectionChangeTime('received cut event'); @@ -288,10 +315,14 @@ export class TextAreaInput extends Disposable { })); this._register(dom.addDisposableListener(textArea.domNode, 'copy', (e: ClipboardEvent) => { + this._lastTextAreaEvent = TextAreaInputEventType.copy; + this._ensureClipboardGetsEditorSelection(e); })); this._register(dom.addDisposableListener(textArea.domNode, 'paste', (e: ClipboardEvent) => { + this._lastTextAreaEvent = TextAreaInputEventType.paste; + // Pretend here we touched the text area, as the `paste` event will most likely // result in a `selectionchange` event which we want to ignore this._textArea.setIgnoreSelectionChangeTime('received paste event'); @@ -312,8 +343,14 @@ export class TextAreaInput extends Disposable { } })); - this._register(dom.addDisposableListener(textArea.domNode, 'focus', () => this._setHasFocus(true))); - this._register(dom.addDisposableListener(textArea.domNode, 'blur', () => this._setHasFocus(false))); + this._register(dom.addDisposableListener(textArea.domNode, 'focus', () => { + this._lastTextAreaEvent = TextAreaInputEventType.focus; + this._setHasFocus(true); + })); + this._register(dom.addDisposableListener(textArea.domNode, 'blur', () => { + this._lastTextAreaEvent = TextAreaInputEventType.blur; + this._setHasFocus(false); + })); // See https://github.com/Microsoft/vscode/issues/27216 diff --git a/src/vs/editor/browser/controller/textAreaState.ts b/src/vs/editor/browser/controller/textAreaState.ts index a9b1b7f6f30..09f54a8a013 100644 --- a/src/vs/editor/browser/controller/textAreaState.ts +++ b/src/vs/editor/browser/controller/textAreaState.ts @@ -60,7 +60,7 @@ export class TextAreaState { } public writeToTextArea(reason: string, textArea: ITextAreaWrapper, select: boolean): void { - // console.log(Date.now() + ': applyToTextArea ' + reason + ': ' + this.toString()); + // console.log(Date.now() + ': writeToTextArea ' + reason + ': ' + this.toString()); textArea.setValue(reason, this.value); if (select) { textArea.setSelectionRange(reason, this.selectionStart, this.selectionEnd); @@ -97,7 +97,7 @@ export class TextAreaState { return new TextAreaState(text, 0, text.length, null, null); } - public static deduceInput(previousState: TextAreaState, currentState: TextAreaState, couldBeEmojiInput: boolean): ITypeData { + public static deduceInput(previousState: TextAreaState, currentState: TextAreaState, couldBeEmojiInput: boolean, couldBeTypingAtOffset0: boolean): ITypeData { if (!previousState) { // This is the EMPTY state return { @@ -117,6 +117,18 @@ export class TextAreaState { let currentSelectionStart = currentState.selectionStart; let currentSelectionEnd = currentState.selectionEnd; + if (couldBeTypingAtOffset0 && previousValue.length > 0 && previousSelectionStart === previousSelectionEnd && currentSelectionStart === currentSelectionEnd) { + // See https://github.com/Microsoft/vscode/issues/42251 + // where typing always happens at offset 0 in the textarea + // when using a custom title area in OSX and moving the window + if (strings.endsWith(currentValue, previousValue)) { + // Looks like something was typed at offset 0 + // ==> pretend we placed the cursor at offset 0 to begin with... + previousSelectionStart = 0; + previousSelectionEnd = 0; + } + } + // Strip the previous suffix from the value (without interfering with the current selection) const previousSuffix = previousValue.substring(previousSelectionEnd); const currentSuffix = currentValue.substring(currentSelectionEnd); diff --git a/src/vs/editor/test/browser/controller/textAreaState.test.ts b/src/vs/editor/test/browser/controller/textAreaState.test.ts index 087af746c69..492ad210207 100644 --- a/src/vs/editor/test/browser/controller/textAreaState.test.ts +++ b/src/vs/editor/test/browser/controller/textAreaState.test.ts @@ -124,7 +124,7 @@ suite('TextAreaState', () => { textArea.dispose(); }); - function testDeduceInput(prevState: TextAreaState, value: string, selectionStart: number, selectionEnd: number, expected: string, expectedCharReplaceCnt: number): void { + function testDeduceInput(prevState: TextAreaState, value: string, selectionStart: number, selectionEnd: number, couldBeEmojiInput: boolean, couldBeTypingAtOffset0: boolean, expected: string, expectedCharReplaceCnt: number): void { prevState = prevState || TextAreaState.EMPTY; let textArea = new MockTextAreaWrapper(); @@ -133,7 +133,7 @@ suite('TextAreaState', () => { textArea._selectionEnd = selectionEnd; let newState = TextAreaState.readFromTextArea(textArea); - let actual = TextAreaState.deduceInput(prevState, newState, true); + let actual = TextAreaState.deduceInput(prevState, newState, couldBeEmojiInput, couldBeTypingAtOffset0); assert.equal(actual.text, expected); assert.equal(actual.replaceCharCnt, expectedCharReplaceCnt); @@ -154,7 +154,7 @@ suite('TextAreaState', () => { testDeduceInput( TextAreaState.EMPTY, 's', - 0, 1, + 0, 1, true, false, 's', 0 ); @@ -164,7 +164,7 @@ suite('TextAreaState', () => { testDeduceInput( new TextAreaState('s', 0, 1, null, null), 'せ', - 0, 1, + 0, 1, true, false, 'せ', 1 ); @@ -174,7 +174,7 @@ suite('TextAreaState', () => { testDeduceInput( new TextAreaState('せ', 0, 1, null, null), 'せn', - 0, 2, + 0, 2, true, false, 'せn', 1 ); @@ -184,7 +184,7 @@ suite('TextAreaState', () => { testDeduceInput( new TextAreaState('せn', 0, 2, null, null), 'せん', - 0, 2, + 0, 2, true, false, 'せん', 2 ); @@ -194,7 +194,7 @@ suite('TextAreaState', () => { testDeduceInput( new TextAreaState('せん', 0, 2, null, null), 'せんs', - 0, 3, + 0, 3, true, false, 'せんs', 2 ); @@ -204,7 +204,7 @@ suite('TextAreaState', () => { testDeduceInput( new TextAreaState('せんs', 0, 3, null, null), 'せんせ', - 0, 3, + 0, 3, true, false, 'せんせ', 3 ); @@ -214,7 +214,7 @@ suite('TextAreaState', () => { testDeduceInput( new TextAreaState('せんせ', 0, 3, null, null), 'せんせ', - 0, 3, + 0, 3, true, false, 'せんせ', 3 ); @@ -224,7 +224,7 @@ suite('TextAreaState', () => { testDeduceInput( new TextAreaState('せんせ', 0, 3, null, null), 'せんせい', - 0, 4, + 0, 4, true, false, 'せんせい', 3 ); @@ -234,7 +234,7 @@ suite('TextAreaState', () => { testDeduceInput( new TextAreaState('せんせい', 0, 4, null, null), 'せんせい', - 4, 4, + 4, 4, true, false, '', 0 ); }); @@ -253,7 +253,7 @@ suite('TextAreaState', () => { testDeduceInput( new TextAreaState('せんせい', 0, 4, null, null), 'せんせい', - 0, 4, + 0, 4, true, false, 'せんせい', 4 ); @@ -263,7 +263,7 @@ suite('TextAreaState', () => { testDeduceInput( new TextAreaState('せんせい', 0, 4, null, null), '先生', - 0, 2, + 0, 2, true, false, '先生', 4 ); @@ -273,7 +273,7 @@ suite('TextAreaState', () => { testDeduceInput( new TextAreaState('先生', 0, 2, null, null), '先生', - 2, 2, + 2, 2, true, false, '', 0 ); }); @@ -282,7 +282,7 @@ suite('TextAreaState', () => { testDeduceInput( null, 'a', - 0, 1, + 0, 1, true, false, 'a', 0 ); }); @@ -291,7 +291,7 @@ suite('TextAreaState', () => { testDeduceInput( new TextAreaState(']\n', 1, 2, null, null), ']\n', - 2, 2, + 2, 2, true, false, '\n', 0 ); }); @@ -300,7 +300,7 @@ suite('TextAreaState', () => { testDeduceInput( null, 'a', - 1, 1, + 1, 1, true, false, 'a', 0 ); }); @@ -309,7 +309,7 @@ suite('TextAreaState', () => { testDeduceInput( TextAreaState.EMPTY, 'a', - 0, 1, + 0, 1, true, false, 'a', 0 ); }); @@ -318,7 +318,7 @@ suite('TextAreaState', () => { testDeduceInput( TextAreaState.EMPTY, 'a', - 1, 1, + 1, 1, true, false, 'a', 0 ); }); @@ -327,7 +327,7 @@ suite('TextAreaState', () => { testDeduceInput( new TextAreaState('Hello world!', 0, 12, null, null), 'H', - 1, 1, + 1, 1, true, false, 'H', 0 ); }); @@ -336,7 +336,7 @@ suite('TextAreaState', () => { testDeduceInput( new TextAreaState('Hello world!', 12, 12, null, null), 'Hello world!a', - 13, 13, + 13, 13, true, false, 'a', 0 ); }); @@ -345,7 +345,7 @@ suite('TextAreaState', () => { testDeduceInput( new TextAreaState('Hello world!', 0, 0, null, null), 'aHello world!', - 1, 1, + 1, 1, true, false, 'a', 0 ); }); @@ -354,7 +354,7 @@ suite('TextAreaState', () => { testDeduceInput( new TextAreaState('Hello world!', 6, 11, null, null), 'Hello other!', - 11, 11, + 11, 11, true, false, 'other', 0 ); }); @@ -363,7 +363,7 @@ suite('TextAreaState', () => { testDeduceInput( TextAreaState.EMPTY, 'これは', - 3, 3, + 3, 3, true, false, 'これは', 0 ); }); @@ -372,7 +372,7 @@ suite('TextAreaState', () => { testDeduceInput( new TextAreaState('Hello world!', 0, 0, null, null), 'Aello world!', - 1, 1, + 1, 1, true, false, 'A', 0 ); }); @@ -381,7 +381,7 @@ suite('TextAreaState', () => { testDeduceInput( new TextAreaState('Hello world!', 5, 5, null, null), 'Hellö world!', - 4, 5, + 4, 5, true, false, 'ö', 0 ); }); @@ -390,7 +390,7 @@ suite('TextAreaState', () => { testDeduceInput( new TextAreaState('Hello world!', 5, 5, null, null), 'Hellöö world!', - 5, 5, + 5, 5, true, false, 'öö', 1 ); }); @@ -399,7 +399,7 @@ suite('TextAreaState', () => { testDeduceInput( new TextAreaState('Hello world!', 5, 5, null, null), 'Helöö world!', - 5, 5, + 5, 5, true, false, 'öö', 2 ); }); @@ -408,7 +408,7 @@ suite('TextAreaState', () => { testDeduceInput( new TextAreaState('Hello world!', 5, 5, null, null), 'Hellö world!', - 5, 5, + 5, 5, true, false, 'ö', 1 ); }); @@ -417,7 +417,7 @@ suite('TextAreaState', () => { testDeduceInput( new TextAreaState('a', 0, 1, null, null), 'a', - 1, 1, + 1, 1, true, false, 'a', 0 ); }); @@ -426,7 +426,7 @@ suite('TextAreaState', () => { testDeduceInput( new TextAreaState('x x', 0, 1, null, null), 'x x', - 1, 1, + 1, 1, true, false, 'x', 0 ); }); @@ -456,7 +456,7 @@ suite('TextAreaState', () => { 'some6 text', 'some7 text' ].join('\n'), - 4, 4, + 4, 4, true, false, '📅', 0 ); }); @@ -470,7 +470,7 @@ suite('TextAreaState', () => { null, null ), 'some💊1 text', - 6, 6, + 6, 6, true, false, '💊', 0 ); }); @@ -484,7 +484,7 @@ suite('TextAreaState', () => { null, null ), 'qwertyu\nasdfghj\nzxcvbnm🎈', - 25, 25, + 25, 25, true, false, '🎈', 0 ); }); @@ -499,11 +499,25 @@ suite('TextAreaState', () => { null, null ), 'some⌨️1 text', - 6, 6, + 6, 6, true, false, '⌨️', 0 ); }); + test('issue #42251: Minor issue, character swapped when typing', () => { + // Typing on OSX occurs at offset 0 after moving the window using the custom (non-native) titlebar. + testDeduceInput( + new TextAreaState( + 'ab', + 2, 2, + null, null + ), + 'cab', + 1, 1, true, true, + 'c', 0 + ); + }); + suite('PagedScreenReaderStrategy', () => { function testPagedScreenReaderStrategy(lines: string[], selection: Selection, expected: TextAreaState): void { From b58f27d0cb8ab868526a38bb5bb935231a53a977 Mon Sep 17 00:00:00 2001 From: Alex Dima Date: Wed, 4 Apr 2018 12:12:45 +0200 Subject: [PATCH 27/49] Simplify ViewLineRenderingData (#22352) --- .../browser/viewParts/lines/viewLine.ts | 12 +++------- .../common/viewLayout/viewLineRenderer.ts | 16 +++++--------- src/vs/editor/common/viewModel/viewModel.ts | 22 ++++++++++++++----- 3 files changed, 25 insertions(+), 25 deletions(-) diff --git a/src/vs/editor/browser/viewParts/lines/viewLine.ts b/src/vs/editor/browser/viewParts/lines/viewLine.ts index 7a9761d20d8..13eb203b8db 100644 --- a/src/vs/editor/browser/viewParts/lines/viewLine.ts +++ b/src/vs/editor/browser/viewParts/lines/viewLine.ts @@ -6,7 +6,6 @@ import * as browser from 'vs/base/browser/browser'; import * as platform from 'vs/base/common/platform'; -import * as strings from 'vs/base/common/strings'; import { FastDomNode, createFastDomNode } from 'vs/base/browser/fastDomNode'; import { IConfiguration } from 'vs/editor/common/editorCommon'; import { LineDecoration } from 'vs/editor/common/viewLayout/lineDecorations'; @@ -192,7 +191,7 @@ export class ViewLine implements IVisibleLine { let renderLineInput = new RenderLineInput( options.useMonospaceOptimizations, lineData.content, - lineData.mightContainRTL, + lineData.containsRTL, lineData.minColumn - 1, lineData.tokens, actualInlineDecorations, @@ -222,13 +221,8 @@ export class ViewLine implements IVisibleLine { sb.appendASCIIString(''); let renderedViewLine: IRenderedViewLine = null; - if (canUseFastRenderedViewLine && options.useMonospaceOptimizations && !output.containsForeignElements) { - let isRegularASCII = true; - if (lineData.mightContainNonBasicASCII) { - isRegularASCII = strings.isBasicASCII(lineData.content); - } - - if (isRegularASCII && lineData.content.length < 1000 && renderLineInput.lineTokens.getCount() < 100) { + if (canUseFastRenderedViewLine && lineData.isBasicASCII && options.useMonospaceOptimizations && !output.containsForeignElements) { + if (lineData.content.length < 1000 && renderLineInput.lineTokens.getCount() < 100) { // Browser rounding errors have been observed in Chrome and IE, so using the fast // view line only for short lines. Please test before removing the length check... // --- diff --git a/src/vs/editor/common/viewLayout/viewLineRenderer.ts b/src/vs/editor/common/viewLayout/viewLineRenderer.ts index a3c3bef72de..e959bc5f1c9 100644 --- a/src/vs/editor/common/viewLayout/viewLineRenderer.ts +++ b/src/vs/editor/common/viewLayout/viewLineRenderer.ts @@ -36,7 +36,7 @@ export class RenderLineInput { public readonly useMonospaceOptimizations: boolean; public readonly lineContent: string; - public readonly mightContainRTL: boolean; + public readonly containsRTL: boolean; public readonly fauxIndentLength: number; public readonly lineTokens: IViewLineTokens; public readonly lineDecorations: LineDecoration[]; @@ -50,7 +50,7 @@ export class RenderLineInput { constructor( useMonospaceOptimizations: boolean, lineContent: string, - mightContainRTL: boolean, + containsRTL: boolean, fauxIndentLength: number, lineTokens: IViewLineTokens, lineDecorations: LineDecoration[], @@ -63,7 +63,7 @@ export class RenderLineInput { ) { this.useMonospaceOptimizations = useMonospaceOptimizations; this.lineContent = lineContent; - this.mightContainRTL = mightContainRTL; + this.containsRTL = containsRTL; this.fauxIndentLength = fauxIndentLength; this.lineTokens = lineTokens; this.lineDecorations = lineDecorations; @@ -85,7 +85,7 @@ export class RenderLineInput { return ( this.useMonospaceOptimizations === other.useMonospaceOptimizations && this.lineContent === other.lineContent - && this.mightContainRTL === other.mightContainRTL + && this.containsRTL === other.containsRTL && this.fauxIndentLength === other.fauxIndentLength && this.tabSize === other.tabSize && this.spaceWidth === other.spaceWidth @@ -330,11 +330,7 @@ function resolveRenderLineInput(input: RenderLineInput): ResolvedRenderLineInput } tokens = _applyInlineDecorations(lineContent, len, tokens, input.lineDecorations); } - let containsRTL = false; - if (input.mightContainRTL) { - containsRTL = strings.containsRTL(lineContent); - } - if (!containsRTL && !input.fontLigatures) { + if (!input.containsRTL && !input.fontLigatures) { tokens = splitLargeTokens(lineContent, tokens); } @@ -346,7 +342,7 @@ function resolveRenderLineInput(input: RenderLineInput): ResolvedRenderLineInput tokens, containsForeignElements, input.tabSize, - containsRTL, + input.containsRTL, input.spaceWidth, input.renderWhitespace, input.renderControlCharacters diff --git a/src/vs/editor/common/viewModel/viewModel.ts b/src/vs/editor/common/viewModel/viewModel.ts index 12766993b5d..857ea028fb3 100644 --- a/src/vs/editor/common/viewModel/viewModel.ts +++ b/src/vs/editor/common/viewModel/viewModel.ts @@ -15,6 +15,7 @@ import { Scrollable, IScrollPosition } from 'vs/base/common/scrollable'; import { IPartialViewLinesViewportData } from 'vs/editor/common/viewLayout/viewLinesViewportData'; import { IEditorWhitespace } from 'vs/editor/common/viewLayout/whitespaceComputer'; import { ITheme } from 'vs/platform/theme/common/themeService'; +import * as strings from 'vs/base/common/strings'; export interface IViewWhitespaceViewportData { readonly id: number; @@ -208,13 +209,13 @@ export class ViewLineRenderingData { */ public readonly content: string; /** - * If set to false, it is guaranteed that `content` contains only LTR chars. + * Describes if `content` contains RTL characters. */ - public readonly mightContainRTL: boolean; + public readonly containsRTL: boolean; /** - * If set to false, it is guaranteed that `content` contains only basic ASCII chars. + * Describes if `content` contains non basic ASCII chars. */ - public readonly mightContainNonBasicASCII: boolean; + public readonly isBasicASCII: boolean; /** * The tokens at this view line. */ @@ -241,8 +242,17 @@ export class ViewLineRenderingData { this.minColumn = minColumn; this.maxColumn = maxColumn; this.content = content; - this.mightContainRTL = mightContainRTL; - this.mightContainNonBasicASCII = mightContainNonBasicASCII; + + this.isBasicASCII = true; + if (mightContainNonBasicASCII) { + this.isBasicASCII = strings.isBasicASCII(this.content); + } + + this.containsRTL = false; + if (!this.isBasicASCII && mightContainRTL) { + this.containsRTL = strings.containsRTL(this.content); + } + this.tokens = tokens; this.inlineDecorations = inlineDecorations; this.tabSize = tabSize; From 0f436b570009764ed6f3d480242d320d0470641b Mon Sep 17 00:00:00 2001 From: Alex Dima Date: Wed, 4 Apr 2018 12:34:40 +0200 Subject: [PATCH 28/49] Add isBasicASCII to RenderLineInput (#22352) --- .../browser/viewParts/lines/viewLine.ts | 1 + .../editor/browser/widget/diffEditorWidget.ts | 7 +- src/vs/editor/browser/widget/diffReview.ts | 6 +- .../common/viewLayout/viewLineRenderer.ts | 4 + src/vs/editor/common/viewModel/viewModel.ts | 25 ++-- src/vs/editor/standalone/browser/colorizer.ts | 20 +++- .../viewLayout/viewLineRenderer.test.ts | 107 ++++++++++++++++++ 7 files changed, 153 insertions(+), 17 deletions(-) diff --git a/src/vs/editor/browser/viewParts/lines/viewLine.ts b/src/vs/editor/browser/viewParts/lines/viewLine.ts index 13eb203b8db..c1fa275be29 100644 --- a/src/vs/editor/browser/viewParts/lines/viewLine.ts +++ b/src/vs/editor/browser/viewParts/lines/viewLine.ts @@ -191,6 +191,7 @@ export class ViewLine implements IVisibleLine { let renderLineInput = new RenderLineInput( options.useMonospaceOptimizations, lineData.content, + lineData.isBasicASCII, lineData.containsRTL, lineData.minColumn - 1, lineData.tokens, diff --git a/src/vs/editor/browser/widget/diffEditorWidget.ts b/src/vs/editor/browser/widget/diffEditorWidget.ts index 294e27a714c..b823747dede 100644 --- a/src/vs/editor/browser/widget/diffEditorWidget.ts +++ b/src/vs/editor/browser/widget/diffEditorWidget.ts @@ -27,7 +27,7 @@ import { LineTokens } from 'vs/editor/common/core/lineTokens'; import { Configuration } from 'vs/editor/browser/config/configuration'; import { Position, IPosition } from 'vs/editor/common/core/position'; import { Selection, ISelection } from 'vs/editor/common/core/selection'; -import { InlineDecoration, InlineDecorationType } from 'vs/editor/common/viewModel/viewModel'; +import { InlineDecoration, InlineDecorationType, ViewLineRenderingData } from 'vs/editor/common/viewModel/viewModel'; import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; import { ColorId, MetadataConsts, FontStyle } from 'vs/editor/common/modes'; import { Event, Emitter } from 'vs/base/common/event'; @@ -1998,10 +1998,13 @@ class InlineViewZonesComputer extends ViewZonesComputer { sb.appendASCIIString(String(count * config.lineHeight)); sb.appendASCIIString('px;width:1000000px;">'); + const isBasicASCII = ViewLineRenderingData.isBasicASCII(lineContent, originalModel.mightContainNonBasicASCII()); + const containsRTL = ViewLineRenderingData.containsRTL(lineContent, isBasicASCII, originalModel.mightContainRTL()); renderViewLine(new RenderLineInput( (config.fontInfo.isMonospace && !config.viewInfo.disableMonospaceOptimizations), lineContent, - originalModel.mightContainRTL(), + isBasicASCII, + containsRTL, 0, lineTokens, actualDecorations, diff --git a/src/vs/editor/browser/widget/diffReview.ts b/src/vs/editor/browser/widget/diffReview.ts index 0f6af96eedd..7715c7482d7 100644 --- a/src/vs/editor/browser/widget/diffReview.ts +++ b/src/vs/editor/browser/widget/diffReview.ts @@ -29,6 +29,7 @@ import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { ITextModel, TextModelResolvedOptions } from 'vs/editor/common/model'; +import { ViewLineRenderingData } from 'vs/editor/common/viewModel/viewModel'; const DIFF_LINES_PADDING = 3; @@ -738,10 +739,13 @@ export class DiffReview extends Disposable { const lineTokens = new LineTokens(tokens, lineContent); + const isBasicASCII = ViewLineRenderingData.isBasicASCII(lineContent, model.mightContainNonBasicASCII()); + const containsRTL = ViewLineRenderingData.containsRTL(lineContent, isBasicASCII, model.mightContainRTL()); const r = renderViewLine(new RenderLineInput( (config.fontInfo.isMonospace && !config.viewInfo.disableMonospaceOptimizations), lineContent, - model.mightContainRTL(), + isBasicASCII, + containsRTL, 0, lineTokens, [], diff --git a/src/vs/editor/common/viewLayout/viewLineRenderer.ts b/src/vs/editor/common/viewLayout/viewLineRenderer.ts index e959bc5f1c9..e4ae47ae47d 100644 --- a/src/vs/editor/common/viewLayout/viewLineRenderer.ts +++ b/src/vs/editor/common/viewLayout/viewLineRenderer.ts @@ -36,6 +36,7 @@ export class RenderLineInput { public readonly useMonospaceOptimizations: boolean; public readonly lineContent: string; + public readonly isBasicASCII: boolean; public readonly containsRTL: boolean; public readonly fauxIndentLength: number; public readonly lineTokens: IViewLineTokens; @@ -50,6 +51,7 @@ export class RenderLineInput { constructor( useMonospaceOptimizations: boolean, lineContent: string, + isBasicASCII: boolean, containsRTL: boolean, fauxIndentLength: number, lineTokens: IViewLineTokens, @@ -63,6 +65,7 @@ export class RenderLineInput { ) { this.useMonospaceOptimizations = useMonospaceOptimizations; this.lineContent = lineContent; + this.isBasicASCII = isBasicASCII; this.containsRTL = containsRTL; this.fauxIndentLength = fauxIndentLength; this.lineTokens = lineTokens; @@ -85,6 +88,7 @@ export class RenderLineInput { return ( this.useMonospaceOptimizations === other.useMonospaceOptimizations && this.lineContent === other.lineContent + && this.isBasicASCII === other.isBasicASCII && this.containsRTL === other.containsRTL && this.fauxIndentLength === other.fauxIndentLength && this.tabSize === other.tabSize diff --git a/src/vs/editor/common/viewModel/viewModel.ts b/src/vs/editor/common/viewModel/viewModel.ts index 857ea028fb3..1702d8fd59f 100644 --- a/src/vs/editor/common/viewModel/viewModel.ts +++ b/src/vs/editor/common/viewModel/viewModel.ts @@ -243,20 +243,27 @@ export class ViewLineRenderingData { this.maxColumn = maxColumn; this.content = content; - this.isBasicASCII = true; - if (mightContainNonBasicASCII) { - this.isBasicASCII = strings.isBasicASCII(this.content); - } - - this.containsRTL = false; - if (!this.isBasicASCII && mightContainRTL) { - this.containsRTL = strings.containsRTL(this.content); - } + this.isBasicASCII = ViewLineRenderingData.isBasicASCII(content, mightContainNonBasicASCII); + this.containsRTL = ViewLineRenderingData.containsRTL(content, this.isBasicASCII, mightContainRTL); this.tokens = tokens; this.inlineDecorations = inlineDecorations; this.tabSize = tabSize; } + + public static isBasicASCII(lineContent: string, mightContainNonBasicASCII: boolean): boolean { + if (mightContainNonBasicASCII) { + return strings.isBasicASCII(lineContent); + } + return true; + } + + public static containsRTL(lineContent: string, isBasicASCII: boolean, mightContainRTL: boolean): boolean { + if (!isBasicASCII && mightContainRTL) { + return strings.containsRTL(lineContent); + } + return false; + } } export const enum InlineDecorationType { diff --git a/src/vs/editor/standalone/browser/colorizer.ts b/src/vs/editor/standalone/browser/colorizer.ts index 83603e32539..61e3b448392 100644 --- a/src/vs/editor/standalone/browser/colorizer.ts +++ b/src/vs/editor/standalone/browser/colorizer.ts @@ -13,6 +13,7 @@ import { renderViewLine2 as renderViewLine, RenderLineInput } from 'vs/editor/co import { LineTokens, IViewLineTokens } from 'vs/editor/common/core/lineTokens'; import * as strings from 'vs/base/common/strings'; import { IStandaloneThemeService } from 'vs/editor/standalone/common/standaloneThemeService'; +import { ViewLineRenderingData } from 'vs/editor/common/viewModel/viewModel'; export interface IColorizerOptions { tabSize?: number; @@ -93,11 +94,14 @@ export class Colorizer { }); } - public static colorizeLine(line: string, mightContainRTL: boolean, tokens: IViewLineTokens, tabSize: number = 4): string { + public static colorizeLine(line: string, mightContainNonBasicASCII: boolean, mightContainRTL: boolean, tokens: IViewLineTokens, tabSize: number = 4): string { + const isBasicASCII = ViewLineRenderingData.isBasicASCII(line, mightContainNonBasicASCII); + const containsRTL = ViewLineRenderingData.containsRTL(line, isBasicASCII, mightContainRTL); let renderResult = renderViewLine(new RenderLineInput( false, line, - mightContainRTL, + isBasicASCII, + containsRTL, 0, tokens, [], @@ -116,7 +120,7 @@ export class Colorizer { model.forceTokenization(lineNumber); let tokens = model.getLineTokens(lineNumber); let inflatedTokens = tokens.inflate(); - return this.colorizeLine(content, model.mightContainRTL(), inflatedTokens, tabSize); + return this.colorizeLine(content, model.mightContainNonBasicASCII(), model.mightContainRTL(), inflatedTokens, tabSize); } } @@ -143,10 +147,13 @@ function _fakeColorize(lines: string[], tabSize: number): string { tokens[0] = line.length; const lineTokens = new LineTokens(tokens, line); + const isBasicASCII = ViewLineRenderingData.isBasicASCII(line, /* check for basic ASCII */true); + const containsRTL = ViewLineRenderingData.containsRTL(line, isBasicASCII, /* check for RTL */true); let renderResult = renderViewLine(new RenderLineInput( false, line, - false, + isBasicASCII, + containsRTL, 0, lineTokens, [], @@ -174,10 +181,13 @@ function _actualColorize(lines: string[], tabSize: number, tokenizationSupport: let tokenizeResult = tokenizationSupport.tokenize2(line, state, 0); LineTokens.convertToEndOffset(tokenizeResult.tokens, line.length); let lineTokens = new LineTokens(tokenizeResult.tokens, line); + const isBasicASCII = ViewLineRenderingData.isBasicASCII(line, /* check for basic ASCII */true); + const containsRTL = ViewLineRenderingData.containsRTL(line, isBasicASCII, /* check for RTL */true); let renderResult = renderViewLine(new RenderLineInput( false, line, - true/* check for RTL */, + isBasicASCII, + containsRTL, 0, lineTokens.inflate(), [], diff --git a/src/vs/editor/test/common/viewLayout/viewLineRenderer.test.ts b/src/vs/editor/test/common/viewLayout/viewLineRenderer.test.ts index b0214ecf8d9..f4db5c50977 100644 --- a/src/vs/editor/test/common/viewLayout/viewLineRenderer.test.ts +++ b/src/vs/editor/test/common/viewLayout/viewLineRenderer.test.ts @@ -12,6 +12,7 @@ import { MetadataConsts } from 'vs/editor/common/modes'; import { LineDecoration } from 'vs/editor/common/viewLayout/lineDecorations'; import { InlineDecorationType } from 'vs/editor/common/viewModel/viewModel'; import { IViewLineTokens } from 'vs/editor/common/core/lineTokens'; +import * as strings from 'vs/base/common/strings'; function createViewLineTokens(viewLineTokens: ViewLineToken[]): IViewLineTokens { return new ViewLineTokens(viewLineTokens); @@ -29,6 +30,7 @@ suite('viewLineRenderer.renderLine', () => { let _actual = renderViewLine(new RenderLineInput( false, lineContent, + strings.isBasicASCII(lineContent), false, 0, createViewLineTokens([new ViewLineToken(lineContent.length, 0)]), @@ -75,6 +77,7 @@ suite('viewLineRenderer.renderLine', () => { let _actual = renderViewLine(new RenderLineInput( false, lineContent, + true, false, 0, createViewLineTokens(parts), @@ -111,6 +114,7 @@ suite('viewLineRenderer.renderLine', () => { let _actual = renderViewLine(new RenderLineInput( false, 'Hello world!', + true, false, 0, createViewLineTokens([ @@ -212,6 +216,7 @@ suite('viewLineRenderer.renderLine', () => { let _actual = renderViewLine(new RenderLineInput( false, lineText, + true, false, 0, lineParts, @@ -271,6 +276,7 @@ suite('viewLineRenderer.renderLine', () => { let _actual = renderViewLine(new RenderLineInput( false, lineText, + true, false, 0, lineParts, @@ -330,6 +336,7 @@ suite('viewLineRenderer.renderLine', () => { let _actual = renderViewLine(new RenderLineInput( false, lineText, + true, false, 0, lineParts, @@ -366,6 +373,7 @@ suite('viewLineRenderer.renderLine', () => { let _actual = renderViewLine(new RenderLineInput( false, lineText, + false, true, 0, lineParts, @@ -393,6 +401,7 @@ suite('viewLineRenderer.renderLine', () => { let actual = renderViewLine(new RenderLineInput( false, lineText, + true, false, 0, lineParts, @@ -490,6 +499,7 @@ suite('viewLineRenderer.renderLine', () => { let actual = renderViewLine(new RenderLineInput( false, lineText, + true, false, 0, lineParts, @@ -524,6 +534,7 @@ suite('viewLineRenderer.renderLine', () => { false, lineText, false, + false, 0, lineParts, [], @@ -553,6 +564,7 @@ suite('viewLineRenderer.renderLine', () => { let actual = renderViewLine(new RenderLineInput( false, lineText, + false, true, 0, lineParts, @@ -596,6 +608,7 @@ suite('viewLineRenderer.renderLine', () => { let _actual = renderViewLine(new RenderLineInput( true, lineText, + true, false, 4, lineParts, @@ -676,6 +689,7 @@ suite('viewLineRenderer.renderLine 2', () => { let actual = renderViewLine(new RenderLineInput( fontIsMonospace, lineContent, + true, false, fauxIndentLength, createViewLineTokens(tokens), @@ -698,6 +712,7 @@ suite('viewLineRenderer.renderLine 2', () => { let actual = renderViewLine(new RenderLineInput( false, lineContent, + true, false, 0, createViewLineTokens([createPart(21, 3)]), @@ -726,6 +741,7 @@ suite('viewLineRenderer.renderLine 2', () => { let actual = renderViewLine(new RenderLineInput( true, lineContent, + true, false, 0, createViewLineTokens([ @@ -990,6 +1006,7 @@ suite('viewLineRenderer.renderLine 2', () => { let actual = renderViewLine(new RenderLineInput( false, 'Hello world', + true, false, 0, createViewLineTokens([createPart(11, 0)]), @@ -1031,6 +1048,7 @@ suite('viewLineRenderer.renderLine 2', () => { let actual = renderViewLine(new RenderLineInput( false, lineContent, + true, false, 0, createViewLineTokens([createPart(4, 3)]), @@ -1060,6 +1078,7 @@ suite('viewLineRenderer.renderLine 2', () => { let actual = renderViewLine(new RenderLineInput( false, lineContent, + true, false, 0, createViewLineTokens([createPart(4, 3)]), @@ -1090,6 +1109,7 @@ suite('viewLineRenderer.renderLine 2', () => { let actual = renderViewLine(new RenderLineInput( false, lineContent, + true, false, 0, createViewLineTokens([createPart(0, 3)]), @@ -1117,6 +1137,7 @@ suite('viewLineRenderer.renderLine 2', () => { true, ' 1. 🙏', false, + false, 0, createViewLineTokens([createPart(7, 3)]), [new LineDecoration(7, 8, 'inline-folded', InlineDecorationType.After)], @@ -1143,6 +1164,7 @@ suite('viewLineRenderer.renderLine 2', () => { let actual = renderViewLine(new RenderLineInput( true, '', + true, false, 0, createViewLineTokens([createPart(0, 3)]), @@ -1172,6 +1194,7 @@ suite('viewLineRenderer.renderLine 2', () => { let actual = renderViewLine(new RenderLineInput( true, '\t}', + true, false, 0, createViewLineTokens([createPart(2, 3)]), @@ -1203,6 +1226,7 @@ suite('viewLineRenderer.renderLine 2', () => { true, 'asd = "擦"\t\t#asd', false, + false, 0, createViewLineTokens([createPart(15, 3)]), [], @@ -1229,6 +1253,7 @@ suite('viewLineRenderer.renderLine 2', () => { true, 'asd = "擦"\t\t#asd', false, + false, 0, createViewLineTokens([createPart(15, 3)]), [], @@ -1255,10 +1280,92 @@ suite('viewLineRenderer.renderLine 2', () => { assert.deepEqual(actual.html, expected); }); + // test('issue #22352: COMBINING ACUTE ACCENT (U+0301)', () => { + + // let actual = renderViewLine(new RenderLineInput( + // true, + // '12345689012345678901234568901234567890123456890abába', + // false, + // false, + // 0, + // createViewLineTokens([createPart(53, 3)]), + // [], + // 4, + // 10, + // 10000, + // 'none', + // false, + // false + // )); + + // let expected = [ + // '', + // '12345689012345678901234568901234567890123456890abába', + // '' + // ].join(''); + + // assert.deepEqual(actual.html, expected); + // }); + + // test('issue #22352: Partially Broken Complex Script Rendering of Tamil', () => { + + // let actual = renderViewLine(new RenderLineInput( + // true, + // ' JoyShareல் பின்தொடர்ந்து, விடீயோ, ஜோக்குகள், அனிமேசன், நகைச்சுவை படங்கள் மற்றும் செய்திகளை பெறுவீர்', + // false, + // false, + // 0, + // createViewLineTokens([createPart(100, 3)]), + // [], + // 4, + // 10, + // 10000, + // 'none', + // false, + // false + // )); + + // let expected = [ + // '', + // ' JoyShareல் பின்தொடர்ந்து, விடீயோ, ஜோக்குகள், அனிமேசன், நகைச்சுவை படங்கள் மற்றும் செய்திகளை பெறுவீர்', + // '' + // ].join(''); + + // assert.deepEqual(actual.html, expected); + // }); + + // test('issue #42700: Hindi characters are not being rendered properly', () => { + + // let actual = renderViewLine(new RenderLineInput( + // true, + // ' वो ऐसा क्या है जो हमारे अंदर भी है और बाहर भी है। जिसकी वजह से हम सब हैं। जिसने इस सृष्टि की रचना की है।', + // false, + // false, + // 0, + // createViewLineTokens([createPart(105, 3)]), + // [], + // 4, + // 10, + // 10000, + // 'none', + // false, + // false + // )); + + // let expected = [ + // '', + // ' वो ऐसा क्या है जो हमारे अंदर भी है और बाहर भी है। जिसकी वजह से हम सब हैं। जिसने इस सृष्टि की रचना की है।', + // '' + // ].join(''); + + // assert.deepEqual(actual.html, expected); + // }); + function createTestGetColumnOfLinePartOffset(lineContent: string, tabSize: number, parts: ViewLineToken[], expectedPartLengths: number[]): (partIndex: number, partLength: number, offset: number, expected: number) => void { let renderLineOutput = renderViewLine(new RenderLineInput( false, lineContent, + true, false, 0, createViewLineTokens(parts), From a8f068a5400b6b7907fa9763615e13e6de7f195d Mon Sep 17 00:00:00 2001 From: Alex Dima Date: Wed, 4 Apr 2018 12:48:40 +0200 Subject: [PATCH 29/49] Fixes #22352: Only break long text into spans if it is basic ASCII --- .../common/viewLayout/viewLineRenderer.ts | 7 +- .../viewLayout/viewLineRenderer.test.ts | 148 +++++++++--------- 2 files changed, 75 insertions(+), 80 deletions(-) diff --git a/src/vs/editor/common/viewLayout/viewLineRenderer.ts b/src/vs/editor/common/viewLayout/viewLineRenderer.ts index e4ae47ae47d..c6faa239326 100644 --- a/src/vs/editor/common/viewLayout/viewLineRenderer.ts +++ b/src/vs/editor/common/viewLayout/viewLineRenderer.ts @@ -334,7 +334,7 @@ function resolveRenderLineInput(input: RenderLineInput): ResolvedRenderLineInput } tokens = _applyInlineDecorations(lineContent, len, tokens, input.lineDecorations); } - if (!input.containsRTL && !input.fontLigatures) { + if (input.isBasicASCII && !input.fontLigatures) { tokens = splitLargeTokens(lineContent, tokens); } @@ -406,11 +406,6 @@ function splitLargeTokens(lineContent: string, tokens: LinePart[]): LinePart[] { const piecesCount = Math.ceil(diff / Constants.LongToken); for (let j = 1; j < piecesCount; j++) { let pieceEndIndex = lastTokenEndIndex + (j * Constants.LongToken); - let lastCharInPiece = lineContent.charCodeAt(pieceEndIndex - 1); - if (strings.isHighSurrogate(lastCharInPiece)) { - // Don't cut in the middle of a surrogate pair - pieceEndIndex--; - } result[resultLen++] = new LinePart(pieceEndIndex, tokenType); } result[resultLen++] = new LinePart(tokenEndIndex, tokenType); diff --git a/src/vs/editor/test/common/viewLayout/viewLineRenderer.test.ts b/src/vs/editor/test/common/viewLayout/viewLineRenderer.test.ts index f4db5c50977..550eb85bf20 100644 --- a/src/vs/editor/test/common/viewLayout/viewLineRenderer.test.ts +++ b/src/vs/editor/test/common/viewLayout/viewLineRenderer.test.ts @@ -546,11 +546,7 @@ suite('viewLineRenderer.renderLine', () => { false )); let expectedOutput = [ - 'a𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷', - '𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷', - '𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷', - '𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷', - '𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷', + 'a𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷', ]; assert.equal(actual.html, '' + expectedOutput.join('') + ''); }); @@ -1280,86 +1276,90 @@ suite('viewLineRenderer.renderLine 2', () => { assert.deepEqual(actual.html, expected); }); - // test('issue #22352: COMBINING ACUTE ACCENT (U+0301)', () => { + test('issue #22352: COMBINING ACUTE ACCENT (U+0301)', () => { - // let actual = renderViewLine(new RenderLineInput( - // true, - // '12345689012345678901234568901234567890123456890abába', - // false, - // false, - // 0, - // createViewLineTokens([createPart(53, 3)]), - // [], - // 4, - // 10, - // 10000, - // 'none', - // false, - // false - // )); + let actual = renderViewLine(new RenderLineInput( + true, + '12345689012345678901234568901234567890123456890abába', + false, + false, + 0, + createViewLineTokens([createPart(53, 3)]), + [], + 4, + 10, + 10000, + 'none', + false, + false + )); - // let expected = [ - // '', - // '12345689012345678901234568901234567890123456890abába', - // '' - // ].join(''); + let expected = [ + '', + '12345689012345678901234568901234567890123456890abába', + '' + ].join(''); - // assert.deepEqual(actual.html, expected); - // }); + assert.deepEqual(actual.html, expected); + }); - // test('issue #22352: Partially Broken Complex Script Rendering of Tamil', () => { + test('issue #22352: Partially Broken Complex Script Rendering of Tamil', () => { - // let actual = renderViewLine(new RenderLineInput( - // true, - // ' JoyShareல் பின்தொடர்ந்து, விடீயோ, ஜோக்குகள், அனிமேசன், நகைச்சுவை படங்கள் மற்றும் செய்திகளை பெறுவீர்', - // false, - // false, - // 0, - // createViewLineTokens([createPart(100, 3)]), - // [], - // 4, - // 10, - // 10000, - // 'none', - // false, - // false - // )); + let actual = renderViewLine(new RenderLineInput( + true, + ' JoyShareல் பின்தொடர்ந்து, விடீயோ, ஜோக்குகள், அனிமேசன், நகைச்சுவை படங்கள் மற்றும் செய்திகளை பெறுவீர்', + false, + false, + 0, + createViewLineTokens([createPart(100, 3)]), + [], + 4, + 10, + 10000, + 'none', + false, + false + )); - // let expected = [ - // '', - // ' JoyShareல் பின்தொடர்ந்து, விடீயோ, ஜோக்குகள், அனிமேசன், நகைச்சுவை படங்கள் மற்றும் செய்திகளை பெறுவீர்', - // '' - // ].join(''); + let expected = [ + '', + '\u00a0JoyShareல்\u00a0பின்தொடர்ந்து,\u00a0விடீயோ,\u00a0ஜோக்குகள்,\u00a0அனிமேசன்,\u00a0நகைச்சுவை\u00a0படங்கள்\u00a0மற்றும்\u00a0செய்திகளை\u00a0பெறுவீர்', + '' + ].join(''); - // assert.deepEqual(actual.html, expected); - // }); + let _expected = expected.split('').map(c => c.charCodeAt(0)); + let _actual = actual.html.split('').map(c => c.charCodeAt(0)); + assert.deepEqual(_actual, _expected); - // test('issue #42700: Hindi characters are not being rendered properly', () => { + assert.deepEqual(actual.html, expected); + }); - // let actual = renderViewLine(new RenderLineInput( - // true, - // ' वो ऐसा क्या है जो हमारे अंदर भी है और बाहर भी है। जिसकी वजह से हम सब हैं। जिसने इस सृष्टि की रचना की है।', - // false, - // false, - // 0, - // createViewLineTokens([createPart(105, 3)]), - // [], - // 4, - // 10, - // 10000, - // 'none', - // false, - // false - // )); + test('issue #42700: Hindi characters are not being rendered properly', () => { - // let expected = [ - // '', - // ' वो ऐसा क्या है जो हमारे अंदर भी है और बाहर भी है। जिसकी वजह से हम सब हैं। जिसने इस सृष्टि की रचना की है।', - // '' - // ].join(''); + let actual = renderViewLine(new RenderLineInput( + true, + ' वो ऐसा क्या है जो हमारे अंदर भी है और बाहर भी है। जिसकी वजह से हम सब हैं। जिसने इस सृष्टि की रचना की है।', + false, + false, + 0, + createViewLineTokens([createPart(105, 3)]), + [], + 4, + 10, + 10000, + 'none', + false, + false + )); - // assert.deepEqual(actual.html, expected); - // }); + let expected = [ + '', + '\u00a0वो\u00a0ऐसा\u00a0क्या\u00a0है\u00a0जो\u00a0हमारे\u00a0अंदर\u00a0भी\u00a0है\u00a0और\u00a0बाहर\u00a0भी\u00a0है।\u00a0जिसकी\u00a0वजह\u00a0से\u00a0हम\u00a0सब\u00a0हैं।\u00a0जिसने\u00a0इस\u00a0सृष्टि\u00a0की\u00a0रचना\u00a0की\u00a0है।', + '' + ].join(''); + + assert.deepEqual(actual.html, expected); + }); function createTestGetColumnOfLinePartOffset(lineContent: string, tabSize: number, parts: ViewLineToken[], expectedPartLengths: number[]): (partIndex: number, partLength: number, offset: number, expected: number) => void { let renderLineOutput = renderViewLine(new RenderLineInput( From 99b6b5d69385294050bf4436197819c101c37b3a Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Wed, 4 Apr 2018 11:22:15 +0200 Subject: [PATCH 30/49] Change explorer model to a Map to fix #47120 --- .../parts/files/common/explorerModel.ts | 52 ++++++++++++------- .../electron-browser/views/explorerViewer.ts | 10 ++-- 2 files changed, 40 insertions(+), 22 deletions(-) diff --git a/src/vs/workbench/parts/files/common/explorerModel.ts b/src/vs/workbench/parts/files/common/explorerModel.ts index 6abc54c895a..e72ab77eb13 100644 --- a/src/vs/workbench/parts/files/common/explorerModel.ts +++ b/src/vs/workbench/parts/files/common/explorerModel.ts @@ -77,7 +77,7 @@ export class ExplorerItem { public etag: string; private _isDirectory: boolean; private _isSymbolicLink: boolean; - private children: { [name: string]: ExplorerItem }; + private children: Map; public parent: ExplorerItem; public isDirectoryResolved: boolean; @@ -109,7 +109,7 @@ export class ExplorerItem { if (value !== this._isDirectory) { this._isDirectory = value; if (this._isDirectory) { - this.children = Object.create(null); + this.children = new Map(); } else { this.children = undefined; } @@ -185,18 +185,16 @@ export class ExplorerItem { // Map resource => stat const oldLocalChildren = new ResourceMap(); if (local.children) { - for (let name in local.children) { - const child = local.children[name]; + local.children.forEach(child => { oldLocalChildren.set(child.resource, child); - } + }); } // Clear current children - local.children = Object.create(null); + local.children = new Map(); // Merge received children - for (let name in disk.children) { - const diskChild = disk.children[name]; + disk.children.forEach(diskChild => { const formerLocalChild = oldLocalChildren.get(diskChild.resource); // Existing child: merge if (formerLocalChild) { @@ -210,7 +208,7 @@ export class ExplorerItem { diskChild.parent = local; local.addChild(diskChild); } - } + }); } } @@ -223,7 +221,7 @@ export class ExplorerItem { child.parent = this; child.updateResource(false); - this.children[this.getPlatformAwareName(child.name)] = child; + this.children.set(this.getPlatformAwareName(child.name), child); } public getChild(name: string): ExplorerItem { @@ -231,7 +229,7 @@ export class ExplorerItem { return undefined; } - return this.children[this.getPlatformAwareName(name)]; + return this.children.get(this.getPlatformAwareName(name)); } /** @@ -242,7 +240,20 @@ export class ExplorerItem { return undefined; } - return Object.keys(this.children).map(name => this.children[name]); + const items: ExplorerItem[] = []; + this.children.forEach(child => { + items.push(child); + }); + + return items; + } + + public getChildrenCount(): number { + if (!this.children) { + return 0; + } + + return this.children.size; } public getChildrenNames(): string[] { @@ -250,14 +261,19 @@ export class ExplorerItem { return []; } - return Object.keys(this.children); + const names: string[] = []; + this.children.forEach(child => { + names.push(child.name); + }); + + return names; } /** * Removes a child element from this folder. */ public removeChild(child: ExplorerItem): void { - delete this.children[this.getPlatformAwareName(child.name)]; + this.children.delete(this.getPlatformAwareName(child.name)); } private getPlatformAwareName(name: string): string { @@ -289,9 +305,9 @@ export class ExplorerItem { if (recursive) { if (this.isDirectory && this.children) { - for (let name in this.children) { - this.children[name].updateResource(true); - } + this.children.forEach(child => { + child.updateResource(true); + }); } } } @@ -347,7 +363,7 @@ export class ExplorerItem { // The name to search is between two separators const name = path.substring(index, indexOfNextSep); - const child = this.children[this.getPlatformAwareName(name)]; + const child = this.children.get(this.getPlatformAwareName(name)); if (child) { // We found a child with the given name, search inside it diff --git a/src/vs/workbench/parts/files/electron-browser/views/explorerViewer.ts b/src/vs/workbench/parts/files/electron-browser/views/explorerViewer.ts index 60493bdca56..1d04e8a2165 100644 --- a/src/vs/workbench/parts/files/electron-browser/views/explorerViewer.ts +++ b/src/vs/workbench/parts/files/electron-browser/views/explorerViewer.ts @@ -694,13 +694,15 @@ export class FileFilter implements IFilter { } // Workaround for O(N^2) complexity (https://github.com/Microsoft/vscode/issues/9962) - let siblingNames = stat.parent && stat.parent.getChildrenNames(); - if (siblingNames && siblingNames.length > FileFilter.MAX_SIBLINGS_FILTER_THRESHOLD) { - siblingNames = void 0; + let siblingsFn: () => string[]; + let siblingCount = stat.parent && stat.parent.getChildrenCount(); + if (siblingCount && siblingCount > FileFilter.MAX_SIBLINGS_FILTER_THRESHOLD) { + siblingsFn = () => void 0; + } else { + siblingsFn = () => stat.parent ? stat.parent.getChildrenNames() : void 0; } // Hide those that match Hidden Patterns - const siblingsFn = () => siblingNames; const expression = this.hiddenExpressionPerRoot.get(stat.root.resource.toString()) || Object.create(null); if (glob.match(expression, paths.normalize(relative(stat.root.resource.fsPath, stat.resource.fsPath), true), siblingsFn)) { return false; // hidden through pattern From a396179e23ab86bc13158d732c35f6e7532f942f Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Wed, 4 Apr 2018 16:55:38 +0200 Subject: [PATCH 31/49] debt - notifications should talk about closing not disposal --- .../notification/common/notification.ts | 22 ++++---- .../mainThreadMessageService.ts | 2 +- .../browser/parts/editor/editorPart.ts | 2 +- .../notifications/notificationsCenter.ts | 4 +- .../notifications/notificationsCommands.ts | 2 +- .../notifications/notificationsToasts.ts | 4 +- .../notifications/notificationsViewer.ts | 2 +- src/vs/workbench/common/notifications.ts | 50 +++++++++---------- .../electron-browser/saveErrorHandler.ts | 8 +-- .../common/notificationService.ts | 4 +- .../progress/browser/progressService2.ts | 4 +- .../test/common/notifications.test.ts | 10 ++-- 12 files changed, 59 insertions(+), 55 deletions(-) diff --git a/src/vs/platform/notification/common/notification.ts b/src/vs/platform/notification/common/notification.ts index 7f1c3d4c507..4b1315c077f 100644 --- a/src/vs/platform/notification/common/notification.ts +++ b/src/vs/platform/notification/common/notification.ts @@ -7,7 +7,6 @@ import BaseSeverity from 'vs/base/common/severity'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; -import { IDisposable } from 'vs/base/common/lifecycle'; import { IAction } from 'vs/base/common/actions'; import { Event, Emitter } from 'vs/base/common/event'; @@ -89,12 +88,12 @@ export interface INotificationProgress { done(): void; } -export interface INotificationHandle extends IDisposable { +export interface INotificationHandle { /** - * Will be fired once the notification is disposed. + * Will be fired once the notification is closed. */ - readonly onDidDispose: Event; + readonly onDidClose: Event; /** * Allows to indicate progress on the notification even after the @@ -118,6 +117,11 @@ export interface INotificationHandle extends IDisposable { * notification is already visible. */ updateActions(actions?: INotificationActions): void; + + /** + * Hide the notification and remove it from the notification center. + */ + close(): void; } export interface IPromptChoice { @@ -199,18 +203,18 @@ export interface INotificationService { export class NoOpNotification implements INotificationHandle { readonly progress = new NoOpProgress(); - private readonly _onDidDispose: Emitter = new Emitter(); + private readonly _onDidClose: Emitter = new Emitter(); - public get onDidDispose(): Event { - return this._onDidDispose.event; + public get onDidClose(): Event { + return this._onDidClose.event; } updateSeverity(severity: Severity): void { } updateMessage(message: NotificationMessage): void { } updateActions(actions?: INotificationActions): void { } - dispose(): void { - this._onDidDispose.dispose(); + close(): void { + this._onDidClose.dispose(); } } diff --git a/src/vs/workbench/api/electron-browser/mainThreadMessageService.ts b/src/vs/workbench/api/electron-browser/mainThreadMessageService.ts index 016ebbf355a..92ca4c3cb44 100644 --- a/src/vs/workbench/api/electron-browser/mainThreadMessageService.ts +++ b/src/vs/workbench/api/electron-browser/mainThreadMessageService.ts @@ -92,7 +92,7 @@ export class MainThreadMessageService implements MainThreadMessageServiceShape { // if promise has not been resolved yet, now is the time to ensure a return value // otherwise if already resolved it means the user clicked one of the buttons - once(messageHandle.onDidDispose)(() => { + once(messageHandle.onDidClose)(() => { dispose(...primaryActions, ...secondaryActions); resolve(undefined); }); diff --git a/src/vs/workbench/browser/parts/editor/editorPart.ts b/src/vs/workbench/browser/parts/editor/editorPart.ts index 246dc838d0e..22d490f27e1 100644 --- a/src/vs/workbench/browser/parts/editor/editorPart.ts +++ b/src/vs/workbench/browser/parts/editor/editorPart.ts @@ -559,7 +559,7 @@ export class EditorPart extends Part implements IEditorPart, IEditorGroupService actions }); - once(handle.onDidDispose)(() => dispose(actions.primary)); + once(handle.onDidClose)(() => dispose(actions.primary)); } this.editorGroupsControl.updateProgress(position, ProgressState.DONE); diff --git a/src/vs/workbench/browser/parts/notifications/notificationsCenter.ts b/src/vs/workbench/browser/parts/notifications/notificationsCenter.ts index fc2ac1dcbdb..50192e62212 100644 --- a/src/vs/workbench/browser/parts/notifications/notificationsCenter.ts +++ b/src/vs/workbench/browser/parts/notifications/notificationsCenter.ts @@ -289,9 +289,9 @@ export class NotificationsCenter extends Themable { // Hide notifications center first this.hide(); - // Dispose all + // Close all while (this.model.notifications.length) { - this.model.notifications[0].dispose(); + this.model.notifications[0].close(); } } } diff --git a/src/vs/workbench/browser/parts/notifications/notificationsCommands.ts b/src/vs/workbench/browser/parts/notifications/notificationsCommands.ts index 469360901d6..858f3bbe848 100644 --- a/src/vs/workbench/browser/parts/notifications/notificationsCommands.ts +++ b/src/vs/workbench/browser/parts/notifications/notificationsCommands.ts @@ -115,7 +115,7 @@ export function registerNotificationCommands(center: INotificationsCenterControl handler: (accessor, args?: any) => { const notification = getNotificationFromContext(accessor.get(IListService), args); if (notification) { - notification.dispose(); + notification.close(); } } }); diff --git a/src/vs/workbench/browser/parts/notifications/notificationsToasts.ts b/src/vs/workbench/browser/parts/notifications/notificationsToasts.ts index 8c1fa51b1b3..f52e60460aa 100644 --- a/src/vs/workbench/browser/parts/notifications/notificationsToasts.ts +++ b/src/vs/workbench/browser/parts/notifications/notificationsToasts.ts @@ -168,8 +168,8 @@ export class NotificationsToasts extends Themable { } })); - // Remove when item gets disposed - once(item.onDidDispose)(() => { + // Remove when item gets closed + once(item.onDidClose)(() => { this.removeToast(item); }); diff --git a/src/vs/workbench/browser/parts/notifications/notificationsViewer.ts b/src/vs/workbench/browser/parts/notifications/notificationsViewer.ts index 124588a01cf..9cc3d416e22 100644 --- a/src/vs/workbench/browser/parts/notifications/notificationsViewer.ts +++ b/src/vs/workbench/browser/parts/notifications/notificationsViewer.ts @@ -442,7 +442,7 @@ export class NotificationTemplateRenderer { this.actionRunner.run(action, notification); // Hide notification - notification.dispose(); + notification.close(); })); this.inputDisposeables.push(attachButtonStyler(button, this.themeService)); diff --git a/src/vs/workbench/common/notifications.ts b/src/vs/workbench/common/notifications.ts index 88fd2bc2e44..d27b8209c2b 100644 --- a/src/vs/workbench/common/notifications.ts +++ b/src/vs/workbench/common/notifications.ts @@ -44,21 +44,21 @@ export interface INotificationChangeEvent { } export class NotificationHandle implements INotificationHandle { - private readonly _onDidDispose: Emitter = new Emitter(); + private readonly _onDidClose: Emitter = new Emitter(); - constructor(private item: INotificationViewItem, private disposeItem: (item: INotificationViewItem) => void) { + constructor(private item: INotificationViewItem, private closeItem: (item: INotificationViewItem) => void) { this.registerListeners(); } private registerListeners(): void { - once(this.item.onDidDispose)(() => { - this._onDidDispose.fire(); - this._onDidDispose.dispose(); + once(this.item.onDidClose)(() => { + this._onDidClose.fire(); + this._onDidClose.dispose(); }); } - public get onDidDispose(): Event { - return this._onDidDispose.event; + public get onDidClose(): Event { + return this._onDidClose.event; } public get progress(): INotificationProgress { @@ -77,9 +77,9 @@ export class NotificationHandle implements INotificationHandle { this.item.updateActions(actions); } - public dispose(): void { - this.disposeItem(this.item); - this._onDidDispose.dispose(); + public close(): void { + this.closeItem(this.item); + this._onDidClose.dispose(); } } @@ -117,7 +117,7 @@ export class NotificationsModel implements INotificationsModel { // Deduplicate const duplicate = this.findNotification(item); if (duplicate) { - duplicate.dispose(); + duplicate.close(); } // Add to list as first entry @@ -127,15 +127,15 @@ export class NotificationsModel implements INotificationsModel { this._onDidNotificationChange.fire({ item, index: 0, kind: NotificationChangeType.ADD }); // Wrap into handle - return new NotificationHandle(item, item => this.disposeItem(item)); + return new NotificationHandle(item, item => this.closeItem(item)); } - private disposeItem(item: INotificationViewItem): void { + private closeItem(item: INotificationViewItem): void { const liveItem = this.findNotification(item); if (liveItem && liveItem !== item) { - liveItem.dispose(); // item could have been replaced with another one, make sure to dispose the live item + liveItem.close(); // item could have been replaced with another one, make sure to close the live item } else { - item.dispose(); // otherwise just dispose the item that was passed in + item.close(); // otherwise just close the item that was passed in } } @@ -174,7 +174,7 @@ export class NotificationsModel implements INotificationsModel { } }); - once(item.onDidDispose)(() => { + once(item.onDidClose)(() => { itemExpansionChangeListener.dispose(); itemLabelChangeListener.dispose(); @@ -204,7 +204,7 @@ export interface INotificationViewItem { readonly canCollapse: boolean; readonly onDidExpansionChange: Event; - readonly onDidDispose: Event; + readonly onDidClose: Event; readonly onDidLabelChange: Event; expand(): void; @@ -217,7 +217,7 @@ export interface INotificationViewItem { updateMessage(message: NotificationMessage): void; updateActions(actions?: INotificationActions): void; - dispose(): void; + close(): void; equals(item: INotificationViewItem); } @@ -359,7 +359,7 @@ export class NotificationViewItem implements INotificationViewItem { private _progress: NotificationViewItemProgress; private readonly _onDidExpansionChange: Emitter; - private readonly _onDidDispose: Emitter; + private readonly _onDidClose: Emitter; private readonly _onDidLabelChange: Emitter; public static create(notification: INotification): INotificationViewItem { @@ -435,8 +435,8 @@ export class NotificationViewItem implements INotificationViewItem { this._onDidLabelChange = new Emitter(); this.toDispose.push(this._onDidLabelChange); - this._onDidDispose = new Emitter(); - this.toDispose.push(this._onDidDispose); + this._onDidClose = new Emitter(); + this.toDispose.push(this._onDidClose); } private setActions(actions: INotificationActions): void { @@ -464,8 +464,8 @@ export class NotificationViewItem implements INotificationViewItem { return this._onDidLabelChange.event; } - public get onDidDispose(): Event { - return this._onDidDispose.event; + public get onDidClose(): Event { + return this._onDidClose.event; } public get canCollapse(): boolean { @@ -556,8 +556,8 @@ export class NotificationViewItem implements INotificationViewItem { } } - public dispose(): void { - this._onDidDispose.fire(); + public close(): void { + this._onDidClose.fire(); this.toDispose = dispose(this.toDispose); } diff --git a/src/vs/workbench/parts/files/electron-browser/saveErrorHandler.ts b/src/vs/workbench/parts/files/electron-browser/saveErrorHandler.ts index 56bf6d7a656..8ddc5c94b45 100644 --- a/src/vs/workbench/parts/files/electron-browser/saveErrorHandler.ts +++ b/src/vs/workbench/parts/files/electron-browser/saveErrorHandler.ts @@ -102,7 +102,7 @@ export class SaveErrorHandler implements ISaveErrorHandler, IWorkbenchContributi private onFileSavedOrReverted(resource: URI): void { const messageHandle = this.messages.get(resource); if (messageHandle) { - messageHandle.dispose(); + messageHandle.close(); this.messages.delete(resource); } } @@ -179,7 +179,7 @@ export class SaveErrorHandler implements ISaveErrorHandler, IWorkbenchContributi // Show message and keep function to hide in case the file gets saved/reverted const handle = this.notificationService.notify({ severity: Severity.Error, message, actions }); - once(handle.onDidDispose)(() => dispose(...actions.primary, ...actions.secondary)); + once(handle.onDidClose)(() => dispose(...actions.primary, ...actions.secondary)); this.messages.set(model.getResource(), handle); } @@ -193,7 +193,7 @@ export class SaveErrorHandler implements ISaveErrorHandler, IWorkbenchContributi const pendingResolveSaveConflictMessages: INotificationHandle[] = []; function clearPendingResolveSaveConflictMessages(): void { while (pendingResolveSaveConflictMessages.length > 0) { - pendingResolveSaveConflictMessages.pop().dispose(); + pendingResolveSaveConflictMessages.pop().close(); } } @@ -265,7 +265,7 @@ class ResolveSaveConflictAction extends Action { actions.secondary.push(this.instantiationService.createInstance(DoNotShowResolveConflictLearnMoreAction)); const handle = this.notificationService.notify({ severity: Severity.Info, message: conflictEditorHelp, actions }); - once(handle.onDidDispose)(() => dispose(...actions.primary, ...actions.secondary)); + once(handle.onDidClose)(() => dispose(...actions.primary, ...actions.secondary)); pendingResolveSaveConflictMessages.push(handle); }); } diff --git a/src/vs/workbench/services/notification/common/notificationService.ts b/src/vs/workbench/services/notification/common/notificationService.ts index d2cd39a8132..52fbfe0638c 100644 --- a/src/vs/workbench/services/notification/common/notificationService.ts +++ b/src/vs/workbench/services/notification/common/notificationService.ts @@ -80,7 +80,7 @@ export class NotificationService implements INotificationService { // Close notification unless we are told to keep open if (!choice.keepOpen) { - handle.dispose(); + handle.close(); } return TPromise.as(void 0); @@ -96,7 +96,7 @@ export class NotificationService implements INotificationService { // Show notification with actions handle = this.notify({ severity, message, actions }); - once(handle.onDidDispose)(() => { + once(handle.onDidClose)(() => { // Cleanup when notification gets disposed dispose(...actions.primary, ...actions.secondary); diff --git a/src/vs/workbench/services/progress/browser/progressService2.ts b/src/vs/workbench/services/progress/browser/progressService2.ts index 7f58433b7c2..876dd549910 100644 --- a/src/vs/workbench/services/progress/browser/progressService2.ts +++ b/src/vs/workbench/services/progress/browser/progressService2.ts @@ -203,7 +203,7 @@ export class ProgressService2 implements IProgressService2 { updateProgress(handle, increment); - once(handle.onDidDispose)(() => { + once(handle.onDidClose)(() => { dispose(toDispose); }); @@ -247,7 +247,7 @@ export class ProgressService2 implements IProgressService2 { // Show progress for at least 800ms and then hide once done or canceled always(TPromise.join([TPromise.timeout(800), p]), () => { if (handle) { - handle.dispose(); + handle.close(); } }); diff --git a/src/vs/workbench/test/common/notifications.test.ts b/src/vs/workbench/test/common/notifications.test.ts index 533cc9ae266..fc7699a81f1 100644 --- a/src/vs/workbench/test/common/notifications.test.ts +++ b/src/vs/workbench/test/common/notifications.test.ts @@ -96,11 +96,11 @@ suite('Notifications', () => { assert.equal(called, 1); called = 0; - item1.onDidDispose(() => { + item1.onDidClose(() => { called++; }); - item1.dispose(); + item1.close(); assert.equal(called, 1); // Error with Action @@ -157,11 +157,11 @@ suite('Notifications', () => { assert.equal(model.notifications.length, 3); let called = 0; - item1Handle.onDidDispose(() => { + item1Handle.onDidClose(() => { called++; }); - item1Handle.dispose(); + item1Handle.close(); assert.equal(called, 1); assert.equal(model.notifications.length, 2); assert.equal(lastEvent.item.severity, item1.severity); @@ -176,7 +176,7 @@ suite('Notifications', () => { assert.equal(lastEvent.index, 0); assert.equal(lastEvent.kind, NotificationChangeType.ADD); - item2Handle.dispose(); + item2Handle.close(); assert.equal(model.notifications.length, 1); assert.equal(lastEvent.item.severity, item2Duplicate.severity); assert.equal(lastEvent.item.message.value, item2Duplicate.message); From a2765bf43a5b196b437c6e6e803430ae336e719c Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Wed, 4 Apr 2018 17:54:48 +0200 Subject: [PATCH 32/49] Implement #46750 - Introduce Application scope - Update configuration service and models to respect the application scope - Update settings editor to respect the application scope --- .../common/configurationRegistry.ts | 42 +++++++--- .../preferences/browser/preferencesEditor.ts | 15 +++- .../browser/preferencesRenderers.ts | 44 +---------- .../preferences/browser/preferencesService.ts | 79 ++++++++++++------- .../preferences/common/preferencesModels.ts | 16 ++-- .../common/configurationModels.ts | 7 +- .../configuration/node/configuration.ts | 2 +- .../node/configurationEditingService.ts | 15 ++++ .../node/configurationService.ts | 32 ++++++-- .../test/common/configurationModels.test.ts | 6 +- .../test/node/configurationService.test.ts | 43 +++++++++- 11 files changed, 194 insertions(+), 107 deletions(-) diff --git a/src/vs/platform/configuration/common/configurationRegistry.ts b/src/vs/platform/configuration/common/configurationRegistry.ts index 1db8bcc8ff4..1227443eaf4 100644 --- a/src/vs/platform/configuration/common/configurationRegistry.ts +++ b/src/vs/platform/configuration/common/configurationRegistry.ts @@ -11,7 +11,6 @@ import { Registry } from 'vs/platform/registry/common/platform'; import * as types from 'vs/base/common/types'; import * as strings from 'vs/base/common/strings'; import { IJSONContributionRegistry, Extensions as JSONExtensions } from 'vs/platform/jsonschemas/common/jsonContributionRegistry'; -import { deepClone } from 'vs/base/common/objects'; export const Extensions = { Configuration: 'base.contributions.configuration' @@ -63,8 +62,9 @@ export interface IConfigurationRegistry { } export enum ConfigurationScope { - WINDOW = 1, - RESOURCE + APPLICATION = 1, + WINDOW, + RESOURCE, } export interface IConfigurationPropertySchema extends IJSONSchema { @@ -93,8 +93,10 @@ export interface IDefaultConfigurationExtension { defaults: { [key: string]: {} }; } -export const settingsSchema: IJSONSchema = { properties: {}, patternProperties: {}, additionalProperties: false, errorMessage: 'Unknown configuration setting' }; -export const resourceSettingsSchema: IJSONSchema = { properties: {}, patternProperties: {}, additionalProperties: false, errorMessage: 'Unknown configuration setting' }; +export const allSettings: { properties: {}, patternProperties: {} } = { properties: {}, patternProperties: {} }; +export const applicationSettings: { properties: {}, patternProperties: {} } = { properties: {}, patternProperties: {} }; +export const windowSettings: { properties: {}, patternProperties: {} } = { properties: {}, patternProperties: {} }; +export const resourceSettings: { properties: {}, patternProperties: {} } = { properties: {}, patternProperties: {} }; export const editorConfigurationSchemaId = 'vscode://schemas/settings/editor'; const contributionRegistry = Registry.as(JSONExtensions.JSONContribution); @@ -239,10 +241,17 @@ class ConfigurationRegistry implements IConfigurationRegistry { let properties = configuration.properties; if (properties) { for (let key in properties) { - settingsSchema.properties[key] = properties[key]; - resourceSettingsSchema.properties[key] = deepClone(properties[key]); - if (properties[key].scope !== ConfigurationScope.RESOURCE) { - resourceSettingsSchema.properties[key].doNotSuggest = true; + allSettings.properties[key] = properties[key]; + switch (properties[key].scope) { + case ConfigurationScope.APPLICATION: + applicationSettings.properties[key] = properties[key]; + break; + case ConfigurationScope.WINDOW: + windowSettings.properties[key] = properties[key]; + break; + case ConfigurationScope.RESOURCE: + resourceSettings.properties[key] = properties[key]; + break; } } } @@ -262,7 +271,7 @@ class ConfigurationRegistry implements IConfigurationRegistry { } private updateOverridePropertyPatternKey(): void { - let patternProperties: IJSONSchema = settingsSchema.patternProperties[this.overridePropertyPattern]; + let patternProperties: IJSONSchema = allSettings.patternProperties[this.overridePropertyPattern]; if (!patternProperties) { patternProperties = { type: 'object', @@ -271,11 +280,18 @@ class ConfigurationRegistry implements IConfigurationRegistry { $ref: editorConfigurationSchemaId }; } - delete settingsSchema.patternProperties[this.overridePropertyPattern]; + + delete allSettings.patternProperties[this.overridePropertyPattern]; + delete applicationSettings.patternProperties[this.overridePropertyPattern]; + delete windowSettings.patternProperties[this.overridePropertyPattern]; + delete resourceSettings.patternProperties[this.overridePropertyPattern]; + this.computeOverridePropertyPattern(); - settingsSchema.patternProperties[this.overridePropertyPattern] = patternProperties; - resourceSettingsSchema.patternProperties[this.overridePropertyPattern] = patternProperties; + allSettings.patternProperties[this.overridePropertyPattern] = patternProperties; + applicationSettings.patternProperties[this.overridePropertyPattern] = patternProperties; + windowSettings.patternProperties[this.overridePropertyPattern] = patternProperties; + resourceSettings.patternProperties[this.overridePropertyPattern] = patternProperties; } private update(configuration: IConfigurationNode): void { diff --git a/src/vs/workbench/parts/preferences/browser/preferencesEditor.ts b/src/vs/workbench/parts/preferences/browser/preferencesEditor.ts index 70c4761e517..fb19dd05d4d 100644 --- a/src/vs/workbench/parts/preferences/browser/preferencesEditor.ts +++ b/src/vs/workbench/parts/preferences/browser/preferencesEditor.ts @@ -57,7 +57,6 @@ import { Registry } from 'vs/platform/registry/common/platform'; import { MessageController } from 'vs/editor/contrib/message/messageController'; import { ConfigurationTarget } from 'vs/platform/configuration/common/configuration'; import { IHashService } from 'vs/workbench/services/hash/common/hashService'; -import { ConfigurationScope } from 'vs/platform/configuration/common/configurationRegistry'; import { IStringDictionary } from 'vs/base/common/collections'; import { IProgressService } from 'vs/platform/progress/common/progress'; import { ILogService } from 'vs/platform/log/common/log'; @@ -848,11 +847,23 @@ class SideBySidePreferencesWidget extends Widget { return TPromise.join([this.updateInput(this.defaultPreferencesEditor, defaultPreferencesEditorInput, DefaultSettingsEditorContribution.ID, editablePreferencesEditorInput.getResource(), options), this.updateInput(this.editablePreferencesEditor, editablePreferencesEditorInput, SettingsEditorContribution.ID, defaultPreferencesEditorInput.getResource(), options)]) .then(([defaultPreferencesRenderer, editablePreferencesRenderer]) => { - this.defaultPreferencesHeader.textContent = defaultPreferencesRenderer && (defaultPreferencesRenderer.preferencesModel).configurationScope === ConfigurationScope.RESOURCE ? nls.localize('defaultFolderSettings', "Default Folder Settings") : nls.localize('defaultSettings', "Default Settings"); + this.defaultPreferencesHeader.textContent = defaultPreferencesRenderer && this.getDefaultPreferencesHeaderText((defaultPreferencesRenderer.preferencesModel).target); return { defaultPreferencesRenderer, editablePreferencesRenderer }; }); } + private getDefaultPreferencesHeaderText(target: ConfigurationTarget): string { + switch (target) { + case ConfigurationTarget.USER: + return nls.localize('defaultUserSettings', "Default User Settings"); + case ConfigurationTarget.WORKSPACE: + return nls.localize('defaultWorkspaceSettings', "Default Workspace Settings"); + case ConfigurationTarget.WORKSPACE_FOLDER: + return nls.localize('defaultFolderSettings', "Default Folder Settings"); + } + return ''; + } + public setResultCount(settingsTarget: SettingsTarget, count: number): void { this.settingsTargetsWidget.setResultCount(settingsTarget, count); } diff --git a/src/vs/workbench/parts/preferences/browser/preferencesRenderers.ts b/src/vs/workbench/parts/preferences/browser/preferencesRenderers.ts index 20e9351d138..6f134ab7bc9 100644 --- a/src/vs/workbench/parts/preferences/browser/preferencesRenderers.ts +++ b/src/vs/workbench/parts/preferences/browser/preferencesRenderers.ts @@ -29,7 +29,6 @@ import { IMarkerService, IMarkerData, MarkerSeverity } from 'vs/platform/markers import { ICursorPositionChangedEvent } from 'vs/editor/common/controller/cursorEvents'; import { ModelDecorationOptions } from 'vs/editor/common/model/textModel'; import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; -import { MarkdownString } from 'vs/base/common/htmlContent'; import { overrideIdentifierFromKey, IConfigurationService, ConfigurationTarget } from 'vs/platform/configuration/common/configuration'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { ITextModel, IModelDeltaDecoration, TrackedRangeStickiness } from 'vs/editor/common/model'; @@ -273,7 +272,7 @@ export class DefaultSettingsRenderer extends Disposable implements IPreferencesR ) { super(); this.settingHighlighter = this._register(instantiationService.createInstance(SettingHighlighter, editor, this._onFocusPreference, this._onClearFocusPreference)); - this.settingsHeaderRenderer = this._register(instantiationService.createInstance(DefaultSettingsHeaderRenderer, editor, preferencesModel.configurationScope)); + this.settingsHeaderRenderer = this._register(instantiationService.createInstance(DefaultSettingsHeaderRenderer, editor)); this.settingsGroupTitleRenderer = this._register(instantiationService.createInstance(SettingsGroupTitleRenderer, editor)); this.filteredMatchesRenderer = this._register(instantiationService.createInstance(FilteredMatchesRenderer, editor)); this.editSettingActionRenderer = this._register(instantiationService.createInstance(EditSettingRenderer, editor, preferencesModel, this.settingHighlighter)); @@ -467,7 +466,7 @@ class DefaultSettingsHeaderRenderer extends Disposable { private settingsHeaderWidget: DefaultSettingsHeaderWidget; public readonly onClick: Event; - constructor(editor: ICodeEditor, scope: ConfigurationScope) { + constructor(editor: ICodeEditor) { super(); this.settingsHeaderWidget = this._register(new DefaultSettingsHeaderWidget(editor, '')); this.onClick = this.settingsHeaderWidget.onClick; @@ -1312,14 +1311,12 @@ class SettingHighlighter extends Disposable { class UnsupportedSettingsRenderer extends Disposable { - private decorationIds: string[] = []; private renderingDelayer: Delayer = new Delayer(200); constructor( private editor: ICodeEditor, private settingsEditorModel: SettingsEditorModel, - @IMarkerService private markerService: IMarkerService, - @IEnvironmentService private environmentService: IEnvironmentService + @IMarkerService private markerService: IMarkerService ) { super(); this._register(this.editor.getModel().onDidChangeContent(() => this.renderingDelayer.trigger(() => this.render()))); @@ -1327,7 +1324,6 @@ class UnsupportedSettingsRenderer extends Disposable { public render(): void { const configurationRegistry = Registry.as(ConfigurationExtensions.Configuration).getConfigurationProperties(); - const ranges: IRange[] = []; const markerData: IMarkerData[] = []; for (const settingsGroup of this.settingsEditorModel.settingsGroups) { for (const section of settingsGroup.sections) { @@ -1345,17 +1341,6 @@ class UnsupportedSettingsRenderer extends Disposable { }); } } - if (this.settingsEditorModel.configurationTarget === ConfigurationTarget.WORKSPACE_FOLDER) { - // Dim and show information for window settings - if (configurationRegistry[setting.key] && configurationRegistry[setting.key].scope === ConfigurationScope.WINDOW) { - ranges.push({ - startLineNumber: setting.keyRange.startLineNumber, - startColumn: setting.keyRange.startColumn - 1, - endLineNumber: setting.valueRange.endLineNumber, - endColumn: setting.valueRange.endColumn - }); - } - } } } } @@ -1364,14 +1349,6 @@ class UnsupportedSettingsRenderer extends Disposable { } else { this.markerService.remove('preferencesEditor', [this.settingsEditorModel.uri]); } - this.decorationIds = this.editor.deltaDecorations(this.decorationIds, ranges.map(range => this.createDecoration(range, this.editor.getModel()))); - } - - private createDecoration(range: IRange, model: ITextModel): IModelDeltaDecoration { - return { - range, - options: !this.environmentService.isBuilt || this.environmentService.isExtensionDevelopment ? UnsupportedSettingsRenderer._DIM_CONFIGUARATION_DEV_MODE : UnsupportedSettingsRenderer._DIM_CONFIGUARATION_ - }; } private getMarkerMessage(settingKey: string): string { @@ -1385,23 +1362,8 @@ class UnsupportedSettingsRenderer extends Disposable { public dispose(): void { this.markerService.remove('preferencesEditor', [this.settingsEditorModel.uri]); - this.decorationIds = this.editor.deltaDecorations(this.decorationIds, []); super.dispose(); } - - private static readonly _DIM_CONFIGUARATION_ = ModelDecorationOptions.register({ - stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges, - inlineClassName: 'dim-configuration', - beforeContentClassName: 'unsupportedWorkbenhSettingInfo', - hoverMessage: new MarkdownString().appendText(nls.localize('unsupportedWorkbenchSetting', "This setting cannot be applied now. It will be applied when you open this folder directly.")) - }); - - private static readonly _DIM_CONFIGUARATION_DEV_MODE = ModelDecorationOptions.register({ - stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges, - inlineClassName: 'dim-configuration', - beforeContentClassName: 'unsupportedWorkbenhSettingInfo', - hoverMessage: new MarkdownString().appendText(nls.localize('unsupportedWorkbenchSettingDevMode', "This setting cannot be applied now. It will be applied if you define it's scope as 'resource' while registering, or when you open this folder directly.")) - }); } class WorkspaceConfigurationRenderer extends Disposable { diff --git a/src/vs/workbench/parts/preferences/browser/preferencesService.ts b/src/vs/workbench/parts/preferences/browser/preferencesService.ts index c569308d4e8..f453fce8e0d 100644 --- a/src/vs/workbench/parts/preferences/browser/preferencesService.ts +++ b/src/vs/workbench/parts/preferences/browser/preferencesService.ts @@ -34,7 +34,6 @@ import { Position, IPosition } from 'vs/editor/common/core/position'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IModelService } from 'vs/editor/common/services/modelService'; import { IJSONEditingService } from 'vs/workbench/services/configuration/common/jsonEditing'; -import { ConfigurationScope } from 'vs/platform/configuration/common/configurationRegistry'; import { ConfigurationTarget } from 'vs/platform/configuration/common/configuration'; import { IModeService } from 'vs/editor/common/services/modeService'; import { parse } from 'vs/base/common/json'; @@ -51,10 +50,12 @@ export class PreferencesService extends Disposable implements IPreferencesServic private readonly _onDispose: Emitter = new Emitter(); - private _defaultSettingsUriCounter = 0; - private _defaultSettingsContentModel: DefaultSettings; - private _defaultResourceSettingsUriCounter = 0; - private _defaultResourceSettingsContentModel: DefaultSettings; + private _defaultUserSettingsUriCounter = 0; + private _defaultUserSettingsContentModel: DefaultSettings; + private _defaultWorkspaceSettingsUriCounter = 0; + private _defaultWorkspaceSettingsContentModel: DefaultSettings; + private _defaultFolderSettingsUriCounter = 0; + private _defaultFolderSettingsContentModel: DefaultSettings; constructor( @IWorkbenchEditorService private editorService: IWorkbenchEditorService, @@ -108,9 +109,9 @@ export class PreferencesService extends Disposable implements IPreferencesServic } resolveModel(uri: URI): TPromise { - if (this.isDefaultSettingsResource(uri) || this.isDefaultResourceSettingsResource(uri)) { + if (this.isDefaultSettingsResource(uri)) { - const scope = this.isDefaultSettingsResource(uri) ? ConfigurationScope.WINDOW : ConfigurationScope.RESOURCE; + const target = this.getConfigurationTargetFromDefaultSettingsResource(uri); const mode = this.modeService.getOrCreateMode('jsonc'); const model = this._register(this.modelService.createModel('', mode, uri)); @@ -122,7 +123,7 @@ export class PreferencesService extends Disposable implements IPreferencesServic // model has not been given out => nothing to do return; } - defaultSettings = this.getDefaultSettings(scope); + defaultSettings = this.getDefaultSettings(target); this.modelService.updateModel(model, defaultSettings.parse()); defaultSettings._onDidChange.fire(); } @@ -130,7 +131,7 @@ export class PreferencesService extends Disposable implements IPreferencesServic // Check if Default settings is already created and updated in above promise if (!defaultSettings) { - defaultSettings = this.getDefaultSettings(scope); + defaultSettings = this.getDefaultSettings(target); this.modelService.updateModel(model, defaultSettings.parse()); } @@ -138,7 +139,7 @@ export class PreferencesService extends Disposable implements IPreferencesServic } if (this.defaultSettingsRawResource.toString() === uri.toString()) { - let defaultSettings: DefaultSettings = this.getDefaultSettings(ConfigurationScope.WINDOW); + let defaultSettings: DefaultSettings = this.getDefaultSettings(ConfigurationTarget.USER); const mode = this.modeService.getOrCreateMode('jsonc'); const model = this._register(this.modelService.createModel(defaultSettings.raw, mode, uri)); return TPromise.as(model); @@ -155,7 +156,7 @@ export class PreferencesService extends Disposable implements IPreferencesServic } createPreferencesEditorModel(uri: URI): TPromise> { - if (this.isDefaultSettingsResource(uri) || this.isDefaultResourceSettingsResource(uri)) { + if (this.isDefaultSettingsResource(uri)) { return this.createDefaultSettingsEditorModel(uri); } @@ -271,20 +272,34 @@ export class PreferencesService extends Disposable implements IPreferencesServic }); } + private getConfigurationTargetFromDefaultSettingsResource(uri: URI) { + return this.isDefaultWorkspaceSettingsResource(uri) ? ConfigurationTarget.WORKSPACE : this.isDefaultFolderSettingsResource(uri) ? ConfigurationTarget.WORKSPACE_FOLDER : ConfigurationTarget.USER; + } + private isDefaultSettingsResource(uri: URI): boolean { + return this.isDefaultUserSettingsResource(uri) || this.isDefaultWorkspaceSettingsResource(uri) || this.isDefaultFolderSettingsResource(uri); + } + + private isDefaultUserSettingsResource(uri: URI): boolean { return uri.authority === 'defaultsettings' && uri.scheme === network.Schemas.vscode && !!uri.path.match(/\/(\d+\/)?settings\.json$/); } - private isDefaultResourceSettingsResource(uri: URI): boolean { + private isDefaultWorkspaceSettingsResource(uri: URI): boolean { + return uri.authority === 'defaultsettings' && uri.scheme === network.Schemas.vscode && !!uri.path.match(/\/(\d+\/)?workspaceSettings\.json$/); + } + + private isDefaultFolderSettingsResource(uri: URI): boolean { return uri.authority === 'defaultsettings' && uri.scheme === network.Schemas.vscode && !!uri.path.match(/\/(\d+\/)?resourceSettings\.json$/); } private getDefaultSettingsResource(configurationTarget: ConfigurationTarget): URI { - if (configurationTarget === ConfigurationTarget.WORKSPACE_FOLDER) { - return URI.from({ scheme: network.Schemas.vscode, authority: 'defaultsettings', path: `/${this._defaultResourceSettingsUriCounter++}/resourceSettings.json` }); - } else { - return URI.from({ scheme: network.Schemas.vscode, authority: 'defaultsettings', path: `/${this._defaultSettingsUriCounter++}/settings.json` }); + switch (configurationTarget) { + case ConfigurationTarget.WORKSPACE: + return URI.from({ scheme: network.Schemas.vscode, authority: 'defaultsettings', path: `/${this._defaultWorkspaceSettingsUriCounter++}/workspaceSettings.json` }); + case ConfigurationTarget.WORKSPACE_FOLDER: + return URI.from({ scheme: network.Schemas.vscode, authority: 'defaultsettings', path: `/${this._defaultFolderSettingsUriCounter++}/resourceSettings.json` }); } + return URI.from({ scheme: network.Schemas.vscode, authority: 'defaultsettings', path: `/${this._defaultUserSettingsUriCounter++}/settings.json` }); } private getPreferencesEditorInputName(target: ConfigurationTarget, resource: URI): string { @@ -314,24 +329,28 @@ export class PreferencesService extends Disposable implements IPreferencesServic private createDefaultSettingsEditorModel(defaultSettingsUri: URI): TPromise { return this.textModelResolverService.createModelReference(defaultSettingsUri) .then(reference => { - const scope = this.isDefaultSettingsResource(defaultSettingsUri) ? ConfigurationScope.WINDOW : ConfigurationScope.RESOURCE; - return this.instantiationService.createInstance(DefaultSettingsEditorModel, defaultSettingsUri, reference, scope, this.getDefaultSettings(scope)); + const target = this.getConfigurationTargetFromDefaultSettingsResource(defaultSettingsUri); + return this.instantiationService.createInstance(DefaultSettingsEditorModel, defaultSettingsUri, reference, this.getDefaultSettings(target)); }); } - private getDefaultSettings(scope: ConfigurationScope): DefaultSettings { - switch (scope) { - case ConfigurationScope.WINDOW: - if (!this._defaultSettingsContentModel) { - this._defaultSettingsContentModel = new DefaultSettings(this.getMostCommonlyUsedSettings(), scope); - } - return this._defaultSettingsContentModel; - case ConfigurationScope.RESOURCE: - if (!this._defaultResourceSettingsContentModel) { - this._defaultResourceSettingsContentModel = new DefaultSettings(this.getMostCommonlyUsedSettings(), scope); - } - return this._defaultResourceSettingsContentModel; + private getDefaultSettings(target: ConfigurationTarget): DefaultSettings { + if (target === ConfigurationTarget.WORKSPACE) { + if (!this._defaultWorkspaceSettingsContentModel) { + this._defaultWorkspaceSettingsContentModel = new DefaultSettings(this.getMostCommonlyUsedSettings(), target); + } + return this._defaultWorkspaceSettingsContentModel; } + if (target === ConfigurationTarget.WORKSPACE_FOLDER) { + if (!this._defaultFolderSettingsContentModel) { + this._defaultFolderSettingsContentModel = new DefaultSettings(this.getMostCommonlyUsedSettings(), target); + } + return this._defaultFolderSettingsContentModel; + } + if (!this._defaultUserSettingsContentModel) { + this._defaultUserSettingsContentModel = new DefaultSettings(this.getMostCommonlyUsedSettings(), target); + } + return this._defaultUserSettingsContentModel; } private getEditableSettingsURI(configurationTarget: ConfigurationTarget, resource?: URI): URI { diff --git a/src/vs/workbench/parts/preferences/common/preferencesModels.ts b/src/vs/workbench/parts/preferences/common/preferencesModels.ts index 3119e7a4b16..8decbe43134 100644 --- a/src/vs/workbench/parts/preferences/common/preferencesModels.ts +++ b/src/vs/workbench/parts/preferences/common/preferencesModels.ts @@ -403,7 +403,7 @@ export class DefaultSettings extends Disposable { constructor( private _mostCommonlyUsedSettingsKeys: string[], - readonly configurationScope: ConfigurationScope, + readonly target: ConfigurationTarget, ) { super(); } @@ -555,10 +555,13 @@ export class DefaultSettings extends Disposable { } private matchesScope(property: IConfigurationNode): boolean { - if (this.configurationScope === ConfigurationScope.WINDOW) { - return true; + if (this.target === ConfigurationTarget.WORKSPACE_FOLDER) { + return property.scope === ConfigurationScope.RESOURCE; } - return property.scope === this.configurationScope; + if (this.target === ConfigurationTarget.WORKSPACE) { + return property.scope === ConfigurationScope.WINDOW || property.scope === ConfigurationScope.RESOURCE; + } + return true; } private compareConfigurationNodes(c1: IConfigurationNode, c2: IConfigurationNode): number { @@ -603,7 +606,6 @@ export class DefaultSettingsEditorModel extends AbstractSettingsModel implements constructor( private _uri: URI, reference: IReference, - readonly configurationScope: ConfigurationScope, private readonly defaultSettings: DefaultSettings ) { super(); @@ -617,6 +619,10 @@ export class DefaultSettingsEditorModel extends AbstractSettingsModel implements return this._uri; } + public get target(): ConfigurationTarget { + return this.defaultSettings.target; + } + public get settingsGroups(): ISettingsGroup[] { return this.defaultSettings.settingsGroups; } diff --git a/src/vs/workbench/services/configuration/common/configurationModels.ts b/src/vs/workbench/services/configuration/common/configurationModels.ts index 27be593edfe..9550abcf826 100644 --- a/src/vs/workbench/services/configuration/common/configurationModels.ts +++ b/src/vs/workbench/services/configuration/common/configurationModels.ts @@ -37,7 +37,7 @@ export class WorkspaceConfigurationModelParser extends ConfigurationModelParser constructor(name: string) { super(name); - this._settingsModelParser = new FolderSettingsModelParser(name); + this._settingsModelParser = new FolderSettingsModelParser(name, [ConfigurationScope.WINDOW, ConfigurationScope.RESOURCE]); this._launchModel = new ConfigurationModel(); } @@ -98,7 +98,7 @@ export class FolderSettingsModelParser extends ConfigurationModelParser { private _raw: any; private _settingsModel: SettingsModel; - constructor(name: string, private configurationScope?: ConfigurationScope) { + constructor(name: string, private scopes: ConfigurationScope[]) { super(name); } @@ -125,7 +125,8 @@ export class FolderSettingsModelParser extends ConfigurationModelParser { const configurationProperties = Registry.as(Extensions.Configuration).getConfigurationProperties(); for (let key in rawSettings) { if (this.isNotExecutable(key, configurationProperties)) { - if (this.configurationScope === void 0 || this.getScope(key, configurationProperties) === this.configurationScope) { + const scope = this.getScope(key, configurationProperties); + if (this.scopes.indexOf(scope) !== -1) { rawWorkspaceSettings[key] = rawSettings[key]; } } else { diff --git a/src/vs/workbench/services/configuration/node/configuration.ts b/src/vs/workbench/services/configuration/node/configuration.ts index 70082cd28ba..0d7fbcbb8eb 100644 --- a/src/vs/workbench/services/configuration/node/configuration.ts +++ b/src/vs/workbench/services/configuration/node/configuration.ts @@ -187,7 +187,7 @@ export class FolderConfiguration extends Disposable { constructor(private folder: URI, private configFolderRelativePath: string, workbenchState: WorkbenchState) { super(); - this._folderSettingsModelParser = new FolderSettingsModelParser(FOLDER_SETTINGS_PATH, WorkbenchState.WORKSPACE === workbenchState ? ConfigurationScope.RESOURCE : void 0); + this._folderSettingsModelParser = new FolderSettingsModelParser(FOLDER_SETTINGS_PATH, WorkbenchState.WORKSPACE === workbenchState ? [ConfigurationScope.RESOURCE] : [ConfigurationScope.WINDOW, ConfigurationScope.RESOURCE]); this.workspaceFilePathToConfiguration = Object.create(null); this.reloadConfigurationScheduler = this._register(new RunOnceScheduler(() => this.loadConfiguration().then(configuration => this.reloadConfigurationEventEmitter.fire(configuration), errors.onUnexpectedError), FolderConfiguration.RELOAD_CONFIGURATION_DELAY)); } diff --git a/src/vs/workbench/services/configuration/node/configurationEditingService.ts b/src/vs/workbench/services/configuration/node/configurationEditingService.ts index 065e6601dd6..2e4521f6e26 100644 --- a/src/vs/workbench/services/configuration/node/configurationEditingService.ts +++ b/src/vs/workbench/services/configuration/node/configurationEditingService.ts @@ -39,6 +39,11 @@ export enum ConfigurationEditingErrorCode { */ ERROR_UNKNOWN_KEY, + /** + * Error when trying to write an application setting into workspace settings. + */ + ERROR_INVALID_WORKSPACE_CONFIGURATION_APPLICATION, + /** * Error when trying to write an invalid folder configuration key to folder settings. */ @@ -274,6 +279,7 @@ export class ConfigurationEditingService { // API constraints case ConfigurationEditingErrorCode.ERROR_UNKNOWN_KEY: return nls.localize('errorUnknownKey', "Unable to write to {0} because {1} is not a registered configuration.", this.stringifyTarget(target), operation.key); + case ConfigurationEditingErrorCode.ERROR_INVALID_WORKSPACE_CONFIGURATION_APPLICATION: return nls.localize('errorInvalidWorkspaceConfigurationApplication', "Unable to write {0} to Workspace Settings. This setting can be written only into User settings.", operation.key); case ConfigurationEditingErrorCode.ERROR_INVALID_FOLDER_CONFIGURATION: return nls.localize('errorInvalidFolderConfiguration', "Unable to write to Folder Settings because {0} does not support the folder resource scope.", operation.key); case ConfigurationEditingErrorCode.ERROR_INVALID_USER_TARGET: return nls.localize('errorInvalidUserTarget', "Unable to write to User Settings because {0} does not support for global scope.", operation.key); case ConfigurationEditingErrorCode.ERROR_INVALID_WORKSPACE_TARGET: return nls.localize('errorInvalidWorkspaceTarget', "Unable to write to Workspace Settings because {0} does not support for workspace scope in a multi folder workspace.", operation.key); @@ -396,6 +402,15 @@ export class ConfigurationEditingService { return this.wrapError(ConfigurationEditingErrorCode.ERROR_NO_WORKSPACE_OPENED, target, operation); } + if (target === ConfigurationTarget.WORKSPACE) { + if (!operation.workspaceStandAloneConfigurationKey) { + const configurationProperties = Registry.as(ConfigurationExtensions.Configuration).getConfigurationProperties(); + if (configurationProperties[operation.key].scope === ConfigurationScope.APPLICATION) { + return this.wrapError(ConfigurationEditingErrorCode.ERROR_INVALID_WORKSPACE_CONFIGURATION_APPLICATION, target, operation); + } + } + } + if (target === ConfigurationTarget.WORKSPACE_FOLDER) { if (!operation.resource) { return this.wrapError(ConfigurationEditingErrorCode.ERROR_INVALID_FOLDER_TARGET, target, operation); diff --git a/src/vs/workbench/services/configuration/node/configurationService.ts b/src/vs/workbench/services/configuration/node/configurationService.ts index 72329335ed4..3284a2771a8 100644 --- a/src/vs/workbench/services/configuration/node/configurationService.ts +++ b/src/vs/workbench/services/configuration/node/configurationService.ts @@ -10,7 +10,7 @@ import { dirname, basename } from 'path'; import * as assert from 'vs/base/common/assert'; import { Event, Emitter } from 'vs/base/common/event'; import { StrictResourceMap } from 'vs/base/common/map'; -import { equals } from 'vs/base/common/objects'; +import { equals, deepClone } from 'vs/base/common/objects'; import { Disposable } from 'vs/base/common/lifecycle'; import { Queue } from 'vs/base/common/async'; import { stat, writeFile } from 'vs/base/node/pfs'; @@ -24,7 +24,7 @@ import { IConfigurationChangeEvent, ConfigurationTarget, IConfigurationOverrides import { Configuration, WorkspaceConfigurationChangeEvent, AllKeysConfigurationChangeEvent } from 'vs/workbench/services/configuration/common/configurationModels'; import { IWorkspaceConfigurationService, FOLDER_CONFIG_FOLDER_NAME, defaultSettingsSchemaId, userSettingsSchemaId, workspaceSettingsSchemaId, folderSettingsSchemaId } from 'vs/workbench/services/configuration/common/configuration'; import { Registry } from 'vs/platform/registry/common/platform'; -import { IConfigurationNode, IConfigurationRegistry, Extensions, settingsSchema, resourceSettingsSchema, IConfigurationPropertySchema } from 'vs/platform/configuration/common/configurationRegistry'; +import { IConfigurationNode, IConfigurationRegistry, Extensions, IConfigurationPropertySchema, allSettings, windowSettings, resourceSettings, applicationSettings } from 'vs/platform/configuration/common/configurationRegistry'; import { createHash } from 'crypto'; import { getWorkspaceLabel, IWorkspaceIdentifier, ISingleFolderWorkspaceIdentifier, isSingleFolderWorkspaceIdentifier, isWorkspaceIdentifier, IStoredWorkspaceFolder, isStoredWorkspaceFolder, IWorkspaceFolderCreationData } from 'vs/platform/workspaces/common/workspaces'; import { IWindowConfiguration } from 'vs/platform/windows/common/windows'; @@ -40,6 +40,8 @@ import { massageFolderPathForWorkspace } from 'vs/platform/workspaces/node/works import { distinct } from 'vs/base/common/arrays'; import { UserConfiguration } from 'vs/platform/configuration/node/configuration'; import { getBaseLabel } from 'vs/base/common/labels'; +import { IJSONSchema, IJSONSchemaMap } from 'vs/base/common/jsonSchema'; +import { localize } from 'vs/nls'; export class WorkspaceService extends Disposable implements IWorkspaceConfigurationService, IWorkspaceContextService { @@ -489,15 +491,29 @@ export class WorkspaceService extends Disposable implements IWorkspaceConfigurat private registerConfigurationSchemas(): void { if (this.workspace) { const jsonRegistry = Registry.as(JSONExtensions.JSONContribution); - jsonRegistry.registerSchema(defaultSettingsSchemaId, settingsSchema); - jsonRegistry.registerSchema(userSettingsSchemaId, settingsSchema); + const convertToNotSuggestedProperties = (properties: IJSONSchemaMap, errorMessage: string): IJSONSchemaMap => { + return Object.keys(properties).reduce((result: IJSONSchemaMap, property) => { + result[property] = deepClone(properties[property]); + result[property].deprecationMessage = errorMessage; + return result; + }, {}); + }; + + const allSettingsSchema: IJSONSchema = { properties: allSettings.properties, patternProperties: allSettings.patternProperties, additionalProperties: false, errorMessage: 'Unknown configuration setting' }; + const unsupportedApplicationSettings = convertToNotSuggestedProperties(applicationSettings.properties, localize('unsupportedApplicationSetting', "This setting can be applied only in User Settings")); + const workspaceSettingsSchema: IJSONSchema = { properties: { ...unsupportedApplicationSettings, ...windowSettings.properties, ...resourceSettings.properties }, patternProperties: allSettings.patternProperties, additionalProperties: false, errorMessage: 'Unknown configuration setting' }; + + jsonRegistry.registerSchema(defaultSettingsSchemaId, allSettingsSchema); + jsonRegistry.registerSchema(userSettingsSchemaId, allSettingsSchema); if (WorkbenchState.WORKSPACE === this.getWorkbenchState()) { - jsonRegistry.registerSchema(workspaceSettingsSchemaId, settingsSchema); - jsonRegistry.registerSchema(folderSettingsSchemaId, resourceSettingsSchema); + const unsupportedWindowSettings = convertToNotSuggestedProperties(windowSettings.properties, localize('unsupportedWindowSetting', "This setting cannot be applied now. It will be applied when you open this folder directly.")); + const folderSettingsSchema: IJSONSchema = { properties: { ...unsupportedApplicationSettings, ...unsupportedWindowSettings, ...resourceSettings.properties }, patternProperties: allSettings.patternProperties, additionalProperties: false, errorMessage: 'Unknown configuration setting' }; + jsonRegistry.registerSchema(workspaceSettingsSchemaId, workspaceSettingsSchema); + jsonRegistry.registerSchema(folderSettingsSchemaId, folderSettingsSchema); } else { - jsonRegistry.registerSchema(workspaceSettingsSchemaId, settingsSchema); - jsonRegistry.registerSchema(folderSettingsSchemaId, settingsSchema); + jsonRegistry.registerSchema(workspaceSettingsSchemaId, workspaceSettingsSchema); + jsonRegistry.registerSchema(folderSettingsSchemaId, workspaceSettingsSchema); } } } diff --git a/src/vs/workbench/services/configuration/test/common/configurationModels.test.ts b/src/vs/workbench/services/configuration/test/common/configurationModels.test.ts index 2b675b8cab6..78af2a757c8 100644 --- a/src/vs/workbench/services/configuration/test/common/configurationModels.test.ts +++ b/src/vs/workbench/services/configuration/test/common/configurationModels.test.ts @@ -42,7 +42,7 @@ suite('FolderSettingsModelParser', () => { }); test('parse all folder settings', () => { - const testObject = new FolderSettingsModelParser('settings'); + const testObject = new FolderSettingsModelParser('settings', [ConfigurationScope.RESOURCE, ConfigurationScope.WINDOW]); testObject.parse(JSON.stringify({ 'FolderSettingsModelParser.window': 'window', 'FolderSettingsModelParser.resource': 'resource', 'FolderSettingsModelParser.executable': 'executable' })); @@ -50,7 +50,7 @@ suite('FolderSettingsModelParser', () => { }); test('parse resource folder settings', () => { - const testObject = new FolderSettingsModelParser('settings', ConfigurationScope.RESOURCE); + const testObject = new FolderSettingsModelParser('settings', [ConfigurationScope.RESOURCE]); testObject.parse(JSON.stringify({ 'FolderSettingsModelParser.window': 'window', 'FolderSettingsModelParser.resource': 'resource', 'FolderSettingsModelParser.executable': 'executable' })); @@ -58,7 +58,7 @@ suite('FolderSettingsModelParser', () => { }); test('reprocess folder settings excludes executable', () => { - const testObject = new FolderSettingsModelParser('settings'); + const testObject = new FolderSettingsModelParser('settings', [ConfigurationScope.RESOURCE, ConfigurationScope.WINDOW]); testObject.parse(JSON.stringify({ 'FolderSettingsModelParser.resource': 'resource', 'FolderSettingsModelParser.anotherExecutable': 'executable' })); diff --git a/src/vs/workbench/services/configuration/test/node/configurationService.test.ts b/src/vs/workbench/services/configuration/test/node/configurationService.test.ts index b099e02c1b3..651ce72f7c6 100644 --- a/src/vs/workbench/services/configuration/test/node/configurationService.test.ts +++ b/src/vs/workbench/services/configuration/test/node/configurationService.test.ts @@ -632,6 +632,11 @@ suite('WorkspaceConfigurationService - Folder', () => { 'id': '_test', 'type': 'object', 'properties': { + 'configurationService.folder.applicationSetting': { + 'type': 'string', + 'default': 'isSet', + scope: ConfigurationScope.APPLICATION + }, 'configurationService.folder.testSetting': { 'type': 'string', 'default': 'isSet', @@ -682,7 +687,7 @@ suite('WorkspaceConfigurationService - Folder', () => { }); test('defaults', () => { - assert.deepEqual(testObject.getValue('configurationService'), { 'folder': { 'testSetting': 'isSet', 'executableSetting': 'isSet' } }); + assert.deepEqual(testObject.getValue('configurationService'), { 'folder': { 'applicationSetting': 'isSet', 'testSetting': 'isSet', 'executableSetting': 'isSet' } }); }); test('globals override defaults', () => { @@ -731,6 +736,13 @@ suite('WorkspaceConfigurationService - Folder', () => { }); }); + test('application settings are not read from workspace', () => { + fs.writeFileSync(globalSettingsFile, '{ "configurationService.folder.applicationSetting": "userValue" }'); + fs.writeFileSync(path.join(workspaceDir, '.vscode', 'settings.json'), '{ "configurationService.folder.applicationSetting": "workspaceValue" }'); + return testObject.reloadConfiguration() + .then(() => assert.equal(testObject.getValue('configurationService.folder.applicationSetting'), 'userValue')); + }); + test('executable settings are not read from workspace', () => { fs.writeFileSync(globalSettingsFile, '{ "configurationService.folder.executableSetting": "userValue" }'); fs.writeFileSync(path.join(workspaceDir, '.vscode', 'settings.json'), '{ "configurationService.folder.executableSetting": "workspaceValue" }'); @@ -880,6 +892,11 @@ suite('WorkspaceConfigurationService - Folder', () => { .then(() => assert.equal(testObject.getValue('tasks.service.testSetting'), 'value')); }); + test('update application setting into workspace configuration in a workspace is not supported', () => { + return testObject.updateValue('configurationService.folder.applicationSetting', 'workspaceValue', {}, ConfigurationTarget.WORKSPACE, true) + .then(() => assert.fail('Should not be supported'), (e) => assert.equal(e.code, ConfigurationEditingErrorCode.ERROR_INVALID_WORKSPACE_CONFIGURATION_APPLICATION)); + }); + test('update tasks configuration', () => { return testObject.updateValue('tasks', { 'version': '1.0.0', tasks: [{ 'taskName': 'myTask' }] }, ConfigurationTarget.WORKSPACE) .then(() => assert.deepEqual(testObject.getValue('tasks'), { 'version': '1.0.0', tasks: [{ 'taskName': 'myTask' }] })); @@ -922,6 +939,11 @@ suite('WorkspaceConfigurationService - Multiroot', () => { 'type': 'string', 'default': 'isSet' }, + 'configurationService.workspace.applicationSetting': { + 'type': 'string', + 'default': 'isSet', + scope: ConfigurationScope.APPLICATION + }, 'configurationService.workspace.testResourceSetting': { 'type': 'string', 'default': 'isSet', @@ -1001,6 +1023,13 @@ suite('WorkspaceConfigurationService - Multiroot', () => { .then(() => assert.deepEqual(testObject.getUnsupportedWorkspaceKeys(), ['configurationService.workspace.testExecutableSetting', 'configurationService.workspace.testExecutableResourceSetting'])); }); + test('application settings are not read from workspace', () => { + fs.writeFileSync(environmentService.appSettingsPath, '{ "configurationService.workspace.applicationSetting": "userValue" }'); + return jsonEditingServce.write(workspaceContextService.getWorkspace().configuration, { key: 'settings', value: { 'configurationService.workspace.applicationSetting': 'workspaceValue' } }, true) + .then(() => testObject.reloadConfiguration()) + .then(() => assert.equal(testObject.getValue('configurationService.workspace.applicationSetting'), 'userValue')); + }); + test('workspace settings override user settings after defaults are registered ', () => { fs.writeFileSync(environmentService.appSettingsPath, '{ "configurationService.workspace.newSetting": "userValue" }'); return jsonEditingServce.write(workspaceContextService.getWorkspace().configuration, { key: 'settings', value: { 'configurationService.workspace.newSetting': 'workspaceValue' } }, true) @@ -1020,6 +1049,13 @@ suite('WorkspaceConfigurationService - Multiroot', () => { }); }); + test('application settings are not read from workspace folder', () => { + fs.writeFileSync(environmentService.appSettingsPath, '{ "configurationService.workspace.applicationSetting": "userValue" }'); + fs.writeFileSync(workspaceContextService.getWorkspace().folders[0].toResource('.vscode/settings.json').fsPath, '{ "configurationService.workspace.applicationSetting": "workspaceFolderValue" }'); + return testObject.reloadConfiguration() + .then(() => assert.equal(testObject.getValue('configurationService.workspace.applicationSetting'), 'userValue')); + }); + test('executable settings are not read from workspace folder after defaults are registered', () => { fs.writeFileSync(environmentService.appSettingsPath, '{ "configurationService.workspace.testNewExecutableResourceSetting": "userValue" }'); fs.writeFileSync(workspaceContextService.getWorkspace().folders[0].toResource('.vscode/settings.json').fsPath, '{ "configurationService.workspace.testNewExecutableResourceSetting": "workspaceFolderValue" }'); @@ -1206,6 +1242,11 @@ suite('WorkspaceConfigurationService - Multiroot', () => { .then(() => assert.ok(target.called)); }); + test('update application setting into workspace configuration in a workspace is not supported', () => { + return testObject.updateValue('configurationService.workspace.applicationSetting', 'workspaceValue', {}, ConfigurationTarget.WORKSPACE, true) + .then(() => assert.fail('Should not be supported'), (e) => assert.equal(e.code, ConfigurationEditingErrorCode.ERROR_INVALID_WORKSPACE_CONFIGURATION_APPLICATION)); + }); + test('update workspace folder configuration', () => { const workspace = workspaceContextService.getWorkspace(); return testObject.updateValue('configurationService.workspace.testResourceSetting', 'workspaceFolderValue', { resource: workspace.folders[0].uri }, ConfigurationTarget.WORKSPACE_FOLDER) From cfc3e0ffa6f9401a870b06f77ba8a7e447ad48af Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Wed, 4 Apr 2018 17:55:27 +0200 Subject: [PATCH 33/49] #46750 - Set update channel setting as application setting --- src/vs/platform/update/node/update.config.contribution.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/vs/platform/update/node/update.config.contribution.ts b/src/vs/platform/update/node/update.config.contribution.ts index 9b368fa02b1..2ec106013a0 100644 --- a/src/vs/platform/update/node/update.config.contribution.ts +++ b/src/vs/platform/update/node/update.config.contribution.ts @@ -7,7 +7,7 @@ import * as nls from 'vs/nls'; import { Registry } from 'vs/platform/registry/common/platform'; -import { IConfigurationRegistry, Extensions as ConfigurationExtensions } from 'vs/platform/configuration/common/configurationRegistry'; +import { IConfigurationRegistry, Extensions as ConfigurationExtensions, ConfigurationScope } from 'vs/platform/configuration/common/configurationRegistry'; const configurationRegistry = Registry.as(ConfigurationExtensions.Configuration); configurationRegistry.registerConfiguration({ @@ -20,6 +20,7 @@ configurationRegistry.registerConfiguration({ 'type': 'string', 'enum': ['none', 'default'], 'default': 'default', + 'scope': ConfigurationScope.APPLICATION, 'description': nls.localize('updateChannel', "Configure whether you receive automatic updates from an update channel. Requires a restart after change.") }, 'update.enableWindowsBackgroundUpdates': { From a14cd440e11440431fa438567d7da6fe4e320724 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Wed, 4 Apr 2018 18:21:53 +0200 Subject: [PATCH 34/49] #46750 Expose application scope to extensions --- .../common/configurationExtensionPoint.ts | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/services/configuration/common/configurationExtensionPoint.ts b/src/vs/workbench/services/configuration/common/configurationExtensionPoint.ts index 8ca3d17b3da..2bee4041a3c 100644 --- a/src/vs/workbench/services/configuration/common/configurationExtensionPoint.ts +++ b/src/vs/workbench/services/configuration/common/configurationExtensionPoint.ts @@ -36,9 +36,10 @@ const configurationEntrySchema: IJSONSchema = { }, scope: { type: 'string', - enum: ['window', 'resource'], + enum: ['application', 'window', 'resource'], default: 'window', enumDescriptions: [ + nls.localize('scope.application.description', "Application specific configuration, which can be configured only in User settings."), nls.localize('scope.window.description', "Window specific configuration, which can be configured in the User or Workspace settings."), nls.localize('scope.resource.description', "Resource specific configuration, which can be configured in the User, Workspace or Folder settings.") ], @@ -130,7 +131,14 @@ function validateProperties(configuration: IConfigurationNode, extension: IExten for (let key in properties) { const message = validateProperty(key); const propertyConfiguration = configuration.properties[key]; - propertyConfiguration.scope = propertyConfiguration.scope && propertyConfiguration.scope.toString() === 'resource' ? ConfigurationScope.RESOURCE : ConfigurationScope.WINDOW; + propertyConfiguration.scope = ConfigurationScope.WINDOW; + if (propertyConfiguration.scope) { + if (propertyConfiguration.scope.toString() === 'application') { + propertyConfiguration.scope = ConfigurationScope.APPLICATION; + } else if (propertyConfiguration.scope.toString() === 'resource') { + propertyConfiguration.scope = ConfigurationScope.RESOURCE; + } + } propertyConfiguration.notMultiRootAdopted = !(extension.description.isBuiltin || (Array.isArray(extension.description.keywords) && extension.description.keywords.indexOf('multi-root ready') !== -1)); if (message) { extension.collector.warn(message); From 707d5ecd0d20ece194163fbed09a68a4c49bf648 Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Wed, 4 Apr 2018 11:13:59 -0700 Subject: [PATCH 35/49] Fix basic PHP extension name --- extensions/php/package.nls.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/php/package.nls.json b/extensions/php/package.nls.json index b6170bf3b9a..95897a72dec 100644 --- a/extensions/php/package.nls.json +++ b/extensions/php/package.nls.json @@ -1,4 +1,4 @@ { - "displayName": "PHP Language Features", + "displayName": "PHP Language Basics", "description": "Provides syntax highlighting and bracket matching for PHP files." } \ No newline at end of file From 7276c215a7562fbab39a73c48362546e970e42f0 Mon Sep 17 00:00:00 2001 From: Rachel Macfarlane Date: Wed, 4 Apr 2018 11:45:59 -0700 Subject: [PATCH 36/49] Zoom in issue reporter, fixes #46801 --- .../issue/issueReporterMain.ts | 37 +++++++++---------- 1 file changed, 18 insertions(+), 19 deletions(-) diff --git a/src/vs/code/electron-browser/issue/issueReporterMain.ts b/src/vs/code/electron-browser/issue/issueReporterMain.ts index fe645528c44..d90240884e5 100644 --- a/src/vs/code/electron-browser/issue/issueReporterMain.ts +++ b/src/vs/code/electron-browser/issue/issueReporterMain.ts @@ -375,33 +375,32 @@ export class IssueReporter extends Disposable { }); this.addEventListener('disableExtensions', 'keydown', (e: KeyboardEvent) => { + e.stopPropagation(); if (e.keyCode === 13 || e.keyCode === 32) { ipcRenderer.send('workbenchCommand', 'workbench.extensions.action.disableAll'); ipcRenderer.send('workbenchCommand', 'workbench.action.reloadWindow'); } }); - // Cmd+Enter or Mac or Ctrl+Enter on other platforms previews issue and closes window - if (platform.isMacintosh) { - let prevKeyWasCommand = false; - document.onkeydown = (e: KeyboardEvent) => { - if (prevKeyWasCommand && e.keyCode === 13) { - if (this.createIssue()) { - remote.getCurrentWindow().close(); - } + document.onkeydown = (e: KeyboardEvent) => { + const cmdOrCtrlKey = platform.isMacintosh ? e.metaKey : e.ctrlKey; + // Cmd/Ctrl+Enter previews issue and closes window + if (cmdOrCtrlKey && e.keyCode === 13) { + if (this.createIssue()) { + remote.getCurrentWindow().close(); } + } - prevKeyWasCommand = e.keyCode === 91 || e.keyCode === 93; - }; - } else { - document.onkeydown = (e: KeyboardEvent) => { - if (e.ctrlKey && e.keyCode === 13) { - if (this.createIssue()) { - remote.getCurrentWindow().close(); - } - } - }; - } + // Cmd/Ctrl + zooms in + if (cmdOrCtrlKey && e.keyCode === 187) { + this.applyZoom(webFrame.getZoomLevel() + 1); + } + + // Cmd/Ctrl - zooms out + if (cmdOrCtrlKey && e.keyCode === 189) { + this.applyZoom(webFrame.getZoomLevel() - 1); + } + }; } private updatePreviewButtonState() { From eb7e893b7fa79ce05abbc900ddd8f76536c8fd28 Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Wed, 4 Apr 2018 14:26:29 -0700 Subject: [PATCH 37/49] Remove "Preview" from search.location setting description --- .../parts/search/electron-browser/search.contribution.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 9de9d564d94..013b18dd348 100644 --- a/src/vs/workbench/parts/search/electron-browser/search.contribution.ts +++ b/src/vs/workbench/parts/search/electron-browser/search.contribution.ts @@ -579,7 +579,7 @@ configurationRegistry.registerConfiguration({ 'search.location': { enum: ['sidebar', 'panel'], default: 'sidebar', - description: nls.localize('search.location', "Preview: controls if the search will be shown as a view in the sidebar or as a panel in the panel area for more horizontal space. Next release search in panel will have improved horizontal layout and this will no longer be a preview."), + description: nls.localize('search.location', "Controls if the search will be shown as a view in the sidebar or as a panel in the panel area for more horizontal space. Next release search in panel will have improved horizontal layout and this will no longer be a preview."), }, } }); From 96750628f7919941d6c1ec23b01b59ebca85025c Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Wed, 4 Apr 2018 16:48:15 -0700 Subject: [PATCH 38/49] Bump node-debug2 --- build/builtInExtensions.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/builtInExtensions.json b/build/builtInExtensions.json index 696a6d05383..ca2bcf5144a 100644 --- a/build/builtInExtensions.json +++ b/build/builtInExtensions.json @@ -6,7 +6,7 @@ }, { "name": "ms-vscode.node-debug2", - "version": "1.22.8", + "version": "1.23.0", "repo": "https://github.com/Microsoft/vscode-node-debug2" } ] From ba9921dfd0210e657c7cc998e6fb829dbbc5f17d Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Wed, 4 Apr 2018 18:03:19 -0700 Subject: [PATCH 39/49] Fix #45148 - Find in Files should hide replace input --- .../parts/search/browser/searchActions.ts | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/src/vs/workbench/parts/search/browser/searchActions.ts b/src/vs/workbench/parts/search/browser/searchActions.ts index 6c6b5d03b53..0895c195cee 100644 --- a/src/vs/workbench/parts/search/browser/searchActions.ts +++ b/src/vs/workbench/parts/search/browser/searchActions.ts @@ -221,21 +221,16 @@ export abstract class FindOrReplaceInFilesAction extends Action { } public run(): TPromise { - const searchView = getSearchView(this.viewletService, this.panelService); return openSearchView(this.viewletService, this.panelService, true).then(openedView => { - if (!searchView || this.expandSearchReplaceWidget) { - const searchAndReplaceWidget = openedView.searchAndReplaceWidget; - searchAndReplaceWidget.toggleReplace(this.expandSearchReplaceWidget); - // Focus replace only when there is text in the searchInput box - const focusReplace = this.focusReplace && searchAndReplaceWidget.searchInput.getValue(); - searchAndReplaceWidget.focus(this.selectWidgetText, !!focusReplace); - } + const searchAndReplaceWidget = openedView.searchAndReplaceWidget; + searchAndReplaceWidget.toggleReplace(this.expandSearchReplaceWidget); + // Focus replace only when there is text in the searchInput box + const focusReplace = this.focusReplace && searchAndReplaceWidget.searchInput.getValue(); + searchAndReplaceWidget.focus(this.selectWidgetText, !!focusReplace); }); } } -export const SHOW_SEARCH_LABEL = nls.localize('showSearchViewlet', "Show Search"); - export class FindInFilesAction extends FindOrReplaceInFilesAction { public static readonly LABEL = nls.localize('findInFiles', "Find in Files"); From fc389d227eb8defc10c4893d82d2751120489231 Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Wed, 4 Apr 2018 18:12:19 -0700 Subject: [PATCH 40/49] Fix #46990 - "Toggle Search View Position" to command palette --- .../electron-browser/search.contribution.ts | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) 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 013b18dd348..5b65ba18480 100644 --- a/src/vs/workbench/parts/search/electron-browser/search.contribution.ts +++ b/src/vs/workbench/parts/search/electron-browser/search.contribution.ts @@ -16,7 +16,7 @@ import { Action } from 'vs/base/common/actions'; import * as objects from 'vs/base/common/objects'; import * as platform from 'vs/base/common/platform'; import { ExplorerFolderContext, ExplorerRootContext } from 'vs/workbench/parts/files/common/files'; -import { SyncActionDescriptor, MenuRegistry, MenuId } from 'vs/platform/actions/common/actions'; +import { SyncActionDescriptor, MenuRegistry, MenuId, ICommandAction } from 'vs/platform/actions/common/actions'; import { IWorkbenchActionRegistry, Extensions as ActionExtensions } from 'vs/workbench/common/actions'; import { QuickOpenHandlerDescriptor, IQuickOpenRegistry, Extensions as QuickOpenExtensions } from 'vs/workbench/browser/quickopen'; import { KeybindingsRegistry } from 'vs/platform/keybinding/common/keybindingsRegistry'; @@ -64,6 +64,8 @@ registerSingleton(ISearchWorkbenchService, SearchWorkbenchService); replaceContributions(); searchWidgetContributions(); +const category = nls.localize('search', "Search"); + KeybindingsRegistry.registerCommandAndKeybindingRule({ id: 'workbench.action.search.toggleQueryDetails', weight: KeybindingsRegistry.WEIGHT.workbenchContrib(), @@ -301,11 +303,14 @@ CommandsRegistry.registerCommand({ }); const toggleSearchViewPositionLabel = nls.localize('toggleSearchViewPositionLabel', "Toggle Search View Position"); +const ToggleSearchViewPositionCommand: ICommandAction = { + id: Constants.ToggleSearchViewPositionCommandId, + title: toggleSearchViewPositionLabel, + category +}; +MenuRegistry.addCommand(ToggleSearchViewPositionCommand); MenuRegistry.appendMenuItem(MenuId.SearchContext, { - command: { - id: Constants.ToggleSearchViewPositionCommandId, - title: toggleSearchViewPositionLabel - }, + command: ToggleSearchViewPositionCommand, when: Constants.SearchViewVisibleKey, group: 'search_9', order: 1 @@ -431,7 +436,6 @@ Registry.as(WorkbenchExtensions.Workbench).regi // Actions const registry = Registry.as(ActionExtensions.WorkbenchActions); -const category = nls.localize('search', "Search"); registry.registerWorkbenchAction(new SyncActionDescriptor(FindInFilesAction, VIEW_ID, nls.localize('showSearchViewl', "Show Search"), { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_F }, Constants.SearchViewVisibleKey.toNegated()), 'View: Show Search', nls.localize('view', "View")); From dd460f29af313afa3d4f4c02a07ecbf780f5664b Mon Sep 17 00:00:00 2001 From: Alex Dima Date: Thu, 5 Apr 2018 10:18:45 +0200 Subject: [PATCH 41/49] Fixes #47004: Toggle comments shouldn't move cursor --- .../contrib/comment/lineCommentCommand.ts | 8 ++-- .../comment/test/lineCommentCommand.test.ts | 47 ++++++++++++------- 2 files changed, 35 insertions(+), 20 deletions(-) diff --git a/src/vs/editor/contrib/comment/lineCommentCommand.ts b/src/vs/editor/contrib/comment/lineCommentCommand.ts index 4a31a0424b7..fd3c9a3912a 100644 --- a/src/vs/editor/contrib/comment/lineCommentCommand.ts +++ b/src/vs/editor/contrib/comment/lineCommentCommand.ts @@ -336,10 +336,10 @@ export class LineCommentCommand implements editorCommon.ICommand { } return new Selection( - result.startLineNumber, - result.startColumn + this._deltaColumn, - result.endLineNumber, - result.endColumn + this._deltaColumn + result.selectionStartLineNumber, + result.selectionStartColumn + this._deltaColumn, + result.positionLineNumber, + result.positionColumn + this._deltaColumn ); } diff --git a/src/vs/editor/contrib/comment/test/lineCommentCommand.test.ts b/src/vs/editor/contrib/comment/test/lineCommentCommand.test.ts index 57cd4ccde56..932e73bde2e 100644 --- a/src/vs/editor/contrib/comment/test/lineCommentCommand.test.ts +++ b/src/vs/editor/contrib/comment/test/lineCommentCommand.test.ts @@ -256,7 +256,7 @@ suite('Editor Contrib - Line Comment Command', () => { '\t!@# some text', '\t!@# some more text' ], - new Selection(1, 1, 2, 2) + new Selection(2, 2, 1, 1) ); }); @@ -271,7 +271,7 @@ suite('Editor Contrib - Line Comment Command', () => { '\t!@# some text', ' !@# some more text' ], - new Selection(1, 1, 2, 2) + new Selection(2, 2, 1, 1) ); }); @@ -290,7 +290,7 @@ suite('Editor Contrib - Line Comment Command', () => { '', '\t!@# some more text' ], - new Selection(1, 1, 4, 2) + new Selection(4, 2, 1, 1) ); }); @@ -307,7 +307,7 @@ suite('Editor Contrib - Line Comment Command', () => { '\t ', '\t\tsome more text' ], - new Selection(1, 1, 3, 2) + new Selection(3, 2, 1, 1) ); }); @@ -324,7 +324,7 @@ suite('Editor Contrib - Line Comment Command', () => { '\t!@# ', '\t\tsome more text' ], - new Selection(1, 1, 3, 1) + new Selection(3, 1, 1, 1) ); }); @@ -369,7 +369,7 @@ suite('Editor Contrib - Line Comment Command', () => { 'first!@#', '\t!@# second line' ], - new Selection(2, 1, 2, 7) + new Selection(2, 7, 2, 1) ); }); @@ -390,7 +390,7 @@ suite('Editor Contrib - Line Comment Command', () => { 'fourth line', 'fifth' ], - new Selection(1, 5, 2, 1) + new Selection(2, 1, 1, 5) ); }); @@ -411,7 +411,7 @@ suite('Editor Contrib - Line Comment Command', () => { 'fourth line', 'fifth' ], - new Selection(1, 5, 2, 8) + new Selection(2, 8, 1, 5) ); }); @@ -432,7 +432,7 @@ suite('Editor Contrib - Line Comment Command', () => { '!@# fourth line', 'fifth' ], - new Selection(3, 5, 4, 8) + new Selection(4, 8, 3, 5) ); }); @@ -493,7 +493,7 @@ suite('Editor Contrib - Line Comment Command', () => { 'fourth line', 'fifth' ], - new Selection(1, 5, 2, 8) + new Selection(2, 8, 1, 5) ); testLineCommentCommand( @@ -512,7 +512,7 @@ suite('Editor Contrib - Line Comment Command', () => { 'fourth line', 'fifth' ], - new Selection(1, 1, 2, 3) + new Selection(2, 3, 1, 1) ); }); @@ -607,6 +607,21 @@ suite('Editor Contrib - Line Comment Command', () => { new Selection(1, 1, 8, 60) ); }); + + test('issue #47004: Toggle comments shouldn\'t move cursor', () => { + testAddLineCommentCommand( + [ + ' A line', + ' Another line' + ], + new Selection(2, 7, 1, 1), + [ + ' !@# A line', + ' !@# Another line' + ], + new Selection(2, 11, 1, 1) + ); + }); }); suite('Editor Contrib - Line Comment As Block Comment', () => { @@ -655,7 +670,7 @@ suite('Editor Contrib - Line Comment As Block Comment', () => { 'fourth line', 'fifth' ], - new Selection(1, 1, 1, 6) + new Selection(1, 6, 1, 1) ); }); @@ -697,7 +712,7 @@ suite('Editor Contrib - Line Comment As Block Comment', () => { 'fourth line', 'fifth' ], - new Selection(1, 5, 3, 2) + new Selection(3, 2, 1, 5) ); testLineCommentCommand( @@ -716,7 +731,7 @@ suite('Editor Contrib - Line Comment As Block Comment', () => { 'fourth line', 'fifth' ], - new Selection(1, 1, 3, 11) + new Selection(3, 11, 1, 1) ); }); }); @@ -842,7 +857,7 @@ suite('Editor Contrib - Line Comment As Block Comment 2', () => { 'fourth line', '\t\tfifth\t\t' ], - new Selection(5, 3, 5, 8) + new Selection(5, 8, 5, 3) ); testLineCommentCommand( @@ -861,7 +876,7 @@ suite('Editor Contrib - Line Comment As Block Comment 2', () => { 'fourth line', '\t\tfifth\t\t' ], - new Selection(5, 3, 5, 8) + new Selection(5, 8, 5, 3) ); testLineCommentCommand( From b07b786e18f997a74e4eb84ce76df5c3cd6eb57e Mon Sep 17 00:00:00 2001 From: Alex Dima Date: Thu, 5 Apr 2018 11:36:59 +0200 Subject: [PATCH 42/49] Better usage of editor API --- .../editor/browser/widget/diffEditorWidget.ts | 7 +-- .../contrib/colorPicker/colorDetector.ts | 3 +- src/vs/editor/contrib/dnd/dnd.ts | 17 ++---- .../contrib/folding/foldingDecorations.ts | 12 ++-- .../contrib/inPlaceReplace/inPlaceReplace.ts | 5 +- src/vs/editor/contrib/links/links.ts | 42 +++++++------- .../editor/contrib/multicursor/multicursor.ts | 4 +- .../referenceSearch/referencesWidget.ts | 55 +++++++++---------- .../editor/contrib/snippet/snippetSession.ts | 4 +- .../browser/quickOpen/editorQuickOpen.ts | 36 ++++++------ .../debug/electron-browser/debugHover.ts | 9 ++- .../browser/preferencesRenderers.ts | 10 ++-- 12 files changed, 93 insertions(+), 111 deletions(-) diff --git a/src/vs/editor/browser/widget/diffEditorWidget.ts b/src/vs/editor/browser/widget/diffEditorWidget.ts index b823747dede..fd92e00b778 100644 --- a/src/vs/editor/browser/widget/diffEditorWidget.ts +++ b/src/vs/editor/browser/widget/diffEditorWidget.ts @@ -99,12 +99,7 @@ class VisualEditorState { this._zonesMap = {}; // (2) Model decorations - if (this._decorations.length > 0) { - editor.changeDecorations((changeAccessor: IModelDecorationsChangeAccessor) => { - changeAccessor.deltaDecorations(this._decorations, []); - }); - } - this._decorations = []; + this._decorations = editor.deltaDecorations(this._decorations, []); } public apply(editor: CodeEditor, overviewRuler: editorBrowser.IOverviewRuler, newDecorations: IEditorDiffDecorationsWithZones): void { diff --git a/src/vs/editor/contrib/colorPicker/colorDetector.ts b/src/vs/editor/contrib/colorPicker/colorDetector.ts index b839e90d207..97fbb7b8852 100644 --- a/src/vs/editor/contrib/colorPicker/colorDetector.ts +++ b/src/vs/editor/contrib/colorPicker/colorDetector.ts @@ -16,6 +16,7 @@ import { ColorProviderRegistry } from 'vs/editor/common/modes'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; import { getColors, IColorData } from 'vs/editor/contrib/colorPicker/color'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { ModelDecorationOptions } from 'vs/editor/common/model/textModel'; const MAX_DECORATORS = 500; @@ -153,7 +154,7 @@ export class ColorDetector implements IEditorContribution { endLineNumber: c.colorInfo.range.endLineNumber, endColumn: c.colorInfo.range.endColumn }, - options: {} + options: ModelDecorationOptions.EMPTY })); this._decorationsIds = this._editor.deltaDecorations(this._decorationsIds, decorations); diff --git a/src/vs/editor/contrib/dnd/dnd.ts b/src/vs/editor/contrib/dnd/dnd.ts index 6cc7e1aced4..25a6ccf4363 100644 --- a/src/vs/editor/contrib/dnd/dnd.ts +++ b/src/vs/editor/contrib/dnd/dnd.ts @@ -173,22 +173,17 @@ export class DragAndDropController implements editorCommon.IEditorContribution { }); public showAt(position: Position): void { - this._editor.changeDecorations(changeAccessor => { - let newDecorations: IModelDeltaDecoration[] = []; - newDecorations.push({ - range: new Range(position.lineNumber, position.column, position.lineNumber, position.column), - options: DragAndDropController._DECORATION_OPTIONS - }); + let newDecorations: IModelDeltaDecoration[] = [{ + range: new Range(position.lineNumber, position.column, position.lineNumber, position.column), + options: DragAndDropController._DECORATION_OPTIONS + }]; - this._dndDecorationIds = changeAccessor.deltaDecorations(this._dndDecorationIds, newDecorations); - }); + this._dndDecorationIds = this._editor.deltaDecorations(this._dndDecorationIds, newDecorations); this._editor.revealPosition(position, editorCommon.ScrollType.Immediate); } private _removeDecoration(): void { - this._editor.changeDecorations(changeAccessor => { - changeAccessor.deltaDecorations(this._dndDecorationIds, []); - }); + this._dndDecorationIds = this._editor.deltaDecorations(this._dndDecorationIds, []); } private _hitContent(target: IMouseTarget): boolean { diff --git a/src/vs/editor/contrib/folding/foldingDecorations.ts b/src/vs/editor/contrib/folding/foldingDecorations.ts index fcc392cb184..17359b411fb 100644 --- a/src/vs/editor/contrib/folding/foldingDecorations.ts +++ b/src/vs/editor/contrib/folding/foldingDecorations.ts @@ -10,18 +10,18 @@ import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; export class FoldingDecorationProvider implements IDecorationProvider { - private COLLAPSED_VISUAL_DECORATION = ModelDecorationOptions.register({ + private static COLLAPSED_VISUAL_DECORATION = ModelDecorationOptions.register({ stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges, afterContentClassName: 'inline-folded', linesDecorationsClassName: 'folding collapsed' }); - private EXPANDED_AUTO_HIDE_VISUAL_DECORATION = ModelDecorationOptions.register({ + private static EXPANDED_AUTO_HIDE_VISUAL_DECORATION = ModelDecorationOptions.register({ stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges, linesDecorationsClassName: 'folding' }); - private EXPANDED_VISUAL_DECORATION = ModelDecorationOptions.register({ + private static EXPANDED_VISUAL_DECORATION = ModelDecorationOptions.register({ stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges, linesDecorationsClassName: 'folding alwaysShowFoldIcons' }); @@ -33,11 +33,11 @@ export class FoldingDecorationProvider implements IDecorationProvider { getDecorationOption(isCollapsed: boolean): ModelDecorationOptions { if (isCollapsed) { - return this.COLLAPSED_VISUAL_DECORATION; + return FoldingDecorationProvider.COLLAPSED_VISUAL_DECORATION; } else if (this.autoHideFoldingControls) { - return this.EXPANDED_AUTO_HIDE_VISUAL_DECORATION; + return FoldingDecorationProvider.EXPANDED_AUTO_HIDE_VISUAL_DECORATION; } else { - return this.EXPANDED_VISUAL_DECORATION; + return FoldingDecorationProvider.EXPANDED_VISUAL_DECORATION; } } diff --git a/src/vs/editor/contrib/inPlaceReplace/inPlaceReplace.ts b/src/vs/editor/contrib/inPlaceReplace/inPlaceReplace.ts index 0d8c406b5e7..18548f6eccc 100644 --- a/src/vs/editor/contrib/inPlaceReplace/inPlaceReplace.ts +++ b/src/vs/editor/contrib/inPlaceReplace/inPlaceReplace.ts @@ -10,7 +10,6 @@ import { TPromise } from 'vs/base/common/winjs.base'; import { Range } from 'vs/editor/common/core/range'; import { Selection } from 'vs/editor/common/core/selection'; import { IEditorContribution } from 'vs/editor/common/editorCommon'; -import { IModelDecorationsChangeAccessor } from 'vs/editor/common/model'; import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; import { registerEditorAction, ServicesAccessor, EditorAction, registerEditorContribution } from 'vs/editor/browser/editorExtensions'; import { IInplaceReplaceSupportResult } from 'vs/editor/common/modes'; @@ -131,9 +130,7 @@ class InPlaceReplaceController implements IEditorContribution { this.decorationRemover.cancel(); this.decorationRemover = TPromise.timeout(350); this.decorationRemover.then(() => { - this.editor.changeDecorations((accessor: IModelDecorationsChangeAccessor) => { - this.decorationIds = accessor.deltaDecorations(this.decorationIds, []); - }); + this.decorationIds = this.editor.deltaDecorations(this.decorationIds, []); }); }); } diff --git a/src/vs/editor/contrib/links/links.ts b/src/vs/editor/contrib/links/links.ts index d62f77d70a4..b9814039159 100644 --- a/src/vs/editor/contrib/links/links.ts +++ b/src/vs/editor/contrib/links/links.ts @@ -251,32 +251,30 @@ class LinkDetector implements editorCommon.IEditorContribution { private updateDecorations(links: Link[]): void { const useMetaKey = (this.editor.getConfiguration().multiCursorModifier === 'altKey'); - this.editor.changeDecorations((changeAccessor: IModelDecorationsChangeAccessor) => { - var oldDecorations: string[] = []; - let keys = Object.keys(this.currentOccurrences); - for (let i = 0, len = keys.length; i < len; i++) { - let decorationId = keys[i]; - let occurance = this.currentOccurrences[decorationId]; - oldDecorations.push(occurance.decorationId); - } + let oldDecorations: string[] = []; + let keys = Object.keys(this.currentOccurrences); + for (let i = 0, len = keys.length; i < len; i++) { + let decorationId = keys[i]; + let occurance = this.currentOccurrences[decorationId]; + oldDecorations.push(occurance.decorationId); + } - var newDecorations: IModelDeltaDecoration[] = []; - if (links) { - // Not sure why this is sometimes null - for (var i = 0; i < links.length; i++) { - newDecorations.push(LinkOccurrence.decoration(links[i], useMetaKey)); - } + let newDecorations: IModelDeltaDecoration[] = []; + if (links) { + // Not sure why this is sometimes null + for (let i = 0; i < links.length; i++) { + newDecorations.push(LinkOccurrence.decoration(links[i], useMetaKey)); } + } - var decorations = changeAccessor.deltaDecorations(oldDecorations, newDecorations); + let decorations = this.editor.deltaDecorations(oldDecorations, newDecorations); - this.currentOccurrences = {}; - this.activeLinkDecorationId = null; - for (let i = 0, len = decorations.length; i < len; i++) { - var occurance = new LinkOccurrence(links[i], decorations[i]); - this.currentOccurrences[occurance.decorationId] = occurance; - } - }); + this.currentOccurrences = {}; + this.activeLinkDecorationId = null; + for (let i = 0, len = decorations.length; i < len; i++) { + let occurance = new LinkOccurrence(links[i], decorations[i]); + this.currentOccurrences[occurance.decorationId] = occurance; + } } private _onEditorMouseMove(mouseEvent: ClickLinkMouseEvent, withKey?: ClickLinkKeyboardEvent): void { diff --git a/src/vs/editor/contrib/multicursor/multicursor.ts b/src/vs/editor/contrib/multicursor/multicursor.ts index 6561c8b9ab6..931c4f02404 100644 --- a/src/vs/editor/contrib/multicursor/multicursor.ts +++ b/src/vs/editor/contrib/multicursor/multicursor.ts @@ -803,9 +803,7 @@ export class SelectionHighlighter extends Disposable implements IEditorContribut this.state = state; if (!this.state) { - if (this.decorations.length > 0) { - this.decorations = this.editor.deltaDecorations(this.decorations, []); - } + this.decorations = this.editor.deltaDecorations(this.decorations, []); return; } diff --git a/src/vs/editor/contrib/referenceSearch/referencesWidget.ts b/src/vs/editor/contrib/referenceSearch/referencesWidget.ts index 125595fb249..d944550b9d1 100644 --- a/src/vs/editor/contrib/referenceSearch/referencesWidget.ts +++ b/src/vs/editor/contrib/referenceSearch/referencesWidget.ts @@ -84,28 +84,25 @@ class DecorationsManager implements IDisposable { private _addDecorations(reference: FileReferences): void { this._callOnModelChange.push(this._editor.getModel().onDidChangeDecorations((event) => this._onDecorationChanged())); - this._editor.changeDecorations(accessor => { + const newDecorations: IModelDeltaDecoration[] = []; + const newDecorationsActualIndex: number[] = []; - const newDecorations: IModelDeltaDecoration[] = []; - const newDecorationsActualIndex: number[] = []; - - for (let i = 0, len = reference.children.length; i < len; i++) { - let oneReference = reference.children[i]; - if (this._decorationIgnoreSet.has(oneReference.id)) { - continue; - } - newDecorations.push({ - range: oneReference.range, - options: DecorationsManager.DecorationOptions - }); - newDecorationsActualIndex.push(i); + for (let i = 0, len = reference.children.length; i < len; i++) { + let oneReference = reference.children[i]; + if (this._decorationIgnoreSet.has(oneReference.id)) { + continue; } + newDecorations.push({ + range: oneReference.range, + options: DecorationsManager.DecorationOptions + }); + newDecorationsActualIndex.push(i); + } - const decorations = accessor.deltaDecorations([], newDecorations); - for (let i = 0; i < decorations.length; i++) { - this._decorations.set(decorations[i], reference.children[newDecorationsActualIndex[i]]); - } - }); + const decorations = this._editor.deltaDecorations([], newDecorations); + for (let i = 0; i < decorations.length; i++) { + this._decorations.set(decorations[i], reference.children[newDecorationsActualIndex[i]]); + } } private _onDecorationChanged(): void { @@ -143,21 +140,19 @@ class DecorationsManager implements IDisposable { } }); - this._editor.changeDecorations((accessor) => { - for (let i = 0, len = toRemove.length; i < len; i++) { - this._decorations.delete(toRemove[i]); - } - accessor.deltaDecorations(toRemove, []); - }); + for (let i = 0, len = toRemove.length; i < len; i++) { + this._decorations.delete(toRemove[i]); + } + this._editor.deltaDecorations(toRemove, []); } public removeDecorations(): void { - this._editor.changeDecorations(accessor => { - this._decorations.forEach((value, key) => { - accessor.removeDecoration(key); - }); - this._decorations.clear(); + let toRemove: string[] = []; + this._decorations.forEach((value, key) => { + toRemove.push(key); }); + this._editor.deltaDecorations(toRemove, []); + this._decorations.clear(); } } diff --git a/src/vs/editor/contrib/snippet/snippetSession.ts b/src/vs/editor/contrib/snippet/snippetSession.ts index 218e5ec9fd5..a101e5e16bb 100644 --- a/src/vs/editor/contrib/snippet/snippetSession.ts +++ b/src/vs/editor/contrib/snippet/snippetSession.ts @@ -50,7 +50,9 @@ export class OneSnippet { dispose(): void { if (this._placeholderDecorations) { - this._editor.changeDecorations(accessor => this._placeholderDecorations.forEach(handle => accessor.removeDecoration(handle))); + let toRemove: string[] = []; + this._placeholderDecorations.forEach(handle => toRemove.push(handle)); + this._editor.deltaDecorations(toRemove, []); } this._placeholderGroups.length = 0; } diff --git a/src/vs/editor/standalone/browser/quickOpen/editorQuickOpen.ts b/src/vs/editor/standalone/browser/quickOpen/editorQuickOpen.ts index 8d1bce53f7b..8bd223b6a2f 100644 --- a/src/vs/editor/standalone/browser/quickOpen/editorQuickOpen.ts +++ b/src/vs/editor/standalone/browser/quickOpen/editorQuickOpen.ts @@ -14,7 +14,7 @@ import { registerEditorContribution, IActionOptions, EditorAction } from 'vs/edi import { IThemeService } from 'vs/platform/theme/common/themeService'; import { Range } from 'vs/editor/common/core/range'; import { ModelDecorationOptions } from 'vs/editor/common/model/textModel'; -import { IModelDecorationsChangeAccessor, IModelDeltaDecoration } from 'vs/editor/common/model'; +import { IModelDeltaDecoration } from 'vs/editor/common/model'; export interface IQuickOpenControllerOpts { inputAriaLabel: string; @@ -100,31 +100,27 @@ export class QuickOpenController implements editorCommon.IEditorContribution, ID }); public decorateLine(range: Range, editor: ICodeEditor): void { - editor.changeDecorations((changeAccessor: IModelDecorationsChangeAccessor) => { - const oldDecorations: string[] = []; - if (this.rangeHighlightDecorationId) { - oldDecorations.push(this.rangeHighlightDecorationId); - this.rangeHighlightDecorationId = null; + const oldDecorations: string[] = []; + if (this.rangeHighlightDecorationId) { + oldDecorations.push(this.rangeHighlightDecorationId); + this.rangeHighlightDecorationId = null; + } + + const newDecorations: IModelDeltaDecoration[] = [ + { + range: range, + options: QuickOpenController._RANGE_HIGHLIGHT_DECORATION } + ]; - const newDecorations: IModelDeltaDecoration[] = [ - { - range: range, - options: QuickOpenController._RANGE_HIGHLIGHT_DECORATION - } - ]; - - const decorations = changeAccessor.deltaDecorations(oldDecorations, newDecorations); - this.rangeHighlightDecorationId = decorations[0]; - }); + const decorations = editor.deltaDecorations(oldDecorations, newDecorations); + this.rangeHighlightDecorationId = decorations[0]; } public clearDecorations(): void { if (this.rangeHighlightDecorationId) { - this.editor.changeDecorations((changeAccessor: IModelDecorationsChangeAccessor) => { - changeAccessor.deltaDecorations([this.rangeHighlightDecorationId], []); - this.rangeHighlightDecorationId = null; - }); + this.editor.deltaDecorations([this.rangeHighlightDecorationId], []); + this.rangeHighlightDecorationId = null; } } } diff --git a/src/vs/workbench/parts/debug/electron-browser/debugHover.ts b/src/vs/workbench/parts/debug/electron-browser/debugHover.ts index e6248b7f74e..c91609db06a 100644 --- a/src/vs/workbench/parts/debug/electron-browser/debugHover.ts +++ b/src/vs/workbench/parts/debug/electron-browser/debugHover.ts @@ -27,6 +27,7 @@ import { IThemeService } from 'vs/platform/theme/common/themeService'; import { editorHoverBackground, editorHoverBorder } from 'vs/platform/theme/common/colorRegistry'; import { WorkbenchTree, WorkbenchTreeController } from 'vs/platform/list/browser/listService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { ModelDecorationOptions } from 'vs/editor/common/model/textModel'; const $ = dom.$; const MAX_ELEMENTS_SHOWN = 18; @@ -208,15 +209,17 @@ export class DebugHoverWidget implements IContentWidget { this.highlightDecorations = this.editor.deltaDecorations(this.highlightDecorations, [{ range: new Range(pos.lineNumber, expressionRange.startColumn, pos.lineNumber, expressionRange.startColumn + matchingExpression.length), - options: { - className: 'hoverHighlight' - } + options: DebugHoverWidget._HOVER_HIGHLIGHT_DECORATION_OPTIONS }]); return this.doShow(pos, expression, focus); }); } + private static _HOVER_HIGHLIGHT_DECORATION_OPTIONS = ModelDecorationOptions.register({ + className: 'hoverHighlight' + }); + private doFindExpression(container: IExpressionContainer, namesToFind: string[]): TPromise { if (!container) { return TPromise.as(null); diff --git a/src/vs/workbench/parts/preferences/browser/preferencesRenderers.ts b/src/vs/workbench/parts/preferences/browser/preferencesRenderers.ts index 6f134ab7bc9..f033c84ab3e 100644 --- a/src/vs/workbench/parts/preferences/browser/preferencesRenderers.ts +++ b/src/vs/workbench/parts/preferences/browser/preferencesRenderers.ts @@ -890,13 +890,15 @@ export class FilteredMatchesRenderer extends Disposable implements HiddenAreasPr private createDecoration(range: IRange, model: ITextModel): IModelDeltaDecoration { return { range, - options: { - stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges, - className: 'findMatch' - } + options: FilteredMatchesRenderer._FIND_MATCH }; } + private static readonly _FIND_MATCH = ModelDecorationOptions.register({ + stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges, + className: 'findMatch' + }); + private computeHiddenRanges(filteredGroups: ISettingsGroup[], allSettingsGroups: ISettingsGroup[], model: ITextModel): IRange[] { // Hide the contents of hidden groups const notMatchesRanges: IRange[] = []; From 843facb14633ca588d5e44a83e728f674961a2fd Mon Sep 17 00:00:00 2001 From: Alex Dima Date: Thu, 5 Apr 2018 11:54:27 +0200 Subject: [PATCH 43/49] Introduce and adopt IModelDecorationOptions.zIndex (#46995) --- .../browser/viewParts/decorations/decorations.ts | 10 ++++++++-- src/vs/editor/common/model.ts | 7 ++++++- src/vs/editor/common/model/intervalTree.ts | 11 ++++------- src/vs/editor/common/model/textModel.ts | 12 +++++------- src/vs/editor/common/services/modelServiceImpl.ts | 8 +++++++- src/vs/monaco.d.ts | 5 +++++ 6 files changed, 35 insertions(+), 18 deletions(-) diff --git a/src/vs/editor/browser/viewParts/decorations/decorations.ts b/src/vs/editor/browser/viewParts/decorations/decorations.ts index da0359739fe..1c3f303611d 100644 --- a/src/vs/editor/browser/viewParts/decorations/decorations.ts +++ b/src/vs/editor/browser/viewParts/decorations/decorations.ts @@ -85,8 +85,14 @@ export class DecorationsOverlay extends DynamicViewOverlay { // Sort decorations for consistent render output decorations = decorations.sort((a, b) => { - let aClassName = a.options.className; - let bClassName = b.options.className; + if (a.options.zIndex < b.options.zIndex) { + return -1; + } + if (a.options.zIndex > b.options.zIndex) { + return 1; + } + const aClassName = a.options.className; + const bClassName = b.options.className; if (aClassName < bClassName) { return -1; diff --git a/src/vs/editor/common/model.ts b/src/vs/editor/common/model.ts index 2f2a7433d4b..03c99eb566a 100644 --- a/src/vs/editor/common/model.ts +++ b/src/vs/editor/common/model.ts @@ -81,7 +81,12 @@ export interface IModelDecorationOptions { * Always render the decoration (even when the range it encompasses is collapsed). * @internal */ - readonly showIfCollapsed?: boolean; + showIfCollapsed?: boolean; + /** + * Specifies the stack order of a decoration. + * A decoration with greater stack order is always in front of a decoration with a lower stack order. + */ + zIndex?: number; /** * If set, render this decoration in the overview ruler. */ diff --git a/src/vs/editor/common/model/intervalTree.ts b/src/vs/editor/common/model/intervalTree.ts index 07f7200be6e..55636d2eace 100644 --- a/src/vs/editor/common/model/intervalTree.ts +++ b/src/vs/editor/common/model/intervalTree.ts @@ -12,14 +12,11 @@ import { IModelDecoration } from 'vs/editor/common/model'; // The red-black tree is based on the "Introduction to Algorithms" by Cormen, Leiserson and Rivest. // -/** - * The class name sort order must match the severity order. Highest severity last. - */ export const ClassName = { - EditorHintDecoration: 'squiggly-a-hint', - EditorInfoDecoration: 'squiggly-b-info', - EditorWarningDecoration: 'squiggly-c-warning', - EditorErrorDecoration: 'squiggly-d-error' + EditorHintDecoration: 'squiggly-hint', + EditorInfoDecoration: 'squiggly-info', + EditorWarningDecoration: 'squiggly-warning', + EditorErrorDecoration: 'squiggly-error' }; /** diff --git a/src/vs/editor/common/model/textModel.ts b/src/vs/editor/common/model/textModel.ts index e169ad2684c..2127ebf1a00 100644 --- a/src/vs/editor/common/model/textModel.ts +++ b/src/vs/editor/common/model/textModel.ts @@ -2585,22 +2585,20 @@ export class ModelDecorationOverviewRulerOptions implements model.IModelDecorati } } -let lastStaticId = 0; - export class ModelDecorationOptions implements model.IModelDecorationOptions { public static EMPTY: ModelDecorationOptions; public static register(options: model.IModelDecorationOptions): ModelDecorationOptions { - return new ModelDecorationOptions(++lastStaticId, options); + return new ModelDecorationOptions(options); } public static createDynamic(options: model.IModelDecorationOptions): ModelDecorationOptions { - return new ModelDecorationOptions(0, options); + return new ModelDecorationOptions(options); } - readonly staticId: number; readonly stickiness: model.TrackedRangeStickiness; + readonly zIndex: number; readonly className: string; readonly hoverMessage: IMarkdownString | IMarkdownString[]; readonly glyphMarginHoverMessage: IMarkdownString | IMarkdownString[]; @@ -2614,9 +2612,9 @@ export class ModelDecorationOptions implements model.IModelDecorationOptions { readonly beforeContentClassName: string; readonly afterContentClassName: string; - private constructor(staticId: number, options: model.IModelDecorationOptions) { - this.staticId = staticId; + private constructor(options: model.IModelDecorationOptions) { this.stickiness = options.stickiness || model.TrackedRangeStickiness.AlwaysGrowsWhenTypingAtEdges; + this.zIndex = options.zIndex || 0; this.className = options.className ? cleanClassName(options.className) : strings.empty; this.hoverMessage = options.hoverMessage || []; this.glyphMarginHoverMessage = options.glyphMarginHoverMessage || []; diff --git a/src/vs/editor/common/services/modelServiceImpl.ts b/src/vs/editor/common/services/modelServiceImpl.ts index 041cf88e4d6..fa395f924f0 100644 --- a/src/vs/editor/common/services/modelServiceImpl.ts +++ b/src/vs/editor/common/services/modelServiceImpl.ts @@ -118,26 +118,31 @@ class ModelMarkerHandler { let className: string; let color: ThemeColor; let darkColor: ThemeColor; + let zIndex: number; switch (marker.severity) { case MarkerSeverity.Hint: className = ClassName.EditorHintDecoration; + zIndex = 0; break; case MarkerSeverity.Warning: className = ClassName.EditorWarningDecoration; color = themeColorFromId(overviewRulerWarning); darkColor = themeColorFromId(overviewRulerWarning); + zIndex = 20; break; case MarkerSeverity.Info: className = ClassName.EditorInfoDecoration; color = themeColorFromId(overviewRulerInfo); darkColor = themeColorFromId(overviewRulerInfo); + zIndex = 10; break; case MarkerSeverity.Error: default: className = ClassName.EditorErrorDecoration; color = themeColorFromId(overviewRulerError); darkColor = themeColorFromId(overviewRulerError); + zIndex = 30; break; } @@ -177,7 +182,8 @@ class ModelMarkerHandler { color, darkColor, position: OverviewRulerLane.Right - } + }, + zIndex }; } } diff --git a/src/vs/monaco.d.ts b/src/vs/monaco.d.ts index e1e2ccb0a60..715a3ec520d 100644 --- a/src/vs/monaco.d.ts +++ b/src/vs/monaco.d.ts @@ -1193,6 +1193,11 @@ declare namespace monaco.editor { * Should the decoration expand to encompass a whole line. */ isWholeLine?: boolean; + /** + * Specifies the stack order of a decoration. + * A decoration with greater stack order is always in front of a decoration with a lower stack order. + */ + zIndex?: number; /** * If set, render this decoration in the overview ruler. */ From f26b9ef408b843101cd865b72a96a156bc2b3e72 Mon Sep 17 00:00:00 2001 From: Alex Dima Date: Thu, 5 Apr 2018 11:57:44 +0200 Subject: [PATCH 44/49] Fixes #46995: Add a higher zIndex to currentFindMatch --- src/vs/editor/contrib/find/findDecorations.ts | 1 + src/vs/workbench/parts/search/common/searchModel.ts | 1 + 2 files changed, 2 insertions(+) diff --git a/src/vs/editor/contrib/find/findDecorations.ts b/src/vs/editor/contrib/find/findDecorations.ts index 84ddb11b262..ff1c795d2e3 100644 --- a/src/vs/editor/contrib/find/findDecorations.ts +++ b/src/vs/editor/contrib/find/findDecorations.ts @@ -267,6 +267,7 @@ export class FindDecorations implements IDisposable { private static readonly _CURRENT_FIND_MATCH_DECORATION = ModelDecorationOptions.register({ stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges, + zIndex: 13, className: 'currentFindMatch', showIfCollapsed: true, overviewRuler: { diff --git a/src/vs/workbench/parts/search/common/searchModel.ts b/src/vs/workbench/parts/search/common/searchModel.ts index ac9827a3d67..6084c48c45d 100644 --- a/src/vs/workbench/parts/search/common/searchModel.ts +++ b/src/vs/workbench/parts/search/common/searchModel.ts @@ -95,6 +95,7 @@ export class FileMatch extends Disposable { private static readonly _CURRENT_FIND_MATCH = ModelDecorationOptions.register({ stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges, + zIndex: 13, className: 'currentFindMatch', overviewRuler: { color: themeColorFromId(overviewRulerFindMatchForeground), From e50d290378f0758c4af73b8d5236083312f8710e Mon Sep 17 00:00:00 2001 From: Alex Dima Date: Thu, 5 Apr 2018 12:21:10 +0200 Subject: [PATCH 45/49] Fixes #10047: include `rangeOffset` in `TextDocumentContentChangeEvent` --- src/vs/editor/common/model.ts | 1 - src/vs/editor/common/model/textModel.ts | 9 +++++---- src/vs/editor/common/model/textModelEvents.ts | 4 ++++ src/vs/monaco.d.ts | 4 ++++ src/vs/vscode.d.ts | 4 ++++ src/vs/workbench/api/node/extHostDocuments.ts | 1 + .../electron-browser/api/extHostDocumentData.test.ts | 6 ++++++ .../api/extHostDocumentSaveParticipant.test.ts | 2 ++ 8 files changed, 26 insertions(+), 5 deletions(-) diff --git a/src/vs/editor/common/model.ts b/src/vs/editor/common/model.ts index 03c99eb566a..ff909869d8e 100644 --- a/src/vs/editor/common/model.ts +++ b/src/vs/editor/common/model.ts @@ -1152,6 +1152,5 @@ export class ApplyEditsResult { */ export interface IInternalModelContentChange extends IModelContentChange { range: Range; - rangeOffset: number; forceMoveMarkers: boolean; } diff --git a/src/vs/editor/common/model/textModel.ts b/src/vs/editor/common/model/textModel.ts index 2127ebf1a00..3975b563698 100644 --- a/src/vs/editor/common/model/textModel.ts +++ b/src/vs/editor/common/model/textModel.ts @@ -386,10 +386,11 @@ export class TextModel extends Disposable implements model.ITextModel { this.setValueFromTextBuffer(textBuffer); } - private _createContentChanged2(startLineNumber: number, startColumn: number, endLineNumber: number, endColumn: number, rangeLength: number, text: string, isUndoing: boolean, isRedoing: boolean, isFlush: boolean): IModelContentChangedEvent { + private _createContentChanged2(range: Range, rangeOffset: number, rangeLength: number, text: string, isUndoing: boolean, isRedoing: boolean, isFlush: boolean): IModelContentChangedEvent { return { changes: [{ - range: new Range(startLineNumber, startColumn, endLineNumber, endColumn), + range: range, + rangeOffset: rangeOffset, rangeLength: rangeLength, text: text, }], @@ -435,7 +436,7 @@ export class TextModel extends Disposable implements model.ITextModel { false, false ), - this._createContentChanged2(1, 1, endLineNumber, endColumn, oldModelValueLength, this.getValue(), false, false, true) + this._createContentChanged2(new Range(1, 1, endLineNumber, endColumn), 0, oldModelValueLength, this.getValue(), false, false, true) ); } @@ -466,7 +467,7 @@ export class TextModel extends Disposable implements model.ITextModel { false, false ), - this._createContentChanged2(1, 1, endLineNumber, endColumn, oldModelValueLength, this.getValue(), false, false, false) + this._createContentChanged2(new Range(1, 1, endLineNumber, endColumn), 0, oldModelValueLength, this.getValue(), false, false, false) ); } diff --git a/src/vs/editor/common/model/textModelEvents.ts b/src/vs/editor/common/model/textModelEvents.ts index f65fdca1ef2..d6593abd1cb 100644 --- a/src/vs/editor/common/model/textModelEvents.ts +++ b/src/vs/editor/common/model/textModelEvents.ts @@ -32,6 +32,10 @@ export interface IModelContentChange { * The range that got replaced. */ readonly range: IRange; + /** + * The offset of the range that got replaced. + */ + readonly rangeOffset: number; /** * The length of the range that got replaced. */ diff --git a/src/vs/monaco.d.ts b/src/vs/monaco.d.ts index 715a3ec520d..6c481514777 100644 --- a/src/vs/monaco.d.ts +++ b/src/vs/monaco.d.ts @@ -2196,6 +2196,10 @@ declare namespace monaco.editor { * The range that got replaced. */ readonly range: IRange; + /** + * The offset of the range that got replaced. + */ + readonly rangeOffset: number; /** * The length of the range that got replaced. */ diff --git a/src/vs/vscode.d.ts b/src/vs/vscode.d.ts index c841a93202a..8142906c5ec 100644 --- a/src/vs/vscode.d.ts +++ b/src/vs/vscode.d.ts @@ -5511,6 +5511,10 @@ declare module 'vscode' { * The range that got replaced. */ range: Range; + /** + * The offset of the range that got replaced. + */ + rangeOffset: number; /** * The length of the range that got replaced. */ diff --git a/src/vs/workbench/api/node/extHostDocuments.ts b/src/vs/workbench/api/node/extHostDocuments.ts index 05cae8cfcb6..54e17e42f71 100644 --- a/src/vs/workbench/api/node/extHostDocuments.ts +++ b/src/vs/workbench/api/node/extHostDocuments.ts @@ -137,6 +137,7 @@ export class ExtHostDocuments implements ExtHostDocumentsShape { contentChanges: events.changes.map((change) => { return { range: TypeConverters.toRange(change.range), + rangeOffset: change.rangeOffset, rangeLength: change.rangeLength, text: change.text }; diff --git a/src/vs/workbench/test/electron-browser/api/extHostDocumentData.test.ts b/src/vs/workbench/test/electron-browser/api/extHostDocumentData.test.ts index f808f44c17f..daad9c035e9 100644 --- a/src/vs/workbench/test/electron-browser/api/extHostDocumentData.test.ts +++ b/src/vs/workbench/test/electron-browser/api/extHostDocumentData.test.ts @@ -101,6 +101,7 @@ suite('ExtHostDocumentData', () => { data.onEvents({ changes: [{ range: { startLineNumber: 1, startColumn: 1, endLineNumber: 1, endColumn: 1 }, + rangeOffset: undefined, rangeLength: undefined, text: '\t ' }], @@ -157,6 +158,7 @@ suite('ExtHostDocumentData', () => { data.onEvents({ changes: [{ range: { startLineNumber: 1, startColumn: 3, endLineNumber: 1, endColumn: 6 }, + rangeOffset: undefined, rangeLength: undefined, text: '' }], @@ -174,6 +176,7 @@ suite('ExtHostDocumentData', () => { data.onEvents({ changes: [{ range: { startLineNumber: 1, startColumn: 3, endLineNumber: 1, endColumn: 6 }, + rangeOffset: undefined, rangeLength: undefined, text: 'is could be' }], @@ -191,6 +194,7 @@ suite('ExtHostDocumentData', () => { data.onEvents({ changes: [{ range: { startLineNumber: 1, startColumn: 3, endLineNumber: 1, endColumn: 6 }, + rangeOffset: undefined, rangeLength: undefined, text: 'is could be\na line with number' }], @@ -211,6 +215,7 @@ suite('ExtHostDocumentData', () => { data.onEvents({ changes: [{ range: { startLineNumber: 1, startColumn: 3, endLineNumber: 2, endColumn: 6 }, + rangeOffset: undefined, rangeLength: undefined, text: '' }], @@ -344,6 +349,7 @@ suite('ExtHostDocumentData updates line mapping', () => { return { changes: [{ range: range, + rangeOffset: undefined, rangeLength: undefined, text: text }], diff --git a/src/vs/workbench/test/electron-browser/api/extHostDocumentSaveParticipant.test.ts b/src/vs/workbench/test/electron-browser/api/extHostDocumentSaveParticipant.test.ts index b44d25fba63..a8c6bfbfcdf 100644 --- a/src/vs/workbench/test/electron-browser/api/extHostDocumentSaveParticipant.test.ts +++ b/src/vs/workbench/test/electron-browser/api/extHostDocumentSaveParticipant.test.ts @@ -302,6 +302,7 @@ suite('ExtHostDocumentSaveParticipant', () => { documents.$acceptModelChanged(resource, { changes: [{ range: { startLineNumber: 1, startColumn: 1, endLineNumber: 1, endColumn: 1 }, + rangeOffset: undefined, rangeLength: undefined, text: 'bar' }], @@ -337,6 +338,7 @@ suite('ExtHostDocumentSaveParticipant', () => { changes: [{ range, text, + rangeOffset: undefined, rangeLength: undefined, }], eol: undefined, From 51512bc594bcc6e6d0e67a86f2e617e78ef1913f Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Thu, 5 Apr 2018 12:23:30 +0200 Subject: [PATCH 46/49] #47180 Report install/uninstall events in extension management --- .../node/extensionManagementService.ts | 52 +++++++++++++++---- 1 file changed, 43 insertions(+), 9 deletions(-) diff --git a/src/vs/platform/extensionManagement/node/extensionManagementService.ts b/src/vs/platform/extensionManagement/node/extensionManagementService.ts index 7418a9e3985..cb95dff7fd3 100644 --- a/src/vs/platform/extensionManagement/node/extensionManagementService.ts +++ b/src/vs/platform/extensionManagement/node/extensionManagementService.ts @@ -22,7 +22,7 @@ import { IExtensionIdentifier, IReportedExtension } from 'vs/platform/extensionManagement/common/extensionManagement'; -import { getGalleryExtensionIdFromLocal, adoptToGalleryExtensionId, areSameExtensions, getGalleryExtensionId, groupByExtension, getMaliciousExtensionsSet, getLocalExtensionId } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; +import { getGalleryExtensionIdFromLocal, adoptToGalleryExtensionId, areSameExtensions, getGalleryExtensionId, groupByExtension, getMaliciousExtensionsSet, getLocalExtensionId, getGalleryExtensionTelemetryData, getLocalExtensionTelemetryData } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; import { localizeManifest } from '../common/extensionNls'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { Limiter, always } from 'vs/base/common/async'; @@ -37,6 +37,7 @@ import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; import Severity from 'vs/base/common/severity'; import { ExtensionsLifecycle } from 'vs/platform/extensionManagement/node/extensionLifecycle'; import { toErrorMessage } from 'vs/base/common/errorMessage'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; const SystemExtensionsRoot = path.normalize(path.join(URI.parse(require.toUrl('')).fsPath, '..', 'extensions')); const ERROR_SCANNING_SYS_EXTENSIONS = 'scanningSystem'; @@ -49,7 +50,7 @@ const INSTALL_ERROR_GALLERY = 'gallery'; const INSTALL_ERROR_LOCAL = 'local'; const INSTALL_ERROR_EXTRACTING = 'extracting'; const INSTALL_ERROR_DELETING = 'deleting'; -const INSTALL_ERROR_UNKNOWN = 'unknown'; +const ERROR_UNKNOWN = 'unknown'; export class ExtensionManagementError extends Error { constructor(message: string, readonly code: string) { @@ -108,6 +109,7 @@ export class ExtensionManagementService extends Disposable implements IExtension private uninstalledFileLimiter: Limiter; private reportedExtensions: TPromise | undefined; private lastReportTimestamp = 0; + private readonly installationStartTime: Map = new Map(); private readonly installingExtensions: Map> = new Map>(); private readonly manifestCache: ExtensionsManifestCache; private readonly extensionLifecycle: ExtensionsLifecycle; @@ -128,7 +130,8 @@ export class ExtensionManagementService extends Disposable implements IExtension @IEnvironmentService environmentService: IEnvironmentService, @IDialogService private dialogService: IDialogService, @IExtensionGalleryService private galleryService: IExtensionGalleryService, - @ILogService private logService: ILogService + @ILogService private logService: ILogService, + @ITelemetryService private telemetryService: ITelemetryService, ) { super(); this.extensionsPath = environmentService.extensionsPath; @@ -326,6 +329,7 @@ export class ExtensionManagementService extends Disposable implements IExtension private onInstallExtensions(extensions: IGalleryExtension[]): void { for (const extension of extensions) { this.logService.info('Installing extension:', extension.name); + this.installationStartTime.set(extension.identifier.id, new Date().getTime()); const id = getLocalExtensionIdFromGallery(extension, extension.version); this._onInstallExtension.fire({ identifier: { id, uuid: extension.identifier.uuid }, gallery: extension }); } @@ -340,10 +344,13 @@ export class ExtensionManagementService extends Disposable implements IExtension this.logService.info(`Extensions installed successfully:`, gallery.identifier.id); this._onDidInstallExtension.fire({ identifier, gallery, local }); } else { - const errorCode = error && (error).code ? (error).code : INSTALL_ERROR_UNKNOWN; + const errorCode = error && (error).code ? (error).code : ERROR_UNKNOWN; this.logService.error(`Failed to install extension:`, gallery.identifier.id, error ? error.message : errorCode); 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.installationStartTime.delete(gallery.identifier.id); }); return errors.length ? TPromise.wrapError(this.joinErrors(errors)) : TPromise.as(null); } @@ -524,7 +531,7 @@ export class ExtensionManagementService extends Disposable implements IExtension .then(() => this.hasDependencies(extension, installed) ? this.promptForDependenciesAndUninstall(extension, installed, force) : this.promptAndUninstall(extension, installed, force)) .then(() => this.postUninstallExtension(extension), error => { - this.postUninstallExtension(extension, INSTALL_ERROR_LOCAL); + this.postUninstallExtension(extension, new ExtensionManagementError(error instanceof Error ? error.message : error, INSTALL_ERROR_LOCAL)); return TPromise.wrapError(error); }); } @@ -644,7 +651,7 @@ export class ExtensionManagementService extends Disposable implements IExtension .then(() => this.uninstallExtension(extension)) .then(() => this.postUninstallExtension(extension), error => { - this.postUninstallExtension(extension, INSTALL_ERROR_LOCAL); + this.postUninstallExtension(extension, new ExtensionManagementError(error instanceof Error ? error.message : error, INSTALL_ERROR_LOCAL)); return TPromise.wrapError(error); }); } @@ -664,9 +671,9 @@ export class ExtensionManagementService extends Disposable implements IExtension .then(userExtensions => this.setUninstalled(...userExtensions.filter(u => areSameExtensions({ id: getGalleryExtensionIdFromLocal(u), uuid: u.identifier.uuid }, { id: getGalleryExtensionIdFromLocal(local), uuid: local.identifier.uuid })))); } - private async postUninstallExtension(extension: ILocalExtension, error?: string): TPromise { + private async postUninstallExtension(extension: ILocalExtension, error?: Error): TPromise { if (error) { - this.logService.error('Failed to uninstall extension:', extension.identifier.id, error); + this.logService.error('Failed to uninstall extension:', extension.identifier.id, error.message); } else { this.logService.info('Successfully uninstalled extension:', extension.identifier.id); // only report if extension has a mapped gallery extension. UUID identifies the gallery extension. @@ -674,7 +681,9 @@ export class ExtensionManagementService extends Disposable implements IExtension await this.galleryService.reportStatistic(extension.manifest.publisher, extension.manifest.name, extension.manifest.version, StatisticType.Uninstall); } } - this._onDidUninstallExtension.fire({ identifier: extension.identifier, error }); + this.reportTelemetry('extensionGallery:uninstall', getLocalExtensionTelemetryData(extension), void 0, error); + const errorcode = error ? error instanceof ExtensionManagementError ? error.code : ERROR_UNKNOWN : void 0; + this._onDidUninstallExtension.fire({ identifier: extension.identifier, error: errorcode }); } getInstalled(type: LocalExtensionType = null): TPromise { @@ -852,6 +861,31 @@ export class ExtensionManagementService extends Disposable implements IExtension return []; }); } + + private reportTelemetry(eventName: string, extensionData: any, duration: number, error?: Error): void { + const errorcode = error ? error instanceof ExtensionManagementError ? error.code : ERROR_UNKNOWN : void 0; + /* __GDPR__ + "extensionGallery:install" : { + "success": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true }, + "duration" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true }, + "errorcode": { "classification": "CallstackOrException", "purpose": "PerformanceAndHealth" }, + "${include}": [ + "${GalleryExtensionTelemetryData}" + ] + } + */ + /* __GDPR__ + "extensionGallery:uninstall" : { + "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 })); + } } export function getLocalExtensionIdFromGallery(extension: IGalleryExtension, version: string): string { From db7ddfd9a01370b9a2aa2c7e1beb22bcc16c0606 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Thu, 5 Apr 2018 12:30:17 +0200 Subject: [PATCH 47/49] Error code for rename --- .../extensionManagement/node/extensionManagementService.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/vs/platform/extensionManagement/node/extensionManagementService.ts b/src/vs/platform/extensionManagement/node/extensionManagementService.ts index cb95dff7fd3..29940b2d94e 100644 --- a/src/vs/platform/extensionManagement/node/extensionManagementService.ts +++ b/src/vs/platform/extensionManagement/node/extensionManagementService.ts @@ -49,6 +49,7 @@ const INSTALL_ERROR_VALIDATING = 'validating'; const INSTALL_ERROR_GALLERY = 'gallery'; const INSTALL_ERROR_LOCAL = 'local'; const INSTALL_ERROR_EXTRACTING = 'extracting'; +const INSTALL_ERROR_RENAMING = 'renaming'; const INSTALL_ERROR_DELETING = 'deleting'; const ERROR_UNKNOWN = 'unknown'; @@ -452,7 +453,7 @@ export class ExtensionManagementService extends Disposable implements IExtension .then(null, error => isWindows && error && error.code === 'EPERM' && Date.now() < retryUntil ? this.rename(id, extractPath, renamePath, retryUntil) - : TPromise.wrapError(error) + : TPromise.wrapError(new ExtensionManagementError(error.message || nls.localize('renameError', "Unknown error while"), error.code || INSTALL_ERROR_RENAMING)) ); } From 7dc5e68852536e1627736452557feab9c2fb7dc7 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Thu, 5 Apr 2018 12:41:29 +0200 Subject: [PATCH 48/49] #46750 Mark extensions.autoUpdate as application setting --- .../extensions/electron-browser/extensions.contribution.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/parts/extensions/electron-browser/extensions.contribution.ts b/src/vs/workbench/parts/extensions/electron-browser/extensions.contribution.ts index 9fcd5142a61..17911ae1a23 100644 --- a/src/vs/workbench/parts/extensions/electron-browser/extensions.contribution.ts +++ b/src/vs/workbench/parts/extensions/electron-browser/extensions.contribution.ts @@ -29,7 +29,7 @@ import { ViewletRegistry, Extensions as ViewletExtensions, ViewletDescriptor } f import { ExtensionEditor } from 'vs/workbench/parts/extensions/electron-browser/extensionEditor'; import { StatusUpdater, ExtensionsViewlet, MaliciousExtensionChecker } from 'vs/workbench/parts/extensions/electron-browser/extensionsViewlet'; import { IQuickOpenRegistry, Extensions, QuickOpenHandlerDescriptor } from 'vs/workbench/browser/quickopen'; -import { IConfigurationRegistry, Extensions as ConfigurationExtensions } from 'vs/platform/configuration/common/configurationRegistry'; +import { IConfigurationRegistry, Extensions as ConfigurationExtensions, ConfigurationScope } from 'vs/platform/configuration/common/configurationRegistry'; import * as jsonContributionRegistry from 'vs/platform/jsonschemas/common/jsonContributionRegistry'; import { ExtensionsConfigurationSchema, ExtensionsConfigurationSchemaId } from 'vs/workbench/parts/extensions/common/extensionsFileTemplate'; import { CommandsRegistry } from 'vs/platform/commands/common/commands'; @@ -204,7 +204,8 @@ Registry.as(ConfigurationExtensions.Configuration) 'extensions.autoUpdate': { type: 'boolean', description: localize('extensionsAutoUpdate', "Automatically update extensions"), - default: true + default: true, + scope: ConfigurationScope.APPLICATION }, 'extensions.ignoreRecommendations': { type: 'boolean', From 1e5c0c3e4e11af82e82d7761d7875646d1c6336b Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Thu, 5 Apr 2018 13:00:38 +0200 Subject: [PATCH 49/49] Remove back up buffered output channel --- .../output/electron-browser/outputServices.ts | 162 +----------------- 1 file changed, 5 insertions(+), 157 deletions(-) diff --git a/src/vs/workbench/parts/output/electron-browser/outputServices.ts b/src/vs/workbench/parts/output/electron-browser/outputServices.ts index 9fd70a0a2f6..b8d2a78d4a1 100644 --- a/src/vs/workbench/parts/output/electron-browser/outputServices.ts +++ b/src/vs/workbench/parts/output/electron-browser/outputServices.ts @@ -5,7 +5,6 @@ import * as nls from 'vs/nls'; import * as paths from 'vs/base/common/paths'; -import * as strings from 'vs/base/common/strings'; import * as extfs from 'vs/base/node/extfs'; import * as fs from 'fs'; import { TPromise } from 'vs/base/common/winjs.base'; @@ -16,7 +15,7 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; import { Registry } from 'vs/platform/registry/common/platform'; import { EditorOptions } from 'vs/workbench/common/editor'; -import { IOutputChannelIdentifier, IOutputChannel, IOutputService, Extensions, OUTPUT_PANEL_ID, IOutputChannelRegistry, OUTPUT_SCHEME, OUTPUT_MIME, MAX_OUTPUT_LENGTH, LOG_SCHEME, LOG_MIME, CONTEXT_ACTIVE_LOG_OUTPUT } from 'vs/workbench/parts/output/common/output'; +import { IOutputChannelIdentifier, IOutputChannel, IOutputService, Extensions, OUTPUT_PANEL_ID, IOutputChannelRegistry, OUTPUT_SCHEME, OUTPUT_MIME, LOG_SCHEME, LOG_MIME, CONTEXT_ACTIVE_LOG_OUTPUT } from 'vs/workbench/parts/output/common/output'; import { OutputPanel } from 'vs/workbench/parts/output/browser/outputPanel'; import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; import { IModelService } from 'vs/editor/common/services/modelService'; @@ -36,8 +35,6 @@ import { RotatingLogger } from 'spdlog'; import { toLocalISOString } from 'vs/base/common/date'; import { IWindowService } from 'vs/platform/windows/common/windows'; import { ILogService } from 'vs/platform/log/common/log'; -import { binarySearch } from 'vs/base/common/arrays'; -import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { Schemas } from 'vs/base/common/network'; import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; @@ -437,7 +434,6 @@ export class OutputService extends Disposable implements IOutputService, ITextMo @ITextModelService textModelResolverService: ITextModelService, @IEnvironmentService environmentService: IEnvironmentService, @IWindowService windowService: IWindowService, - @ITelemetryService private telemetryService: ITelemetryService, @ILogService private logService: ILogService, @ILifecycleService private lifecycleService: ILifecycleService, @IContextKeyService private contextKeyService: IContextKeyService, @@ -570,16 +566,9 @@ export class OutputService extends Disposable implements IOutputService, ITextMo } const uri = URI.from({ scheme: OUTPUT_SCHEME, path: id }); - if (channelData && channelData.file) { - return this.instantiationService.createInstance(FileOutputChannel, channelData, uri); - } - try { - return this.instantiationService.createInstance(OutputChannelBackedByFile, { id, label: channelData ? channelData.label : '' }, this.outputDir, uri); - } catch (e) { - this.logService.error(e); - this.telemetryService.publicLog('output.used.bufferedChannel'); - return this.instantiationService.createInstance(BufferredOutputChannel, { id, label: channelData ? channelData.label : '' }); - } + return channelData && channelData.file + ? this.instantiationService.createInstance(FileOutputChannel, channelData, uri) + : this.instantiationService.createInstance(OutputChannelBackedByFile, { id, label: channelData ? channelData.label : '' }, this.outputDir, uri); } private doShowChannel(channel: IOutputChannel, preserveFocus: boolean): TPromise { @@ -647,145 +636,4 @@ export class LogContentProvider { } return channel; } -} - -// Remove this channel when there are no issues using Output channel backed by file -class BufferredOutputChannel extends Disposable implements OutputChannel { - - readonly id: string; - readonly label: string; - readonly file: URI = null; - scrollLock: boolean = false; - - protected _onDidAppendedContent: Emitter = new Emitter(); - readonly onDidAppendedContent: Event = this._onDidAppendedContent.event; - - private readonly _onDispose: Emitter = new Emitter(); - readonly onDispose: Event = this._onDispose.event; - - private modelUpdater: RunOnceScheduler; - private model: ITextModel; - private readonly bufferredContent: BufferedContent; - private lastReadId: number = void 0; - - constructor( - protected readonly outputChannelIdentifier: IOutputChannelIdentifier, - @IModelService private modelService: IModelService, - @IModeService private modeService: IModeService - ) { - super(); - - this.id = outputChannelIdentifier.id; - this.label = outputChannelIdentifier.label; - - this.modelUpdater = new RunOnceScheduler(() => this.updateModel(), 300); - this._register(toDisposable(() => this.modelUpdater.cancel())); - - this.bufferredContent = new BufferedContent(); - this._register(toDisposable(() => this.bufferredContent.clear())); - } - - append(output: string) { - this.bufferredContent.append(output); - if (!this.modelUpdater.isScheduled()) { - this.modelUpdater.schedule(); - } - } - - clear(): void { - if (this.modelUpdater.isScheduled()) { - this.modelUpdater.cancel(); - } - if (this.model) { - this.model.setValue(''); - } - this.bufferredContent.clear(); - this.lastReadId = void 0; - } - - loadModel(): TPromise { - const { value, id } = this.bufferredContent.getDelta(this.lastReadId); - if (this.model) { - this.model.setValue(value); - } else { - this.model = this.createModel(value); - } - this.lastReadId = id; - return TPromise.as(this.model); - } - - private createModel(content: string): ITextModel { - const model = this.modelService.createModel(content, this.modeService.getOrCreateMode(OUTPUT_MIME), URI.from({ scheme: OUTPUT_SCHEME, path: this.id })); - const disposables: IDisposable[] = []; - disposables.push(model.onWillDispose(() => { - this.model = null; - dispose(disposables); - })); - return model; - } - - private updateModel(): void { - if (this.model) { - const { value, id } = this.bufferredContent.getDelta(this.lastReadId); - this.lastReadId = id; - const lastLine = this.model.getLineCount(); - const lastLineMaxColumn = this.model.getLineMaxColumn(lastLine); - this.model.applyEdits([EditOperation.insert(new Position(lastLine, lastLineMaxColumn), value)]); - this._onDidAppendedContent.fire(); - } - } - - dispose(): void { - this._onDispose.fire(); - super.dispose(); - } -} - -class BufferedContent { - - private data: string[] = []; - private dataIds: number[] = []; - private idPool = 0; - private length = 0; - - public append(content: string): void { - this.data.push(content); - this.dataIds.push(++this.idPool); - this.length += content.length; - this.trim(); - } - - public clear(): void { - this.data.length = 0; - this.dataIds.length = 0; - this.length = 0; - } - - private trim(): void { - if (this.length < MAX_OUTPUT_LENGTH * 1.2) { - return; - } - - while (this.length > MAX_OUTPUT_LENGTH) { - this.dataIds.shift(); - const removed = this.data.shift(); - this.length -= removed.length; - } - } - - public getDelta(previousId?: number): { value: string, id: number } { - let idx = -1; - if (previousId !== void 0) { - idx = binarySearch(this.dataIds, previousId, (a, b) => a - b); - } - - const id = this.idPool; - if (idx >= 0) { - const value = strings.removeAnsiEscapeCodes(this.data.slice(idx + 1).join('')); - return { value, id }; - } else { - const value = strings.removeAnsiEscapeCodes(this.data.join('')); - return { value, id }; - } - } -} +} \ No newline at end of file