From c37579fc4d626601ce04d7c6561c0f859c26cf00 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Fri, 17 Apr 2020 17:53:52 +0200 Subject: [PATCH 01/44] extract NotImplementedProxy-util to types --- src/vs/base/common/types.ts | 16 ++++++++++++++++ .../extensions/worker/extHost.services.ts | 19 ++----------------- 2 files changed, 18 insertions(+), 17 deletions(-) diff --git a/src/vs/base/common/types.ts b/src/vs/base/common/types.ts index 42eea76fe40..634c6e5143a 100644 --- a/src/vs/base/common/types.ts +++ b/src/vs/base/common/types.ts @@ -258,3 +258,19 @@ export type Dto = { [K in keyof T]: T[K] extends URI : T[K] extends Function ? never : UriDto }; + + +export function NotImplementedProxy(name: string): { new(): T } { + return class { + constructor() { + return new Proxy({}, { + get(target: any, prop: PropertyKey) { + if (target[prop]) { + return target[prop]; + } + throw new Error(`Not Implemented: ${name}->${String(prop)}`); + } + }); + } + }; +} diff --git a/src/vs/workbench/services/extensions/worker/extHost.services.ts b/src/vs/workbench/services/extensions/worker/extHost.services.ts index 90568629458..100864519dc 100644 --- a/src/vs/workbench/services/extensions/worker/extHost.services.ts +++ b/src/vs/workbench/services/extensions/worker/extHost.services.ts @@ -18,11 +18,11 @@ import { IExtensionStoragePaths } from 'vs/workbench/api/common/extHostStoragePa import { IExtHostExtensionService } from 'vs/workbench/api/common/extHostExtensionService'; import { IExtHostStorage, ExtHostStorage } from 'vs/workbench/api/common/extHostStorage'; import { ExtHostExtensionService } from 'vs/workbench/api/worker/extHostExtensionService'; -import { ServiceIdentifier } from 'vs/platform/instantiation/common/instantiation'; import { ILogService } from 'vs/platform/log/common/log'; import { ExtHostLogService } from 'vs/workbench/api/worker/extHostLogService'; import { IExtHostTunnelService, ExtHostTunnelService } from 'vs/workbench/api/common/extHostTunnelService'; import { IExtHostApiDeprecationService, ExtHostApiDeprecationService, } from 'vs/workbench/api/common/extHostApiDeprecationService'; +import { NotImplementedProxy } from 'vs/base/common/types'; // register singleton services registerSingleton(ILogService, ExtHostLogService); @@ -38,22 +38,7 @@ registerSingleton(IExtHostExtensionService, ExtHostExtensionService); registerSingleton(IExtHostSearch, ExtHostSearch); registerSingleton(IExtHostTunnelService, ExtHostTunnelService); -// register services that only throw errors -function NotImplementedProxy(name: ServiceIdentifier): { new(): T } { - return class { - constructor() { - return new Proxy({}, { - get(target: any, prop: PropertyKey) { - if (target[prop]) { - return target[prop]; - } - throw new Error(`Not Implemented: ${name}->${String(prop)}`); - } - }); - } - }; -} registerSingleton(IExtHostTerminalService, WorkerExtHostTerminalService); registerSingleton(IExtHostTask, WorkerExtHostTask); registerSingleton(IExtHostDebugService, WorkerExtHostDebugService); -registerSingleton(IExtensionStoragePaths, class extends NotImplementedProxy(IExtensionStoragePaths) { whenReady = Promise.resolve(); }); +registerSingleton(IExtensionStoragePaths, class extends NotImplementedProxy(String(IExtensionStoragePaths)) { whenReady = Promise.resolve(); }); From 1e8f97d997555d1b0027557aab10ea114c3623ee Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Fri, 17 Apr 2020 17:57:00 +0200 Subject: [PATCH 02/44] wip - concat document for notebook cells --- src/vs/vscode.proposed.d.ts | 2 + .../workbench/api/common/extHostNotebook.ts | 90 ++++++++------- .../common/extHostNotebookConcatDocument.ts | 107 ++++++++++++++++++ 3 files changed, 156 insertions(+), 43 deletions(-) create mode 100644 src/vs/workbench/api/common/extHostNotebookConcatDocument.ts diff --git a/src/vs/vscode.proposed.d.ts b/src/vs/vscode.proposed.d.ts index 66297865982..11a7edb1316 100644 --- a/src/vs/vscode.proposed.d.ts +++ b/src/vs/vscode.proposed.d.ts @@ -1558,6 +1558,8 @@ declare module 'vscode' { export interface NotebookCell { readonly uri: Uri; readonly cellKind: CellKind; + readonly document: TextDocument; + // API remove `source` or doc it as shorthand for document.getText() readonly source: string; language: string; outputs: CellOutput[]; diff --git a/src/vs/workbench/api/common/extHostNotebook.ts b/src/vs/workbench/api/common/extHostNotebook.ts index 5e2ce8a9c3a..177d50c8cf5 100644 --- a/src/vs/workbench/api/common/extHostNotebook.ts +++ b/src/vs/workbench/api/common/extHostNotebook.ts @@ -10,12 +10,14 @@ import { Disposable, DisposableStore, IDisposable } from 'vs/base/common/lifecyc import { ISplice } from 'vs/base/common/sequence'; import { URI, UriComponents } from 'vs/base/common/uri'; import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; -import { CellKind, CellOutputKind, ExtHostNotebookShape, IMainContext, MainContext, MainThreadNotebookShape, NotebookCellOutputsSplice } from 'vs/workbench/api/common/extHost.protocol'; +import { CellKind, CellOutputKind, ExtHostNotebookShape, IMainContext, MainContext, MainThreadNotebookShape, NotebookCellOutputsSplice, MainThreadDocumentsShape } from 'vs/workbench/api/common/extHost.protocol'; import { ExtHostCommands } from 'vs/workbench/api/common/extHostCommands'; import { ExtHostDocumentsAndEditors } from 'vs/workbench/api/common/extHostDocumentsAndEditors'; import { CellEditType, CellUri, diff, ICellEditOperation, ICellInsertEdit, IErrorOutput, INotebookDisplayOrder, INotebookEditData, IOrderedMimeType, IStreamOutput, ITransformedDisplayOutputDto, mimeTypeSupportedByCore, NotebookCellsChangedEvent, NotebookCellsSplice2, sortMimeTypes, ICellDeleteEdit, notebookDocumentMetadataDefaults } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { Disposable as VSCodeDisposable } from './extHostTypes'; import { CancellationToken } from 'vs/base/common/cancellation'; +import { ExtHostDocumentData } from 'vs/workbench/api/common/extHostDocumentData'; +import { NotImplementedProxy } from 'vs/base/common/types'; interface IObservable { proxy: T; @@ -40,39 +42,48 @@ function getObservable(obj: T): IObservable { export class ExtHostCell extends Disposable implements vscode.NotebookCell { - private originalSource: string[]; + // private originalSource: string[]; private _outputs: any[]; private _onDidChangeOutputs = new Emitter[]>(); onDidChangeOutputs: Event[]> = this._onDidChangeOutputs.event; - private _textDocument: vscode.TextDocument | undefined; - private _initalVersion: number = -1; + // private _textDocument: vscode.TextDocument | undefined; + // private _initalVersion: number = -1; private _outputMapping = new Set(); private _metadata: vscode.NotebookCellMetadata; private _metadataChangeListener: IDisposable; + private _documentData: ExtHostDocumentData; + + get document(): vscode.TextDocument { + return this._documentData.document; + } + get source() { - if (this._textDocument && this._initalVersion !== this._textDocument?.version) { - return this._textDocument.getText(); - } else { - return this.originalSource.join('\n'); - } + // todo@jrieken remove this + return this._documentData.getText(); } constructor( - private viewType: string, - private documentUri: URI, + private readonly viewType: string, + private readonly documentUri: URI, readonly handle: number, readonly uri: URI, - private _content: string, + content: string, public readonly cellKind: CellKind, public language: string, outputs: any[], _metadata: vscode.NotebookCellMetadata | undefined, - private _proxy: MainThreadNotebookShape + private _proxy: MainThreadNotebookShape, ) { super(); - this.originalSource = this._content.split(/\r|\n|\r\n/g); + this._documentData = new ExtHostDocumentData( + new class extends NotImplementedProxy('document') { }, + uri, + content.split(/\r|\n|\r\n/g), '\n', + language, 0, false + ); + this._outputs = outputs; const observableMetadata = getObservable(_metadata || {}); @@ -125,26 +136,19 @@ export class ExtHostCell extends Disposable implements vscode.NotebookCell { return this._proxy.$updateNotebookCellMetadata(this.viewType, this.documentUri, this.handle, this._metadata); } - getContent(): string { - if (this._textDocument && this._initalVersion !== this._textDocument?.version) { - return this._textDocument.getText(); - } else { - return this.originalSource.join('\n'); - } - } - - attachTextDocument(document: vscode.TextDocument) { - this._textDocument = document; - this._initalVersion = this._textDocument.version; + attachTextDocument(document: ExtHostDocumentData) { + this._documentData = document; + // this._initalVersion = this._documentData.version; } detachTextDocument() { - if (this._textDocument && this._textDocument.version !== this._initalVersion) { - this.originalSource = this._textDocument.getText().split(/\r|\n|\r\n/g); - } + // no-op? keep stale document until new comes along? - this._textDocument = undefined; - this._initalVersion = -1; + // if (this._textDocument && this._textDocument.version !== this._initalVersion) { + // this.originalSource = this._textDocument.getText().split(/\r|\n|\r\n/g); + // } + // this._textDocument = undefined; + // this._initalVersion = -1; } } @@ -256,10 +260,10 @@ export class ExtHostNotebookDocument extends Disposable implements vscode.Notebo let cellDtos = splice[2]; let newCells = cellDtos.map(cell => { const extCell = new ExtHostCell(this.viewType, this.uri, cell.handle, URI.revive(cell.uri), cell.source.join('\n'), cell.cellKind, cell.language, cell.outputs, cell.metadata, this._proxy); - const document = this._documentsAndEditors.getDocument(URI.revive(cell.uri)); + const documentData = this._documentsAndEditors.getDocument(URI.revive(cell.uri)); - if (document) { - extCell.attachTextDocument(document.document); + if (documentData) { + extCell.attachTextDocument(documentData); } if (!this._cellDisposableMapping.has(extCell.handle)) { @@ -366,15 +370,15 @@ export class ExtHostNotebookDocument extends Disposable implements vscode.Notebo return this.cells.find(cell => cell.handle === cellHandle); } - attachCellTextDocument(textDocument: vscode.TextDocument) { - let cell = this.cells.find(cell => cell.uri.toString() === textDocument.uri.toString()); + attachCellTextDocument(textDocument: ExtHostDocumentData) { + let cell = this.cells.find(cell => cell.uri.toString() === textDocument.document.uri.toString()); if (cell) { cell.attachTextDocument(textDocument); } } - detachCellTextDocument(textDocument: vscode.TextDocument) { - let cell = this.cells.find(cell => cell.uri.toString() === textDocument.uri.toString()); + detachCellTextDocument(textDocument: ExtHostDocumentData) { + let cell = this.cells.find(cell => cell.uri.toString() === textDocument.document.uri.toString()); if (cell) { cell.detachTextDocument(); } @@ -468,22 +472,22 @@ export class ExtHostNotebookEditor extends Disposable implements vscode.Notebook ) { super(); this._register(this._documentsAndEditors.onDidAddDocuments(documents => { - for (const { document: textDocument } of documents) { - let data = CellUri.parse(textDocument.uri); + for (const documentData of documents) { + let data = CellUri.parse(documentData.document.uri); if (data) { if (this.document.uri.toString() === data.notebook.toString()) { - document.attachCellTextDocument(textDocument); + document.attachCellTextDocument(documentData); } } } })); this._register(this._documentsAndEditors.onDidRemoveDocuments(documents => { - for (const { document: textDocument } of documents) { - let data = CellUri.parse(textDocument.uri); + for (const documentData of documents) { + let data = CellUri.parse(documentData.document.uri); if (data) { if (this.document.uri.toString() === data.notebook.toString()) { - document.detachCellTextDocument(textDocument); + document.detachCellTextDocument(documentData); } } } diff --git a/src/vs/workbench/api/common/extHostNotebookConcatDocument.ts b/src/vs/workbench/api/common/extHostNotebookConcatDocument.ts new file mode 100644 index 00000000000..260ac62bd3c --- /dev/null +++ b/src/vs/workbench/api/common/extHostNotebookConcatDocument.ts @@ -0,0 +1,107 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as types from 'vs/workbench/api/common/extHostTypes'; +import * as vscode from 'vscode'; +import { Event, Emitter } from 'vs/base/common/event'; +import { ExtHostNotebookDocument } from 'vs/workbench/api/common/extHostNotebook'; +import { ExtHostDocuments } from 'vs/workbench/api/common/extHostDocuments'; +import { PrefixSumComputer } from 'vs/editor/common/viewModel/prefixSumComputer'; +import { ExtHostDocumentData } from 'vs/workbench/api/common/extHostDocumentData'; +import { Range } from 'vs/workbench/api/common/extHostTypeConverters'; + +//todo@jrieken ConcatDiagnosticsCollection... + +export interface INotebookConcatDocument { + readonly versionId: number; + readonly onDidChange: Event; + dispose(): void; + getText(): string; + locationAt(position: types.Position): types.Location; + positionAt(location: types.Location): types.Position; +} + +export async function createConcatDocument(document: ExtHostNotebookDocument, extHostDocument: ExtHostDocuments) { + + // const cells = new Map(); + + const _onDidChange = new Emitter(); + const listener = extHostDocument.onDidChangeDocument(e => { + const key = e.document.uri.toString(); + const cell = document.cells.find(candidate => candidate.uri.toString() === key); + if (cell) { + + // todo@jrieken reuse raw event! + concatDocument.onEvents({ + versionId: concatDocument.version + 1, + eol: '\n', + changes: e.contentChanges.map(change => { + return { + range: Range.from(change.range), + rangeOffset: change.rangeOffset, + rangeLength: change.rangeLength, + text: change.text, + }; + }) + }); + + _onDidChange.fire(); + } + }); + + function dispose(): void { + listener.dispose(); + concatDocument.dispose(); + } + + const lines: string[] = []; + const values = new Uint32Array(document.cells.length); + for (let i = 0; i < document.cells.length; i++) { + + const cell = document.cells[i]; + + // update prefix sum + values[i] = cell.document.getText().length + 1; // 1 is newline + + //todo@jrieken reuse lines! + for (let line = 0; line < cell.document.lineCount; line++) { + lines.push(cell.document.lineAt(line).text); + } + } + + const cellStarts = new PrefixSumComputer(values); + const concatDocument = new ExtHostDocumentData( + null!, + document.uri.with({ scheme: 'vscode-concatdoc' }), + lines, '\n', + document.languages[0], + 0, false + ); + + return { + get versionId() { return concatDocument.version; }, + onDidChange: _onDidChange.event, + dispose, + getText() { return concatDocument.getText(); }, + locationAt(position: types.Position): types.Location { + const offset = concatDocument.document.offsetAt(position); + const index = cellStarts.getIndexOf(offset); + const cell = document.cells[index.index]; + const cellPosi = cell.document.positionAt(index.remainder); + return new types.Location(cell.uri, cellPosi); + }, + positionAt(location: types.Location): vscode.Position | undefined { + const idx = document.cells.findIndex(candidate => candidate.uri.toString() === location.uri.toString()); + if (idx > 0) { + return undefined; + } + const docOffset = document.cells[idx].document.offsetAt(location.range.start); + const cellOffset = cellStarts.getAccumulatedValue(idx); + return concatDocument.document.positionAt(docOffset + cellOffset); + } + }; +} + + From b419aee673c3708e39633c0f1bc55d78e480a0e2 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Fri, 17 Apr 2020 18:21:30 +0200 Subject: [PATCH 03/44] view model skeleton --- src/vs/workbench/common/views.ts | 34 +++++++++++++++++++++++++++++--- 1 file changed, 31 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/common/views.ts b/src/vs/workbench/common/views.ts index c303cd3c1ab..dcf23586400 100644 --- a/src/vs/workbench/common/views.ts +++ b/src/vs/workbench/common/views.ts @@ -207,11 +207,39 @@ export interface IViewDescriptor { readonly remoteAuthority?: string | string[]; } +export interface IViewDescriptorRef { + viewDescriptor: IViewDescriptor; + index: number; +} + +export interface IAddedViewDescriptorRef extends IViewDescriptorRef { + collapsed: boolean; + size?: number; +} + export interface IViewDescriptorCollection extends IDisposable { - readonly onDidChangeViews: Event<{ added: IViewDescriptor[], removed: IViewDescriptor[] }>; - readonly onDidChangeActiveViews: Event<{ added: IViewDescriptor[], removed: IViewDescriptor[] }>; - readonly activeViewDescriptors: IViewDescriptor[]; + readonly allViewDescriptors: IViewDescriptor[]; + readonly onDidChangeViews: Event<{ added: IViewDescriptor[], removed: IViewDescriptor[] }>; + + readonly activeViewDescriptors: IViewDescriptor[]; + readonly onDidChangeActiveViews: Event<{ added: IViewDescriptor[], removed: IViewDescriptor[] }>; + + readonly visibleViewDescriptors: IViewDescriptor[]; + readonly onDidAdd: Event; + readonly onDidRemove: Event + readonly onDidMove: Event<{ from: IViewDescriptorRef; to: IViewDescriptorRef; }> + + isVisible(id: string): boolean; + setVisible(id: string, visible: boolean, size?: number): void; + + isCollapsed(id: string): boolean; + setCollapsed(id: string, collapsed: boolean): void; + + getSize(id: string): number | undefined; + setSize(id: string, size: number): void + + move(from: string, to: string): void; } export enum ViewContentPriority { From f0ab6627c474628443cac0e827f19db9fbe1c718 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Fri, 17 Apr 2020 19:40:52 +0200 Subject: [PATCH 04/44] move and implement views model to view descriptors model --- .../api/browser/mainThreadComments.ts | 3 +- .../api/browser/viewsExtensionPoint.ts | 2 +- .../browser/parts/views/viewPaneContainer.ts | 46 +- src/vs/workbench/browser/parts/views/views.ts | 488 +------------- .../browser/parts/views/viewsViewlet.ts | 19 +- src/vs/workbench/common/views.ts | 4 +- .../bulkEdit/browser/bulkEdit.contribution.ts | 5 +- .../debug/browser/debug.contribution.ts | 3 +- .../contrib/debug/browser/debugViewlet.ts | 2 +- .../extensions/browser/extensionsViewlet.ts | 5 +- .../contrib/files/browser/explorerViewlet.ts | 5 +- .../markers/browser/markers.contribution.ts | 3 +- .../output/browser/output.contribution.ts | 3 +- .../contrib/remote/browser/remote.ts | 3 +- .../contrib/scm/browser/scm.contribution.ts | 1 + .../contrib/scm/browser/scmViewlet.ts | 27 +- .../search/browser/search.contribution.ts | 2 +- .../terminal/browser/terminal.contribution.ts | 3 +- .../userDataSync/browser/userDataSyncView.ts | 2 +- .../views/browser/viewDescriptorService.ts | 174 +---- .../services/views/common/viewsModel.ts | 635 ++++++++++++++++++ .../browser/viewDescriptorService.test.ts | 265 ++++++++ .../views/test/browser/viewsModel.test.ts | 375 +++++++++++ .../test/browser/parts/views/views.test.ts | 628 ----------------- 24 files changed, 1379 insertions(+), 1324 deletions(-) create mode 100644 src/vs/workbench/services/views/common/viewsModel.ts create mode 100644 src/vs/workbench/services/views/test/browser/viewDescriptorService.test.ts create mode 100644 src/vs/workbench/services/views/test/browser/viewsModel.test.ts delete mode 100644 src/vs/workbench/test/browser/parts/views/views.test.ts diff --git a/src/vs/workbench/api/browser/mainThreadComments.ts b/src/vs/workbench/api/browser/mainThreadComments.ts index b6bf77177b8..1963845b781 100644 --- a/src/vs/workbench/api/browser/mainThreadComments.ts +++ b/src/vs/workbench/api/browser/mainThreadComments.ts @@ -451,7 +451,8 @@ export class MainThreadComments extends Disposable implements MainThreadComments const VIEW_CONTAINER: ViewContainer = Registry.as(ViewExtensions.ViewContainersRegistry).registerViewContainer({ id: COMMENTS_VIEW_ID, name: COMMENTS_VIEW_TITLE, - ctorDescriptor: new SyncDescriptor(ViewPaneContainer, [COMMENTS_VIEW_ID, COMMENTS_VIEW_TITLE, { mergeViewWithContainerWhenSingleView: true, donotShowContainerTitleWhenMergedWithContainer: true }]), + ctorDescriptor: new SyncDescriptor(ViewPaneContainer, [COMMENTS_VIEW_ID, { mergeViewWithContainerWhenSingleView: true, donotShowContainerTitleWhenMergedWithContainer: true }]), + storageId: COMMENTS_VIEW_TITLE, hideIfEmpty: true, order: 10, }, ViewContainerLocation.Panel); diff --git a/src/vs/workbench/api/browser/viewsExtensionPoint.ts b/src/vs/workbench/api/browser/viewsExtensionPoint.ts index 354fbf83287..7a0e83d8ba8 100644 --- a/src/vs/workbench/api/browser/viewsExtensionPoint.ts +++ b/src/vs/workbench/api/browser/viewsExtensionPoint.ts @@ -320,7 +320,7 @@ class ViewsExtensionHandler implements IWorkbenchContribution { name: title, extensionId, ctorDescriptor: new SyncDescriptor( ViewPaneContainer, - [id, `${id}.state`, { mergeViewWithContainerWhenSingleView: true }] + [id, { mergeViewWithContainerWhenSingleView: true }] ), hideIfEmpty: true, order, diff --git a/src/vs/workbench/browser/parts/views/viewPaneContainer.ts b/src/vs/workbench/browser/parts/views/viewPaneContainer.ts index 7e771ce718f..4e3ff25052a 100644 --- a/src/vs/workbench/browser/parts/views/viewPaneContainer.ts +++ b/src/vs/workbench/browser/parts/views/viewPaneContainer.ts @@ -24,11 +24,10 @@ import { PaneView, IPaneViewOptions, IPaneOptions, Pane } from 'vs/base/browser/ import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IWorkbenchLayoutService, Position } from 'vs/workbench/services/layout/browser/layoutService'; import { StandardMouseEvent } from 'vs/base/browser/mouseEvent'; -import { Extensions as ViewContainerExtensions, IView, FocusedViewContext, IViewContainersRegistry, IViewDescriptor, ViewContainer, IViewDescriptorService, ViewContainerLocation, IViewPaneContainer, IViewsRegistry, IViewContentDescriptor } from 'vs/workbench/common/views'; +import { Extensions as ViewContainerExtensions, IView, FocusedViewContext, IViewContainersRegistry, IViewDescriptor, ViewContainer, IViewDescriptorService, ViewContainerLocation, IViewPaneContainer, IViewsRegistry, IViewContentDescriptor, IAddedViewDescriptorRef, IViewDescriptorRef, IViewDescriptorCollection } from 'vs/workbench/common/views'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { assertIsDefined } from 'vs/base/common/types'; -import { PersistentContributableViewsModel, IAddedViewDescriptorRef, IViewDescriptorRef } from 'vs/workbench/browser/parts/views/views'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IViewletViewOptions } from 'vs/workbench/browser/parts/views/viewsViewlet'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; @@ -746,7 +745,7 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer { private readonly visibleViewsCountFromCache: number | undefined; private readonly visibleViewsStorageId: string; - protected readonly viewsModel: PersistentContributableViewsModel; + protected readonly viewsDescriptors: IViewDescriptorCollection; private viewDisposables: IDisposable[] = []; private readonly _onTitleAreaUpdate: Emitter = this._register(new Emitter()); @@ -782,7 +781,6 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer { constructor( id: string, - viewPaneContainerStateStorageId: string, private options: IViewPaneContainerOptions, @IInstantiationService protected instantiationService: IInstantiationService, @IConfigurationService protected configurationService: IConfigurationService, @@ -808,7 +806,7 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer { this.visibleViewsStorageId = `${id}.numberOfVisibleViews`; this.visibleViewsCountFromCache = this.storageService.getNumber(this.visibleViewsStorageId, StorageScope.WORKSPACE, undefined); this._register(toDisposable(() => this.viewDisposables = dispose(this.viewDisposables))); - this.viewsModel = this._register(this.instantiationService.createInstance(PersistentContributableViewsModel, container, viewPaneContainerStateStorageId)); + this.viewsDescriptors = this.viewDescriptorService.getViewDescriptors(container); } create(parent: HTMLElement): void { @@ -884,11 +882,11 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer { })); this._register(this.onDidSashChange(() => this.saveViewSizes())); - this.viewsModel.onDidAdd(added => this.onDidAddViewDescriptors(added)); - this.viewsModel.onDidRemove(removed => this.onDidRemoveViewDescriptors(removed)); - const addedViews: IAddedViewDescriptorRef[] = this.viewsModel.visibleViewDescriptors.map((viewDescriptor, index) => { - const size = this.viewsModel.getSize(viewDescriptor.id); - const collapsed = this.viewsModel.isCollapsed(viewDescriptor.id); + this.viewsDescriptors.onDidAdd(added => this.onDidAddViewDescriptors(added)); + this.viewsDescriptors.onDidRemove(removed => this.onDidRemoveViewDescriptors(removed)); + const addedViews: IAddedViewDescriptorRef[] = this.viewsDescriptors.visibleViewDescriptors.map((viewDescriptor, index) => { + const size = this.viewsDescriptors.getSize(viewDescriptor.id); + const collapsed = this.viewsDescriptors.isCollapsed(viewDescriptor.id); return ({ viewDescriptor, index, size, collapsed }); }); if (addedViews.length) { @@ -959,10 +957,10 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer { } } - const viewToggleActions = this.viewsModel.viewDescriptors.map(viewDescriptor => ({ + const viewToggleActions = this.viewsDescriptors.activeViewDescriptors.map(viewDescriptor => ({ id: `${viewDescriptor.id}.toggleVisibility`, label: viewDescriptor.name, - checked: this.viewsModel.isVisible(viewDescriptor.id), + checked: this.viewsDescriptors.isVisible(viewDescriptor.id), enabled: viewDescriptor.canToggleVisibility, run: () => this.toggleViewVisibility(viewDescriptor.id) })); @@ -1091,7 +1089,7 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer { // Save size only when the layout has happened if (this.didLayout) { for (const view of this.panes) { - this.viewsModel.setSize(view.id, this.getPaneSize(view)); + this.viewsDescriptors.setSize(view.id, this.getPaneSize(view)); } } } @@ -1100,10 +1098,10 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer { // Restore sizes only when the layout has happened if (this.didLayout) { let initialSizes; - for (let i = 0; i < this.viewsModel.visibleViewDescriptors.length; i++) { + for (let i = 0; i < this.viewsDescriptors.visibleViewDescriptors.length; i++) { const pane = this.panes[i]; - const viewDescriptor = this.viewsModel.visibleViewDescriptors[i]; - const size = this.viewsModel.getSize(viewDescriptor.id); + const viewDescriptor = this.viewsDescriptors.visibleViewDescriptors[i]; + const size = this.viewsDescriptors.getSize(viewDescriptor.id); if (typeof size === 'number') { this.resizePane(pane, size); @@ -1118,8 +1116,8 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer { private computeInitialSizes(): Map { const sizes: Map = new Map(); if (this.dimension) { - const totalWeight = this.viewsModel.visibleViewDescriptors.reduce((totalWeight, { weight }) => totalWeight + (weight || 20), 0); - for (const viewDescriptor of this.viewsModel.visibleViewDescriptors) { + const totalWeight = this.viewsDescriptors.visibleViewDescriptors.reduce((totalWeight, { weight }) => totalWeight + (weight || 20), 0); + for (const viewDescriptor of this.viewsDescriptors.visibleViewDescriptors) { if (this.orientation === Orientation.VERTICAL) { sizes.set(viewDescriptor.id, this.dimension.height * (viewDescriptor.weight || 20) / totalWeight); } else { @@ -1180,7 +1178,7 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer { }); const collapseDisposable = Event.latch(Event.map(pane.onDidChange, () => !pane.isExpanded()))(collapsed => { - this.viewsModel.setCollapsed(viewDescriptor.id, collapsed); + this.viewsDescriptors.setCollapsed(viewDescriptor.id, collapsed); }); this.viewDisposables.splice(index, 0, combinedDisposable(contextMenuDisposable, collapseDisposable)); @@ -1211,13 +1209,13 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer { } protected toggleViewVisibility(viewId: string): void { - const visible = !this.viewsModel.isVisible(viewId); + const visible = !this.viewsDescriptors.isVisible(viewId); type ViewsToggleVisibilityClassification = { viewId: { classification: 'SystemMetaData', purpose: 'FeatureInsight' }; visible: { classification: 'SystemMetaData', purpose: 'FeatureInsight' }; }; this.telemetryService.publicLog2<{ viewId: String, visible: boolean }, ViewsToggleVisibilityClassification>('views.toggleVisibility', { viewId, visible }); - this.viewsModel.setVisible(viewId, visible); + this.viewsDescriptors.setVisible(viewId, visible); } private addPane(pane: ViewPane, size: number, index = this.paneItems.length - 1): void { @@ -1409,8 +1407,8 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer { const fromIndex = firstIndex(this.paneItems, item => item.pane === from); const toIndex = firstIndex(this.paneItems, item => item.pane === to); - const fromViewDescriptor = this.viewsModel.visibleViewDescriptors[fromIndex]; - const toViewDescriptor = this.viewsModel.visibleViewDescriptors[toIndex]; + const fromViewDescriptor = this.viewsDescriptors.visibleViewDescriptors[fromIndex]; + const toViewDescriptor = this.viewsDescriptors.visibleViewDescriptors[toIndex]; if (fromIndex < 0 || fromIndex >= this.paneItems.length) { return; @@ -1425,7 +1423,7 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer { assertIsDefined(this.paneview).movePane(from, to); - this.viewsModel.move(fromViewDescriptor.id, toViewDescriptor.id); + this.viewsDescriptors.move(fromViewDescriptor.id, toViewDescriptor.id); this.updateTitleArea(); } diff --git a/src/vs/workbench/browser/parts/views/views.ts b/src/vs/workbench/browser/parts/views/views.ts index 42ae7b7deda..1050408d134 100644 --- a/src/vs/workbench/browser/parts/views/views.ts +++ b/src/vs/workbench/browser/parts/views/views.ts @@ -7,16 +7,14 @@ import 'vs/css!./media/views'; import { Disposable, IDisposable, toDisposable, DisposableStore } from 'vs/base/common/lifecycle'; import { IViewDescriptorService, ViewContainer, IViewDescriptor, IViewContainersRegistry, Extensions as ViewExtensions, IView, ViewContainerLocation, IViewsService, IViewPaneContainer, getVisbileViewContextKey } from 'vs/workbench/common/views'; import { Registry } from 'vs/platform/registry/common/platform'; -import { IStorageService, StorageScope, IWorkspaceStorageChangeEvent } from 'vs/platform/storage/common/storage'; +import { IStorageService } from 'vs/platform/storage/common/storage'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; import { ContextKeyExpr, IContextKey, IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; import { Event, Emitter } from 'vs/base/common/event'; -import { firstIndex, move } from 'vs/base/common/arrays'; -import { isUndefinedOrNull, isUndefined, isString } from 'vs/base/common/types'; +import { isString } from 'vs/base/common/types'; import { MenuId, registerAction2, Action2 } from 'vs/platform/actions/common/actions'; import { localize } from 'vs/nls'; import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; -import { values } from 'vs/base/common/map'; import { toggleClass, addClass } from 'vs/base/browser/dom'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { IPaneComposite } from 'vs/workbench/common/panecomposite'; @@ -34,488 +32,6 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; import { URI } from 'vs/base/common/uri'; import { IProgressIndicator } from 'vs/platform/progress/common/progress'; -import { IStorageKeysSyncRegistryService } from 'vs/platform/userDataSync/common/storageKeys'; - -export interface IViewState { - visibleGlobal: boolean | undefined; - visibleWorkspace: boolean | undefined; - collapsed: boolean | undefined; - order?: number; - size?: number; -} - -export interface IViewDescriptorRef { - viewDescriptor: IViewDescriptor; - index: number; -} - -export interface IAddedViewDescriptorRef extends IViewDescriptorRef { - collapsed: boolean; - size?: number; -} - -export class ContributableViewsModel extends Disposable { - - private _viewDescriptors: IViewDescriptor[] = []; - get viewDescriptors(): ReadonlyArray { - return this._viewDescriptors; - } - - get visibleViewDescriptors(): IViewDescriptor[] { - return this.viewDescriptors.filter(v => this.isViewDescriptorVisible(v)); - } - - private _onDidAdd = this._register(new Emitter()); - readonly onDidAdd: Event = this._onDidAdd.event; - - private _onDidRemove = this._register(new Emitter()); - readonly onDidRemove: Event = this._onDidRemove.event; - - private _onDidMove = this._register(new Emitter<{ from: IViewDescriptorRef; to: IViewDescriptorRef; }>()); - readonly onDidMove: Event<{ from: IViewDescriptorRef; to: IViewDescriptorRef; }> = this._onDidMove.event; - - private _onDidChangeViewState = this._register(new Emitter()); - protected readonly onDidChangeViewState: Event = this._onDidChangeViewState.event; - - private _onDidChangeActiveViews = this._register(new Emitter>()); - readonly onDidChangeActiveViews: Event> = this._onDidChangeActiveViews.event; - - constructor( - container: ViewContainer, - viewsService: IViewDescriptorService, - protected viewStates = new Map(), - ) { - super(); - const viewDescriptorCollection = viewsService.getViewDescriptors(container); - this._register(viewDescriptorCollection.onDidChangeActiveViews(() => this.onDidChangeViewDescriptors(viewDescriptorCollection.activeViewDescriptors))); - this.onDidChangeViewDescriptors(viewDescriptorCollection.activeViewDescriptors); - } - - isVisible(id: string): boolean { - const viewDescriptor = this.viewDescriptors.filter(v => v.id === id)[0]; - - if (!viewDescriptor) { - throw new Error(`Unknown view ${id}`); - } - - return this.isViewDescriptorVisible(viewDescriptor); - } - - setVisible(id: string, visible: boolean, size?: number): void { - this.doSetVisible([{ id, visible, size }]); - } - - protected doSetVisible(viewDescriptors: { id: string, visible: boolean, size?: number }[]): void { - const added: IAddedViewDescriptorRef[] = []; - const removed: IViewDescriptorRef[] = []; - - for (const { visibleIndex, viewDescriptor, state, visible, size } of viewDescriptors.map(({ id, visible, size }) => ({ ...this.find(id), visible, size }))) { - - if (!viewDescriptor.canToggleVisibility) { - throw new Error(`Can't toggle this view's visibility`); - } - - if (this.isViewDescriptorVisible(viewDescriptor) === visible) { - return; - } - - if (viewDescriptor.workspace) { - state.visibleWorkspace = visible; - } else { - state.visibleGlobal = visible; - } - - if (typeof size === 'number') { - state.size = size; - } - - if (visible) { - added.push({ index: visibleIndex, viewDescriptor, size: state.size, collapsed: !!state.collapsed }); - } else { - removed.push({ index: visibleIndex, viewDescriptor }); - } - } - - if (added.length) { - this._onDidAdd.fire(added); - } - if (removed.length) { - this._onDidRemove.fire(removed); - } - } - - isCollapsed(id: string): boolean { - const state = this.viewStates.get(id); - - if (!state) { - throw new Error(`Unknown view ${id}`); - } - - return !!state.collapsed; - } - - setCollapsed(id: string, collapsed: boolean): void { - const { index, state, viewDescriptor } = this.find(id); - if (state.collapsed !== collapsed) { - state.collapsed = collapsed; - this._onDidChangeViewState.fire({ viewDescriptor, index }); - } - } - - getSize(id: string): number | undefined { - const state = this.viewStates.get(id); - - if (!state) { - throw new Error(`Unknown view ${id}`); - } - - return state.size; - } - - setSize(id: string, size: number): void { - const { index, state, viewDescriptor } = this.find(id); - if (state.size !== size) { - state.size = size; - this._onDidChangeViewState.fire({ viewDescriptor, index }); - } - } - - move(from: string, to: string): void { - const fromIndex = firstIndex(this.viewDescriptors, v => v.id === from); - const toIndex = firstIndex(this.viewDescriptors, v => v.id === to); - - const fromViewDescriptor = this.viewDescriptors[fromIndex]; - const toViewDescriptor = this.viewDescriptors[toIndex]; - - move(this._viewDescriptors, fromIndex, toIndex); - - for (let index = 0; index < this.viewDescriptors.length; index++) { - const state = this.viewStates.get(this.viewDescriptors[index].id)!; - state.order = index; - } - - this._onDidMove.fire({ - from: { index: fromIndex, viewDescriptor: fromViewDescriptor }, - to: { index: toIndex, viewDescriptor: toViewDescriptor } - }); - } - - private isViewDescriptorVisible(viewDescriptor: IViewDescriptor): boolean { - const viewState = this.viewStates.get(viewDescriptor.id); - if (!viewState) { - throw new Error(`Unknown view ${viewDescriptor.id}`); - } - return viewDescriptor.workspace ? !!viewState.visibleWorkspace : !!viewState.visibleGlobal; - } - - private find(id: string): { index: number, visibleIndex: number, viewDescriptor: IViewDescriptor, state: IViewState; } { - for (let i = 0, visibleIndex = 0; i < this.viewDescriptors.length; i++) { - const viewDescriptor = this.viewDescriptors[i]; - const state = this.viewStates.get(viewDescriptor.id); - if (!state) { - throw new Error(`View state for ${id} not found`); - } - - if (viewDescriptor.id === id) { - return { index: i, visibleIndex, viewDescriptor, state }; - } - - if (viewDescriptor.workspace ? state.visibleWorkspace : state.visibleGlobal) { - visibleIndex++; - } - } - - throw new Error(`view descriptor ${id} not found`); - } - - private compareViewDescriptors(a: IViewDescriptor, b: IViewDescriptor): number { - if (a.id === b.id) { - return 0; - } - - return (this.getViewOrder(a) - this.getViewOrder(b)) || this.getGroupOrderResult(a, b); - } - - private getGroupOrderResult(a: IViewDescriptor, b: IViewDescriptor) { - if (!a.group || !b.group) { - return 0; - } - - if (a.group === b.group) { - return 0; - } - - return a.group < b.group ? -1 : 1; - } - - private getViewOrder(viewDescriptor: IViewDescriptor): number { - const viewState = this.viewStates.get(viewDescriptor.id); - const viewOrder = viewState && typeof viewState.order === 'number' ? viewState.order : viewDescriptor.order; - return typeof viewOrder === 'number' ? viewOrder : Number.MAX_VALUE; - } - - private onDidChangeViewDescriptors(viewDescriptors: IViewDescriptor[]): void { - for (const viewDescriptor of viewDescriptors) { - const viewState = this.viewStates.get(viewDescriptor.id); - if (viewState) { - // set defaults if not set - if (viewDescriptor.workspace) { - viewState.visibleWorkspace = isUndefinedOrNull(viewState.visibleWorkspace) ? !viewDescriptor.hideByDefault : viewState.visibleWorkspace; - } else { - viewState.visibleGlobal = isUndefinedOrNull(viewState.visibleGlobal) ? !viewDescriptor.hideByDefault : viewState.visibleGlobal; - } - viewState.collapsed = isUndefinedOrNull(viewState.collapsed) ? !!viewDescriptor.collapsed : viewState.collapsed; - } else { - this.viewStates.set(viewDescriptor.id, { - visibleGlobal: !viewDescriptor.hideByDefault, - visibleWorkspace: !viewDescriptor.hideByDefault, - collapsed: !!viewDescriptor.collapsed - }); - } - } - - viewDescriptors = viewDescriptors.sort(this.compareViewDescriptors.bind(this)); - - const toRemove: { index: number, viewDescriptor: IViewDescriptor; }[] = []; - for (let index = 0; index < this._viewDescriptors.length; index++) { - const previousViewDescriptor = this._viewDescriptors[index]; - if (this.isViewDescriptorVisible(previousViewDescriptor) && viewDescriptors.every(viewDescriptor => viewDescriptor.id !== previousViewDescriptor.id)) { - const { visibleIndex } = this.find(previousViewDescriptor.id); - toRemove.push({ index: visibleIndex, viewDescriptor: previousViewDescriptor }); - } - } - - const previous = this._viewDescriptors; - this._viewDescriptors = viewDescriptors.slice(0); - - const toAdd: { index: number, viewDescriptor: IViewDescriptor, size?: number, collapsed: boolean; }[] = []; - for (let i = 0; i < this._viewDescriptors.length; i++) { - const viewDescriptor = this._viewDescriptors[i]; - if (this.isViewDescriptorVisible(viewDescriptor) && previous.every(previousViewDescriptor => previousViewDescriptor.id !== viewDescriptor.id)) { - const { visibleIndex, state } = this.find(viewDescriptor.id); - toAdd.push({ index: visibleIndex, viewDescriptor, size: state.size, collapsed: !!state.collapsed }); - } - } - - if (toRemove.length) { - this._onDidRemove.fire(toRemove); - } - - if (toAdd.length) { - this._onDidAdd.fire(toAdd); - } - - this._onDidChangeActiveViews.fire(this.viewDescriptors); - } -} - -interface IStoredWorkspaceViewState { - collapsed: boolean; - isHidden: boolean; - size?: number; - order?: number; -} - -interface IStoredGlobalViewState { - id: string; - isHidden: boolean; - order?: number; -} - -export class PersistentContributableViewsModel extends ContributableViewsModel { - - private readonly workspaceViewsStateStorageId: string; - private readonly globalViewsStateStorageId: string; - - private storageService: IStorageService; - - constructor( - container: ViewContainer, - viewletStateStorageId: string, - @IViewDescriptorService viewDescriptorService: IViewDescriptorService, - @IStorageService storageService: IStorageService, - @IStorageKeysSyncRegistryService storageKeysSyncRegistryService: IStorageKeysSyncRegistryService - ) { - const globalViewsStateStorageId = `${viewletStateStorageId}.hidden`; - storageKeysSyncRegistryService.registerStorageKey({ key: globalViewsStateStorageId, version: 1 }); - const viewStates = PersistentContributableViewsModel.loadViewsStates(viewletStateStorageId, globalViewsStateStorageId, storageService); - - super(container, viewDescriptorService, viewStates); - - this.storageService = storageService; - this.workspaceViewsStateStorageId = viewletStateStorageId; - this.globalViewsStateStorageId = globalViewsStateStorageId; - - this._register(Event.any( - this.onDidAdd, - this.onDidRemove, - Event.map(this.onDidMove, ({ from, to }) => [from, to]), - Event.map(this.onDidChangeViewState, viewDescriptorRef => [viewDescriptorRef])) - (viewDescriptorRefs => this.saveViewsStates())); - - this._globalViewsStatesValue = this.getStoredGlobalViewsStatesValue(); - this._register(this.storageService.onDidChangeStorage(e => this.onDidStorageChange(e))); - } - - private onDidStorageChange(e: IWorkspaceStorageChangeEvent): void { - if (e.key === this.globalViewsStateStorageId && e.scope === StorageScope.GLOBAL - && this.globalViewsStatesValue !== this.getStoredGlobalViewsStatesValue() /* This checks if current window changed the value or not */) { - this._globalViewsStatesValue = undefined; - const storedViewsVisibilityStates = PersistentContributableViewsModel.loadGlobalViewsState(this.globalViewsStateStorageId, this.storageService, StorageScope.GLOBAL); - const changedViews: { id: string, visible: boolean }[] = []; - for (const [id, state] of storedViewsVisibilityStates) { - const viewState = this.viewStates.get(id); - if (viewState) { - if (viewState.visibleGlobal !== !state.isHidden) { - changedViews.push({ id, visible: !state.isHidden }); - } - } - } - if (changedViews.length) { - this.doSetVisible(changedViews); - } - } - } - - private saveViewsStates(): void { - this.saveWorkspaceViewsStates(); - this.saveGlobalViewsStates(); - } - - private saveWorkspaceViewsStates(): void { - const storedViewsStates: { [id: string]: IStoredWorkspaceViewState; } = JSON.parse(this.storageService.get(this.workspaceViewsStateStorageId, StorageScope.WORKSPACE, '{}')); - for (const viewDescriptor of this.viewDescriptors) { - const viewState = this.viewStates.get(viewDescriptor.id); - if (viewState) { - storedViewsStates[viewDescriptor.id] = { - collapsed: !!viewState.collapsed, - isHidden: !viewState.visibleWorkspace, - size: viewState.size, - order: viewDescriptor.workspace && viewState ? viewState.order : undefined - }; - } - } - - if (Object.keys(storedViewsStates).length > 0) { - this.storageService.store(this.workspaceViewsStateStorageId, JSON.stringify(storedViewsStates), StorageScope.WORKSPACE); - } else { - this.storageService.remove(this.workspaceViewsStateStorageId, StorageScope.WORKSPACE); - } - } - - private saveGlobalViewsStates(): void { - const storedViewsVisibilityStates = PersistentContributableViewsModel.loadGlobalViewsState(this.globalViewsStateStorageId, this.storageService, StorageScope.GLOBAL); - for (const viewDescriptor of this.viewDescriptors) { - const viewState = this.viewStates.get(viewDescriptor.id); - storedViewsVisibilityStates.set(viewDescriptor.id, { - id: viewDescriptor.id, - isHidden: viewState && viewDescriptor.canToggleVisibility ? !viewState.visibleGlobal : false, - order: !viewDescriptor.workspace && viewState ? viewState.order : undefined - }); - } - this.globalViewsStatesValue = JSON.stringify(values(storedViewsVisibilityStates)); - } - - private _globalViewsStatesValue: string | undefined; - private get globalViewsStatesValue(): string { - if (!this._globalViewsStatesValue) { - this._globalViewsStatesValue = this.getStoredGlobalViewsStatesValue(); - } - - return this._globalViewsStatesValue; - } - - private set globalViewsStatesValue(globalViewsStatesValue: string) { - if (this.globalViewsStatesValue !== globalViewsStatesValue) { - this._globalViewsStatesValue = globalViewsStatesValue; - this.setStoredGlobalViewsStatesValue(globalViewsStatesValue); - } - } - - private getStoredGlobalViewsStatesValue(): string { - return this.storageService.get(this.globalViewsStateStorageId, StorageScope.GLOBAL, '[]'); - } - - private setStoredGlobalViewsStatesValue(value: string): void { - this.storageService.store(this.globalViewsStateStorageId, value, StorageScope.GLOBAL); - } - - private static loadViewsStates(workspaceViewsStateStorageId: string, globalViewsStateStorageId: string, storageService: IStorageService): Map { - const viewStates = new Map(); - const workspaceViewsStates = <{ [id: string]: IStoredWorkspaceViewState; }>JSON.parse(storageService.get(workspaceViewsStateStorageId, StorageScope.WORKSPACE, '{}')); - for (const id of Object.keys(workspaceViewsStates)) { - const workspaceViewState = workspaceViewsStates[id]; - viewStates.set(id, { - visibleGlobal: undefined, - visibleWorkspace: isUndefined(workspaceViewState.isHidden) ? undefined : !workspaceViewState.isHidden, - collapsed: workspaceViewState.collapsed, - order: workspaceViewState.order, - size: workspaceViewState.size - }); - } - - // Migrate to `viewletStateStorageId` - const workspaceVisibilityStates = this.loadGlobalViewsState(globalViewsStateStorageId, storageService, StorageScope.WORKSPACE); - if (workspaceVisibilityStates.size > 0) { - for (const { id, isHidden } of values(workspaceVisibilityStates)) { - let viewState = viewStates.get(id); - // Not migrated to `viewletStateStorageId` - if (viewState) { - if (isUndefined(viewState.visibleWorkspace)) { - viewState.visibleWorkspace = !isHidden; - } - } else { - viewStates.set(id, { - collapsed: undefined, - visibleGlobal: undefined, - visibleWorkspace: !isHidden, - }); - } - } - storageService.remove(globalViewsStateStorageId, StorageScope.WORKSPACE); - } - - const globalViewsStates = this.loadGlobalViewsState(globalViewsStateStorageId, storageService, StorageScope.GLOBAL); - for (const { id, isHidden, order } of values(globalViewsStates)) { - let viewState = viewStates.get(id); - if (viewState) { - viewState.visibleGlobal = !isHidden; - if (!isUndefined(order)) { - viewState.order = order; - } - } else { - viewStates.set(id, { - visibleGlobal: !isHidden, - order, - collapsed: undefined, - visibleWorkspace: undefined, - }); - } - } - return viewStates; - } - - private static loadGlobalViewsState(globalViewsStateStorageId: string, storageService: IStorageService, scope: StorageScope): Map { - const storedValue = >JSON.parse(storageService.get(globalViewsStateStorageId, scope, '[]')); - let hasDuplicates = false; - const storedGlobalViewsState = storedValue.reduce((result, storedState) => { - if (typeof storedState === 'string' /* migration */) { - hasDuplicates = hasDuplicates || result.has(storedState); - result.set(storedState, { id: storedState, isHidden: true }); - } else { - hasDuplicates = hasDuplicates || result.has(storedState.id); - result.set(storedState.id, storedState); - } - return result; - }, new Map()); - - if (hasDuplicates) { - storageService.store(globalViewsStateStorageId, JSON.stringify(values(storedGlobalViewsState)), scope); - } - - return storedGlobalViewsState; - } -} export class ViewsService extends Disposable implements IViewsService { diff --git a/src/vs/workbench/browser/parts/views/viewsViewlet.ts b/src/vs/workbench/browser/parts/views/viewsViewlet.ts index 8588ffe9aac..4fdd40c1197 100644 --- a/src/vs/workbench/browser/parts/views/viewsViewlet.ts +++ b/src/vs/workbench/browser/parts/views/viewsViewlet.ts @@ -6,7 +6,7 @@ import { IAction } from 'vs/base/common/actions'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; -import { IViewDescriptor, IViewDescriptorService } from 'vs/workbench/common/views'; +import { IViewDescriptor, IViewDescriptorService, IAddedViewDescriptorRef } from 'vs/workbench/common/views'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; @@ -16,7 +16,6 @@ import { ViewPaneContainer, ViewPane, IViewPaneOptions } from 'vs/workbench/brow import { Event } from 'vs/base/common/event'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; -import { IAddedViewDescriptorRef } from 'vs/workbench/browser/parts/views/views'; export interface IViewletViewOptions extends IViewPaneOptions { } @@ -41,13 +40,13 @@ export abstract class FilterViewPaneContainer extends ViewPaneContainer { @IViewDescriptorService viewDescriptorService: IViewDescriptorService ) { - super(viewletId, `${viewletId}.state`, { mergeViewWithContainerWhenSingleView: false }, instantiationService, configurationService, layoutService, contextMenuService, telemetryService, extensionService, themeService, storageService, contextService, viewDescriptorService); + super(viewletId, { mergeViewWithContainerWhenSingleView: false }, instantiationService, configurationService, layoutService, contextMenuService, telemetryService, extensionService, themeService, storageService, contextService, viewDescriptorService); this._register(onDidChangeFilterValue(newFilterValue => { this.filterValue = newFilterValue; this.onFilterChanged(newFilterValue); })); - this._register(this.viewsModel.onDidChangeActiveViews((viewDescriptors) => { + this._register(this.viewsDescriptors.onDidChangeActiveViews((viewDescriptors) => { this.updateAllViews(viewDescriptors); })); } @@ -63,7 +62,7 @@ export abstract class FilterViewPaneContainer extends ViewPaneContainer { } this.allViews.get(filterOnValue)!.set(descriptor.id, descriptor); if (this.filterValue && !this.filterValue.includes(filterOnValue)) { - this.viewsModel.setVisible(descriptor.id, false); + this.viewsDescriptors.setVisible(descriptor.id, false); } }); } @@ -76,17 +75,17 @@ export abstract class FilterViewPaneContainer extends ViewPaneContainer { private onFilterChanged(newFilterValue: string[]) { if (this.allViews.size === 0) { - this.updateAllViews(this.viewsModel.viewDescriptors); + this.updateAllViews(this.viewsDescriptors.activeViewDescriptors); } - this.getViewsNotForTarget(newFilterValue).forEach(item => this.viewsModel.setVisible(item.id, false)); - this.getViewsForTarget(newFilterValue).forEach(item => this.viewsModel.setVisible(item.id, true)); + this.getViewsNotForTarget(newFilterValue).forEach(item => this.viewsDescriptors.setVisible(item.id, false)); + this.getViewsForTarget(newFilterValue).forEach(item => this.viewsDescriptors.setVisible(item.id, true)); } getContextMenuActions(): IAction[] { const result: IAction[] = Array.from(this.constantViewDescriptors.values()).map(viewDescriptor => ({ id: `${viewDescriptor.id}.toggleVisibility`, label: viewDescriptor.name, - checked: this.viewsModel.isVisible(viewDescriptor.id), + checked: this.viewsDescriptors.isVisible(viewDescriptor.id), enabled: viewDescriptor.canToggleVisibility, run: () => this.toggleViewVisibility(viewDescriptor.id) })); @@ -134,7 +133,7 @@ export abstract class FilterViewPaneContainer extends ViewPaneContainer { } // Check that allViews is ready if (this.allViews.size === 0) { - this.updateAllViews(this.viewsModel.viewDescriptors); + this.updateAllViews(this.viewsDescriptors.activeViewDescriptors); } return panes; } diff --git a/src/vs/workbench/common/views.ts b/src/vs/workbench/common/views.ts index dcf23586400..3e1ddd2ad2a 100644 --- a/src/vs/workbench/common/views.ts +++ b/src/vs/workbench/common/views.ts @@ -43,6 +43,8 @@ export interface IViewContainerDescriptor { readonly ctorDescriptor: SyncDescriptor; + readonly storageId?: string; + readonly icon?: string | URI; readonly order?: number; @@ -223,7 +225,7 @@ export interface IViewDescriptorCollection extends IDisposable { readonly onDidChangeViews: Event<{ added: IViewDescriptor[], removed: IViewDescriptor[] }>; readonly activeViewDescriptors: IViewDescriptor[]; - readonly onDidChangeActiveViews: Event<{ added: IViewDescriptor[], removed: IViewDescriptor[] }>; + readonly onDidChangeActiveViews: Event>; readonly visibleViewDescriptors: IViewDescriptor[]; readonly onDidAdd: Event; diff --git a/src/vs/workbench/contrib/bulkEdit/browser/bulkEdit.contribution.ts b/src/vs/workbench/contrib/bulkEdit/browser/bulkEdit.contribution.ts index faf9b538534..289ee656910 100644 --- a/src/vs/workbench/contrib/bulkEdit/browser/bulkEdit.contribution.ts +++ b/src/vs/workbench/contrib/bulkEdit/browser/bulkEdit.contribution.ts @@ -352,8 +352,9 @@ const container = Registry.as(ViewContainerExtensions.V hideIfEmpty: true, ctorDescriptor: new SyncDescriptor( ViewPaneContainer, - [BulkEditPane.ID, BulkEditPane.ID, { mergeViewWithContainerWhenSingleView: true, donotShowContainerTitleWhenMergedWithContainer: true }] - ) + [BulkEditPane.ID, { mergeViewWithContainerWhenSingleView: true, donotShowContainerTitleWhenMergedWithContainer: true }] + ), + storageId: BulkEditPane.ID }, ViewContainerLocation.Panel); Registry.as(ViewContainerExtensions.ViewsRegistry).registerViews([{ diff --git a/src/vs/workbench/contrib/debug/browser/debug.contribution.ts b/src/vs/workbench/contrib/debug/browser/debug.contribution.ts index 5255e0e4083..e9c6fb869bb 100644 --- a/src/vs/workbench/contrib/debug/browser/debug.contribution.ts +++ b/src/vs/workbench/contrib/debug/browser/debug.contribution.ts @@ -90,7 +90,8 @@ const openPanelKb: IKeybindings = { const VIEW_CONTAINER: ViewContainer = Registry.as(ViewExtensions.ViewContainersRegistry).registerViewContainer({ id: DEBUG_PANEL_ID, name: nls.localize({ comment: ['Debug is a noun in this context, not a verb.'], key: 'debugPanel' }, 'Debug Console'), - ctorDescriptor: new SyncDescriptor(ViewPaneContainer, [DEBUG_PANEL_ID, DEBUG_PANEL_ID, { mergeViewWithContainerWhenSingleView: true, donotShowContainerTitleWhenMergedWithContainer: true }]), + ctorDescriptor: new SyncDescriptor(ViewPaneContainer, [DEBUG_PANEL_ID, { mergeViewWithContainerWhenSingleView: true, donotShowContainerTitleWhenMergedWithContainer: true }]), + storageId: DEBUG_PANEL_ID, focusCommand: { id: OpenDebugConsoleAction.ID, keybindings: openPanelKb diff --git a/src/vs/workbench/contrib/debug/browser/debugViewlet.ts b/src/vs/workbench/contrib/debug/browser/debugViewlet.ts index e80f6f0670b..ca458d7b809 100644 --- a/src/vs/workbench/contrib/debug/browser/debugViewlet.ts +++ b/src/vs/workbench/contrib/debug/browser/debugViewlet.ts @@ -62,7 +62,7 @@ export class DebugViewPaneContainer extends ViewPaneContainer { @INotificationService private readonly notificationService: INotificationService, @IViewDescriptorService viewDescriptorService: IViewDescriptorService ) { - super(VIEWLET_ID, `${VIEWLET_ID}.state`, { mergeViewWithContainerWhenSingleView: true }, instantiationService, configurationService, layoutService, contextMenuService, telemetryService, extensionService, themeService, storageService, contextService, viewDescriptorService); + super(VIEWLET_ID, { mergeViewWithContainerWhenSingleView: true }, instantiationService, configurationService, layoutService, contextMenuService, telemetryService, extensionService, themeService, storageService, contextService, viewDescriptorService); this._register(this.debugService.onDidChangeState(state => this.onDebugServiceStateChange(state))); this._register(this.debugService.onDidNewSession(() => this.updateToolBar())); diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsViewlet.ts b/src/vs/workbench/contrib/extensions/browser/extensionsViewlet.ts index 0311e3ce1b1..f1ff8e2206e 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsViewlet.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsViewlet.ts @@ -34,7 +34,7 @@ import Severity from 'vs/base/common/severity'; import { IActivityService, NumberBadge } from 'vs/workbench/services/activity/common/activity'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { IViewsRegistry, IViewDescriptor, Extensions, ViewContainer, IViewContainersRegistry, IViewDescriptorService } from 'vs/workbench/common/views'; +import { IViewsRegistry, IViewDescriptor, Extensions, ViewContainer, IViewContainersRegistry, IViewDescriptorService, IAddedViewDescriptorRef } from 'vs/workbench/common/views'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; import { IContextKeyService, ContextKeyExpr, RawContextKey, IContextKey } from 'vs/platform/contextkey/common/contextkey'; @@ -44,7 +44,6 @@ import { ILogService } from 'vs/platform/log/common/log'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { IHostService } from 'vs/workbench/services/host/browser/host'; import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; -import { IAddedViewDescriptorRef } from 'vs/workbench/browser/parts/views/views'; import { ViewPane, ViewPaneContainer } from 'vs/workbench/browser/parts/views/viewPaneContainer'; import { Query } from 'vs/workbench/contrib/extensions/common/extensionQuery'; import { SuggestEnabledInput, attachSuggestEnabledInputBoxStyler } from 'vs/workbench/contrib/codeEditor/browser/suggestEnabledInput/suggestEnabledInput'; @@ -359,7 +358,7 @@ export class ExtensionsViewPaneContainer extends ViewPaneContainer implements IE @IViewDescriptorService viewDescriptorService: IViewDescriptorService, @IPreferencesService private readonly preferencesService: IPreferencesService ) { - super(VIEWLET_ID, `${VIEWLET_ID}.state`, { mergeViewWithContainerWhenSingleView: true }, instantiationService, configurationService, layoutService, contextMenuService, telemetryService, extensionService, themeService, storageService, contextService, viewDescriptorService); + super(VIEWLET_ID, { mergeViewWithContainerWhenSingleView: true }, instantiationService, configurationService, layoutService, contextMenuService, telemetryService, extensionService, themeService, storageService, contextService, viewDescriptorService); this.searchDelayer = new Delayer(500); this.nonEmptyWorkspaceContextKey = NonEmptyWorkspaceContext.bindTo(contextKeyService); diff --git a/src/vs/workbench/contrib/files/browser/explorerViewlet.ts b/src/vs/workbench/contrib/files/browser/explorerViewlet.ts index 559404a96fc..368b7ce006c 100644 --- a/src/vs/workbench/contrib/files/browser/explorerViewlet.ts +++ b/src/vs/workbench/contrib/files/browser/explorerViewlet.ts @@ -160,8 +160,6 @@ export class ExplorerViewletViewsContribution extends Disposable implements IWor export class ExplorerViewPaneContainer extends ViewPaneContainer { - private static readonly EXPLORER_VIEWS_STATE = 'workbench.explorer.views.state'; - private viewletVisibleContextKey: IContextKey; constructor( @@ -179,7 +177,7 @@ export class ExplorerViewPaneContainer extends ViewPaneContainer { @IViewDescriptorService viewDescriptorService: IViewDescriptorService ) { - super(VIEWLET_ID, ExplorerViewPaneContainer.EXPLORER_VIEWS_STATE, { mergeViewWithContainerWhenSingleView: true }, instantiationService, configurationService, layoutService, contextMenuService, telemetryService, extensionService, themeService, storageService, contextService, viewDescriptorService); + super(VIEWLET_ID, { mergeViewWithContainerWhenSingleView: true }, instantiationService, configurationService, layoutService, contextMenuService, telemetryService, extensionService, themeService, storageService, contextService, viewDescriptorService); this.viewletVisibleContextKey = ExplorerViewletVisibleContext.bindTo(contextKeyService); @@ -262,6 +260,7 @@ export const VIEW_CONTAINER: ViewContainer = Registry.as(ViewC name: Messages.MARKERS_PANEL_TITLE_PROBLEMS, hideIfEmpty: true, order: 0, - ctorDescriptor: new SyncDescriptor(ViewPaneContainer, [Constants.MARKERS_CONTAINER_ID, Constants.MARKERS_VIEW_STORAGE_ID, { mergeViewWithContainerWhenSingleView: true, donotShowContainerTitleWhenMergedWithContainer: true }]), + ctorDescriptor: new SyncDescriptor(ViewPaneContainer, [Constants.MARKERS_CONTAINER_ID, { mergeViewWithContainerWhenSingleView: true, donotShowContainerTitleWhenMergedWithContainer: true }]), + storageId: Constants.MARKERS_VIEW_STORAGE_ID, focusCommand: { id: ToggleMarkersPanelAction.ID, keybindings: { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_M diff --git a/src/vs/workbench/contrib/output/browser/output.contribution.ts b/src/vs/workbench/contrib/output/browser/output.contribution.ts index b9961b11297..d075c22645d 100644 --- a/src/vs/workbench/contrib/output/browser/output.contribution.ts +++ b/src/vs/workbench/contrib/output/browser/output.contribution.ts @@ -62,7 +62,8 @@ const VIEW_CONTAINER: ViewContainer = Registry.as(ViewC id: OUTPUT_VIEW_ID, name: nls.localize('output', "Output"), order: 1, - ctorDescriptor: new SyncDescriptor(ViewPaneContainer, [OUTPUT_VIEW_ID, OUTPUT_VIEW_ID, { mergeViewWithContainerWhenSingleView: true, donotShowContainerTitleWhenMergedWithContainer: true }]), + ctorDescriptor: new SyncDescriptor(ViewPaneContainer, [OUTPUT_VIEW_ID, { mergeViewWithContainerWhenSingleView: true, donotShowContainerTitleWhenMergedWithContainer: true }]), + storageId: OUTPUT_VIEW_ID, hideIfEmpty: true, focusCommand: { id: toggleOutputAcitonId, keybindings: toggleOutputActionKeybindings } }, ViewContainerLocation.Panel); diff --git a/src/vs/workbench/contrib/remote/browser/remote.ts b/src/vs/workbench/contrib/remote/browser/remote.ts index 233d83deff8..476a5acab39 100644 --- a/src/vs/workbench/contrib/remote/browser/remote.ts +++ b/src/vs/workbench/contrib/remote/browser/remote.ts @@ -19,7 +19,7 @@ import { IExtensionService } from 'vs/workbench/services/extensions/common/exten import { FilterViewPaneContainer } from 'vs/workbench/browser/parts/views/viewsViewlet'; import { VIEWLET_ID } from 'vs/workbench/contrib/remote/common/remote.contribution'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; -import { IViewDescriptor, IViewsRegistry, Extensions, ViewContainerLocation, IViewContainersRegistry, IViewDescriptorService } from 'vs/workbench/common/views'; +import { IViewDescriptor, IViewsRegistry, Extensions, ViewContainerLocation, IViewContainersRegistry, IViewDescriptorService, IAddedViewDescriptorRef } from 'vs/workbench/common/views'; import { Registry } from 'vs/platform/registry/common/platform'; import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; import { IOpenerService } from 'vs/platform/opener/common/opener'; @@ -46,7 +46,6 @@ import { IRemoteExplorerService } from 'vs/workbench/services/remote/common/remo import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { startsWith } from 'vs/base/common/strings'; import { TunnelPanelDescriptor, TunnelViewModel, forwardedPortsViewEnabled } from 'vs/workbench/contrib/remote/browser/tunnelView'; -import { IAddedViewDescriptorRef } from 'vs/workbench/browser/parts/views/views'; import { ViewPane, IViewPaneOptions } from 'vs/workbench/browser/parts/views/viewPaneContainer'; import { IListVirtualDelegate } from 'vs/base/browser/ui/list/list'; import { ITreeRenderer, ITreeNode, IAsyncDataSource } from 'vs/base/browser/ui/tree/tree'; diff --git a/src/vs/workbench/contrib/scm/browser/scm.contribution.ts b/src/vs/workbench/contrib/scm/browser/scm.contribution.ts index ca87f4502b4..e23716150df 100644 --- a/src/vs/workbench/contrib/scm/browser/scm.contribution.ts +++ b/src/vs/workbench/contrib/scm/browser/scm.contribution.ts @@ -51,6 +51,7 @@ Registry.as(ViewContainerExtensions.ViewContainersRegis id: VIEWLET_ID, name: localize('source control', "Source Control"), ctorDescriptor: new SyncDescriptor(SCMViewPaneContainer), + storageId: 'workbench.scm.views.state', icon: 'codicon-source-control', order: 2 }, ViewContainerLocation.Sidebar); diff --git a/src/vs/workbench/contrib/scm/browser/scmViewlet.ts b/src/vs/workbench/contrib/scm/browser/scmViewlet.ts index e60545de4a6..2bd012782a3 100644 --- a/src/vs/workbench/contrib/scm/browser/scmViewlet.ts +++ b/src/vs/workbench/contrib/scm/browser/scmViewlet.ts @@ -24,12 +24,11 @@ import { INotificationService } from 'vs/platform/notification/common/notificati import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; -import { IViewsRegistry, Extensions, IViewDescriptorService, IViewDescriptor } from 'vs/workbench/common/views'; +import { IViewsRegistry, Extensions, IViewDescriptorService, IViewDescriptor, IAddedViewDescriptorRef, IViewDescriptorRef } from 'vs/workbench/common/views'; import { Registry } from 'vs/platform/registry/common/platform'; import { RepositoryPane, RepositoryViewDescriptor } from 'vs/workbench/contrib/scm/browser/repositoryPane'; import { MainPaneDescriptor, MainPane, IViewModel } from 'vs/workbench/contrib/scm/browser/mainPane'; import { ViewPaneContainer, IViewPaneOptions, ViewPane } from 'vs/workbench/browser/parts/views/viewPaneContainer'; -import type { IAddedViewDescriptorRef, IViewDescriptorRef } from 'vs/workbench/browser/parts/views/views'; import { debounce } from 'vs/base/common/decorators'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { IOpenerService } from 'vs/platform/opener/common/opener'; @@ -80,8 +79,6 @@ export class EmptyPaneDescriptor implements IViewDescriptor { export class SCMViewPaneContainer extends ViewPaneContainer implements IViewModel { - private static readonly STATE_KEY = 'workbench.scm.views.state'; - private menus: SCMMenus; private _repositories: ISCMRepository[] = []; @@ -104,7 +101,7 @@ export class SCMViewPaneContainer extends ViewPaneContainer implements IViewMode } get onDidChangeVisibleRepositories(): Event { - const modificationEvent = Event.debounce(Event.any(this.viewsModel.onDidAdd, this.viewsModel.onDidRemove), () => null, 0); + const modificationEvent = Event.debounce(Event.any(this.viewsDescriptors.onDidAdd, this.viewsDescriptors.onDidRemove), () => null, 0); return Event.map(modificationEvent, () => this.visibleRepositories); } @@ -126,7 +123,7 @@ export class SCMViewPaneContainer extends ViewPaneContainer implements IViewMode @IContextKeyService contextKeyService: IContextKeyService, @IViewDescriptorService viewDescriptorService: IViewDescriptorService ) { - super(VIEWLET_ID, SCMViewPaneContainer.STATE_KEY, { mergeViewWithContainerWhenSingleView: true }, instantiationService, configurationService, layoutService, contextMenuService, telemetryService, extensionService, themeService, storageService, contextService, viewDescriptorService); + super(VIEWLET_ID, { mergeViewWithContainerWhenSingleView: true }, instantiationService, configurationService, layoutService, contextMenuService, telemetryService, extensionService, themeService, storageService, contextService, viewDescriptorService); this.menus = instantiationService.createInstance(SCMMenus, undefined); this._register(this.menus.onDidChangeTitle(this.updateTitleArea, this)); @@ -143,14 +140,14 @@ export class SCMViewPaneContainer extends ViewPaneContainer implements IViewMode this._register(configurationService.onDidChangeConfiguration(e => { if (e.affectsConfiguration('scm.alwaysShowProviders') && configurationService.getValue('scm.alwaysShowProviders')) { - this.viewsModel.setVisible(MainPane.ID, true); + this.viewsDescriptors.setVisible(MainPane.ID, true); } })); this.repositoryCountKey = contextKeyService.createKey('scm.providerCount', 0); - this._register(this.viewsModel.onDidAdd(this.onDidShowView, this)); - this._register(this.viewsModel.onDidRemove(this.onDidHideView, this)); + this._register(this.viewsDescriptors.onDidAdd(this.onDidShowView, this)); + this._register(this.viewsDescriptors.onDidRemove(this.onDidHideView, this)); } create(parent: HTMLElement): void { @@ -217,8 +214,8 @@ export class SCMViewPaneContainer extends ViewPaneContainer implements IViewMode @debounce(0) private afterOnDidHideView(): void { - if (this.repositoryCountKey.get()! > 0 && this.viewDescriptors.every(d => !this.viewsModel.isVisible(d.id))) { - this.viewsModel.setVisible(this.viewDescriptors[0].id, true); + if (this.repositoryCountKey.get()! > 0 && this.viewDescriptors.every(d => !this.viewsDescriptors.isVisible(d.id))) { + this.viewsDescriptors.setVisible(this.viewDescriptors[0].id, true); } } @@ -285,9 +282,9 @@ export class SCMViewPaneContainer extends ViewPaneContainer implements IViewMode } setVisibleRepositories(repositories: ISCMRepository[]): void { - const visibleViewDescriptors = this.viewsModel.visibleViewDescriptors; + const visibleViewDescriptors = this.viewsDescriptors.visibleViewDescriptors; - const toSetVisible = this.viewsModel.viewDescriptors + const toSetVisible = this.viewsDescriptors.activeViewDescriptors .filter((d): d is RepositoryViewDescriptor => d instanceof RepositoryViewDescriptor && repositories.indexOf(d.repository) > -1 && visibleViewDescriptors.indexOf(d) === -1); const toSetInvisible = visibleViewDescriptors @@ -305,11 +302,11 @@ export class SCMViewPaneContainer extends ViewPaneContainer implements IViewMode } } - this.viewsModel.setVisible(viewDescriptor.id, false); + this.viewsDescriptors.setVisible(viewDescriptor.id, false); } for (const viewDescriptor of toSetVisible) { - this.viewsModel.setVisible(viewDescriptor.id, true, size); + this.viewsDescriptors.setVisible(viewDescriptor.id, true, size); } } } diff --git a/src/vs/workbench/contrib/search/browser/search.contribution.ts b/src/vs/workbench/contrib/search/browser/search.contribution.ts index 8958f059445..33980a72b56 100644 --- a/src/vs/workbench/contrib/search/browser/search.contribution.ts +++ b/src/vs/workbench/contrib/search/browser/search.contribution.ts @@ -504,7 +504,7 @@ class ShowAllSymbolsAction extends Action { const viewContainer = Registry.as(ViewExtensions.ViewContainersRegistry).registerViewContainer({ id: VIEWLET_ID, name: nls.localize('name', "Search"), - ctorDescriptor: new SyncDescriptor(ViewPaneContainer, [VIEWLET_ID, `${VIEWLET_ID}.state`, { mergeViewWithContainerWhenSingleView: true, donotShowContainerTitleWhenMergedWithContainer: true }]), + ctorDescriptor: new SyncDescriptor(ViewPaneContainer, [VIEWLET_ID, { mergeViewWithContainerWhenSingleView: true, donotShowContainerTitleWhenMergedWithContainer: true }]), hideIfEmpty: true, icon: 'codicon-search', order: 1 diff --git a/src/vs/workbench/contrib/terminal/browser/terminal.contribution.ts b/src/vs/workbench/contrib/terminal/browser/terminal.contribution.ts index 7c3ce8efd89..f43c490c9c3 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminal.contribution.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminal.contribution.ts @@ -68,7 +68,8 @@ if (platform.isWeb) { const VIEW_CONTAINER = Registry.as(ViewContainerExtensions.ViewContainersRegistry).registerViewContainer({ id: TERMINAL_VIEW_ID, name: nls.localize('terminal', "Terminal"), - ctorDescriptor: new SyncDescriptor(ViewPaneContainer, [TERMINAL_VIEW_ID, TERMINAL_VIEW_ID, { mergeViewWithContainerWhenSingleView: true, donotShowContainerTitleWhenMergedWithContainer: true }]), + ctorDescriptor: new SyncDescriptor(ViewPaneContainer, [TERMINAL_VIEW_ID, { mergeViewWithContainerWhenSingleView: true, donotShowContainerTitleWhenMergedWithContainer: true }]), + storageId: TERMINAL_VIEW_ID, focusCommand: { id: TERMINAL_COMMAND_ID.FOCUS }, hideIfEmpty: true, order: 3 diff --git a/src/vs/workbench/contrib/userDataSync/browser/userDataSyncView.ts b/src/vs/workbench/contrib/userDataSync/browser/userDataSyncView.ts index ba6877133d5..7f2c4f35174 100644 --- a/src/vs/workbench/contrib/userDataSync/browser/userDataSyncView.ts +++ b/src/vs/workbench/contrib/userDataSync/browser/userDataSyncView.ts @@ -39,7 +39,7 @@ export class UserDataSyncViewContribution implements IWorkbenchContribution { name: localize('sync preferences', "Preferences Sync"), ctorDescriptor: new SyncDescriptor( ViewPaneContainer, - ['workbench.view.sync', `workbench.view.sync.state`, { mergeViewWithContainerWhenSingleView: true }] + ['workbench.view.sync', { mergeViewWithContainerWhenSingleView: true }] ), icon: 'codicon-sync', hideIfEmpty: true, diff --git a/src/vs/workbench/services/views/browser/viewDescriptorService.ts b/src/vs/workbench/services/views/browser/viewDescriptorService.ts index 7c52f852fdf..39a14e295c5 100644 --- a/src/vs/workbench/services/views/browser/viewDescriptorService.ts +++ b/src/vs/workbench/services/views/browser/viewDescriptorService.ts @@ -3,8 +3,8 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { ViewContainerLocation, IViewDescriptorService, ViewContainer, IViewsRegistry, IViewContainersRegistry, IViewDescriptor, Extensions as ViewExtensions, IViewDescriptorCollection } from 'vs/workbench/common/views'; -import { IContextKey, RawContextKey, IContextKeyService, IReadableSet, IContextKeyChangeEvent } from 'vs/platform/contextkey/common/contextkey'; +import { ViewContainerLocation, IViewDescriptorService, ViewContainer, IViewsRegistry, IViewContainersRegistry, IViewDescriptor, Extensions as ViewExtensions, IViewDescriptorCollection, IAddedViewDescriptorRef, IViewDescriptorRef } from 'vs/workbench/common/views'; +import { IContextKey, RawContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IStorageService, StorageScope, IWorkspaceStorageChangeEvent } from 'vs/platform/storage/common/storage'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { Registry } from 'vs/platform/registry/common/platform'; @@ -13,162 +13,52 @@ import { ViewPaneContainer } from 'vs/workbench/browser/parts/views/viewPaneCont import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { Event, Emitter } from 'vs/base/common/event'; -import { firstIndex } from 'vs/base/common/arrays'; import { IStorageKeysSyncRegistryService } from 'vs/platform/userDataSync/common/storageKeys'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { generateUuid } from 'vs/base/common/uuid'; - -class CounterSet implements IReadableSet { - - private map = new Map(); - - add(value: T): CounterSet { - this.map.set(value, (this.map.get(value) || 0) + 1); - return this; - } - - delete(value: T): boolean { - let counter = this.map.get(value) || 0; - - if (counter === 0) { - return false; - } - - counter--; - - if (counter === 0) { - this.map.delete(value); - } else { - this.map.set(value, counter); - } - - return true; - } - - has(value: T): boolean { - return this.map.has(value); - } -} - -interface IViewItem { - viewDescriptor: IViewDescriptor; - active: boolean; -} +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { PersistentContributableViewsModel, ViewDescriptorsModel } from 'vs/workbench/services/views/common/viewsModel'; class ViewDescriptorCollection extends Disposable implements IViewDescriptorCollection { - private contextKeys = new CounterSet(); - private items: IViewItem[] = []; - - private _onDidChangeViews: Emitter<{ added: IViewDescriptor[], removed: IViewDescriptor[]; }> = this._register(new Emitter<{ added: IViewDescriptor[], removed: IViewDescriptor[]; }>()); - readonly onDidChangeViews: Event<{ added: IViewDescriptor[], removed: IViewDescriptor[]; }> = this._onDidChangeViews.event; - - private _onDidChangeActiveViews: Emitter<{ added: IViewDescriptor[], removed: IViewDescriptor[]; }> = this._register(new Emitter<{ added: IViewDescriptor[], removed: IViewDescriptor[]; }>()); - readonly onDidChangeActiveViews: Event<{ added: IViewDescriptor[], removed: IViewDescriptor[]; }> = this._onDidChangeActiveViews.event; - - get activeViewDescriptors(): IViewDescriptor[] { - return this.items - .filter(i => i.active) - .map(i => i.viewDescriptor); - } - - get allViewDescriptors(): IViewDescriptor[] { - return this.items.map(i => i.viewDescriptor); - } + private readonly viewDescriptorsModel: ViewDescriptorsModel; + private readonly viewsModel: PersistentContributableViewsModel; constructor( - @IContextKeyService private readonly contextKeyService: IContextKeyService, + container: ViewContainer, + @IInstantiationService instantiationService: IInstantiationService, ) { super(); - this._register(Event.filter(contextKeyService.onDidChangeContext, e => e.affectsSome(this.contextKeys))(this.onContextChanged, this)); + this.viewDescriptorsModel = instantiationService.createInstance(ViewDescriptorsModel); + this.viewsModel = instantiationService.createInstance(PersistentContributableViewsModel, container.storageId || `${container.id}.state`, this.viewDescriptorsModel); } - addViews(viewDescriptors: IViewDescriptor[]): void { - const added: IViewDescriptor[] = []; + get allViewDescriptors(): IViewDescriptor[] { return this.viewDescriptorsModel.allViewDescriptors; } + get onDidChangeViews(): Event<{ added: IViewDescriptor[], removed: IViewDescriptor[] }> { return this.viewDescriptorsModel.onDidChangeViews; } - for (const viewDescriptor of viewDescriptors) { - const item = { - viewDescriptor, - active: this.isViewDescriptorActive(viewDescriptor) // TODO: should read from some state? - }; + get activeViewDescriptors(): IViewDescriptor[] { return this.viewDescriptorsModel.activeViewDescriptors; } + get _onDidChangeActiveViews(): Event<{ added: IViewDescriptor[], removed: IViewDescriptor[]; }> { return this.viewDescriptorsModel.onDidChangeActiveViews; } + get onDidChangeActiveViews(): Event> { return this.viewsModel.onDidChangeActiveViews; } - this.items.push(item); + addViews(viewDescriptors: IViewDescriptor[]): void { return this.viewDescriptorsModel.addViews(viewDescriptors); } + removeViews(viewDescriptors: IViewDescriptor[]): void { return this.viewDescriptorsModel.removeViews(viewDescriptors); } - if (viewDescriptor.when) { - for (const key of viewDescriptor.when.keys()) { - this.contextKeys.add(key); - } - } + get visibleViewDescriptors(): IViewDescriptor[] { return this.viewsModel.visibleViewDescriptors; } + get onDidAdd(): Event { return this.viewsModel.onDidAdd; } + get onDidRemove(): Event { return this.viewsModel.onDidRemove; } + get onDidMove(): Event<{ from: IViewDescriptorRef; to: IViewDescriptorRef; }> { return this.viewsModel.onDidMove; } - if (item.active) { - added.push(viewDescriptor); - } - } + isVisible(id: string): boolean { return this.viewsModel.isVisible(id); } + setVisible(id: string, visible: boolean, size?: number): void { return this.viewsModel.setVisible(id, visible, size); } - this._onDidChangeViews.fire({ added: viewDescriptors, removed: [] }); + isCollapsed(id: string): boolean { return this.viewsModel.isCollapsed(id); } + setCollapsed(id: string, collapsed: boolean): void { return this.viewsModel.setCollapsed(id, collapsed); } - if (added.length) { - this._onDidChangeActiveViews.fire({ added, removed: [] }); - } - } + getSize(id: string): number | undefined { return this.viewsModel.getSize(id); } + setSize(id: string, size: number): void { return this.viewsModel.setSize(id, size); } - removeViews(viewDescriptors: IViewDescriptor[]): void { - const removed: IViewDescriptor[] = []; + move(from: string, to: string): void { return this.viewsModel.move(from, to); } - for (const viewDescriptor of viewDescriptors) { - const index = firstIndex(this.items, i => i.viewDescriptor.id === viewDescriptor.id); - - if (index === -1) { - continue; - } - - const item = this.items[index]; - this.items.splice(index, 1); - - if (viewDescriptor.when) { - for (const key of viewDescriptor.when.keys()) { - this.contextKeys.delete(key); - } - } - - if (item.active) { - removed.push(viewDescriptor); - } - } - - this._onDidChangeViews.fire({ added: [], removed: viewDescriptors }); - - if (removed.length) { - this._onDidChangeActiveViews.fire({ added: [], removed }); - } - } - - private onContextChanged(event: IContextKeyChangeEvent): void { - const removed: IViewDescriptor[] = []; - const added: IViewDescriptor[] = []; - - for (const item of this.items) { - const active = this.isViewDescriptorActive(item.viewDescriptor); - - if (item.active !== active) { - if (active) { - added.push(item.viewDescriptor); - } else { - removed.push(item.viewDescriptor); - } - } - - item.active = active; - } - - if (added.length || removed.length) { - this._onDidChangeActiveViews.fire({ added, removed }); - } - } - - private isViewDescriptorActive(viewDescriptor: IViewDescriptor): boolean { - return !viewDescriptor.when || this.contextKeyService.contextMatchesRules(viewDescriptor.when); - } } interface ICachedViewContainerInfo { @@ -216,6 +106,7 @@ export class ViewDescriptorService extends Disposable implements IViewDescriptor } constructor( + @IInstantiationService private readonly instantiationService: IInstantiationService, @IContextKeyService private readonly contextKeyService: IContextKeyService, @IStorageService private readonly storageService: IStorageService, @IExtensionService private readonly extensionService: IExtensionService, @@ -492,9 +383,10 @@ export class ViewDescriptorService extends Disposable implements IViewDescriptor return this.viewContainersRegistry.registerViewContainer({ id, - ctorDescriptor: new SyncDescriptor(ViewPaneContainer, [id, `${id}.state`, { mergeViewWithContainerWhenSingleView: true, donotShowContainerTitleWhenMergedWithContainer: true }]), + ctorDescriptor: new SyncDescriptor(ViewPaneContainer, [id, { mergeViewWithContainerWhenSingleView: true, donotShowContainerTitleWhenMergedWithContainer: true }]), name: 'Custom Views', // we don't want to see this, so no need to localize icon: location === ViewContainerLocation.Sidebar ? 'codicon-window' : undefined, + storageId: `${id}.state`, hideIfEmpty: true }, location); } @@ -636,10 +528,10 @@ export class ViewDescriptorService extends Disposable implements IViewDescriptor if (!viewDescriptorCollection) { const disposables = new DisposableStore(); - viewDescriptorCollection = disposables.add(new ViewDescriptorCollection(this.contextKeyService)); + viewDescriptorCollection = disposables.add(this.instantiationService.createInstance(ViewDescriptorCollection, viewContainer)); this.onDidChangeActiveViews({ added: viewDescriptorCollection.activeViewDescriptors, removed: [] }); - viewDescriptorCollection.onDidChangeActiveViews(changed => this.onDidChangeActiveViews(changed), this, disposables); + viewDescriptorCollection._onDidChangeActiveViews(changed => this.onDidChangeActiveViews(changed), this, disposables); this.viewDescriptorCollections.set(viewContainer, { viewDescriptorCollection, disposable: disposables }); diff --git a/src/vs/workbench/services/views/common/viewsModel.ts b/src/vs/workbench/services/views/common/viewsModel.ts new file mode 100644 index 00000000000..3e84efe31cc --- /dev/null +++ b/src/vs/workbench/services/views/common/viewsModel.ts @@ -0,0 +1,635 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Disposable } from 'vs/base/common/lifecycle'; +import { IViewDescriptor, IAddedViewDescriptorRef, IViewDescriptorRef } from 'vs/workbench/common/views'; +import { IStorageService, StorageScope, IWorkspaceStorageChangeEvent } from 'vs/platform/storage/common/storage'; +import { Event, Emitter } from 'vs/base/common/event'; +import { firstIndex, move } from 'vs/base/common/arrays'; +import { isUndefinedOrNull, isUndefined } from 'vs/base/common/types'; +import { values } from 'vs/base/common/map'; +import { IStorageKeysSyncRegistryService } from 'vs/platform/userDataSync/common/storageKeys'; +import { IContextKeyChangeEvent, IContextKeyService, IReadableSet } from 'vs/platform/contextkey/common/contextkey'; + +class CounterSet implements IReadableSet { + + private map = new Map(); + + add(value: T): CounterSet { + this.map.set(value, (this.map.get(value) || 0) + 1); + return this; + } + + delete(value: T): boolean { + let counter = this.map.get(value) || 0; + + if (counter === 0) { + return false; + } + + counter--; + + if (counter === 0) { + this.map.delete(value); + } else { + this.map.set(value, counter); + } + + return true; + } + + has(value: T): boolean { + return this.map.has(value); + } +} + +interface IViewItem { + viewDescriptor: IViewDescriptor; + active: boolean; +} + +export class ViewDescriptorsModel extends Disposable { + + private contextKeys = new CounterSet(); + private items: IViewItem[] = []; + + private _onDidChangeViews: Emitter<{ added: IViewDescriptor[], removed: IViewDescriptor[]; }> = this._register(new Emitter<{ added: IViewDescriptor[], removed: IViewDescriptor[]; }>()); + readonly onDidChangeViews: Event<{ added: IViewDescriptor[], removed: IViewDescriptor[]; }> = this._onDidChangeViews.event; + + private _onDidChangeActiveViews: Emitter<{ added: IViewDescriptor[], removed: IViewDescriptor[]; }> = this._register(new Emitter<{ added: IViewDescriptor[], removed: IViewDescriptor[]; }>()); + readonly onDidChangeActiveViews: Event<{ added: IViewDescriptor[], removed: IViewDescriptor[]; }> = this._onDidChangeActiveViews.event; + + get activeViewDescriptors(): IViewDescriptor[] { + return this.items + .filter(i => i.active) + .map(i => i.viewDescriptor); + } + + get allViewDescriptors(): IViewDescriptor[] { + return this.items.map(i => i.viewDescriptor); + } + + constructor( + @IContextKeyService private readonly contextKeyService: IContextKeyService, + ) { + super(); + this._register(Event.filter(contextKeyService.onDidChangeContext, e => e.affectsSome(this.contextKeys))(this.onContextChanged, this)); + } + + addViews(viewDescriptors: IViewDescriptor[]): void { + const added: IViewDescriptor[] = []; + + for (const viewDescriptor of viewDescriptors) { + const item = { + viewDescriptor, + active: this.isViewDescriptorActive(viewDescriptor) // TODO: should read from some state? + }; + + this.items.push(item); + + if (viewDescriptor.when) { + for (const key of viewDescriptor.when.keys()) { + this.contextKeys.add(key); + } + } + + if (item.active) { + added.push(viewDescriptor); + } + } + + this._onDidChangeViews.fire({ added: viewDescriptors, removed: [] }); + + if (added.length) { + this._onDidChangeActiveViews.fire({ added, removed: [] }); + } + } + + removeViews(viewDescriptors: IViewDescriptor[]): void { + const removed: IViewDescriptor[] = []; + + for (const viewDescriptor of viewDescriptors) { + const index = firstIndex(this.items, i => i.viewDescriptor.id === viewDescriptor.id); + + if (index === -1) { + continue; + } + + const item = this.items[index]; + this.items.splice(index, 1); + + if (viewDescriptor.when) { + for (const key of viewDescriptor.when.keys()) { + this.contextKeys.delete(key); + } + } + + if (item.active) { + removed.push(viewDescriptor); + } + } + + this._onDidChangeViews.fire({ added: [], removed: viewDescriptors }); + + if (removed.length) { + this._onDidChangeActiveViews.fire({ added: [], removed }); + } + } + + private onContextChanged(event: IContextKeyChangeEvent): void { + const removed: IViewDescriptor[] = []; + const added: IViewDescriptor[] = []; + + for (const item of this.items) { + const active = this.isViewDescriptorActive(item.viewDescriptor); + + if (item.active !== active) { + if (active) { + added.push(item.viewDescriptor); + } else { + removed.push(item.viewDescriptor); + } + } + + item.active = active; + } + + if (added.length || removed.length) { + this._onDidChangeActiveViews.fire({ added, removed }); + } + } + + private isViewDescriptorActive(viewDescriptor: IViewDescriptor): boolean { + return !viewDescriptor.when || this.contextKeyService.contextMatchesRules(viewDescriptor.when); + } +} + +export interface IViewState { + visibleGlobal: boolean | undefined; + visibleWorkspace: boolean | undefined; + collapsed: boolean | undefined; + order?: number; + size?: number; +} + +export class ContributableViewsModel extends Disposable { + + private _viewDescriptors: IViewDescriptor[] = []; + get viewDescriptors(): ReadonlyArray { + return this._viewDescriptors; + } + + get visibleViewDescriptors(): IViewDescriptor[] { + return this.viewDescriptors.filter(v => this.isViewDescriptorVisible(v)); + } + + private _onDidAdd = this._register(new Emitter()); + readonly onDidAdd: Event = this._onDidAdd.event; + + private _onDidRemove = this._register(new Emitter()); + readonly onDidRemove: Event = this._onDidRemove.event; + + private _onDidMove = this._register(new Emitter<{ from: IViewDescriptorRef; to: IViewDescriptorRef; }>()); + readonly onDidMove: Event<{ from: IViewDescriptorRef; to: IViewDescriptorRef; }> = this._onDidMove.event; + + private _onDidChangeViewState = this._register(new Emitter()); + protected readonly onDidChangeViewState: Event = this._onDidChangeViewState.event; + + private _onDidChangeActiveViews = this._register(new Emitter>()); + readonly onDidChangeActiveViews: Event> = this._onDidChangeActiveViews.event; + + constructor( + viewDescriptorCollection: ViewDescriptorsModel, + protected viewStates = new Map(), + ) { + super(); + this._register(viewDescriptorCollection.onDidChangeActiveViews(() => this.onDidChangeViewDescriptors(viewDescriptorCollection.activeViewDescriptors))); + this.onDidChangeViewDescriptors(viewDescriptorCollection.activeViewDescriptors); + } + + isVisible(id: string): boolean { + const viewDescriptor = this.viewDescriptors.filter(v => v.id === id)[0]; + + if (!viewDescriptor) { + throw new Error(`Unknown view ${id}`); + } + + return this.isViewDescriptorVisible(viewDescriptor); + } + + setVisible(id: string, visible: boolean, size?: number): void { + this.doSetVisible([{ id, visible, size }]); + } + + protected doSetVisible(viewDescriptors: { id: string, visible: boolean, size?: number }[]): void { + const added: IAddedViewDescriptorRef[] = []; + const removed: IViewDescriptorRef[] = []; + + for (const { visibleIndex, viewDescriptor, state, visible, size } of viewDescriptors.map(({ id, visible, size }) => ({ ...this.find(id), visible, size }))) { + + if (!viewDescriptor.canToggleVisibility) { + throw new Error(`Can't toggle this view's visibility`); + } + + if (this.isViewDescriptorVisible(viewDescriptor) === visible) { + return; + } + + if (viewDescriptor.workspace) { + state.visibleWorkspace = visible; + } else { + state.visibleGlobal = visible; + } + + if (typeof size === 'number') { + state.size = size; + } + + if (visible) { + added.push({ index: visibleIndex, viewDescriptor, size: state.size, collapsed: !!state.collapsed }); + } else { + removed.push({ index: visibleIndex, viewDescriptor }); + } + } + + if (added.length) { + this._onDidAdd.fire(added); + } + if (removed.length) { + this._onDidRemove.fire(removed); + } + } + + isCollapsed(id: string): boolean { + const state = this.viewStates.get(id); + + if (!state) { + throw new Error(`Unknown view ${id}`); + } + + return !!state.collapsed; + } + + setCollapsed(id: string, collapsed: boolean): void { + const { index, state, viewDescriptor } = this.find(id); + if (state.collapsed !== collapsed) { + state.collapsed = collapsed; + this._onDidChangeViewState.fire({ viewDescriptor, index }); + } + } + + getSize(id: string): number | undefined { + const state = this.viewStates.get(id); + + if (!state) { + throw new Error(`Unknown view ${id}`); + } + + return state.size; + } + + setSize(id: string, size: number): void { + const { index, state, viewDescriptor } = this.find(id); + if (state.size !== size) { + state.size = size; + this._onDidChangeViewState.fire({ viewDescriptor, index }); + } + } + + move(from: string, to: string): void { + const fromIndex = firstIndex(this.viewDescriptors, v => v.id === from); + const toIndex = firstIndex(this.viewDescriptors, v => v.id === to); + + const fromViewDescriptor = this.viewDescriptors[fromIndex]; + const toViewDescriptor = this.viewDescriptors[toIndex]; + + move(this._viewDescriptors, fromIndex, toIndex); + + for (let index = 0; index < this.viewDescriptors.length; index++) { + const state = this.viewStates.get(this.viewDescriptors[index].id)!; + state.order = index; + } + + this._onDidMove.fire({ + from: { index: fromIndex, viewDescriptor: fromViewDescriptor }, + to: { index: toIndex, viewDescriptor: toViewDescriptor } + }); + } + + private isViewDescriptorVisible(viewDescriptor: IViewDescriptor): boolean { + const viewState = this.viewStates.get(viewDescriptor.id); + if (!viewState) { + throw new Error(`Unknown view ${viewDescriptor.id}`); + } + return viewDescriptor.workspace ? !!viewState.visibleWorkspace : !!viewState.visibleGlobal; + } + + private find(id: string): { index: number, visibleIndex: number, viewDescriptor: IViewDescriptor, state: IViewState; } { + for (let i = 0, visibleIndex = 0; i < this.viewDescriptors.length; i++) { + const viewDescriptor = this.viewDescriptors[i]; + const state = this.viewStates.get(viewDescriptor.id); + if (!state) { + throw new Error(`View state for ${id} not found`); + } + + if (viewDescriptor.id === id) { + return { index: i, visibleIndex, viewDescriptor, state }; + } + + if (viewDescriptor.workspace ? state.visibleWorkspace : state.visibleGlobal) { + visibleIndex++; + } + } + + throw new Error(`view descriptor ${id} not found`); + } + + private compareViewDescriptors(a: IViewDescriptor, b: IViewDescriptor): number { + if (a.id === b.id) { + return 0; + } + + return (this.getViewOrder(a) - this.getViewOrder(b)) || this.getGroupOrderResult(a, b); + } + + private getGroupOrderResult(a: IViewDescriptor, b: IViewDescriptor) { + if (!a.group || !b.group) { + return 0; + } + + if (a.group === b.group) { + return 0; + } + + return a.group < b.group ? -1 : 1; + } + + private getViewOrder(viewDescriptor: IViewDescriptor): number { + const viewState = this.viewStates.get(viewDescriptor.id); + const viewOrder = viewState && typeof viewState.order === 'number' ? viewState.order : viewDescriptor.order; + return typeof viewOrder === 'number' ? viewOrder : Number.MAX_VALUE; + } + + private onDidChangeViewDescriptors(viewDescriptors: IViewDescriptor[]): void { + for (const viewDescriptor of viewDescriptors) { + const viewState = this.viewStates.get(viewDescriptor.id); + if (viewState) { + // set defaults if not set + if (viewDescriptor.workspace) { + viewState.visibleWorkspace = isUndefinedOrNull(viewState.visibleWorkspace) ? !viewDescriptor.hideByDefault : viewState.visibleWorkspace; + } else { + viewState.visibleGlobal = isUndefinedOrNull(viewState.visibleGlobal) ? !viewDescriptor.hideByDefault : viewState.visibleGlobal; + } + viewState.collapsed = isUndefinedOrNull(viewState.collapsed) ? !!viewDescriptor.collapsed : viewState.collapsed; + } else { + this.viewStates.set(viewDescriptor.id, { + visibleGlobal: !viewDescriptor.hideByDefault, + visibleWorkspace: !viewDescriptor.hideByDefault, + collapsed: !!viewDescriptor.collapsed + }); + } + } + + viewDescriptors = viewDescriptors.sort(this.compareViewDescriptors.bind(this)); + + const toRemove: { index: number, viewDescriptor: IViewDescriptor; }[] = []; + for (let index = 0; index < this._viewDescriptors.length; index++) { + const previousViewDescriptor = this._viewDescriptors[index]; + if (this.isViewDescriptorVisible(previousViewDescriptor) && viewDescriptors.every(viewDescriptor => viewDescriptor.id !== previousViewDescriptor.id)) { + const { visibleIndex } = this.find(previousViewDescriptor.id); + toRemove.push({ index: visibleIndex, viewDescriptor: previousViewDescriptor }); + } + } + + const previous = this._viewDescriptors; + this._viewDescriptors = viewDescriptors.slice(0); + + const toAdd: { index: number, viewDescriptor: IViewDescriptor, size?: number, collapsed: boolean; }[] = []; + for (let i = 0; i < this._viewDescriptors.length; i++) { + const viewDescriptor = this._viewDescriptors[i]; + if (this.isViewDescriptorVisible(viewDescriptor) && previous.every(previousViewDescriptor => previousViewDescriptor.id !== viewDescriptor.id)) { + const { visibleIndex, state } = this.find(viewDescriptor.id); + toAdd.push({ index: visibleIndex, viewDescriptor, size: state.size, collapsed: !!state.collapsed }); + } + } + + if (toRemove.length) { + this._onDidRemove.fire(toRemove); + } + + if (toAdd.length) { + this._onDidAdd.fire(toAdd); + } + + this._onDidChangeActiveViews.fire(this.viewDescriptors); + } +} + +interface IStoredWorkspaceViewState { + collapsed: boolean; + isHidden: boolean; + size?: number; + order?: number; +} + +interface IStoredGlobalViewState { + id: string; + isHidden: boolean; + order?: number; +} + +export class PersistentContributableViewsModel extends ContributableViewsModel { + + private readonly workspaceViewsStateStorageId: string; + private readonly globalViewsStateStorageId: string; + + private storageService: IStorageService; + + constructor( + viewletStateStorageId: string, + viewDescriptorCollection: ViewDescriptorsModel, + @IStorageService storageService: IStorageService, + @IStorageKeysSyncRegistryService storageKeysSyncRegistryService: IStorageKeysSyncRegistryService + ) { + const globalViewsStateStorageId = `${viewletStateStorageId}.hidden`; + storageKeysSyncRegistryService.registerStorageKey({ key: globalViewsStateStorageId, version: 1 }); + const viewStates = PersistentContributableViewsModel.loadViewsStates(viewletStateStorageId, globalViewsStateStorageId, storageService); + + super(viewDescriptorCollection, viewStates); + + this.storageService = storageService; + this.workspaceViewsStateStorageId = viewletStateStorageId; + this.globalViewsStateStorageId = globalViewsStateStorageId; + + this._register(Event.any( + this.onDidAdd, + this.onDidRemove, + Event.map(this.onDidMove, ({ from, to }) => [from, to]), + Event.map(this.onDidChangeViewState, viewDescriptorRef => [viewDescriptorRef])) + (viewDescriptorRefs => this.saveViewsStates())); + + this._globalViewsStatesValue = this.getStoredGlobalViewsStatesValue(); + this._register(this.storageService.onDidChangeStorage(e => this.onDidStorageChange(e))); + } + + private onDidStorageChange(e: IWorkspaceStorageChangeEvent): void { + if (e.key === this.globalViewsStateStorageId && e.scope === StorageScope.GLOBAL + && this.globalViewsStatesValue !== this.getStoredGlobalViewsStatesValue() /* This checks if current window changed the value or not */) { + this._globalViewsStatesValue = undefined; + const storedViewsVisibilityStates = PersistentContributableViewsModel.loadGlobalViewsState(this.globalViewsStateStorageId, this.storageService, StorageScope.GLOBAL); + const changedViews: { id: string, visible: boolean }[] = []; + for (const [id, state] of storedViewsVisibilityStates) { + const viewState = this.viewStates.get(id); + if (viewState) { + if (viewState.visibleGlobal !== !state.isHidden) { + changedViews.push({ id, visible: !state.isHidden }); + } + } + } + if (changedViews.length) { + this.doSetVisible(changedViews); + } + } + } + + private saveViewsStates(): void { + this.saveWorkspaceViewsStates(); + this.saveGlobalViewsStates(); + } + + private saveWorkspaceViewsStates(): void { + const storedViewsStates: { [id: string]: IStoredWorkspaceViewState; } = JSON.parse(this.storageService.get(this.workspaceViewsStateStorageId, StorageScope.WORKSPACE, '{}')); + for (const viewDescriptor of this.viewDescriptors) { + const viewState = this.viewStates.get(viewDescriptor.id); + if (viewState) { + storedViewsStates[viewDescriptor.id] = { + collapsed: !!viewState.collapsed, + isHidden: !viewState.visibleWorkspace, + size: viewState.size, + order: viewDescriptor.workspace && viewState ? viewState.order : undefined + }; + } + } + + if (Object.keys(storedViewsStates).length > 0) { + this.storageService.store(this.workspaceViewsStateStorageId, JSON.stringify(storedViewsStates), StorageScope.WORKSPACE); + } else { + this.storageService.remove(this.workspaceViewsStateStorageId, StorageScope.WORKSPACE); + } + } + + private saveGlobalViewsStates(): void { + const storedViewsVisibilityStates = PersistentContributableViewsModel.loadGlobalViewsState(this.globalViewsStateStorageId, this.storageService, StorageScope.GLOBAL); + for (const viewDescriptor of this.viewDescriptors) { + const viewState = this.viewStates.get(viewDescriptor.id); + storedViewsVisibilityStates.set(viewDescriptor.id, { + id: viewDescriptor.id, + isHidden: viewState && viewDescriptor.canToggleVisibility ? !viewState.visibleGlobal : false, + order: !viewDescriptor.workspace && viewState ? viewState.order : undefined + }); + } + this.globalViewsStatesValue = JSON.stringify(values(storedViewsVisibilityStates)); + } + + private _globalViewsStatesValue: string | undefined; + private get globalViewsStatesValue(): string { + if (!this._globalViewsStatesValue) { + this._globalViewsStatesValue = this.getStoredGlobalViewsStatesValue(); + } + + return this._globalViewsStatesValue; + } + + private set globalViewsStatesValue(globalViewsStatesValue: string) { + if (this.globalViewsStatesValue !== globalViewsStatesValue) { + this._globalViewsStatesValue = globalViewsStatesValue; + this.setStoredGlobalViewsStatesValue(globalViewsStatesValue); + } + } + + private getStoredGlobalViewsStatesValue(): string { + return this.storageService.get(this.globalViewsStateStorageId, StorageScope.GLOBAL, '[]'); + } + + private setStoredGlobalViewsStatesValue(value: string): void { + this.storageService.store(this.globalViewsStateStorageId, value, StorageScope.GLOBAL); + } + + private static loadViewsStates(workspaceViewsStateStorageId: string, globalViewsStateStorageId: string, storageService: IStorageService): Map { + const viewStates = new Map(); + const workspaceViewsStates = <{ [id: string]: IStoredWorkspaceViewState; }>JSON.parse(storageService.get(workspaceViewsStateStorageId, StorageScope.WORKSPACE, '{}')); + for (const id of Object.keys(workspaceViewsStates)) { + const workspaceViewState = workspaceViewsStates[id]; + viewStates.set(id, { + visibleGlobal: undefined, + visibleWorkspace: isUndefined(workspaceViewState.isHidden) ? undefined : !workspaceViewState.isHidden, + collapsed: workspaceViewState.collapsed, + order: workspaceViewState.order, + size: workspaceViewState.size + }); + } + + // Migrate to `viewletStateStorageId` + const workspaceVisibilityStates = this.loadGlobalViewsState(globalViewsStateStorageId, storageService, StorageScope.WORKSPACE); + if (workspaceVisibilityStates.size > 0) { + for (const { id, isHidden } of values(workspaceVisibilityStates)) { + let viewState = viewStates.get(id); + // Not migrated to `viewletStateStorageId` + if (viewState) { + if (isUndefined(viewState.visibleWorkspace)) { + viewState.visibleWorkspace = !isHidden; + } + } else { + viewStates.set(id, { + collapsed: undefined, + visibleGlobal: undefined, + visibleWorkspace: !isHidden, + }); + } + } + storageService.remove(globalViewsStateStorageId, StorageScope.WORKSPACE); + } + + const globalViewsStates = this.loadGlobalViewsState(globalViewsStateStorageId, storageService, StorageScope.GLOBAL); + for (const { id, isHidden, order } of values(globalViewsStates)) { + let viewState = viewStates.get(id); + if (viewState) { + viewState.visibleGlobal = !isHidden; + if (!isUndefined(order)) { + viewState.order = order; + } + } else { + viewStates.set(id, { + visibleGlobal: !isHidden, + order, + collapsed: undefined, + visibleWorkspace: undefined, + }); + } + } + return viewStates; + } + + private static loadGlobalViewsState(globalViewsStateStorageId: string, storageService: IStorageService, scope: StorageScope): Map { + const storedValue = >JSON.parse(storageService.get(globalViewsStateStorageId, scope, '[]')); + let hasDuplicates = false; + const storedGlobalViewsState = storedValue.reduce((result, storedState) => { + if (typeof storedState === 'string' /* migration */) { + hasDuplicates = hasDuplicates || result.has(storedState); + result.set(storedState, { id: storedState, isHidden: true }); + } else { + hasDuplicates = hasDuplicates || result.has(storedState.id); + result.set(storedState.id, storedState); + } + return result; + }, new Map()); + + if (hasDuplicates) { + storageService.store(globalViewsStateStorageId, JSON.stringify(values(storedGlobalViewsState)), scope); + } + + return storedGlobalViewsState; + } +} diff --git a/src/vs/workbench/services/views/test/browser/viewDescriptorService.test.ts b/src/vs/workbench/services/views/test/browser/viewDescriptorService.test.ts new file mode 100644 index 00000000000..ef72f7846c5 --- /dev/null +++ b/src/vs/workbench/services/views/test/browser/viewDescriptorService.test.ts @@ -0,0 +1,265 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as assert from 'assert'; +import { IViewsRegistry, IViewDescriptor, IViewContainersRegistry, Extensions as ViewContainerExtensions, IViewDescriptorService, ViewContainerLocation, ViewContainer } from 'vs/workbench/common/views'; +import { Registry } from 'vs/platform/registry/common/platform'; +import { workbenchInstantiationService } from 'vs/workbench/test/browser/workbenchTestServices'; +import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; +import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; +import { ViewDescriptorService } from 'vs/workbench/services/views/browser/viewDescriptorService'; +import { assertIsDefined } from 'vs/base/common/types'; + +const ViewsRegistry = Registry.as(ViewContainerExtensions.ViewsRegistry); +const sidebarContainer = Registry.as(ViewContainerExtensions.ViewContainersRegistry).registerViewContainer({ id: 'testSidebar', name: 'test', ctorDescriptor: new SyncDescriptor({}) }, ViewContainerLocation.Sidebar); +const panelContainer = Registry.as(ViewContainerExtensions.ViewContainersRegistry).registerViewContainer({ id: 'testPanel', name: 'test', ctorDescriptor: new SyncDescriptor({}) }, ViewContainerLocation.Panel); + +suite('ViewDescriptorService', () => { + + let viewDescriptorService: IViewDescriptorService; + + setup(() => { + const instantiationService: TestInstantiationService = workbenchInstantiationService(); + viewDescriptorService = instantiationService.createInstance(ViewDescriptorService); + }); + + teardown(() => { + ViewsRegistry.deregisterViews(ViewsRegistry.getViews(sidebarContainer), sidebarContainer); + ViewsRegistry.deregisterViews(ViewsRegistry.getViews(panelContainer), panelContainer); + }); + + test('Empty Containers', function () { + const sidebarViews = viewDescriptorService.getViewDescriptors(sidebarContainer); + const panelViews = viewDescriptorService.getViewDescriptors(panelContainer); + assert.equal(sidebarViews.allViewDescriptors.length, 0, 'The sidebar container should have no views yet.'); + assert.equal(panelViews.allViewDescriptors.length, 0, 'The panel container should have no views yet.'); + }); + + test('Register/Deregister', () => { + const viewDescriptors: IViewDescriptor[] = [ + { + id: 'view1', + ctorDescriptor: null!, + name: 'Test View 1', + canMoveView: true + }, + { + id: 'view2', + ctorDescriptor: null!, + name: 'Test View 2', + canMoveView: true + }, + { + id: 'view3', + ctorDescriptor: null!, + name: 'Test View 3', + canMoveView: true + } + ]; + + + ViewsRegistry.registerViews(viewDescriptors.slice(0, 2), sidebarContainer); + ViewsRegistry.registerViews(viewDescriptors.slice(2), panelContainer); + + + let sidebarViews = viewDescriptorService.getViewDescriptors(sidebarContainer); + let panelViews = viewDescriptorService.getViewDescriptors(panelContainer); + + assert.equal(sidebarViews.activeViewDescriptors.length, 2, 'Sidebar should have 2 views'); + assert.equal(panelViews.activeViewDescriptors.length, 1, 'Panel should have 1 view'); + + ViewsRegistry.deregisterViews(viewDescriptors.slice(0, 2), sidebarContainer); + ViewsRegistry.deregisterViews(viewDescriptors.slice(2), panelContainer); + + + sidebarViews = viewDescriptorService.getViewDescriptors(sidebarContainer); + panelViews = viewDescriptorService.getViewDescriptors(panelContainer); + + assert.equal(sidebarViews.activeViewDescriptors.length, 0, 'Sidebar should have no views'); + assert.equal(panelViews.activeViewDescriptors.length, 0, 'Panel should have no views'); + }); + + test('move views to existing containers', async function () { + const viewDescriptors: IViewDescriptor[] = [ + { + id: 'view1', + ctorDescriptor: null!, + name: 'Test View 1', + canMoveView: true + }, + { + id: 'view2', + ctorDescriptor: null!, + name: 'Test View 2', + canMoveView: true + }, + { + id: 'view3', + ctorDescriptor: null!, + name: 'Test View 3', + canMoveView: true + } + ]; + + ViewsRegistry.registerViews(viewDescriptors.slice(0, 2), sidebarContainer); + ViewsRegistry.registerViews(viewDescriptors.slice(2), panelContainer); + + viewDescriptorService.moveViewsToContainer(viewDescriptors.slice(2), sidebarContainer); + viewDescriptorService.moveViewsToContainer(viewDescriptors.slice(0, 2), panelContainer); + + let sidebarViews = viewDescriptorService.getViewDescriptors(sidebarContainer); + let panelViews = viewDescriptorService.getViewDescriptors(panelContainer); + + assert.equal(sidebarViews.activeViewDescriptors.length, 1, 'Sidebar should have 2 views'); + assert.equal(panelViews.activeViewDescriptors.length, 2, 'Panel should have 1 view'); + + assert.notEqual(sidebarViews.activeViewDescriptors.indexOf(viewDescriptors[2]), -1, `Sidebar should have ${viewDescriptors[2].name}`); + assert.notEqual(panelViews.activeViewDescriptors.indexOf(viewDescriptors[0]), -1, `Panel should have ${viewDescriptors[0].name}`); + assert.notEqual(panelViews.activeViewDescriptors.indexOf(viewDescriptors[1]), -1, `Panel should have ${viewDescriptors[1].name}`); + }); + + test('move views to generated containers', async function () { + const viewDescriptors: IViewDescriptor[] = [ + { + id: 'view1', + ctorDescriptor: null!, + name: 'Test View 1', + canMoveView: true + }, + { + id: 'view2', + ctorDescriptor: null!, + name: 'Test View 2', + canMoveView: true + }, + { + id: 'view3', + ctorDescriptor: null!, + name: 'Test View 3', + canMoveView: true + } + ]; + + ViewsRegistry.registerViews(viewDescriptors.slice(0, 2), sidebarContainer); + ViewsRegistry.registerViews(viewDescriptors.slice(2), panelContainer); + + viewDescriptorService.moveViewToLocation(viewDescriptors[0], ViewContainerLocation.Panel); + viewDescriptorService.moveViewToLocation(viewDescriptors[2], ViewContainerLocation.Sidebar); + + let sidebarViews = viewDescriptorService.getViewDescriptors(sidebarContainer); + let panelViews = viewDescriptorService.getViewDescriptors(panelContainer); + + assert.equal(sidebarViews.activeViewDescriptors.length, 1, 'Sidebar container should have 1 view'); + assert.equal(panelViews.activeViewDescriptors.length, 0, 'Panel container should have no views'); + + const generatedPanel = assertIsDefined(viewDescriptorService.getViewContainer(viewDescriptors[0].id)); + const generatedSidebar = assertIsDefined(viewDescriptorService.getViewContainer(viewDescriptors[2].id)); + + assert.equal(viewDescriptorService.getViewContainerLocation(generatedPanel), ViewContainerLocation.Panel, 'Generated Panel should be in located in the panel'); + assert.equal(viewDescriptorService.getViewContainerLocation(generatedSidebar), ViewContainerLocation.Sidebar, 'Generated Sidebar should be in located in the sidebar'); + + assert.equal(viewDescriptorService.getViewContainerLocation(generatedPanel), viewDescriptorService.getViewLocation(viewDescriptors[0].id), 'Panel view location and container location should match'); + assert.equal(viewDescriptorService.getViewContainerLocation(generatedSidebar), viewDescriptorService.getViewLocation(viewDescriptors[2].id), 'Sidebar view location and container location should match'); + + assert.equal(viewDescriptorService.getDefaultContainer(viewDescriptors[2].id), panelContainer, `${viewDescriptors[2].name} has wrong default container`); + assert.equal(viewDescriptorService.getDefaultContainer(viewDescriptors[0].id), sidebarContainer, `${viewDescriptors[0].name} has wrong default container`); + + viewDescriptorService.moveViewToLocation(viewDescriptors[0], ViewContainerLocation.Sidebar); + viewDescriptorService.moveViewToLocation(viewDescriptors[2], ViewContainerLocation.Panel); + + sidebarViews = viewDescriptorService.getViewDescriptors(sidebarContainer); + panelViews = viewDescriptorService.getViewDescriptors(panelContainer); + + assert.equal(sidebarViews.activeViewDescriptors.length, 1, 'Sidebar should have 2 views'); + assert.equal(panelViews.activeViewDescriptors.length, 0, 'Panel should have 1 view'); + + assert.equal(viewDescriptorService.getViewLocation(viewDescriptors[0].id), ViewContainerLocation.Sidebar, 'View should be located in the sidebar'); + assert.equal(viewDescriptorService.getViewLocation(viewDescriptors[2].id), ViewContainerLocation.Panel, 'View should be located in the panel'); + }); + + test('move view events', async function () { + const viewDescriptors: IViewDescriptor[] = [ + { + id: 'view1', + ctorDescriptor: null!, + name: 'Test View 1', + canMoveView: true + }, + { + id: 'view2', + ctorDescriptor: null!, + name: 'Test View 2', + canMoveView: true + }, + { + id: 'view3', + ctorDescriptor: null!, + name: 'Test View 3', + canMoveView: true + } + ]; + + + let expectedSequence = ''; + let actualSequence = ''; + const disposables = []; + + const containerMoveString = (view: IViewDescriptor, from: ViewContainer, to: ViewContainer) => { + return `Moved ${view.id} from ${from.id} to ${to.id}\n`; + }; + + const locationMoveString = (view: IViewDescriptor, from: ViewContainerLocation, to: ViewContainerLocation) => { + return `Moved ${view.id} from ${from === ViewContainerLocation.Sidebar ? 'Sidebar' : 'Panel'} to ${to === ViewContainerLocation.Sidebar ? 'Sidebar' : 'Panel'}\n`; + }; + disposables.push(viewDescriptorService.onDidChangeContainer(({ views, from, to }) => { + views.forEach(view => { + actualSequence += containerMoveString(view, from, to); + }); + })); + + disposables.push(viewDescriptorService.onDidChangeLocation(({ views, from, to }) => { + views.forEach(view => { + actualSequence += locationMoveString(view, from, to); + }); + })); + + ViewsRegistry.registerViews(viewDescriptors.slice(0, 2), sidebarContainer); + ViewsRegistry.registerViews(viewDescriptors.slice(2), panelContainer); + + expectedSequence += locationMoveString(viewDescriptors[0], ViewContainerLocation.Sidebar, ViewContainerLocation.Panel); + viewDescriptorService.moveViewToLocation(viewDescriptors[0], ViewContainerLocation.Panel); + expectedSequence += containerMoveString(viewDescriptors[0], sidebarContainer, viewDescriptorService.getViewContainer(viewDescriptors[0].id)!); + + expectedSequence += locationMoveString(viewDescriptors[2], ViewContainerLocation.Panel, ViewContainerLocation.Sidebar); + viewDescriptorService.moveViewToLocation(viewDescriptors[2], ViewContainerLocation.Sidebar); + expectedSequence += containerMoveString(viewDescriptors[2], panelContainer, viewDescriptorService.getViewContainer(viewDescriptors[2].id)!); + + + expectedSequence += locationMoveString(viewDescriptors[0], ViewContainerLocation.Panel, ViewContainerLocation.Sidebar); + expectedSequence += containerMoveString(viewDescriptors[0], viewDescriptorService.getViewContainer(viewDescriptors[0].id)!, sidebarContainer); + viewDescriptorService.moveViewsToContainer([viewDescriptors[0]], sidebarContainer); + + expectedSequence += locationMoveString(viewDescriptors[2], ViewContainerLocation.Sidebar, ViewContainerLocation.Panel); + expectedSequence += containerMoveString(viewDescriptors[2], viewDescriptorService.getViewContainer(viewDescriptors[2].id)!, panelContainer); + viewDescriptorService.moveViewsToContainer([viewDescriptors[2]], panelContainer); + + expectedSequence += locationMoveString(viewDescriptors[0], ViewContainerLocation.Sidebar, ViewContainerLocation.Panel); + expectedSequence += containerMoveString(viewDescriptors[0], sidebarContainer, panelContainer); + viewDescriptorService.moveViewsToContainer([viewDescriptors[0]], panelContainer); + + expectedSequence += locationMoveString(viewDescriptors[2], ViewContainerLocation.Panel, ViewContainerLocation.Sidebar); + expectedSequence += containerMoveString(viewDescriptors[2], panelContainer, sidebarContainer); + viewDescriptorService.moveViewsToContainer([viewDescriptors[2]], sidebarContainer); + + expectedSequence += locationMoveString(viewDescriptors[1], ViewContainerLocation.Sidebar, ViewContainerLocation.Panel); + expectedSequence += locationMoveString(viewDescriptors[2], ViewContainerLocation.Sidebar, ViewContainerLocation.Panel); + expectedSequence += containerMoveString(viewDescriptors[1], sidebarContainer, panelContainer); + expectedSequence += containerMoveString(viewDescriptors[2], sidebarContainer, panelContainer); + viewDescriptorService.moveViewsToContainer([viewDescriptors[1], viewDescriptors[2]], panelContainer); + + assert.equal(actualSequence, expectedSequence, 'Event sequence not matching expected sequence'); + }); + +}); diff --git a/src/vs/workbench/services/views/test/browser/viewsModel.test.ts b/src/vs/workbench/services/views/test/browser/viewsModel.test.ts new file mode 100644 index 00000000000..dde516354a5 --- /dev/null +++ b/src/vs/workbench/services/views/test/browser/viewsModel.test.ts @@ -0,0 +1,375 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as assert from 'assert'; +import * as sinon from 'sinon'; +import { IViewDescriptor } from 'vs/workbench/common/views'; +import { IDisposable, dispose } from 'vs/base/common/lifecycle'; +import { move } from 'vs/base/common/arrays'; +import { workbenchInstantiationService } from 'vs/workbench/test/browser/workbenchTestServices'; +import { ContextKeyExpr, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; +import { ContextKeyService } from 'vs/platform/contextkey/browser/contextKeyService'; +import { IViewState, ViewDescriptorsModel, ContributableViewsModel } from 'vs/workbench/services/views/common/viewsModel'; + +class ViewDescriptorSequence { + + readonly elements: IViewDescriptor[]; + private disposables: IDisposable[] = []; + + constructor(model: ContributableViewsModel) { + this.elements = [...model.visibleViewDescriptors]; + model.onDidAdd(added => added.forEach(({ viewDescriptor, index }) => this.elements.splice(index, 0, viewDescriptor)), null, this.disposables); + model.onDidRemove(removed => removed.sort((a, b) => b.index - a.index).forEach(({ index }) => this.elements.splice(index, 1)), null, this.disposables); + model.onDidMove(({ from, to }) => move(this.elements, from.index, to.index), null, this.disposables); + } + + dispose() { + this.disposables = dispose(this.disposables); + } +} + +suite('ContributableViewsModel', () => { + + let contextKeyService: IContextKeyService; + + setup(() => { + const instantiationService: TestInstantiationService = workbenchInstantiationService(); + contextKeyService = instantiationService.createInstance(ContextKeyService); + instantiationService.stub(IContextKeyService, contextKeyService); + }); + + test('empty model', function () { + const viewsDescriptorsModel = new ViewDescriptorsModel(contextKeyService); + const model = new ContributableViewsModel(viewsDescriptorsModel); + assert.equal(model.visibleViewDescriptors.length, 0); + }); + + test('register/unregister', () => { + const viewsDescriptorsModel = new ViewDescriptorsModel(contextKeyService); + const model = new ContributableViewsModel(viewsDescriptorsModel); + const seq = new ViewDescriptorSequence(model); + + assert.equal(model.visibleViewDescriptors.length, 0); + assert.equal(seq.elements.length, 0); + + const viewDescriptor: IViewDescriptor = { + id: 'view1', + ctorDescriptor: null!, + name: 'Test View 1' + }; + + viewsDescriptorsModel.addViews([viewDescriptor]); + + assert.equal(model.visibleViewDescriptors.length, 1); + assert.equal(seq.elements.length, 1); + assert.deepEqual(model.visibleViewDescriptors[0], viewDescriptor); + assert.deepEqual(seq.elements[0], viewDescriptor); + + viewsDescriptorsModel.removeViews([viewDescriptor]); + + assert.equal(model.visibleViewDescriptors.length, 0); + assert.equal(seq.elements.length, 0); + }); + + test('when contexts', async function () { + const viewsDescriptorsModel = new ViewDescriptorsModel(contextKeyService); + const model = new ContributableViewsModel(viewsDescriptorsModel); + const seq = new ViewDescriptorSequence(model); + + assert.equal(model.visibleViewDescriptors.length, 0); + assert.equal(seq.elements.length, 0); + + const viewDescriptor: IViewDescriptor = { + id: 'view1', + ctorDescriptor: null!, + name: 'Test View 1', + when: ContextKeyExpr.equals('showview1', true) + }; + + viewsDescriptorsModel.addViews([viewDescriptor]); + assert.equal(model.visibleViewDescriptors.length, 0, 'view should not appear since context isnt in'); + assert.equal(seq.elements.length, 0); + + const key = contextKeyService.createKey('showview1', false); + assert.equal(model.visibleViewDescriptors.length, 0, 'view should still not appear since showview1 isnt true'); + assert.equal(seq.elements.length, 0); + + key.set(true); + await new Promise(c => setTimeout(c, 30)); + assert.equal(model.visibleViewDescriptors.length, 1, 'view should appear'); + assert.equal(seq.elements.length, 1); + assert.deepEqual(model.visibleViewDescriptors[0], viewDescriptor); + assert.equal(seq.elements[0], viewDescriptor); + + key.set(false); + await new Promise(c => setTimeout(c, 30)); + assert.equal(model.visibleViewDescriptors.length, 0, 'view should disappear'); + assert.equal(seq.elements.length, 0); + + viewsDescriptorsModel.removeViews([viewDescriptor]); + assert.equal(model.visibleViewDescriptors.length, 0, 'view should not be there anymore'); + assert.equal(seq.elements.length, 0); + + key.set(true); + await new Promise(c => setTimeout(c, 30)); + assert.equal(model.visibleViewDescriptors.length, 0, 'view should not be there anymore'); + assert.equal(seq.elements.length, 0); + }); + + test('when contexts - multiple', async function () { + const viewsDescriptorsModel = new ViewDescriptorsModel(contextKeyService); + const model = new ContributableViewsModel(viewsDescriptorsModel); + const seq = new ViewDescriptorSequence(model); + + const view1: IViewDescriptor = { id: 'view1', ctorDescriptor: null!, name: 'Test View 1' }; + const view2: IViewDescriptor = { id: 'view2', ctorDescriptor: null!, name: 'Test View 2', when: ContextKeyExpr.equals('showview2', true) }; + + viewsDescriptorsModel.addViews([view1, view2]); + assert.deepEqual(model.visibleViewDescriptors, [view1], 'only view1 should be visible'); + assert.deepEqual(seq.elements, [view1], 'only view1 should be visible'); + + const key = contextKeyService.createKey('showview2', false); + assert.deepEqual(model.visibleViewDescriptors, [view1], 'still only view1 should be visible'); + assert.deepEqual(seq.elements, [view1], 'still only view1 should be visible'); + + key.set(true); + await new Promise(c => setTimeout(c, 30)); + assert.deepEqual(model.visibleViewDescriptors, [view1, view2], 'both views should be visible'); + assert.deepEqual(seq.elements, [view1, view2], 'both views should be visible'); + + viewsDescriptorsModel.removeViews([view1, view2]); + }); + + test('when contexts - multiple 2', async function () { + const viewsDescriptorsModel = new ViewDescriptorsModel(contextKeyService); + const model = new ContributableViewsModel(viewsDescriptorsModel); + const seq = new ViewDescriptorSequence(model); + + const view1: IViewDescriptor = { id: 'view1', ctorDescriptor: null!, name: 'Test View 1', when: ContextKeyExpr.equals('showview1', true) }; + const view2: IViewDescriptor = { id: 'view2', ctorDescriptor: null!, name: 'Test View 2' }; + + viewsDescriptorsModel.addViews([view1, view2]); + assert.deepEqual(model.visibleViewDescriptors, [view2], 'only view2 should be visible'); + assert.deepEqual(seq.elements, [view2], 'only view2 should be visible'); + + const key = contextKeyService.createKey('showview1', false); + assert.deepEqual(model.visibleViewDescriptors, [view2], 'still only view2 should be visible'); + assert.deepEqual(seq.elements, [view2], 'still only view2 should be visible'); + + key.set(true); + await new Promise(c => setTimeout(c, 30)); + assert.deepEqual(model.visibleViewDescriptors, [view1, view2], 'both views should be visible'); + assert.deepEqual(seq.elements, [view1, view2], 'both views should be visible'); + + viewsDescriptorsModel.removeViews([view1, view2]); + }); + + test('setVisible', () => { + const viewsDescriptorsModel = new ViewDescriptorsModel(contextKeyService); + const model = new ContributableViewsModel(viewsDescriptorsModel); + const seq = new ViewDescriptorSequence(model); + + const view1: IViewDescriptor = { id: 'view1', ctorDescriptor: null!, name: 'Test View 1', canToggleVisibility: true }; + const view2: IViewDescriptor = { id: 'view2', ctorDescriptor: null!, name: 'Test View 2', canToggleVisibility: true }; + const view3: IViewDescriptor = { id: 'view3', ctorDescriptor: null!, name: 'Test View 3', canToggleVisibility: true }; + + viewsDescriptorsModel.addViews([view1, view2, view3]); + assert.deepEqual(model.visibleViewDescriptors, [view1, view2, view3]); + assert.deepEqual(seq.elements, [view1, view2, view3]); + + model.setVisible('view2', true); + assert.deepEqual(model.visibleViewDescriptors, [view1, view2, view3], 'nothing should happen'); + assert.deepEqual(seq.elements, [view1, view2, view3]); + + model.setVisible('view2', false); + assert.deepEqual(model.visibleViewDescriptors, [view1, view3], 'view2 should hide'); + assert.deepEqual(seq.elements, [view1, view3]); + + model.setVisible('view1', false); + assert.deepEqual(model.visibleViewDescriptors, [view3], 'view1 should hide'); + assert.deepEqual(seq.elements, [view3]); + + model.setVisible('view3', false); + assert.deepEqual(model.visibleViewDescriptors, [], 'view3 shoud hide'); + assert.deepEqual(seq.elements, []); + + model.setVisible('view1', true); + assert.deepEqual(model.visibleViewDescriptors, [view1], 'view1 should show'); + assert.deepEqual(seq.elements, [view1]); + + model.setVisible('view3', true); + assert.deepEqual(model.visibleViewDescriptors, [view1, view3], 'view3 should show'); + assert.deepEqual(seq.elements, [view1, view3]); + + model.setVisible('view2', true); + assert.deepEqual(model.visibleViewDescriptors, [view1, view2, view3], 'view2 should show'); + assert.deepEqual(seq.elements, [view1, view2, view3]); + + viewsDescriptorsModel.removeViews([view1, view2, view3]); + assert.deepEqual(model.visibleViewDescriptors, []); + assert.deepEqual(seq.elements, []); + }); + + test('move', () => { + const viewsDescriptorsModel = new ViewDescriptorsModel(contextKeyService); + const model = new ContributableViewsModel(viewsDescriptorsModel); + const seq = new ViewDescriptorSequence(model); + + const view1: IViewDescriptor = { id: 'view1', ctorDescriptor: null!, name: 'Test View 1' }; + const view2: IViewDescriptor = { id: 'view2', ctorDescriptor: null!, name: 'Test View 2' }; + const view3: IViewDescriptor = { id: 'view3', ctorDescriptor: null!, name: 'Test View 3' }; + + viewsDescriptorsModel.addViews([view1, view2, view3]); + assert.deepEqual(model.visibleViewDescriptors, [view1, view2, view3], 'model views should be OK'); + assert.deepEqual(seq.elements, [view1, view2, view3], 'sql views should be OK'); + + model.move('view3', 'view1'); + assert.deepEqual(model.visibleViewDescriptors, [view3, view1, view2], 'view3 should go to the front'); + assert.deepEqual(seq.elements, [view3, view1, view2]); + + model.move('view1', 'view2'); + assert.deepEqual(model.visibleViewDescriptors, [view3, view2, view1], 'view1 should go to the end'); + assert.deepEqual(seq.elements, [view3, view2, view1]); + + model.move('view1', 'view3'); + assert.deepEqual(model.visibleViewDescriptors, [view1, view3, view2], 'view1 should go to the front'); + assert.deepEqual(seq.elements, [view1, view3, view2]); + + model.move('view2', 'view3'); + assert.deepEqual(model.visibleViewDescriptors, [view1, view2, view3], 'view2 should go to the middle'); + assert.deepEqual(seq.elements, [view1, view2, view3]); + }); + + test('view states', async function () { + const viewStates = new Map(); + viewStates.set('view1', { visibleGlobal: false, collapsed: false, visibleWorkspace: undefined }); + const viewsDescriptorsModel = new ViewDescriptorsModel(contextKeyService); + const model = new ContributableViewsModel(viewsDescriptorsModel, viewStates); + const seq = new ViewDescriptorSequence(model); + + assert.equal(model.visibleViewDescriptors.length, 0); + assert.equal(seq.elements.length, 0); + + const viewDescriptor: IViewDescriptor = { + id: 'view1', + ctorDescriptor: null!, + name: 'Test View 1' + }; + + viewsDescriptorsModel.addViews([viewDescriptor]); + assert.equal(model.visibleViewDescriptors.length, 0, 'view should not appear since it was set not visible in view state'); + assert.equal(seq.elements.length, 0); + }); + + test('view states and when contexts', async function () { + const viewStates = new Map(); + viewStates.set('view1', { visibleGlobal: false, collapsed: false, visibleWorkspace: undefined }); + const viewsDescriptorsModel = new ViewDescriptorsModel(contextKeyService); + const model = new ContributableViewsModel(viewsDescriptorsModel, viewStates); + const seq = new ViewDescriptorSequence(model); + + assert.equal(model.visibleViewDescriptors.length, 0); + assert.equal(seq.elements.length, 0); + + const viewDescriptor: IViewDescriptor = { + id: 'view1', + ctorDescriptor: null!, + name: 'Test View 1', + when: ContextKeyExpr.equals('showview1', true) + }; + + viewsDescriptorsModel.addViews([viewDescriptor]); + assert.equal(model.visibleViewDescriptors.length, 0, 'view should not appear since context isnt in'); + assert.equal(seq.elements.length, 0); + + const key = contextKeyService.createKey('showview1', false); + assert.equal(model.visibleViewDescriptors.length, 0, 'view should still not appear since showview1 isnt true'); + assert.equal(seq.elements.length, 0); + + key.set(true); + await new Promise(c => setTimeout(c, 30)); + assert.equal(model.visibleViewDescriptors.length, 0, 'view should still not appear since it was set not visible in view state'); + assert.equal(seq.elements.length, 0); + }); + + test('view states and when contexts multiple views', async function () { + const viewStates = new Map(); + viewStates.set('view1', { visibleGlobal: false, collapsed: false, visibleWorkspace: undefined }); + const viewsDescriptorsModel = new ViewDescriptorsModel(contextKeyService); + const model = new ContributableViewsModel(viewsDescriptorsModel, viewStates); + const seq = new ViewDescriptorSequence(model); + + assert.equal(model.visibleViewDescriptors.length, 0); + assert.equal(seq.elements.length, 0); + + const view1: IViewDescriptor = { + id: 'view1', + ctorDescriptor: null!, + name: 'Test View 1', + when: ContextKeyExpr.equals('showview', true) + }; + const view2: IViewDescriptor = { + id: 'view2', + ctorDescriptor: null!, + name: 'Test View 2', + }; + const view3: IViewDescriptor = { + id: 'view3', + ctorDescriptor: null!, + name: 'Test View 3', + when: ContextKeyExpr.equals('showview', true) + }; + + viewsDescriptorsModel.addViews([view1, view2, view3]); + assert.deepEqual(model.visibleViewDescriptors, [view2], 'Only view2 should be visible'); + assert.deepEqual(seq.elements, [view2]); + + const key = contextKeyService.createKey('showview', false); + assert.deepEqual(model.visibleViewDescriptors, [view2], 'Only view2 should be visible'); + assert.deepEqual(seq.elements, [view2]); + + key.set(true); + await new Promise(c => setTimeout(c, 30)); + assert.deepEqual(model.visibleViewDescriptors, [view2, view3], 'view3 should be visible'); + assert.deepEqual(seq.elements, [view2, view3]); + + key.set(false); + await new Promise(c => setTimeout(c, 30)); + assert.deepEqual(model.visibleViewDescriptors, [view2], 'Only view2 should be visible'); + assert.deepEqual(seq.elements, [view2]); + }); + + test('remove event is not triggered if view was hidden and removed', async function () { + const viewsDescriptorsModel = new ViewDescriptorsModel(contextKeyService); + const model = new ContributableViewsModel(viewsDescriptorsModel); + const seq = new ViewDescriptorSequence(model); + + const viewDescriptor: IViewDescriptor = { + id: 'view1', + ctorDescriptor: null!, + name: 'Test View 1', + when: ContextKeyExpr.equals('showview1', true), + canToggleVisibility: true + }; + + viewsDescriptorsModel.addViews([viewDescriptor]); + + const key = contextKeyService.createKey('showview1', true); + await new Promise(c => setTimeout(c, 30)); + assert.equal(model.visibleViewDescriptors.length, 1, 'view should appear after context is set'); + assert.equal(seq.elements.length, 1); + + model.setVisible('view1', false); + assert.equal(model.visibleViewDescriptors.length, 0, 'view should disappear after setting visibility to false'); + assert.equal(seq.elements.length, 0); + + const target = sinon.spy(model.onDidRemove); + key.set(false); + await new Promise(c => setTimeout(c, 30)); + assert.ok(!target.called, 'remove event should not be called since it is already hidden'); + }); + +}); diff --git a/src/vs/workbench/test/browser/parts/views/views.test.ts b/src/vs/workbench/test/browser/parts/views/views.test.ts deleted file mode 100644 index 066b67fa550..00000000000 --- a/src/vs/workbench/test/browser/parts/views/views.test.ts +++ /dev/null @@ -1,628 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import * as assert from 'assert'; -import { ContributableViewsModel, IViewState } from 'vs/workbench/browser/parts/views/views'; -import { IViewsRegistry, IViewDescriptor, IViewContainersRegistry, Extensions as ViewContainerExtensions, IViewDescriptorService, ViewContainerLocation, ViewContainer } from 'vs/workbench/common/views'; -import { IDisposable, dispose } from 'vs/base/common/lifecycle'; -import { move } from 'vs/base/common/arrays'; -import { Registry } from 'vs/platform/registry/common/platform'; -import { workbenchInstantiationService } from 'vs/workbench/test/browser/workbenchTestServices'; -import { ContextKeyExpr, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; -import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; -import { ContextKeyService } from 'vs/platform/contextkey/browser/contextKeyService'; -import sinon = require('sinon'); -import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; -import { ViewDescriptorService } from 'vs/workbench/services/views/browser/viewDescriptorService'; -import { assertIsDefined } from 'vs/base/common/types'; - -const container = Registry.as(ViewContainerExtensions.ViewContainersRegistry).registerViewContainer({ id: 'test', name: 'test', ctorDescriptor: new SyncDescriptor({}) }, ViewContainerLocation.Sidebar); -const ViewsRegistry = Registry.as(ViewContainerExtensions.ViewsRegistry); - -class ViewDescriptorSequence { - - readonly elements: IViewDescriptor[]; - private disposables: IDisposable[] = []; - - constructor(model: ContributableViewsModel) { - this.elements = [...model.visibleViewDescriptors]; - model.onDidAdd(added => added.forEach(({ viewDescriptor, index }) => this.elements.splice(index, 0, viewDescriptor)), null, this.disposables); - model.onDidRemove(removed => removed.sort((a, b) => b.index - a.index).forEach(({ index }) => this.elements.splice(index, 1)), null, this.disposables); - model.onDidMove(({ from, to }) => move(this.elements, from.index, to.index), null, this.disposables); - } - - dispose() { - this.disposables = dispose(this.disposables); - } -} - -suite('ContributableViewsModel', () => { - - let viewDescriptorService: IViewDescriptorService; - let contextKeyService: IContextKeyService; - - setup(() => { - const instantiationService: TestInstantiationService = workbenchInstantiationService(); - contextKeyService = instantiationService.createInstance(ContextKeyService); - instantiationService.stub(IContextKeyService, contextKeyService); - viewDescriptorService = instantiationService.createInstance(ViewDescriptorService); - }); - - teardown(() => { - ViewsRegistry.deregisterViews(ViewsRegistry.getViews(container), container); - }); - - test('empty model', function () { - const model = new ContributableViewsModel(container, viewDescriptorService); - assert.equal(model.visibleViewDescriptors.length, 0); - }); - - test('register/unregister', () => { - const model = new ContributableViewsModel(container, viewDescriptorService); - const seq = new ViewDescriptorSequence(model); - - assert.equal(model.visibleViewDescriptors.length, 0); - assert.equal(seq.elements.length, 0); - - const viewDescriptor: IViewDescriptor = { - id: 'view1', - ctorDescriptor: null!, - name: 'Test View 1' - }; - - ViewsRegistry.registerViews([viewDescriptor], container); - - assert.equal(model.visibleViewDescriptors.length, 1); - assert.equal(seq.elements.length, 1); - assert.deepEqual(model.visibleViewDescriptors[0], viewDescriptor); - assert.deepEqual(seq.elements[0], viewDescriptor); - - ViewsRegistry.deregisterViews([viewDescriptor], container); - - assert.equal(model.visibleViewDescriptors.length, 0); - assert.equal(seq.elements.length, 0); - }); - - test('when contexts', async function () { - const model = new ContributableViewsModel(container, viewDescriptorService); - const seq = new ViewDescriptorSequence(model); - - assert.equal(model.visibleViewDescriptors.length, 0); - assert.equal(seq.elements.length, 0); - - const viewDescriptor: IViewDescriptor = { - id: 'view1', - ctorDescriptor: null!, - name: 'Test View 1', - when: ContextKeyExpr.equals('showview1', true) - }; - - ViewsRegistry.registerViews([viewDescriptor], container); - assert.equal(model.visibleViewDescriptors.length, 0, 'view should not appear since context isnt in'); - assert.equal(seq.elements.length, 0); - - const key = contextKeyService.createKey('showview1', false); - assert.equal(model.visibleViewDescriptors.length, 0, 'view should still not appear since showview1 isnt true'); - assert.equal(seq.elements.length, 0); - - key.set(true); - await new Promise(c => setTimeout(c, 30)); - assert.equal(model.visibleViewDescriptors.length, 1, 'view should appear'); - assert.equal(seq.elements.length, 1); - assert.deepEqual(model.visibleViewDescriptors[0], viewDescriptor); - assert.equal(seq.elements[0], viewDescriptor); - - key.set(false); - await new Promise(c => setTimeout(c, 30)); - assert.equal(model.visibleViewDescriptors.length, 0, 'view should disappear'); - assert.equal(seq.elements.length, 0); - - ViewsRegistry.deregisterViews([viewDescriptor], container); - assert.equal(model.visibleViewDescriptors.length, 0, 'view should not be there anymore'); - assert.equal(seq.elements.length, 0); - - key.set(true); - await new Promise(c => setTimeout(c, 30)); - assert.equal(model.visibleViewDescriptors.length, 0, 'view should not be there anymore'); - assert.equal(seq.elements.length, 0); - }); - - test('when contexts - multiple', async function () { - const model = new ContributableViewsModel(container, viewDescriptorService); - const seq = new ViewDescriptorSequence(model); - - const view1: IViewDescriptor = { id: 'view1', ctorDescriptor: null!, name: 'Test View 1' }; - const view2: IViewDescriptor = { id: 'view2', ctorDescriptor: null!, name: 'Test View 2', when: ContextKeyExpr.equals('showview2', true) }; - - ViewsRegistry.registerViews([view1, view2], container); - assert.deepEqual(model.visibleViewDescriptors, [view1], 'only view1 should be visible'); - assert.deepEqual(seq.elements, [view1], 'only view1 should be visible'); - - const key = contextKeyService.createKey('showview2', false); - assert.deepEqual(model.visibleViewDescriptors, [view1], 'still only view1 should be visible'); - assert.deepEqual(seq.elements, [view1], 'still only view1 should be visible'); - - key.set(true); - await new Promise(c => setTimeout(c, 30)); - assert.deepEqual(model.visibleViewDescriptors, [view1, view2], 'both views should be visible'); - assert.deepEqual(seq.elements, [view1, view2], 'both views should be visible'); - - ViewsRegistry.deregisterViews([view1, view2], container); - }); - - test('when contexts - multiple 2', async function () { - const model = new ContributableViewsModel(container, viewDescriptorService); - const seq = new ViewDescriptorSequence(model); - - const view1: IViewDescriptor = { id: 'view1', ctorDescriptor: null!, name: 'Test View 1', when: ContextKeyExpr.equals('showview1', true) }; - const view2: IViewDescriptor = { id: 'view2', ctorDescriptor: null!, name: 'Test View 2' }; - - ViewsRegistry.registerViews([view1, view2], container); - assert.deepEqual(model.visibleViewDescriptors, [view2], 'only view2 should be visible'); - assert.deepEqual(seq.elements, [view2], 'only view2 should be visible'); - - const key = contextKeyService.createKey('showview1', false); - assert.deepEqual(model.visibleViewDescriptors, [view2], 'still only view2 should be visible'); - assert.deepEqual(seq.elements, [view2], 'still only view2 should be visible'); - - key.set(true); - await new Promise(c => setTimeout(c, 30)); - assert.deepEqual(model.visibleViewDescriptors, [view1, view2], 'both views should be visible'); - assert.deepEqual(seq.elements, [view1, view2], 'both views should be visible'); - - ViewsRegistry.deregisterViews([view1, view2], container); - }); - - test('setVisible', () => { - const model = new ContributableViewsModel(container, viewDescriptorService); - const seq = new ViewDescriptorSequence(model); - - const view1: IViewDescriptor = { id: 'view1', ctorDescriptor: null!, name: 'Test View 1', canToggleVisibility: true }; - const view2: IViewDescriptor = { id: 'view2', ctorDescriptor: null!, name: 'Test View 2', canToggleVisibility: true }; - const view3: IViewDescriptor = { id: 'view3', ctorDescriptor: null!, name: 'Test View 3', canToggleVisibility: true }; - - ViewsRegistry.registerViews([view1, view2, view3], container); - assert.deepEqual(model.visibleViewDescriptors, [view1, view2, view3]); - assert.deepEqual(seq.elements, [view1, view2, view3]); - - model.setVisible('view2', true); - assert.deepEqual(model.visibleViewDescriptors, [view1, view2, view3], 'nothing should happen'); - assert.deepEqual(seq.elements, [view1, view2, view3]); - - model.setVisible('view2', false); - assert.deepEqual(model.visibleViewDescriptors, [view1, view3], 'view2 should hide'); - assert.deepEqual(seq.elements, [view1, view3]); - - model.setVisible('view1', false); - assert.deepEqual(model.visibleViewDescriptors, [view3], 'view1 should hide'); - assert.deepEqual(seq.elements, [view3]); - - model.setVisible('view3', false); - assert.deepEqual(model.visibleViewDescriptors, [], 'view3 shoud hide'); - assert.deepEqual(seq.elements, []); - - model.setVisible('view1', true); - assert.deepEqual(model.visibleViewDescriptors, [view1], 'view1 should show'); - assert.deepEqual(seq.elements, [view1]); - - model.setVisible('view3', true); - assert.deepEqual(model.visibleViewDescriptors, [view1, view3], 'view3 should show'); - assert.deepEqual(seq.elements, [view1, view3]); - - model.setVisible('view2', true); - assert.deepEqual(model.visibleViewDescriptors, [view1, view2, view3], 'view2 should show'); - assert.deepEqual(seq.elements, [view1, view2, view3]); - - ViewsRegistry.deregisterViews([view1, view2, view3], container); - assert.deepEqual(model.visibleViewDescriptors, []); - assert.deepEqual(seq.elements, []); - }); - - test('move', () => { - const model = new ContributableViewsModel(container, viewDescriptorService); - const seq = new ViewDescriptorSequence(model); - - const view1: IViewDescriptor = { id: 'view1', ctorDescriptor: null!, name: 'Test View 1' }; - const view2: IViewDescriptor = { id: 'view2', ctorDescriptor: null!, name: 'Test View 2' }; - const view3: IViewDescriptor = { id: 'view3', ctorDescriptor: null!, name: 'Test View 3' }; - - ViewsRegistry.registerViews([view1, view2, view3], container); - assert.deepEqual(model.visibleViewDescriptors, [view1, view2, view3], 'model views should be OK'); - assert.deepEqual(seq.elements, [view1, view2, view3], 'sql views should be OK'); - - model.move('view3', 'view1'); - assert.deepEqual(model.visibleViewDescriptors, [view3, view1, view2], 'view3 should go to the front'); - assert.deepEqual(seq.elements, [view3, view1, view2]); - - model.move('view1', 'view2'); - assert.deepEqual(model.visibleViewDescriptors, [view3, view2, view1], 'view1 should go to the end'); - assert.deepEqual(seq.elements, [view3, view2, view1]); - - model.move('view1', 'view3'); - assert.deepEqual(model.visibleViewDescriptors, [view1, view3, view2], 'view1 should go to the front'); - assert.deepEqual(seq.elements, [view1, view3, view2]); - - model.move('view2', 'view3'); - assert.deepEqual(model.visibleViewDescriptors, [view1, view2, view3], 'view2 should go to the middle'); - assert.deepEqual(seq.elements, [view1, view2, view3]); - }); - - test('view states', async function () { - const viewStates = new Map(); - viewStates.set('view1', { visibleGlobal: false, collapsed: false, visibleWorkspace: undefined }); - const model = new ContributableViewsModel(container, viewDescriptorService, viewStates); - const seq = new ViewDescriptorSequence(model); - - assert.equal(model.visibleViewDescriptors.length, 0); - assert.equal(seq.elements.length, 0); - - const viewDescriptor: IViewDescriptor = { - id: 'view1', - ctorDescriptor: null!, - name: 'Test View 1' - }; - - ViewsRegistry.registerViews([viewDescriptor], container); - assert.equal(model.visibleViewDescriptors.length, 0, 'view should not appear since it was set not visible in view state'); - assert.equal(seq.elements.length, 0); - }); - - test('view states and when contexts', async function () { - const viewStates = new Map(); - viewStates.set('view1', { visibleGlobal: false, collapsed: false, visibleWorkspace: undefined }); - const model = new ContributableViewsModel(container, viewDescriptorService, viewStates); - const seq = new ViewDescriptorSequence(model); - - assert.equal(model.visibleViewDescriptors.length, 0); - assert.equal(seq.elements.length, 0); - - const viewDescriptor: IViewDescriptor = { - id: 'view1', - ctorDescriptor: null!, - name: 'Test View 1', - when: ContextKeyExpr.equals('showview1', true) - }; - - ViewsRegistry.registerViews([viewDescriptor], container); - assert.equal(model.visibleViewDescriptors.length, 0, 'view should not appear since context isnt in'); - assert.equal(seq.elements.length, 0); - - const key = contextKeyService.createKey('showview1', false); - assert.equal(model.visibleViewDescriptors.length, 0, 'view should still not appear since showview1 isnt true'); - assert.equal(seq.elements.length, 0); - - key.set(true); - await new Promise(c => setTimeout(c, 30)); - assert.equal(model.visibleViewDescriptors.length, 0, 'view should still not appear since it was set not visible in view state'); - assert.equal(seq.elements.length, 0); - }); - - test('view states and when contexts multiple views', async function () { - const viewStates = new Map(); - viewStates.set('view1', { visibleGlobal: false, collapsed: false, visibleWorkspace: undefined }); - const model = new ContributableViewsModel(container, viewDescriptorService, viewStates); - const seq = new ViewDescriptorSequence(model); - - assert.equal(model.visibleViewDescriptors.length, 0); - assert.equal(seq.elements.length, 0); - - const view1: IViewDescriptor = { - id: 'view1', - ctorDescriptor: null!, - name: 'Test View 1', - when: ContextKeyExpr.equals('showview', true) - }; - const view2: IViewDescriptor = { - id: 'view2', - ctorDescriptor: null!, - name: 'Test View 2', - }; - const view3: IViewDescriptor = { - id: 'view3', - ctorDescriptor: null!, - name: 'Test View 3', - when: ContextKeyExpr.equals('showview', true) - }; - - ViewsRegistry.registerViews([view1, view2, view3], container); - assert.deepEqual(model.visibleViewDescriptors, [view2], 'Only view2 should be visible'); - assert.deepEqual(seq.elements, [view2]); - - const key = contextKeyService.createKey('showview', false); - assert.deepEqual(model.visibleViewDescriptors, [view2], 'Only view2 should be visible'); - assert.deepEqual(seq.elements, [view2]); - - key.set(true); - await new Promise(c => setTimeout(c, 30)); - assert.deepEqual(model.visibleViewDescriptors, [view2, view3], 'view3 should be visible'); - assert.deepEqual(seq.elements, [view2, view3]); - - key.set(false); - await new Promise(c => setTimeout(c, 30)); - assert.deepEqual(model.visibleViewDescriptors, [view2], 'Only view2 should be visible'); - assert.deepEqual(seq.elements, [view2]); - }); - - test('remove event is not triggered if view was hidden and removed', async function () { - const model = new ContributableViewsModel(container, viewDescriptorService); - const seq = new ViewDescriptorSequence(model); - - const viewDescriptor: IViewDescriptor = { - id: 'view1', - ctorDescriptor: null!, - name: 'Test View 1', - when: ContextKeyExpr.equals('showview1', true), - canToggleVisibility: true - }; - - ViewsRegistry.registerViews([viewDescriptor], container); - - const key = contextKeyService.createKey('showview1', true); - await new Promise(c => setTimeout(c, 30)); - assert.equal(model.visibleViewDescriptors.length, 1, 'view should appear after context is set'); - assert.equal(seq.elements.length, 1); - - model.setVisible('view1', false); - assert.equal(model.visibleViewDescriptors.length, 0, 'view should disappear after setting visibility to false'); - assert.equal(seq.elements.length, 0); - - const target = sinon.spy(model.onDidRemove); - key.set(false); - await new Promise(c => setTimeout(c, 30)); - assert.ok(!target.called, 'remove event should not be called since it is already hidden'); - }); - -}); - -const sidebarContainer = Registry.as(ViewContainerExtensions.ViewContainersRegistry).registerViewContainer({ id: 'testSidebar', name: 'test', ctorDescriptor: new SyncDescriptor({}) }, ViewContainerLocation.Sidebar); -const panelContainer = Registry.as(ViewContainerExtensions.ViewContainersRegistry).registerViewContainer({ id: 'testPanel', name: 'test', ctorDescriptor: new SyncDescriptor({}) }, ViewContainerLocation.Panel); - -suite('ViewDescriptorService', () => { - - let viewDescriptorService: IViewDescriptorService; - - setup(() => { - const instantiationService: TestInstantiationService = workbenchInstantiationService(); - viewDescriptorService = instantiationService.createInstance(ViewDescriptorService); - }); - - teardown(() => { - ViewsRegistry.deregisterViews(ViewsRegistry.getViews(sidebarContainer), sidebarContainer); - ViewsRegistry.deregisterViews(ViewsRegistry.getViews(panelContainer), panelContainer); - }); - - test('Empty Containers', function () { - const sidebarViews = viewDescriptorService.getViewDescriptors(sidebarContainer); - const panelViews = viewDescriptorService.getViewDescriptors(panelContainer); - assert.equal(sidebarViews.allViewDescriptors.length, 0, 'The sidebar container should have no views yet.'); - assert.equal(panelViews.allViewDescriptors.length, 0, 'The panel container should have no views yet.'); - }); - - test('Register/Deregister', () => { - const viewDescriptors: IViewDescriptor[] = [ - { - id: 'view1', - ctorDescriptor: null!, - name: 'Test View 1', - canMoveView: true - }, - { - id: 'view2', - ctorDescriptor: null!, - name: 'Test View 2', - canMoveView: true - }, - { - id: 'view3', - ctorDescriptor: null!, - name: 'Test View 3', - canMoveView: true - } - ]; - - - ViewsRegistry.registerViews(viewDescriptors.slice(0, 2), sidebarContainer); - ViewsRegistry.registerViews(viewDescriptors.slice(2), panelContainer); - - - let sidebarViews = viewDescriptorService.getViewDescriptors(sidebarContainer); - let panelViews = viewDescriptorService.getViewDescriptors(panelContainer); - - assert.equal(sidebarViews.activeViewDescriptors.length, 2, 'Sidebar should have 2 views'); - assert.equal(panelViews.activeViewDescriptors.length, 1, 'Panel should have 1 view'); - - ViewsRegistry.deregisterViews(viewDescriptors.slice(0, 2), sidebarContainer); - ViewsRegistry.deregisterViews(viewDescriptors.slice(2), panelContainer); - - - sidebarViews = viewDescriptorService.getViewDescriptors(sidebarContainer); - panelViews = viewDescriptorService.getViewDescriptors(panelContainer); - - assert.equal(sidebarViews.activeViewDescriptors.length, 0, 'Sidebar should have no views'); - assert.equal(panelViews.activeViewDescriptors.length, 0, 'Panel should have no views'); - }); - - test('move views to existing containers', async function () { - const viewDescriptors: IViewDescriptor[] = [ - { - id: 'view1', - ctorDescriptor: null!, - name: 'Test View 1', - canMoveView: true - }, - { - id: 'view2', - ctorDescriptor: null!, - name: 'Test View 2', - canMoveView: true - }, - { - id: 'view3', - ctorDescriptor: null!, - name: 'Test View 3', - canMoveView: true - } - ]; - - ViewsRegistry.registerViews(viewDescriptors.slice(0, 2), sidebarContainer); - ViewsRegistry.registerViews(viewDescriptors.slice(2), panelContainer); - - viewDescriptorService.moveViewsToContainer(viewDescriptors.slice(2), sidebarContainer); - viewDescriptorService.moveViewsToContainer(viewDescriptors.slice(0, 2), panelContainer); - - let sidebarViews = viewDescriptorService.getViewDescriptors(sidebarContainer); - let panelViews = viewDescriptorService.getViewDescriptors(panelContainer); - - assert.equal(sidebarViews.activeViewDescriptors.length, 1, 'Sidebar should have 2 views'); - assert.equal(panelViews.activeViewDescriptors.length, 2, 'Panel should have 1 view'); - - assert.notEqual(sidebarViews.activeViewDescriptors.indexOf(viewDescriptors[2]), -1, `Sidebar should have ${viewDescriptors[2].name}`); - assert.notEqual(panelViews.activeViewDescriptors.indexOf(viewDescriptors[0]), -1, `Panel should have ${viewDescriptors[0].name}`); - assert.notEqual(panelViews.activeViewDescriptors.indexOf(viewDescriptors[1]), -1, `Panel should have ${viewDescriptors[1].name}`); - }); - - test('move views to generated containers', async function () { - const viewDescriptors: IViewDescriptor[] = [ - { - id: 'view1', - ctorDescriptor: null!, - name: 'Test View 1', - canMoveView: true - }, - { - id: 'view2', - ctorDescriptor: null!, - name: 'Test View 2', - canMoveView: true - }, - { - id: 'view3', - ctorDescriptor: null!, - name: 'Test View 3', - canMoveView: true - } - ]; - - ViewsRegistry.registerViews(viewDescriptors.slice(0, 2), sidebarContainer); - ViewsRegistry.registerViews(viewDescriptors.slice(2), panelContainer); - - viewDescriptorService.moveViewToLocation(viewDescriptors[0], ViewContainerLocation.Panel); - viewDescriptorService.moveViewToLocation(viewDescriptors[2], ViewContainerLocation.Sidebar); - - let sidebarViews = viewDescriptorService.getViewDescriptors(sidebarContainer); - let panelViews = viewDescriptorService.getViewDescriptors(panelContainer); - - assert.equal(sidebarViews.activeViewDescriptors.length, 1, 'Sidebar container should have 1 view'); - assert.equal(panelViews.activeViewDescriptors.length, 0, 'Panel container should have no views'); - - const generatedPanel = assertIsDefined(viewDescriptorService.getViewContainer(viewDescriptors[0].id)); - const generatedSidebar = assertIsDefined(viewDescriptorService.getViewContainer(viewDescriptors[2].id)); - - assert.equal(viewDescriptorService.getViewContainerLocation(generatedPanel), ViewContainerLocation.Panel, 'Generated Panel should be in located in the panel'); - assert.equal(viewDescriptorService.getViewContainerLocation(generatedSidebar), ViewContainerLocation.Sidebar, 'Generated Sidebar should be in located in the sidebar'); - - assert.equal(viewDescriptorService.getViewContainerLocation(generatedPanel), viewDescriptorService.getViewLocation(viewDescriptors[0].id), 'Panel view location and container location should match'); - assert.equal(viewDescriptorService.getViewContainerLocation(generatedSidebar), viewDescriptorService.getViewLocation(viewDescriptors[2].id), 'Sidebar view location and container location should match'); - - assert.equal(viewDescriptorService.getDefaultContainer(viewDescriptors[2].id), panelContainer, `${viewDescriptors[2].name} has wrong default container`); - assert.equal(viewDescriptorService.getDefaultContainer(viewDescriptors[0].id), sidebarContainer, `${viewDescriptors[0].name} has wrong default container`); - - viewDescriptorService.moveViewToLocation(viewDescriptors[0], ViewContainerLocation.Sidebar); - viewDescriptorService.moveViewToLocation(viewDescriptors[2], ViewContainerLocation.Panel); - - sidebarViews = viewDescriptorService.getViewDescriptors(sidebarContainer); - panelViews = viewDescriptorService.getViewDescriptors(panelContainer); - - assert.equal(sidebarViews.activeViewDescriptors.length, 1, 'Sidebar should have 2 views'); - assert.equal(panelViews.activeViewDescriptors.length, 0, 'Panel should have 1 view'); - - assert.equal(viewDescriptorService.getViewLocation(viewDescriptors[0].id), ViewContainerLocation.Sidebar, 'View should be located in the sidebar'); - assert.equal(viewDescriptorService.getViewLocation(viewDescriptors[2].id), ViewContainerLocation.Panel, 'View should be located in the panel'); - }); - - test('move view events', async function () { - const viewDescriptors: IViewDescriptor[] = [ - { - id: 'view1', - ctorDescriptor: null!, - name: 'Test View 1', - canMoveView: true - }, - { - id: 'view2', - ctorDescriptor: null!, - name: 'Test View 2', - canMoveView: true - }, - { - id: 'view3', - ctorDescriptor: null!, - name: 'Test View 3', - canMoveView: true - } - ]; - - - let expectedSequence = ''; - let actualSequence = ''; - const disposables = []; - - const containerMoveString = (view: IViewDescriptor, from: ViewContainer, to: ViewContainer) => { - return `Moved ${view.id} from ${from.id} to ${to.id}\n`; - }; - - const locationMoveString = (view: IViewDescriptor, from: ViewContainerLocation, to: ViewContainerLocation) => { - return `Moved ${view.id} from ${from === ViewContainerLocation.Sidebar ? 'Sidebar' : 'Panel'} to ${to === ViewContainerLocation.Sidebar ? 'Sidebar' : 'Panel'}\n`; - }; - disposables.push(viewDescriptorService.onDidChangeContainer(({ views, from, to }) => { - views.forEach(view => { - actualSequence += containerMoveString(view, from, to); - }); - })); - - disposables.push(viewDescriptorService.onDidChangeLocation(({ views, from, to }) => { - views.forEach(view => { - actualSequence += locationMoveString(view, from, to); - }); - })); - - ViewsRegistry.registerViews(viewDescriptors.slice(0, 2), sidebarContainer); - ViewsRegistry.registerViews(viewDescriptors.slice(2), panelContainer); - - expectedSequence += locationMoveString(viewDescriptors[0], ViewContainerLocation.Sidebar, ViewContainerLocation.Panel); - viewDescriptorService.moveViewToLocation(viewDescriptors[0], ViewContainerLocation.Panel); - expectedSequence += containerMoveString(viewDescriptors[0], sidebarContainer, viewDescriptorService.getViewContainer(viewDescriptors[0].id)!); - - expectedSequence += locationMoveString(viewDescriptors[2], ViewContainerLocation.Panel, ViewContainerLocation.Sidebar); - viewDescriptorService.moveViewToLocation(viewDescriptors[2], ViewContainerLocation.Sidebar); - expectedSequence += containerMoveString(viewDescriptors[2], panelContainer, viewDescriptorService.getViewContainer(viewDescriptors[2].id)!); - - - expectedSequence += locationMoveString(viewDescriptors[0], ViewContainerLocation.Panel, ViewContainerLocation.Sidebar); - expectedSequence += containerMoveString(viewDescriptors[0], viewDescriptorService.getViewContainer(viewDescriptors[0].id)!, sidebarContainer); - viewDescriptorService.moveViewsToContainer([viewDescriptors[0]], sidebarContainer); - - expectedSequence += locationMoveString(viewDescriptors[2], ViewContainerLocation.Sidebar, ViewContainerLocation.Panel); - expectedSequence += containerMoveString(viewDescriptors[2], viewDescriptorService.getViewContainer(viewDescriptors[2].id)!, panelContainer); - viewDescriptorService.moveViewsToContainer([viewDescriptors[2]], panelContainer); - - expectedSequence += locationMoveString(viewDescriptors[0], ViewContainerLocation.Sidebar, ViewContainerLocation.Panel); - expectedSequence += containerMoveString(viewDescriptors[0], sidebarContainer, panelContainer); - viewDescriptorService.moveViewsToContainer([viewDescriptors[0]], panelContainer); - - expectedSequence += locationMoveString(viewDescriptors[2], ViewContainerLocation.Panel, ViewContainerLocation.Sidebar); - expectedSequence += containerMoveString(viewDescriptors[2], panelContainer, sidebarContainer); - viewDescriptorService.moveViewsToContainer([viewDescriptors[2]], sidebarContainer); - - expectedSequence += locationMoveString(viewDescriptors[1], ViewContainerLocation.Sidebar, ViewContainerLocation.Panel); - expectedSequence += locationMoveString(viewDescriptors[2], ViewContainerLocation.Sidebar, ViewContainerLocation.Panel); - expectedSequence += containerMoveString(viewDescriptors[1], sidebarContainer, panelContainer); - expectedSequence += containerMoveString(viewDescriptors[2], sidebarContainer, panelContainer); - viewDescriptorService.moveViewsToContainer([viewDescriptors[1], viewDescriptors[2]], panelContainer); - - assert.equal(actualSequence, expectedSequence, 'Event sequence not matching expected sequence'); - }); - -}); From f4a5613d81083876b3d2365dd45e12c60c68c403 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Fri, 17 Apr 2020 19:53:22 +0200 Subject: [PATCH 05/44] :lipstick: --- .../views/browser/viewDescriptorService.ts | 6 ++--- .../services/views/common/viewsModel.ts | 10 +++---- .../views/test/browser/viewsModel.test.ts | 26 +++++++++---------- 3 files changed, 21 insertions(+), 21 deletions(-) diff --git a/src/vs/workbench/services/views/browser/viewDescriptorService.ts b/src/vs/workbench/services/views/browser/viewDescriptorService.ts index 39a14e295c5..376f4abab60 100644 --- a/src/vs/workbench/services/views/browser/viewDescriptorService.ts +++ b/src/vs/workbench/services/views/browser/viewDescriptorService.ts @@ -17,12 +17,12 @@ import { IStorageKeysSyncRegistryService } from 'vs/platform/userDataSync/common import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { generateUuid } from 'vs/base/common/uuid'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { PersistentContributableViewsModel, ViewDescriptorsModel } from 'vs/workbench/services/views/common/viewsModel'; +import { PersistentViewsModel, ViewDescriptorsModel } from 'vs/workbench/services/views/common/viewsModel'; class ViewDescriptorCollection extends Disposable implements IViewDescriptorCollection { private readonly viewDescriptorsModel: ViewDescriptorsModel; - private readonly viewsModel: PersistentContributableViewsModel; + private readonly viewsModel: PersistentViewsModel; constructor( container: ViewContainer, @@ -30,7 +30,7 @@ class ViewDescriptorCollection extends Disposable implements IViewDescriptorColl ) { super(); this.viewDescriptorsModel = instantiationService.createInstance(ViewDescriptorsModel); - this.viewsModel = instantiationService.createInstance(PersistentContributableViewsModel, container.storageId || `${container.id}.state`, this.viewDescriptorsModel); + this.viewsModel = instantiationService.createInstance(PersistentViewsModel, container.storageId || `${container.id}.state`, this.viewDescriptorsModel); } get allViewDescriptors(): IViewDescriptor[] { return this.viewDescriptorsModel.allViewDescriptors; } diff --git a/src/vs/workbench/services/views/common/viewsModel.ts b/src/vs/workbench/services/views/common/viewsModel.ts index 3e84efe31cc..607a10586a7 100644 --- a/src/vs/workbench/services/views/common/viewsModel.ts +++ b/src/vs/workbench/services/views/common/viewsModel.ts @@ -174,7 +174,7 @@ export interface IViewState { size?: number; } -export class ContributableViewsModel extends Disposable { +export class ViewsModel extends Disposable { private _viewDescriptors: IViewDescriptor[] = []; get viewDescriptors(): ReadonlyArray { @@ -440,7 +440,7 @@ interface IStoredGlobalViewState { order?: number; } -export class PersistentContributableViewsModel extends ContributableViewsModel { +export class PersistentViewsModel extends ViewsModel { private readonly workspaceViewsStateStorageId: string; private readonly globalViewsStateStorageId: string; @@ -455,7 +455,7 @@ export class PersistentContributableViewsModel extends ContributableViewsModel { ) { const globalViewsStateStorageId = `${viewletStateStorageId}.hidden`; storageKeysSyncRegistryService.registerStorageKey({ key: globalViewsStateStorageId, version: 1 }); - const viewStates = PersistentContributableViewsModel.loadViewsStates(viewletStateStorageId, globalViewsStateStorageId, storageService); + const viewStates = PersistentViewsModel.loadViewsStates(viewletStateStorageId, globalViewsStateStorageId, storageService); super(viewDescriptorCollection, viewStates); @@ -478,7 +478,7 @@ export class PersistentContributableViewsModel extends ContributableViewsModel { if (e.key === this.globalViewsStateStorageId && e.scope === StorageScope.GLOBAL && this.globalViewsStatesValue !== this.getStoredGlobalViewsStatesValue() /* This checks if current window changed the value or not */) { this._globalViewsStatesValue = undefined; - const storedViewsVisibilityStates = PersistentContributableViewsModel.loadGlobalViewsState(this.globalViewsStateStorageId, this.storageService, StorageScope.GLOBAL); + const storedViewsVisibilityStates = PersistentViewsModel.loadGlobalViewsState(this.globalViewsStateStorageId, this.storageService, StorageScope.GLOBAL); const changedViews: { id: string, visible: boolean }[] = []; for (const [id, state] of storedViewsVisibilityStates) { const viewState = this.viewStates.get(id); @@ -521,7 +521,7 @@ export class PersistentContributableViewsModel extends ContributableViewsModel { } private saveGlobalViewsStates(): void { - const storedViewsVisibilityStates = PersistentContributableViewsModel.loadGlobalViewsState(this.globalViewsStateStorageId, this.storageService, StorageScope.GLOBAL); + const storedViewsVisibilityStates = PersistentViewsModel.loadGlobalViewsState(this.globalViewsStateStorageId, this.storageService, StorageScope.GLOBAL); for (const viewDescriptor of this.viewDescriptors) { const viewState = this.viewStates.get(viewDescriptor.id); storedViewsVisibilityStates.set(viewDescriptor.id, { diff --git a/src/vs/workbench/services/views/test/browser/viewsModel.test.ts b/src/vs/workbench/services/views/test/browser/viewsModel.test.ts index dde516354a5..dd7e1c6c18f 100644 --- a/src/vs/workbench/services/views/test/browser/viewsModel.test.ts +++ b/src/vs/workbench/services/views/test/browser/viewsModel.test.ts @@ -12,14 +12,14 @@ import { workbenchInstantiationService } from 'vs/workbench/test/browser/workben import { ContextKeyExpr, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; import { ContextKeyService } from 'vs/platform/contextkey/browser/contextKeyService'; -import { IViewState, ViewDescriptorsModel, ContributableViewsModel } from 'vs/workbench/services/views/common/viewsModel'; +import { IViewState, ViewDescriptorsModel, ViewsModel } from 'vs/workbench/services/views/common/viewsModel'; class ViewDescriptorSequence { readonly elements: IViewDescriptor[]; private disposables: IDisposable[] = []; - constructor(model: ContributableViewsModel) { + constructor(model: ViewsModel) { this.elements = [...model.visibleViewDescriptors]; model.onDidAdd(added => added.forEach(({ viewDescriptor, index }) => this.elements.splice(index, 0, viewDescriptor)), null, this.disposables); model.onDidRemove(removed => removed.sort((a, b) => b.index - a.index).forEach(({ index }) => this.elements.splice(index, 1)), null, this.disposables); @@ -43,13 +43,13 @@ suite('ContributableViewsModel', () => { test('empty model', function () { const viewsDescriptorsModel = new ViewDescriptorsModel(contextKeyService); - const model = new ContributableViewsModel(viewsDescriptorsModel); + const model = new ViewsModel(viewsDescriptorsModel); assert.equal(model.visibleViewDescriptors.length, 0); }); test('register/unregister', () => { const viewsDescriptorsModel = new ViewDescriptorsModel(contextKeyService); - const model = new ContributableViewsModel(viewsDescriptorsModel); + const model = new ViewsModel(viewsDescriptorsModel); const seq = new ViewDescriptorSequence(model); assert.equal(model.visibleViewDescriptors.length, 0); @@ -76,7 +76,7 @@ suite('ContributableViewsModel', () => { test('when contexts', async function () { const viewsDescriptorsModel = new ViewDescriptorsModel(contextKeyService); - const model = new ContributableViewsModel(viewsDescriptorsModel); + const model = new ViewsModel(viewsDescriptorsModel); const seq = new ViewDescriptorSequence(model); assert.equal(model.visibleViewDescriptors.length, 0); @@ -121,7 +121,7 @@ suite('ContributableViewsModel', () => { test('when contexts - multiple', async function () { const viewsDescriptorsModel = new ViewDescriptorsModel(contextKeyService); - const model = new ContributableViewsModel(viewsDescriptorsModel); + const model = new ViewsModel(viewsDescriptorsModel); const seq = new ViewDescriptorSequence(model); const view1: IViewDescriptor = { id: 'view1', ctorDescriptor: null!, name: 'Test View 1' }; @@ -145,7 +145,7 @@ suite('ContributableViewsModel', () => { test('when contexts - multiple 2', async function () { const viewsDescriptorsModel = new ViewDescriptorsModel(contextKeyService); - const model = new ContributableViewsModel(viewsDescriptorsModel); + const model = new ViewsModel(viewsDescriptorsModel); const seq = new ViewDescriptorSequence(model); const view1: IViewDescriptor = { id: 'view1', ctorDescriptor: null!, name: 'Test View 1', when: ContextKeyExpr.equals('showview1', true) }; @@ -169,7 +169,7 @@ suite('ContributableViewsModel', () => { test('setVisible', () => { const viewsDescriptorsModel = new ViewDescriptorsModel(contextKeyService); - const model = new ContributableViewsModel(viewsDescriptorsModel); + const model = new ViewsModel(viewsDescriptorsModel); const seq = new ViewDescriptorSequence(model); const view1: IViewDescriptor = { id: 'view1', ctorDescriptor: null!, name: 'Test View 1', canToggleVisibility: true }; @@ -215,7 +215,7 @@ suite('ContributableViewsModel', () => { test('move', () => { const viewsDescriptorsModel = new ViewDescriptorsModel(contextKeyService); - const model = new ContributableViewsModel(viewsDescriptorsModel); + const model = new ViewsModel(viewsDescriptorsModel); const seq = new ViewDescriptorSequence(model); const view1: IViewDescriptor = { id: 'view1', ctorDescriptor: null!, name: 'Test View 1' }; @@ -247,7 +247,7 @@ suite('ContributableViewsModel', () => { const viewStates = new Map(); viewStates.set('view1', { visibleGlobal: false, collapsed: false, visibleWorkspace: undefined }); const viewsDescriptorsModel = new ViewDescriptorsModel(contextKeyService); - const model = new ContributableViewsModel(viewsDescriptorsModel, viewStates); + const model = new ViewsModel(viewsDescriptorsModel, viewStates); const seq = new ViewDescriptorSequence(model); assert.equal(model.visibleViewDescriptors.length, 0); @@ -268,7 +268,7 @@ suite('ContributableViewsModel', () => { const viewStates = new Map(); viewStates.set('view1', { visibleGlobal: false, collapsed: false, visibleWorkspace: undefined }); const viewsDescriptorsModel = new ViewDescriptorsModel(contextKeyService); - const model = new ContributableViewsModel(viewsDescriptorsModel, viewStates); + const model = new ViewsModel(viewsDescriptorsModel, viewStates); const seq = new ViewDescriptorSequence(model); assert.equal(model.visibleViewDescriptors.length, 0); @@ -299,7 +299,7 @@ suite('ContributableViewsModel', () => { const viewStates = new Map(); viewStates.set('view1', { visibleGlobal: false, collapsed: false, visibleWorkspace: undefined }); const viewsDescriptorsModel = new ViewDescriptorsModel(contextKeyService); - const model = new ContributableViewsModel(viewsDescriptorsModel, viewStates); + const model = new ViewsModel(viewsDescriptorsModel, viewStates); const seq = new ViewDescriptorSequence(model); assert.equal(model.visibleViewDescriptors.length, 0); @@ -344,7 +344,7 @@ suite('ContributableViewsModel', () => { test('remove event is not triggered if view was hidden and removed', async function () { const viewsDescriptorsModel = new ViewDescriptorsModel(contextKeyService); - const model = new ContributableViewsModel(viewsDescriptorsModel); + const model = new ViewsModel(viewsDescriptorsModel); const seq = new ViewDescriptorSequence(model); const viewDescriptor: IViewDescriptor = { From 5d4ceafae24a7a02064e8a16ceea0e50140aab6d Mon Sep 17 00:00:00 2001 From: SteVen Batten Date: Fri, 17 Apr 2020 14:29:59 -0700 Subject: [PATCH 06/44] activity bar support --- .../parts/activitybar/activitybarPart.ts | 23 +++++++++++------- .../browser/parts/compositeBarActions.ts | 2 ++ .../browser/parts/views/viewPaneContainer.ts | 4 +--- src/vs/workbench/common/views.ts | 3 +++ .../views/browser/viewDescriptorService.ts | 24 +++++++++++++++++-- .../services/views/common/viewsModel.ts | 15 +++++++++++- 6 files changed, 57 insertions(+), 14 deletions(-) diff --git a/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts b/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts index e9f064d4168..b55d9c0fca0 100644 --- a/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts +++ b/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts @@ -492,7 +492,13 @@ export class ActivitybarPart extends Part implements IActivityBarService { const viewContainer = this.getViewContainer(viewlet.id)!; const viewDescriptors = this.viewDescriptorService.getViewDescriptors(viewContainer); this.onDidChangeActiveViews(viewlet, viewDescriptors, viewContainer.hideIfEmpty); - this.viewletDisposables.set(viewlet.id, viewDescriptors.onDidChangeActiveViews(() => this.onDidChangeActiveViews(viewlet, viewDescriptors, viewContainer.hideIfEmpty))); + + const disposables = new DisposableStore(); + disposables.add(viewDescriptors.onDidChangeActiveViews(() => this.onDidChangeActiveViews(viewlet, viewDescriptors, viewContainer.hideIfEmpty))); + disposables.add(viewDescriptors.onDidChangeViews(() => this.onDidUpdateViews(viewlet, viewDescriptors))); + disposables.add(viewDescriptors.onDidMove(() => this.onDidUpdateViews(viewlet, viewDescriptors))); + + this.viewletDisposables.set(viewlet.id, disposables); } } @@ -507,16 +513,13 @@ export class ActivitybarPart extends Part implements IActivityBarService { } private updateActivity(viewlet: ViewletDescriptor, viewDescriptors: IViewDescriptorCollection): void { - const viewDescriptor = viewDescriptors.activeViewDescriptors[0]; - - // Use the viewlet icon if any view inside belongs to it statically - const shouldUseViewletIcon = viewDescriptors.allViewDescriptors.some(v => this.viewDescriptorService.getDefaultContainer(v.id)?.id === viewlet.id); + const icon = viewDescriptors.getIcon(); const activity: IActivity = { id: viewlet.id, - name: shouldUseViewletIcon ? viewlet.name : viewDescriptor.name, - cssClass: shouldUseViewletIcon ? viewlet.cssClass : (isString(viewDescriptor.containerIcon) ? viewDescriptor.containerIcon : (viewDescriptor.containerIcon === undefined ? 'codicon-window' : undefined)), - iconUrl: shouldUseViewletIcon ? viewlet.iconUrl : (viewDescriptor.containerIcon instanceof URI ? viewDescriptor.containerIcon : undefined), + name: viewDescriptors.getTitle(), + cssClass: isString(icon) ? icon : undefined, + iconUrl: icon instanceof URI ? icon : undefined, keybindingId: viewlet.keybindingId }; @@ -528,6 +531,10 @@ export class ActivitybarPart extends Part implements IActivityBarService { } } + private onDidUpdateViews(viewlet: ViewletDescriptor, viewDescriptors: IViewDescriptorCollection): void { + this.updateActivity(viewlet, viewDescriptors); + } + private onDidChangeActiveViews(viewlet: ViewletDescriptor, viewDescriptors: IViewDescriptorCollection, hideIfEmpty?: boolean): void { if (viewDescriptors.activeViewDescriptors.length) { this.updateActivity(viewlet, viewDescriptors); diff --git a/src/vs/workbench/browser/parts/compositeBarActions.ts b/src/vs/workbench/browser/parts/compositeBarActions.ts index 969d0712b2f..029f20efa4d 100644 --- a/src/vs/workbench/browser/parts/compositeBarActions.ts +++ b/src/vs/workbench/browser/parts/compositeBarActions.ts @@ -161,9 +161,11 @@ export class ActivityActionViewItem extends BaseActionViewItem { if (this.activity.iconUrl) { // Apply background color to activity bar item provided with iconUrls this.label.style.backgroundColor = foreground ? foreground.toString() : ''; + this.label.style.color = ''; } else { // Apply foreground color to activity bar items provided with codicons this.label.style.color = foreground ? foreground.toString() : ''; + this.label.style.backgroundColor = ''; } const dragColor = colors.activeBackgroundColor || colors.activeForegroundColor; diff --git a/src/vs/workbench/browser/parts/views/viewPaneContainer.ts b/src/vs/workbench/browser/parts/views/viewPaneContainer.ts index 4e3ff25052a..41c93486799 100644 --- a/src/vs/workbench/browser/parts/views/viewPaneContainer.ts +++ b/src/vs/workbench/browser/parts/views/viewPaneContainer.ts @@ -904,9 +904,7 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer { } getTitle(): string { - // use registered title if any of our panes are statically registered to this container - const allViewDescriptors = this.viewDescriptorService.getViewDescriptors(this.viewContainer).allViewDescriptors; - const containerTitle = this.paneItems.length === 0 || allViewDescriptors.length === 0 || allViewDescriptors.some(v => this.viewDescriptorService.getDefaultContainer(v.id) === this.viewContainer) ? this.viewContainer.name : this.paneItems[0].pane.title; + const containerTitle = this.viewsDescriptors.getTitle(); if (this.isViewMergedWithContainer()) { const paneItemTitle = this.paneItems[0].pane.title; diff --git a/src/vs/workbench/common/views.ts b/src/vs/workbench/common/views.ts index 3e1ddd2ad2a..9a75589a419 100644 --- a/src/vs/workbench/common/views.ts +++ b/src/vs/workbench/common/views.ts @@ -241,6 +241,9 @@ export interface IViewDescriptorCollection extends IDisposable { getSize(id: string): number | undefined; setSize(id: string, size: number): void + getTitle(): string; + getIcon(): URI | string | undefined; + move(from: string, to: string): void; } diff --git a/src/vs/workbench/services/views/browser/viewDescriptorService.ts b/src/vs/workbench/services/views/browser/viewDescriptorService.ts index 376f4abab60..f834a9e7330 100644 --- a/src/vs/workbench/services/views/browser/viewDescriptorService.ts +++ b/src/vs/workbench/services/views/browser/viewDescriptorService.ts @@ -18,6 +18,7 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { generateUuid } from 'vs/base/common/uuid'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { PersistentViewsModel, ViewDescriptorsModel } from 'vs/workbench/services/views/common/viewsModel'; +import { URI } from 'vs/base/common/uri'; class ViewDescriptorCollection extends Disposable implements IViewDescriptorCollection { @@ -25,7 +26,7 @@ class ViewDescriptorCollection extends Disposable implements IViewDescriptorColl private readonly viewsModel: PersistentViewsModel; constructor( - container: ViewContainer, + private readonly container: ViewContainer, @IInstantiationService instantiationService: IInstantiationService, ) { super(); @@ -54,11 +55,30 @@ class ViewDescriptorCollection extends Disposable implements IViewDescriptorColl isCollapsed(id: string): boolean { return this.viewsModel.isCollapsed(id); } setCollapsed(id: string, collapsed: boolean): void { return this.viewsModel.setCollapsed(id, collapsed); } + private shouldUseContainerInfo(): boolean { + if (this.allViewDescriptors.length === 0) { + return true; + } + + if (this.visibleViewDescriptors.length === 0) { + return true; + } + + return this.allViewDescriptors.some(v => Registry.as(ViewExtensions.ViewsRegistry).getViewContainer(v.id) === this.container); + } + + getTitle(): string { + return this.shouldUseContainerInfo() ? this.container.name : this.visibleViewDescriptors[0].name; + } + + getIcon(): URI | string | undefined { + return this.shouldUseContainerInfo() ? this.container.icon : this.visibleViewDescriptors[0]?.containerIcon || 'codicon-window'; + } + getSize(id: string): number | undefined { return this.viewsModel.getSize(id); } setSize(id: string, size: number): void { return this.viewsModel.setSize(id, size); } move(from: string, to: string): void { return this.viewsModel.move(from, to); } - } interface ICachedViewContainerInfo { diff --git a/src/vs/workbench/services/views/common/viewsModel.ts b/src/vs/workbench/services/views/common/viewsModel.ts index 607a10586a7..e3fb06acfac 100644 --- a/src/vs/workbench/services/views/common/viewsModel.ts +++ b/src/vs/workbench/services/views/common/viewsModel.ts @@ -182,7 +182,20 @@ export class ViewsModel extends Disposable { } get visibleViewDescriptors(): IViewDescriptor[] { - return this.viewDescriptors.filter(v => this.isViewDescriptorVisible(v)); + return this.viewDescriptors.filter(v => this.isViewDescriptorVisible(v)).sort((a, b) => { + const aIndex = this.viewStates.get(a.id)?.order; + const bIndex = this.viewStates.get(b.id)?.order; + + if (aIndex === undefined) { + return 1; + } + + if (bIndex === undefined) { + return -1; + } + + return aIndex - bIndex; + }); } private _onDidAdd = this._register(new Emitter()); From 20acf30da2fe9ebdf75c7f0c36008e61bbc69c37 Mon Sep 17 00:00:00 2001 From: SteVen Batten Date: Fri, 17 Apr 2020 14:59:57 -0700 Subject: [PATCH 07/44] adopt in panel and revert unnecessary changes --- .../browser/parts/compositeBarActions.ts | 5 -- .../browser/parts/panel/panelActions.ts | 4 ++ .../browser/parts/panel/panelPart.ts | 67 ++++++++++--------- 3 files changed, 38 insertions(+), 38 deletions(-) diff --git a/src/vs/workbench/browser/parts/compositeBarActions.ts b/src/vs/workbench/browser/parts/compositeBarActions.ts index 029f20efa4d..52af0a4bcde 100644 --- a/src/vs/workbench/browser/parts/compositeBarActions.ts +++ b/src/vs/workbench/browser/parts/compositeBarActions.ts @@ -693,9 +693,4 @@ export class ToggleCompositePinnedAction extends Action { this.compositeBar.pin(id); } } - - setActivity(activity: IActivity): void { - this.activity = activity; - this.label = activity.name; - } } diff --git a/src/vs/workbench/browser/parts/panel/panelActions.ts b/src/vs/workbench/browser/parts/panel/panelActions.ts index 831406fd15f..1501c87bcce 100644 --- a/src/vs/workbench/browser/parts/panel/panelActions.ts +++ b/src/vs/workbench/browser/parts/panel/panelActions.ts @@ -200,6 +200,10 @@ export class PlaceHolderToggleCompositePinnedAction extends ToggleCompositePinne constructor(id: string, compositeBar: ICompositeBar) { super({ id, name: id, cssClass: undefined }, compositeBar); } + + setActivity(activity: IActivity): void { + this.label = activity.name; + } } diff --git a/src/vs/workbench/browser/parts/panel/panelPart.ts b/src/vs/workbench/browser/parts/panel/panelPart.ts index 9001b38a9e0..628f11eea3c 100644 --- a/src/vs/workbench/browser/parts/panel/panelPart.ts +++ b/src/vs/workbench/browser/parts/panel/panelPart.ts @@ -39,6 +39,7 @@ import { ViewMenuActions } from 'vs/workbench/browser/parts/views/viewMenuAction import { IPaneComposite } from 'vs/workbench/common/panecomposite'; import { IStorageKeysSyncRegistryService } from 'vs/platform/userDataSync/common/storageKeys'; import { Before2D } from 'vs/workbench/browser/dnd'; +import { IActivity } from 'vs/workbench/common/activity'; interface ICachedPanel { id: string; @@ -209,12 +210,16 @@ export class PanelPart extends CompositePart implements IPanelService { for (const panel of panels) { this.enableCompositeActions(panel); - const viewContainer = this.getViewContainer(panel.id); - if (viewContainer?.hideIfEmpty) { - const viewDescriptors = this.viewDescriptorService.getViewDescriptors(viewContainer); - this.onDidChangeActiveViews(panel, viewDescriptors); - this.panelDisposables.set(panel.id, viewDescriptors.onDidChangeActiveViews(() => this.onDidChangeActiveViews(panel, viewDescriptors))); - } + const viewContainer = this.getViewContainer(panel.id)!; + const viewDescriptors = this.viewDescriptorService.getViewDescriptors(viewContainer); + this.onDidChangeActiveViews(panel, viewDescriptors, viewContainer.hideIfEmpty); + + const disposables = new DisposableStore(); + disposables.add(viewDescriptors.onDidChangeActiveViews(() => this.onDidChangeActiveViews(panel, viewDescriptors, viewContainer.hideIfEmpty))); + disposables.add(viewDescriptors.onDidChangeViews(() => this.onDidUpdateViews(panel, viewDescriptors))); + disposables.add(viewDescriptors.onDidMove(() => this.onDidUpdateViews(panel, viewDescriptors))); + + this.panelDisposables.set(panel.id, disposables); } } @@ -230,19 +235,37 @@ export class PanelPart extends CompositePart implements IPanelService { private enableCompositeActions(panel: PanelDescriptor): void { const { activityAction, pinnedAction } = this.getCompositeActions(panel.id); - if (activityAction instanceof PlaceHolderPanelActivityAction) { - activityAction.setActivity(panel); - } + activityAction.setActivity(panel); if (pinnedAction instanceof PlaceHolderToggleCompositePinnedAction) { pinnedAction.setActivity(panel); } } - private onDidChangeActiveViews(panel: PanelDescriptor, viewDescriptors: IViewDescriptorCollection): void { + private updateActivity(panel: PanelDescriptor, viewDescriptors: IViewDescriptorCollection): void { + const activity: IActivity = { + id: panel.id, + name: viewDescriptors.getTitle(), + keybindingId: panel.keybindingId + }; + + const { activityAction, pinnedAction } = this.getCompositeActions(panel.id); + activityAction.setActivity(activity); + + if (pinnedAction instanceof PlaceHolderToggleCompositePinnedAction) { + pinnedAction.setActivity(activity); + } + } + + private onDidUpdateViews(panel: PanelDescriptor, viewDescriptors: IViewDescriptorCollection): void { + this.updateActivity(panel, viewDescriptors); + } + + private onDidChangeActiveViews(panel: PanelDescriptor, viewDescriptors: IViewDescriptorCollection, hideIfEmpty?: boolean): void { if (viewDescriptors.activeViewDescriptors.length) { + this.updateActivity(panel, viewDescriptors); this.compositeBar.addComposite(panel); - } else { + } else if (hideIfEmpty) { this.hideComposite(panel.id); } } @@ -540,28 +563,6 @@ export class PanelPart extends CompositePart implements IPanelService { return false; } - protected onTitleAreaUpdate(compositeId: string): void { - super.onTitleAreaUpdate(compositeId); - - const activePanel = this.getActivePanel(); - const panel = this.createComposite(compositeId, activePanel?.getId() === compositeId); - - if (panel) { - const compositeActions = this.compositeActions.get(compositeId); - if (compositeActions) { - compositeActions.activityAction.setActivity({ - id: compositeActions.activityAction.id, - name: panel.getTitle() || compositeActions.activityAction.label - }); - - compositeActions.pinnedAction.setActivity({ - id: compositeActions.activityAction.id, - name: panel.getTitle() || compositeActions.activityAction.label - }); - } - } - } - private getToolbarWidth(): number { const activePanel = this.getActivePanel(); if (!activePanel || !this.toolBar) { From 9275e7ed56ba79d88f43810a8273c77ca67db2c7 Mon Sep 17 00:00:00 2001 From: SteVen Batten Date: Fri, 17 Apr 2020 15:28:47 -0700 Subject: [PATCH 08/44] better panel caching --- src/vs/workbench/browser/parts/panel/panelPart.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/browser/parts/panel/panelPart.ts b/src/vs/workbench/browser/parts/panel/panelPart.ts index 628f11eea3c..0f66d61a73b 100644 --- a/src/vs/workbench/browser/parts/panel/panelPart.ts +++ b/src/vs/workbench/browser/parts/panel/panelPart.ts @@ -610,8 +610,10 @@ export class PanelPart extends CompositePart implements IPanelService { const compositeItems = this.compositeBar.getCompositeBarItems(); for (const compositeItem of compositeItems) { - const activityAction = this.getCompositeActions(compositeItem.id).activityAction; - state.push({ id: compositeItem.id, name: activityAction.label, pinned: compositeItem.pinned, order: compositeItem.order, visible: compositeItem.visible }); + const viewContainer = this.getViewContainer(compositeItem.id)!; + const viewDescriptors = this.viewDescriptorService.getViewDescriptors(viewContainer); + + state.push({ id: compositeItem.id, name: viewDescriptors.getTitle(), pinned: compositeItem.pinned, order: compositeItem.order, visible: compositeItem.visible }); } this.cachedPanelsValue = JSON.stringify(state); From 0c0c15ae32cd7157acd72a65b401fed279753b8e Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Mon, 20 Apr 2020 11:00:30 +0200 Subject: [PATCH 09/44] eventing, naming, fixes, and tests --- .../api/common/extHostDocumentData.ts | 4 + .../workbench/api/common/extHostNotebook.ts | 8 + .../common/extHostNotebookConcatDocument.ts | 170 ++++++++++------- .../api/extHostNotebookConcatDocument.test.ts | 176 ++++++++++++++++++ 4 files changed, 286 insertions(+), 72 deletions(-) create mode 100644 src/vs/workbench/test/browser/api/extHostNotebookConcatDocument.test.ts diff --git a/src/vs/workbench/api/common/extHostDocumentData.ts b/src/vs/workbench/api/common/extHostDocumentData.ts index a295f646dd3..756f1a03f84 100644 --- a/src/vs/workbench/api/common/extHostDocumentData.ts +++ b/src/vs/workbench/api/common/extHostDocumentData.ts @@ -181,6 +181,10 @@ export class ExtHostDocumentData extends MirrorTextModel { throw new Error('Invalid argument'); } + if (this._lines.length === 0) { + return position.with(0, 0); + } + let { line, character } = position; let hasChanged = false; diff --git a/src/vs/workbench/api/common/extHostNotebook.ts b/src/vs/workbench/api/common/extHostNotebook.ts index 177d50c8cf5..8db82c8a67b 100644 --- a/src/vs/workbench/api/common/extHostNotebook.ts +++ b/src/vs/workbench/api/common/extHostNotebook.ts @@ -597,6 +597,10 @@ export class ExtHostNotebookController implements ExtHostNotebookShape, ExtHostN private readonly _documents = new Map(); private readonly _editors = new Map }>(); private readonly _notebookOutputRenderers = new Map(); + + private readonly _onDidChangeNotebookDocument = new Emitter<{ document: ExtHostNotebookDocument, changes: NotebookCellsSplice2[] }>(); + readonly onDidChangeNotebookDocument: Event<{ document: ExtHostNotebookDocument, changes: NotebookCellsSplice2[] }> = this._onDidChangeNotebookDocument.event; + private _outputDisplayOrder: INotebookDisplayOrder | undefined; get outputDisplayOrder(): INotebookDisplayOrder | undefined { @@ -786,6 +790,10 @@ export class ExtHostNotebookController implements ExtHostNotebookShape, ExtHostN if (editor) { editor.editor.document.accpetModelChanged(event); + this._onDidChangeNotebookDocument.fire({ + document: editor.editor.document, + changes: event.changes + }); } } diff --git a/src/vs/workbench/api/common/extHostNotebookConcatDocument.ts b/src/vs/workbench/api/common/extHostNotebookConcatDocument.ts index 260ac62bd3c..86721de3f54 100644 --- a/src/vs/workbench/api/common/extHostNotebookConcatDocument.ts +++ b/src/vs/workbench/api/common/extHostNotebookConcatDocument.ts @@ -6,36 +6,42 @@ import * as types from 'vs/workbench/api/common/extHostTypes'; import * as vscode from 'vscode'; import { Event, Emitter } from 'vs/base/common/event'; -import { ExtHostNotebookDocument } from 'vs/workbench/api/common/extHostNotebook'; +import { ExtHostNotebookDocument, ExtHostNotebookController } from 'vs/workbench/api/common/extHostNotebook'; import { ExtHostDocuments } from 'vs/workbench/api/common/extHostDocuments'; import { PrefixSumComputer } from 'vs/editor/common/viewModel/prefixSumComputer'; import { ExtHostDocumentData } from 'vs/workbench/api/common/extHostDocumentData'; import { Range } from 'vs/workbench/api/common/extHostTypeConverters'; +import { DisposableStore } from 'vs/base/common/lifecycle'; //todo@jrieken ConcatDiagnosticsCollection... -export interface INotebookConcatDocument { - readonly versionId: number; - readonly onDidChange: Event; - dispose(): void; - getText(): string; - locationAt(position: types.Position): types.Location; - positionAt(location: types.Location): types.Position; -} +export class ExtHostNotebookConcatDocument { -export async function createConcatDocument(document: ExtHostNotebookDocument, extHostDocument: ExtHostDocuments) { + private _disposables = new DisposableStore(); - // const cells = new Map(); + private readonly _onDidChange = new Emitter(); + readonly onDidChange: Event = this._onDidChange.event; - const _onDidChange = new Emitter(); - const listener = extHostDocument.onDidChangeDocument(e => { - const key = e.document.uri.toString(); - const cell = document.cells.find(candidate => candidate.uri.toString() === key); - if (cell) { + private _delegate!: ExtHostDocumentData; + private _cellStarts!: PrefixSumComputer; + private _versionId = 0; + constructor( + private readonly _notebook: ExtHostNotebookDocument, + extHostNotebooks: ExtHostNotebookController, + extHostDocuments: ExtHostDocuments, + ) { + this._init(); + + extHostDocuments.onDidChangeDocument(e => { + const cell = this._notebook.cells.find(candidate => candidate.uri.toString() === e.document.uri.toString()); + if (!cell) { + return; + } // todo@jrieken reuse raw event! - concatDocument.onEvents({ - versionId: concatDocument.version + 1, + this._versionId += 1; + this._delegate.onEvents({ + versionId: this._versionId, eol: '\n', changes: e.contentChanges.map(change => { return { @@ -46,62 +52,82 @@ export async function createConcatDocument(document: ExtHostNotebookDocument, ex }; }) }); + this._onDidChange.fire(this); - _onDidChange.fire(); - } - }); + }, undefined, this._disposables); - function dispose(): void { - listener.dispose(); - concatDocument.dispose(); - } - - const lines: string[] = []; - const values = new Uint32Array(document.cells.length); - for (let i = 0; i < document.cells.length; i++) { - - const cell = document.cells[i]; - - // update prefix sum - values[i] = cell.document.getText().length + 1; // 1 is newline - - //todo@jrieken reuse lines! - for (let line = 0; line < cell.document.lineCount; line++) { - lines.push(cell.document.lineAt(line).text); - } - } - - const cellStarts = new PrefixSumComputer(values); - const concatDocument = new ExtHostDocumentData( - null!, - document.uri.with({ scheme: 'vscode-concatdoc' }), - lines, '\n', - document.languages[0], - 0, false - ); - - return { - get versionId() { return concatDocument.version; }, - onDidChange: _onDidChange.event, - dispose, - getText() { return concatDocument.getText(); }, - locationAt(position: types.Position): types.Location { - const offset = concatDocument.document.offsetAt(position); - const index = cellStarts.getIndexOf(offset); - const cell = document.cells[index.index]; - const cellPosi = cell.document.positionAt(index.remainder); - return new types.Location(cell.uri, cellPosi); - }, - positionAt(location: types.Location): vscode.Position | undefined { - const idx = document.cells.findIndex(candidate => candidate.uri.toString() === location.uri.toString()); - if (idx > 0) { - return undefined; + extHostNotebooks.onDidChangeNotebookDocument(e => { + if (e.document !== this._notebook) { + return; + } + //todo@jrieken update instead of flushing... + this._versionId += 1; + this._init(); + this._onDidChange.fire(this); + }, undefined, this._disposables); + } + + dispose(): void { + this._disposables.dispose(); + this._delegate.dispose(); + } + + private _init() { + const lines: string[] = []; + const values = new Uint32Array(this._notebook.cells.length); + for (let i = 0; i < this._notebook.cells.length; i++) { + + const cell = this._notebook.cells[i]; + + // update prefix sum + values[i] = cell.document.getText().length + 1; // 1 is newline + + //todo@jrieken reuse lines! + for (let line = 0; line < cell.document.lineCount; line++) { + lines.push(cell.document.lineAt(line).text); } - const docOffset = document.cells[idx].document.offsetAt(location.range.start); - const cellOffset = cellStarts.getAccumulatedValue(idx); - return concatDocument.document.positionAt(docOffset + cellOffset); } - }; + + this._cellStarts = new PrefixSumComputer(values); + this._delegate = new ExtHostDocumentData( + null!, + this._notebook.uri.with({ scheme: 'vscode-concatdoc' }), + lines, '\n', + this._notebook.languages[0], + 0, false + ); + } + + get versionId() { + return this._versionId; + } + + getText() { + return this._delegate.getText(); + } + + locationAt(position: vscode.Position): vscode.Location { + const offset = this._delegate.document.offsetAt(position); + const index = this._cellStarts.getIndexOf(offset); + const cell = this._notebook.cells[index.index]; + if (!cell) { + // do better? + // return undefined; + return new types.Location(this._notebook.uri, new types.Position(0, 0)); + } + const cellPos = cell.document.positionAt(index.remainder); + return new types.Location(cell.uri, cellPos); + } + + positionAt(location: vscode.Location): vscode.Position { + const idx = this._notebook.cells.findIndex(candidate => candidate.uri.toString() === location.uri.toString()); + if (idx < 0) { + // do better? + // return undefined; + return new types.Position(0, 0); + } + const docOffset = this._notebook.cells[idx].document.offsetAt(location.range.start); + const cellOffset = this._cellStarts.getAccumulatedValue(idx - 1); + return this._delegate.document.positionAt(docOffset + cellOffset); + } } - - diff --git a/src/vs/workbench/test/browser/api/extHostNotebookConcatDocument.test.ts b/src/vs/workbench/test/browser/api/extHostNotebookConcatDocument.test.ts new file mode 100644 index 00000000000..e3466539767 --- /dev/null +++ b/src/vs/workbench/test/browser/api/extHostNotebookConcatDocument.test.ts @@ -0,0 +1,176 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as assert from 'assert'; +import { TestRPCProtocol } from 'vs/workbench/test/browser/api/testRPCProtocol'; +import { ExtHostDocuments } from 'vs/workbench/api/common/extHostDocuments'; +import { ExtHostDocumentsAndEditors } from 'vs/workbench/api/common/extHostDocumentsAndEditors'; +import { NullLogService } from 'vs/platform/log/common/log'; +import { ExtHostNotebookConcatDocument } from 'vs/workbench/api/common/extHostNotebookConcatDocument'; +import { ExtHostNotebookDocument, ExtHostNotebookController } from 'vs/workbench/api/common/extHostNotebook'; +import { URI } from 'vs/base/common/uri'; +import { CellKind, CellUri } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { Position, Location } from 'vs/workbench/api/common/extHostTypes'; +import { ExtHostCommands } from 'vs/workbench/api/common/extHostCommands'; +import { nullExtensionDescription } from 'vs/workbench/services/extensions/common/extensions'; +import { NotebookProvider } from 'vscode'; +import { mock } from 'vs/workbench/test/common/workbenchTestServices'; +import { MainContext, MainThreadCommandsShape, MainThreadNotebookShape } from 'vs/workbench/api/common/extHost.protocol'; +import { DisposableStore } from 'vs/base/common/lifecycle'; + + +suite('NotebookConcatDocument', function () { + + let rpcProtocol: TestRPCProtocol; + let notebook: ExtHostNotebookDocument; + let extHostDocuments: ExtHostDocuments; + let extHostNotebooks: ExtHostNotebookController; + const notebookUri = URI.parse('test:///notebook.file'); + const disposables = new DisposableStore(); + + setup(async function () { + disposables.clear(); + + rpcProtocol = new TestRPCProtocol(); + rpcProtocol.set(MainContext.MainThreadCommands, new class extends mock() { + $registerCommand() { } + }); + rpcProtocol.set(MainContext.MainThreadNotebook, new class extends mock() { + async $registerNotebookProvider() { } + async $unregisterNotebookProvider() { } + async $createNotebookDocument() { } + }); + const extHostDocumentsAndEditors = new ExtHostDocumentsAndEditors(rpcProtocol, new NullLogService()); + extHostDocuments = new ExtHostDocuments(rpcProtocol, extHostDocumentsAndEditors); + extHostNotebooks = new ExtHostNotebookController(rpcProtocol, new ExtHostCommands(rpcProtocol, new NullLogService()), extHostDocumentsAndEditors); + let reg = extHostNotebooks.registerNotebookProvider(nullExtensionDescription, 'test', new class extends mock() { + async resolveNotebook() { } + }); + await extHostNotebooks.$resolveNotebook('test', notebookUri); + await extHostNotebooks.$updateActiveEditor('test', notebookUri); + + notebook = extHostNotebooks.activeNotebookDocument!; + + disposables.add(reg); + disposables.add(notebook); + disposables.add(extHostDocuments); + }); + + test('empty', function () { + let doc = new ExtHostNotebookConcatDocument(notebook, extHostNotebooks, extHostDocuments); + assert.equal(doc.getText(), ''); + assert.equal(doc.versionId, 0); + + // assert.equal(doc.locationAt(new Position(0, 0)), undefined); + // assert.equal(doc.positionAt(SOME_FAKE_LOCATION?), undefined); + }); + + function assertLocation(doc: ExtHostNotebookConcatDocument, pos: Position, expected: Location, identCheck = true) { + const actual = doc.locationAt(pos); + assert.equal(actual.uri.toString(), expected.uri.toString()); + assert.equal(actual.range.isEqual(expected.range), true); + + if (identCheck) { + // reverse + const actualPosition = doc.positionAt(actual); + assert.equal(actualPosition.isEqual(pos), true); + } + } + + test('location, position mapping', function () { + + extHostNotebooks.$acceptModelChanged(notebookUri, { + versionId: notebook.versionId + 1, + changes: [[0, 0, [{ + handle: 1, + uri: CellUri.generate(notebook.uri, 1), + source: ['Hello', 'World', 'Hello World!'], + language: 'test', + cellKind: CellKind.Code, + outputs: [], + }, { + handle: 2, + uri: CellUri.generate(notebook.uri, 2), + source: ['Hallo', 'Welt', 'Hallo Welt!'], + language: 'test', + cellKind: CellKind.Code, + outputs: [], + }]]] + }); + + + assert.equal(notebook.cells.length, 2); + + let doc = new ExtHostNotebookConcatDocument(notebook, extHostNotebooks, extHostDocuments); + assert.equal(doc.getText(), ['Hello', 'World', 'Hello World!', 'Hallo', 'Welt', 'Hallo Welt!'].join('\n')); + + assertLocation(doc, new Position(0, 0), new Location(notebook.cells[0].uri, new Position(0, 0))); + assertLocation(doc, new Position(4, 0), new Location(notebook.cells[1].uri, new Position(1, 0))); + assertLocation(doc, new Position(4, 3), new Location(notebook.cells[1].uri, new Position(1, 3))); + assertLocation(doc, new Position(5, 11), new Location(notebook.cells[1].uri, new Position(2, 11))); + assertLocation(doc, new Position(5, 12), new Location(notebook.cells[1].uri, new Position(2, 11)), false); // don't check identity because position will be clamped + }); + + + test('location, position mapping, changes', function () { + + let doc = new ExtHostNotebookConcatDocument(notebook, extHostNotebooks, extHostDocuments); + + // UPDATE 1 + extHostNotebooks.$acceptModelChanged(notebookUri, { + versionId: notebook.versionId + 1, + changes: [[0, 0, [{ + handle: 1, + uri: CellUri.generate(notebook.uri, 1), + source: ['Hello', 'World', 'Hello World!'], + language: 'test', + cellKind: CellKind.Code, + outputs: [], + }]]] + }); + assert.equal(notebook.cells.length, 1); + assert.equal(doc.versionId, 1); + assert.equal(doc.getText(), ['Hello', 'World', 'Hello World!'].join('\n')); + assertLocation(doc, new Position(0, 0), new Location(notebook.cells[0].uri, new Position(0, 0))); + assertLocation(doc, new Position(2, 2), new Location(notebook.cells[0].uri, new Position(2, 2))); + assertLocation(doc, new Position(4, 0), new Location(notebook.cells[0].uri, new Position(2, 12)), false); // clamped + + + // UPDATE 2 + extHostNotebooks.$acceptModelChanged(notebookUri, { + versionId: notebook.versionId + 1, + changes: [[1, 0, [{ + handle: 2, + uri: CellUri.generate(notebook.uri, 2), + source: ['Hallo', 'Welt', 'Hallo Welt!'], + language: 'test', + cellKind: CellKind.Code, + outputs: [], + }]]] + }); + + assert.equal(notebook.cells.length, 2); + assert.equal(doc.versionId, 2); + assert.equal(doc.getText(), ['Hello', 'World', 'Hello World!', 'Hallo', 'Welt', 'Hallo Welt!'].join('\n')); + assertLocation(doc, new Position(0, 0), new Location(notebook.cells[0].uri, new Position(0, 0))); + assertLocation(doc, new Position(4, 0), new Location(notebook.cells[1].uri, new Position(1, 0))); + assertLocation(doc, new Position(4, 3), new Location(notebook.cells[1].uri, new Position(1, 3))); + assertLocation(doc, new Position(5, 11), new Location(notebook.cells[1].uri, new Position(2, 11))); + assertLocation(doc, new Position(5, 12), new Location(notebook.cells[1].uri, new Position(2, 11)), false); // don't check identity because position will be clamped + + // UPDATE 3 (remove cell #2 again) + extHostNotebooks.$acceptModelChanged(notebookUri, { + versionId: notebook.versionId + 1, + changes: [[1, 1, []]] + }); + assert.equal(notebook.cells.length, 1); + assert.equal(doc.versionId, 3); + assert.equal(doc.getText(), ['Hello', 'World', 'Hello World!'].join('\n')); + assertLocation(doc, new Position(0, 0), new Location(notebook.cells[0].uri, new Position(0, 0))); + assertLocation(doc, new Position(2, 2), new Location(notebook.cells[0].uri, new Position(2, 2))); + assertLocation(doc, new Position(4, 0), new Location(notebook.cells[0].uri, new Position(2, 12)), false); // clamped + + }); +}); From 2b5426b4477ecdb602c4bb23fde7bfb2fab254fb Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Mon, 20 Apr 2020 12:57:13 +0200 Subject: [PATCH 10/44] update prefix-sum when docs change, add offset/position math, tests --- .../common/extHostNotebookConcatDocument.ts | 22 ++++-- .../api/extHostNotebookConcatDocument.test.ts | 79 +++++++++++++++++-- 2 files changed, 91 insertions(+), 10 deletions(-) diff --git a/src/vs/workbench/api/common/extHostNotebookConcatDocument.ts b/src/vs/workbench/api/common/extHostNotebookConcatDocument.ts index 86721de3f54..85c59d03e8a 100644 --- a/src/vs/workbench/api/common/extHostNotebookConcatDocument.ts +++ b/src/vs/workbench/api/common/extHostNotebookConcatDocument.ts @@ -34,8 +34,8 @@ export class ExtHostNotebookConcatDocument { this._init(); extHostDocuments.onDidChangeDocument(e => { - const cell = this._notebook.cells.find(candidate => candidate.uri.toString() === e.document.uri.toString()); - if (!cell) { + const cellIdx = this._notebook.cells.findIndex(candidate => candidate.uri.toString() === e.document.uri.toString()); + if (cellIdx < 0) { return; } // todo@jrieken reuse raw event! @@ -52,6 +52,8 @@ export class ExtHostNotebookConcatDocument { }; }) }); + this._cellStarts.changeValue(cellIdx, e.document.getText().length + 1); + this._onDidChange.fire(this); }, undefined, this._disposables); @@ -106,6 +108,7 @@ export class ExtHostNotebookConcatDocument { return this._delegate.getText(); } + locationAt(position: vscode.Position): vscode.Location { const offset = this._delegate.document.offsetAt(position); const index = this._cellStarts.getIndexOf(offset); @@ -119,15 +122,24 @@ export class ExtHostNotebookConcatDocument { return new types.Location(cell.uri, cellPos); } - positionAt(location: vscode.Location): vscode.Position { - const idx = this._notebook.cells.findIndex(candidate => candidate.uri.toString() === location.uri.toString()); + positionAt(offset: number): vscode.Position; + positionAt(location: vscode.Location): vscode.Position; + positionAt(offsetOrLocation: number | vscode.Location): vscode.Position { + if (typeof offsetOrLocation === 'number') { + return this._delegate.document.positionAt(offsetOrLocation); + } + const idx = this._notebook.cells.findIndex(candidate => candidate.uri.toString() === offsetOrLocation.uri.toString()); if (idx < 0) { // do better? // return undefined; return new types.Position(0, 0); } - const docOffset = this._notebook.cells[idx].document.offsetAt(location.range.start); + const docOffset = this._notebook.cells[idx].document.offsetAt(offsetOrLocation.range.start); const cellOffset = this._cellStarts.getAccumulatedValue(idx - 1); return this._delegate.document.positionAt(docOffset + cellOffset); } + + offsetAt(position: vscode.Position): number { + return this._delegate.document.offsetAt(position); + } } diff --git a/src/vs/workbench/test/browser/api/extHostNotebookConcatDocument.test.ts b/src/vs/workbench/test/browser/api/extHostNotebookConcatDocument.test.ts index e3466539767..bdf551e75da 100644 --- a/src/vs/workbench/test/browser/api/extHostNotebookConcatDocument.test.ts +++ b/src/vs/workbench/test/browser/api/extHostNotebookConcatDocument.test.ts @@ -25,6 +25,7 @@ suite('NotebookConcatDocument', function () { let rpcProtocol: TestRPCProtocol; let notebook: ExtHostNotebookDocument; + let extHostDocumentsAndEditors: ExtHostDocumentsAndEditors; let extHostDocuments: ExtHostDocuments; let extHostNotebooks: ExtHostNotebookController; const notebookUri = URI.parse('test:///notebook.file'); @@ -42,7 +43,7 @@ suite('NotebookConcatDocument', function () { async $unregisterNotebookProvider() { } async $createNotebookDocument() { } }); - const extHostDocumentsAndEditors = new ExtHostDocumentsAndEditors(rpcProtocol, new NullLogService()); + extHostDocumentsAndEditors = new ExtHostDocumentsAndEditors(rpcProtocol, new NullLogService()); extHostDocuments = new ExtHostDocuments(rpcProtocol, extHostDocumentsAndEditors); extHostNotebooks = new ExtHostNotebookController(rpcProtocol, new ExtHostCommands(rpcProtocol, new NullLogService()), extHostDocumentsAndEditors); let reg = extHostNotebooks.registerNotebookProvider(nullExtensionDescription, 'test', new class extends mock() { @@ -67,13 +68,17 @@ suite('NotebookConcatDocument', function () { // assert.equal(doc.positionAt(SOME_FAKE_LOCATION?), undefined); }); - function assertLocation(doc: ExtHostNotebookConcatDocument, pos: Position, expected: Location, identCheck = true) { + function assertLocation(doc: ExtHostNotebookConcatDocument, pos: Position, expected: Location, reverse = true) { const actual = doc.locationAt(pos); assert.equal(actual.uri.toString(), expected.uri.toString()); assert.equal(actual.range.isEqual(expected.range), true); - if (identCheck) { - // reverse + if (reverse) { + // reverse - offset + const offset = doc.offsetAt(pos); + assert.equal(doc.positionAt(offset).isEqual(pos), true); + + // reverse - pos const actualPosition = doc.positionAt(actual); assert.equal(actualPosition.isEqual(pos), true); } @@ -114,7 +119,7 @@ suite('NotebookConcatDocument', function () { }); - test('location, position mapping, changes', function () { + test('location, position mapping, cell changes', function () { let doc = new ExtHostNotebookConcatDocument(notebook, extHostNotebooks, extHostDocuments); @@ -171,6 +176,70 @@ suite('NotebookConcatDocument', function () { assertLocation(doc, new Position(0, 0), new Location(notebook.cells[0].uri, new Position(0, 0))); assertLocation(doc, new Position(2, 2), new Location(notebook.cells[0].uri, new Position(2, 2))); assertLocation(doc, new Position(4, 0), new Location(notebook.cells[0].uri, new Position(2, 12)), false); // clamped + }); + + test('location, position mapping, cell-document changes', function () { + + let doc = new ExtHostNotebookConcatDocument(notebook, extHostNotebooks, extHostDocuments); + + // UPDATE 1 + extHostNotebooks.$acceptModelChanged(notebookUri, { + versionId: notebook.versionId + 1, + changes: [[0, 0, [{ + handle: 1, + uri: CellUri.generate(notebook.uri, 1), + source: ['Hello', 'World', 'Hello World!'], + language: 'test', + cellKind: CellKind.Code, + outputs: [], + }, { + handle: 2, + uri: CellUri.generate(notebook.uri, 2), + source: ['Hallo', 'Welt', 'Hallo Welt!'], + language: 'test', + cellKind: CellKind.Code, + outputs: [], + }]]] + }); + assert.equal(notebook.cells.length, 2); + assert.equal(doc.versionId, 1); + + assert.equal(doc.getText(), ['Hello', 'World', 'Hello World!', 'Hallo', 'Welt', 'Hallo Welt!'].join('\n')); + assertLocation(doc, new Position(0, 0), new Location(notebook.cells[0].uri, new Position(0, 0))); + assertLocation(doc, new Position(2, 2), new Location(notebook.cells[0].uri, new Position(2, 2))); + assertLocation(doc, new Position(2, 12), new Location(notebook.cells[0].uri, new Position(2, 12))); + assertLocation(doc, new Position(4, 0), new Location(notebook.cells[1].uri, new Position(1, 0))); + assertLocation(doc, new Position(4, 3), new Location(notebook.cells[1].uri, new Position(1, 3))); + + // offset math + let cell1End = doc.offsetAt(new Position(2, 12)); + assert.equal(doc.positionAt(cell1End).isEqual(new Position(2, 12)), true); + + extHostDocumentsAndEditors.$acceptDocumentsAndEditorsDelta({ + addedDocuments: [{ + uri: notebook.cells[0].uri, + versionId: 1, + lines: ['Hello', 'World', 'Hello World!'], + EOL: '\n', + modeId: '', + isDirty: false + }] + }); + + extHostDocuments.$acceptModelChanged(notebook.cells[0].uri, { + versionId: 0, + eol: '\n', + changes: [{ + range: { startLineNumber: 3, startColumn: 1, endLineNumber: 3, endColumn: 6 }, + rangeLength: 6, + rangeOffset: 12, + text: 'Hi' + }] + }, false); + assert.equal(doc.getText(), ['Hello', 'World', 'Hi World!', 'Hallo', 'Welt', 'Hallo Welt!'].join('\n')); + assertLocation(doc, new Position(2, 12), new Location(notebook.cells[0].uri, new Position(2, 9)), false); + + assert.equal(doc.positionAt(cell1End).isEqual(new Position(3, 2)), true); }); }); From 88eb90504ab95c03af1d1045bd344f5585eaa884 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Mon, 20 Apr 2020 14:31:00 +0200 Subject: [PATCH 11/44] support location at range --- .../common/extHostNotebookConcatDocument.ts | 34 +++++++++++++++---- 1 file changed, 27 insertions(+), 7 deletions(-) diff --git a/src/vs/workbench/api/common/extHostNotebookConcatDocument.ts b/src/vs/workbench/api/common/extHostNotebookConcatDocument.ts index 85c59d03e8a..2ed2c2e3854 100644 --- a/src/vs/workbench/api/common/extHostNotebookConcatDocument.ts +++ b/src/vs/workbench/api/common/extHostNotebookConcatDocument.ts @@ -13,6 +13,7 @@ import { ExtHostDocumentData } from 'vs/workbench/api/common/extHostDocumentData import { Range } from 'vs/workbench/api/common/extHostTypeConverters'; import { DisposableStore } from 'vs/base/common/lifecycle'; + //todo@jrieken ConcatDiagnosticsCollection... export class ExtHostNotebookConcatDocument { @@ -109,17 +110,36 @@ export class ExtHostNotebookConcatDocument { } - locationAt(position: vscode.Position): vscode.Location { - const offset = this._delegate.document.offsetAt(position); - const index = this._cellStarts.getIndexOf(offset); - const cell = this._notebook.cells[index.index]; - if (!cell) { + locationAt(positionOrRange: vscode.Position | vscode.Range): vscode.Location { + + if (!types.Range.isRange(positionOrRange)) { + positionOrRange = new types.Range(positionOrRange, positionOrRange); + } + + const start = this._delegate.document.offsetAt(positionOrRange.start); + const startIndex = this._cellStarts.getIndexOf(start); + const startCell = this._notebook.cells[startIndex.index]; + if (!startCell) { // do better? // return undefined; return new types.Location(this._notebook.uri, new types.Position(0, 0)); } - const cellPos = cell.document.positionAt(index.remainder); - return new types.Location(cell.uri, cellPos); + + let endCell = startCell; + let endIndex = startIndex; + if (!positionOrRange.isEmpty) { + const end = this._delegate.document.offsetAt(positionOrRange.end); + endIndex = this._cellStarts.getIndexOf(end); + endCell = this._notebook.cells[endIndex.index]; + } + + const startPos = startCell.document.positionAt(startIndex.remainder); + let endPos = startPos; + if (endCell && endCell.handle === startCell.handle) { + endPos = endCell.document.positionAt(endIndex.remainder); + } + + return new types.Location(startCell.uri, new types.Range(startPos.line, startPos.character, endPos.line, endPos.character)); } positionAt(offset: number): vscode.Position; From 6e9740cba4f7840157d75dfd4a3fa101338c79ed Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Mon, 20 Apr 2020 15:41:22 +0200 Subject: [PATCH 12/44] remove extra overloads --- src/vs/workbench/api/common/extHostNotebookConcatDocument.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/vs/workbench/api/common/extHostNotebookConcatDocument.ts b/src/vs/workbench/api/common/extHostNotebookConcatDocument.ts index 2ed2c2e3854..d02ecb4de60 100644 --- a/src/vs/workbench/api/common/extHostNotebookConcatDocument.ts +++ b/src/vs/workbench/api/common/extHostNotebookConcatDocument.ts @@ -109,7 +109,6 @@ export class ExtHostNotebookConcatDocument { return this._delegate.getText(); } - locationAt(positionOrRange: vscode.Position | vscode.Range): vscode.Location { if (!types.Range.isRange(positionOrRange)) { @@ -142,8 +141,6 @@ export class ExtHostNotebookConcatDocument { return new types.Location(startCell.uri, new types.Range(startPos.line, startPos.character, endPos.line, endPos.character)); } - positionAt(offset: number): vscode.Position; - positionAt(location: vscode.Location): vscode.Position; positionAt(offsetOrLocation: number | vscode.Location): vscode.Position { if (typeof offsetOrLocation === 'number') { return this._delegate.document.positionAt(offsetOrLocation); From 13c9d8f21a9dc1488f24f8e7c0cf9367cf9741d8 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Mon, 20 Apr 2020 16:24:16 +0200 Subject: [PATCH 13/44] refactor test a little --- .../api/extHostNotebookConcatDocument.test.ts | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/src/vs/workbench/test/browser/api/extHostNotebookConcatDocument.test.ts b/src/vs/workbench/test/browser/api/extHostNotebookConcatDocument.test.ts index bdf551e75da..835b57fc75a 100644 --- a/src/vs/workbench/test/browser/api/extHostNotebookConcatDocument.test.ts +++ b/src/vs/workbench/test/browser/api/extHostNotebookConcatDocument.test.ts @@ -84,6 +84,11 @@ suite('NotebookConcatDocument', function () { } } + function assertLines(doc: ExtHostNotebookConcatDocument, ...lines: string[]) { + let actual = doc.getText().split(/\r\n|\n|\r/); + assert.deepStrictEqual(actual, lines); + } + test('location, position mapping', function () { extHostNotebooks.$acceptModelChanged(notebookUri, { @@ -109,7 +114,7 @@ suite('NotebookConcatDocument', function () { assert.equal(notebook.cells.length, 2); let doc = new ExtHostNotebookConcatDocument(notebook, extHostNotebooks, extHostDocuments); - assert.equal(doc.getText(), ['Hello', 'World', 'Hello World!', 'Hallo', 'Welt', 'Hallo Welt!'].join('\n')); + assertLines(doc, 'Hello', 'World', 'Hello World!', 'Hallo', 'Welt', 'Hallo Welt!'); assertLocation(doc, new Position(0, 0), new Location(notebook.cells[0].uri, new Position(0, 0))); assertLocation(doc, new Position(4, 0), new Location(notebook.cells[1].uri, new Position(1, 0))); @@ -137,7 +142,8 @@ suite('NotebookConcatDocument', function () { }); assert.equal(notebook.cells.length, 1); assert.equal(doc.versionId, 1); - assert.equal(doc.getText(), ['Hello', 'World', 'Hello World!'].join('\n')); + assertLines(doc, 'Hello', 'World', 'Hello World!'); + assertLocation(doc, new Position(0, 0), new Location(notebook.cells[0].uri, new Position(0, 0))); assertLocation(doc, new Position(2, 2), new Location(notebook.cells[0].uri, new Position(2, 2))); assertLocation(doc, new Position(4, 0), new Location(notebook.cells[0].uri, new Position(2, 12)), false); // clamped @@ -158,7 +164,7 @@ suite('NotebookConcatDocument', function () { assert.equal(notebook.cells.length, 2); assert.equal(doc.versionId, 2); - assert.equal(doc.getText(), ['Hello', 'World', 'Hello World!', 'Hallo', 'Welt', 'Hallo Welt!'].join('\n')); + assertLines(doc, 'Hello', 'World', 'Hello World!', 'Hallo', 'Welt', 'Hallo Welt!'); assertLocation(doc, new Position(0, 0), new Location(notebook.cells[0].uri, new Position(0, 0))); assertLocation(doc, new Position(4, 0), new Location(notebook.cells[1].uri, new Position(1, 0))); assertLocation(doc, new Position(4, 3), new Location(notebook.cells[1].uri, new Position(1, 3))); @@ -172,7 +178,7 @@ suite('NotebookConcatDocument', function () { }); assert.equal(notebook.cells.length, 1); assert.equal(doc.versionId, 3); - assert.equal(doc.getText(), ['Hello', 'World', 'Hello World!'].join('\n')); + assertLines(doc, 'Hello', 'World', 'Hello World!'); assertLocation(doc, new Position(0, 0), new Location(notebook.cells[0].uri, new Position(0, 0))); assertLocation(doc, new Position(2, 2), new Location(notebook.cells[0].uri, new Position(2, 2))); assertLocation(doc, new Position(4, 0), new Location(notebook.cells[0].uri, new Position(2, 12)), false); // clamped @@ -204,7 +210,7 @@ suite('NotebookConcatDocument', function () { assert.equal(notebook.cells.length, 2); assert.equal(doc.versionId, 1); - assert.equal(doc.getText(), ['Hello', 'World', 'Hello World!', 'Hallo', 'Welt', 'Hallo Welt!'].join('\n')); + assertLines(doc, 'Hello', 'World', 'Hello World!', 'Hallo', 'Welt', 'Hallo Welt!'); assertLocation(doc, new Position(0, 0), new Location(notebook.cells[0].uri, new Position(0, 0))); assertLocation(doc, new Position(2, 2), new Location(notebook.cells[0].uri, new Position(2, 2))); assertLocation(doc, new Position(2, 12), new Location(notebook.cells[0].uri, new Position(2, 12))); @@ -236,7 +242,7 @@ suite('NotebookConcatDocument', function () { text: 'Hi' }] }, false); - assert.equal(doc.getText(), ['Hello', 'World', 'Hi World!', 'Hallo', 'Welt', 'Hallo Welt!'].join('\n')); + assertLines(doc, 'Hello', 'World', 'Hi World!', 'Hallo', 'Welt', 'Hallo Welt!'); assertLocation(doc, new Position(2, 12), new Location(notebook.cells[0].uri, new Position(2, 9)), false); assert.equal(doc.positionAt(cell1End).isEqual(new Position(3, 2)), true); From fc20cdf5e7f6216f85501e7509733c1781bd39d3 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Mon, 20 Apr 2020 18:11:20 +0200 Subject: [PATCH 14/44] add NotebookConcatTextDocument to API, support selector-based creation --- src/vs/vscode.proposed.d.ts | 11 +++ .../common/extHostNotebookConcatDocument.ts | 62 ++++++++------ .../api/extHostNotebookConcatDocument.test.ts | 85 ++++++++++++++++--- 3 files changed, 118 insertions(+), 40 deletions(-) diff --git a/src/vs/vscode.proposed.d.ts b/src/vs/vscode.proposed.d.ts index 11a7edb1316..25b6d013b35 100644 --- a/src/vs/vscode.proposed.d.ts +++ b/src/vs/vscode.proposed.d.ts @@ -1600,6 +1600,17 @@ declare module 'vscode' { languages: string[]; displayOrder?: GlobPattern[]; metadata: NotebookDocumentMetadata; + + } + + export interface NotebookConcatTextDocument { + version: number; + getText(): string; + getText(range: Range): string; + offsetAt(position: Position): number; + positionAt(offset: number): Position; + locationAt(positionOrRange: Position | Range): Location; + positionAt(location: Location): Position; } export interface NotebookEditorCellEdit { diff --git a/src/vs/workbench/api/common/extHostNotebookConcatDocument.ts b/src/vs/workbench/api/common/extHostNotebookConcatDocument.ts index d02ecb4de60..1d20f7c05e6 100644 --- a/src/vs/workbench/api/common/extHostNotebookConcatDocument.ts +++ b/src/vs/workbench/api/common/extHostNotebookConcatDocument.ts @@ -6,36 +6,42 @@ import * as types from 'vs/workbench/api/common/extHostTypes'; import * as vscode from 'vscode'; import { Event, Emitter } from 'vs/base/common/event'; -import { ExtHostNotebookDocument, ExtHostNotebookController } from 'vs/workbench/api/common/extHostNotebook'; +import { ExtHostNotebookDocument, ExtHostNotebookController, ExtHostCell } from 'vs/workbench/api/common/extHostNotebook'; import { ExtHostDocuments } from 'vs/workbench/api/common/extHostDocuments'; import { PrefixSumComputer } from 'vs/editor/common/viewModel/prefixSumComputer'; import { ExtHostDocumentData } from 'vs/workbench/api/common/extHostDocumentData'; -import { Range } from 'vs/workbench/api/common/extHostTypeConverters'; +import { Range, LanguageSelector } from 'vs/workbench/api/common/extHostTypeConverters'; import { DisposableStore } from 'vs/base/common/lifecycle'; +import { score } from 'vs/editor/common/modes/languageSelector'; +import { CellKind } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { NotImplementedProxy } from 'vs/base/common/types'; +import { MainThreadDocumentsShape } from 'vs/workbench/api/common/extHost.protocol'; //todo@jrieken ConcatDiagnosticsCollection... -export class ExtHostNotebookConcatDocument { +export class ExtHostNotebookConcatDocument implements vscode.NotebookConcatTextDocument { private _disposables = new DisposableStore(); private readonly _onDidChange = new Emitter(); readonly onDidChange: Event = this._onDidChange.event; - private _delegate!: ExtHostDocumentData; - private _cellStarts!: PrefixSumComputer; private _versionId = 0; + private _delegate!: ExtHostDocumentData; + private _selectedCells!: ExtHostCell[]; + private _cellStarts!: PrefixSumComputer; constructor( - private readonly _notebook: ExtHostNotebookDocument, extHostNotebooks: ExtHostNotebookController, extHostDocuments: ExtHostDocuments, + private readonly _notebook: ExtHostNotebookDocument, + private readonly _selector: vscode.DocumentSelector | undefined, ) { this._init(); extHostDocuments.onDidChangeDocument(e => { - const cellIdx = this._notebook.cells.findIndex(candidate => candidate.uri.toString() === e.document.uri.toString()); + const cellIdx = this._selectedCells.findIndex(candidate => candidate.uri.toString() === e.document.uri.toString()); if (cellIdx < 0) { return; } @@ -76,37 +82,42 @@ export class ExtHostNotebookConcatDocument { } private _init() { + + // only allow Code-cells and those that are selected by the language selector + this._selectedCells = this._notebook.cells + .filter(cell => cell.cellKind === CellKind.Code && (!this._selector || score(LanguageSelector.from(this._selector), cell.uri, cell.language, true))); + const lines: string[] = []; - const values = new Uint32Array(this._notebook.cells.length); - for (let i = 0; i < this._notebook.cells.length; i++) { - - const cell = this._notebook.cells[i]; + const cellLengths = new Uint32Array(this._selectedCells.length); + for (let i = 0; i < this._selectedCells.length; i++) { + const cell = this._selectedCells[i]; // update prefix sum - values[i] = cell.document.getText().length + 1; // 1 is newline - + cellLengths[i] = cell.document.getText().length + 1; // 1 is newline //todo@jrieken reuse lines! for (let line = 0; line < cell.document.lineCount; line++) { lines.push(cell.document.lineAt(line).text); } } - this._cellStarts = new PrefixSumComputer(values); + this._cellStarts = new PrefixSumComputer(cellLengths); this._delegate = new ExtHostDocumentData( - null!, + new class extends NotImplementedProxy('MainThreadDocumentsShape') { }, this._notebook.uri.with({ scheme: 'vscode-concatdoc' }), - lines, '\n', + lines, + '\n', this._notebook.languages[0], - 0, false + this._versionId, + false ); } - get versionId() { + get version() { return this._versionId; } - getText() { - return this._delegate.getText(); + getText(range?: vscode.Range) { + return this._delegate.document.getText(range); } locationAt(positionOrRange: vscode.Position | vscode.Range): vscode.Location { @@ -117,10 +128,9 @@ export class ExtHostNotebookConcatDocument { const start = this._delegate.document.offsetAt(positionOrRange.start); const startIndex = this._cellStarts.getIndexOf(start); - const startCell = this._notebook.cells[startIndex.index]; + const startCell = this._selectedCells[startIndex.index]; if (!startCell) { - // do better? - // return undefined; + // do better? throw an error insead? return undefined? return new types.Location(this._notebook.uri, new types.Position(0, 0)); } @@ -129,7 +139,7 @@ export class ExtHostNotebookConcatDocument { if (!positionOrRange.isEmpty) { const end = this._delegate.document.offsetAt(positionOrRange.end); endIndex = this._cellStarts.getIndexOf(end); - endCell = this._notebook.cells[endIndex.index]; + endCell = this._selectedCells[endIndex.index]; } const startPos = startCell.document.positionAt(startIndex.remainder); @@ -145,13 +155,13 @@ export class ExtHostNotebookConcatDocument { if (typeof offsetOrLocation === 'number') { return this._delegate.document.positionAt(offsetOrLocation); } - const idx = this._notebook.cells.findIndex(candidate => candidate.uri.toString() === offsetOrLocation.uri.toString()); + const idx = this._selectedCells.findIndex(candidate => candidate.uri.toString() === offsetOrLocation.uri.toString()); if (idx < 0) { // do better? // return undefined; return new types.Position(0, 0); } - const docOffset = this._notebook.cells[idx].document.offsetAt(offsetOrLocation.range.start); + const docOffset = this._selectedCells[idx].document.offsetAt(offsetOrLocation.range.start); const cellOffset = this._cellStarts.getAccumulatedValue(idx - 1); return this._delegate.document.positionAt(docOffset + cellOffset); } diff --git a/src/vs/workbench/test/browser/api/extHostNotebookConcatDocument.test.ts b/src/vs/workbench/test/browser/api/extHostNotebookConcatDocument.test.ts index 835b57fc75a..c00ca986fe6 100644 --- a/src/vs/workbench/test/browser/api/extHostNotebookConcatDocument.test.ts +++ b/src/vs/workbench/test/browser/api/extHostNotebookConcatDocument.test.ts @@ -50,6 +50,17 @@ suite('NotebookConcatDocument', function () { async resolveNotebook() { } }); await extHostNotebooks.$resolveNotebook('test', notebookUri); + extHostNotebooks.$acceptModelChanged(notebookUri, { + versionId: 0, + changes: [[0, 0, [{ + handle: 0, + uri: CellUri.generate(notebookUri, 0), + source: ['### Heading'], + language: 'markdown', + cellKind: CellKind.Markdown, + outputs: [], + }]]] + }); await extHostNotebooks.$updateActiveEditor('test', notebookUri); notebook = extHostNotebooks.activeNotebookDocument!; @@ -60,9 +71,9 @@ suite('NotebookConcatDocument', function () { }); test('empty', function () { - let doc = new ExtHostNotebookConcatDocument(notebook, extHostNotebooks, extHostDocuments); + let doc = new ExtHostNotebookConcatDocument(extHostNotebooks, extHostDocuments, notebook, undefined); assert.equal(doc.getText(), ''); - assert.equal(doc.versionId, 0); + assert.equal(doc.version, 0); // assert.equal(doc.locationAt(new Position(0, 0)), undefined); // assert.equal(doc.positionAt(SOME_FAKE_LOCATION?), undefined); @@ -111,9 +122,9 @@ suite('NotebookConcatDocument', function () { }); - assert.equal(notebook.cells.length, 2); + assert.equal(notebook.cells.length, 1 + 2); // markdown and code - let doc = new ExtHostNotebookConcatDocument(notebook, extHostNotebooks, extHostDocuments); + let doc = new ExtHostNotebookConcatDocument(extHostNotebooks, extHostDocuments, notebook, undefined); assertLines(doc, 'Hello', 'World', 'Hello World!', 'Hallo', 'Welt', 'Hallo Welt!'); assertLocation(doc, new Position(0, 0), new Location(notebook.cells[0].uri, new Position(0, 0))); @@ -126,7 +137,7 @@ suite('NotebookConcatDocument', function () { test('location, position mapping, cell changes', function () { - let doc = new ExtHostNotebookConcatDocument(notebook, extHostNotebooks, extHostDocuments); + let doc = new ExtHostNotebookConcatDocument(extHostNotebooks, extHostDocuments, notebook, undefined); // UPDATE 1 extHostNotebooks.$acceptModelChanged(notebookUri, { @@ -140,8 +151,8 @@ suite('NotebookConcatDocument', function () { outputs: [], }]]] }); - assert.equal(notebook.cells.length, 1); - assert.equal(doc.versionId, 1); + assert.equal(notebook.cells.length, 1 + 1); + assert.equal(doc.version, 1); assertLines(doc, 'Hello', 'World', 'Hello World!'); assertLocation(doc, new Position(0, 0), new Location(notebook.cells[0].uri, new Position(0, 0))); @@ -162,8 +173,8 @@ suite('NotebookConcatDocument', function () { }]]] }); - assert.equal(notebook.cells.length, 2); - assert.equal(doc.versionId, 2); + assert.equal(notebook.cells.length, 1 + 2); + assert.equal(doc.version, 2); assertLines(doc, 'Hello', 'World', 'Hello World!', 'Hallo', 'Welt', 'Hallo Welt!'); assertLocation(doc, new Position(0, 0), new Location(notebook.cells[0].uri, new Position(0, 0))); assertLocation(doc, new Position(4, 0), new Location(notebook.cells[1].uri, new Position(1, 0))); @@ -176,8 +187,8 @@ suite('NotebookConcatDocument', function () { versionId: notebook.versionId + 1, changes: [[1, 1, []]] }); - assert.equal(notebook.cells.length, 1); - assert.equal(doc.versionId, 3); + assert.equal(notebook.cells.length, 1 + 1); + assert.equal(doc.version, 3); assertLines(doc, 'Hello', 'World', 'Hello World!'); assertLocation(doc, new Position(0, 0), new Location(notebook.cells[0].uri, new Position(0, 0))); assertLocation(doc, new Position(2, 2), new Location(notebook.cells[0].uri, new Position(2, 2))); @@ -186,7 +197,7 @@ suite('NotebookConcatDocument', function () { test('location, position mapping, cell-document changes', function () { - let doc = new ExtHostNotebookConcatDocument(notebook, extHostNotebooks, extHostDocuments); + let doc = new ExtHostNotebookConcatDocument(extHostNotebooks, extHostDocuments, notebook, undefined); // UPDATE 1 extHostNotebooks.$acceptModelChanged(notebookUri, { @@ -207,8 +218,8 @@ suite('NotebookConcatDocument', function () { outputs: [], }]]] }); - assert.equal(notebook.cells.length, 2); - assert.equal(doc.versionId, 1); + assert.equal(notebook.cells.length, 1 + 2); + assert.equal(doc.version, 1); assertLines(doc, 'Hello', 'World', 'Hello World!', 'Hallo', 'Welt', 'Hallo Welt!'); assertLocation(doc, new Position(0, 0), new Location(notebook.cells[0].uri, new Position(0, 0))); @@ -248,4 +259,50 @@ suite('NotebookConcatDocument', function () { assert.equal(doc.positionAt(cell1End).isEqual(new Position(3, 2)), true); }); + + test('selector', function () { + + extHostNotebooks.$acceptModelChanged(notebookUri, { + versionId: notebook.versionId + 1, + changes: [[0, 0, [{ + handle: 1, + uri: CellUri.generate(notebook.uri, 1), + source: ['fooLang-document'], + language: 'fooLang', + cellKind: CellKind.Code, + outputs: [], + }, { + handle: 2, + uri: CellUri.generate(notebook.uri, 2), + source: ['barLang-document'], + language: 'barLang', + cellKind: CellKind.Code, + outputs: [], + }]]] + }); + + const mixedDoc = new ExtHostNotebookConcatDocument(extHostNotebooks, extHostDocuments, notebook, undefined); + const fooLangDoc = new ExtHostNotebookConcatDocument(extHostNotebooks, extHostDocuments, notebook, 'fooLang'); + const barLangDoc = new ExtHostNotebookConcatDocument(extHostNotebooks, extHostDocuments, notebook, 'barLang'); + + assertLines(mixedDoc, 'fooLang-document', 'barLang-document'); + assertLines(fooLangDoc, 'fooLang-document'); + assertLines(barLangDoc, 'barLang-document'); + + extHostNotebooks.$acceptModelChanged(notebookUri, { + versionId: notebook.versionId + 1, + changes: [[2, 0, [{ + handle: 3, + uri: CellUri.generate(notebook.uri, 3), + source: ['barLang-document2'], + language: 'barLang', + cellKind: CellKind.Code, + outputs: [], + }]]] + }); + + assertLines(mixedDoc, 'fooLang-document', 'barLang-document', 'barLang-document2'); + assertLines(fooLangDoc, 'fooLang-document'); + assertLines(barLangDoc, 'barLang-document', 'barLang-document2'); + }); }); From 116a3de416d91863f531c54593b276ff26955059 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Mon, 20 Apr 2020 20:26:56 +0200 Subject: [PATCH 15/44] Unify into single ViewContainerModel --- .../browser/actions/layoutActions.ts | 6 +- .../parts/activitybar/activitybarPart.ts | 31 +- .../workbench/browser/parts/compositeBar.ts | 4 +- .../browser/parts/panel/panelPart.ts | 39 +- .../browser/parts/views/viewPaneContainer.ts | 54 +- src/vs/workbench/browser/parts/views/views.ts | 10 +- .../browser/parts/views/viewsViewlet.ts | 16 +- src/vs/workbench/common/views.ts | 27 +- .../quickaccess/browser/viewQuickAccess.ts | 6 +- .../contrib/scm/browser/scmViewlet.ts | 20 +- .../terminal/browser/terminalService.ts | 2 +- .../views/browser/viewDescriptorService.ts | 718 ++++++++++++++++-- .../services/views/common/viewsModel.ts | 648 ---------------- .../browser/viewDescriptorService.test.ts | 27 +- .../views/test/browser/viewsModel.test.ts | 354 ++++----- 15 files changed, 951 insertions(+), 1011 deletions(-) delete mode 100644 src/vs/workbench/services/views/common/viewsModel.ts diff --git a/src/vs/workbench/browser/actions/layoutActions.ts b/src/vs/workbench/browser/actions/layoutActions.ts index 5ad55ee2b3a..043ff9b3a97 100644 --- a/src/vs/workbench/browser/actions/layoutActions.ts +++ b/src/vs/workbench/browser/actions/layoutActions.ts @@ -488,9 +488,9 @@ export class ResetViewLocationsAction extends Action { async run(): Promise { const viewContainerRegistry = Registry.as(ViewContainerExtensions.ViewContainersRegistry); viewContainerRegistry.all.forEach(viewContainer => { - const viewDescriptors = this.viewDescriptorService.getViewDescriptors(viewContainer); + const viewContainerModel = this.viewDescriptorService.getViewContainerModel(viewContainer); - viewDescriptors.allViewDescriptors.forEach(viewDescriptor => { + viewContainerModel.allViewDescriptors.forEach(viewDescriptor => { const defaultContainer = this.viewDescriptorService.getDefaultContainer(viewDescriptor.id); const currentContainer = this.viewDescriptorService.getViewContainer(viewDescriptor.id); @@ -583,7 +583,7 @@ export class MoveFocusedViewAction extends Action { const currentContainer = this.viewDescriptorService.getViewContainer(focusedViewId)!; const currentLocation = this.viewDescriptorService.getViewLocation(focusedViewId)!; - const isViewSolo = this.viewDescriptorService.getViewDescriptors(currentContainer).allViewDescriptors.length === 1; + const isViewSolo = this.viewDescriptorService.getViewContainerModel(currentContainer).allViewDescriptors.length === 1; if (!(isViewSolo && currentLocation === ViewContainerLocation.Sidebar)) { items.push({ diff --git a/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts b/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts index b55d9c0fca0..11f1592d7b5 100644 --- a/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts +++ b/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts @@ -26,7 +26,7 @@ import { IExtensionService } from 'vs/workbench/services/extensions/common/exten import { URI, UriComponents } from 'vs/base/common/uri'; import { ToggleCompositePinnedAction, ICompositeBarColors, ActivityAction, ICompositeActivity } from 'vs/workbench/browser/parts/compositeBarActions'; import { ViewletDescriptor } from 'vs/workbench/browser/viewlet'; -import { IViewDescriptorService, IViewContainersRegistry, Extensions as ViewContainerExtensions, ViewContainer, TEST_VIEW_CONTAINER_ID, IViewDescriptorCollection, ViewContainerLocation } from 'vs/workbench/common/views'; +import { IViewDescriptorService, IViewContainersRegistry, Extensions as ViewContainerExtensions, ViewContainer, TEST_VIEW_CONTAINER_ID, IViewContainerModel, ViewContainerLocation } from 'vs/workbench/common/views'; import { IContextKeyService, ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { IViewlet } from 'vs/workbench/common/viewlet'; import { isUndefinedOrNull, assertIsDefined, isString } from 'vs/base/common/types'; @@ -218,8 +218,8 @@ export class ActivitybarPart extends Part implements IActivityBarService { if (viewletDescriptor) { const viewContainer = this.getViewContainer(viewletDescriptor.id); if (viewContainer?.hideIfEmpty) { - const viewDescriptors = this.viewDescriptorService.getViewDescriptors(viewContainer); - if (viewDescriptors.activeViewDescriptors.length === 0) { + const viewContainerModel = this.viewDescriptorService.getViewContainerModel(viewContainer); + if (viewContainerModel.activeViewDescriptors.length === 0) { this.hideComposite(viewletDescriptor.id); // Update the composite bar by hiding } } @@ -490,13 +490,12 @@ export class ActivitybarPart extends Part implements IActivityBarService { for (const viewlet of viewlets) { this.enableCompositeActions(viewlet); const viewContainer = this.getViewContainer(viewlet.id)!; - const viewDescriptors = this.viewDescriptorService.getViewDescriptors(viewContainer); - this.onDidChangeActiveViews(viewlet, viewDescriptors, viewContainer.hideIfEmpty); + const viewContainerModel = this.viewDescriptorService.getViewContainerModel(viewContainer); + this.onDidChangeActiveViews(viewlet, viewContainerModel, viewContainer.hideIfEmpty); const disposables = new DisposableStore(); - disposables.add(viewDescriptors.onDidChangeActiveViews(() => this.onDidChangeActiveViews(viewlet, viewDescriptors, viewContainer.hideIfEmpty))); - disposables.add(viewDescriptors.onDidChangeViews(() => this.onDidUpdateViews(viewlet, viewDescriptors))); - disposables.add(viewDescriptors.onDidMove(() => this.onDidUpdateViews(viewlet, viewDescriptors))); + disposables.add(viewContainerModel.onDidChangeActiveViewDescriptors(() => this.onDidChangeActiveViews(viewlet, viewContainerModel, viewContainer.hideIfEmpty))); + disposables.add(viewContainerModel.onDidChangeContainerInfo(() => this.updateActivity(viewlet, viewContainerModel))); this.viewletDisposables.set(viewlet.id, disposables); } @@ -512,12 +511,12 @@ export class ActivitybarPart extends Part implements IActivityBarService { this.hideComposite(viewletId); } - private updateActivity(viewlet: ViewletDescriptor, viewDescriptors: IViewDescriptorCollection): void { - const icon = viewDescriptors.getIcon(); + private updateActivity(viewlet: ViewletDescriptor, viewContainerModel: IViewContainerModel): void { + const icon = viewContainerModel.icon; const activity: IActivity = { id: viewlet.id, - name: viewDescriptors.getTitle(), + name: viewContainerModel.title, cssClass: isString(icon) ? icon : undefined, iconUrl: icon instanceof URI ? icon : undefined, keybindingId: viewlet.keybindingId @@ -531,11 +530,7 @@ export class ActivitybarPart extends Part implements IActivityBarService { } } - private onDidUpdateViews(viewlet: ViewletDescriptor, viewDescriptors: IViewDescriptorCollection): void { - this.updateActivity(viewlet, viewDescriptors); - } - - private onDidChangeActiveViews(viewlet: ViewletDescriptor, viewDescriptors: IViewDescriptorCollection, hideIfEmpty?: boolean): void { + private onDidChangeActiveViews(viewlet: ViewletDescriptor, viewDescriptors: IViewContainerModel, hideIfEmpty?: boolean): void { if (viewDescriptors.activeViewDescriptors.length) { this.updateActivity(viewlet, viewDescriptors); this.compositeBar.addComposite(viewlet); @@ -663,8 +658,8 @@ export class ActivitybarPart extends Part implements IActivityBarService { if (viewlet) { const views: { when: string | undefined }[] = []; if (viewContainer) { - const viewDescriptors = this.viewDescriptorService.getViewDescriptors(viewContainer); - for (const { when } of viewDescriptors.allViewDescriptors) { + const viewContainerModel = this.viewDescriptorService.getViewContainerModel(viewContainer); + for (const { when } of viewContainerModel.allViewDescriptors) { views.push({ when: when ? when.serialize() : undefined }); } } diff --git a/src/vs/workbench/browser/parts/compositeBar.ts b/src/vs/workbench/browser/parts/compositeBar.ts index 2b67559e904..d25f80c50d7 100644 --- a/src/vs/workbench/browser/parts/compositeBar.ts +++ b/src/vs/workbench/browser/parts/compositeBar.ts @@ -56,7 +56,7 @@ export class CompositeDragAndDrop implements ICompositeDragAndDrop { } // ... on a different composite bar else { - const viewsToMove = this.viewDescriptorService.getViewDescriptors(currentContainer)!.allViewDescriptors; + const viewsToMove = this.viewDescriptorService.getViewContainerModel(currentContainer)!.allViewDescriptors; if (viewsToMove.some(v => !v.canMoveView)) { return; } @@ -119,7 +119,7 @@ export class CompositeDragAndDrop implements ICompositeDragAndDrop { } // ... to another composite location - const draggedViews = this.viewDescriptorService.getViewDescriptors(currentContainer)!.allViewDescriptors; + const draggedViews = this.viewDescriptorService.getViewContainerModel(currentContainer)!.allViewDescriptors; // ... all views must be movable return !draggedViews.some(v => !v.canMoveView); diff --git a/src/vs/workbench/browser/parts/panel/panelPart.ts b/src/vs/workbench/browser/parts/panel/panelPart.ts index 0f66d61a73b..e9713d8aab2 100644 --- a/src/vs/workbench/browser/parts/panel/panelPart.ts +++ b/src/vs/workbench/browser/parts/panel/panelPart.ts @@ -33,7 +33,7 @@ import { IContextKey, IContextKeyService, ContextKeyExpr } from 'vs/platform/con import { isUndefinedOrNull, assertIsDefined } from 'vs/base/common/types'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; -import { ViewContainer, IViewContainersRegistry, Extensions as ViewContainerExtensions, IViewDescriptorService, IViewDescriptorCollection, ViewContainerLocation } from 'vs/workbench/common/views'; +import { ViewContainer, IViewContainersRegistry, Extensions as ViewContainerExtensions, IViewDescriptorService, IViewContainerModel, ViewContainerLocation } from 'vs/workbench/common/views'; import { MenuId } from 'vs/platform/actions/common/actions'; import { ViewMenuActions } from 'vs/workbench/browser/parts/views/viewMenuActions'; import { IPaneComposite } from 'vs/workbench/common/panecomposite'; @@ -178,9 +178,9 @@ export class PanelPart extends CompositePart implements IPanelService { const result: IAction[] = []; const container = this.getViewContainer(compositeId); if (container) { - const viewDescriptors = this.viewDescriptorService.getViewDescriptors(container); - if (viewDescriptors.allViewDescriptors.length === 1) { - const viewMenuActions = this.instantiationService.createInstance(ViewMenuActions, viewDescriptors.allViewDescriptors[0].id, MenuId.ViewTitle, MenuId.ViewTitleContext); + const viewContainerModel = this.viewDescriptorService.getViewContainerModel(container); + if (viewContainerModel.allViewDescriptors.length === 1) { + const viewMenuActions = this.instantiationService.createInstance(ViewMenuActions, viewContainerModel.allViewDescriptors[0].id, MenuId.ViewTitle, MenuId.ViewTitleContext); result.push(...viewMenuActions.getContextMenuActions()); viewMenuActions.dispose(); } @@ -211,13 +211,13 @@ export class PanelPart extends CompositePart implements IPanelService { for (const panel of panels) { this.enableCompositeActions(panel); const viewContainer = this.getViewContainer(panel.id)!; - const viewDescriptors = this.viewDescriptorService.getViewDescriptors(viewContainer); - this.onDidChangeActiveViews(panel, viewDescriptors, viewContainer.hideIfEmpty); + const viewContainerModel = this.viewDescriptorService.getViewContainerModel(viewContainer); + this.onDidChangeActiveViews(panel, viewContainerModel, viewContainer.hideIfEmpty); const disposables = new DisposableStore(); - disposables.add(viewDescriptors.onDidChangeActiveViews(() => this.onDidChangeActiveViews(panel, viewDescriptors, viewContainer.hideIfEmpty))); - disposables.add(viewDescriptors.onDidChangeViews(() => this.onDidUpdateViews(panel, viewDescriptors))); - disposables.add(viewDescriptors.onDidMove(() => this.onDidUpdateViews(panel, viewDescriptors))); + disposables.add(viewContainerModel.onDidChangeActiveViewDescriptors(() => this.onDidChangeActiveViews(panel, viewContainerModel, viewContainer.hideIfEmpty))); + disposables.add(viewContainerModel.onDidChangeAllViewDescriptors(() => this.onDidUpdateViews(panel, viewContainerModel))); + disposables.add(viewContainerModel.onDidMoveVisibleViewDescriptors(() => this.onDidUpdateViews(panel, viewContainerModel))); this.panelDisposables.set(panel.id, disposables); } @@ -242,10 +242,10 @@ export class PanelPart extends CompositePart implements IPanelService { } } - private updateActivity(panel: PanelDescriptor, viewDescriptors: IViewDescriptorCollection): void { + private updateActivity(panel: PanelDescriptor, viewContainerModel: IViewContainerModel): void { const activity: IActivity = { id: panel.id, - name: viewDescriptors.getTitle(), + name: viewContainerModel.title, keybindingId: panel.keybindingId }; @@ -257,11 +257,11 @@ export class PanelPart extends CompositePart implements IPanelService { } } - private onDidUpdateViews(panel: PanelDescriptor, viewDescriptors: IViewDescriptorCollection): void { + private onDidUpdateViews(panel: PanelDescriptor, viewDescriptors: IViewContainerModel): void { this.updateActivity(panel, viewDescriptors); } - private onDidChangeActiveViews(panel: PanelDescriptor, viewDescriptors: IViewDescriptorCollection, hideIfEmpty?: boolean): void { + private onDidChangeActiveViews(panel: PanelDescriptor, viewDescriptors: IViewContainerModel, hideIfEmpty?: boolean): void { if (viewDescriptors.activeViewDescriptors.length) { this.updateActivity(panel, viewDescriptors); this.compositeBar.addComposite(panel); @@ -343,8 +343,8 @@ export class PanelPart extends CompositePart implements IPanelService { if (panelDescriptor) { const viewContainer = this.getViewContainer(panelDescriptor.id); if (viewContainer?.hideIfEmpty) { - const viewDescriptors = this.viewDescriptorService.getViewDescriptors(viewContainer); - if (viewDescriptors.activeViewDescriptors.length === 0 && this.compositeBar.getPinnedComposites().length > 1) { + const viewContainerModel = this.viewDescriptorService.getViewContainerModel(viewContainer); + if (viewContainerModel.activeViewDescriptors.length === 0 && this.compositeBar.getPinnedComposites().length > 1) { this.hideComposite(panelDescriptor.id); // Update the composite bar by hiding } } @@ -610,10 +610,11 @@ export class PanelPart extends CompositePart implements IPanelService { const compositeItems = this.compositeBar.getCompositeBarItems(); for (const compositeItem of compositeItems) { - const viewContainer = this.getViewContainer(compositeItem.id)!; - const viewDescriptors = this.viewDescriptorService.getViewDescriptors(viewContainer); - - state.push({ id: compositeItem.id, name: viewDescriptors.getTitle(), pinned: compositeItem.pinned, order: compositeItem.order, visible: compositeItem.visible }); + const viewContainer = this.getViewContainer(compositeItem.id); + if (viewContainer) { + const viewContainerModel = this.viewDescriptorService.getViewContainerModel(viewContainer); + state.push({ id: compositeItem.id, name: viewContainerModel.title, pinned: compositeItem.pinned, order: compositeItem.order, visible: compositeItem.visible }); + } } this.cachedPanelsValue = JSON.stringify(state); diff --git a/src/vs/workbench/browser/parts/views/viewPaneContainer.ts b/src/vs/workbench/browser/parts/views/viewPaneContainer.ts index 41c93486799..16c7bcdd413 100644 --- a/src/vs/workbench/browser/parts/views/viewPaneContainer.ts +++ b/src/vs/workbench/browser/parts/views/viewPaneContainer.ts @@ -24,7 +24,7 @@ import { PaneView, IPaneViewOptions, IPaneOptions, Pane } from 'vs/base/browser/ import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IWorkbenchLayoutService, Position } from 'vs/workbench/services/layout/browser/layoutService'; import { StandardMouseEvent } from 'vs/base/browser/mouseEvent'; -import { Extensions as ViewContainerExtensions, IView, FocusedViewContext, IViewContainersRegistry, IViewDescriptor, ViewContainer, IViewDescriptorService, ViewContainerLocation, IViewPaneContainer, IViewsRegistry, IViewContentDescriptor, IAddedViewDescriptorRef, IViewDescriptorRef, IViewDescriptorCollection } from 'vs/workbench/common/views'; +import { Extensions as ViewContainerExtensions, IView, FocusedViewContext, IViewContainersRegistry, IViewDescriptor, ViewContainer, IViewDescriptorService, ViewContainerLocation, IViewPaneContainer, IViewsRegistry, IViewContentDescriptor, IAddedViewDescriptorRef, IViewDescriptorRef, IViewContainerModel } from 'vs/workbench/common/views'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { assertIsDefined } from 'vs/base/common/types'; @@ -745,7 +745,7 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer { private readonly visibleViewsCountFromCache: number | undefined; private readonly visibleViewsStorageId: string; - protected readonly viewsDescriptors: IViewDescriptorCollection; + protected readonly viewContainerModel: IViewContainerModel; private viewDisposables: IDisposable[] = []; private readonly _onTitleAreaUpdate: Emitter = this._register(new Emitter()); @@ -806,7 +806,7 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer { this.visibleViewsStorageId = `${id}.numberOfVisibleViews`; this.visibleViewsCountFromCache = this.storageService.getNumber(this.visibleViewsStorageId, StorageScope.WORKSPACE, undefined); this._register(toDisposable(() => this.viewDisposables = dispose(this.viewDisposables))); - this.viewsDescriptors = this.viewDescriptorService.getViewDescriptors(container); + this.viewContainerModel = this.viewDescriptorService.getViewContainerModel(container); } create(parent: HTMLElement): void { @@ -837,7 +837,7 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer { const viewContainerRegistry = Registry.as(ViewContainerExtensions.ViewContainersRegistry); const container = viewContainerRegistry.get(dropData.id)!; - const viewsToMove = this.viewDescriptorService.getViewDescriptors(container).allViewDescriptors; + const viewsToMove = this.viewDescriptorService.getViewContainerModel(container).allViewDescriptors; if (!viewsToMove.some(v => !v.canMoveView)) { overlay = new ViewPaneDropOverlay(parent, undefined, this.themeService); @@ -859,7 +859,7 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer { const viewContainerRegistry = Registry.as(ViewContainerExtensions.ViewContainersRegistry); const container = viewContainerRegistry.get(dropData.id)!; - const allViews = this.viewDescriptorService.getViewDescriptors(container).allViewDescriptors; + const allViews = this.viewDescriptorService.getViewContainerModel(container).allViewDescriptors; if (!allViews.some(v => !v.canMoveView)) { viewsToMove.push(...allViews); } @@ -882,11 +882,11 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer { })); this._register(this.onDidSashChange(() => this.saveViewSizes())); - this.viewsDescriptors.onDidAdd(added => this.onDidAddViewDescriptors(added)); - this.viewsDescriptors.onDidRemove(removed => this.onDidRemoveViewDescriptors(removed)); - const addedViews: IAddedViewDescriptorRef[] = this.viewsDescriptors.visibleViewDescriptors.map((viewDescriptor, index) => { - const size = this.viewsDescriptors.getSize(viewDescriptor.id); - const collapsed = this.viewsDescriptors.isCollapsed(viewDescriptor.id); + this.viewContainerModel.onDidAddVisibleViewDescriptors(added => this.onDidAddViewDescriptors(added)); + this.viewContainerModel.onDidRemoveVisibleViewDescriptors(removed => this.onDidRemoveViewDescriptors(removed)); + const addedViews: IAddedViewDescriptorRef[] = this.viewContainerModel.visibleViewDescriptors.map((viewDescriptor, index) => { + const size = this.viewContainerModel.getSize(viewDescriptor.id); + const collapsed = this.viewContainerModel.isCollapsed(viewDescriptor.id); return ({ viewDescriptor, index, size, collapsed }); }); if (addedViews.length) { @@ -904,7 +904,7 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer { } getTitle(): string { - const containerTitle = this.viewsDescriptors.getTitle(); + const containerTitle = this.viewContainerModel.title; if (this.isViewMergedWithContainer()) { const paneItemTitle = this.paneItems[0].pane.title; @@ -955,10 +955,10 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer { } } - const viewToggleActions = this.viewsDescriptors.activeViewDescriptors.map(viewDescriptor => ({ + const viewToggleActions = this.viewContainerModel.activeViewDescriptors.map(viewDescriptor => ({ id: `${viewDescriptor.id}.toggleVisibility`, label: viewDescriptor.name, - checked: this.viewsDescriptors.isVisible(viewDescriptor.id), + checked: this.viewContainerModel.isVisible(viewDescriptor.id), enabled: viewDescriptor.canToggleVisibility, run: () => this.toggleViewVisibility(viewDescriptor.id) })); @@ -1087,7 +1087,7 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer { // Save size only when the layout has happened if (this.didLayout) { for (const view of this.panes) { - this.viewsDescriptors.setSize(view.id, this.getPaneSize(view)); + this.viewContainerModel.setSize(view.id, this.getPaneSize(view)); } } } @@ -1096,10 +1096,10 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer { // Restore sizes only when the layout has happened if (this.didLayout) { let initialSizes; - for (let i = 0; i < this.viewsDescriptors.visibleViewDescriptors.length; i++) { + for (let i = 0; i < this.viewContainerModel.visibleViewDescriptors.length; i++) { const pane = this.panes[i]; - const viewDescriptor = this.viewsDescriptors.visibleViewDescriptors[i]; - const size = this.viewsDescriptors.getSize(viewDescriptor.id); + const viewDescriptor = this.viewContainerModel.visibleViewDescriptors[i]; + const size = this.viewContainerModel.getSize(viewDescriptor.id); if (typeof size === 'number') { this.resizePane(pane, size); @@ -1114,8 +1114,8 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer { private computeInitialSizes(): Map { const sizes: Map = new Map(); if (this.dimension) { - const totalWeight = this.viewsDescriptors.visibleViewDescriptors.reduce((totalWeight, { weight }) => totalWeight + (weight || 20), 0); - for (const viewDescriptor of this.viewsDescriptors.visibleViewDescriptors) { + const totalWeight = this.viewContainerModel.visibleViewDescriptors.reduce((totalWeight, { weight }) => totalWeight + (weight || 20), 0); + for (const viewDescriptor of this.viewContainerModel.visibleViewDescriptors) { if (this.orientation === Orientation.VERTICAL) { sizes.set(viewDescriptor.id, this.dimension.height * (viewDescriptor.weight || 20) / totalWeight); } else { @@ -1176,7 +1176,7 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer { }); const collapseDisposable = Event.latch(Event.map(pane.onDidChange, () => !pane.isExpanded()))(collapsed => { - this.viewsDescriptors.setCollapsed(viewDescriptor.id, collapsed); + this.viewContainerModel.setCollapsed(viewDescriptor.id, collapsed); }); this.viewDisposables.splice(index, 0, combinedDisposable(contextMenuDisposable, collapseDisposable)); @@ -1207,13 +1207,13 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer { } protected toggleViewVisibility(viewId: string): void { - const visible = !this.viewsDescriptors.isVisible(viewId); + const visible = !this.viewContainerModel.isVisible(viewId); type ViewsToggleVisibilityClassification = { viewId: { classification: 'SystemMetaData', purpose: 'FeatureInsight' }; visible: { classification: 'SystemMetaData', purpose: 'FeatureInsight' }; }; this.telemetryService.publicLog2<{ viewId: String, visible: boolean }, ViewsToggleVisibilityClassification>('views.toggleVisibility', { viewId, visible }); - this.viewsDescriptors.setVisible(viewId, visible); + this.viewContainerModel.setVisible(viewId, visible); } private addPane(pane: ViewPane, size: number, index = this.paneItems.length - 1): void { @@ -1268,7 +1268,7 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer { const viewContainerRegistry = Registry.as(ViewContainerExtensions.ViewContainersRegistry); const container = viewContainerRegistry.get(dropData.id)!; - const viewsToMove = this.viewDescriptorService.getViewDescriptors(container).allViewDescriptors; + const viewsToMove = this.viewDescriptorService.getViewContainerModel(container).allViewDescriptors; if (!viewsToMove.some(v => !v.canMoveView)) { overlay = new ViewPaneDropOverlay(pane.dropTargetElement, this.orientation ?? Orientation.VERTICAL, this.themeService); @@ -1290,7 +1290,7 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer { const viewContainerRegistry = Registry.as(ViewContainerExtensions.ViewContainersRegistry); const container = viewContainerRegistry.get(dropData.id)!; - const allViews = this.viewDescriptorService.getViewDescriptors(container).allViewDescriptors; + const allViews = this.viewDescriptorService.getViewContainerModel(container).allViewDescriptors; if (allViews.length > 0 && !allViews.some(v => !v.canMoveView)) { viewsToMove.push(...allViews); @@ -1405,8 +1405,8 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer { const fromIndex = firstIndex(this.paneItems, item => item.pane === from); const toIndex = firstIndex(this.paneItems, item => item.pane === to); - const fromViewDescriptor = this.viewsDescriptors.visibleViewDescriptors[fromIndex]; - const toViewDescriptor = this.viewsDescriptors.visibleViewDescriptors[toIndex]; + const fromViewDescriptor = this.viewContainerModel.visibleViewDescriptors[fromIndex]; + const toViewDescriptor = this.viewContainerModel.visibleViewDescriptors[toIndex]; if (fromIndex < 0 || fromIndex >= this.paneItems.length) { return; @@ -1421,7 +1421,7 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer { assertIsDefined(this.paneview).movePane(from, to); - this.viewsDescriptors.move(fromViewDescriptor.id, toViewDescriptor.id); + this.viewContainerModel.move(fromViewDescriptor.id, toViewDescriptor.id); this.updateTitleArea(); } diff --git a/src/vs/workbench/browser/parts/views/views.ts b/src/vs/workbench/browser/parts/views/views.ts index 1050408d134..d9e0c6e66a3 100644 --- a/src/vs/workbench/browser/parts/views/views.ts +++ b/src/vs/workbench/browser/parts/views/views.ts @@ -109,15 +109,15 @@ export class ViewsService extends Disposable implements IViewsService { private onDidRegisterViewContainer(viewContainer: ViewContainer, viewContainerLocation: ViewContainerLocation): void { this.registerViewletOrPanel(viewContainer, viewContainerLocation); - const viewDescriptorCollection = this.viewDescriptorService.getViewDescriptors(viewContainer); - this.onViewDescriptorsAdded(viewDescriptorCollection.allViewDescriptors, viewContainer); - this._register(viewDescriptorCollection.onDidChangeViews(({ added, removed }) => { + const viewContainerModel = this.viewDescriptorService.getViewContainerModel(viewContainer); + this.onViewDescriptorsAdded(viewContainerModel.allViewDescriptors, viewContainer); + this._register(viewContainerModel.onDidChangeAllViewDescriptors(({ added, removed }) => { this.onViewDescriptorsAdded(added, viewContainer); this.onViewDescriptorsRemoved(removed); })); } - private onViewDescriptorsAdded(views: IViewDescriptor[], container: ViewContainer): void { + private onViewDescriptorsAdded(views: ReadonlyArray, container: ViewContainer): void { const location = this.viewContainersRegistry.getViewContainerLocation(container); if (location === undefined) { return; @@ -181,7 +181,7 @@ export class ViewsService extends Disposable implements IViewsService { } } - private onViewDescriptorsRemoved(views: IViewDescriptor[]): void { + private onViewDescriptorsRemoved(views: ReadonlyArray): void { for (const view of views) { const disposable = this.viewDisposable.get(view); if (disposable) { diff --git a/src/vs/workbench/browser/parts/views/viewsViewlet.ts b/src/vs/workbench/browser/parts/views/viewsViewlet.ts index 4fdd40c1197..8335c7352c3 100644 --- a/src/vs/workbench/browser/parts/views/viewsViewlet.ts +++ b/src/vs/workbench/browser/parts/views/viewsViewlet.ts @@ -46,8 +46,8 @@ export abstract class FilterViewPaneContainer extends ViewPaneContainer { this.onFilterChanged(newFilterValue); })); - this._register(this.viewsDescriptors.onDidChangeActiveViews((viewDescriptors) => { - this.updateAllViews(viewDescriptors); + this._register(this.viewContainerModel.onDidChangeActiveViewDescriptors(() => { + this.updateAllViews(this.viewContainerModel.activeViewDescriptors); })); } @@ -62,7 +62,7 @@ export abstract class FilterViewPaneContainer extends ViewPaneContainer { } this.allViews.get(filterOnValue)!.set(descriptor.id, descriptor); if (this.filterValue && !this.filterValue.includes(filterOnValue)) { - this.viewsDescriptors.setVisible(descriptor.id, false); + this.viewContainerModel.setVisible(descriptor.id, false); } }); } @@ -75,17 +75,17 @@ export abstract class FilterViewPaneContainer extends ViewPaneContainer { private onFilterChanged(newFilterValue: string[]) { if (this.allViews.size === 0) { - this.updateAllViews(this.viewsDescriptors.activeViewDescriptors); + this.updateAllViews(this.viewContainerModel.activeViewDescriptors); } - this.getViewsNotForTarget(newFilterValue).forEach(item => this.viewsDescriptors.setVisible(item.id, false)); - this.getViewsForTarget(newFilterValue).forEach(item => this.viewsDescriptors.setVisible(item.id, true)); + this.getViewsNotForTarget(newFilterValue).forEach(item => this.viewContainerModel.setVisible(item.id, false)); + this.getViewsForTarget(newFilterValue).forEach(item => this.viewContainerModel.setVisible(item.id, true)); } getContextMenuActions(): IAction[] { const result: IAction[] = Array.from(this.constantViewDescriptors.values()).map(viewDescriptor => ({ id: `${viewDescriptor.id}.toggleVisibility`, label: viewDescriptor.name, - checked: this.viewsDescriptors.isVisible(viewDescriptor.id), + checked: this.viewContainerModel.isVisible(viewDescriptor.id), enabled: viewDescriptor.canToggleVisibility, run: () => this.toggleViewVisibility(viewDescriptor.id) })); @@ -133,7 +133,7 @@ export abstract class FilterViewPaneContainer extends ViewPaneContainer { } // Check that allViews is ready if (this.allViews.size === 0) { - this.updateAllViews(this.viewsDescriptors.activeViewDescriptors); + this.updateAllViews(this.viewContainerModel.activeViewDescriptors); } return panes; } diff --git a/src/vs/workbench/common/views.ts b/src/vs/workbench/common/views.ts index 9a75589a419..dbdcdccdf92 100644 --- a/src/vs/workbench/common/views.ts +++ b/src/vs/workbench/common/views.ts @@ -219,18 +219,22 @@ export interface IAddedViewDescriptorRef extends IViewDescriptorRef { size?: number; } -export interface IViewDescriptorCollection extends IDisposable { +export interface IViewContainerModel { - readonly allViewDescriptors: IViewDescriptor[]; - readonly onDidChangeViews: Event<{ added: IViewDescriptor[], removed: IViewDescriptor[] }>; + readonly title: string; + readonly icon: string | URI | undefined; + readonly onDidChangeContainerInfo: Event<{ title?: boolean, icon?: boolean }>; - readonly activeViewDescriptors: IViewDescriptor[]; - readonly onDidChangeActiveViews: Event>; + readonly allViewDescriptors: ReadonlyArray; + readonly onDidChangeAllViewDescriptors: Event<{ added: ReadonlyArray, removed: ReadonlyArray }>; - readonly visibleViewDescriptors: IViewDescriptor[]; - readonly onDidAdd: Event; - readonly onDidRemove: Event - readonly onDidMove: Event<{ from: IViewDescriptorRef; to: IViewDescriptorRef; }> + readonly activeViewDescriptors: ReadonlyArray; + readonly onDidChangeActiveViewDescriptors: Event<{ added: ReadonlyArray, removed: ReadonlyArray }>; + + readonly visibleViewDescriptors: ReadonlyArray; + readonly onDidAddVisibleViewDescriptors: Event; + readonly onDidRemoveVisibleViewDescriptors: Event + readonly onDidMoveVisibleViewDescriptors: Event<{ from: IViewDescriptorRef; to: IViewDescriptorRef; }> isVisible(id: string): boolean; setVisible(id: string, visible: boolean, size?: number): void; @@ -241,9 +245,6 @@ export interface IViewDescriptorCollection extends IDisposable { getSize(id: string): number | undefined; setSize(id: string, size: number): void - getTitle(): string; - getIcon(): URI | string | undefined; - move(from: string, to: string): void; } @@ -484,7 +485,7 @@ export interface IViewDescriptorService { moveViewsToContainer(views: IViewDescriptor[], viewContainer: ViewContainer): void; - getViewDescriptors(container: ViewContainer): IViewDescriptorCollection; + getViewContainerModel(container: ViewContainer): IViewContainerModel; getViewDescriptor(viewId: string): IViewDescriptor | null; diff --git a/src/vs/workbench/contrib/quickaccess/browser/viewQuickAccess.ts b/src/vs/workbench/contrib/quickaccess/browser/viewQuickAccess.ts index e764dc28dc9..66ce39f9cec 100644 --- a/src/vs/workbench/contrib/quickaccess/browser/viewQuickAccess.ts +++ b/src/vs/workbench/contrib/quickaccess/browser/viewQuickAccess.ts @@ -96,9 +96,9 @@ export class ViewQuickAccessProvider extends PickerQuickAccessProvider = []; const getViewEntriesForViewlet = (viewlet: ViewletDescriptor, viewContainer: ViewContainer): IViewQuickPickItem[] => { - const viewDescriptors = this.viewDescriptorService.getViewDescriptors(viewContainer); + const viewContainerModel = this.viewDescriptorService.getViewContainerModel(viewContainer); const result: IViewQuickPickItem[] = []; - for (const view of viewDescriptors.allViewDescriptors) { + for (const view of viewContainerModel.allViewDescriptors) { if (this.contextKeyService.contextMatchesRules(view.when)) { result.push({ label: view.name, @@ -176,7 +176,7 @@ export class ViewQuickAccessProvider extends PickerQuickAccessProvider(ViewExtensions.ViewContainersRegistry).get(container.id); if (viewContainer?.hideIfEmpty) { - return this.viewDescriptorService.getViewDescriptors(viewContainer).activeViewDescriptors.length > 0; + return this.viewDescriptorService.getViewContainerModel(viewContainer).activeViewDescriptors.length > 0; } return true; diff --git a/src/vs/workbench/contrib/scm/browser/scmViewlet.ts b/src/vs/workbench/contrib/scm/browser/scmViewlet.ts index 2bd012782a3..5de61ced71d 100644 --- a/src/vs/workbench/contrib/scm/browser/scmViewlet.ts +++ b/src/vs/workbench/contrib/scm/browser/scmViewlet.ts @@ -101,7 +101,7 @@ export class SCMViewPaneContainer extends ViewPaneContainer implements IViewMode } get onDidChangeVisibleRepositories(): Event { - const modificationEvent = Event.debounce(Event.any(this.viewsDescriptors.onDidAdd, this.viewsDescriptors.onDidRemove), () => null, 0); + const modificationEvent = Event.debounce(Event.any(this.viewContainerModel.onDidAddVisibleViewDescriptors, this.viewContainerModel.onDidRemoveVisibleViewDescriptors), () => null, 0); return Event.map(modificationEvent, () => this.visibleRepositories); } @@ -140,14 +140,14 @@ export class SCMViewPaneContainer extends ViewPaneContainer implements IViewMode this._register(configurationService.onDidChangeConfiguration(e => { if (e.affectsConfiguration('scm.alwaysShowProviders') && configurationService.getValue('scm.alwaysShowProviders')) { - this.viewsDescriptors.setVisible(MainPane.ID, true); + this.viewContainerModel.setVisible(MainPane.ID, true); } })); this.repositoryCountKey = contextKeyService.createKey('scm.providerCount', 0); - this._register(this.viewsDescriptors.onDidAdd(this.onDidShowView, this)); - this._register(this.viewsDescriptors.onDidRemove(this.onDidHideView, this)); + this._register(this.viewContainerModel.onDidAddVisibleViewDescriptors(this.onDidShowView, this)); + this._register(this.viewContainerModel.onDidRemoveVisibleViewDescriptors(this.onDidHideView, this)); } create(parent: HTMLElement): void { @@ -214,8 +214,8 @@ export class SCMViewPaneContainer extends ViewPaneContainer implements IViewMode @debounce(0) private afterOnDidHideView(): void { - if (this.repositoryCountKey.get()! > 0 && this.viewDescriptors.every(d => !this.viewsDescriptors.isVisible(d.id))) { - this.viewsDescriptors.setVisible(this.viewDescriptors[0].id, true); + if (this.repositoryCountKey.get()! > 0 && this.viewDescriptors.every(d => !this.viewContainerModel.isVisible(d.id))) { + this.viewContainerModel.setVisible(this.viewDescriptors[0].id, true); } } @@ -282,9 +282,9 @@ export class SCMViewPaneContainer extends ViewPaneContainer implements IViewMode } setVisibleRepositories(repositories: ISCMRepository[]): void { - const visibleViewDescriptors = this.viewsDescriptors.visibleViewDescriptors; + const visibleViewDescriptors = this.viewContainerModel.visibleViewDescriptors; - const toSetVisible = this.viewsDescriptors.activeViewDescriptors + const toSetVisible = this.viewContainerModel.activeViewDescriptors .filter((d): d is RepositoryViewDescriptor => d instanceof RepositoryViewDescriptor && repositories.indexOf(d.repository) > -1 && visibleViewDescriptors.indexOf(d) === -1); const toSetInvisible = visibleViewDescriptors @@ -302,11 +302,11 @@ export class SCMViewPaneContainer extends ViewPaneContainer implements IViewMode } } - this.viewsDescriptors.setVisible(viewDescriptor.id, false); + this.viewContainerModel.setVisible(viewDescriptor.id, false); } for (const viewDescriptor of toSetVisible) { - this.viewsDescriptors.setVisible(viewDescriptor.id, true, size); + this.viewContainerModel.setVisible(viewDescriptor.id, true, size); } } } diff --git a/src/vs/workbench/contrib/terminal/browser/terminalService.ts b/src/vs/workbench/contrib/terminal/browser/terminalService.ts index 667b3ed88da..e5cd0501514 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalService.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalService.ts @@ -704,7 +704,7 @@ export class TerminalService implements ITerminalService { const location = this._viewDescriptorService.getViewLocation(TERMINAL_VIEW_ID); if (location === ViewContainerLocation.Panel) { const panel = this._viewDescriptorService.getViewContainer(TERMINAL_VIEW_ID); - if (panel && this._viewDescriptorService.getViewDescriptors(panel).activeViewDescriptors.length === 1) { + if (panel && this._viewDescriptorService.getViewContainerModel(panel).activeViewDescriptors.length === 1) { this._layoutService.setPanelHidden(true); } } diff --git a/src/vs/workbench/services/views/browser/viewDescriptorService.ts b/src/vs/workbench/services/views/browser/viewDescriptorService.ts index f834a9e7330..0967eff4b2a 100644 --- a/src/vs/workbench/services/views/browser/viewDescriptorService.ts +++ b/src/vs/workbench/services/views/browser/viewDescriptorService.ts @@ -3,8 +3,8 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { ViewContainerLocation, IViewDescriptorService, ViewContainer, IViewsRegistry, IViewContainersRegistry, IViewDescriptor, Extensions as ViewExtensions, IViewDescriptorCollection, IAddedViewDescriptorRef, IViewDescriptorRef } from 'vs/workbench/common/views'; -import { IContextKey, RawContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { ViewContainerLocation, IViewDescriptorService, ViewContainer, IViewsRegistry, IViewContainersRegistry, IViewDescriptor, Extensions as ViewExtensions, IViewContainerModel, IAddedViewDescriptorRef, IViewDescriptorRef } from 'vs/workbench/common/views'; +import { IContextKey, RawContextKey, IContextKeyService, IReadableSet } from 'vs/platform/contextkey/common/contextkey'; import { IStorageService, StorageScope, IWorkspaceStorageChangeEvent } from 'vs/platform/storage/common/storage'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { Registry } from 'vs/platform/registry/common/platform'; @@ -17,68 +17,646 @@ import { IStorageKeysSyncRegistryService } from 'vs/platform/userDataSync/common import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { generateUuid } from 'vs/base/common/uuid'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { PersistentViewsModel, ViewDescriptorsModel } from 'vs/workbench/services/views/common/viewsModel'; import { URI } from 'vs/base/common/uri'; +import { firstIndex, move } from 'vs/base/common/arrays'; +import { isUndefined, isUndefinedOrNull } from 'vs/base/common/types'; +import { values } from 'vs/base/common/map'; +import { isEqual } from 'vs/base/common/resources'; -class ViewDescriptorCollection extends Disposable implements IViewDescriptorCollection { +class CounterSet implements IReadableSet { - private readonly viewDescriptorsModel: ViewDescriptorsModel; - private readonly viewsModel: PersistentViewsModel; + private map = new Map(); + + add(value: T): CounterSet { + this.map.set(value, (this.map.get(value) || 0) + 1); + return this; + } + + delete(value: T): boolean { + let counter = this.map.get(value) || 0; + + if (counter === 0) { + return false; + } + + counter--; + + if (counter === 0) { + this.map.delete(value); + } else { + this.map.set(value, counter); + } + + return true; + } + + has(value: T): boolean { + return this.map.has(value); + } +} + +interface IStoredWorkspaceViewState { + collapsed: boolean; + isHidden: boolean; + size?: number; + order?: number; +} + +interface IStoredGlobalViewState { + id: string; + isHidden: boolean; + order?: number; +} + +interface IViewDescriptorState { + visibleGlobal: boolean | undefined; + visibleWorkspace: boolean | undefined; + collapsed: boolean | undefined; + active: boolean + order?: number; + size?: number; +} + +class ViewDescriptorsState extends Disposable { + + private readonly workspaceViewsStateStorageId: string; + private readonly globalViewsStateStorageId: string; + private readonly state: Map; + + private _onDidChangeStoredState = this._register(new Emitter<{ id: string, visible: boolean }[]>()); + readonly onDidChangeStoredState = this._onDidChangeStoredState.event; + + constructor( + viewContainerStorageId: string, + @IStorageService private readonly storageService: IStorageService, + @IStorageKeysSyncRegistryService storageKeysSyncRegistryService: IStorageKeysSyncRegistryService + ) { + super(); + + this.globalViewsStateStorageId = `${viewContainerStorageId}.hidden`; + this.workspaceViewsStateStorageId = viewContainerStorageId; + storageKeysSyncRegistryService.registerStorageKey({ key: this.globalViewsStateStorageId, version: 1 }); + this._register(this.storageService.onDidChangeStorage(e => this.onDidStorageChange(e))); + + this.state = this.initialize(); + } + + set(id: string, state: IViewDescriptorState): void { + this.state.set(id, state); + } + + get(id: string): IViewDescriptorState | undefined { + return this.state.get(id); + } + + updateState(viewDescriptors: ReadonlyArray): void { + this.updateWorkspaceState(viewDescriptors); + this.updateGlobalState(viewDescriptors); + } + + private updateWorkspaceState(viewDescriptors: ReadonlyArray): void { + const storedViewsStates: { [id: string]: IStoredWorkspaceViewState; } = JSON.parse(this.storageService.get(this.workspaceViewsStateStorageId, StorageScope.WORKSPACE, '{}')); + for (const viewDescriptor of viewDescriptors) { + const viewState = this.state.get(viewDescriptor.id); + if (viewState) { + storedViewsStates[viewDescriptor.id] = { + collapsed: !!viewState.collapsed, + isHidden: !viewState.visibleWorkspace, + size: viewState.size, + order: viewDescriptor.workspace && viewState ? viewState.order : undefined + }; + } + } + + if (Object.keys(storedViewsStates).length > 0) { + this.storageService.store(this.workspaceViewsStateStorageId, JSON.stringify(storedViewsStates), StorageScope.WORKSPACE); + } else { + this.storageService.remove(this.workspaceViewsStateStorageId, StorageScope.WORKSPACE); + } + } + + private updateGlobalState(viewDescriptors: ReadonlyArray): void { + const storedGlobalState = this.getStoredGlobalState(); + for (const viewDescriptor of viewDescriptors) { + const state = this.state.get(viewDescriptor.id); + storedGlobalState.set(viewDescriptor.id, { + id: viewDescriptor.id, + isHidden: state && viewDescriptor.canToggleVisibility ? !state.visibleGlobal : false, + order: !viewDescriptor.workspace && state ? state.order : undefined + }); + } + this.setStoredGlobalState(storedGlobalState); + } + + private onDidStorageChange(e: IWorkspaceStorageChangeEvent): void { + if (e.key === this.globalViewsStateStorageId && e.scope === StorageScope.GLOBAL + && this.globalViewsStatesValue !== this.getStoredGlobalViewsStatesValue() /* This checks if current window changed the value or not */) { + this._globalViewsStatesValue = undefined; + const storedViewsVisibilityStates = this.getStoredGlobalState(); + const changedStates: { id: string, visible: boolean }[] = []; + for (const [id, storedState] of storedViewsVisibilityStates) { + const state = this.state.get(id); + if (state) { + if (state.visibleGlobal !== !storedState.isHidden) { + changedStates.push({ id, visible: !storedState.isHidden }); + } + } + } + if (changedStates.length) { + this._onDidChangeStoredState.fire(changedStates); + } + } + } + + private initialize(): Map { + const viewStates = new Map(); + const workspaceViewsStates = <{ [id: string]: IStoredWorkspaceViewState; }>JSON.parse(this.storageService.get(this.workspaceViewsStateStorageId, StorageScope.WORKSPACE, '{}')); + for (const id of Object.keys(workspaceViewsStates)) { + const workspaceViewState = workspaceViewsStates[id]; + viewStates.set(id, { + active: false, + visibleGlobal: undefined, + visibleWorkspace: isUndefined(workspaceViewState.isHidden) ? undefined : !workspaceViewState.isHidden, + collapsed: workspaceViewState.collapsed, + order: workspaceViewState.order, + size: workspaceViewState.size, + }); + } + + // Migrate to `viewletStateStorageId` + const value = this.storageService.get(this.globalViewsStateStorageId, StorageScope.WORKSPACE, '[]'); + const { state: workspaceVisibilityStates } = this.parseStoredGlobalState(value); + if (workspaceVisibilityStates.size > 0) { + for (const { id, isHidden } of values(workspaceVisibilityStates)) { + let viewState = viewStates.get(id); + // Not migrated to `viewletStateStorageId` + if (viewState) { + if (isUndefined(viewState.visibleWorkspace)) { + viewState.visibleWorkspace = !isHidden; + } + } else { + viewStates.set(id, { + active: false, + collapsed: undefined, + visibleGlobal: undefined, + visibleWorkspace: !isHidden, + }); + } + } + this.storageService.remove(this.globalViewsStateStorageId, StorageScope.WORKSPACE); + } + + const { state, hasDuplicates } = this.parseStoredGlobalState(this.globalViewsStatesValue); + if (hasDuplicates) { + this.setStoredGlobalState(state); + } + for (const { id, isHidden, order } of values(state)) { + let viewState = viewStates.get(id); + if (viewState) { + viewState.visibleGlobal = !isHidden; + if (!isUndefined(order)) { + viewState.order = order; + } + } else { + viewStates.set(id, { + active: false, + visibleGlobal: !isHidden, + order, + collapsed: undefined, + visibleWorkspace: undefined, + }); + } + } + return viewStates; + } + + private getStoredGlobalState(): Map { + return this.parseStoredGlobalState(this.globalViewsStatesValue).state; + } + + private setStoredGlobalState(storedGlobalState: Map): void { + this.globalViewsStatesValue = JSON.stringify(values(storedGlobalState)); + } + + private parseStoredGlobalState(value: string): { state: Map, hasDuplicates: boolean } { + const storedValue = >JSON.parse(value); + let hasDuplicates = false; + const state = storedValue.reduce((result, storedState) => { + if (typeof storedState === 'string' /* migration */) { + hasDuplicates = hasDuplicates || result.has(storedState); + result.set(storedState, { id: storedState, isHidden: true }); + } else { + hasDuplicates = hasDuplicates || result.has(storedState.id); + result.set(storedState.id, storedState); + } + return result; + }, new Map()); + return { state, hasDuplicates }; + } + + private _globalViewsStatesValue: string | undefined; + private get globalViewsStatesValue(): string { + if (!this._globalViewsStatesValue) { + this._globalViewsStatesValue = this.getStoredGlobalViewsStatesValue(); + } + + return this._globalViewsStatesValue; + } + + private set globalViewsStatesValue(globalViewsStatesValue: string) { + if (this.globalViewsStatesValue !== globalViewsStatesValue) { + this._globalViewsStatesValue = globalViewsStatesValue; + this.setStoredGlobalViewsStatesValue(globalViewsStatesValue); + } + } + + private getStoredGlobalViewsStatesValue(): string { + return this.storageService.get(this.globalViewsStateStorageId, StorageScope.GLOBAL, '[]'); + } + + private setStoredGlobalViewsStatesValue(value: string): void { + this.storageService.store(this.globalViewsStateStorageId, value, StorageScope.GLOBAL); + } + +} + +interface IViewDescriptorItem { + viewDescriptor: IViewDescriptor; + state: IViewDescriptorState; +} + +class ViewContainerModel extends Disposable implements IViewContainerModel { + + private readonly contextKeys = new CounterSet(); + private viewDescriptorItems: IViewDescriptorItem[] = []; + private viewDescriptorsState: ViewDescriptorsState; + + // Container Info + private _title!: string; + get title(): string { return this._title; } + private _icon: URI | string | undefined; + get icon(): URI | string | undefined { return this._icon; } + + private _onDidChangeContainerInfo = this._register(new Emitter<{ title?: boolean, icon?: boolean }>()); + readonly onDidChangeContainerInfo = this._onDidChangeContainerInfo.event; + + // All View Descriptors + get allViewDescriptors(): ReadonlyArray { return this.viewDescriptorItems.map(item => item.viewDescriptor); } + private _onDidChangeAllViewDescriptors = this._register(new Emitter<{ added: ReadonlyArray, removed: ReadonlyArray }>()); + readonly onDidChangeAllViewDescriptors = this._onDidChangeAllViewDescriptors.event; + + // Active View Descriptors + get activeViewDescriptors(): ReadonlyArray { return this.viewDescriptorItems.filter(item => item.state.active).map(item => item.viewDescriptor); } + private _onDidChangeActiveViewDescriptors = this._register(new Emitter<{ added: ReadonlyArray, removed: ReadonlyArray }>()); + readonly onDidChangeActiveViewDescriptors = this._onDidChangeActiveViewDescriptors.event; + + // Visible View Descriptors + get visibleViewDescriptors(): ReadonlyArray { return this.viewDescriptorItems.filter(item => this.isViewDescriptorVisible(item)).map(item => item.viewDescriptor); } + + private _onDidAddVisibleViewDescriptors = this._register(new Emitter()); + readonly onDidAddVisibleViewDescriptors: Event = this._onDidAddVisibleViewDescriptors.event; + + private _onDidRemoveVisibleViewDescriptors = this._register(new Emitter()); + readonly onDidRemoveVisibleViewDescriptors: Event = this._onDidRemoveVisibleViewDescriptors.event; + + private _onDidMoveVisibleViewDescriptors = this._register(new Emitter<{ from: IViewDescriptorRef; to: IViewDescriptorRef; }>()); + readonly onDidMoveVisibleViewDescriptors: Event<{ from: IViewDescriptorRef; to: IViewDescriptorRef; }> = this._onDidMoveVisibleViewDescriptors.event; constructor( private readonly container: ViewContainer, @IInstantiationService instantiationService: IInstantiationService, + @IContextKeyService private readonly contextKeyService: IContextKeyService, ) { super(); - this.viewDescriptorsModel = instantiationService.createInstance(ViewDescriptorsModel); - this.viewsModel = instantiationService.createInstance(PersistentViewsModel, container.storageId || `${container.id}.state`, this.viewDescriptorsModel); + + this._register(Event.filter(contextKeyService.onDidChangeContext, e => e.affectsSome(this.contextKeys))(() => this.onDidChangeContext())); + this.viewDescriptorsState = this._register(instantiationService.createInstance(ViewDescriptorsState, container.storageId || `${container.id}.state`)); + this._register(this.viewDescriptorsState.onDidChangeStoredState(items => this.updateVisibility(items))); + + this._register(Event.any( + this.onDidAddVisibleViewDescriptors, + this.onDidRemoveVisibleViewDescriptors, + this.onDidMoveVisibleViewDescriptors) + (() => { + this.viewDescriptorsState.updateState(this.allViewDescriptors); + this.updateContainerInfo(); + })); + + this.updateContainerInfo(); } - get allViewDescriptors(): IViewDescriptor[] { return this.viewDescriptorsModel.allViewDescriptors; } - get onDidChangeViews(): Event<{ added: IViewDescriptor[], removed: IViewDescriptor[] }> { return this.viewDescriptorsModel.onDidChangeViews; } - - get activeViewDescriptors(): IViewDescriptor[] { return this.viewDescriptorsModel.activeViewDescriptors; } - get _onDidChangeActiveViews(): Event<{ added: IViewDescriptor[], removed: IViewDescriptor[]; }> { return this.viewDescriptorsModel.onDidChangeActiveViews; } - get onDidChangeActiveViews(): Event> { return this.viewsModel.onDidChangeActiveViews; } - - addViews(viewDescriptors: IViewDescriptor[]): void { return this.viewDescriptorsModel.addViews(viewDescriptors); } - removeViews(viewDescriptors: IViewDescriptor[]): void { return this.viewDescriptorsModel.removeViews(viewDescriptors); } - - get visibleViewDescriptors(): IViewDescriptor[] { return this.viewsModel.visibleViewDescriptors; } - get onDidAdd(): Event { return this.viewsModel.onDidAdd; } - get onDidRemove(): Event { return this.viewsModel.onDidRemove; } - get onDidMove(): Event<{ from: IViewDescriptorRef; to: IViewDescriptorRef; }> { return this.viewsModel.onDidMove; } - - isVisible(id: string): boolean { return this.viewsModel.isVisible(id); } - setVisible(id: string, visible: boolean, size?: number): void { return this.viewsModel.setVisible(id, visible, size); } - - isCollapsed(id: string): boolean { return this.viewsModel.isCollapsed(id); } - setCollapsed(id: string, collapsed: boolean): void { return this.viewsModel.setCollapsed(id, collapsed); } - - private shouldUseContainerInfo(): boolean { - if (this.allViewDescriptors.length === 0) { - return true; + private updateContainerInfo(): void { + /* Use default container info if one of the visible view descriptors belongs to the current container by default */ + const useDefaultContainerInfo = this.visibleViewDescriptors.some(v => Registry.as(ViewExtensions.ViewsRegistry).getViewContainer(v.id) === this.container); + const title = useDefaultContainerInfo ? this.container.name : this.visibleViewDescriptors[0]?.name || ''; + let titleChanged: boolean = false; + if (this._title !== title) { + this._title = title; + titleChanged = true; } - if (this.visibleViewDescriptors.length === 0) { - return true; + const icon = useDefaultContainerInfo ? this.container.icon : this.visibleViewDescriptors[0]?.containerIcon || 'codicon-window'; + let iconChanged: boolean = false; + if (URI.isUri(icon) && URI.isUri(this._icon) ? isEqual(icon, this._icon) : this._icon !== icon) { + this._icon = icon; + iconChanged = true; } - return this.allViewDescriptors.some(v => Registry.as(ViewExtensions.ViewsRegistry).getViewContainer(v.id) === this.container); + if (titleChanged || iconChanged) { + this._onDidChangeContainerInfo.fire({ title: titleChanged, icon: iconChanged }); + } } - getTitle(): string { - return this.shouldUseContainerInfo() ? this.container.name : this.visibleViewDescriptors[0].name; + isVisible(id: string): boolean { + const viewDescriptorItem = this.viewDescriptorItems.filter(v => v.viewDescriptor.id === id)[0]; + if (!viewDescriptorItem) { + throw new Error(`Unknown view ${id}`); + } + return this.isViewDescriptorVisible(viewDescriptorItem); } - getIcon(): URI | string | undefined { - return this.shouldUseContainerInfo() ? this.container.icon : this.visibleViewDescriptors[0]?.containerIcon || 'codicon-window'; + setVisible(id: string, visible: boolean, size?: number): void { + this.updateVisibility([{ id, visible, size }]); } - getSize(id: string): number | undefined { return this.viewsModel.getSize(id); } - setSize(id: string, size: number): void { return this.viewsModel.setSize(id, size); } + private updateVisibility(viewDescriptors: { id: string, visible: boolean, size?: number }[]): void { + const added: IAddedViewDescriptorRef[] = []; + const removed: IViewDescriptorRef[] = []; - move(from: string, to: string): void { return this.viewsModel.move(from, to); } + for (const { visibleIndex, viewDescriptorItem, visible, size } of viewDescriptors.map(({ id, visible, size }) => ({ ...this.find(id), visible, size }))) { + const viewDescriptor = viewDescriptorItem.viewDescriptor; + + if (!viewDescriptor.canToggleVisibility) { + throw new Error(`Can't toggle this view's visibility`); + } + + if (this.isViewDescriptorVisible(viewDescriptorItem) === visible) { + return; + } + + if (viewDescriptor.workspace) { + viewDescriptorItem.state.visibleWorkspace = visible; + } else { + viewDescriptorItem.state.visibleGlobal = visible; + } + + if (typeof viewDescriptorItem.state.size === 'number') { + viewDescriptorItem.state.size = size; + } + + if (visible) { + added.push({ index: visibleIndex, viewDescriptor, size: viewDescriptorItem.state.size, collapsed: !!viewDescriptorItem.state.collapsed }); + } else { + removed.push({ index: visibleIndex, viewDescriptor }); + } + } + + if (added.length) { + this._onDidAddVisibleViewDescriptors.fire(added); + } + if (removed.length) { + this._onDidRemoveVisibleViewDescriptors.fire(removed); + } + } + + isCollapsed(id: string): boolean { + return !!this.find(id).viewDescriptorItem.state.collapsed; + } + + setCollapsed(id: string, collapsed: boolean): void { + const { viewDescriptorItem } = this.find(id); + if (viewDescriptorItem.state.collapsed !== collapsed) { + viewDescriptorItem.state.collapsed = collapsed; + } + this.viewDescriptorsState.updateState(this.allViewDescriptors); + } + + getSize(id: string): number | undefined { + return this.find(id).viewDescriptorItem.state.size; + } + + setSize(id: string, size: number): void { + const { viewDescriptorItem } = this.find(id); + if (viewDescriptorItem.state.size !== size) { + viewDescriptorItem.state.size = size; + } + this.viewDescriptorsState.updateState(this.allViewDescriptors); + } + + move(from: string, to: string): void { + const fromIndex = firstIndex(this.viewDescriptorItems, v => v.viewDescriptor.id === from); + const toIndex = firstIndex(this.viewDescriptorItems, v => v.viewDescriptor.id === to); + + const fromViewDescriptor = this.viewDescriptorItems[fromIndex]; + const toViewDescriptor = this.viewDescriptorItems[toIndex]; + + move(this.viewDescriptorItems, fromIndex, toIndex); + + for (let index = 0; index < this.viewDescriptorItems.length; index++) { + this.viewDescriptorItems[index].state.order = index; + } + + this._onDidMoveVisibleViewDescriptors.fire({ + from: { index: fromIndex, viewDescriptor: fromViewDescriptor.viewDescriptor }, + to: { index: toIndex, viewDescriptor: toViewDescriptor.viewDescriptor } + }); + } + + add(viewDescriptors: IViewDescriptor[]): void { + const addedItems: IViewDescriptorItem[] = []; + const addedActiveDescriptors: IViewDescriptor[] = []; + const addedVisibleItems: { index: number, viewDescriptor: IViewDescriptor, size?: number, collapsed: boolean; }[] = []; + + for (const viewDescriptor of viewDescriptors) { + + if (viewDescriptor.when) { + for (const key of viewDescriptor.when.keys()) { + this.contextKeys.add(key); + } + } + + let state = this.viewDescriptorsState.get(viewDescriptor.id); + if (state) { + // set defaults if not set + if (viewDescriptor.workspace) { + state.visibleWorkspace = isUndefinedOrNull(state.visibleWorkspace) ? !viewDescriptor.hideByDefault : state.visibleWorkspace; + } else { + state.visibleGlobal = isUndefinedOrNull(state.visibleGlobal) ? !viewDescriptor.hideByDefault : state.visibleGlobal; + } + state.collapsed = isUndefinedOrNull(state.collapsed) ? !!viewDescriptor.collapsed : state.collapsed; + } else { + state = { + active: false, + visibleGlobal: !viewDescriptor.hideByDefault, + visibleWorkspace: !viewDescriptor.hideByDefault, + collapsed: !!viewDescriptor.collapsed, + }; + } + this.viewDescriptorsState.set(viewDescriptor.id, state); + state.active = this.contextKeyService.contextMatchesRules(viewDescriptor.when); + addedItems.push({ viewDescriptor, state }); + + if (state.active) { + addedActiveDescriptors.push(viewDescriptor); + } + } + + this.viewDescriptorItems.push(...addedItems); + this.viewDescriptorItems.sort(this.compareViewDescriptors.bind(this)); + + for (const viewDescriptorItem of addedItems) { + if (this.isViewDescriptorVisible(viewDescriptorItem)) { + const { visibleIndex } = this.find(viewDescriptorItem.viewDescriptor.id); + addedVisibleItems.push({ index: visibleIndex, viewDescriptor: viewDescriptorItem.viewDescriptor, size: viewDescriptorItem.state.size, collapsed: !!viewDescriptorItem.state.collapsed }); + } + } + + this._onDidChangeAllViewDescriptors.fire({ added: addedItems.map(({ viewDescriptor }) => viewDescriptor), removed: [] }); + if (addedActiveDescriptors.length) { + this._onDidChangeActiveViewDescriptors.fire(({ added: addedActiveDescriptors, removed: [] })); + } + if (addedVisibleItems.length) { + this._onDidAddVisibleViewDescriptors.fire(addedVisibleItems); + } + } + + remove(viewDescriptors: IViewDescriptor[]): void { + const removed: IViewDescriptor[] = []; + const removedItems: IViewDescriptorItem[] = []; + const removedActiveDescriptors: IViewDescriptor[] = []; + const removedVisibleItems: { index: number, viewDescriptor: IViewDescriptor; }[] = []; + + for (const viewDescriptor of viewDescriptors) { + if (viewDescriptor.when) { + for (const key of viewDescriptor.when.keys()) { + this.contextKeys.delete(key); + } + } + const index = firstIndex(this.viewDescriptorItems, i => i.viewDescriptor.id === viewDescriptor.id); + if (index !== -1) { + removed.push(viewDescriptor); + const viewDescriptorItem = this.viewDescriptorItems[index]; + if (viewDescriptorItem.state.active) { + removedActiveDescriptors.push(viewDescriptorItem.viewDescriptor); + } + if (this.isViewDescriptorVisible(viewDescriptorItem)) { + const { visibleIndex } = this.find(viewDescriptorItem.viewDescriptor.id); + removedVisibleItems.push({ index: visibleIndex, viewDescriptor: viewDescriptorItem.viewDescriptor }); + } + removedItems.push(viewDescriptorItem); + } + } + + removedItems.forEach(item => this.viewDescriptorItems.splice(this.viewDescriptorItems.indexOf(item), 1)); + + this._onDidChangeAllViewDescriptors.fire({ added: [], removed }); + if (removedActiveDescriptors.length) { + this._onDidChangeActiveViewDescriptors.fire(({ added: [], removed: removedActiveDescriptors })); + } + if (removedVisibleItems.length) { + this._onDidRemoveVisibleViewDescriptors.fire(removedVisibleItems); + } + } + + private onDidChangeContext(): void { + const addedActiveItems: { item: IViewDescriptorItem, wasVisible: boolean }[] = []; + const removedActiveItems: { item: IViewDescriptorItem, wasVisible: boolean }[] = []; + const removedVisibleItems: { index: number, viewDescriptor: IViewDescriptor; }[] = []; + const addedVisibleItems: { index: number, viewDescriptor: IViewDescriptor, size?: number, collapsed: boolean; }[] = []; + + for (const item of this.viewDescriptorItems) { + const wasActive = item.state.active; + const wasVisible = this.isViewDescriptorVisible(item); + const isActive = this.contextKeyService.contextMatchesRules(item.viewDescriptor.when); + if (wasActive !== isActive) { + if (isActive) { + addedActiveItems.push({ item, wasVisible }); + } else { + removedActiveItems.push({ item, wasVisible }); + } + } + } + + for (const { item, wasVisible } of removedActiveItems) { + if (wasVisible) { + const { visibleIndex } = this.find(item.viewDescriptor.id); + removedVisibleItems.push({ index: visibleIndex, viewDescriptor: item.viewDescriptor }); + } + } + + // Update the State + removedActiveItems.forEach(({ item }) => item.state.active = false); + addedActiveItems.forEach(({ item }) => item.state.active = true); + + for (const { item, wasVisible } of addedActiveItems) { + if (wasVisible !== this.isViewDescriptorVisibleWhenActive(item)) { + const { visibleIndex } = this.find(item.viewDescriptor.id); + addedVisibleItems.push({ index: visibleIndex, viewDescriptor: item.viewDescriptor, size: item.state.size, collapsed: !!item.state.collapsed }); + } + } + + if (addedActiveItems.length || removedActiveItems.length) { + this._onDidChangeActiveViewDescriptors.fire(({ added: addedActiveItems.map(({ item }) => item.viewDescriptor), removed: removedActiveItems.map(({ item }) => item.viewDescriptor) })); + } + if (removedVisibleItems.length) { + this._onDidRemoveVisibleViewDescriptors.fire(removedVisibleItems); + } + if (addedVisibleItems.length) { + this._onDidAddVisibleViewDescriptors.fire(addedVisibleItems); + } + } + + private isViewDescriptorVisible(viewDescriptorItem: IViewDescriptorItem): boolean { + if (!viewDescriptorItem.state.active) { + return false; + } + return this.isViewDescriptorVisibleWhenActive(viewDescriptorItem); + } + + private isViewDescriptorVisibleWhenActive(viewDescriptorItem: IViewDescriptorItem): boolean { + if (viewDescriptorItem.viewDescriptor.workspace) { + return !!viewDescriptorItem.state.visibleWorkspace; + } + return !!viewDescriptorItem.state.visibleGlobal; + } + + private find(id: string): { index: number, visibleIndex: number, viewDescriptorItem: IViewDescriptorItem; } { + for (let i = 0, visibleIndex = 0; i < this.viewDescriptorItems.length; i++) { + const viewDescriptorItem = this.viewDescriptorItems[i]; + if (viewDescriptorItem.viewDescriptor.id === id) { + return { index: i, visibleIndex, viewDescriptorItem: viewDescriptorItem }; + } + if (this.isViewDescriptorVisible(viewDescriptorItem)) { + visibleIndex++; + } + } + throw new Error(`view descriptor ${id} not found`); + } + + private compareViewDescriptors(a: IViewDescriptorItem, b: IViewDescriptorItem): number { + if (a.viewDescriptor.id === b.viewDescriptor.id) { + return 0; + } + + return (this.getViewOrder(a) - this.getViewOrder(b)) || this.getGroupOrderResult(a.viewDescriptor, b.viewDescriptor); + } + + private getViewOrder(viewDescriptorItem: IViewDescriptorItem): number { + const viewOrder = typeof viewDescriptorItem.state.order === 'number' ? viewDescriptorItem.state.order : viewDescriptorItem.viewDescriptor.order; + return typeof viewOrder === 'number' ? viewOrder : Number.MAX_VALUE; + } + + private getGroupOrderResult(a: IViewDescriptor, b: IViewDescriptor) { + if (!a.group || !b.group) { + return 0; + } + + if (a.group === b.group) { + return 0; + } + + return a.group < b.group ? -1 : 1; + } } interface ICachedViewContainerInfo { @@ -99,7 +677,7 @@ export class ViewDescriptorService extends Disposable implements IViewDescriptor private readonly _onDidChangeLocation: Emitter<{ views: IViewDescriptor[], from: ViewContainerLocation, to: ViewContainerLocation }> = this._register(new Emitter<{ views: IViewDescriptor[], from: ViewContainerLocation, to: ViewContainerLocation }>()); readonly onDidChangeLocation: Event<{ views: IViewDescriptor[], from: ViewContainerLocation, to: ViewContainerLocation }> = this._onDidChangeLocation.event; - private readonly viewDescriptorCollections: Map; + private readonly viewContainerModels: Map; private readonly activeViewContextKeys: Map>; private readonly movableViewContextKeys: Map>; private readonly defaultViewLocationContextKeys: Map>; @@ -136,7 +714,7 @@ export class ViewDescriptorService extends Disposable implements IViewDescriptor super(); storageKeysSyncRegistryService.registerStorageKey({ key: ViewDescriptorService.CACHED_VIEW_POSITIONS, version: 1 }); - this.viewDescriptorCollections = new Map(); + this.viewContainerModels = new Map(); this.activeViewContextKeys = new Map>(); this.movableViewContextKeys = new Map>(); this.defaultViewLocationContextKeys = new Map>(); @@ -160,8 +738,8 @@ export class ViewDescriptorService extends Disposable implements IViewDescriptor this._register(this.viewContainersRegistry.onDidRegister(({ viewContainer }) => this.onDidRegisterViewContainer(viewContainer))); this._register(this.viewContainersRegistry.onDidDeregister(({ viewContainer }) => this.onDidDeregisterViewContainer(viewContainer))); this._register(toDisposable(() => { - this.viewDescriptorCollections.forEach(({ disposable }) => disposable.dispose()); - this.viewDescriptorCollections.clear(); + this.viewContainerModels.forEach(({ disposable }) => disposable.dispose()); + this.viewContainerModels.clear(); })); this._register(this.storageService.onDidChangeStorage((e) => { this.onDidStorageChange(e); })); @@ -176,7 +754,7 @@ export class ViewDescriptorService extends Disposable implements IViewDescriptor const containerData = groupedViews.get(containerId)!; // The container has not been registered yet - if (!viewContainer || !this.viewDescriptorCollections.has(viewContainer)) { + if (!viewContainer || !this.viewContainerModels.has(viewContainer)) { if (containerData.cachedContainerInfo && this.shouldGenerateContainer(containerData.cachedContainerInfo)) { const containerInfo = containerData.cachedContainerInfo; @@ -198,7 +776,7 @@ export class ViewDescriptorService extends Disposable implements IViewDescriptor const viewContainer = this.viewContainersRegistry.get(viewContainerId); // The container has not been registered yet - if (!viewContainer || !this.viewDescriptorCollections.has(viewContainer)) { + if (!viewContainer || !this.viewContainerModels.has(viewContainer)) { continue; } @@ -321,8 +899,8 @@ export class ViewDescriptorService extends Disposable implements IViewDescriptor return this.viewsRegistry.getViewContainer(viewId) ?? null; } - getViewDescriptors(container: ViewContainer): ViewDescriptorCollection { - return this.getOrRegisterViewDescriptorCollection(container); + getViewContainerModel(container: ViewContainer): ViewContainerModel { + return this.getOrRegisterViewContainerModel(container); } moveViewToLocation(view: IViewDescriptor, location: ViewContainerLocation): void { @@ -456,8 +1034,8 @@ export class ViewDescriptorService extends Disposable implements IViewDescriptor // If a value is not present in the cache, it must be reset to default this.viewContainersRegistry.all.forEach(viewContainer => { - const viewDescriptorCollection = this.getViewDescriptors(viewContainer); - viewDescriptorCollection.allViewDescriptors.forEach(viewDescriptor => { + const viewContainerModel = this.getViewContainerModel(viewContainer); + viewContainerModel.allViewDescriptors.forEach(viewDescriptor => { if (!newCachedPositions.has(viewDescriptor.id)) { const currentContainer = this.getViewContainer(viewDescriptor.id); const defaultContainer = this.getDefaultContainer(viewDescriptor.id); @@ -492,8 +1070,8 @@ export class ViewDescriptorService extends Disposable implements IViewDescriptor private saveViewPositionsToCache(): void { this.viewContainersRegistry.all.forEach(viewContainer => { - const viewDescriptorCollection = this.getViewDescriptors(viewContainer); - viewDescriptorCollection.allViewDescriptors.forEach(viewDescriptor => { + const viewContainerModel = this.getViewContainerModel(viewContainer); + viewContainerModel.allViewDescriptors.forEach(viewDescriptor => { const containerLocation = this.getViewContainerLocation(viewContainer); this.cachedViewInfo.set(viewDescriptor.id, { containerId: viewContainer.id, @@ -540,20 +1118,20 @@ export class ViewDescriptorService extends Disposable implements IViewDescriptor } private onDidRegisterViewContainer(viewContainer: ViewContainer): void { - this.getOrRegisterViewDescriptorCollection(viewContainer); + this.getOrRegisterViewContainerModel(viewContainer); } - private getOrRegisterViewDescriptorCollection(viewContainer: ViewContainer): ViewDescriptorCollection { - let viewDescriptorCollection = this.viewDescriptorCollections.get(viewContainer)?.viewDescriptorCollection; + private getOrRegisterViewContainerModel(viewContainer: ViewContainer): ViewContainerModel { + let viewContainerModel = this.viewContainerModels.get(viewContainer)?.viewContainerModel; - if (!viewDescriptorCollection) { + if (!viewContainerModel) { const disposables = new DisposableStore(); - viewDescriptorCollection = disposables.add(this.instantiationService.createInstance(ViewDescriptorCollection, viewContainer)); + viewContainerModel = disposables.add(this.instantiationService.createInstance(ViewContainerModel, viewContainer)); - this.onDidChangeActiveViews({ added: viewDescriptorCollection.activeViewDescriptors, removed: [] }); - viewDescriptorCollection._onDidChangeActiveViews(changed => this.onDidChangeActiveViews(changed), this, disposables); + this.onDidChangeActiveViews({ added: viewContainerModel.activeViewDescriptors, removed: [] }); + viewContainerModel.onDidChangeActiveViewDescriptors(changed => this.onDidChangeActiveViews(changed), this, disposables); - this.viewDescriptorCollections.set(viewContainer, { viewDescriptorCollection, disposable: disposables }); + this.viewContainerModels.set(viewContainer, { viewContainerModel: viewContainerModel, disposable: disposables }); const viewsToRegister = this.getViewsByContainer(viewContainer); if (viewsToRegister.length) { @@ -562,18 +1140,18 @@ export class ViewDescriptorService extends Disposable implements IViewDescriptor } } - return viewDescriptorCollection; + return viewContainerModel; } private onDidDeregisterViewContainer(viewContainer: ViewContainer): void { - const viewDescriptorCollectionItem = this.viewDescriptorCollections.get(viewContainer); - if (viewDescriptorCollectionItem) { - viewDescriptorCollectionItem.disposable.dispose(); - this.viewDescriptorCollections.delete(viewContainer); + const viewContainerModelItem = this.viewContainerModels.get(viewContainer); + if (viewContainerModelItem) { + viewContainerModelItem.disposable.dispose(); + this.viewContainerModels.delete(viewContainer); } } - private onDidChangeActiveViews({ added, removed }: { added: IViewDescriptor[], removed: IViewDescriptor[]; }): void { + private onDidChangeActiveViews({ added, removed }: { added: ReadonlyArray, removed: ReadonlyArray; }): void { added.forEach(viewDescriptor => this.getOrCreateActiveViewContextKey(viewDescriptor).set(true)); removed.forEach(viewDescriptor => this.getOrCreateActiveViewContextKey(viewDescriptor).set(false)); } @@ -586,7 +1164,7 @@ export class ViewDescriptorService extends Disposable implements IViewDescriptor this.getOrCreateDefaultViewLocationContextKey(view).set(this.getDefaultContainer(view.id) === container); }); - this.getViewDescriptors(container).addViews(views); + this.getViewContainerModel(container).add(views); } private removeViews(container: ViewContainer, views: IViewDescriptor[]): void { @@ -594,7 +1172,7 @@ export class ViewDescriptorService extends Disposable implements IViewDescriptor views.forEach(view => this.getOrCreateDefaultViewLocationContextKey(view).set(false)); // Remove the views - this.getViewDescriptors(container).removeViews(views); + this.getViewContainerModel(container).remove(views); } private getOrCreateActiveViewContextKey(viewDescriptor: IViewDescriptor): IContextKey { diff --git a/src/vs/workbench/services/views/common/viewsModel.ts b/src/vs/workbench/services/views/common/viewsModel.ts deleted file mode 100644 index e3fb06acfac..00000000000 --- a/src/vs/workbench/services/views/common/viewsModel.ts +++ /dev/null @@ -1,648 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { Disposable } from 'vs/base/common/lifecycle'; -import { IViewDescriptor, IAddedViewDescriptorRef, IViewDescriptorRef } from 'vs/workbench/common/views'; -import { IStorageService, StorageScope, IWorkspaceStorageChangeEvent } from 'vs/platform/storage/common/storage'; -import { Event, Emitter } from 'vs/base/common/event'; -import { firstIndex, move } from 'vs/base/common/arrays'; -import { isUndefinedOrNull, isUndefined } from 'vs/base/common/types'; -import { values } from 'vs/base/common/map'; -import { IStorageKeysSyncRegistryService } from 'vs/platform/userDataSync/common/storageKeys'; -import { IContextKeyChangeEvent, IContextKeyService, IReadableSet } from 'vs/platform/contextkey/common/contextkey'; - -class CounterSet implements IReadableSet { - - private map = new Map(); - - add(value: T): CounterSet { - this.map.set(value, (this.map.get(value) || 0) + 1); - return this; - } - - delete(value: T): boolean { - let counter = this.map.get(value) || 0; - - if (counter === 0) { - return false; - } - - counter--; - - if (counter === 0) { - this.map.delete(value); - } else { - this.map.set(value, counter); - } - - return true; - } - - has(value: T): boolean { - return this.map.has(value); - } -} - -interface IViewItem { - viewDescriptor: IViewDescriptor; - active: boolean; -} - -export class ViewDescriptorsModel extends Disposable { - - private contextKeys = new CounterSet(); - private items: IViewItem[] = []; - - private _onDidChangeViews: Emitter<{ added: IViewDescriptor[], removed: IViewDescriptor[]; }> = this._register(new Emitter<{ added: IViewDescriptor[], removed: IViewDescriptor[]; }>()); - readonly onDidChangeViews: Event<{ added: IViewDescriptor[], removed: IViewDescriptor[]; }> = this._onDidChangeViews.event; - - private _onDidChangeActiveViews: Emitter<{ added: IViewDescriptor[], removed: IViewDescriptor[]; }> = this._register(new Emitter<{ added: IViewDescriptor[], removed: IViewDescriptor[]; }>()); - readonly onDidChangeActiveViews: Event<{ added: IViewDescriptor[], removed: IViewDescriptor[]; }> = this._onDidChangeActiveViews.event; - - get activeViewDescriptors(): IViewDescriptor[] { - return this.items - .filter(i => i.active) - .map(i => i.viewDescriptor); - } - - get allViewDescriptors(): IViewDescriptor[] { - return this.items.map(i => i.viewDescriptor); - } - - constructor( - @IContextKeyService private readonly contextKeyService: IContextKeyService, - ) { - super(); - this._register(Event.filter(contextKeyService.onDidChangeContext, e => e.affectsSome(this.contextKeys))(this.onContextChanged, this)); - } - - addViews(viewDescriptors: IViewDescriptor[]): void { - const added: IViewDescriptor[] = []; - - for (const viewDescriptor of viewDescriptors) { - const item = { - viewDescriptor, - active: this.isViewDescriptorActive(viewDescriptor) // TODO: should read from some state? - }; - - this.items.push(item); - - if (viewDescriptor.when) { - for (const key of viewDescriptor.when.keys()) { - this.contextKeys.add(key); - } - } - - if (item.active) { - added.push(viewDescriptor); - } - } - - this._onDidChangeViews.fire({ added: viewDescriptors, removed: [] }); - - if (added.length) { - this._onDidChangeActiveViews.fire({ added, removed: [] }); - } - } - - removeViews(viewDescriptors: IViewDescriptor[]): void { - const removed: IViewDescriptor[] = []; - - for (const viewDescriptor of viewDescriptors) { - const index = firstIndex(this.items, i => i.viewDescriptor.id === viewDescriptor.id); - - if (index === -1) { - continue; - } - - const item = this.items[index]; - this.items.splice(index, 1); - - if (viewDescriptor.when) { - for (const key of viewDescriptor.when.keys()) { - this.contextKeys.delete(key); - } - } - - if (item.active) { - removed.push(viewDescriptor); - } - } - - this._onDidChangeViews.fire({ added: [], removed: viewDescriptors }); - - if (removed.length) { - this._onDidChangeActiveViews.fire({ added: [], removed }); - } - } - - private onContextChanged(event: IContextKeyChangeEvent): void { - const removed: IViewDescriptor[] = []; - const added: IViewDescriptor[] = []; - - for (const item of this.items) { - const active = this.isViewDescriptorActive(item.viewDescriptor); - - if (item.active !== active) { - if (active) { - added.push(item.viewDescriptor); - } else { - removed.push(item.viewDescriptor); - } - } - - item.active = active; - } - - if (added.length || removed.length) { - this._onDidChangeActiveViews.fire({ added, removed }); - } - } - - private isViewDescriptorActive(viewDescriptor: IViewDescriptor): boolean { - return !viewDescriptor.when || this.contextKeyService.contextMatchesRules(viewDescriptor.when); - } -} - -export interface IViewState { - visibleGlobal: boolean | undefined; - visibleWorkspace: boolean | undefined; - collapsed: boolean | undefined; - order?: number; - size?: number; -} - -export class ViewsModel extends Disposable { - - private _viewDescriptors: IViewDescriptor[] = []; - get viewDescriptors(): ReadonlyArray { - return this._viewDescriptors; - } - - get visibleViewDescriptors(): IViewDescriptor[] { - return this.viewDescriptors.filter(v => this.isViewDescriptorVisible(v)).sort((a, b) => { - const aIndex = this.viewStates.get(a.id)?.order; - const bIndex = this.viewStates.get(b.id)?.order; - - if (aIndex === undefined) { - return 1; - } - - if (bIndex === undefined) { - return -1; - } - - return aIndex - bIndex; - }); - } - - private _onDidAdd = this._register(new Emitter()); - readonly onDidAdd: Event = this._onDidAdd.event; - - private _onDidRemove = this._register(new Emitter()); - readonly onDidRemove: Event = this._onDidRemove.event; - - private _onDidMove = this._register(new Emitter<{ from: IViewDescriptorRef; to: IViewDescriptorRef; }>()); - readonly onDidMove: Event<{ from: IViewDescriptorRef; to: IViewDescriptorRef; }> = this._onDidMove.event; - - private _onDidChangeViewState = this._register(new Emitter()); - protected readonly onDidChangeViewState: Event = this._onDidChangeViewState.event; - - private _onDidChangeActiveViews = this._register(new Emitter>()); - readonly onDidChangeActiveViews: Event> = this._onDidChangeActiveViews.event; - - constructor( - viewDescriptorCollection: ViewDescriptorsModel, - protected viewStates = new Map(), - ) { - super(); - this._register(viewDescriptorCollection.onDidChangeActiveViews(() => this.onDidChangeViewDescriptors(viewDescriptorCollection.activeViewDescriptors))); - this.onDidChangeViewDescriptors(viewDescriptorCollection.activeViewDescriptors); - } - - isVisible(id: string): boolean { - const viewDescriptor = this.viewDescriptors.filter(v => v.id === id)[0]; - - if (!viewDescriptor) { - throw new Error(`Unknown view ${id}`); - } - - return this.isViewDescriptorVisible(viewDescriptor); - } - - setVisible(id: string, visible: boolean, size?: number): void { - this.doSetVisible([{ id, visible, size }]); - } - - protected doSetVisible(viewDescriptors: { id: string, visible: boolean, size?: number }[]): void { - const added: IAddedViewDescriptorRef[] = []; - const removed: IViewDescriptorRef[] = []; - - for (const { visibleIndex, viewDescriptor, state, visible, size } of viewDescriptors.map(({ id, visible, size }) => ({ ...this.find(id), visible, size }))) { - - if (!viewDescriptor.canToggleVisibility) { - throw new Error(`Can't toggle this view's visibility`); - } - - if (this.isViewDescriptorVisible(viewDescriptor) === visible) { - return; - } - - if (viewDescriptor.workspace) { - state.visibleWorkspace = visible; - } else { - state.visibleGlobal = visible; - } - - if (typeof size === 'number') { - state.size = size; - } - - if (visible) { - added.push({ index: visibleIndex, viewDescriptor, size: state.size, collapsed: !!state.collapsed }); - } else { - removed.push({ index: visibleIndex, viewDescriptor }); - } - } - - if (added.length) { - this._onDidAdd.fire(added); - } - if (removed.length) { - this._onDidRemove.fire(removed); - } - } - - isCollapsed(id: string): boolean { - const state = this.viewStates.get(id); - - if (!state) { - throw new Error(`Unknown view ${id}`); - } - - return !!state.collapsed; - } - - setCollapsed(id: string, collapsed: boolean): void { - const { index, state, viewDescriptor } = this.find(id); - if (state.collapsed !== collapsed) { - state.collapsed = collapsed; - this._onDidChangeViewState.fire({ viewDescriptor, index }); - } - } - - getSize(id: string): number | undefined { - const state = this.viewStates.get(id); - - if (!state) { - throw new Error(`Unknown view ${id}`); - } - - return state.size; - } - - setSize(id: string, size: number): void { - const { index, state, viewDescriptor } = this.find(id); - if (state.size !== size) { - state.size = size; - this._onDidChangeViewState.fire({ viewDescriptor, index }); - } - } - - move(from: string, to: string): void { - const fromIndex = firstIndex(this.viewDescriptors, v => v.id === from); - const toIndex = firstIndex(this.viewDescriptors, v => v.id === to); - - const fromViewDescriptor = this.viewDescriptors[fromIndex]; - const toViewDescriptor = this.viewDescriptors[toIndex]; - - move(this._viewDescriptors, fromIndex, toIndex); - - for (let index = 0; index < this.viewDescriptors.length; index++) { - const state = this.viewStates.get(this.viewDescriptors[index].id)!; - state.order = index; - } - - this._onDidMove.fire({ - from: { index: fromIndex, viewDescriptor: fromViewDescriptor }, - to: { index: toIndex, viewDescriptor: toViewDescriptor } - }); - } - - private isViewDescriptorVisible(viewDescriptor: IViewDescriptor): boolean { - const viewState = this.viewStates.get(viewDescriptor.id); - if (!viewState) { - throw new Error(`Unknown view ${viewDescriptor.id}`); - } - return viewDescriptor.workspace ? !!viewState.visibleWorkspace : !!viewState.visibleGlobal; - } - - private find(id: string): { index: number, visibleIndex: number, viewDescriptor: IViewDescriptor, state: IViewState; } { - for (let i = 0, visibleIndex = 0; i < this.viewDescriptors.length; i++) { - const viewDescriptor = this.viewDescriptors[i]; - const state = this.viewStates.get(viewDescriptor.id); - if (!state) { - throw new Error(`View state for ${id} not found`); - } - - if (viewDescriptor.id === id) { - return { index: i, visibleIndex, viewDescriptor, state }; - } - - if (viewDescriptor.workspace ? state.visibleWorkspace : state.visibleGlobal) { - visibleIndex++; - } - } - - throw new Error(`view descriptor ${id} not found`); - } - - private compareViewDescriptors(a: IViewDescriptor, b: IViewDescriptor): number { - if (a.id === b.id) { - return 0; - } - - return (this.getViewOrder(a) - this.getViewOrder(b)) || this.getGroupOrderResult(a, b); - } - - private getGroupOrderResult(a: IViewDescriptor, b: IViewDescriptor) { - if (!a.group || !b.group) { - return 0; - } - - if (a.group === b.group) { - return 0; - } - - return a.group < b.group ? -1 : 1; - } - - private getViewOrder(viewDescriptor: IViewDescriptor): number { - const viewState = this.viewStates.get(viewDescriptor.id); - const viewOrder = viewState && typeof viewState.order === 'number' ? viewState.order : viewDescriptor.order; - return typeof viewOrder === 'number' ? viewOrder : Number.MAX_VALUE; - } - - private onDidChangeViewDescriptors(viewDescriptors: IViewDescriptor[]): void { - for (const viewDescriptor of viewDescriptors) { - const viewState = this.viewStates.get(viewDescriptor.id); - if (viewState) { - // set defaults if not set - if (viewDescriptor.workspace) { - viewState.visibleWorkspace = isUndefinedOrNull(viewState.visibleWorkspace) ? !viewDescriptor.hideByDefault : viewState.visibleWorkspace; - } else { - viewState.visibleGlobal = isUndefinedOrNull(viewState.visibleGlobal) ? !viewDescriptor.hideByDefault : viewState.visibleGlobal; - } - viewState.collapsed = isUndefinedOrNull(viewState.collapsed) ? !!viewDescriptor.collapsed : viewState.collapsed; - } else { - this.viewStates.set(viewDescriptor.id, { - visibleGlobal: !viewDescriptor.hideByDefault, - visibleWorkspace: !viewDescriptor.hideByDefault, - collapsed: !!viewDescriptor.collapsed - }); - } - } - - viewDescriptors = viewDescriptors.sort(this.compareViewDescriptors.bind(this)); - - const toRemove: { index: number, viewDescriptor: IViewDescriptor; }[] = []; - for (let index = 0; index < this._viewDescriptors.length; index++) { - const previousViewDescriptor = this._viewDescriptors[index]; - if (this.isViewDescriptorVisible(previousViewDescriptor) && viewDescriptors.every(viewDescriptor => viewDescriptor.id !== previousViewDescriptor.id)) { - const { visibleIndex } = this.find(previousViewDescriptor.id); - toRemove.push({ index: visibleIndex, viewDescriptor: previousViewDescriptor }); - } - } - - const previous = this._viewDescriptors; - this._viewDescriptors = viewDescriptors.slice(0); - - const toAdd: { index: number, viewDescriptor: IViewDescriptor, size?: number, collapsed: boolean; }[] = []; - for (let i = 0; i < this._viewDescriptors.length; i++) { - const viewDescriptor = this._viewDescriptors[i]; - if (this.isViewDescriptorVisible(viewDescriptor) && previous.every(previousViewDescriptor => previousViewDescriptor.id !== viewDescriptor.id)) { - const { visibleIndex, state } = this.find(viewDescriptor.id); - toAdd.push({ index: visibleIndex, viewDescriptor, size: state.size, collapsed: !!state.collapsed }); - } - } - - if (toRemove.length) { - this._onDidRemove.fire(toRemove); - } - - if (toAdd.length) { - this._onDidAdd.fire(toAdd); - } - - this._onDidChangeActiveViews.fire(this.viewDescriptors); - } -} - -interface IStoredWorkspaceViewState { - collapsed: boolean; - isHidden: boolean; - size?: number; - order?: number; -} - -interface IStoredGlobalViewState { - id: string; - isHidden: boolean; - order?: number; -} - -export class PersistentViewsModel extends ViewsModel { - - private readonly workspaceViewsStateStorageId: string; - private readonly globalViewsStateStorageId: string; - - private storageService: IStorageService; - - constructor( - viewletStateStorageId: string, - viewDescriptorCollection: ViewDescriptorsModel, - @IStorageService storageService: IStorageService, - @IStorageKeysSyncRegistryService storageKeysSyncRegistryService: IStorageKeysSyncRegistryService - ) { - const globalViewsStateStorageId = `${viewletStateStorageId}.hidden`; - storageKeysSyncRegistryService.registerStorageKey({ key: globalViewsStateStorageId, version: 1 }); - const viewStates = PersistentViewsModel.loadViewsStates(viewletStateStorageId, globalViewsStateStorageId, storageService); - - super(viewDescriptorCollection, viewStates); - - this.storageService = storageService; - this.workspaceViewsStateStorageId = viewletStateStorageId; - this.globalViewsStateStorageId = globalViewsStateStorageId; - - this._register(Event.any( - this.onDidAdd, - this.onDidRemove, - Event.map(this.onDidMove, ({ from, to }) => [from, to]), - Event.map(this.onDidChangeViewState, viewDescriptorRef => [viewDescriptorRef])) - (viewDescriptorRefs => this.saveViewsStates())); - - this._globalViewsStatesValue = this.getStoredGlobalViewsStatesValue(); - this._register(this.storageService.onDidChangeStorage(e => this.onDidStorageChange(e))); - } - - private onDidStorageChange(e: IWorkspaceStorageChangeEvent): void { - if (e.key === this.globalViewsStateStorageId && e.scope === StorageScope.GLOBAL - && this.globalViewsStatesValue !== this.getStoredGlobalViewsStatesValue() /* This checks if current window changed the value or not */) { - this._globalViewsStatesValue = undefined; - const storedViewsVisibilityStates = PersistentViewsModel.loadGlobalViewsState(this.globalViewsStateStorageId, this.storageService, StorageScope.GLOBAL); - const changedViews: { id: string, visible: boolean }[] = []; - for (const [id, state] of storedViewsVisibilityStates) { - const viewState = this.viewStates.get(id); - if (viewState) { - if (viewState.visibleGlobal !== !state.isHidden) { - changedViews.push({ id, visible: !state.isHidden }); - } - } - } - if (changedViews.length) { - this.doSetVisible(changedViews); - } - } - } - - private saveViewsStates(): void { - this.saveWorkspaceViewsStates(); - this.saveGlobalViewsStates(); - } - - private saveWorkspaceViewsStates(): void { - const storedViewsStates: { [id: string]: IStoredWorkspaceViewState; } = JSON.parse(this.storageService.get(this.workspaceViewsStateStorageId, StorageScope.WORKSPACE, '{}')); - for (const viewDescriptor of this.viewDescriptors) { - const viewState = this.viewStates.get(viewDescriptor.id); - if (viewState) { - storedViewsStates[viewDescriptor.id] = { - collapsed: !!viewState.collapsed, - isHidden: !viewState.visibleWorkspace, - size: viewState.size, - order: viewDescriptor.workspace && viewState ? viewState.order : undefined - }; - } - } - - if (Object.keys(storedViewsStates).length > 0) { - this.storageService.store(this.workspaceViewsStateStorageId, JSON.stringify(storedViewsStates), StorageScope.WORKSPACE); - } else { - this.storageService.remove(this.workspaceViewsStateStorageId, StorageScope.WORKSPACE); - } - } - - private saveGlobalViewsStates(): void { - const storedViewsVisibilityStates = PersistentViewsModel.loadGlobalViewsState(this.globalViewsStateStorageId, this.storageService, StorageScope.GLOBAL); - for (const viewDescriptor of this.viewDescriptors) { - const viewState = this.viewStates.get(viewDescriptor.id); - storedViewsVisibilityStates.set(viewDescriptor.id, { - id: viewDescriptor.id, - isHidden: viewState && viewDescriptor.canToggleVisibility ? !viewState.visibleGlobal : false, - order: !viewDescriptor.workspace && viewState ? viewState.order : undefined - }); - } - this.globalViewsStatesValue = JSON.stringify(values(storedViewsVisibilityStates)); - } - - private _globalViewsStatesValue: string | undefined; - private get globalViewsStatesValue(): string { - if (!this._globalViewsStatesValue) { - this._globalViewsStatesValue = this.getStoredGlobalViewsStatesValue(); - } - - return this._globalViewsStatesValue; - } - - private set globalViewsStatesValue(globalViewsStatesValue: string) { - if (this.globalViewsStatesValue !== globalViewsStatesValue) { - this._globalViewsStatesValue = globalViewsStatesValue; - this.setStoredGlobalViewsStatesValue(globalViewsStatesValue); - } - } - - private getStoredGlobalViewsStatesValue(): string { - return this.storageService.get(this.globalViewsStateStorageId, StorageScope.GLOBAL, '[]'); - } - - private setStoredGlobalViewsStatesValue(value: string): void { - this.storageService.store(this.globalViewsStateStorageId, value, StorageScope.GLOBAL); - } - - private static loadViewsStates(workspaceViewsStateStorageId: string, globalViewsStateStorageId: string, storageService: IStorageService): Map { - const viewStates = new Map(); - const workspaceViewsStates = <{ [id: string]: IStoredWorkspaceViewState; }>JSON.parse(storageService.get(workspaceViewsStateStorageId, StorageScope.WORKSPACE, '{}')); - for (const id of Object.keys(workspaceViewsStates)) { - const workspaceViewState = workspaceViewsStates[id]; - viewStates.set(id, { - visibleGlobal: undefined, - visibleWorkspace: isUndefined(workspaceViewState.isHidden) ? undefined : !workspaceViewState.isHidden, - collapsed: workspaceViewState.collapsed, - order: workspaceViewState.order, - size: workspaceViewState.size - }); - } - - // Migrate to `viewletStateStorageId` - const workspaceVisibilityStates = this.loadGlobalViewsState(globalViewsStateStorageId, storageService, StorageScope.WORKSPACE); - if (workspaceVisibilityStates.size > 0) { - for (const { id, isHidden } of values(workspaceVisibilityStates)) { - let viewState = viewStates.get(id); - // Not migrated to `viewletStateStorageId` - if (viewState) { - if (isUndefined(viewState.visibleWorkspace)) { - viewState.visibleWorkspace = !isHidden; - } - } else { - viewStates.set(id, { - collapsed: undefined, - visibleGlobal: undefined, - visibleWorkspace: !isHidden, - }); - } - } - storageService.remove(globalViewsStateStorageId, StorageScope.WORKSPACE); - } - - const globalViewsStates = this.loadGlobalViewsState(globalViewsStateStorageId, storageService, StorageScope.GLOBAL); - for (const { id, isHidden, order } of values(globalViewsStates)) { - let viewState = viewStates.get(id); - if (viewState) { - viewState.visibleGlobal = !isHidden; - if (!isUndefined(order)) { - viewState.order = order; - } - } else { - viewStates.set(id, { - visibleGlobal: !isHidden, - order, - collapsed: undefined, - visibleWorkspace: undefined, - }); - } - } - return viewStates; - } - - private static loadGlobalViewsState(globalViewsStateStorageId: string, storageService: IStorageService, scope: StorageScope): Map { - const storedValue = >JSON.parse(storageService.get(globalViewsStateStorageId, scope, '[]')); - let hasDuplicates = false; - const storedGlobalViewsState = storedValue.reduce((result, storedState) => { - if (typeof storedState === 'string' /* migration */) { - hasDuplicates = hasDuplicates || result.has(storedState); - result.set(storedState, { id: storedState, isHidden: true }); - } else { - hasDuplicates = hasDuplicates || result.has(storedState.id); - result.set(storedState.id, storedState); - } - return result; - }, new Map()); - - if (hasDuplicates) { - storageService.store(globalViewsStateStorageId, JSON.stringify(values(storedGlobalViewsState)), scope); - } - - return storedGlobalViewsState; - } -} diff --git a/src/vs/workbench/services/views/test/browser/viewDescriptorService.test.ts b/src/vs/workbench/services/views/test/browser/viewDescriptorService.test.ts index ef72f7846c5..7b2f470bc49 100644 --- a/src/vs/workbench/services/views/test/browser/viewDescriptorService.test.ts +++ b/src/vs/workbench/services/views/test/browser/viewDescriptorService.test.ts @@ -11,6 +11,8 @@ import { TestInstantiationService } from 'vs/platform/instantiation/test/common/ import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { ViewDescriptorService } from 'vs/workbench/services/views/browser/viewDescriptorService'; import { assertIsDefined } from 'vs/base/common/types'; +import { ContextKeyService } from 'vs/platform/contextkey/browser/contextKeyService'; +import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; const ViewsRegistry = Registry.as(ViewContainerExtensions.ViewsRegistry); const sidebarContainer = Registry.as(ViewContainerExtensions.ViewContainersRegistry).registerViewContainer({ id: 'testSidebar', name: 'test', ctorDescriptor: new SyncDescriptor({}) }, ViewContainerLocation.Sidebar); @@ -22,6 +24,7 @@ suite('ViewDescriptorService', () => { setup(() => { const instantiationService: TestInstantiationService = workbenchInstantiationService(); + instantiationService.stub(IContextKeyService, instantiationService.createInstance(ContextKeyService)); viewDescriptorService = instantiationService.createInstance(ViewDescriptorService); }); @@ -31,8 +34,8 @@ suite('ViewDescriptorService', () => { }); test('Empty Containers', function () { - const sidebarViews = viewDescriptorService.getViewDescriptors(sidebarContainer); - const panelViews = viewDescriptorService.getViewDescriptors(panelContainer); + const sidebarViews = viewDescriptorService.getViewContainerModel(sidebarContainer); + const panelViews = viewDescriptorService.getViewContainerModel(panelContainer); assert.equal(sidebarViews.allViewDescriptors.length, 0, 'The sidebar container should have no views yet.'); assert.equal(panelViews.allViewDescriptors.length, 0, 'The panel container should have no views yet.'); }); @@ -64,8 +67,8 @@ suite('ViewDescriptorService', () => { ViewsRegistry.registerViews(viewDescriptors.slice(2), panelContainer); - let sidebarViews = viewDescriptorService.getViewDescriptors(sidebarContainer); - let panelViews = viewDescriptorService.getViewDescriptors(panelContainer); + let sidebarViews = viewDescriptorService.getViewContainerModel(sidebarContainer); + let panelViews = viewDescriptorService.getViewContainerModel(panelContainer); assert.equal(sidebarViews.activeViewDescriptors.length, 2, 'Sidebar should have 2 views'); assert.equal(panelViews.activeViewDescriptors.length, 1, 'Panel should have 1 view'); @@ -74,8 +77,8 @@ suite('ViewDescriptorService', () => { ViewsRegistry.deregisterViews(viewDescriptors.slice(2), panelContainer); - sidebarViews = viewDescriptorService.getViewDescriptors(sidebarContainer); - panelViews = viewDescriptorService.getViewDescriptors(panelContainer); + sidebarViews = viewDescriptorService.getViewContainerModel(sidebarContainer); + panelViews = viewDescriptorService.getViewContainerModel(panelContainer); assert.equal(sidebarViews.activeViewDescriptors.length, 0, 'Sidebar should have no views'); assert.equal(panelViews.activeViewDescriptors.length, 0, 'Panel should have no views'); @@ -109,8 +112,8 @@ suite('ViewDescriptorService', () => { viewDescriptorService.moveViewsToContainer(viewDescriptors.slice(2), sidebarContainer); viewDescriptorService.moveViewsToContainer(viewDescriptors.slice(0, 2), panelContainer); - let sidebarViews = viewDescriptorService.getViewDescriptors(sidebarContainer); - let panelViews = viewDescriptorService.getViewDescriptors(panelContainer); + let sidebarViews = viewDescriptorService.getViewContainerModel(sidebarContainer); + let panelViews = viewDescriptorService.getViewContainerModel(panelContainer); assert.equal(sidebarViews.activeViewDescriptors.length, 1, 'Sidebar should have 2 views'); assert.equal(panelViews.activeViewDescriptors.length, 2, 'Panel should have 1 view'); @@ -148,8 +151,8 @@ suite('ViewDescriptorService', () => { viewDescriptorService.moveViewToLocation(viewDescriptors[0], ViewContainerLocation.Panel); viewDescriptorService.moveViewToLocation(viewDescriptors[2], ViewContainerLocation.Sidebar); - let sidebarViews = viewDescriptorService.getViewDescriptors(sidebarContainer); - let panelViews = viewDescriptorService.getViewDescriptors(panelContainer); + let sidebarViews = viewDescriptorService.getViewContainerModel(sidebarContainer); + let panelViews = viewDescriptorService.getViewContainerModel(panelContainer); assert.equal(sidebarViews.activeViewDescriptors.length, 1, 'Sidebar container should have 1 view'); assert.equal(panelViews.activeViewDescriptors.length, 0, 'Panel container should have no views'); @@ -169,8 +172,8 @@ suite('ViewDescriptorService', () => { viewDescriptorService.moveViewToLocation(viewDescriptors[0], ViewContainerLocation.Sidebar); viewDescriptorService.moveViewToLocation(viewDescriptors[2], ViewContainerLocation.Panel); - sidebarViews = viewDescriptorService.getViewDescriptors(sidebarContainer); - panelViews = viewDescriptorService.getViewDescriptors(panelContainer); + sidebarViews = viewDescriptorService.getViewContainerModel(sidebarContainer); + panelViews = viewDescriptorService.getViewContainerModel(panelContainer); assert.equal(sidebarViews.activeViewDescriptors.length, 1, 'Sidebar should have 2 views'); assert.equal(panelViews.activeViewDescriptors.length, 0, 'Panel should have 1 view'); diff --git a/src/vs/workbench/services/views/test/browser/viewsModel.test.ts b/src/vs/workbench/services/views/test/browser/viewsModel.test.ts index dd7e1c6c18f..e6d4fd83eab 100644 --- a/src/vs/workbench/services/views/test/browser/viewsModel.test.ts +++ b/src/vs/workbench/services/views/test/browser/viewsModel.test.ts @@ -5,25 +5,31 @@ import * as assert from 'assert'; import * as sinon from 'sinon'; -import { IViewDescriptor } from 'vs/workbench/common/views'; -import { IDisposable, dispose } from 'vs/base/common/lifecycle'; +import { IViewsRegistry, IViewDescriptor, IViewContainersRegistry, Extensions as ViewContainerExtensions, ViewContainerLocation, IViewContainerModel, IViewDescriptorService, ViewContainer } from 'vs/workbench/common/views'; +import { IDisposable, dispose, DisposableStore } from 'vs/base/common/lifecycle'; import { move } from 'vs/base/common/arrays'; import { workbenchInstantiationService } from 'vs/workbench/test/browser/workbenchTestServices'; import { ContextKeyExpr, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; import { ContextKeyService } from 'vs/platform/contextkey/browser/contextKeyService'; -import { IViewState, ViewDescriptorsModel, ViewsModel } from 'vs/workbench/services/views/common/viewsModel'; +import { ViewDescriptorService } from 'vs/workbench/services/views/browser/viewDescriptorService'; +import { Registry } from 'vs/platform/registry/common/platform'; +import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; +import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; + +const ViewContainerRegistry = Registry.as(ViewContainerExtensions.ViewContainersRegistry); +const ViewsRegistry = Registry.as(ViewContainerExtensions.ViewsRegistry); class ViewDescriptorSequence { readonly elements: IViewDescriptor[]; private disposables: IDisposable[] = []; - constructor(model: ViewsModel) { + constructor(model: IViewContainerModel) { this.elements = [...model.visibleViewDescriptors]; - model.onDidAdd(added => added.forEach(({ viewDescriptor, index }) => this.elements.splice(index, 0, viewDescriptor)), null, this.disposables); - model.onDidRemove(removed => removed.sort((a, b) => b.index - a.index).forEach(({ index }) => this.elements.splice(index, 1)), null, this.disposables); - model.onDidMove(({ from, to }) => move(this.elements, from.index, to.index), null, this.disposables); + model.onDidAddVisibleViewDescriptors(added => added.forEach(({ viewDescriptor, index }) => this.elements.splice(index, 0, viewDescriptor)), null, this.disposables); + model.onDidRemoveVisibleViewDescriptors(removed => removed.sort((a, b) => b.index - a.index).forEach(({ index }) => this.elements.splice(index, 1)), null, this.disposables); + model.onDidMoveVisibleViewDescriptors(({ from, to }) => move(this.elements, from.index, to.index), null, this.disposables); } dispose() { @@ -31,29 +37,42 @@ class ViewDescriptorSequence { } } -suite('ContributableViewsModel', () => { +suite('ViewContainerModel', () => { + let container: ViewContainer; + let disposableStore: DisposableStore; let contextKeyService: IContextKeyService; + let viewDescriptorService: IViewDescriptorService; + let storageService: IStorageService; setup(() => { + disposableStore = new DisposableStore(); const instantiationService: TestInstantiationService = workbenchInstantiationService(); contextKeyService = instantiationService.createInstance(ContextKeyService); instantiationService.stub(IContextKeyService, contextKeyService); + storageService = instantiationService.get(IStorageService); + viewDescriptorService = instantiationService.createInstance(ViewDescriptorService); + }); + + teardown(() => { + disposableStore.dispose(); + ViewsRegistry.deregisterViews(ViewsRegistry.getViews(container), container); + ViewContainerRegistry.deregisterViewContainer(container); }); test('empty model', function () { - const viewsDescriptorsModel = new ViewDescriptorsModel(contextKeyService); - const model = new ViewsModel(viewsDescriptorsModel); - assert.equal(model.visibleViewDescriptors.length, 0); + container = ViewContainerRegistry.registerViewContainer({ id: 'test', name: 'test', ctorDescriptor: new SyncDescriptor({}) }, ViewContainerLocation.Sidebar); + const testObject = viewDescriptorService.getViewContainerModel(container); + assert.equal(testObject.visibleViewDescriptors.length, 0); }); test('register/unregister', () => { - const viewsDescriptorsModel = new ViewDescriptorsModel(contextKeyService); - const model = new ViewsModel(viewsDescriptorsModel); - const seq = new ViewDescriptorSequence(model); + container = ViewContainerRegistry.registerViewContainer({ id: 'test', name: 'test', ctorDescriptor: new SyncDescriptor({}) }, ViewContainerLocation.Sidebar); + const testObject = viewDescriptorService.getViewContainerModel(container); + const target = disposableStore.add(new ViewDescriptorSequence(testObject)); - assert.equal(model.visibleViewDescriptors.length, 0); - assert.equal(seq.elements.length, 0); + assert.equal(testObject.visibleViewDescriptors.length, 0); + assert.equal(target.elements.length, 0); const viewDescriptor: IViewDescriptor = { id: 'view1', @@ -61,26 +80,25 @@ suite('ContributableViewsModel', () => { name: 'Test View 1' }; - viewsDescriptorsModel.addViews([viewDescriptor]); + ViewsRegistry.registerViews([viewDescriptor], container); - assert.equal(model.visibleViewDescriptors.length, 1); - assert.equal(seq.elements.length, 1); - assert.deepEqual(model.visibleViewDescriptors[0], viewDescriptor); - assert.deepEqual(seq.elements[0], viewDescriptor); + assert.equal(testObject.visibleViewDescriptors.length, 1); + assert.equal(target.elements.length, 1); + assert.deepEqual(testObject.visibleViewDescriptors[0], viewDescriptor); + assert.deepEqual(target.elements[0], viewDescriptor); - viewsDescriptorsModel.removeViews([viewDescriptor]); + ViewsRegistry.deregisterViews([viewDescriptor], container); - assert.equal(model.visibleViewDescriptors.length, 0); - assert.equal(seq.elements.length, 0); + assert.equal(testObject.visibleViewDescriptors.length, 0); + assert.equal(target.elements.length, 0); }); test('when contexts', async function () { - const viewsDescriptorsModel = new ViewDescriptorsModel(contextKeyService); - const model = new ViewsModel(viewsDescriptorsModel); - const seq = new ViewDescriptorSequence(model); - - assert.equal(model.visibleViewDescriptors.length, 0); - assert.equal(seq.elements.length, 0); + container = ViewContainerRegistry.registerViewContainer({ id: 'test', name: 'test', ctorDescriptor: new SyncDescriptor({}) }, ViewContainerLocation.Sidebar); + const testObject = viewDescriptorService.getViewContainerModel(container); + const target = disposableStore.add(new ViewDescriptorSequence(testObject)); + assert.equal(testObject.visibleViewDescriptors.length, 0); + assert.equal(target.elements.length, 0); const viewDescriptor: IViewDescriptor = { id: 'view1', @@ -89,169 +107,164 @@ suite('ContributableViewsModel', () => { when: ContextKeyExpr.equals('showview1', true) }; - viewsDescriptorsModel.addViews([viewDescriptor]); - assert.equal(model.visibleViewDescriptors.length, 0, 'view should not appear since context isnt in'); - assert.equal(seq.elements.length, 0); + ViewsRegistry.registerViews([viewDescriptor], container); + assert.equal(testObject.visibleViewDescriptors.length, 0, 'view should not appear since context isnt in'); + assert.equal(target.elements.length, 0); const key = contextKeyService.createKey('showview1', false); - assert.equal(model.visibleViewDescriptors.length, 0, 'view should still not appear since showview1 isnt true'); - assert.equal(seq.elements.length, 0); + assert.equal(testObject.visibleViewDescriptors.length, 0, 'view should still not appear since showview1 isnt true'); + assert.equal(target.elements.length, 0); key.set(true); await new Promise(c => setTimeout(c, 30)); - assert.equal(model.visibleViewDescriptors.length, 1, 'view should appear'); - assert.equal(seq.elements.length, 1); - assert.deepEqual(model.visibleViewDescriptors[0], viewDescriptor); - assert.equal(seq.elements[0], viewDescriptor); + assert.equal(testObject.visibleViewDescriptors.length, 1, 'view should appear'); + assert.equal(target.elements.length, 1); + assert.deepEqual(testObject.visibleViewDescriptors[0], viewDescriptor); + assert.equal(target.elements[0], viewDescriptor); key.set(false); await new Promise(c => setTimeout(c, 30)); - assert.equal(model.visibleViewDescriptors.length, 0, 'view should disappear'); - assert.equal(seq.elements.length, 0); + assert.equal(testObject.visibleViewDescriptors.length, 0, 'view should disappear'); + assert.equal(target.elements.length, 0); - viewsDescriptorsModel.removeViews([viewDescriptor]); - assert.equal(model.visibleViewDescriptors.length, 0, 'view should not be there anymore'); - assert.equal(seq.elements.length, 0); + ViewsRegistry.deregisterViews([viewDescriptor], container); + assert.equal(testObject.visibleViewDescriptors.length, 0, 'view should not be there anymore'); + assert.equal(target.elements.length, 0); key.set(true); await new Promise(c => setTimeout(c, 30)); - assert.equal(model.visibleViewDescriptors.length, 0, 'view should not be there anymore'); - assert.equal(seq.elements.length, 0); + assert.equal(testObject.visibleViewDescriptors.length, 0, 'view should not be there anymore'); + assert.equal(target.elements.length, 0); }); test('when contexts - multiple', async function () { - const viewsDescriptorsModel = new ViewDescriptorsModel(contextKeyService); - const model = new ViewsModel(viewsDescriptorsModel); - const seq = new ViewDescriptorSequence(model); - + container = ViewContainerRegistry.registerViewContainer({ id: 'test', name: 'test', ctorDescriptor: new SyncDescriptor({}) }, ViewContainerLocation.Sidebar); + const testObject = viewDescriptorService.getViewContainerModel(container); + const target = disposableStore.add(new ViewDescriptorSequence(testObject)); const view1: IViewDescriptor = { id: 'view1', ctorDescriptor: null!, name: 'Test View 1' }; const view2: IViewDescriptor = { id: 'view2', ctorDescriptor: null!, name: 'Test View 2', when: ContextKeyExpr.equals('showview2', true) }; - viewsDescriptorsModel.addViews([view1, view2]); - assert.deepEqual(model.visibleViewDescriptors, [view1], 'only view1 should be visible'); - assert.deepEqual(seq.elements, [view1], 'only view1 should be visible'); + ViewsRegistry.registerViews([view1, view2], container); + assert.deepEqual(testObject.visibleViewDescriptors, [view1], 'only view1 should be visible'); + assert.deepEqual(target.elements, [view1], 'only view1 should be visible'); const key = contextKeyService.createKey('showview2', false); - assert.deepEqual(model.visibleViewDescriptors, [view1], 'still only view1 should be visible'); - assert.deepEqual(seq.elements, [view1], 'still only view1 should be visible'); + assert.deepEqual(testObject.visibleViewDescriptors, [view1], 'still only view1 should be visible'); + assert.deepEqual(target.elements, [view1], 'still only view1 should be visible'); key.set(true); await new Promise(c => setTimeout(c, 30)); - assert.deepEqual(model.visibleViewDescriptors, [view1, view2], 'both views should be visible'); - assert.deepEqual(seq.elements, [view1, view2], 'both views should be visible'); + assert.deepEqual(testObject.visibleViewDescriptors, [view1, view2], 'both views should be visible'); + assert.deepEqual(target.elements, [view1, view2], 'both views should be visible'); - viewsDescriptorsModel.removeViews([view1, view2]); + ViewsRegistry.deregisterViews([view1, view2], container); }); test('when contexts - multiple 2', async function () { - const viewsDescriptorsModel = new ViewDescriptorsModel(contextKeyService); - const model = new ViewsModel(viewsDescriptorsModel); - const seq = new ViewDescriptorSequence(model); - + container = ViewContainerRegistry.registerViewContainer({ id: 'test', name: 'test', ctorDescriptor: new SyncDescriptor({}) }, ViewContainerLocation.Sidebar); + const testObject = viewDescriptorService.getViewContainerModel(container); + const target = disposableStore.add(new ViewDescriptorSequence(testObject)); const view1: IViewDescriptor = { id: 'view1', ctorDescriptor: null!, name: 'Test View 1', when: ContextKeyExpr.equals('showview1', true) }; const view2: IViewDescriptor = { id: 'view2', ctorDescriptor: null!, name: 'Test View 2' }; - viewsDescriptorsModel.addViews([view1, view2]); - assert.deepEqual(model.visibleViewDescriptors, [view2], 'only view2 should be visible'); - assert.deepEqual(seq.elements, [view2], 'only view2 should be visible'); + ViewsRegistry.registerViews([view1, view2], container); + assert.deepEqual(testObject.visibleViewDescriptors, [view2], 'only view2 should be visible'); + assert.deepEqual(target.elements, [view2], 'only view2 should be visible'); const key = contextKeyService.createKey('showview1', false); - assert.deepEqual(model.visibleViewDescriptors, [view2], 'still only view2 should be visible'); - assert.deepEqual(seq.elements, [view2], 'still only view2 should be visible'); + assert.deepEqual(testObject.visibleViewDescriptors, [view2], 'still only view2 should be visible'); + assert.deepEqual(target.elements, [view2], 'still only view2 should be visible'); key.set(true); await new Promise(c => setTimeout(c, 30)); - assert.deepEqual(model.visibleViewDescriptors, [view1, view2], 'both views should be visible'); - assert.deepEqual(seq.elements, [view1, view2], 'both views should be visible'); + assert.deepEqual(testObject.visibleViewDescriptors, [view1, view2], 'both views should be visible'); + assert.deepEqual(target.elements, [view1, view2], 'both views should be visible'); - viewsDescriptorsModel.removeViews([view1, view2]); + ViewsRegistry.deregisterViews([view1, view2], container); }); test('setVisible', () => { - const viewsDescriptorsModel = new ViewDescriptorsModel(contextKeyService); - const model = new ViewsModel(viewsDescriptorsModel); - const seq = new ViewDescriptorSequence(model); - + container = ViewContainerRegistry.registerViewContainer({ id: 'test', name: 'test', ctorDescriptor: new SyncDescriptor({}) }, ViewContainerLocation.Sidebar); + const testObject = viewDescriptorService.getViewContainerModel(container); + const target = disposableStore.add(new ViewDescriptorSequence(testObject)); const view1: IViewDescriptor = { id: 'view1', ctorDescriptor: null!, name: 'Test View 1', canToggleVisibility: true }; const view2: IViewDescriptor = { id: 'view2', ctorDescriptor: null!, name: 'Test View 2', canToggleVisibility: true }; const view3: IViewDescriptor = { id: 'view3', ctorDescriptor: null!, name: 'Test View 3', canToggleVisibility: true }; - viewsDescriptorsModel.addViews([view1, view2, view3]); - assert.deepEqual(model.visibleViewDescriptors, [view1, view2, view3]); - assert.deepEqual(seq.elements, [view1, view2, view3]); + ViewsRegistry.registerViews([view1, view2, view3], container); + assert.deepEqual(testObject.visibleViewDescriptors, [view1, view2, view3]); + assert.deepEqual(target.elements, [view1, view2, view3]); - model.setVisible('view2', true); - assert.deepEqual(model.visibleViewDescriptors, [view1, view2, view3], 'nothing should happen'); - assert.deepEqual(seq.elements, [view1, view2, view3]); + testObject.setVisible('view2', true); + assert.deepEqual(testObject.visibleViewDescriptors, [view1, view2, view3], 'nothing should happen'); + assert.deepEqual(target.elements, [view1, view2, view3]); - model.setVisible('view2', false); - assert.deepEqual(model.visibleViewDescriptors, [view1, view3], 'view2 should hide'); - assert.deepEqual(seq.elements, [view1, view3]); + testObject.setVisible('view2', false); + assert.deepEqual(testObject.visibleViewDescriptors, [view1, view3], 'view2 should hide'); + assert.deepEqual(target.elements, [view1, view3]); - model.setVisible('view1', false); - assert.deepEqual(model.visibleViewDescriptors, [view3], 'view1 should hide'); - assert.deepEqual(seq.elements, [view3]); + testObject.setVisible('view1', false); + assert.deepEqual(testObject.visibleViewDescriptors, [view3], 'view1 should hide'); + assert.deepEqual(target.elements, [view3]); - model.setVisible('view3', false); - assert.deepEqual(model.visibleViewDescriptors, [], 'view3 shoud hide'); - assert.deepEqual(seq.elements, []); + testObject.setVisible('view3', false); + assert.deepEqual(testObject.visibleViewDescriptors, [], 'view3 shoud hide'); + assert.deepEqual(target.elements, []); - model.setVisible('view1', true); - assert.deepEqual(model.visibleViewDescriptors, [view1], 'view1 should show'); - assert.deepEqual(seq.elements, [view1]); + testObject.setVisible('view1', true); + assert.deepEqual(testObject.visibleViewDescriptors, [view1], 'view1 should show'); + assert.deepEqual(target.elements, [view1]); - model.setVisible('view3', true); - assert.deepEqual(model.visibleViewDescriptors, [view1, view3], 'view3 should show'); - assert.deepEqual(seq.elements, [view1, view3]); + testObject.setVisible('view3', true); + assert.deepEqual(testObject.visibleViewDescriptors, [view1, view3], 'view3 should show'); + assert.deepEqual(target.elements, [view1, view3]); - model.setVisible('view2', true); - assert.deepEqual(model.visibleViewDescriptors, [view1, view2, view3], 'view2 should show'); - assert.deepEqual(seq.elements, [view1, view2, view3]); + testObject.setVisible('view2', true); + assert.deepEqual(testObject.visibleViewDescriptors, [view1, view2, view3], 'view2 should show'); + assert.deepEqual(target.elements, [view1, view2, view3]); - viewsDescriptorsModel.removeViews([view1, view2, view3]); - assert.deepEqual(model.visibleViewDescriptors, []); - assert.deepEqual(seq.elements, []); + ViewsRegistry.deregisterViews([view1, view2, view3], container); + assert.deepEqual(testObject.visibleViewDescriptors, []); + assert.deepEqual(target.elements, []); }); test('move', () => { - const viewsDescriptorsModel = new ViewDescriptorsModel(contextKeyService); - const model = new ViewsModel(viewsDescriptorsModel); - const seq = new ViewDescriptorSequence(model); - + container = ViewContainerRegistry.registerViewContainer({ id: 'test', name: 'test', ctorDescriptor: new SyncDescriptor({}) }, ViewContainerLocation.Sidebar); + const testObject = viewDescriptorService.getViewContainerModel(container); + const target = disposableStore.add(new ViewDescriptorSequence(testObject)); const view1: IViewDescriptor = { id: 'view1', ctorDescriptor: null!, name: 'Test View 1' }; const view2: IViewDescriptor = { id: 'view2', ctorDescriptor: null!, name: 'Test View 2' }; const view3: IViewDescriptor = { id: 'view3', ctorDescriptor: null!, name: 'Test View 3' }; - viewsDescriptorsModel.addViews([view1, view2, view3]); - assert.deepEqual(model.visibleViewDescriptors, [view1, view2, view3], 'model views should be OK'); - assert.deepEqual(seq.elements, [view1, view2, view3], 'sql views should be OK'); + ViewsRegistry.registerViews([view1, view2, view3], container); + assert.deepEqual(testObject.visibleViewDescriptors, [view1, view2, view3], 'model views should be OK'); + assert.deepEqual(target.elements, [view1, view2, view3], 'sql views should be OK'); - model.move('view3', 'view1'); - assert.deepEqual(model.visibleViewDescriptors, [view3, view1, view2], 'view3 should go to the front'); - assert.deepEqual(seq.elements, [view3, view1, view2]); + testObject.move('view3', 'view1'); + assert.deepEqual(testObject.visibleViewDescriptors, [view3, view1, view2], 'view3 should go to the front'); + assert.deepEqual(target.elements, [view3, view1, view2]); - model.move('view1', 'view2'); - assert.deepEqual(model.visibleViewDescriptors, [view3, view2, view1], 'view1 should go to the end'); - assert.deepEqual(seq.elements, [view3, view2, view1]); + testObject.move('view1', 'view2'); + assert.deepEqual(testObject.visibleViewDescriptors, [view3, view2, view1], 'view1 should go to the end'); + assert.deepEqual(target.elements, [view3, view2, view1]); - model.move('view1', 'view3'); - assert.deepEqual(model.visibleViewDescriptors, [view1, view3, view2], 'view1 should go to the front'); - assert.deepEqual(seq.elements, [view1, view3, view2]); + testObject.move('view1', 'view3'); + assert.deepEqual(testObject.visibleViewDescriptors, [view1, view3, view2], 'view1 should go to the front'); + assert.deepEqual(target.elements, [view1, view3, view2]); - model.move('view2', 'view3'); - assert.deepEqual(model.visibleViewDescriptors, [view1, view2, view3], 'view2 should go to the middle'); - assert.deepEqual(seq.elements, [view1, view2, view3]); + testObject.move('view2', 'view3'); + assert.deepEqual(testObject.visibleViewDescriptors, [view1, view2, view3], 'view2 should go to the middle'); + assert.deepEqual(target.elements, [view1, view2, view3]); }); test('view states', async function () { - const viewStates = new Map(); - viewStates.set('view1', { visibleGlobal: false, collapsed: false, visibleWorkspace: undefined }); - const viewsDescriptorsModel = new ViewDescriptorsModel(contextKeyService); - const model = new ViewsModel(viewsDescriptorsModel, viewStates); - const seq = new ViewDescriptorSequence(model); + storageService.store(`${container.id}.state.hidden`, JSON.stringify([{ id: 'view1', isHidden: true }]), StorageScope.GLOBAL); + container = ViewContainerRegistry.registerViewContainer({ id: 'test', name: 'test', ctorDescriptor: new SyncDescriptor({}) }, ViewContainerLocation.Sidebar); + const testObject = viewDescriptorService.getViewContainerModel(container); + const target = disposableStore.add(new ViewDescriptorSequence(testObject)); - assert.equal(model.visibleViewDescriptors.length, 0); - assert.equal(seq.elements.length, 0); + assert.equal(testObject.visibleViewDescriptors.length, 0); + assert.equal(target.elements.length, 0); const viewDescriptor: IViewDescriptor = { id: 'view1', @@ -259,20 +272,19 @@ suite('ContributableViewsModel', () => { name: 'Test View 1' }; - viewsDescriptorsModel.addViews([viewDescriptor]); - assert.equal(model.visibleViewDescriptors.length, 0, 'view should not appear since it was set not visible in view state'); - assert.equal(seq.elements.length, 0); + ViewsRegistry.registerViews([viewDescriptor], container); + assert.equal(testObject.visibleViewDescriptors.length, 0, 'view should not appear since it was set not visible in view state'); + assert.equal(target.elements.length, 0); }); test('view states and when contexts', async function () { - const viewStates = new Map(); - viewStates.set('view1', { visibleGlobal: false, collapsed: false, visibleWorkspace: undefined }); - const viewsDescriptorsModel = new ViewDescriptorsModel(contextKeyService); - const model = new ViewsModel(viewsDescriptorsModel, viewStates); - const seq = new ViewDescriptorSequence(model); + storageService.store(`${container.id}.state.hidden`, JSON.stringify([{ id: 'view1', isHidden: true }]), StorageScope.GLOBAL); + container = ViewContainerRegistry.registerViewContainer({ id: 'test', name: 'test', ctorDescriptor: new SyncDescriptor({}) }, ViewContainerLocation.Sidebar); + const testObject = viewDescriptorService.getViewContainerModel(container); + const target = disposableStore.add(new ViewDescriptorSequence(testObject)); - assert.equal(model.visibleViewDescriptors.length, 0); - assert.equal(seq.elements.length, 0); + assert.equal(testObject.visibleViewDescriptors.length, 0); + assert.equal(target.elements.length, 0); const viewDescriptor: IViewDescriptor = { id: 'view1', @@ -281,29 +293,28 @@ suite('ContributableViewsModel', () => { when: ContextKeyExpr.equals('showview1', true) }; - viewsDescriptorsModel.addViews([viewDescriptor]); - assert.equal(model.visibleViewDescriptors.length, 0, 'view should not appear since context isnt in'); - assert.equal(seq.elements.length, 0); + ViewsRegistry.registerViews([viewDescriptor], container); + assert.equal(testObject.visibleViewDescriptors.length, 0, 'view should not appear since context isnt in'); + assert.equal(target.elements.length, 0); const key = contextKeyService.createKey('showview1', false); - assert.equal(model.visibleViewDescriptors.length, 0, 'view should still not appear since showview1 isnt true'); - assert.equal(seq.elements.length, 0); + assert.equal(testObject.visibleViewDescriptors.length, 0, 'view should still not appear since showview1 isnt true'); + assert.equal(target.elements.length, 0); key.set(true); await new Promise(c => setTimeout(c, 30)); - assert.equal(model.visibleViewDescriptors.length, 0, 'view should still not appear since it was set not visible in view state'); - assert.equal(seq.elements.length, 0); + assert.equal(testObject.visibleViewDescriptors.length, 0, 'view should still not appear since it was set not visible in view state'); + assert.equal(target.elements.length, 0); }); test('view states and when contexts multiple views', async function () { - const viewStates = new Map(); - viewStates.set('view1', { visibleGlobal: false, collapsed: false, visibleWorkspace: undefined }); - const viewsDescriptorsModel = new ViewDescriptorsModel(contextKeyService); - const model = new ViewsModel(viewsDescriptorsModel, viewStates); - const seq = new ViewDescriptorSequence(model); + storageService.store(`${container.id}.state.hidden`, JSON.stringify([{ id: 'view1', isHidden: true }]), StorageScope.GLOBAL); + container = ViewContainerRegistry.registerViewContainer({ id: 'test', name: 'test', ctorDescriptor: new SyncDescriptor({}) }, ViewContainerLocation.Sidebar); + const testObject = viewDescriptorService.getViewContainerModel(container); + const target = disposableStore.add(new ViewDescriptorSequence(testObject)); - assert.equal(model.visibleViewDescriptors.length, 0); - assert.equal(seq.elements.length, 0); + assert.equal(testObject.visibleViewDescriptors.length, 0); + assert.equal(target.elements.length, 0); const view1: IViewDescriptor = { id: 'view1', @@ -323,30 +334,29 @@ suite('ContributableViewsModel', () => { when: ContextKeyExpr.equals('showview', true) }; - viewsDescriptorsModel.addViews([view1, view2, view3]); - assert.deepEqual(model.visibleViewDescriptors, [view2], 'Only view2 should be visible'); - assert.deepEqual(seq.elements, [view2]); + ViewsRegistry.registerViews([view1, view2, view3], container); + assert.deepEqual(testObject.visibleViewDescriptors, [view2], 'Only view2 should be visible'); + assert.deepEqual(target.elements, [view2]); const key = contextKeyService.createKey('showview', false); - assert.deepEqual(model.visibleViewDescriptors, [view2], 'Only view2 should be visible'); - assert.deepEqual(seq.elements, [view2]); + assert.deepEqual(testObject.visibleViewDescriptors, [view2], 'Only view2 should be visible'); + assert.deepEqual(target.elements, [view2]); key.set(true); await new Promise(c => setTimeout(c, 30)); - assert.deepEqual(model.visibleViewDescriptors, [view2, view3], 'view3 should be visible'); - assert.deepEqual(seq.elements, [view2, view3]); + assert.deepEqual(testObject.visibleViewDescriptors, [view2, view3], 'view3 should be visible'); + assert.deepEqual(target.elements, [view2, view3]); key.set(false); await new Promise(c => setTimeout(c, 30)); - assert.deepEqual(model.visibleViewDescriptors, [view2], 'Only view2 should be visible'); - assert.deepEqual(seq.elements, [view2]); + assert.deepEqual(testObject.visibleViewDescriptors, [view2], 'Only view2 should be visible'); + assert.deepEqual(target.elements, [view2]); }); test('remove event is not triggered if view was hidden and removed', async function () { - const viewsDescriptorsModel = new ViewDescriptorsModel(contextKeyService); - const model = new ViewsModel(viewsDescriptorsModel); - const seq = new ViewDescriptorSequence(model); - + container = ViewContainerRegistry.registerViewContainer({ id: 'test', name: 'test', ctorDescriptor: new SyncDescriptor({}) }, ViewContainerLocation.Sidebar); + const testObject = viewDescriptorService.getViewContainerModel(container); + const target = disposableStore.add(new ViewDescriptorSequence(testObject)); const viewDescriptor: IViewDescriptor = { id: 'view1', ctorDescriptor: null!, @@ -355,21 +365,21 @@ suite('ContributableViewsModel', () => { canToggleVisibility: true }; - viewsDescriptorsModel.addViews([viewDescriptor]); + ViewsRegistry.registerViews([viewDescriptor], container); const key = contextKeyService.createKey('showview1', true); await new Promise(c => setTimeout(c, 30)); - assert.equal(model.visibleViewDescriptors.length, 1, 'view should appear after context is set'); - assert.equal(seq.elements.length, 1); + assert.equal(testObject.visibleViewDescriptors.length, 1, 'view should appear after context is set'); + assert.equal(target.elements.length, 1); - model.setVisible('view1', false); - assert.equal(model.visibleViewDescriptors.length, 0, 'view should disappear after setting visibility to false'); - assert.equal(seq.elements.length, 0); + testObject.setVisible('view1', false); + assert.equal(testObject.visibleViewDescriptors.length, 0, 'view should disappear after setting visibility to false'); + assert.equal(target.elements.length, 0); - const target = sinon.spy(model.onDidRemove); + const targetEvent = sinon.spy(testObject.onDidRemoveVisibleViewDescriptors); key.set(false); await new Promise(c => setTimeout(c, 30)); - assert.ok(!target.called, 'remove event should not be called since it is already hidden'); + assert.ok(!targetEvent.called, 'remove event should not be called since it is already hidden'); }); }); From 6e2d4c93654263149d2d7ad36864a8dd2a8d017e Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Mon, 20 Apr 2020 20:48:55 +0200 Subject: [PATCH 16/44] rename test --- .../browser/{viewsModel.test.ts => viewContainerModel.test.ts} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/vs/workbench/services/views/test/browser/{viewsModel.test.ts => viewContainerModel.test.ts} (100%) diff --git a/src/vs/workbench/services/views/test/browser/viewsModel.test.ts b/src/vs/workbench/services/views/test/browser/viewContainerModel.test.ts similarity index 100% rename from src/vs/workbench/services/views/test/browser/viewsModel.test.ts rename to src/vs/workbench/services/views/test/browser/viewContainerModel.test.ts From 0e82f1186151f72dcba42bf537e9b8d5d77ebc6e Mon Sep 17 00:00:00 2001 From: rebornix Date: Mon, 20 Apr 2020 09:14:46 -0700 Subject: [PATCH 17/44] index based folding --- .../notebook/browser/contrib/fold/folding.ts | 5 ++-- .../browser/viewModel/foldingModel.ts | 5 ++-- .../viewModel/markdownCellViewModel.ts | 2 +- .../browser/viewModel/notebookViewModel.ts | 29 +++++-------------- .../notebook/test/notebookFolding.test.ts | 18 +++++------- 5 files changed, 22 insertions(+), 37 deletions(-) diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/fold/folding.ts b/src/vs/workbench/contrib/notebook/browser/contrib/fold/folding.ts index 9bbf4eca7b0..6f598eef643 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/fold/folding.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/fold/folding.ts @@ -45,13 +45,14 @@ export class FoldingController extends Disposable { // folding icon const cellViewModel = e.target; - const state = viewModel.getFoldingState(cellViewModel); + const modelIndex = viewModel.getCellIndex(cellViewModel); + const state = viewModel.getFoldingState(modelIndex); if (state === CellFoldingState.None) { return; } - viewModel.setFoldingState(cellViewModel, state === CellFoldingState.Collapsed ? CellFoldingState.Expanded : CellFoldingState.Collapsed); + viewModel.setFoldingState(modelIndex, state === CellFoldingState.Collapsed ? CellFoldingState.Expanded : CellFoldingState.Collapsed); } return; diff --git a/src/vs/workbench/contrib/notebook/browser/viewModel/foldingModel.ts b/src/vs/workbench/contrib/notebook/browser/viewModel/foldingModel.ts index ec0f9aaef87..cb7ee9d7c6b 100644 --- a/src/vs/workbench/contrib/notebook/browser/viewModel/foldingModel.ts +++ b/src/vs/workbench/contrib/notebook/browser/viewModel/foldingModel.ts @@ -165,6 +165,7 @@ export enum CellFoldingState { export interface FoldingRegionDelegate { onDidFoldingRegionChanged: Event; - getFoldingStartIndex(cell: CellViewModel): number; - getFoldingState(cell: CellViewModel): CellFoldingState; + getCellIndex(cell: CellViewModel): number; + getFoldingStartIndex(index: number): number; + getFoldingState(index: number): CellFoldingState; } diff --git a/src/vs/workbench/contrib/notebook/browser/viewModel/markdownCellViewModel.ts b/src/vs/workbench/contrib/notebook/browser/viewModel/markdownCellViewModel.ts index aeefc169baa..1d12a265de5 100644 --- a/src/vs/workbench/contrib/notebook/browser/viewModel/markdownCellViewModel.ts +++ b/src/vs/workbench/contrib/notebook/browser/viewModel/markdownCellViewModel.ts @@ -39,7 +39,7 @@ export class MarkdownCellViewModel extends BaseCellViewModel implements ICellVie readonly onDidChangeLayout = this._onDidChangeLayout.event; get foldingState() { - return this.foldingDelegate.getFoldingState(this); + return this.foldingDelegate.getFoldingState(this.foldingDelegate.getCellIndex(this)); } constructor( diff --git a/src/vs/workbench/contrib/notebook/browser/viewModel/notebookViewModel.ts b/src/vs/workbench/contrib/notebook/browser/viewModel/notebookViewModel.ts index e3098e1f9c9..7743e716716 100644 --- a/src/vs/workbench/contrib/notebook/browser/viewModel/notebookViewModel.ts +++ b/src/vs/workbench/contrib/notebook/browser/viewModel/notebookViewModel.ts @@ -251,43 +251,30 @@ export class NotebookViewModel extends Disposable implements FoldingRegionDelega this._updateFoldingRanges(); })); } - getFoldingStartIndex(cell: CellViewModel): number { - const modelIndex = this.viewCells.indexOf(cell); - if (modelIndex < 0) { - return -1; - } - const range = this._foldingModel.regions.findRange(modelIndex + 1); + getFoldingStartIndex(index: number): number { + const range = this._foldingModel.regions.findRange(index + 1); const startIndex = this._foldingModel.regions.getStartLineNumber(range) - 1; return startIndex; } - getFoldingState(cell: CellViewModel): CellFoldingState { - const modelIndex = this.viewCells.indexOf(cell); - if (modelIndex < 0) { - return -1; - } - - const range = this._foldingModel.regions.findRange(modelIndex + 1); + getFoldingState(index: number): CellFoldingState { + const range = this._foldingModel.regions.findRange(index + 1); const startIndex = this._foldingModel.regions.getStartLineNumber(range) - 1; - if (startIndex !== modelIndex) { + if (startIndex !== index) { return CellFoldingState.None; } return this._foldingModel.regions.isCollapsed(range) ? CellFoldingState.Collapsed : CellFoldingState.Expanded; } - setFoldingState(cell: CellViewModel, state: CellFoldingState): void { - const modelIndex = this.viewCells.indexOf(cell); - if (modelIndex < 0) { - return; - } + setFoldingState(index: number, state: CellFoldingState): void { - const range = this._foldingModel.regions.findRange(modelIndex + 1); + const range = this._foldingModel.regions.findRange(index + 1); const startIndex = this._foldingModel.regions.getStartLineNumber(range) - 1; - if (startIndex !== modelIndex) { + if (startIndex !== index) { return; } diff --git a/src/vs/workbench/contrib/notebook/test/notebookFolding.test.ts b/src/vs/workbench/contrib/notebook/test/notebookFolding.test.ts index 7f919d1df45..1fba9c73cf1 100644 --- a/src/vs/workbench/contrib/notebook/test/notebookFolding.test.ts +++ b/src/vs/workbench/contrib/notebook/test/notebookFolding.test.ts @@ -10,7 +10,6 @@ import { withTestNotebook, TestCell } from 'vs/workbench/contrib/notebook/test/t import { IBulkEditService } from 'vs/editor/browser/services/bulkEditService'; import { IUndoRedoService } from 'vs/platform/undoRedo/common/undoRedo'; import { FoldingModel, CellFoldingState } from 'vs/workbench/contrib/notebook/browser/viewModel/foldingModel'; -import { CellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/notebookViewModel'; suite('Notebook Folding', () => { const instantiationService = new TestInstantiationService(); @@ -96,8 +95,7 @@ suite('Notebook Folding', () => { [['var e = 7;'], 'markdown', CellKind.Markdown, [], {}], ], (editor, viewModel) => { - const firstCell = viewModel.viewCells[0] as CellViewModel; - viewModel.setFoldingState(firstCell, CellFoldingState.Collapsed); + viewModel.setFoldingState(0, CellFoldingState.Collapsed); assert.deepEqual(viewModel.getHiddenRanges(), [ { start: 1, length: 6 } ]); @@ -118,8 +116,7 @@ suite('Notebook Folding', () => { [['var e = 7;'], 'markdown', CellKind.Markdown, [], {}], ], (editor, viewModel) => { - const thirdCell = viewModel.viewCells[2] as CellViewModel; - viewModel.setFoldingState(thirdCell, CellFoldingState.Collapsed); + viewModel.setFoldingState(2, CellFoldingState.Collapsed); assert.deepEqual(viewModel.getHiddenRanges(), [ { start: 3, length: 2 } ]); @@ -140,8 +137,7 @@ suite('Notebook Folding', () => { [['var e = 7;'], 'markdown', CellKind.Markdown, [], {}], ], (editor, viewModel) => { - const thirdCell = viewModel.viewCells[2] as CellViewModel; - viewModel.setFoldingState(thirdCell, CellFoldingState.Collapsed); + viewModel.setFoldingState(2, CellFoldingState.Collapsed); assert.deepEqual(viewModel.getHiddenRanges(), [ { start: 3, length: 4 } ]); @@ -164,19 +160,19 @@ suite('Notebook Folding', () => { [['var e = 7;'], 'markdown', CellKind.Markdown, [], {}], ], (editor, viewModel) => { - viewModel.setFoldingState(viewModel.viewCells[0] as CellViewModel, CellFoldingState.Collapsed); + viewModel.setFoldingState(0, CellFoldingState.Collapsed); assert.deepEqual(viewModel.getHiddenRanges(), [ { start: 1, length: 1 } ]); - viewModel.setFoldingState(viewModel.viewCells[5] as CellViewModel, CellFoldingState.Collapsed); - viewModel.setFoldingState(viewModel.viewCells[2] as CellViewModel, CellFoldingState.Collapsed); + viewModel.setFoldingState(5, CellFoldingState.Collapsed); + viewModel.setFoldingState(2, CellFoldingState.Collapsed); assert.deepEqual(viewModel.getHiddenRanges(), [ { start: 1, length: 1 }, { start: 3, length: 4 } ]); - viewModel.setFoldingState(viewModel.viewCells[2] as CellViewModel, CellFoldingState.Expanded); + viewModel.setFoldingState(2, CellFoldingState.Expanded); assert.deepEqual(viewModel.getHiddenRanges(), [ { start: 1, length: 1 }, { start: 6, length: 1 } From 6d58e935f3aa305e95a6c8995ece04f8a8d7559e Mon Sep 17 00:00:00 2001 From: rebornix Date: Mon, 20 Apr 2020 11:58:53 -0700 Subject: [PATCH 18/44] Re #94408. Gear icon on quick pick for setting default editor association. --- .../contrib/files/browser/fileActions.ts | 55 ++++++++++++++++--- 1 file changed, 48 insertions(+), 7 deletions(-) diff --git a/src/vs/workbench/contrib/files/browser/fileActions.ts b/src/vs/workbench/contrib/files/browser/fileActions.ts index 648ac62847e..a3b76fbed7c 100644 --- a/src/vs/workbench/contrib/files/browser/fileActions.ts +++ b/src/vs/workbench/contrib/files/browser/fileActions.ts @@ -50,6 +50,7 @@ import { once } from 'vs/base/common/functional'; import { IEditorOptions } from 'vs/platform/editor/common/editor'; import { IEditorGroup } from 'vs/workbench/services/editor/common/editorGroupsService'; import { FileEditorInput } from 'vs/workbench/contrib/files/common/editors/fileEditorInput'; +import { CustomEditorsAssociations, customEditorsAssociationsSettingId, CustomEditorAssociation } from 'vs/workbench/services/editor/browser/editorAssociationsSetting'; export const NEW_FILE_COMMAND_ID = 'explorer.newFile'; export const NEW_FILE_LABEL = nls.localize('newFile', "New File"); @@ -530,6 +531,7 @@ export class ReopenResourcesAction extends Action { label: string, @IQuickInputService private readonly quickInputService: IQuickInputService, @IEditorService private readonly editorService: IEditorService, + @IConfigurationService private readonly configurationService: IConfigurationService ) { super(id, label); } @@ -552,6 +554,8 @@ export class ReopenResourcesAction extends Action { return; } + const resourceExt = extname(activeResource.path); + const overrides = this.editorService.getEditorOverrides(activeInput, options, group); const items: (IQuickPickItem & { handler?: IOpenEditorOverrideHandler })[] = overrides.map((override) => { return { @@ -559,7 +563,11 @@ export class ReopenResourcesAction extends Action { id: override[1].id, label: override[1].label, description: override[1].active ? 'Currently Active' : undefined, - detail: override[1].detail + detail: override[1].detail, + buttons: resourceExt ? [{ + iconClass: 'codicon-settings-gear', + tooltip: nls.localize('promptOpenWith.setDefaultTooltip', "Set as default editor for '{0}' files", resourceExt) + }] : undefined }; }); @@ -567,12 +575,18 @@ export class ReopenResourcesAction extends Action { return; } - items.unshift({ - id: 'default', - label: nls.localize('promptOpenWith.defaultEditor.displayName', "Text Editor"), - description: activeInput instanceof FileEditorInput ? 'Currently Active' : undefined, - detail: builtinProviderDisplayName - }); + if (!items.find(item => item.id === 'default')) { + items.unshift({ + id: 'default', + label: nls.localize('promptOpenWith.defaultEditor.displayName', "Text Editor"), + description: activeInput instanceof FileEditorInput ? 'Currently Active' : undefined, + detail: builtinProviderDisplayName, + buttons: resourceExt ? [{ + iconClass: 'codicon-settings-gear', + tooltip: nls.localize('promptOpenWith.setDefaultTooltip', "Set as default editor for '{0}' files", resourceExt) + }] : undefined + }); + } const picker = this.quickInputService.createQuickPick<(IQuickPickItem & { handler?: IOpenEditorOverrideHandler })>(); picker.items = items; @@ -584,6 +598,33 @@ export class ReopenResourcesAction extends Action { picker.dispose(); }); + picker.onDidTriggerItemButton(e => { + const pick = e.item; + const id = pick.id; + resolve(pick); // open the view + picker.dispose(); + + // And persist the setting + if (pick && id) { + const newAssociation: CustomEditorAssociation = { viewType: id, filenamePattern: '*' + resourceExt }; + const currentAssociations = [...this.configurationService.getValue(customEditorsAssociationsSettingId)] || []; + + // First try updating existing association + for (let i = 0; i < currentAssociations.length; ++i) { + const existing = currentAssociations[i]; + if (existing.filenamePattern === newAssociation.filenamePattern) { + currentAssociations.splice(i, 1, newAssociation); + this.configurationService.updateValue(customEditorsAssociationsSettingId, currentAssociations); + return; + } + } + + // Otherwise, create a new one + currentAssociations.unshift(newAssociation); + this.configurationService.updateValue(customEditorsAssociationsSettingId, currentAssociations); + } + }); + picker.show(); }); From c96976d9918df8975837e20d0974d6b0930731b3 Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Mon, 20 Apr 2020 12:14:05 -0700 Subject: [PATCH 19/44] Update to latest typescript version to build vscode --- build/package.json | 2 +- build/yarn.lock | 8 ++++---- package.json | 2 +- yarn.lock | 8 ++++---- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/build/package.json b/build/package.json index 96cb5414382..d9523ae2b92 100644 --- a/build/package.json +++ b/build/package.json @@ -43,7 +43,7 @@ "minimist": "^1.2.3", "request": "^2.85.0", "terser": "4.3.8", - "typescript": "^3.9.0-dev.20200413", + "typescript": "^3.9.0-dev.20200420", "vsce": "1.48.0", "vscode-telemetry-extractor": "^1.5.4", "xml2js": "^0.4.17" diff --git a/build/yarn.lock b/build/yarn.lock index 3bb4b7744c7..21246ba1b1d 100644 --- a/build/yarn.lock +++ b/build/yarn.lock @@ -2458,10 +2458,10 @@ typescript@^3.0.1: resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.5.3.tgz#c830f657f93f1ea846819e929092f5fe5983e977" integrity sha512-ACzBtm/PhXBDId6a6sDJfroT2pOWt/oOnk4/dElG5G33ZL776N3Y6/6bKZJBFpd+b05F3Ct9qDjMeJmRWtE2/g== -typescript@^3.9.0-dev.20200413: - version "3.9.0-dev.20200413" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.9.0-dev.20200413.tgz#dd38331435f5c30867f19c94fe7cfc567ec13e20" - integrity sha512-wpPljcRc3uLTVpQ8mtKTkS42ipVnW1E7lU5iII+JSKaUDH1ymeo3duvMz8o2sCRUDoZR0SE0SLcinaZ1q4gD7A== +typescript@^3.9.0-dev.20200420: + version "3.9.0-dev.20200420" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.9.0-dev.20200420.tgz#99c2bc0936dbf4479b0b5260d80475ed494b1532" + integrity sha512-36MW6V+oXNnsSgliSjUWvtOkO21g9+iFGHPFv+O06HsCl3dcuqzBac17m8xuOuWo1LUlEgS6yAnD9fiVgvmCfg== typical@^4.0.0: version "4.0.0" diff --git a/package.json b/package.json index 4de717d9273..ae5d4d88ee9 100644 --- a/package.json +++ b/package.json @@ -156,7 +156,7 @@ "source-map": "^0.4.4", "style-loader": "^1.0.0", "ts-loader": "^4.4.2", - "typescript": "^3.9.0-dev.20200413", + "typescript": "^3.9.0-dev.20200420", "typescript-formatter": "7.1.0", "underscore": "^1.8.2", "vinyl": "^2.0.0", diff --git a/yarn.lock b/yarn.lock index 54462bc339e..b6dd7b4c8f4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9265,10 +9265,10 @@ typescript@^2.6.2: resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.6.2.tgz#3c5b6fd7f6de0914269027f03c0946758f7673a4" integrity sha1-PFtv1/beCRQmkCfwPAlGdY92c6Q= -typescript@^3.9.0-dev.20200413: - version "3.9.0-dev.20200413" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.9.0-dev.20200413.tgz#dd38331435f5c30867f19c94fe7cfc567ec13e20" - integrity sha512-wpPljcRc3uLTVpQ8mtKTkS42ipVnW1E7lU5iII+JSKaUDH1ymeo3duvMz8o2sCRUDoZR0SE0SLcinaZ1q4gD7A== +typescript@^3.9.0-dev.20200420: + version "3.9.0-dev.20200420" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.9.0-dev.20200420.tgz#99c2bc0936dbf4479b0b5260d80475ed494b1532" + integrity sha512-36MW6V+oXNnsSgliSjUWvtOkO21g9+iFGHPFv+O06HsCl3dcuqzBac17m8xuOuWo1LUlEgS6yAnD9fiVgvmCfg== uc.micro@^1.0.1, uc.micro@^1.0.3: version "1.0.3" From f5d46633bf35e36afc164eb2b2e9dea19386e8ea Mon Sep 17 00:00:00 2001 From: SteVen Batten Date: Mon, 20 Apr 2020 13:14:31 -0700 Subject: [PATCH 20/44] update panel and fix issue with resetting views --- src/vs/workbench/browser/parts/compositeBarActions.ts | 1 + src/vs/workbench/browser/parts/panel/panelPart.ts | 3 +-- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/browser/parts/compositeBarActions.ts b/src/vs/workbench/browser/parts/compositeBarActions.ts index 52af0a4bcde..c7d6bc7fec8 100644 --- a/src/vs/workbench/browser/parts/compositeBarActions.ts +++ b/src/vs/workbench/browser/parts/compositeBarActions.ts @@ -69,6 +69,7 @@ export class ActivityAction extends Action { } set activity(activity: IActivity) { + this._label = activity.name; this._activity = activity; this._onDidChangeActivity.fire(this); } diff --git a/src/vs/workbench/browser/parts/panel/panelPart.ts b/src/vs/workbench/browser/parts/panel/panelPart.ts index e9713d8aab2..e2ca8aa50a8 100644 --- a/src/vs/workbench/browser/parts/panel/panelPart.ts +++ b/src/vs/workbench/browser/parts/panel/panelPart.ts @@ -216,8 +216,7 @@ export class PanelPart extends CompositePart implements IPanelService { const disposables = new DisposableStore(); disposables.add(viewContainerModel.onDidChangeActiveViewDescriptors(() => this.onDidChangeActiveViews(panel, viewContainerModel, viewContainer.hideIfEmpty))); - disposables.add(viewContainerModel.onDidChangeAllViewDescriptors(() => this.onDidUpdateViews(panel, viewContainerModel))); - disposables.add(viewContainerModel.onDidMoveVisibleViewDescriptors(() => this.onDidUpdateViews(panel, viewContainerModel))); + disposables.add(viewContainerModel.onDidChangeContainerInfo(() => this.onDidUpdateViews(panel, viewContainerModel))); this.panelDisposables.set(panel.id, disposables); } From ca343363d2f0957920c7fe8f3c60d884e4ca939c Mon Sep 17 00:00:00 2001 From: rebornix Date: Mon, 20 Apr 2020 13:19:35 -0700 Subject: [PATCH 21/44] ICellRange: zero based. --- .../browser/contrib/notebookActions.ts | 4 +- .../notebook/browser/notebookBrowser.ts | 21 ++++--- .../notebook/browser/view/notebookCellList.ts | 6 +- .../browser/viewModel/foldingModel.ts | 2 +- .../browser/viewModel/notebookViewModel.ts | 12 ++-- .../notebook/test/notebookFolding.test.ts | 24 +++---- .../notebook/test/notebookViewModel.test.ts | 63 +++++++++++-------- 7 files changed, 71 insertions(+), 61 deletions(-) diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/notebookActions.ts b/src/vs/workbench/contrib/notebook/browser/contrib/notebookActions.ts index 8ddc32e89e5..08a53ceadb2 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/notebookActions.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/notebookActions.ts @@ -1174,7 +1174,7 @@ registerAction2(class extends Action2 { return; } - editor.setHiddenAreas([{ start: 1, length: 3 }]); + editor.setHiddenAreas([{ start: 1, end: 3 }]); } }); @@ -1205,7 +1205,7 @@ registerAction2(class extends Action2 { return; } - editor.setHiddenAreas([{ start: 3, length: 2 }]); + editor.setHiddenAreas([{ start: 3, end: 4 }]); } }); diff --git a/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts b/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts index df2fb08178a..12b658d9d60 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts @@ -442,17 +442,18 @@ export interface CellViewModelStateChangeEvent { } /** - * [start, start + length - 1] + * [start, end] */ export interface ICellRange { /** * zero based index */ start: number; + /** - * One based, includes `start` + * zero based index */ - length: number; + end: number; } @@ -467,21 +468,21 @@ export function reduceCellRanges(_ranges: ICellRange[]): ICellRange[] { let ranges = _ranges.sort((a, b) => a.start - b.start); let result: ICellRange[] = []; let currentRangeStart = ranges[0].start; - let currentRangeEnd = ranges[0].start + ranges[0].length; + let currentRangeEnd = ranges[0].end + 1; for (let i = 0, len = ranges.length; i < len; i++) { let range = ranges[i]; if (range.start > currentRangeEnd) { - result.push({ start: currentRangeStart, length: currentRangeEnd - currentRangeStart }); + result.push({ start: currentRangeStart, end: currentRangeEnd - 1 }); currentRangeStart = range.start; - currentRangeEnd = range.start + range.length; - } else if (range.start + range.length > currentRangeEnd) { - currentRangeEnd = range.start + range.length; + currentRangeEnd = range.end + 1; + } else if (range.end + 1 > currentRangeEnd) { + currentRangeEnd = range.end + 1; } } - result.push({ start: currentRangeStart, length: currentRangeEnd - currentRangeStart }); + result.push({ start: currentRangeStart, end: currentRangeEnd - 1 }); return result; } @@ -499,7 +500,7 @@ export function getVisibleCells(cells: CellViewModel[], hiddenRanges: ICellRange result.push(...cells.slice(start, hiddenRanges[hiddenRangeIndex].start)); } - start = hiddenRanges[hiddenRangeIndex].start + hiddenRanges[hiddenRangeIndex].length; + start = hiddenRanges[hiddenRangeIndex].end + 1; hiddenRangeIndex++; } diff --git a/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts b/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts index 5b7bd3ab364..8f79892f20c 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts @@ -189,7 +189,7 @@ export class NotebookCellList extends WorkbenchList implements ID if (newRanges.length === oldRanges.length) { let hasDifference = false; for (let i = 0; i < newRanges.length; i++) { - if (!(newRanges[i].start === oldRanges[i].start && newRanges[i].length === oldRanges[i].length)) { + if (!(newRanges[i].start === oldRanges[i].start && newRanges[i].end === oldRanges[i].end)) { hasDifference = true; break; } @@ -217,8 +217,8 @@ export class NotebookCellList extends WorkbenchList implements ID ret.push(1); } - ret.push(newRanges[index].length + 1); - start = newRanges[index].start + newRanges[index].length; + ret.push(newRanges[index].end - newRanges[index].start + 1 + 1); + start = newRanges[index].end + 1; index++; } diff --git a/src/vs/workbench/contrib/notebook/browser/viewModel/foldingModel.ts b/src/vs/workbench/contrib/notebook/browser/viewModel/foldingModel.ts index cb7ee9d7c6b..92768b04249 100644 --- a/src/vs/workbench/contrib/notebook/browser/viewModel/foldingModel.ts +++ b/src/vs/workbench/contrib/notebook/browser/viewModel/foldingModel.ts @@ -144,7 +144,7 @@ export class FoldingModel extends Disposable { const cellRanges: ICellRange[] = []; for (let i = 0; i < newRegions.length; i++) { const region = newRegions.toRegion(i); - cellRanges.push({ start: region.startLineNumber - 1, length: region.endLineNumber - region.startLineNumber + 1 }); + cellRanges.push({ start: region.startLineNumber - 1, end: region.endLineNumber - 1 }); } // remove old tracked ranges and add new ones diff --git a/src/vs/workbench/contrib/notebook/browser/viewModel/notebookViewModel.ts b/src/vs/workbench/contrib/notebook/browser/viewModel/notebookViewModel.ts index 7743e716716..5c9c08f9b08 100644 --- a/src/vs/workbench/contrib/notebook/browser/viewModel/notebookViewModel.ts +++ b/src/vs/workbench/contrib/notebook/browser/viewModel/notebookViewModel.ts @@ -305,13 +305,13 @@ export class NotebookViewModel extends Disposable implements FoldingRegionDelega continue; } - if (!updateHiddenAreas && k < this._hiddenRanges.length && this._hiddenRanges[k].start + 1 === startLineNumber && (this._hiddenRanges[k].start + this._hiddenRanges[k].length) === endLineNumber) { + if (!updateHiddenAreas && k < this._hiddenRanges.length && this._hiddenRanges[k].start + 1 === startLineNumber && (this._hiddenRanges[k].end + 1) === endLineNumber) { // reuse the old ranges newHiddenAreas.push(this._hiddenRanges[k]); k++; } else { updateHiddenAreas = true; - newHiddenAreas.push({ start: startLineNumber - 1, length: endLineNumber - startLineNumber + 1 }); + newHiddenAreas.push({ start: startLineNumber - 1, end: endLineNumber - 1 }); } lastCollapsedStart = startLineNumber; lastCollapsedEnd = endLineNumber; @@ -366,10 +366,10 @@ export class NotebookViewModel extends Disposable implements FoldingRegionDelega this._decorationsTree.resolveNode(node, versionId); } if (node.range === null) { - return { start: node.cachedAbsoluteStart - 1, length: node.cachedAbsoluteEnd - node.cachedAbsoluteStart + 1 }; + return { start: node.cachedAbsoluteStart - 1, end: node.cachedAbsoluteEnd - 1 }; } - return { start: node.range.startLineNumber - 1, length: node.range.endLineNumber - node.range.startLineNumber + 1 }; + return { start: node.range.startLineNumber - 1, end: node.range.endLineNumber - 1 }; } setTrackedRange(id: string | null, newRange: ICellRange | null, newStickiness: TrackedRangeStickiness): string | null { @@ -380,7 +380,7 @@ export class NotebookViewModel extends Disposable implements FoldingRegionDelega return null; } - return this._deltaCellDecorationsImpl(0, [], [{ range: new Range(newRange.start + 1, 1, newRange.start + newRange.length, 1), options: TRACKED_RANGE_OPTIONS[newStickiness] }])[0]; + return this._deltaCellDecorationsImpl(0, [], [{ range: new Range(newRange.start + 1, 1, newRange.end + 1, 1), options: TRACKED_RANGE_OPTIONS[newStickiness] }])[0]; } if (!newRange) { @@ -391,7 +391,7 @@ export class NotebookViewModel extends Disposable implements FoldingRegionDelega } this._decorationsTree.delete(node); - node.reset(this.getVersionId(), newRange.start, newRange.start + newRange.length, new Range(newRange.start + 1, 1, newRange.start + newRange.length, 1)); + node.reset(this.getVersionId(), newRange.start, newRange.end + 1, new Range(newRange.start + 1, 1, newRange.end + 1, 1)); node.setOptions(TRACKED_RANGE_OPTIONS[newStickiness]); this._decorationsTree.insert(node); return node.id; diff --git a/src/vs/workbench/contrib/notebook/test/notebookFolding.test.ts b/src/vs/workbench/contrib/notebook/test/notebookFolding.test.ts index 1fba9c73cf1..667b1c8f6e1 100644 --- a/src/vs/workbench/contrib/notebook/test/notebookFolding.test.ts +++ b/src/vs/workbench/contrib/notebook/test/notebookFolding.test.ts @@ -97,7 +97,7 @@ suite('Notebook Folding', () => { (editor, viewModel) => { viewModel.setFoldingState(0, CellFoldingState.Collapsed); assert.deepEqual(viewModel.getHiddenRanges(), [ - { start: 1, length: 6 } + { start: 1, end: 6 } ]); } ); @@ -118,7 +118,7 @@ suite('Notebook Folding', () => { (editor, viewModel) => { viewModel.setFoldingState(2, CellFoldingState.Collapsed); assert.deepEqual(viewModel.getHiddenRanges(), [ - { start: 3, length: 2 } + { start: 3, end: 4 } ]); } ); @@ -139,7 +139,7 @@ suite('Notebook Folding', () => { (editor, viewModel) => { viewModel.setFoldingState(2, CellFoldingState.Collapsed); assert.deepEqual(viewModel.getHiddenRanges(), [ - { start: 3, length: 4 } + { start: 3, end: 6 } ]); } ); @@ -162,33 +162,33 @@ suite('Notebook Folding', () => { (editor, viewModel) => { viewModel.setFoldingState(0, CellFoldingState.Collapsed); assert.deepEqual(viewModel.getHiddenRanges(), [ - { start: 1, length: 1 } + { start: 1, end: 1 } ]); viewModel.setFoldingState(5, CellFoldingState.Collapsed); viewModel.setFoldingState(2, CellFoldingState.Collapsed); assert.deepEqual(viewModel.getHiddenRanges(), [ - { start: 1, length: 1 }, - { start: 3, length: 4 } + { start: 1, end: 1 }, + { start: 3, end: 6 } ]); viewModel.setFoldingState(2, CellFoldingState.Expanded); assert.deepEqual(viewModel.getHiddenRanges(), [ - { start: 1, length: 1 }, - { start: 6, length: 1 } + { start: 1, end: 1 }, + { start: 6, end: 6 } ]); viewModel.insertCell(7, new TestCell(viewModel.viewType, 7, ['var c = 8;'], 'markdown', CellKind.Code, []), true); assert.deepEqual(viewModel.getHiddenRanges(), [ - { start: 1, length: 1 }, - { start: 6, length: 2 } + { start: 1, end: 1 }, + { start: 6, end: 7 } ]); viewModel.insertCell(1, new TestCell(viewModel.viewType, 8, ['var c = 9;'], 'markdown', CellKind.Code, []), true); assert.deepEqual(viewModel.getHiddenRanges(), [ // the first collapsed range is now expanded as we insert content into it. - // { start: 1, length: 2 }, - { start: 7, length: 2 } + // { start: 1,}, + { start: 7, end: 8 } ]); } ); diff --git a/src/vs/workbench/contrib/notebook/test/notebookViewModel.test.ts b/src/vs/workbench/contrib/notebook/test/notebookViewModel.test.ts index 3285bcb7d3d..ccce4ad0f52 100644 --- a/src/vs/workbench/contrib/notebook/test/notebookViewModel.test.ts +++ b/src/vs/workbench/contrib/notebook/test/notebookViewModel.test.ts @@ -251,7 +251,7 @@ function getVisibleCells(cells: any[], hiddenRanges: ICellRange[]) { result.push(...cells.slice(start, hiddenRanges[hiddenRangeIndex].start)); } - start = hiddenRanges[hiddenRangeIndex].start + hiddenRanges[hiddenRangeIndex].length; + start = hiddenRanges[hiddenRangeIndex].end + 1; hiddenRangeIndex++; } @@ -281,40 +281,46 @@ suite('NotebookViewModel Decorations', () => { [['var e = 5;'], 'javascript', CellKind.Code, [], { editable: false, runnable: false }], ], (editor, viewModel) => { - const trackedId = viewModel.setTrackedRange('test', { start: 1, length: 2 }, TrackedRangeStickiness.GrowsOnlyWhenTypingAfter); + const trackedId = viewModel.setTrackedRange('test', { start: 1, end: 2 }, TrackedRangeStickiness.GrowsOnlyWhenTypingAfter); assert.deepEqual(viewModel.getTrackedRange(trackedId!), { start: 1, - length: 2 + + end: 2, }); viewModel.insertCell(0, new TestCell(viewModel.viewType, 5, ['var d = 6;'], 'javascript', CellKind.Code, []), true); assert.deepEqual(viewModel.getTrackedRange(trackedId!), { start: 2, - length: 2 + + end: 3 }); viewModel.deleteCell(0, true); assert.deepEqual(viewModel.getTrackedRange(trackedId!), { start: 1, - length: 2 + + end: 2 }); viewModel.insertCell(3, new TestCell(viewModel.viewType, 6, ['var d = 7;'], 'javascript', CellKind.Code, []), true); assert.deepEqual(viewModel.getTrackedRange(trackedId!), { start: 1, - length: 3 + + end: 3 }); viewModel.deleteCell(3, true); assert.deepEqual(viewModel.getTrackedRange(trackedId!), { start: 1, - length: 2 + + end: 2 }); viewModel.deleteCell(1, true); assert.deepEqual(viewModel.getTrackedRange(trackedId!), { start: 0, - length: 2 + + end: 1 }); } ); @@ -335,22 +341,25 @@ suite('NotebookViewModel Decorations', () => { [['var e = 7;'], 'javascript', CellKind.Code, [], { editable: false, runnable: false }], ], (editor, viewModel) => { - const trackedId = viewModel.setTrackedRange('test', { start: 1, length: 3 }, TrackedRangeStickiness.GrowsOnlyWhenTypingAfter); + const trackedId = viewModel.setTrackedRange('test', { start: 1, end: 3 }, TrackedRangeStickiness.GrowsOnlyWhenTypingAfter); assert.deepEqual(viewModel.getTrackedRange(trackedId!), { start: 1, - length: 3 + + end: 3 }); viewModel.insertCell(5, new TestCell(viewModel.viewType, 8, ['var d = 9;'], 'javascript', CellKind.Code, []), true); assert.deepEqual(viewModel.getTrackedRange(trackedId!), { start: 1, - length: 3 + + end: 3 }); viewModel.insertCell(4, new TestCell(viewModel.viewType, 9, ['var d = 10;'], 'javascript', CellKind.Code, []), true); assert.deepEqual(viewModel.getTrackedRange(trackedId!), { start: 1, - length: 4 + + end: 4 }); } ); @@ -358,20 +367,20 @@ suite('NotebookViewModel Decorations', () => { test('reduce range', function () { assert.deepEqual(reduceCellRanges([ - { start: 0, length: 2 }, - { start: 1, length: 2 }, - { start: 4, length: 2 } + { start: 0, end: 1 }, + { start: 1, end: 2 }, + { start: 4, end: 6 } ]), [ - { start: 0, length: 3 }, - { start: 4, length: 2 } + { start: 0, end: 2 }, + { start: 4, end: 6 } ]); assert.deepEqual(reduceCellRanges([ - { start: 0, length: 2 }, - { start: 1, length: 2 }, - { start: 3, length: 2 } + { start: 0, end: 1 }, + { start: 1, end: 2 }, + { start: 3, end: 4 } ]), [ - { start: 0, length: 5 } + { start: 0, end: 4 } ]); }); @@ -381,7 +390,7 @@ suite('NotebookViewModel Decorations', () => { assert.deepEqual( getVisibleCells( [1, 2, 3, 4, 5], - [{ start: 1, length: 2 }] + [{ start: 1, end: 2 }] ), [1, 4, 5] ); @@ -390,8 +399,8 @@ suite('NotebookViewModel Decorations', () => { getVisibleCells( [1, 2, 3, 4, 5, 6, 7, 8, 9], [ - { start: 1, length: 2 }, - { start: 4, length: 2 } + { start: 1, end: 2 }, + { start: 4, end: 5 } ] ), [1, 4, 7, 8, 9] @@ -400,15 +409,15 @@ suite('NotebookViewModel Decorations', () => { const original = getVisibleCells( [1, 2, 3, 4, 5, 6, 7, 8, 9], [ - { start: 1, length: 2 }, - { start: 4, length: 2 } + { start: 1, end: 2 }, + { start: 4, end: 5 } ] ); const modified = getVisibleCells( [1, 2, 3, 4, 5, 6, 7, 8, 9], [ - { start: 2, length: 3 } + { start: 2, end: 4 } ] ); From 3c0630a09f8f085a3a875854538911e889697d4d Mon Sep 17 00:00:00 2001 From: Pine Wu Date: Mon, 20 Apr 2020 13:35:46 -0700 Subject: [PATCH 22/44] Fix microsoft/vscode-pull-request-github#1578 --- src/vs/editor/contrib/hover/hover.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/editor/contrib/hover/hover.css b/src/vs/editor/contrib/hover/hover.css index 8f35b9aec75..cb5bc10f4a2 100644 --- a/src/vs/editor/contrib/hover/hover.css +++ b/src/vs/editor/contrib/hover/hover.css @@ -30,7 +30,7 @@ } .monaco-editor-hover .markdown-hover > .hover-contents:not(.code-hover-contents) hr { - min-width: 100vw; + min-width: 100%; } .monaco-editor-hover p, From 3ba52212b38c3ea9d77dab556b9051eb56cf832d Mon Sep 17 00:00:00 2001 From: SteVen Batten Date: Mon, 20 Apr 2020 13:50:28 -0700 Subject: [PATCH 23/44] fix editor drag flicker --- src/vs/workbench/browser/parts/editor/editorPart.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/vs/workbench/browser/parts/editor/editorPart.ts b/src/vs/workbench/browser/parts/editor/editorPart.ts index d708c9e414f..ba081b013e1 100644 --- a/src/vs/workbench/browser/parts/editor/editorPart.ts +++ b/src/vs/workbench/browser/parts/editor/editorPart.ts @@ -831,6 +831,11 @@ export class EditorPart extends Part implements IEditorGroupsService, IEditorGro parent.appendChild(overlay); CompositeDragAndDropObserver.INSTANCE.registerTarget(this.element, { + onDragOver: e => { + if (e.eventData.dataTransfer) { + e.eventData.dataTransfer.dropEffect = 'none'; + } + }, onDragStart: e => { toggleClass(overlay, 'visible', true); }, From 432740924d1288eeb735f90b89e485fa6418213f Mon Sep 17 00:00:00 2001 From: SteVen Batten Date: Mon, 20 Apr 2020 15:39:18 -0700 Subject: [PATCH 24/44] check if no visible views when determining title --- .../workbench/services/views/browser/viewDescriptorService.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/services/views/browser/viewDescriptorService.ts b/src/vs/workbench/services/views/browser/viewDescriptorService.ts index 0967eff4b2a..bc178745ef2 100644 --- a/src/vs/workbench/services/views/browser/viewDescriptorService.ts +++ b/src/vs/workbench/services/views/browser/viewDescriptorService.ts @@ -347,7 +347,7 @@ class ViewContainerModel extends Disposable implements IViewContainerModel { private updateContainerInfo(): void { /* Use default container info if one of the visible view descriptors belongs to the current container by default */ - const useDefaultContainerInfo = this.visibleViewDescriptors.some(v => Registry.as(ViewExtensions.ViewsRegistry).getViewContainer(v.id) === this.container); + const useDefaultContainerInfo = this.visibleViewDescriptors.length === 0 || this.visibleViewDescriptors.some(v => Registry.as(ViewExtensions.ViewsRegistry).getViewContainer(v.id) === this.container); const title = useDefaultContainerInfo ? this.container.name : this.visibleViewDescriptors[0]?.name || ''; let titleChanged: boolean = false; if (this._title !== title) { From b442c251ac2aa35abd8288c329f4702a0d842955 Mon Sep 17 00:00:00 2001 From: rebornix Date: Mon, 20 Apr 2020 15:57:05 -0700 Subject: [PATCH 25/44] Fix #95560. Inline vs loader when creating notebook webview in Web env. --- .../notebook/browser/notebookEditor.ts | 2 + .../view/renderers/backLayerWebView.ts | 56 ++++++++++++++++--- 2 files changed, 50 insertions(+), 8 deletions(-) diff --git a/src/vs/workbench/contrib/notebook/browser/notebookEditor.ts b/src/vs/workbench/contrib/notebook/browser/notebookEditor.ts index dcc4626d35f..4c6bf9e03f8 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookEditor.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookEditor.ts @@ -377,6 +377,8 @@ export class NotebookEditor extends BaseEditor implements INotebookEditor { this.list?.rowsContainer.insertAdjacentElement('afterbegin', this.webview!.element); } + await this.webview.waitForInitialization(); + this.eventDispatcher = new NotebookEventDispatcher(); this.notebookViewModel = this.instantiationService.createInstance(NotebookViewModel, input.viewType!, model, this.eventDispatcher, this.getLayoutInfo()); this.editorEditable?.set(!!this.notebookViewModel.metadata?.editable); diff --git a/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts b/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts index f3c8121062e..e28677dd2ac 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts @@ -18,6 +18,7 @@ import { CELL_MARGIN, CELL_RUN_GUTTER } from 'vs/workbench/contrib/notebook/brow import { Emitter, Event } from 'vs/base/common/event'; import { IOpenerService } from 'vs/platform/opener/common/opener'; import { getPathFromAmdModule } from 'vs/base/common/amd'; +import { isWeb } from 'vs/base/common/platform'; export interface IDimentionMessage { __vscode_notebook_message: boolean; @@ -84,7 +85,7 @@ type IMessage = IDimentionMessage | IScrollAckMessage | IWheelMessage; let version = 0; export class BackLayerWebView extends Disposable { element: HTMLElement; - webview: WebviewElement; + webview!: WebviewElement; insetMapping: Map = new Map(); reversedInsetMapping: Map = new Map(); preloadsCache: Map = new Map(); @@ -92,12 +93,13 @@ export class BackLayerWebView extends Disposable { rendererRootsCache: URI[] = []; private readonly _onMessage = this._register(new Emitter()); public readonly onMessage: Event = this._onMessage.event; + private _initalized: Promise; constructor( public notebookEditor: INotebookEditor, - @IWebviewService webviewService: IWebviewService, - @IOpenerService openerService: IOpenerService, + @IWebviewService readonly webviewService: IWebviewService, + @IOpenerService readonly openerService: IOpenerService, @INotebookService private readonly notebookService: INotebookService, ) { super(); @@ -111,8 +113,40 @@ export class BackLayerWebView extends Disposable { const pathsPath = getPathFromAmdModule(require, 'vs/loader.js'); const loader = URI.file(pathsPath).with({ scheme: WebviewResourceScheme }); - const outputNodePadding = 8; - let content = /* html */` + let coreDependencies = ''; + let resolveFunc: () => void; + + this._initalized = new Promise((resolve, reject) => { + resolveFunc = resolve; + }); + + if (!isWeb) { + coreDependencies = ``; + const htmlContent = this.generateContent(8, coreDependencies); + this.initialize(htmlContent); + resolveFunc!(); + } else { + fetch(pathsPath).then(async response => { + if (response.status !== 200) { + throw new Error(response.statusText); + } + + const loaderJs = await response.text(); + + coreDependencies = ` + +`; + const htmlContent = this.generateContent(8, coreDependencies); + this.initialize(htmlContent); + resolveFunc!(); + }); + } + } + + generateContent(outputNodePadding: number, coreDependencies: string) { + return /* html */` @@ -134,7 +168,7 @@ export class BackLayerWebView extends Disposable { - + ${coreDependencies}
`; + } - this.webview = this._createInset(webviewService, content); + initialize(content: string) { + this.webview = this._createInset(this.webviewService, content); this.webview.mountTo(this.element); this._register(this.webview.onDidClickLink(link => { - openerService.open(link, { fromUserGesture: true }); + this.openerService.open(link, { fromUserGesture: true }); })); this._register(this.webview.onMessage((data: IMessage) => { @@ -356,6 +392,10 @@ export class BackLayerWebView extends Disposable { })); } + async waitForInitialization() { + await this._initalized; + } + private _createInset(webviewService: IWebviewService, content: string) { const rootPath = URI.file(path.dirname(getPathFromAmdModule(require, ''))); this.localResourceRootsCache = [...this.notebookService.getNotebookProviderResourceRoots(), rootPath]; From 12e7d78cfb8554d5f2a2b4f1800e173761f3f39e Mon Sep 17 00:00:00 2001 From: rebornix Date: Mon, 20 Apr 2020 16:58:42 -0700 Subject: [PATCH 26/44] notebookviewmodel.viewcells readonly --- .../notebook/browser/notebookEditor.ts | 4 +-- .../notebook/browser/view/notebookCellList.ts | 4 +-- .../browser/viewModel/notebookViewModel.ts | 26 +++++++++++++++++++ 3 files changed, 30 insertions(+), 4 deletions(-) diff --git a/src/vs/workbench/contrib/notebook/browser/notebookEditor.ts b/src/vs/workbench/contrib/notebook/browser/notebookEditor.ts index 4c6bf9e03f8..fc19c97adb9 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookEditor.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookEditor.ts @@ -446,7 +446,7 @@ export class NotebookEditor extends BaseEditor implements INotebookEditor { if (this.list) { state.scrollPosition = { left: this.list.scrollLeft, top: this.list.scrollTop }; let cellHeights: { [key: number]: number } = {}; - for (let i = 0; i < this.viewModel!.viewCells.length; i++) { + for (let i = 0; i < this.viewModel!.length; i++) { const elm = this.viewModel!.viewCells[i] as CellViewModel; if (elm.cellKind === CellKind.Code) { cellHeights[i] = elm.layoutInfo.totalHeight; @@ -618,7 +618,7 @@ export class NotebookEditor extends BaseEditor implements INotebookEditor { async moveCellDown(cell: ICellViewModel): Promise { const index = this.notebookViewModel!.getCellIndex(cell); - if (index === this.notebookViewModel!.viewCells.length - 1) { + if (index === this.notebookViewModel!.length - 1) { return; } diff --git a/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts b/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts index 8f79892f20c..bec586e4db0 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts @@ -222,7 +222,7 @@ export class NotebookCellList extends WorkbenchList implements ID index++; } - for (let i = start; i < this._viewModel!.viewCells.length; i++) { + for (let i = start; i < this._viewModel!.length; i++) { ret.push(1); } @@ -237,7 +237,7 @@ export class NotebookCellList extends WorkbenchList implements ID // console.log(this.hiddenRangesPrefixSum.getAccumulatedValue(i)); // } - // for (let i = 0; i < this._viewModel!.viewCells.length; i++) { + // for (let i = 0; i < this._viewModel!.length; i++) { // console.log(this.hiddenRangesPrefixSum.getIndexOf(i)); // } diff --git a/src/vs/workbench/contrib/notebook/browser/viewModel/notebookViewModel.ts b/src/vs/workbench/contrib/notebook/browser/viewModel/notebookViewModel.ts index 5c9c08f9b08..0ccff8612d8 100644 --- a/src/vs/workbench/contrib/notebook/browser/viewModel/notebookViewModel.ts +++ b/src/vs/workbench/contrib/notebook/browser/viewModel/notebookViewModel.ts @@ -128,6 +128,7 @@ let MODEL_ID = 0; export class NotebookViewModel extends Disposable implements FoldingRegionDelegate { private _localStore: DisposableStore = this._register(new DisposableStore()); private _viewCells: CellViewModel[] = []; + private _handleToViewCellMapping = new Map(); private _currentTokenSource: CancellationTokenSource | undefined; @@ -143,6 +144,14 @@ export class NotebookViewModel extends Disposable implements FoldingRegionDelega return this._viewCells; } + set viewCells(_: ICellViewModel[]) { + throw new Error('NotebookViewModel.viewCells is readonly'); + } + + get length(): number { + return this._viewCells.length; + } + get notebookDocument() { return this._model.notebook; } @@ -244,6 +253,10 @@ export class NotebookViewModel extends Disposable implements FoldingRegionDelega return createCellViewModel(this.instantiationService, this, cell); }); + this._viewCells.forEach(cell => { + this._handleToViewCellMapping.set(cell.handle, cell); + }); + this._foldingModel = new FoldingModel(); this._foldingModel.attachViewModel(this); @@ -348,6 +361,10 @@ export class NotebookViewModel extends Disposable implements FoldingRegionDelega return this._viewCells.indexOf(cell as CellViewModel); } + hasCell(cell: ICellViewModel) { + return this._handleToViewCellMapping.has(cell.handle); + } + getVersionId() { return this._model.notebook.versionId; } @@ -463,13 +480,17 @@ export class NotebookViewModel extends Disposable implements FoldingRegionDelega private _insertCellDelegate(insertIndex: number, insertCell: CellViewModel) { this._viewCells!.splice(insertIndex, 0, insertCell); + this._handleToViewCellMapping.set(insertCell.handle, insertCell); this._model.insertCell(insertCell.model, insertIndex); this._localStore.add(insertCell); this._onDidChangeViewCells.fire({ synchronous: true, splices: [[insertIndex, 0, [insertCell]]] }); } private _deleteCellDelegate(deleteIndex: number, cell: ICell) { + const deleteCell = this._viewCells[deleteIndex]; this._viewCells.splice(deleteIndex, 1); + this._handleToViewCellMapping.delete(deleteCell.handle); + this._model.deleteCell(deleteIndex); this._onDidChangeViewCells.fire({ synchronous: true, splices: [[deleteIndex, 1, []]] }); } @@ -478,6 +499,7 @@ export class NotebookViewModel extends Disposable implements FoldingRegionDelega const cell = this._model.notebook.createCellTextModel(source, language, type, [], undefined); let newCell: CellViewModel = createCellViewModel(this.instantiationService, this, cell); this._viewCells!.splice(index, 0, newCell); + this._handleToViewCellMapping.set(newCell.handle, newCell); this._model.insertCell(cell, index); this._localStore.add(newCell); this.undoService.pushElement(new InsertCellEdit(this.uri, index, newCell, { @@ -493,6 +515,8 @@ export class NotebookViewModel extends Disposable implements FoldingRegionDelega insertCell(index: number, cell: NotebookCellTextModel, synchronous: boolean): CellViewModel { let newCell: CellViewModel = createCellViewModel(this.instantiationService, this, cell); this._viewCells!.splice(index, 0, newCell); + this._handleToViewCellMapping.set(newCell.handle, newCell); + this._model.insertCell(newCell.model, index); this._localStore.add(newCell); this.undoService.pushElement(new InsertCellEdit(this.uri, index, newCell, { @@ -508,6 +532,8 @@ export class NotebookViewModel extends Disposable implements FoldingRegionDelega deleteCell(index: number, synchronous: boolean) { let viewCell = this._viewCells[index]; this._viewCells.splice(index, 1); + this._handleToViewCellMapping.delete(viewCell.handle); + this._model.deleteCell(index); this.undoService.pushElement(new DeleteCellEdit(this.uri, index, viewCell, { From adb075097bf6f1969f1e29f0419826778a93a9bd Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Mon, 20 Apr 2020 17:04:09 -0500 Subject: [PATCH 27/44] Persist focused notebook cell, dom focus on load --- .../contrib/notebook/browser/notebookBrowser.ts | 7 ++++++- .../contrib/notebook/browser/notebookEditor.ts | 16 ++++++++++++++++ .../notebook/browser/view/notebookCellList.ts | 4 ++-- .../browser/viewModel/notebookViewModel.ts | 9 +++++---- 4 files changed, 29 insertions(+), 7 deletions(-) diff --git a/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts b/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts index 12b658d9d60..da14e859c9a 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts @@ -5,7 +5,7 @@ import { IMouseWheelEvent } from 'vs/base/browser/mouseEvent'; import { IListEvent, IListMouseEvent } from 'vs/base/browser/ui/list/list'; -import { IListOptions, IListStyles } from 'vs/base/browser/ui/list/listWidget'; +import { IListOptions, IListStyles, List } from 'vs/base/browser/ui/list/listWidget'; import { ProgressBar } from 'vs/base/browser/ui/progressbar/progressbar'; import { ToolBar } from 'vs/base/browser/ui/toolbar/toolbar'; import { CancellationTokenSource } from 'vs/base/common/cancellation'; @@ -341,6 +341,11 @@ export interface INotebookCellList { updateOptions(options: IListOptions): void; layout(height?: number, width?: number): void; dispose(): void; + + // TODO resolve differences between List and INotebookCellList + getFocus(): number[]; + setFocus(indexes: number[]): void; + setSelection(indexes: number[]): void; } export interface BaseCellRenderTemplate { diff --git a/src/vs/workbench/contrib/notebook/browser/notebookEditor.ts b/src/vs/workbench/contrib/notebook/browser/notebookEditor.ts index fc19c97adb9..4d557738ebe 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookEditor.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookEditor.ts @@ -312,6 +312,7 @@ export class NotebookEditor extends BaseEditor implements INotebookEditor { focus() { super.focus(); this.editorFocus?.set(true); + this.list?.domFocus(); } async setInput(input: NotebookEditorInput, options: EditorOptions | undefined, token: CancellationToken): Promise { @@ -431,6 +432,10 @@ export class NotebookEditor extends BaseEditor implements INotebookEditor { this.list!.layout(); + this.restoreTextEditorViewState(viewState); + } + + private restoreTextEditorViewState(viewState: INotebookEditorViewState | undefined): void { if (viewState?.scrollPosition !== undefined) { this.list!.scrollTop = viewState!.scrollPosition.top; this.list!.scrollLeft = viewState!.scrollPosition.left; @@ -438,6 +443,12 @@ export class NotebookEditor extends BaseEditor implements INotebookEditor { this.list!.scrollTop = 0; this.list!.scrollLeft = 0; } + + if (typeof viewState?.focus === 'number') { + this.list!.setFocus([viewState.focus]); + } else { + this.list!.setFocus([0]); + } } private saveTextEditorViewState(input: NotebookEditorInput): void { @@ -456,6 +467,11 @@ export class NotebookEditor extends BaseEditor implements INotebookEditor { } state.cellTotalHeights = cellHeights; + + const focus = this.list.getFocus()[0]; + if (focus) { + state.focus = focus; + } } this.editorMemento.saveEditorState(this.group, input.resource, state); diff --git a/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts b/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts index bec586e4db0..9a47fcbb1e9 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts @@ -6,7 +6,7 @@ import * as DOM from 'vs/base/browser/dom'; import { IMouseWheelEvent } from 'vs/base/browser/mouseEvent'; import { IListRenderer, IListVirtualDelegate, ListError } from 'vs/base/browser/ui/list/list'; -import { IListStyles, IStyleController, MouseController } from 'vs/base/browser/ui/list/listWidget'; +import { IListStyles, IStyleController, MouseController, IListOptions } from 'vs/base/browser/ui/list/listWidget'; import { Emitter, Event } from 'vs/base/common/event'; import { DisposableStore, IDisposable } from 'vs/base/common/lifecycle'; import { isMacintosh } from 'vs/base/common/platform'; @@ -120,7 +120,7 @@ export class NotebookCellList extends WorkbenchList implements ID } - protected createMouseController(): MouseController { + protected createMouseController(_options: IListOptions): MouseController { return new NotebookMouseController(this); } diff --git a/src/vs/workbench/contrib/notebook/browser/viewModel/notebookViewModel.ts b/src/vs/workbench/contrib/notebook/browser/viewModel/notebookViewModel.ts index 0ccff8612d8..7ca36ebc0ed 100644 --- a/src/vs/workbench/contrib/notebook/browser/viewModel/notebookViewModel.ts +++ b/src/vs/workbench/contrib/notebook/browser/viewModel/notebookViewModel.ts @@ -33,6 +33,7 @@ export interface INotebookEditorViewState { editorViewStates: { [key: number]: editorCommon.ICodeEditorViewState | null }; cellTotalHeights?: { [key: number]: number }; scrollPosition?: { left: number; top: number; }; + focus?: number; } export interface ICellModelDecorations { @@ -578,8 +579,8 @@ export class NotebookViewModel extends Disposable implements FoldingRegionDelega } saveEditorViewState(): INotebookEditorViewState { - const state: { [key: number]: boolean } = {}; - this._viewCells.filter(cell => cell.editState === CellEditState.Editing).forEach(cell => state[cell.model.handle] = true); + const editingCells: { [key: number]: boolean } = {}; + this._viewCells.filter(cell => cell.editState === CellEditState.Editing).forEach(cell => editingCells[cell.model.handle] = true); const editorViewStates: { [key: number]: editorCommon.ICodeEditorViewState } = {}; this._viewCells.map(cell => ({ handle: cell.model.handle, state: cell.saveEditorViewState() })).forEach(viewState => { if (viewState.state) { @@ -588,8 +589,8 @@ export class NotebookViewModel extends Disposable implements FoldingRegionDelega }); return { - editingCells: state, - editorViewStates: editorViewStates + editingCells, + editorViewStates }; } From d88608d3ab9db535108a24bed2e0eafd8b7268fa Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Mon, 20 Apr 2020 20:41:02 -0500 Subject: [PATCH 28/44] Add onWillHide hook so DOM focus can be checked before persisting state --- .../browser/parts/editor/baseEditor.ts | 2 ++ .../browser/parts/editor/editorControl.ts | 1 + .../notebook/browser/notebookBrowser.ts | 2 +- .../notebook/browser/notebookEditor.ts | 32 +++++++++++++------ .../browser/viewModel/notebookViewModel.ts | 1 + 5 files changed, 28 insertions(+), 10 deletions(-) diff --git a/src/vs/workbench/browser/parts/editor/baseEditor.ts b/src/vs/workbench/browser/parts/editor/baseEditor.ts index e57afd9b033..d6fb1701dbb 100644 --- a/src/vs/workbench/browser/parts/editor/baseEditor.ts +++ b/src/vs/workbench/browser/parts/editor/baseEditor.ts @@ -107,6 +107,8 @@ export abstract class BaseEditor extends Composite implements IEditorPane { onHide() { } + onWillHide() { } + /** * Called to create the editor in the parent HTMLElement. */ diff --git a/src/vs/workbench/browser/parts/editor/editorControl.ts b/src/vs/workbench/browser/parts/editor/editorControl.ts index c7acfb330d2..6262b90a14a 100644 --- a/src/vs/workbench/browser/parts/editor/editorControl.ts +++ b/src/vs/workbench/browser/parts/editor/editorControl.ts @@ -205,6 +205,7 @@ export class EditorControl extends Disposable { // Remove editor pane from parent and hide const editorPaneContainer = this._activeEditorPane.getContainer(); if (editorPaneContainer) { + this._activeEditorPane.onWillHide(); this.parent.removeChild(editorPaneContainer); hide(editorPaneContainer); this._activeEditorPane.onHide(); diff --git a/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts b/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts index da14e859c9a..cc453afd092 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts @@ -5,7 +5,7 @@ import { IMouseWheelEvent } from 'vs/base/browser/mouseEvent'; import { IListEvent, IListMouseEvent } from 'vs/base/browser/ui/list/list'; -import { IListOptions, IListStyles, List } from 'vs/base/browser/ui/list/listWidget'; +import { IListOptions, IListStyles } from 'vs/base/browser/ui/list/listWidget'; import { ProgressBar } from 'vs/base/browser/ui/progressbar/progressbar'; import { ToolBar } from 'vs/base/browser/ui/toolbar/toolbar'; import { CancellationTokenSource } from 'vs/base/common/cancellation'; diff --git a/src/vs/workbench/contrib/notebook/browser/notebookEditor.ts b/src/vs/workbench/contrib/notebook/browser/notebookEditor.ts index 4d557738ebe..258ff4ec28d 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookEditor.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookEditor.ts @@ -280,7 +280,11 @@ export class NotebookEditor extends BaseEditor implements INotebookEditor { return this.webview?.webview; } - onHide() { + onWillHide() { + if (this.input && this.input instanceof NotebookEditorInput && !this.input.isDisposed()) { + this.saveTextEditorViewState(this.input); + } + this.editorFocus?.set(false); if (this.webview) { this.localStore.clear(); @@ -354,10 +358,6 @@ export class NotebookEditor extends BaseEditor implements INotebookEditor { } clearInput(): void { - if (this.input && this.input instanceof NotebookEditorInput && !this.input.isDisposed()) { - this.saveTextEditorViewState(this.input); - } - super.clearInput(); } @@ -444,10 +444,16 @@ export class NotebookEditor extends BaseEditor implements INotebookEditor { this.list!.scrollLeft = 0; } - if (typeof viewState?.focus === 'number') { - this.list!.setFocus([viewState.focus]); - } else { - this.list!.setFocus([0]); + const focusIdx = typeof viewState?.focus === 'number' ? viewState.focus : 0; + this.list!.setFocus([focusIdx]); + this.list!.setSelection([focusIdx]); + + if (viewState?.editorFocused) { + this.list?.focusView(); + const cell = this.notebookViewModel?.viewCells[focusIdx]; + if (cell) { + cell.focusMode = CellFocusMode.Editor; + } } } @@ -470,6 +476,14 @@ export class NotebookEditor extends BaseEditor implements INotebookEditor { const focus = this.list.getFocus()[0]; if (focus) { + const element = this.notebookViewModel!.viewCells[focus]; + const itemDOM = this.list?.domElementOfElement(element!); + let editorFocused = false; + if (document.activeElement && itemDOM && itemDOM.contains(document.activeElement)) { + editorFocused = true; + } + + state.editorFocused = editorFocused; state.focus = focus; } } diff --git a/src/vs/workbench/contrib/notebook/browser/viewModel/notebookViewModel.ts b/src/vs/workbench/contrib/notebook/browser/viewModel/notebookViewModel.ts index 7ca36ebc0ed..c0989ec3922 100644 --- a/src/vs/workbench/contrib/notebook/browser/viewModel/notebookViewModel.ts +++ b/src/vs/workbench/contrib/notebook/browser/viewModel/notebookViewModel.ts @@ -34,6 +34,7 @@ export interface INotebookEditorViewState { cellTotalHeights?: { [key: number]: number }; scrollPosition?: { left: number; top: number; }; focus?: number; + editorFocused?: boolean; } export interface ICellModelDecorations { From d76919141d77f2b9dd6a7daf540b0794af6eba50 Mon Sep 17 00:00:00 2001 From: Eric Amodio Date: Mon, 20 Apr 2020 23:33:44 -0400 Subject: [PATCH 29/44] =?UTF-8?q?Adds=20`defaultLayout`=20option=20for=20e?= =?UTF-8?q?mbedding=20=E2=80=94=20#990?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/vs/workbench/browser/web.main.ts | 191 +++++++++++++++++++++++++- src/vs/workbench/workbench.web.api.ts | 48 +++++++ 2 files changed, 238 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/browser/web.main.ts b/src/vs/workbench/browser/web.main.ts index b3cd6fdc700..15e8631f1ed 100644 --- a/src/vs/workbench/browser/web.main.ts +++ b/src/vs/workbench/browser/web.main.ts @@ -38,7 +38,7 @@ import { FileUserDataProvider } from 'vs/workbench/services/userData/common/file import { BACKUPS } from 'vs/platform/environment/common/environment'; import { joinPath } from 'vs/base/common/resources'; import { BrowserStorageService } from 'vs/platform/storage/browser/storageService'; -import { IStorageService } from 'vs/platform/storage/common/storage'; +import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; import { getThemeTypeSelector, DARK, HIGH_CONTRAST, LIGHT } from 'vs/platform/theme/common/themeService'; import { registerWindowDriver } from 'vs/platform/driver/browser/driver'; import { BufferLogService } from 'vs/platform/log/common/bufferLog'; @@ -52,6 +52,23 @@ import { coalesce } from 'vs/base/common/arrays'; import { InMemoryFileSystemProvider } from 'vs/platform/files/common/inMemoryFilesystemProvider'; import { WebResourceIdentityService, IResourceIdentityService } from 'vs/platform/resource/common/resourceIdentityService'; import { ICommandService } from 'vs/platform/commands/common/commands'; +import { firstSessionDateStorageKey } from 'vs/platform/telemetry/common/telemetry'; + +interface PanelActivityState { + id: string; + name?: string; + pinned: boolean; + order: number; + visible: boolean; +} + +interface SideBarActivityState { + id: string; + pinned: boolean; + order: number; + visible: boolean; +} + class BrowserMain extends Disposable { @@ -81,6 +98,8 @@ class BrowserMain extends Disposable { // Listeners this.registerListeners(workbench, services.storageService); + this.applyDefaultLayout(services.storageService); + // Driver if (this.configuration.driver) { (async () => this._register(await registerWindowDriver()))(); @@ -101,6 +120,176 @@ class BrowserMain extends Disposable { }); } + private applyDefaultLayout(storageService: BrowserStorageService) { + const { defaultLayout } = this.configuration; + if (!defaultLayout) { + return; + } + + const firstRun = storageService.get(firstSessionDateStorageKey, StorageScope.GLOBAL); + if (firstRun !== undefined) { + return; + } + + const { sidebar } = defaultLayout; + if (sidebar) { + if (sidebar.visible !== undefined) { + if (sidebar.visible) { + storageService.remove('workbench.sidebar.hidden', StorageScope.WORKSPACE); + } else { + storageService.store('workbench.sidebar.hidden', true, StorageScope.WORKSPACE); + } + } + + if (sidebar.containers !== undefined) { + const sidebarState: SideBarActivityState[] = []; + + let order = -1; + for (const container of sidebar.containers.sort((a, b) => (a.order ?? 1) - (b.order ?? 1))) { + let viewletId; + switch (container.id) { + case 'explorer': + viewletId = 'workbench.view.explorer'; + break; + case 'run': + viewletId = 'workbench.view.debug'; + break; + case 'scm': + viewletId = 'workbench.view.scm'; + break; + case 'search': + viewletId = 'workbench.view.search'; + break; + case 'extensions': + viewletId = 'workbench.view.extensions'; + break; + case 'remote': + viewletId = 'workbench.view.remote'; + break; + default: + viewletId = `workbench.view.extension.${container.id}`; + } + + order = container.order ?? (order + 1); + const state: SideBarActivityState = { + id: viewletId, + order: order, + pinned: true, + visible: true + }; + + if (container.active) { + storageService.store('workbench.sidebar.activeviewletid', viewletId, StorageScope.WORKSPACE); + } else { + if (container.visible !== undefined) { + state.pinned = container.visible; + state.visible = container.visible; + } + } + + sidebarState.push(state); + + if (container.views !== undefined) { + const viewsState: { id: string, isHidden?: boolean, order?: number }[] = []; + const viewsWorkspaceState: { [id: string]: { collapsed: boolean, isHidden?: boolean, size?: number } } = {}; + + for (const view of container.views) { + if (view.order !== undefined || view.visible !== undefined) { + viewsState.push({ + id: view.id, + isHidden: view.visible === undefined ? undefined : !view.visible, + order: view.order === undefined ? undefined : view.order + }); + } + + if (view.collapsed !== undefined) { + viewsWorkspaceState[view.id] = { + collapsed: view.collapsed, + isHidden: view.visible === undefined ? undefined : !view.visible, + }; + } + } + + storageService.store(`${viewletId}.state.hidden`, JSON.stringify(viewsState), StorageScope.GLOBAL); + storageService.store(`${viewletId}.state`, JSON.stringify(viewsWorkspaceState), StorageScope.WORKSPACE); + } + } + + storageService.store('workbench.activity.pinnedViewlets2', JSON.stringify(sidebarState), StorageScope.GLOBAL); + } + } + + const { panel } = defaultLayout; + if (panel) { + if (panel.visible !== undefined) { + if (panel.visible) { + storageService.store('workbench.panel.hidden', false, StorageScope.WORKSPACE); + } else { + storageService.remove('workbench.panel.hidden', StorageScope.WORKSPACE); + } + } + + if (panel.containers !== undefined) { + const panelState: PanelActivityState[] = []; + + let order = -1; + for (const container of panel.containers.sort((a, b) => (a.order ?? 1) - (b.order ?? 1))) { + let name; + let panelId = container.id; + switch (panelId) { + case 'terminal': + name = 'Terminal'; + panelId = 'workbench.panel.terminal'; + break; + case 'debug': + name = 'Debug Console'; + panelId = 'workbench.panel.repl'; + break; + case 'problems': + name = 'Problems'; + panelId = 'workbench.panel.markers'; + break; + case 'output': + name = 'Output'; + panelId = 'workbench.panel.output'; + break; + case 'comments': + name = 'Comments'; + panelId = 'workbench.panel.comments'; + break; + case 'refactor': + name = 'Refactor Preview'; + panelId = 'refactorPreview'; + default: + continue; + } + + order = container.order ?? (order + 1); + const state: PanelActivityState = { + id: panelId, + name: name, + order: order, + pinned: true, + visible: true + }; + + if (container.active) { + storageService.store('workbench.panelpart.activepanelid', panelId, StorageScope.WORKSPACE); + } else { + if (container.visible !== undefined) { + state.pinned = container.visible; + state.visible = container.visible; + } + } + + panelState.push(state); + } + + storageService.store('workbench.panel.pinnedPanels', JSON.stringify(panelState), StorageScope.GLOBAL); + } + } + } + private registerListeners(workbench: Workbench, storageService: BrowserStorageService): void { // Layout diff --git a/src/vs/workbench/workbench.web.api.ts b/src/vs/workbench/workbench.web.api.ts index 9c76a188fb7..64cd3474aa2 100644 --- a/src/vs/workbench/workbench.web.api.ts +++ b/src/vs/workbench/workbench.web.api.ts @@ -105,6 +105,52 @@ interface IHomeIndicator { title: string; } +interface IDefaultSideBarLayout { + visible?: boolean; + containers?: ({ + id: 'explorer' | 'run' | 'scm' | 'search' | 'extensions' | 'remote' | string; + active: true; + order?: number; + views?: { + id: string; + order?: number; + visible?: boolean; + collapsed?: boolean; + }[]; + } | { + id: 'explorer' | 'run' | 'scm' | 'search' | 'extensions' | 'remote' | string; + active?: false | undefined; + order?: number; + visible?: boolean; + views?: { + id: string; + order?: number; + visible?: boolean; + collapsed?: boolean; + }[]; + })[]; +} + +interface IDefaultPanelLayout { + visible?: boolean; + containers?: ({ + id: 'terminal' | 'debug' | 'problems' | 'output' | 'comments' | string; + order?: number; + active: true; + } | { + id: 'terminal' | 'debug' | 'problems' | 'output' | 'comments' | string; + order?: number; + active?: false | undefined; + visible?: boolean; + })[]; +} + +interface IDefaultLayout { + sidebar?: IDefaultSideBarLayout; + panel?: IDefaultPanelLayout; + // editors?: IDefaultWorkspaceEditorsLayout +} + interface IWorkbenchConstructionOptions { //#region Connection related configuration @@ -221,6 +267,8 @@ interface IWorkbenchConstructionOptions { readonly driver?: boolean; //#endregion + + defaultLayout?: IDefaultLayout; } interface IWorkbench { From bdd729aec55217b9d6f9ec2b803cd6c905fe8737 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Tue, 21 Apr 2020 06:07:53 +0200 Subject: [PATCH 30/44] fix flickering of icon in activity bar --- .../parts/activitybar/activitybarActions.ts | 11 ++- .../parts/activitybar/activitybarPart.ts | 67 ++++++++++++++----- 2 files changed, 58 insertions(+), 20 deletions(-) diff --git a/src/vs/workbench/browser/parts/activitybar/activitybarActions.ts b/src/vs/workbench/browser/parts/activitybar/activitybarActions.ts index 22065b584f5..7e776ac4e1d 100644 --- a/src/vs/workbench/browser/parts/activitybar/activitybarActions.ts +++ b/src/vs/workbench/browser/parts/activitybar/activitybarActions.ts @@ -29,6 +29,7 @@ import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { createAndFillInActionBarActions } from 'vs/platform/actions/browser/menuEntryActionViewItem'; import { ICommandService } from 'vs/platform/commands/common/commands'; +import { isString } from 'vs/base/common/types'; export class ViewletActivityAction extends ActivityAction { @@ -250,13 +251,17 @@ export class PlaceHolderViewletActivityAction extends ViewletActivityAction { constructor( id: string, - name: string, - iconUrl: URI | undefined, + icon: URI | string | undefined, @IViewletService viewletService: IViewletService, @IWorkbenchLayoutService layoutService: IWorkbenchLayoutService, @ITelemetryService telemetryService: ITelemetryService ) { - super({ id, name: id, iconUrl }, viewletService, layoutService, telemetryService); + super({ + id, + name: id, + iconUrl: URI.isUri(icon) ? icon : undefined, + cssClass: isString(icon) ? icon : undefined + }, viewletService, layoutService, telemetryService); } } diff --git a/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts b/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts index 283444ae5a3..3bd2cd44b98 100644 --- a/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts +++ b/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts @@ -47,6 +47,7 @@ interface IPlaceholderViewlet { id: string; name?: string; iconUrl?: UriComponents; + iconCSS?: string; views?: { when?: string }[]; } @@ -60,7 +61,7 @@ interface IPinnedViewlet { interface ICachedViewlet { id: string; name?: string; - iconUrl?: UriComponents; + icon?: URI | string; pinned: boolean; order?: number; visible: boolean; @@ -444,7 +445,7 @@ export class ActivitybarPart extends Part implements IActivityBarService { } else { const cachedComposite = this.cachedViewlets.filter(c => c.id === compositeId)[0]; compositeActions = { - activityAction: this.instantiationService.createInstance(PlaceHolderViewletActivityAction, compositeId, cachedComposite?.name || compositeId, cachedComposite?.iconUrl ? URI.revive(cachedComposite.iconUrl) : undefined), + activityAction: this.instantiationService.createInstance(PlaceHolderViewletActivityAction, compositeId, cachedComposite?.icon), pinnedAction: new PlaceHolderToggleCompositePinnedAction(compositeId, this.compositeBar) }; } @@ -637,21 +638,25 @@ export class ActivitybarPart extends Part implements IActivityBarService { private saveCachedViewlets(): void { const state: ICachedViewlet[] = []; - const allViewlets = this.viewletService.getViewlets(); const compositeItems = this.compositeBar.getCompositeBarItems(); for (const compositeItem of compositeItems) { const viewContainer = this.getViewContainer(compositeItem.id); - const viewlet = allViewlets.filter(({ id }) => id === compositeItem.id)[0]; - if (viewlet) { + if (viewContainer) { + const viewContainerModel = this.viewDescriptorService.getViewContainerModel(viewContainer); const views: { when: string | undefined }[] = []; - if (viewContainer) { - const viewContainerModel = this.viewDescriptorService.getViewContainerModel(viewContainer); - for (const { when } of viewContainerModel.allViewDescriptors) { - views.push({ when: when ? when.serialize() : undefined }); - } + for (const { when } of viewContainerModel.allViewDescriptors) { + views.push({ when: when ? when.serialize() : undefined }); } - state.push({ id: compositeItem.id, name: viewlet.name, iconUrl: viewlet.iconUrl && viewlet.iconUrl.scheme === Schemas.file ? viewlet.iconUrl : undefined, views, pinned: compositeItem.pinned, order: compositeItem.order, visible: compositeItem.visible }); + state.push({ + id: compositeItem.id, + name: viewContainerModel.title, + icon: URI.isUri(viewContainerModel.icon) && viewContainerModel.icon.scheme === Schemas.file ? viewContainerModel.icon : viewContainerModel.icon, + views, + pinned: compositeItem.pinned, + order: compositeItem.order, + visible: compositeItem.visible + }); } else { state.push({ id: compositeItem.id, pinned: compositeItem.pinned, order: compositeItem.order, visible: false }); } @@ -661,12 +666,13 @@ export class ActivitybarPart extends Part implements IActivityBarService { } private getCachedViewlets(): ICachedViewlet[] { - const cachedViewlets: Array = JSON.parse(this.pinnedViewletsValue); - for (const placeholderViewlet of JSON.parse(this.placeholderViewletsValue)) { + const cachedViewlets: ICachedViewlet[] = this.getPinnedViewlets(); + for (const placeholderViewlet of this.getPlaceholderViewlets()) { const cachedViewlet = cachedViewlets.filter(cached => cached.id === placeholderViewlet.id)[0]; if (cachedViewlet) { cachedViewlet.name = placeholderViewlet.name; - cachedViewlet.iconUrl = placeholderViewlet.iconUrl; + cachedViewlet.icon = placeholderViewlet.iconCSS ? placeholderViewlet.iconCSS : + placeholderViewlet.iconUrl ? URI.revive(placeholderViewlet.iconUrl) : undefined; cachedViewlet.views = placeholderViewlet.views; } } @@ -675,8 +681,27 @@ export class ActivitybarPart extends Part implements IActivityBarService { } private storeCachedViewletsState(cachedViewlets: ICachedViewlet[]): void { - this.pinnedViewletsValue = JSON.stringify(cachedViewlets.map(({ id, pinned, visible, order }) => ({ id, pinned, visible, order }))); - this.placeholderViewletsValue = JSON.stringify(cachedViewlets.map(({ id, iconUrl, name, views }) => ({ id, iconUrl, name, views }))); + this.setPinnedViewlets(cachedViewlets.map(({ id, pinned, visible, order }) => ({ + id, + pinned, + visible, + order + }))); + this.setPlaceholderViewlets(cachedViewlets.map(({ id, icon, name, views }) => ({ + id, + iconUrl: URI.isUri(icon) ? icon : undefined, + iconCSS: isString(icon) ? icon : undefined, + name, + views + }))); + } + + private getPinnedViewlets(): IPinnedViewlet[] { + return JSON.parse(this.pinnedViewletsValue); + } + + private setPinnedViewlets(pinnedViewlets: IPinnedViewlet[]): void { + this.pinnedViewletsValue = JSON.stringify(pinnedViewlets); } private _pinnedViewletsValue: string | undefined; @@ -703,6 +728,14 @@ export class ActivitybarPart extends Part implements IActivityBarService { this.storageService.store(ActivitybarPart.PINNED_VIEWLETS, value, StorageScope.GLOBAL); } + private getPlaceholderViewlets(): IPlaceholderViewlet[] { + return JSON.parse(this.placeholderViewletsValue); + } + + private setPlaceholderViewlets(placeholderViewlets: IPlaceholderViewlet[]): void { + this.placeholderViewletsValue = JSON.stringify(placeholderViewlets); + } + private _placeholderViewletsValue: string | undefined; private get placeholderViewletsValue(): string { if (!this._placeholderViewletsValue) { @@ -732,7 +765,7 @@ export class ActivitybarPart extends Part implements IActivityBarService { if (value !== undefined) { const storedStates: Array = JSON.parse(value); const cachedViewlets = storedStates.map(c => { - const serialized: ICachedViewlet = typeof c === 'string' /* migration from pinned states to composites states */ ? { id: c, pinned: true, order: undefined, visible: true, name: undefined, iconUrl: undefined, views: undefined } : c; + const serialized: ICachedViewlet = typeof c === 'string' /* migration from pinned states to composites states */ ? { id: c, pinned: true, order: undefined, visible: true, name: undefined, icon: undefined, views: undefined } : c; serialized.visible = isUndefinedOrNull(serialized.visible) ? true : serialized.visible; return serialized; }); From 4a7dc991d0e66126de7d9badffc7aacfc2948471 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Tue, 21 Apr 2020 06:28:37 +0200 Subject: [PATCH 31/44] :lipstick: --- .../parts/activitybar/activitybarPart.ts | 36 +++++++------------ 1 file changed, 12 insertions(+), 24 deletions(-) diff --git a/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts b/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts index 3bd2cd44b98..be402f1c6d9 100644 --- a/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts +++ b/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts @@ -199,7 +199,6 @@ export class ActivitybarPart extends Part implements IActivityBarService { private onDidRegisterExtensions(): void { this.removeNotExistingComposites(); - this.saveCachedViewlets(); } @@ -477,14 +476,14 @@ export class ActivitybarPart extends Part implements IActivityBarService { } for (const viewlet of viewlets) { - this.enableCompositeActions(viewlet); const viewContainer = this.getViewContainer(viewlet.id)!; const viewContainerModel = this.viewDescriptorService.getViewContainerModel(viewContainer); - this.onDidChangeActiveViews(viewlet, viewContainerModel, viewContainer.hideIfEmpty); + this.updateActivity(viewlet, viewContainerModel); + this.onDidChangeActiveViews(viewContainer, viewContainerModel); const disposables = new DisposableStore(); - disposables.add(viewContainerModel.onDidChangeActiveViewDescriptors(() => this.onDidChangeActiveViews(viewlet, viewContainerModel, viewContainer.hideIfEmpty))); disposables.add(viewContainerModel.onDidChangeContainerInfo(() => this.updateActivity(viewlet, viewContainerModel))); + disposables.add(viewContainerModel.onDidChangeActiveViewDescriptors(() => this.onDidChangeActiveViews(viewContainer, viewContainerModel))); this.viewletDisposables.set(viewlet.id, disposables); } @@ -501,13 +500,12 @@ export class ActivitybarPart extends Part implements IActivityBarService { } private updateActivity(viewlet: ViewletDescriptor, viewContainerModel: IViewContainerModel): void { - const icon = viewContainerModel.icon; const activity: IActivity = { id: viewlet.id, name: viewContainerModel.title, - cssClass: isString(icon) ? icon : undefined, - iconUrl: icon instanceof URI ? icon : undefined, + iconUrl: URI.isUri(viewContainerModel.icon) ? viewContainerModel.icon : undefined, + cssClass: isString(viewContainerModel.icon) ? viewContainerModel.icon : undefined, keybindingId: viewlet.keybindingId }; @@ -517,14 +515,15 @@ export class ActivitybarPart extends Part implements IActivityBarService { if (pinnedAction instanceof PlaceHolderToggleCompositePinnedAction) { pinnedAction.setActivity(activity); } + + this.saveCachedViewlets(); } - private onDidChangeActiveViews(viewlet: ViewletDescriptor, viewDescriptors: IViewContainerModel, hideIfEmpty?: boolean): void { - if (viewDescriptors.activeViewDescriptors.length) { - this.updateActivity(viewlet, viewDescriptors); - this.compositeBar.addComposite(viewlet); - } else if (hideIfEmpty) { - this.hideComposite(viewlet.id); + private onDidChangeActiveViews(viewContainer: ViewContainer, viewContainerModel: IViewContainerModel): void { + if (viewContainerModel.activeViewDescriptors.length) { + this.compositeBar.addComposite(viewContainer); + } else if (viewContainer.hideIfEmpty) { + this.hideComposite(viewContainer.id); } } @@ -559,17 +558,6 @@ export class ActivitybarPart extends Part implements IActivityBarService { } } - private enableCompositeActions(viewlet: ViewletDescriptor): void { - const { activityAction, pinnedAction } = this.getCompositeActions(viewlet.id); - if (activityAction instanceof PlaceHolderViewletActivityAction) { - activityAction.setActivity(viewlet); - } - - if (pinnedAction instanceof PlaceHolderToggleCompositePinnedAction) { - pinnedAction.setActivity(viewlet); - } - } - getPinnedViewletIds(): string[] { const pinnedCompositeIds = this.compositeBar.getPinnedComposites().map(v => v.id); From 66efeebc55f8e01dd929ae17e23e113eb657dbcd Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Tue, 21 Apr 2020 06:46:04 +0200 Subject: [PATCH 32/44] :lipstick: --- .../parts/activitybar/activitybarPart.ts | 12 +++--- .../browser/parts/panel/panelPart.ts | 42 +++++++------------ 2 files changed, 21 insertions(+), 33 deletions(-) diff --git a/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts b/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts index be402f1c6d9..c6a7c1b52a4 100644 --- a/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts +++ b/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts @@ -478,11 +478,11 @@ export class ActivitybarPart extends Part implements IActivityBarService { for (const viewlet of viewlets) { const viewContainer = this.getViewContainer(viewlet.id)!; const viewContainerModel = this.viewDescriptorService.getViewContainerModel(viewContainer); - this.updateActivity(viewlet, viewContainerModel); + this.updateActivity(viewContainer, viewContainerModel); this.onDidChangeActiveViews(viewContainer, viewContainerModel); const disposables = new DisposableStore(); - disposables.add(viewContainerModel.onDidChangeContainerInfo(() => this.updateActivity(viewlet, viewContainerModel))); + disposables.add(viewContainerModel.onDidChangeContainerInfo(() => this.updateActivity(viewContainer, viewContainerModel))); disposables.add(viewContainerModel.onDidChangeActiveViewDescriptors(() => this.onDidChangeActiveViews(viewContainer, viewContainerModel))); this.viewletDisposables.set(viewlet.id, disposables); @@ -499,17 +499,17 @@ export class ActivitybarPart extends Part implements IActivityBarService { this.hideComposite(viewletId); } - private updateActivity(viewlet: ViewletDescriptor, viewContainerModel: IViewContainerModel): void { + private updateActivity(viewContainer: ViewContainer, viewContainerModel: IViewContainerModel): void { const activity: IActivity = { - id: viewlet.id, + id: viewContainer.id, name: viewContainerModel.title, iconUrl: URI.isUri(viewContainerModel.icon) ? viewContainerModel.icon : undefined, cssClass: isString(viewContainerModel.icon) ? viewContainerModel.icon : undefined, - keybindingId: viewlet.keybindingId + keybindingId: viewContainer.focusCommand?.id || viewContainer.id }; - const { activityAction, pinnedAction } = this.getCompositeActions(viewlet.id); + const { activityAction, pinnedAction } = this.getCompositeActions(viewContainer.id); activityAction.setActivity(activity); if (pinnedAction instanceof PlaceHolderToggleCompositePinnedAction) { diff --git a/src/vs/workbench/browser/parts/panel/panelPart.ts b/src/vs/workbench/browser/parts/panel/panelPart.ts index e2ca8aa50a8..1ba0b10b40b 100644 --- a/src/vs/workbench/browser/parts/panel/panelPart.ts +++ b/src/vs/workbench/browser/parts/panel/panelPart.ts @@ -209,14 +209,14 @@ export class PanelPart extends CompositePart implements IPanelService { } for (const panel of panels) { - this.enableCompositeActions(panel); const viewContainer = this.getViewContainer(panel.id)!; const viewContainerModel = this.viewDescriptorService.getViewContainerModel(viewContainer); - this.onDidChangeActiveViews(panel, viewContainerModel, viewContainer.hideIfEmpty); + this.updateActivity(viewContainer, viewContainerModel); + this.onDidChangeActiveViews(viewContainer, viewContainerModel); const disposables = new DisposableStore(); - disposables.add(viewContainerModel.onDidChangeActiveViewDescriptors(() => this.onDidChangeActiveViews(panel, viewContainerModel, viewContainer.hideIfEmpty))); - disposables.add(viewContainerModel.onDidChangeContainerInfo(() => this.onDidUpdateViews(panel, viewContainerModel))); + disposables.add(viewContainerModel.onDidChangeActiveViewDescriptors(() => this.onDidChangeActiveViews(viewContainer, viewContainerModel))); + disposables.add(viewContainerModel.onDidChangeContainerInfo(() => this.updateActivity(viewContainer, viewContainerModel))); this.panelDisposables.set(panel.id, disposables); } @@ -232,40 +232,28 @@ export class PanelPart extends CompositePart implements IPanelService { this.hideComposite(panelId); } - private enableCompositeActions(panel: PanelDescriptor): void { - const { activityAction, pinnedAction } = this.getCompositeActions(panel.id); - activityAction.setActivity(panel); - - if (pinnedAction instanceof PlaceHolderToggleCompositePinnedAction) { - pinnedAction.setActivity(panel); - } - } - - private updateActivity(panel: PanelDescriptor, viewContainerModel: IViewContainerModel): void { + private updateActivity(viewContainer: ViewContainer, viewContainerModel: IViewContainerModel): void { const activity: IActivity = { - id: panel.id, + id: viewContainer.id, name: viewContainerModel.title, - keybindingId: panel.keybindingId + keybindingId: viewContainer.focusCommand?.id }; - const { activityAction, pinnedAction } = this.getCompositeActions(panel.id); + const { activityAction, pinnedAction } = this.getCompositeActions(viewContainer.id); activityAction.setActivity(activity); if (pinnedAction instanceof PlaceHolderToggleCompositePinnedAction) { pinnedAction.setActivity(activity); } + + this.saveCachedPanels(); } - private onDidUpdateViews(panel: PanelDescriptor, viewDescriptors: IViewContainerModel): void { - this.updateActivity(panel, viewDescriptors); - } - - private onDidChangeActiveViews(panel: PanelDescriptor, viewDescriptors: IViewContainerModel, hideIfEmpty?: boolean): void { - if (viewDescriptors.activeViewDescriptors.length) { - this.updateActivity(panel, viewDescriptors); - this.compositeBar.addComposite(panel); - } else if (hideIfEmpty) { - this.hideComposite(panel.id); + private onDidChangeActiveViews(viewContainer: ViewContainer, viewContainerModel: IViewContainerModel): void { + if (viewContainerModel.activeViewDescriptors.length) { + this.compositeBar.addComposite(viewContainer); + } else if (viewContainer.hideIfEmpty) { + this.hideComposite(viewContainer.id); } } From b83339a3a7bd8e3ec8a8b79deda9c37e0d91f094 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Tue, 21 Apr 2020 06:53:08 +0200 Subject: [PATCH 33/44] separate model from service --- .../views/browser/viewDescriptorService.ts | 646 +---------------- .../views/common/viewContainerModel.ts | 654 ++++++++++++++++++ 2 files changed, 657 insertions(+), 643 deletions(-) create mode 100644 src/vs/workbench/services/views/common/viewContainerModel.ts diff --git a/src/vs/workbench/services/views/browser/viewDescriptorService.ts b/src/vs/workbench/services/views/browser/viewDescriptorService.ts index bc178745ef2..13f75686a26 100644 --- a/src/vs/workbench/services/views/browser/viewDescriptorService.ts +++ b/src/vs/workbench/services/views/browser/viewDescriptorService.ts @@ -3,8 +3,8 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { ViewContainerLocation, IViewDescriptorService, ViewContainer, IViewsRegistry, IViewContainersRegistry, IViewDescriptor, Extensions as ViewExtensions, IViewContainerModel, IAddedViewDescriptorRef, IViewDescriptorRef } from 'vs/workbench/common/views'; -import { IContextKey, RawContextKey, IContextKeyService, IReadableSet } from 'vs/platform/contextkey/common/contextkey'; +import { ViewContainerLocation, IViewDescriptorService, ViewContainer, IViewsRegistry, IViewContainersRegistry, IViewDescriptor, Extensions as ViewExtensions } from 'vs/workbench/common/views'; +import { IContextKey, RawContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IStorageService, StorageScope, IWorkspaceStorageChangeEvent } from 'vs/platform/storage/common/storage'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { Registry } from 'vs/platform/registry/common/platform'; @@ -17,647 +17,7 @@ import { IStorageKeysSyncRegistryService } from 'vs/platform/userDataSync/common import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { generateUuid } from 'vs/base/common/uuid'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { URI } from 'vs/base/common/uri'; -import { firstIndex, move } from 'vs/base/common/arrays'; -import { isUndefined, isUndefinedOrNull } from 'vs/base/common/types'; -import { values } from 'vs/base/common/map'; -import { isEqual } from 'vs/base/common/resources'; - -class CounterSet implements IReadableSet { - - private map = new Map(); - - add(value: T): CounterSet { - this.map.set(value, (this.map.get(value) || 0) + 1); - return this; - } - - delete(value: T): boolean { - let counter = this.map.get(value) || 0; - - if (counter === 0) { - return false; - } - - counter--; - - if (counter === 0) { - this.map.delete(value); - } else { - this.map.set(value, counter); - } - - return true; - } - - has(value: T): boolean { - return this.map.has(value); - } -} - -interface IStoredWorkspaceViewState { - collapsed: boolean; - isHidden: boolean; - size?: number; - order?: number; -} - -interface IStoredGlobalViewState { - id: string; - isHidden: boolean; - order?: number; -} - -interface IViewDescriptorState { - visibleGlobal: boolean | undefined; - visibleWorkspace: boolean | undefined; - collapsed: boolean | undefined; - active: boolean - order?: number; - size?: number; -} - -class ViewDescriptorsState extends Disposable { - - private readonly workspaceViewsStateStorageId: string; - private readonly globalViewsStateStorageId: string; - private readonly state: Map; - - private _onDidChangeStoredState = this._register(new Emitter<{ id: string, visible: boolean }[]>()); - readonly onDidChangeStoredState = this._onDidChangeStoredState.event; - - constructor( - viewContainerStorageId: string, - @IStorageService private readonly storageService: IStorageService, - @IStorageKeysSyncRegistryService storageKeysSyncRegistryService: IStorageKeysSyncRegistryService - ) { - super(); - - this.globalViewsStateStorageId = `${viewContainerStorageId}.hidden`; - this.workspaceViewsStateStorageId = viewContainerStorageId; - storageKeysSyncRegistryService.registerStorageKey({ key: this.globalViewsStateStorageId, version: 1 }); - this._register(this.storageService.onDidChangeStorage(e => this.onDidStorageChange(e))); - - this.state = this.initialize(); - } - - set(id: string, state: IViewDescriptorState): void { - this.state.set(id, state); - } - - get(id: string): IViewDescriptorState | undefined { - return this.state.get(id); - } - - updateState(viewDescriptors: ReadonlyArray): void { - this.updateWorkspaceState(viewDescriptors); - this.updateGlobalState(viewDescriptors); - } - - private updateWorkspaceState(viewDescriptors: ReadonlyArray): void { - const storedViewsStates: { [id: string]: IStoredWorkspaceViewState; } = JSON.parse(this.storageService.get(this.workspaceViewsStateStorageId, StorageScope.WORKSPACE, '{}')); - for (const viewDescriptor of viewDescriptors) { - const viewState = this.state.get(viewDescriptor.id); - if (viewState) { - storedViewsStates[viewDescriptor.id] = { - collapsed: !!viewState.collapsed, - isHidden: !viewState.visibleWorkspace, - size: viewState.size, - order: viewDescriptor.workspace && viewState ? viewState.order : undefined - }; - } - } - - if (Object.keys(storedViewsStates).length > 0) { - this.storageService.store(this.workspaceViewsStateStorageId, JSON.stringify(storedViewsStates), StorageScope.WORKSPACE); - } else { - this.storageService.remove(this.workspaceViewsStateStorageId, StorageScope.WORKSPACE); - } - } - - private updateGlobalState(viewDescriptors: ReadonlyArray): void { - const storedGlobalState = this.getStoredGlobalState(); - for (const viewDescriptor of viewDescriptors) { - const state = this.state.get(viewDescriptor.id); - storedGlobalState.set(viewDescriptor.id, { - id: viewDescriptor.id, - isHidden: state && viewDescriptor.canToggleVisibility ? !state.visibleGlobal : false, - order: !viewDescriptor.workspace && state ? state.order : undefined - }); - } - this.setStoredGlobalState(storedGlobalState); - } - - private onDidStorageChange(e: IWorkspaceStorageChangeEvent): void { - if (e.key === this.globalViewsStateStorageId && e.scope === StorageScope.GLOBAL - && this.globalViewsStatesValue !== this.getStoredGlobalViewsStatesValue() /* This checks if current window changed the value or not */) { - this._globalViewsStatesValue = undefined; - const storedViewsVisibilityStates = this.getStoredGlobalState(); - const changedStates: { id: string, visible: boolean }[] = []; - for (const [id, storedState] of storedViewsVisibilityStates) { - const state = this.state.get(id); - if (state) { - if (state.visibleGlobal !== !storedState.isHidden) { - changedStates.push({ id, visible: !storedState.isHidden }); - } - } - } - if (changedStates.length) { - this._onDidChangeStoredState.fire(changedStates); - } - } - } - - private initialize(): Map { - const viewStates = new Map(); - const workspaceViewsStates = <{ [id: string]: IStoredWorkspaceViewState; }>JSON.parse(this.storageService.get(this.workspaceViewsStateStorageId, StorageScope.WORKSPACE, '{}')); - for (const id of Object.keys(workspaceViewsStates)) { - const workspaceViewState = workspaceViewsStates[id]; - viewStates.set(id, { - active: false, - visibleGlobal: undefined, - visibleWorkspace: isUndefined(workspaceViewState.isHidden) ? undefined : !workspaceViewState.isHidden, - collapsed: workspaceViewState.collapsed, - order: workspaceViewState.order, - size: workspaceViewState.size, - }); - } - - // Migrate to `viewletStateStorageId` - const value = this.storageService.get(this.globalViewsStateStorageId, StorageScope.WORKSPACE, '[]'); - const { state: workspaceVisibilityStates } = this.parseStoredGlobalState(value); - if (workspaceVisibilityStates.size > 0) { - for (const { id, isHidden } of values(workspaceVisibilityStates)) { - let viewState = viewStates.get(id); - // Not migrated to `viewletStateStorageId` - if (viewState) { - if (isUndefined(viewState.visibleWorkspace)) { - viewState.visibleWorkspace = !isHidden; - } - } else { - viewStates.set(id, { - active: false, - collapsed: undefined, - visibleGlobal: undefined, - visibleWorkspace: !isHidden, - }); - } - } - this.storageService.remove(this.globalViewsStateStorageId, StorageScope.WORKSPACE); - } - - const { state, hasDuplicates } = this.parseStoredGlobalState(this.globalViewsStatesValue); - if (hasDuplicates) { - this.setStoredGlobalState(state); - } - for (const { id, isHidden, order } of values(state)) { - let viewState = viewStates.get(id); - if (viewState) { - viewState.visibleGlobal = !isHidden; - if (!isUndefined(order)) { - viewState.order = order; - } - } else { - viewStates.set(id, { - active: false, - visibleGlobal: !isHidden, - order, - collapsed: undefined, - visibleWorkspace: undefined, - }); - } - } - return viewStates; - } - - private getStoredGlobalState(): Map { - return this.parseStoredGlobalState(this.globalViewsStatesValue).state; - } - - private setStoredGlobalState(storedGlobalState: Map): void { - this.globalViewsStatesValue = JSON.stringify(values(storedGlobalState)); - } - - private parseStoredGlobalState(value: string): { state: Map, hasDuplicates: boolean } { - const storedValue = >JSON.parse(value); - let hasDuplicates = false; - const state = storedValue.reduce((result, storedState) => { - if (typeof storedState === 'string' /* migration */) { - hasDuplicates = hasDuplicates || result.has(storedState); - result.set(storedState, { id: storedState, isHidden: true }); - } else { - hasDuplicates = hasDuplicates || result.has(storedState.id); - result.set(storedState.id, storedState); - } - return result; - }, new Map()); - return { state, hasDuplicates }; - } - - private _globalViewsStatesValue: string | undefined; - private get globalViewsStatesValue(): string { - if (!this._globalViewsStatesValue) { - this._globalViewsStatesValue = this.getStoredGlobalViewsStatesValue(); - } - - return this._globalViewsStatesValue; - } - - private set globalViewsStatesValue(globalViewsStatesValue: string) { - if (this.globalViewsStatesValue !== globalViewsStatesValue) { - this._globalViewsStatesValue = globalViewsStatesValue; - this.setStoredGlobalViewsStatesValue(globalViewsStatesValue); - } - } - - private getStoredGlobalViewsStatesValue(): string { - return this.storageService.get(this.globalViewsStateStorageId, StorageScope.GLOBAL, '[]'); - } - - private setStoredGlobalViewsStatesValue(value: string): void { - this.storageService.store(this.globalViewsStateStorageId, value, StorageScope.GLOBAL); - } - -} - -interface IViewDescriptorItem { - viewDescriptor: IViewDescriptor; - state: IViewDescriptorState; -} - -class ViewContainerModel extends Disposable implements IViewContainerModel { - - private readonly contextKeys = new CounterSet(); - private viewDescriptorItems: IViewDescriptorItem[] = []; - private viewDescriptorsState: ViewDescriptorsState; - - // Container Info - private _title!: string; - get title(): string { return this._title; } - private _icon: URI | string | undefined; - get icon(): URI | string | undefined { return this._icon; } - - private _onDidChangeContainerInfo = this._register(new Emitter<{ title?: boolean, icon?: boolean }>()); - readonly onDidChangeContainerInfo = this._onDidChangeContainerInfo.event; - - // All View Descriptors - get allViewDescriptors(): ReadonlyArray { return this.viewDescriptorItems.map(item => item.viewDescriptor); } - private _onDidChangeAllViewDescriptors = this._register(new Emitter<{ added: ReadonlyArray, removed: ReadonlyArray }>()); - readonly onDidChangeAllViewDescriptors = this._onDidChangeAllViewDescriptors.event; - - // Active View Descriptors - get activeViewDescriptors(): ReadonlyArray { return this.viewDescriptorItems.filter(item => item.state.active).map(item => item.viewDescriptor); } - private _onDidChangeActiveViewDescriptors = this._register(new Emitter<{ added: ReadonlyArray, removed: ReadonlyArray }>()); - readonly onDidChangeActiveViewDescriptors = this._onDidChangeActiveViewDescriptors.event; - - // Visible View Descriptors - get visibleViewDescriptors(): ReadonlyArray { return this.viewDescriptorItems.filter(item => this.isViewDescriptorVisible(item)).map(item => item.viewDescriptor); } - - private _onDidAddVisibleViewDescriptors = this._register(new Emitter()); - readonly onDidAddVisibleViewDescriptors: Event = this._onDidAddVisibleViewDescriptors.event; - - private _onDidRemoveVisibleViewDescriptors = this._register(new Emitter()); - readonly onDidRemoveVisibleViewDescriptors: Event = this._onDidRemoveVisibleViewDescriptors.event; - - private _onDidMoveVisibleViewDescriptors = this._register(new Emitter<{ from: IViewDescriptorRef; to: IViewDescriptorRef; }>()); - readonly onDidMoveVisibleViewDescriptors: Event<{ from: IViewDescriptorRef; to: IViewDescriptorRef; }> = this._onDidMoveVisibleViewDescriptors.event; - - constructor( - private readonly container: ViewContainer, - @IInstantiationService instantiationService: IInstantiationService, - @IContextKeyService private readonly contextKeyService: IContextKeyService, - ) { - super(); - - this._register(Event.filter(contextKeyService.onDidChangeContext, e => e.affectsSome(this.contextKeys))(() => this.onDidChangeContext())); - this.viewDescriptorsState = this._register(instantiationService.createInstance(ViewDescriptorsState, container.storageId || `${container.id}.state`)); - this._register(this.viewDescriptorsState.onDidChangeStoredState(items => this.updateVisibility(items))); - - this._register(Event.any( - this.onDidAddVisibleViewDescriptors, - this.onDidRemoveVisibleViewDescriptors, - this.onDidMoveVisibleViewDescriptors) - (() => { - this.viewDescriptorsState.updateState(this.allViewDescriptors); - this.updateContainerInfo(); - })); - - this.updateContainerInfo(); - } - - private updateContainerInfo(): void { - /* Use default container info if one of the visible view descriptors belongs to the current container by default */ - const useDefaultContainerInfo = this.visibleViewDescriptors.length === 0 || this.visibleViewDescriptors.some(v => Registry.as(ViewExtensions.ViewsRegistry).getViewContainer(v.id) === this.container); - const title = useDefaultContainerInfo ? this.container.name : this.visibleViewDescriptors[0]?.name || ''; - let titleChanged: boolean = false; - if (this._title !== title) { - this._title = title; - titleChanged = true; - } - - const icon = useDefaultContainerInfo ? this.container.icon : this.visibleViewDescriptors[0]?.containerIcon || 'codicon-window'; - let iconChanged: boolean = false; - if (URI.isUri(icon) && URI.isUri(this._icon) ? isEqual(icon, this._icon) : this._icon !== icon) { - this._icon = icon; - iconChanged = true; - } - - if (titleChanged || iconChanged) { - this._onDidChangeContainerInfo.fire({ title: titleChanged, icon: iconChanged }); - } - } - - isVisible(id: string): boolean { - const viewDescriptorItem = this.viewDescriptorItems.filter(v => v.viewDescriptor.id === id)[0]; - if (!viewDescriptorItem) { - throw new Error(`Unknown view ${id}`); - } - return this.isViewDescriptorVisible(viewDescriptorItem); - } - - setVisible(id: string, visible: boolean, size?: number): void { - this.updateVisibility([{ id, visible, size }]); - } - - private updateVisibility(viewDescriptors: { id: string, visible: boolean, size?: number }[]): void { - const added: IAddedViewDescriptorRef[] = []; - const removed: IViewDescriptorRef[] = []; - - for (const { visibleIndex, viewDescriptorItem, visible, size } of viewDescriptors.map(({ id, visible, size }) => ({ ...this.find(id), visible, size }))) { - const viewDescriptor = viewDescriptorItem.viewDescriptor; - - if (!viewDescriptor.canToggleVisibility) { - throw new Error(`Can't toggle this view's visibility`); - } - - if (this.isViewDescriptorVisible(viewDescriptorItem) === visible) { - return; - } - - if (viewDescriptor.workspace) { - viewDescriptorItem.state.visibleWorkspace = visible; - } else { - viewDescriptorItem.state.visibleGlobal = visible; - } - - if (typeof viewDescriptorItem.state.size === 'number') { - viewDescriptorItem.state.size = size; - } - - if (visible) { - added.push({ index: visibleIndex, viewDescriptor, size: viewDescriptorItem.state.size, collapsed: !!viewDescriptorItem.state.collapsed }); - } else { - removed.push({ index: visibleIndex, viewDescriptor }); - } - } - - if (added.length) { - this._onDidAddVisibleViewDescriptors.fire(added); - } - if (removed.length) { - this._onDidRemoveVisibleViewDescriptors.fire(removed); - } - } - - isCollapsed(id: string): boolean { - return !!this.find(id).viewDescriptorItem.state.collapsed; - } - - setCollapsed(id: string, collapsed: boolean): void { - const { viewDescriptorItem } = this.find(id); - if (viewDescriptorItem.state.collapsed !== collapsed) { - viewDescriptorItem.state.collapsed = collapsed; - } - this.viewDescriptorsState.updateState(this.allViewDescriptors); - } - - getSize(id: string): number | undefined { - return this.find(id).viewDescriptorItem.state.size; - } - - setSize(id: string, size: number): void { - const { viewDescriptorItem } = this.find(id); - if (viewDescriptorItem.state.size !== size) { - viewDescriptorItem.state.size = size; - } - this.viewDescriptorsState.updateState(this.allViewDescriptors); - } - - move(from: string, to: string): void { - const fromIndex = firstIndex(this.viewDescriptorItems, v => v.viewDescriptor.id === from); - const toIndex = firstIndex(this.viewDescriptorItems, v => v.viewDescriptor.id === to); - - const fromViewDescriptor = this.viewDescriptorItems[fromIndex]; - const toViewDescriptor = this.viewDescriptorItems[toIndex]; - - move(this.viewDescriptorItems, fromIndex, toIndex); - - for (let index = 0; index < this.viewDescriptorItems.length; index++) { - this.viewDescriptorItems[index].state.order = index; - } - - this._onDidMoveVisibleViewDescriptors.fire({ - from: { index: fromIndex, viewDescriptor: fromViewDescriptor.viewDescriptor }, - to: { index: toIndex, viewDescriptor: toViewDescriptor.viewDescriptor } - }); - } - - add(viewDescriptors: IViewDescriptor[]): void { - const addedItems: IViewDescriptorItem[] = []; - const addedActiveDescriptors: IViewDescriptor[] = []; - const addedVisibleItems: { index: number, viewDescriptor: IViewDescriptor, size?: number, collapsed: boolean; }[] = []; - - for (const viewDescriptor of viewDescriptors) { - - if (viewDescriptor.when) { - for (const key of viewDescriptor.when.keys()) { - this.contextKeys.add(key); - } - } - - let state = this.viewDescriptorsState.get(viewDescriptor.id); - if (state) { - // set defaults if not set - if (viewDescriptor.workspace) { - state.visibleWorkspace = isUndefinedOrNull(state.visibleWorkspace) ? !viewDescriptor.hideByDefault : state.visibleWorkspace; - } else { - state.visibleGlobal = isUndefinedOrNull(state.visibleGlobal) ? !viewDescriptor.hideByDefault : state.visibleGlobal; - } - state.collapsed = isUndefinedOrNull(state.collapsed) ? !!viewDescriptor.collapsed : state.collapsed; - } else { - state = { - active: false, - visibleGlobal: !viewDescriptor.hideByDefault, - visibleWorkspace: !viewDescriptor.hideByDefault, - collapsed: !!viewDescriptor.collapsed, - }; - } - this.viewDescriptorsState.set(viewDescriptor.id, state); - state.active = this.contextKeyService.contextMatchesRules(viewDescriptor.when); - addedItems.push({ viewDescriptor, state }); - - if (state.active) { - addedActiveDescriptors.push(viewDescriptor); - } - } - - this.viewDescriptorItems.push(...addedItems); - this.viewDescriptorItems.sort(this.compareViewDescriptors.bind(this)); - - for (const viewDescriptorItem of addedItems) { - if (this.isViewDescriptorVisible(viewDescriptorItem)) { - const { visibleIndex } = this.find(viewDescriptorItem.viewDescriptor.id); - addedVisibleItems.push({ index: visibleIndex, viewDescriptor: viewDescriptorItem.viewDescriptor, size: viewDescriptorItem.state.size, collapsed: !!viewDescriptorItem.state.collapsed }); - } - } - - this._onDidChangeAllViewDescriptors.fire({ added: addedItems.map(({ viewDescriptor }) => viewDescriptor), removed: [] }); - if (addedActiveDescriptors.length) { - this._onDidChangeActiveViewDescriptors.fire(({ added: addedActiveDescriptors, removed: [] })); - } - if (addedVisibleItems.length) { - this._onDidAddVisibleViewDescriptors.fire(addedVisibleItems); - } - } - - remove(viewDescriptors: IViewDescriptor[]): void { - const removed: IViewDescriptor[] = []; - const removedItems: IViewDescriptorItem[] = []; - const removedActiveDescriptors: IViewDescriptor[] = []; - const removedVisibleItems: { index: number, viewDescriptor: IViewDescriptor; }[] = []; - - for (const viewDescriptor of viewDescriptors) { - if (viewDescriptor.when) { - for (const key of viewDescriptor.when.keys()) { - this.contextKeys.delete(key); - } - } - const index = firstIndex(this.viewDescriptorItems, i => i.viewDescriptor.id === viewDescriptor.id); - if (index !== -1) { - removed.push(viewDescriptor); - const viewDescriptorItem = this.viewDescriptorItems[index]; - if (viewDescriptorItem.state.active) { - removedActiveDescriptors.push(viewDescriptorItem.viewDescriptor); - } - if (this.isViewDescriptorVisible(viewDescriptorItem)) { - const { visibleIndex } = this.find(viewDescriptorItem.viewDescriptor.id); - removedVisibleItems.push({ index: visibleIndex, viewDescriptor: viewDescriptorItem.viewDescriptor }); - } - removedItems.push(viewDescriptorItem); - } - } - - removedItems.forEach(item => this.viewDescriptorItems.splice(this.viewDescriptorItems.indexOf(item), 1)); - - this._onDidChangeAllViewDescriptors.fire({ added: [], removed }); - if (removedActiveDescriptors.length) { - this._onDidChangeActiveViewDescriptors.fire(({ added: [], removed: removedActiveDescriptors })); - } - if (removedVisibleItems.length) { - this._onDidRemoveVisibleViewDescriptors.fire(removedVisibleItems); - } - } - - private onDidChangeContext(): void { - const addedActiveItems: { item: IViewDescriptorItem, wasVisible: boolean }[] = []; - const removedActiveItems: { item: IViewDescriptorItem, wasVisible: boolean }[] = []; - const removedVisibleItems: { index: number, viewDescriptor: IViewDescriptor; }[] = []; - const addedVisibleItems: { index: number, viewDescriptor: IViewDescriptor, size?: number, collapsed: boolean; }[] = []; - - for (const item of this.viewDescriptorItems) { - const wasActive = item.state.active; - const wasVisible = this.isViewDescriptorVisible(item); - const isActive = this.contextKeyService.contextMatchesRules(item.viewDescriptor.when); - if (wasActive !== isActive) { - if (isActive) { - addedActiveItems.push({ item, wasVisible }); - } else { - removedActiveItems.push({ item, wasVisible }); - } - } - } - - for (const { item, wasVisible } of removedActiveItems) { - if (wasVisible) { - const { visibleIndex } = this.find(item.viewDescriptor.id); - removedVisibleItems.push({ index: visibleIndex, viewDescriptor: item.viewDescriptor }); - } - } - - // Update the State - removedActiveItems.forEach(({ item }) => item.state.active = false); - addedActiveItems.forEach(({ item }) => item.state.active = true); - - for (const { item, wasVisible } of addedActiveItems) { - if (wasVisible !== this.isViewDescriptorVisibleWhenActive(item)) { - const { visibleIndex } = this.find(item.viewDescriptor.id); - addedVisibleItems.push({ index: visibleIndex, viewDescriptor: item.viewDescriptor, size: item.state.size, collapsed: !!item.state.collapsed }); - } - } - - if (addedActiveItems.length || removedActiveItems.length) { - this._onDidChangeActiveViewDescriptors.fire(({ added: addedActiveItems.map(({ item }) => item.viewDescriptor), removed: removedActiveItems.map(({ item }) => item.viewDescriptor) })); - } - if (removedVisibleItems.length) { - this._onDidRemoveVisibleViewDescriptors.fire(removedVisibleItems); - } - if (addedVisibleItems.length) { - this._onDidAddVisibleViewDescriptors.fire(addedVisibleItems); - } - } - - private isViewDescriptorVisible(viewDescriptorItem: IViewDescriptorItem): boolean { - if (!viewDescriptorItem.state.active) { - return false; - } - return this.isViewDescriptorVisibleWhenActive(viewDescriptorItem); - } - - private isViewDescriptorVisibleWhenActive(viewDescriptorItem: IViewDescriptorItem): boolean { - if (viewDescriptorItem.viewDescriptor.workspace) { - return !!viewDescriptorItem.state.visibleWorkspace; - } - return !!viewDescriptorItem.state.visibleGlobal; - } - - private find(id: string): { index: number, visibleIndex: number, viewDescriptorItem: IViewDescriptorItem; } { - for (let i = 0, visibleIndex = 0; i < this.viewDescriptorItems.length; i++) { - const viewDescriptorItem = this.viewDescriptorItems[i]; - if (viewDescriptorItem.viewDescriptor.id === id) { - return { index: i, visibleIndex, viewDescriptorItem: viewDescriptorItem }; - } - if (this.isViewDescriptorVisible(viewDescriptorItem)) { - visibleIndex++; - } - } - throw new Error(`view descriptor ${id} not found`); - } - - private compareViewDescriptors(a: IViewDescriptorItem, b: IViewDescriptorItem): number { - if (a.viewDescriptor.id === b.viewDescriptor.id) { - return 0; - } - - return (this.getViewOrder(a) - this.getViewOrder(b)) || this.getGroupOrderResult(a.viewDescriptor, b.viewDescriptor); - } - - private getViewOrder(viewDescriptorItem: IViewDescriptorItem): number { - const viewOrder = typeof viewDescriptorItem.state.order === 'number' ? viewDescriptorItem.state.order : viewDescriptorItem.viewDescriptor.order; - return typeof viewOrder === 'number' ? viewOrder : Number.MAX_VALUE; - } - - private getGroupOrderResult(a: IViewDescriptor, b: IViewDescriptor) { - if (!a.group || !b.group) { - return 0; - } - - if (a.group === b.group) { - return 0; - } - - return a.group < b.group ? -1 : 1; - } -} +import { ViewContainerModel } from 'vs/workbench/services/views/common/viewContainerModel'; interface ICachedViewContainerInfo { containerId: string; diff --git a/src/vs/workbench/services/views/common/viewContainerModel.ts b/src/vs/workbench/services/views/common/viewContainerModel.ts new file mode 100644 index 00000000000..8f6c8e19e0f --- /dev/null +++ b/src/vs/workbench/services/views/common/viewContainerModel.ts @@ -0,0 +1,654 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { ViewContainer, IViewsRegistry, IViewDescriptor, Extensions as ViewExtensions, IViewContainerModel, IAddedViewDescriptorRef, IViewDescriptorRef } from 'vs/workbench/common/views'; +import { IContextKeyService, IReadableSet } from 'vs/platform/contextkey/common/contextkey'; +import { IStorageService, StorageScope, IWorkspaceStorageChangeEvent } from 'vs/platform/storage/common/storage'; +import { Registry } from 'vs/platform/registry/common/platform'; +import { Disposable } from 'vs/base/common/lifecycle'; +import { Event, Emitter } from 'vs/base/common/event'; +import { IStorageKeysSyncRegistryService } from 'vs/platform/userDataSync/common/storageKeys'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { URI } from 'vs/base/common/uri'; +import { firstIndex, move } from 'vs/base/common/arrays'; +import { isUndefined, isUndefinedOrNull } from 'vs/base/common/types'; +import { values } from 'vs/base/common/map'; +import { isEqual } from 'vs/base/common/resources'; + +class CounterSet implements IReadableSet { + + private map = new Map(); + + add(value: T): CounterSet { + this.map.set(value, (this.map.get(value) || 0) + 1); + return this; + } + + delete(value: T): boolean { + let counter = this.map.get(value) || 0; + + if (counter === 0) { + return false; + } + + counter--; + + if (counter === 0) { + this.map.delete(value); + } else { + this.map.set(value, counter); + } + + return true; + } + + has(value: T): boolean { + return this.map.has(value); + } +} + +interface IStoredWorkspaceViewState { + collapsed: boolean; + isHidden: boolean; + size?: number; + order?: number; +} + +interface IStoredGlobalViewState { + id: string; + isHidden: boolean; + order?: number; +} + +interface IViewDescriptorState { + visibleGlobal: boolean | undefined; + visibleWorkspace: boolean | undefined; + collapsed: boolean | undefined; + active: boolean + order?: number; + size?: number; +} + +class ViewDescriptorsState extends Disposable { + + private readonly workspaceViewsStateStorageId: string; + private readonly globalViewsStateStorageId: string; + private readonly state: Map; + + private _onDidChangeStoredState = this._register(new Emitter<{ id: string, visible: boolean }[]>()); + readonly onDidChangeStoredState = this._onDidChangeStoredState.event; + + constructor( + viewContainerStorageId: string, + @IStorageService private readonly storageService: IStorageService, + @IStorageKeysSyncRegistryService storageKeysSyncRegistryService: IStorageKeysSyncRegistryService + ) { + super(); + + this.globalViewsStateStorageId = `${viewContainerStorageId}.hidden`; + this.workspaceViewsStateStorageId = viewContainerStorageId; + storageKeysSyncRegistryService.registerStorageKey({ key: this.globalViewsStateStorageId, version: 1 }); + this._register(this.storageService.onDidChangeStorage(e => this.onDidStorageChange(e))); + + this.state = this.initialize(); + } + + set(id: string, state: IViewDescriptorState): void { + this.state.set(id, state); + } + + get(id: string): IViewDescriptorState | undefined { + return this.state.get(id); + } + + updateState(viewDescriptors: ReadonlyArray): void { + this.updateWorkspaceState(viewDescriptors); + this.updateGlobalState(viewDescriptors); + } + + private updateWorkspaceState(viewDescriptors: ReadonlyArray): void { + const storedViewsStates: { [id: string]: IStoredWorkspaceViewState; } = JSON.parse(this.storageService.get(this.workspaceViewsStateStorageId, StorageScope.WORKSPACE, '{}')); + for (const viewDescriptor of viewDescriptors) { + const viewState = this.state.get(viewDescriptor.id); + if (viewState) { + storedViewsStates[viewDescriptor.id] = { + collapsed: !!viewState.collapsed, + isHidden: !viewState.visibleWorkspace, + size: viewState.size, + order: viewDescriptor.workspace && viewState ? viewState.order : undefined + }; + } + } + + if (Object.keys(storedViewsStates).length > 0) { + this.storageService.store(this.workspaceViewsStateStorageId, JSON.stringify(storedViewsStates), StorageScope.WORKSPACE); + } else { + this.storageService.remove(this.workspaceViewsStateStorageId, StorageScope.WORKSPACE); + } + } + + private updateGlobalState(viewDescriptors: ReadonlyArray): void { + const storedGlobalState = this.getStoredGlobalState(); + for (const viewDescriptor of viewDescriptors) { + const state = this.state.get(viewDescriptor.id); + storedGlobalState.set(viewDescriptor.id, { + id: viewDescriptor.id, + isHidden: state && viewDescriptor.canToggleVisibility ? !state.visibleGlobal : false, + order: !viewDescriptor.workspace && state ? state.order : undefined + }); + } + this.setStoredGlobalState(storedGlobalState); + } + + private onDidStorageChange(e: IWorkspaceStorageChangeEvent): void { + if (e.key === this.globalViewsStateStorageId && e.scope === StorageScope.GLOBAL + && this.globalViewsStatesValue !== this.getStoredGlobalViewsStatesValue() /* This checks if current window changed the value or not */) { + this._globalViewsStatesValue = undefined; + const storedViewsVisibilityStates = this.getStoredGlobalState(); + const changedStates: { id: string, visible: boolean }[] = []; + for (const [id, storedState] of storedViewsVisibilityStates) { + const state = this.state.get(id); + if (state) { + if (state.visibleGlobal !== !storedState.isHidden) { + changedStates.push({ id, visible: !storedState.isHidden }); + } + } + } + if (changedStates.length) { + this._onDidChangeStoredState.fire(changedStates); + } + } + } + + private initialize(): Map { + const viewStates = new Map(); + const workspaceViewsStates = <{ [id: string]: IStoredWorkspaceViewState; }>JSON.parse(this.storageService.get(this.workspaceViewsStateStorageId, StorageScope.WORKSPACE, '{}')); + for (const id of Object.keys(workspaceViewsStates)) { + const workspaceViewState = workspaceViewsStates[id]; + viewStates.set(id, { + active: false, + visibleGlobal: undefined, + visibleWorkspace: isUndefined(workspaceViewState.isHidden) ? undefined : !workspaceViewState.isHidden, + collapsed: workspaceViewState.collapsed, + order: workspaceViewState.order, + size: workspaceViewState.size, + }); + } + + // Migrate to `viewletStateStorageId` + const value = this.storageService.get(this.globalViewsStateStorageId, StorageScope.WORKSPACE, '[]'); + const { state: workspaceVisibilityStates } = this.parseStoredGlobalState(value); + if (workspaceVisibilityStates.size > 0) { + for (const { id, isHidden } of values(workspaceVisibilityStates)) { + let viewState = viewStates.get(id); + // Not migrated to `viewletStateStorageId` + if (viewState) { + if (isUndefined(viewState.visibleWorkspace)) { + viewState.visibleWorkspace = !isHidden; + } + } else { + viewStates.set(id, { + active: false, + collapsed: undefined, + visibleGlobal: undefined, + visibleWorkspace: !isHidden, + }); + } + } + this.storageService.remove(this.globalViewsStateStorageId, StorageScope.WORKSPACE); + } + + const { state, hasDuplicates } = this.parseStoredGlobalState(this.globalViewsStatesValue); + if (hasDuplicates) { + this.setStoredGlobalState(state); + } + for (const { id, isHidden, order } of values(state)) { + let viewState = viewStates.get(id); + if (viewState) { + viewState.visibleGlobal = !isHidden; + if (!isUndefined(order)) { + viewState.order = order; + } + } else { + viewStates.set(id, { + active: false, + visibleGlobal: !isHidden, + order, + collapsed: undefined, + visibleWorkspace: undefined, + }); + } + } + return viewStates; + } + + private getStoredGlobalState(): Map { + return this.parseStoredGlobalState(this.globalViewsStatesValue).state; + } + + private setStoredGlobalState(storedGlobalState: Map): void { + this.globalViewsStatesValue = JSON.stringify(values(storedGlobalState)); + } + + private parseStoredGlobalState(value: string): { state: Map, hasDuplicates: boolean } { + const storedValue = >JSON.parse(value); + let hasDuplicates = false; + const state = storedValue.reduce((result, storedState) => { + if (typeof storedState === 'string' /* migration */) { + hasDuplicates = hasDuplicates || result.has(storedState); + result.set(storedState, { id: storedState, isHidden: true }); + } else { + hasDuplicates = hasDuplicates || result.has(storedState.id); + result.set(storedState.id, storedState); + } + return result; + }, new Map()); + return { state, hasDuplicates }; + } + + private _globalViewsStatesValue: string | undefined; + private get globalViewsStatesValue(): string { + if (!this._globalViewsStatesValue) { + this._globalViewsStatesValue = this.getStoredGlobalViewsStatesValue(); + } + + return this._globalViewsStatesValue; + } + + private set globalViewsStatesValue(globalViewsStatesValue: string) { + if (this.globalViewsStatesValue !== globalViewsStatesValue) { + this._globalViewsStatesValue = globalViewsStatesValue; + this.setStoredGlobalViewsStatesValue(globalViewsStatesValue); + } + } + + private getStoredGlobalViewsStatesValue(): string { + return this.storageService.get(this.globalViewsStateStorageId, StorageScope.GLOBAL, '[]'); + } + + private setStoredGlobalViewsStatesValue(value: string): void { + this.storageService.store(this.globalViewsStateStorageId, value, StorageScope.GLOBAL); + } + +} + +interface IViewDescriptorItem { + viewDescriptor: IViewDescriptor; + state: IViewDescriptorState; +} + +export class ViewContainerModel extends Disposable implements IViewContainerModel { + + private readonly contextKeys = new CounterSet(); + private viewDescriptorItems: IViewDescriptorItem[] = []; + private viewDescriptorsState: ViewDescriptorsState; + + // Container Info + private _title!: string; + get title(): string { return this._title; } + private _icon: URI | string | undefined; + get icon(): URI | string | undefined { return this._icon; } + + private _onDidChangeContainerInfo = this._register(new Emitter<{ title?: boolean, icon?: boolean }>()); + readonly onDidChangeContainerInfo = this._onDidChangeContainerInfo.event; + + // All View Descriptors + get allViewDescriptors(): ReadonlyArray { return this.viewDescriptorItems.map(item => item.viewDescriptor); } + private _onDidChangeAllViewDescriptors = this._register(new Emitter<{ added: ReadonlyArray, removed: ReadonlyArray }>()); + readonly onDidChangeAllViewDescriptors = this._onDidChangeAllViewDescriptors.event; + + // Active View Descriptors + get activeViewDescriptors(): ReadonlyArray { return this.viewDescriptorItems.filter(item => item.state.active).map(item => item.viewDescriptor); } + private _onDidChangeActiveViewDescriptors = this._register(new Emitter<{ added: ReadonlyArray, removed: ReadonlyArray }>()); + readonly onDidChangeActiveViewDescriptors = this._onDidChangeActiveViewDescriptors.event; + + // Visible View Descriptors + get visibleViewDescriptors(): ReadonlyArray { return this.viewDescriptorItems.filter(item => this.isViewDescriptorVisible(item)).map(item => item.viewDescriptor); } + + private _onDidAddVisibleViewDescriptors = this._register(new Emitter()); + readonly onDidAddVisibleViewDescriptors: Event = this._onDidAddVisibleViewDescriptors.event; + + private _onDidRemoveVisibleViewDescriptors = this._register(new Emitter()); + readonly onDidRemoveVisibleViewDescriptors: Event = this._onDidRemoveVisibleViewDescriptors.event; + + private _onDidMoveVisibleViewDescriptors = this._register(new Emitter<{ from: IViewDescriptorRef; to: IViewDescriptorRef; }>()); + readonly onDidMoveVisibleViewDescriptors: Event<{ from: IViewDescriptorRef; to: IViewDescriptorRef; }> = this._onDidMoveVisibleViewDescriptors.event; + + constructor( + private readonly container: ViewContainer, + @IInstantiationService instantiationService: IInstantiationService, + @IContextKeyService private readonly contextKeyService: IContextKeyService, + ) { + super(); + + this._register(Event.filter(contextKeyService.onDidChangeContext, e => e.affectsSome(this.contextKeys))(() => this.onDidChangeContext())); + this.viewDescriptorsState = this._register(instantiationService.createInstance(ViewDescriptorsState, container.storageId || `${container.id}.state`)); + this._register(this.viewDescriptorsState.onDidChangeStoredState(items => this.updateVisibility(items))); + + this._register(Event.any( + this.onDidAddVisibleViewDescriptors, + this.onDidRemoveVisibleViewDescriptors, + this.onDidMoveVisibleViewDescriptors) + (() => { + this.viewDescriptorsState.updateState(this.allViewDescriptors); + this.updateContainerInfo(); + })); + + this.updateContainerInfo(); + } + + private updateContainerInfo(): void { + /* Use default container info if one of the visible view descriptors belongs to the current container by default */ + const useDefaultContainerInfo = this.visibleViewDescriptors.length === 0 || this.visibleViewDescriptors.some(v => Registry.as(ViewExtensions.ViewsRegistry).getViewContainer(v.id) === this.container); + const title = useDefaultContainerInfo ? this.container.name : this.visibleViewDescriptors[0]?.name || ''; + let titleChanged: boolean = false; + if (this._title !== title) { + this._title = title; + titleChanged = true; + } + + const icon = useDefaultContainerInfo ? this.container.icon : this.visibleViewDescriptors[0]?.containerIcon || 'codicon-window'; + let iconChanged: boolean = false; + if (URI.isUri(icon) && URI.isUri(this._icon) ? isEqual(icon, this._icon) : this._icon !== icon) { + this._icon = icon; + iconChanged = true; + } + + if (titleChanged || iconChanged) { + this._onDidChangeContainerInfo.fire({ title: titleChanged, icon: iconChanged }); + } + } + + isVisible(id: string): boolean { + const viewDescriptorItem = this.viewDescriptorItems.filter(v => v.viewDescriptor.id === id)[0]; + if (!viewDescriptorItem) { + throw new Error(`Unknown view ${id}`); + } + return this.isViewDescriptorVisible(viewDescriptorItem); + } + + setVisible(id: string, visible: boolean, size?: number): void { + this.updateVisibility([{ id, visible, size }]); + } + + private updateVisibility(viewDescriptors: { id: string, visible: boolean, size?: number }[]): void { + const added: IAddedViewDescriptorRef[] = []; + const removed: IViewDescriptorRef[] = []; + + for (const { visibleIndex, viewDescriptorItem, visible, size } of viewDescriptors.map(({ id, visible, size }) => ({ ...this.find(id), visible, size }))) { + const viewDescriptor = viewDescriptorItem.viewDescriptor; + + if (!viewDescriptor.canToggleVisibility) { + throw new Error(`Can't toggle this view's visibility`); + } + + if (this.isViewDescriptorVisible(viewDescriptorItem) === visible) { + return; + } + + if (viewDescriptor.workspace) { + viewDescriptorItem.state.visibleWorkspace = visible; + } else { + viewDescriptorItem.state.visibleGlobal = visible; + } + + if (typeof viewDescriptorItem.state.size === 'number') { + viewDescriptorItem.state.size = size; + } + + if (visible) { + added.push({ index: visibleIndex, viewDescriptor, size: viewDescriptorItem.state.size, collapsed: !!viewDescriptorItem.state.collapsed }); + } else { + removed.push({ index: visibleIndex, viewDescriptor }); + } + } + + if (added.length) { + this._onDidAddVisibleViewDescriptors.fire(added); + } + if (removed.length) { + this._onDidRemoveVisibleViewDescriptors.fire(removed); + } + } + + isCollapsed(id: string): boolean { + return !!this.find(id).viewDescriptorItem.state.collapsed; + } + + setCollapsed(id: string, collapsed: boolean): void { + const { viewDescriptorItem } = this.find(id); + if (viewDescriptorItem.state.collapsed !== collapsed) { + viewDescriptorItem.state.collapsed = collapsed; + } + this.viewDescriptorsState.updateState(this.allViewDescriptors); + } + + getSize(id: string): number | undefined { + return this.find(id).viewDescriptorItem.state.size; + } + + setSize(id: string, size: number): void { + const { viewDescriptorItem } = this.find(id); + if (viewDescriptorItem.state.size !== size) { + viewDescriptorItem.state.size = size; + } + this.viewDescriptorsState.updateState(this.allViewDescriptors); + } + + move(from: string, to: string): void { + const fromIndex = firstIndex(this.viewDescriptorItems, v => v.viewDescriptor.id === from); + const toIndex = firstIndex(this.viewDescriptorItems, v => v.viewDescriptor.id === to); + + const fromViewDescriptor = this.viewDescriptorItems[fromIndex]; + const toViewDescriptor = this.viewDescriptorItems[toIndex]; + + move(this.viewDescriptorItems, fromIndex, toIndex); + + for (let index = 0; index < this.viewDescriptorItems.length; index++) { + this.viewDescriptorItems[index].state.order = index; + } + + this._onDidMoveVisibleViewDescriptors.fire({ + from: { index: fromIndex, viewDescriptor: fromViewDescriptor.viewDescriptor }, + to: { index: toIndex, viewDescriptor: toViewDescriptor.viewDescriptor } + }); + } + + add(viewDescriptors: IViewDescriptor[]): void { + const addedItems: IViewDescriptorItem[] = []; + const addedActiveDescriptors: IViewDescriptor[] = []; + const addedVisibleItems: { index: number, viewDescriptor: IViewDescriptor, size?: number, collapsed: boolean; }[] = []; + + for (const viewDescriptor of viewDescriptors) { + + if (viewDescriptor.when) { + for (const key of viewDescriptor.when.keys()) { + this.contextKeys.add(key); + } + } + + let state = this.viewDescriptorsState.get(viewDescriptor.id); + if (state) { + // set defaults if not set + if (viewDescriptor.workspace) { + state.visibleWorkspace = isUndefinedOrNull(state.visibleWorkspace) ? !viewDescriptor.hideByDefault : state.visibleWorkspace; + } else { + state.visibleGlobal = isUndefinedOrNull(state.visibleGlobal) ? !viewDescriptor.hideByDefault : state.visibleGlobal; + } + state.collapsed = isUndefinedOrNull(state.collapsed) ? !!viewDescriptor.collapsed : state.collapsed; + } else { + state = { + active: false, + visibleGlobal: !viewDescriptor.hideByDefault, + visibleWorkspace: !viewDescriptor.hideByDefault, + collapsed: !!viewDescriptor.collapsed, + }; + } + this.viewDescriptorsState.set(viewDescriptor.id, state); + state.active = this.contextKeyService.contextMatchesRules(viewDescriptor.when); + addedItems.push({ viewDescriptor, state }); + + if (state.active) { + addedActiveDescriptors.push(viewDescriptor); + } + } + + this.viewDescriptorItems.push(...addedItems); + this.viewDescriptorItems.sort(this.compareViewDescriptors.bind(this)); + + for (const viewDescriptorItem of addedItems) { + if (this.isViewDescriptorVisible(viewDescriptorItem)) { + const { visibleIndex } = this.find(viewDescriptorItem.viewDescriptor.id); + addedVisibleItems.push({ index: visibleIndex, viewDescriptor: viewDescriptorItem.viewDescriptor, size: viewDescriptorItem.state.size, collapsed: !!viewDescriptorItem.state.collapsed }); + } + } + + this._onDidChangeAllViewDescriptors.fire({ added: addedItems.map(({ viewDescriptor }) => viewDescriptor), removed: [] }); + if (addedActiveDescriptors.length) { + this._onDidChangeActiveViewDescriptors.fire(({ added: addedActiveDescriptors, removed: [] })); + } + if (addedVisibleItems.length) { + this._onDidAddVisibleViewDescriptors.fire(addedVisibleItems); + } + } + + remove(viewDescriptors: IViewDescriptor[]): void { + const removed: IViewDescriptor[] = []; + const removedItems: IViewDescriptorItem[] = []; + const removedActiveDescriptors: IViewDescriptor[] = []; + const removedVisibleItems: { index: number, viewDescriptor: IViewDescriptor; }[] = []; + + for (const viewDescriptor of viewDescriptors) { + if (viewDescriptor.when) { + for (const key of viewDescriptor.when.keys()) { + this.contextKeys.delete(key); + } + } + const index = firstIndex(this.viewDescriptorItems, i => i.viewDescriptor.id === viewDescriptor.id); + if (index !== -1) { + removed.push(viewDescriptor); + const viewDescriptorItem = this.viewDescriptorItems[index]; + if (viewDescriptorItem.state.active) { + removedActiveDescriptors.push(viewDescriptorItem.viewDescriptor); + } + if (this.isViewDescriptorVisible(viewDescriptorItem)) { + const { visibleIndex } = this.find(viewDescriptorItem.viewDescriptor.id); + removedVisibleItems.push({ index: visibleIndex, viewDescriptor: viewDescriptorItem.viewDescriptor }); + } + removedItems.push(viewDescriptorItem); + } + } + + removedItems.forEach(item => this.viewDescriptorItems.splice(this.viewDescriptorItems.indexOf(item), 1)); + + this._onDidChangeAllViewDescriptors.fire({ added: [], removed }); + if (removedActiveDescriptors.length) { + this._onDidChangeActiveViewDescriptors.fire(({ added: [], removed: removedActiveDescriptors })); + } + if (removedVisibleItems.length) { + this._onDidRemoveVisibleViewDescriptors.fire(removedVisibleItems); + } + } + + private onDidChangeContext(): void { + const addedActiveItems: { item: IViewDescriptorItem, wasVisible: boolean }[] = []; + const removedActiveItems: { item: IViewDescriptorItem, wasVisible: boolean }[] = []; + const removedVisibleItems: { index: number, viewDescriptor: IViewDescriptor; }[] = []; + const addedVisibleItems: { index: number, viewDescriptor: IViewDescriptor, size?: number, collapsed: boolean; }[] = []; + + for (const item of this.viewDescriptorItems) { + const wasActive = item.state.active; + const wasVisible = this.isViewDescriptorVisible(item); + const isActive = this.contextKeyService.contextMatchesRules(item.viewDescriptor.when); + if (wasActive !== isActive) { + if (isActive) { + addedActiveItems.push({ item, wasVisible }); + } else { + removedActiveItems.push({ item, wasVisible }); + } + } + } + + for (const { item, wasVisible } of removedActiveItems) { + if (wasVisible) { + const { visibleIndex } = this.find(item.viewDescriptor.id); + removedVisibleItems.push({ index: visibleIndex, viewDescriptor: item.viewDescriptor }); + } + } + + // Update the State + removedActiveItems.forEach(({ item }) => item.state.active = false); + addedActiveItems.forEach(({ item }) => item.state.active = true); + + for (const { item, wasVisible } of addedActiveItems) { + if (wasVisible !== this.isViewDescriptorVisibleWhenActive(item)) { + const { visibleIndex } = this.find(item.viewDescriptor.id); + addedVisibleItems.push({ index: visibleIndex, viewDescriptor: item.viewDescriptor, size: item.state.size, collapsed: !!item.state.collapsed }); + } + } + + if (addedActiveItems.length || removedActiveItems.length) { + this._onDidChangeActiveViewDescriptors.fire(({ added: addedActiveItems.map(({ item }) => item.viewDescriptor), removed: removedActiveItems.map(({ item }) => item.viewDescriptor) })); + } + if (removedVisibleItems.length) { + this._onDidRemoveVisibleViewDescriptors.fire(removedVisibleItems); + } + if (addedVisibleItems.length) { + this._onDidAddVisibleViewDescriptors.fire(addedVisibleItems); + } + } + + private isViewDescriptorVisible(viewDescriptorItem: IViewDescriptorItem): boolean { + if (!viewDescriptorItem.state.active) { + return false; + } + return this.isViewDescriptorVisibleWhenActive(viewDescriptorItem); + } + + private isViewDescriptorVisibleWhenActive(viewDescriptorItem: IViewDescriptorItem): boolean { + if (viewDescriptorItem.viewDescriptor.workspace) { + return !!viewDescriptorItem.state.visibleWorkspace; + } + return !!viewDescriptorItem.state.visibleGlobal; + } + + private find(id: string): { index: number, visibleIndex: number, viewDescriptorItem: IViewDescriptorItem; } { + for (let i = 0, visibleIndex = 0; i < this.viewDescriptorItems.length; i++) { + const viewDescriptorItem = this.viewDescriptorItems[i]; + if (viewDescriptorItem.viewDescriptor.id === id) { + return { index: i, visibleIndex, viewDescriptorItem: viewDescriptorItem }; + } + if (this.isViewDescriptorVisible(viewDescriptorItem)) { + visibleIndex++; + } + } + throw new Error(`view descriptor ${id} not found`); + } + + private compareViewDescriptors(a: IViewDescriptorItem, b: IViewDescriptorItem): number { + if (a.viewDescriptor.id === b.viewDescriptor.id) { + return 0; + } + + return (this.getViewOrder(a) - this.getViewOrder(b)) || this.getGroupOrderResult(a.viewDescriptor, b.viewDescriptor); + } + + private getViewOrder(viewDescriptorItem: IViewDescriptorItem): number { + const viewOrder = typeof viewDescriptorItem.state.order === 'number' ? viewDescriptorItem.state.order : viewDescriptorItem.viewDescriptor.order; + return typeof viewOrder === 'number' ? viewOrder : Number.MAX_VALUE; + } + + private getGroupOrderResult(a: IViewDescriptor, b: IViewDescriptor) { + if (!a.group || !b.group) { + return 0; + } + + if (a.group === b.group) { + return 0; + } + + return a.group < b.group ? -1 : 1; + } +} From 9aaae201a80c6376c30ba2f63986da03438e423e Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Tue, 21 Apr 2020 08:02:40 +0200 Subject: [PATCH 34/44] debt - fix TODO for contextkey computation --- src/vs/workbench/browser/contextkeys.ts | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/vs/workbench/browser/contextkeys.ts b/src/vs/workbench/browser/contextkeys.ts index fa197206bb4..eabd38981aa 100644 --- a/src/vs/workbench/browser/contextkeys.ts +++ b/src/vs/workbench/browser/contextkeys.ts @@ -206,12 +206,7 @@ export class WorkbenchContextKeysHandler extends Disposable { if (activeEditorPane) { this.activeEditorContext.set(activeEditorPane.getId()); - try { - this.activeEditorIsReadonly.set(activeEditorPane.input.isReadonly()); - } catch (error) { - // TODO@ben for https://github.com/microsoft/vscode/issues/93224 - throw new Error(`${error.message}: editor id ${activeEditorPane.getId()}`); - } + this.activeEditorIsReadonly.set(activeEditorPane.input.isReadonly()); } else { this.activeEditorContext.reset(); this.activeEditorIsReadonly.reset(); From edbd62b02caf837523c93b3012a0a922a167a690 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Tue, 21 Apr 2020 08:45:20 +0200 Subject: [PATCH 35/44] quick access - prevent key blubbling (#95587) --- src/vs/base/parts/quickinput/browser/quickInput.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/vs/base/parts/quickinput/browser/quickInput.ts b/src/vs/base/parts/quickinput/browser/quickInput.ts index 3d86c58d73e..7db54359d5b 100644 --- a/src/vs/base/parts/quickinput/browser/quickInput.ts +++ b/src/vs/base/parts/quickinput/browser/quickInput.ts @@ -669,7 +669,7 @@ class QuickPick extends QuickInput implements IQuickPi if (this.canSelectMany) { this.ui.list.domFocus(); } - event.preventDefault(); + dom.EventHelper.stop(event, true); break; case KeyCode.UpArrow: if (this.ui.list.getFocusedElements().length) { @@ -680,21 +680,21 @@ class QuickPick extends QuickInput implements IQuickPi if (this.canSelectMany) { this.ui.list.domFocus(); } - event.preventDefault(); + dom.EventHelper.stop(event, true); break; case KeyCode.PageDown: this.ui.list.focus(QuickInputListFocus.NextPage); if (this.canSelectMany) { this.ui.list.domFocus(); } - event.preventDefault(); + dom.EventHelper.stop(event, true); break; case KeyCode.PageUp: this.ui.list.focus(QuickInputListFocus.PreviousPage); if (this.canSelectMany) { this.ui.list.domFocus(); } - event.preventDefault(); + dom.EventHelper.stop(event, true); break; case KeyCode.RightArrow: if (!this._canAcceptInBackground) { @@ -715,13 +715,13 @@ class QuickPick extends QuickInput implements IQuickPi case KeyCode.Home: if (event.ctrlKey && !event.shiftKey && !event.altKey && !event.metaKey) { this.ui.list.focus(QuickInputListFocus.First); - event.preventDefault(); + dom.EventHelper.stop(event, true); } break; case KeyCode.End: if (event.ctrlKey && !event.shiftKey && !event.altKey && !event.metaKey) { this.ui.list.focus(QuickInputListFocus.Last); - event.preventDefault(); + dom.EventHelper.stop(event, true); } break; } From 817e166ac59b81a7d3f6662a5b7c749868b62e0c Mon Sep 17 00:00:00 2001 From: Eric Amodio Date: Tue, 21 Apr 2020 03:16:30 -0400 Subject: [PATCH 36/44] Exports missing interfaces --- src/vs/workbench/workbench.web.api.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/workbench.web.api.ts b/src/vs/workbench/workbench.web.api.ts index 64cd3474aa2..11647ab25e9 100644 --- a/src/vs/workbench/workbench.web.api.ts +++ b/src/vs/workbench/workbench.web.api.ts @@ -399,7 +399,12 @@ export { commands, // Home Indicator - IHomeIndicator + IHomeIndicator, + + // Default layout + IDefaultLayout, + IDefaultPanelLayout, + IDefaultSideBarLayout, }; //#endregion From 07068baa3e7c33204287902ec6f654c4abcc374c Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Tue, 21 Apr 2020 09:40:14 +0200 Subject: [PATCH 37/44] electron - block permissions requests via setPermissionRequestHandler --- src/vs/code/electron-main/app.ts | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/vs/code/electron-main/app.ts b/src/vs/code/electron-main/app.ts index 74cd460f5e3..e9fc7ad9cf3 100644 --- a/src/vs/code/electron-main/app.ts +++ b/src/vs/code/electron-main/app.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { app, ipcMain as ipc, systemPreferences, shell, Event, contentTracing, protocol, powerMonitor, IpcMainEvent, BrowserWindow, dialog } from 'electron'; +import { app, ipcMain as ipc, systemPreferences, shell, Event, contentTracing, protocol, powerMonitor, IpcMainEvent, BrowserWindow, dialog, session } from 'electron'; import { IProcessEnvironment, isWindows, isMacintosh } from 'vs/base/common/platform'; import { WindowsMainService } from 'vs/platform/windows/electron-main/windowsMainService'; import { IWindowOpenable } from 'vs/platform/windows/common/windows'; @@ -129,7 +129,7 @@ export class CodeApplication extends Disposable { } }); - // Security related measures (https://electronjs.org/docs/tutorial/security) + //#region Security related measures (https://electronjs.org/docs/tutorial/security) // // !!! DO NOT CHANGE without consulting the documentation !!! // @@ -212,8 +212,14 @@ export class CodeApplication extends Disposable { shell.openExternal(url); }); + + session.defaultSession.setPermissionRequestHandler((webContents, permission /* 'media' | 'geolocation' | 'notifications' | 'midiSysex' | 'pointerLock' | 'fullscreen' | 'openExternal' */, callback) => { + return callback(false); + }); }); + //#endregion + let macOpenFileURIs: IWindowOpenable[] = []; let runningTimeout: NodeJS.Timeout | null = null; app.on('open-file', (event: Event, path: string) => { From 06e1bbac2fea0c5202a3960e25938ab6c9b7a456 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Tue, 21 Apr 2020 09:59:26 +0200 Subject: [PATCH 38/44] don't duplicate lines, only keep prefix sums --- .../common/extHostNotebookConcatDocument.ts | 206 ++++++++---------- .../api/extHostNotebookConcatDocument.test.ts | 139 +++++++++++- 2 files changed, 227 insertions(+), 118 deletions(-) diff --git a/src/vs/workbench/api/common/extHostNotebookConcatDocument.ts b/src/vs/workbench/api/common/extHostNotebookConcatDocument.ts index 1d20f7c05e6..fc86657861c 100644 --- a/src/vs/workbench/api/common/extHostNotebookConcatDocument.ts +++ b/src/vs/workbench/api/common/extHostNotebookConcatDocument.ts @@ -9,29 +9,23 @@ import { Event, Emitter } from 'vs/base/common/event'; import { ExtHostNotebookDocument, ExtHostNotebookController, ExtHostCell } from 'vs/workbench/api/common/extHostNotebook'; import { ExtHostDocuments } from 'vs/workbench/api/common/extHostDocuments'; import { PrefixSumComputer } from 'vs/editor/common/viewModel/prefixSumComputer'; -import { ExtHostDocumentData } from 'vs/workbench/api/common/extHostDocumentData'; -import { Range, LanguageSelector } from 'vs/workbench/api/common/extHostTypeConverters'; import { DisposableStore } from 'vs/base/common/lifecycle'; import { score } from 'vs/editor/common/modes/languageSelector'; import { CellKind } from 'vs/workbench/contrib/notebook/common/notebookCommon'; -import { NotImplementedProxy } from 'vs/base/common/types'; -import { MainThreadDocumentsShape } from 'vs/workbench/api/common/extHost.protocol'; - - -//todo@jrieken ConcatDiagnosticsCollection... +import { isEqual } from 'vs/base/common/resources'; export class ExtHostNotebookConcatDocument implements vscode.NotebookConcatTextDocument { private _disposables = new DisposableStore(); + private _cells!: ExtHostCell[]; + private _cellLengths!: PrefixSumComputer; + private _cellLines!: PrefixSumComputer; + private _versionId = 0; + private readonly _onDidChange = new Emitter(); readonly onDidChange: Event = this._onDidChange.event; - private _versionId = 0; - private _delegate!: ExtHostDocumentData; - private _selectedCells!: ExtHostCell[]; - private _cellStarts!: PrefixSumComputer; - constructor( extHostNotebooks: ExtHostNotebookController, extHostDocuments: ExtHostDocuments, @@ -40,133 +34,119 @@ export class ExtHostNotebookConcatDocument implements vscode.NotebookConcatTextD ) { this._init(); - extHostDocuments.onDidChangeDocument(e => { - const cellIdx = this._selectedCells.findIndex(candidate => candidate.uri.toString() === e.document.uri.toString()); - if (cellIdx < 0) { - return; + this._disposables.add(extHostDocuments.onDidChangeDocument(e => { + let cellIdx = this._cells.findIndex(cell => isEqual(cell.uri, e.document.uri)); + if (cellIdx >= 0) { + this._cellLengths.changeValue(cellIdx, this._cells[cellIdx].document.getText().length + 1); + this._cellLines.changeValue(cellIdx, this._cells[cellIdx].document.lineCount); + this._versionId += 1; + this._onDidChange.fire(this); } - // todo@jrieken reuse raw event! - this._versionId += 1; - this._delegate.onEvents({ - versionId: this._versionId, - eol: '\n', - changes: e.contentChanges.map(change => { - return { - range: Range.from(change.range), - rangeOffset: change.rangeOffset, - rangeLength: change.rangeLength, - text: change.text, - }; - }) - }); - this._cellStarts.changeValue(cellIdx, e.document.getText().length + 1); - - this._onDidChange.fire(this); - - }, undefined, this._disposables); - - extHostNotebooks.onDidChangeNotebookDocument(e => { - if (e.document !== this._notebook) { - return; + })); + this._disposables.add(extHostNotebooks.onDidChangeNotebookDocument(e => { + if (e.document === this._notebook) { + this._init(); + this._versionId += 1; + this._onDidChange.fire(this); } - //todo@jrieken update instead of flushing... - this._versionId += 1; - this._init(); - this._onDidChange.fire(this); - }, undefined, this._disposables); + })); } dispose(): void { this._disposables.dispose(); - this._delegate.dispose(); } private _init() { - - // only allow Code-cells and those that are selected by the language selector - this._selectedCells = this._notebook.cells - .filter(cell => cell.cellKind === CellKind.Code && (!this._selector || score(LanguageSelector.from(this._selector), cell.uri, cell.language, true))); - - const lines: string[] = []; - const cellLengths = new Uint32Array(this._selectedCells.length); - - for (let i = 0; i < this._selectedCells.length; i++) { - const cell = this._selectedCells[i]; - // update prefix sum - cellLengths[i] = cell.document.getText().length + 1; // 1 is newline - //todo@jrieken reuse lines! - for (let line = 0; line < cell.document.lineCount; line++) { - lines.push(cell.document.lineAt(line).text); + this._cells = []; + const cellLengths: number[] = []; + const cellLineCounts: number[] = []; + for (let cell of this._notebook.cells) { + if (cell.cellKind === CellKind.Code && (!this._selector || score(this._selector, cell.uri, cell.language, true))) { + this._cells.push(cell); + cellLengths.push(cell.document.getText().length + 1); + cellLineCounts.push(cell.document.lineCount); } } - - this._cellStarts = new PrefixSumComputer(cellLengths); - this._delegate = new ExtHostDocumentData( - new class extends NotImplementedProxy('MainThreadDocumentsShape') { }, - this._notebook.uri.with({ scheme: 'vscode-concatdoc' }), - lines, - '\n', - this._notebook.languages[0], - this._versionId, - false - ); + this._cellLengths = new PrefixSumComputer(new Uint32Array(cellLengths)); + this._cellLines = new PrefixSumComputer(new Uint32Array(cellLineCounts)); } - get version() { + get version(): number { return this._versionId; } - getText(range?: vscode.Range) { - return this._delegate.document.getText(range); + getText(range?: vscode.Range): string { + if (!range) { + let result = ''; + for (let cell of this._cells) { + result += cell.document.getText() + '\n'; + } + // remove last newline again + result = result.slice(0, -1); + return result; + } + + if (range.isEmpty) { + return ''; + } + + // get start and end locations and create substrings + const start = this.locationAt(range.start); + const end = this.locationAt(range.end); + const startCell = this._cells.find(cell => isEqual(cell.uri, start.uri)); + const endCell = this._cells.find(cell => isEqual(cell.uri, end.uri)); + + if (!startCell || !endCell) { + return ''; + } else if (startCell === endCell) { + return startCell.document.getText(new types.Range(start.range.start, end.range.end)); + } else { + let a = startCell.document.getText(new types.Range(start.range.start, new types.Position(startCell.document.lineCount, 0))); + let b = endCell.document.getText(new types.Range(new types.Position(0, 0), end.range.end)); + return a + '\n' + b; + } } - locationAt(positionOrRange: vscode.Position | vscode.Range): vscode.Location { + offsetAt(position: vscode.Position): number { + const idx = this._cellLines.getIndexOf(position.line); + const offset1 = this._cellLengths.getAccumulatedValue(idx.index - 1); + const offset2 = this._cells[idx.index].document.offsetAt(position.with(idx.remainder)); + return offset1 + offset2; + } + positionAt(locationOrOffset: vscode.Location | number): vscode.Position { + if (typeof locationOrOffset === 'number') { + const idx = this._cellLengths.getIndexOf(locationOrOffset); + const lineCount = this._cellLines.getAccumulatedValue(idx.index - 1); + return this._cells[idx.index].document.positionAt(idx.remainder).translate(lineCount); + } + + const idx = this._cells.findIndex(cell => isEqual(cell.uri, locationOrOffset.uri)); + if (idx >= 0) { + let line = this._cellLines.getAccumulatedValue(idx - 1); + return new types.Position(line + locationOrOffset.range.start.line, locationOrOffset.range.start.character); + } + // do better? + // return undefined; + return new types.Position(0, 0); + } + + locationAt(positionOrRange: vscode.Range | vscode.Position): types.Location { if (!types.Range.isRange(positionOrRange)) { positionOrRange = new types.Range(positionOrRange, positionOrRange); } - const start = this._delegate.document.offsetAt(positionOrRange.start); - const startIndex = this._cellStarts.getIndexOf(start); - const startCell = this._selectedCells[startIndex.index]; - if (!startCell) { - // do better? throw an error insead? return undefined? - return new types.Location(this._notebook.uri, new types.Position(0, 0)); - } - - let endCell = startCell; - let endIndex = startIndex; + const startIdx = this._cellLines.getIndexOf(positionOrRange.start.line); + let endIdx = startIdx; if (!positionOrRange.isEmpty) { - const end = this._delegate.document.offsetAt(positionOrRange.end); - endIndex = this._cellStarts.getIndexOf(end); - endCell = this._selectedCells[endIndex.index]; + endIdx = this._cellLines.getIndexOf(positionOrRange.end.line); } - const startPos = startCell.document.positionAt(startIndex.remainder); - let endPos = startPos; - if (endCell && endCell.handle === startCell.handle) { - endPos = endCell.document.positionAt(endIndex.remainder); - } + let startPos = new types.Position(startIdx.remainder, positionOrRange.start.character); + let endPos = new types.Position(endIdx.remainder, positionOrRange.end.character); + let range = new types.Range(startPos, endPos); - return new types.Location(startCell.uri, new types.Range(startPos.line, startPos.character, endPos.line, endPos.character)); - } - - positionAt(offsetOrLocation: number | vscode.Location): vscode.Position { - if (typeof offsetOrLocation === 'number') { - return this._delegate.document.positionAt(offsetOrLocation); - } - const idx = this._selectedCells.findIndex(candidate => candidate.uri.toString() === offsetOrLocation.uri.toString()); - if (idx < 0) { - // do better? - // return undefined; - return new types.Position(0, 0); - } - const docOffset = this._selectedCells[idx].document.offsetAt(offsetOrLocation.range.start); - const cellOffset = this._cellStarts.getAccumulatedValue(idx - 1); - return this._delegate.document.positionAt(docOffset + cellOffset); - } - - offsetAt(position: vscode.Position): number { - return this._delegate.document.offsetAt(position); + const startCell = this._cells[startIdx.index]; + return new types.Location(startCell.uri, startCell.document.validateRange(range)); } } diff --git a/src/vs/workbench/test/browser/api/extHostNotebookConcatDocument.test.ts b/src/vs/workbench/test/browser/api/extHostNotebookConcatDocument.test.ts index c00ca986fe6..c26fdb77f9d 100644 --- a/src/vs/workbench/test/browser/api/extHostNotebookConcatDocument.test.ts +++ b/src/vs/workbench/test/browser/api/extHostNotebookConcatDocument.test.ts @@ -12,10 +12,10 @@ import { ExtHostNotebookConcatDocument } from 'vs/workbench/api/common/extHostNo import { ExtHostNotebookDocument, ExtHostNotebookController } from 'vs/workbench/api/common/extHostNotebook'; import { URI } from 'vs/base/common/uri'; import { CellKind, CellUri } from 'vs/workbench/contrib/notebook/common/notebookCommon'; -import { Position, Location } from 'vs/workbench/api/common/extHostTypes'; +import { Position, Location, Range } from 'vs/workbench/api/common/extHostTypes'; import { ExtHostCommands } from 'vs/workbench/api/common/extHostCommands'; import { nullExtensionDescription } from 'vs/workbench/services/extensions/common/extensions'; -import { NotebookProvider } from 'vscode'; +import * as vscode from 'vscode'; import { mock } from 'vs/workbench/test/common/workbenchTestServices'; import { MainContext, MainThreadCommandsShape, MainThreadNotebookShape } from 'vs/workbench/api/common/extHost.protocol'; import { DisposableStore } from 'vs/base/common/lifecycle'; @@ -46,7 +46,7 @@ suite('NotebookConcatDocument', function () { extHostDocumentsAndEditors = new ExtHostDocumentsAndEditors(rpcProtocol, new NullLogService()); extHostDocuments = new ExtHostDocuments(rpcProtocol, extHostDocumentsAndEditors); extHostNotebooks = new ExtHostNotebookController(rpcProtocol, new ExtHostCommands(rpcProtocol, new NullLogService()), extHostDocumentsAndEditors); - let reg = extHostNotebooks.registerNotebookProvider(nullExtensionDescription, 'test', new class extends mock() { + let reg = extHostNotebooks.registerNotebookProvider(nullExtensionDescription, 'test', new class extends mock() { async resolveNotebook() { } }); await extHostNotebooks.$resolveNotebook('test', notebookUri); @@ -79,7 +79,8 @@ suite('NotebookConcatDocument', function () { // assert.equal(doc.positionAt(SOME_FAKE_LOCATION?), undefined); }); - function assertLocation(doc: ExtHostNotebookConcatDocument, pos: Position, expected: Location, reverse = true) { + + function assertLocation(doc: vscode.NotebookConcatTextDocument, pos: Position, expected: Location, reverse = true) { const actual = doc.locationAt(pos); assert.equal(actual.uri.toString(), expected.uri.toString()); assert.equal(actual.range.isEqual(expected.range), true); @@ -95,7 +96,7 @@ suite('NotebookConcatDocument', function () { } } - function assertLines(doc: ExtHostNotebookConcatDocument, ...lines: string[]) { + function assertLines(doc: vscode.NotebookConcatTextDocument, ...lines: string[]) { let actual = doc.getText().split(/\r\n|\n|\r/); assert.deepStrictEqual(actual, lines); } @@ -305,4 +306,132 @@ suite('NotebookConcatDocument', function () { assertLines(fooLangDoc, 'fooLang-document'); assertLines(barLangDoc, 'barLang-document', 'barLang-document2'); }); + + function assertOffsetAtPosition(doc: vscode.NotebookConcatTextDocument, offset: number, expected: { line: number, character: number }, reverse = true) { + const actual = doc.positionAt(offset); + + assert.equal(actual.line, expected.line); + assert.equal(actual.character, expected.character); + + if (reverse) { + const actualOffset = doc.offsetAt(actual); + assert.equal(actualOffset, offset); + } + } + + + test('offsetAt(position) <-> positionAt(offset)', function () { + + extHostNotebooks.$acceptModelChanged(notebookUri, { + versionId: notebook.versionId + 1, + changes: [[0, 0, [{ + handle: 1, + uri: CellUri.generate(notebook.uri, 1), + source: ['Hello', 'World', 'Hello World!'], + language: 'test', + cellKind: CellKind.Code, + outputs: [], + }, { + handle: 2, + uri: CellUri.generate(notebook.uri, 2), + source: ['Hallo', 'Welt', 'Hallo Welt!'], + language: 'test', + cellKind: CellKind.Code, + outputs: [], + }]]] + }); + + assert.equal(notebook.cells.length, 1 + 2); // markdown and code + + let doc = new ExtHostNotebookConcatDocument(extHostNotebooks, extHostDocuments, notebook, undefined); + assertLines(doc, 'Hello', 'World', 'Hello World!', 'Hallo', 'Welt', 'Hallo Welt!'); + + assertOffsetAtPosition(doc, 0, { line: 0, character: 0 }); + assertOffsetAtPosition(doc, 1, { line: 0, character: 1 }); + assertOffsetAtPosition(doc, 9, { line: 1, character: 3 }); + assertOffsetAtPosition(doc, 32, { line: 4, character: 1 }); + assertOffsetAtPosition(doc, 47, { line: 5, character: 11 }); + }); + + + function assertLocationAtPosition(doc: vscode.NotebookConcatTextDocument, pos: { line: number, character: number }, expected: { uri: URI, line: number, character: number }, reverse = true) { + + const actual = doc.locationAt(new Position(pos.line, pos.character)); + assert.equal(actual.uri.toString(), expected.uri.toString()); + assert.equal(actual.range.start.line, expected.line); + assert.equal(actual.range.end.line, expected.line); + assert.equal(actual.range.start.character, expected.character); + assert.equal(actual.range.end.character, expected.character); + + if (reverse) { + const actualPos = doc.positionAt(actual); + assert.equal(actualPos.line, pos.line); + assert.equal(actualPos.character, pos.character); + } + } + + test('locationAt(position) <-> positionAt(location)', function () { + + extHostNotebooks.$acceptModelChanged(notebookUri, { + versionId: notebook.versionId + 1, + changes: [[0, 0, [{ + handle: 1, + uri: CellUri.generate(notebook.uri, 1), + source: ['Hello', 'World', 'Hello World!'], + language: 'test', + cellKind: CellKind.Code, + outputs: [], + }, { + handle: 2, + uri: CellUri.generate(notebook.uri, 2), + source: ['Hallo', 'Welt', 'Hallo Welt!'], + language: 'test', + cellKind: CellKind.Code, + outputs: [], + }]]] + }); + + assert.equal(notebook.cells.length, 1 + 2); // markdown and code + + let doc = new ExtHostNotebookConcatDocument(extHostNotebooks, extHostDocuments, notebook, undefined); + assertLines(doc, 'Hello', 'World', 'Hello World!', 'Hallo', 'Welt', 'Hallo Welt!'); + + assertLocationAtPosition(doc, { line: 0, character: 0 }, { uri: notebook.cells[0].uri, line: 0, character: 0 }); + assertLocationAtPosition(doc, { line: 2, character: 0 }, { uri: notebook.cells[0].uri, line: 2, character: 0 }); + assertLocationAtPosition(doc, { line: 2, character: 12 }, { uri: notebook.cells[0].uri, line: 2, character: 12 }); + assertLocationAtPosition(doc, { line: 3, character: 0 }, { uri: notebook.cells[1].uri, line: 0, character: 0 }); + assertLocationAtPosition(doc, { line: 5, character: 0 }, { uri: notebook.cells[1].uri, line: 2, character: 0 }); + assertLocationAtPosition(doc, { line: 5, character: 11 }, { uri: notebook.cells[1].uri, line: 2, character: 11 }); + }); + + test('getText(range)', function () { + + extHostNotebooks.$acceptModelChanged(notebookUri, { + versionId: notebook.versionId + 1, + changes: [[0, 0, [{ + handle: 1, + uri: CellUri.generate(notebook.uri, 1), + source: ['Hello', 'World', 'Hello World!'], + language: 'test', + cellKind: CellKind.Code, + outputs: [], + }, { + handle: 2, + uri: CellUri.generate(notebook.uri, 2), + source: ['Hallo', 'Welt', 'Hallo Welt!'], + language: 'test', + cellKind: CellKind.Code, + outputs: [], + }]]] + }); + + assert.equal(notebook.cells.length, 1 + 2); // markdown and code + + let doc = new ExtHostNotebookConcatDocument(extHostNotebooks, extHostDocuments, notebook, undefined); + assertLines(doc, 'Hello', 'World', 'Hello World!', 'Hallo', 'Welt', 'Hallo Welt!'); + + assert.equal(doc.getText(new Range(0, 0, 0, 0)), ''); + assert.equal(doc.getText(new Range(0, 0, 1, 0)), 'Hello\n'); + assert.equal(doc.getText(new Range(2, 0, 4, 0)), 'Hello World!\nHallo\n'); + }); }); From 5201e0e93727599a2982d9191a9572d21e8b4e06 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Tue, 21 Apr 2020 10:01:34 +0200 Subject: [PATCH 39/44] electron - also setPermissionCheckHandler --- src/vs/code/electron-main/app.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/vs/code/electron-main/app.ts b/src/vs/code/electron-main/app.ts index e9fc7ad9cf3..d49d329a098 100644 --- a/src/vs/code/electron-main/app.ts +++ b/src/vs/code/electron-main/app.ts @@ -216,6 +216,10 @@ export class CodeApplication extends Disposable { session.defaultSession.setPermissionRequestHandler((webContents, permission /* 'media' | 'geolocation' | 'notifications' | 'midiSysex' | 'pointerLock' | 'fullscreen' | 'openExternal' */, callback) => { return callback(false); }); + + session.defaultSession.setPermissionCheckHandler((webContents, permission /* 'media' */) => { + return false; + }); }); //#endregion From 9f709d170b06e991502153f281ec3c012add2e42 Mon Sep 17 00:00:00 2001 From: isidor Date: Tue, 21 Apr 2020 10:28:32 +0200 Subject: [PATCH 40/44] aria: do not read duplicated alerts #95693 --- src/vs/base/browser/ui/aria/aria.ts | 34 +++++------------------- src/vs/editor/contrib/find/findWidget.ts | 2 +- 2 files changed, 7 insertions(+), 29 deletions(-) diff --git a/src/vs/base/browser/ui/aria/aria.ts b/src/vs/base/browser/ui/aria/aria.ts index 2867c1256dc..b42fbf94ac2 100644 --- a/src/vs/base/browser/ui/aria/aria.ts +++ b/src/vs/base/browser/ui/aria/aria.ts @@ -4,7 +4,6 @@ *--------------------------------------------------------------------------------------------*/ import 'vs/css!./aria'; -import * as nls from 'vs/nls'; import { isMacintosh } from 'vs/base/common/platform'; import * as dom from 'vs/base/browser/dom'; @@ -34,47 +33,26 @@ export function setARIAContainer(parent: HTMLElement) { /** * Given the provided message, will make sure that it is read as alert to screen readers. */ -export function alert(msg: string, disableRepeat?: boolean): void { - insertMessage(alertContainer, msg, disableRepeat); +export function alert(msg: string): void { + insertMessage(alertContainer, msg); } /** * Given the provided message, will make sure that it is read as status to screen readers. */ -export function status(msg: string, disableRepeat?: boolean): void { +export function status(msg: string): void { if (isMacintosh) { - alert(msg, disableRepeat); // VoiceOver does not seem to support status role + alert(msg); // VoiceOver does not seem to support status role } else { - insertMessage(statusContainer, msg, disableRepeat); + insertMessage(statusContainer, msg); } } -let repeatedTimes = 0; -let prevText: string | undefined = undefined; -function insertMessage(target: HTMLElement, msg: string, disableRepeat?: boolean): void { +function insertMessage(target: HTMLElement, msg: string): void { if (!ariaContainer) { return; } - // If the same message should be inserted that is already present, a screen reader would - // not announce this message because it matches the previous one. As a workaround, we - // alter the message with the number of occurences unless this is explicitly disabled - // via the disableRepeat flag. - if (!disableRepeat) { - if (prevText === msg) { - repeatedTimes++; - } else { - prevText = msg; - repeatedTimes = 0; - } - - switch (repeatedTimes) { - case 0: break; - case 1: msg = nls.localize('repeated', "{0} (occurred again)", msg); break; - default: msg = nls.localize('repeatedNtimes', "{0} (occurred {1} times)", msg, repeatedTimes); break; - } - } - dom.clearNode(target); target.textContent = msg; diff --git a/src/vs/editor/contrib/find/findWidget.ts b/src/vs/editor/contrib/find/findWidget.ts index 613d0654c6b..b256e2c8ba4 100644 --- a/src/vs/editor/contrib/find/findWidget.ts +++ b/src/vs/editor/contrib/find/findWidget.ts @@ -411,7 +411,7 @@ export class FindWidget extends Widget implements IOverlayWidget, IHorizontalSas this._matchesCount.appendChild(document.createTextNode(label)); - alertFn(this._getAriaLabel(label, this._state.currentMatch, this._state.searchString), true); + alertFn(this._getAriaLabel(label, this._state.currentMatch, this._state.searchString)); MAX_MATCHES_COUNT_WIDTH = Math.max(MAX_MATCHES_COUNT_WIDTH, this._matchesCount.clientWidth); } From 01c243f25fef40e710417b5999f11ffeafb83d80 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Tue, 21 Apr 2020 11:47:13 +0200 Subject: [PATCH 41/44] fix #95715 --- .../api/browser/mainThreadLanguageFeatures.ts | 6 ++--- .../api/common/extHostLanguageFeatures.ts | 2 +- .../browser/api/extHostApiCommands.test.ts | 27 +++++++++++++++++++ 3 files changed, 31 insertions(+), 4 deletions(-) diff --git a/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts b/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts index 0584726e363..cb72f4d409e 100644 --- a/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts +++ b/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts @@ -405,8 +405,8 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha private static _inflateSuggestDto(defaultRange: IRange | { insert: IRange, replace: IRange }, data: ISuggestDataDto): modes.CompletionItem { return { - label: data[ISuggestDataDtoField.label2] || data[ISuggestDataDtoField.label], - kind: data[ISuggestDataDtoField.kind] || modes.CompletionItemKind.Property, + label: data[ISuggestDataDtoField.label2] ?? data[ISuggestDataDtoField.label], + kind: data[ISuggestDataDtoField.kind] ?? modes.CompletionItemKind.Property, tags: data[ISuggestDataDtoField.kindModifier], detail: data[ISuggestDataDtoField.detail], documentation: data[ISuggestDataDtoField.documentation], @@ -414,7 +414,7 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha filterText: data[ISuggestDataDtoField.filterText], preselect: data[ISuggestDataDtoField.preselect], insertText: typeof data.h === 'undefined' ? data[ISuggestDataDtoField.label] : data.h, - range: data[ISuggestDataDtoField.range] || defaultRange, + range: data[ISuggestDataDtoField.range] ?? defaultRange, insertTextRules: data[ISuggestDataDtoField.insertTextRules], commitCharacters: data[ISuggestDataDtoField.commitCharacters], additionalTextEdits: data[ISuggestDataDtoField.additionalTextEdits], diff --git a/src/vs/workbench/api/common/extHostLanguageFeatures.ts b/src/vs/workbench/api/common/extHostLanguageFeatures.ts index 96785eb5f41..26084cfab8c 100644 --- a/src/vs/workbench/api/common/extHostLanguageFeatures.ts +++ b/src/vs/workbench/api/common/extHostLanguageFeatures.ts @@ -954,7 +954,7 @@ class SuggestAdapter { // [extHostProtocol.ISuggestDataDtoField.label]: item.label, [extHostProtocol.ISuggestDataDtoField.label2]: item.label2, - [extHostProtocol.ISuggestDataDtoField.kind]: item.kind ? typeConvert.CompletionItemKind.from(item.kind) : undefined, + [extHostProtocol.ISuggestDataDtoField.kind]: item.kind !== undefined ? typeConvert.CompletionItemKind.from(item.kind) : undefined, [extHostProtocol.ISuggestDataDtoField.kindModifier]: item.tags && item.tags.map(typeConvert.CompletionItemTag.from), [extHostProtocol.ISuggestDataDtoField.detail]: item.detail, [extHostProtocol.ISuggestDataDtoField.documentation]: typeof item.documentation === 'undefined' ? undefined : typeConvert.MarkdownString.fromStrict(item.documentation), diff --git a/src/vs/workbench/test/browser/api/extHostApiCommands.test.ts b/src/vs/workbench/test/browser/api/extHostApiCommands.test.ts index 768e9528a37..f40f7e9c29b 100644 --- a/src/vs/workbench/test/browser/api/extHostApiCommands.test.ts +++ b/src/vs/workbench/test/browser/api/extHostApiCommands.test.ts @@ -585,6 +585,33 @@ suite('ExtHostLanguageFeatureCommands', function () { assert.equal(b.commitCharacters, undefined); }); + test('vscode.executeCompletionItemProvider returns the wrong CompletionItemKinds in insiders #95715', async function () { + disposables.push(extHost.registerCompletionItemProvider(nullExtensionDescription, defaultSelector, { + provideCompletionItems(): any { + return [ + new types.CompletionItem('My Method', types.CompletionItemKind.Method), + new types.CompletionItem('My Property', types.CompletionItemKind.Property), + ]; + } + }, [])); + + await rpcProtocol.sync(); + + let list = await commands.executeCommand( + 'vscode.executeCompletionItemProvider', + model.uri, + new types.Position(0, 4), + undefined + ); + + assert.ok(list instanceof types.CompletionList); + assert.equal(list.items.length, 2); + + const [a, b] = list.items; + assert.equal(a.kind, types.CompletionItemKind.Method); + assert.equal(b.kind, types.CompletionItemKind.Property); + }); + // --- signatureHelp test('Parameter Hints, back and forth', async () => { From 03a7223ee2a086bbcf028801da9370d5deff5093 Mon Sep 17 00:00:00 2001 From: Christof Marti Date: Tue, 21 Apr 2020 12:05:41 +0200 Subject: [PATCH 42/44] Investigate #94862 --- src/vs/code/electron-main/window.ts | 2 +- src/vs/platform/request/common/request.ts | 8 +++++++- .../contrib/webview/electron-browser/webviewElement.ts | 2 +- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/vs/code/electron-main/window.ts b/src/vs/code/electron-main/window.ts index 9fd10acf1e8..4821653f75b 100644 --- a/src/vs/code/electron-main/window.ts +++ b/src/vs/code/electron-main/window.ts @@ -409,7 +409,7 @@ export class CodeWindow extends Disposable implements ICodeWindow { return callback({ cancel: true }); } - return callback({ cancel: false, responseHeaders }); + return callback({ cancel: false }); }); // Remember that we loaded diff --git a/src/vs/platform/request/common/request.ts b/src/vs/platform/request/common/request.ts index 81ec255e65b..5ddf08de7bf 100644 --- a/src/vs/platform/request/common/request.ts +++ b/src/vs/platform/request/common/request.ts @@ -48,7 +48,13 @@ export async function asJson(context: IRequestContext): Promise Date: Tue, 21 Apr 2020 12:13:51 +0200 Subject: [PATCH 43/44] handle remote source provider errors --- extensions/git/src/commands.ts | 32 +++++++++++++++++++------------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/extensions/git/src/commands.ts b/extensions/git/src/commands.ts index bc826cf012c..53217156be4 100644 --- a/extensions/git/src/commands.ts +++ b/extensions/git/src/commands.ts @@ -269,20 +269,26 @@ class RemoteSourceProviderQuickPick { @throttle async query(): Promise { this.quickpick.busy = true; - const remoteSources = await this.provider.getRemoteSources(this.quickpick.value) || []; - this.quickpick.busy = false; - if (remoteSources.length === 0) { - this.quickpick.items = [{ - label: localize('none found', "No remote repositories found."), - alwaysShow: true - }]; - } else { - this.quickpick.items = remoteSources.map(remoteSource => ({ - label: remoteSource.name, - description: remoteSource.url, - remote: remoteSource - })); + try { + const remoteSources = await this.provider.getRemoteSources(this.quickpick.value) || []; + + if (remoteSources.length === 0) { + this.quickpick.items = [{ + label: localize('none found', "No remote repositories found."), + alwaysShow: true + }]; + } else { + this.quickpick.items = remoteSources.map(remoteSource => ({ + label: remoteSource.name, + description: remoteSource.url, + remote: remoteSource + })); + } + } catch (err) { + this.quickpick.items = [{ label: localize('error', "$(error) Error: {0}", err.message), alwaysShow: true }]; + } finally { + this.quickpick.busy = false; } } From 054cbf4f241ebb2e5996e0ca5309aae8130983da Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Tue, 21 Apr 2020 12:18:05 +0200 Subject: [PATCH 44/44] export onDidChange-event in concat-doc --- src/vs/vscode.proposed.d.ts | 1 + .../workbench/api/common/extHostNotebookConcatDocument.ts | 8 ++++---- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/vs/vscode.proposed.d.ts b/src/vs/vscode.proposed.d.ts index b25973d0cb0..0c534c0dbe6 100644 --- a/src/vs/vscode.proposed.d.ts +++ b/src/vs/vscode.proposed.d.ts @@ -1671,6 +1671,7 @@ declare module 'vscode' { } export interface NotebookConcatTextDocument { + onDidChange: Event; version: number; getText(): string; getText(range: Range): string; diff --git a/src/vs/workbench/api/common/extHostNotebookConcatDocument.ts b/src/vs/workbench/api/common/extHostNotebookConcatDocument.ts index fc86657861c..60e87eca726 100644 --- a/src/vs/workbench/api/common/extHostNotebookConcatDocument.ts +++ b/src/vs/workbench/api/common/extHostNotebookConcatDocument.ts @@ -23,8 +23,8 @@ export class ExtHostNotebookConcatDocument implements vscode.NotebookConcatTextD private _cellLines!: PrefixSumComputer; private _versionId = 0; - private readonly _onDidChange = new Emitter(); - readonly onDidChange: Event = this._onDidChange.event; + private readonly _onDidChange = new Emitter(); + readonly onDidChange: Event = this._onDidChange.event; constructor( extHostNotebooks: ExtHostNotebookController, @@ -40,14 +40,14 @@ export class ExtHostNotebookConcatDocument implements vscode.NotebookConcatTextD this._cellLengths.changeValue(cellIdx, this._cells[cellIdx].document.getText().length + 1); this._cellLines.changeValue(cellIdx, this._cells[cellIdx].document.lineCount); this._versionId += 1; - this._onDidChange.fire(this); + this._onDidChange.fire(undefined); } })); this._disposables.add(extHostNotebooks.onDidChangeNotebookDocument(e => { if (e.document === this._notebook) { this._init(); this._versionId += 1; - this._onDidChange.fire(this); + this._onDidChange.fire(undefined); } })); }