diff --git a/extensions/json-language-features/server/.npmignore b/extensions/json-language-features/server/.npmignore index a6661ddb7f..3032fe8b26 100644 --- a/extensions/json-language-features/server/.npmignore +++ b/extensions/json-language-features/server/.npmignore @@ -4,4 +4,7 @@ out/**/*.js.map src/ test/ tsconfig.json -.gitignore \ No newline at end of file +.gitignore +yarn.lock +extension.webpack.config.js +vscode-json-languageserver-*.tgz \ No newline at end of file diff --git a/extensions/json-language-features/server/package.json b/extensions/json-language-features/server/package.json index c07dca1e44..061cb69249 100644 --- a/extensions/json-language-features/server/package.json +++ b/extensions/json-language-features/server/package.json @@ -1,7 +1,7 @@ { "name": "vscode-json-languageserver", "description": "JSON language server", - "version": "1.2.0", + "version": "1.2.1", "author": "Microsoft Corporation", "license": "MIT", "engines": { @@ -24,10 +24,9 @@ "@types/node": "^10.14.8" }, "scripts": { - "prepublishOnly": "npm run clean && npm run test", - "preversion": "npm test", - "compile": "gulp compile-extension:json-language-features-server", - "watch": "gulp watch-extension:json-language-features-server", + "prepublishOnly": "npm run clean && npm run compile", + "compile": "npx gulp compile-extension:json-language-features-server", + "watch": "npx gulp watch-extension:json-language-features-server", "clean": "../../../node_modules/.bin/rimraf out", "install-service-next": "yarn add vscode-json-languageservice@next", "install-service-local": "yarn link vscode-json-languageservice", diff --git a/extensions/markdown-language-features/media/markdown.css b/extensions/markdown-language-features/media/markdown.css index d4f6b427b6..cfe9a45cdc 100644 --- a/extensions/markdown-language-features/media/markdown.css +++ b/extensions/markdown-language-features/media/markdown.css @@ -186,6 +186,7 @@ pre.hljs code > div { pre code { color: var(--vscode-editor-foreground); + tab-size: 4; } diff --git a/extensions/markdown-language-features/src/features/preview.ts b/extensions/markdown-language-features/src/features/preview.ts index 8dd5ce8485..2f93a4f50e 100644 --- a/extensions/markdown-language-features/src/features/preview.ts +++ b/extensions/markdown-language-features/src/features/preview.ts @@ -75,7 +75,7 @@ export class PreviewDocumentVersion { export class MarkdownPreview extends Disposable { - public static viewType = 'markdown.preview'; + public static readonly viewType = 'markdown.preview'; private _resource: vscode.Uri; private _locked: boolean; diff --git a/package.json b/package.json index 1ff677c50e..ce99387b51 100644 --- a/package.json +++ b/package.json @@ -192,4 +192,4 @@ "windows-mutex": "0.3.0", "windows-process-tree": "0.2.4" } -} +} \ No newline at end of file diff --git a/src/sql/workbench/parts/dataExplorer/browser/dataExplorer.contribution.ts b/src/sql/workbench/parts/dataExplorer/browser/dataExplorer.contribution.ts index 7d3dfd8338..131505169b 100644 --- a/src/sql/workbench/parts/dataExplorer/browser/dataExplorer.contribution.ts +++ b/src/sql/workbench/parts/dataExplorer/browser/dataExplorer.contribution.ts @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import 'vs/css!sql/media/actionBarLabel'; +import 'vs/css!./media/dataExplorer.contribution'; import { localize } from 'vs/nls'; import { ViewletRegistry, Extensions as ViewletExtensions, ViewletDescriptor, ShowViewletAction } from 'vs/workbench/browser/viewlet'; import { Registry } from 'vs/platform/registry/common/platform'; diff --git a/src/sql/workbench/parts/dataExplorer/browser/media/dataExplorer.contribution.css b/src/sql/workbench/parts/dataExplorer/browser/media/dataExplorer.contribution.css new file mode 100644 index 0000000000..0e353e15e7 --- /dev/null +++ b/src/sql/workbench/parts/dataExplorer/browser/media/dataExplorer.contribution.css @@ -0,0 +1,10 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +/* Activity Bar */ +.monaco-workbench .activitybar .monaco-action-bar .action-label.dataExplorer { + -webkit-mask: url('server_page_inverse.svg') no-repeat 50% 50%; + -webkit-mask-size: 25px 25px; +} \ No newline at end of file diff --git a/src/sql/workbench/parts/dataExplorer/browser/media/server_page_inverse.svg b/src/sql/workbench/parts/dataExplorer/browser/media/server_page_inverse.svg new file mode 100644 index 0000000000..c11b61e1cd --- /dev/null +++ b/src/sql/workbench/parts/dataExplorer/browser/media/server_page_inverse.svg @@ -0,0 +1 @@ +server_inverse_16x16 \ No newline at end of file diff --git a/src/vs/base/browser/dom.ts b/src/vs/base/browser/dom.ts index be2b8aa9cd..8d0f3733dd 100644 --- a/src/vs/base/browser/dom.ts +++ b/src/vs/base/browser/dom.ts @@ -34,7 +34,7 @@ export function isInDOM(node: Node | null): boolean { if (node === document.body) { return true; } - node = node.parentNode; + node = node.parentNode || (node as ShadowRoot).host; } return false; } @@ -806,7 +806,8 @@ export function createCSSRule(selector: string, cssText: string, style: HTMLStyl if (!style || !cssText) { return; } - style.textContent = `${selector}{${cssText}}\n${style.textContent}`; + + (style.sheet).insertRule(selector + '{' + cssText + '}', 0); } export function removeCSSRulesContainingSelector(ruleName: string, style: HTMLStyleElement = getSharedStyleSheet()): void { diff --git a/src/vs/base/browser/ui/grid/gridview.ts b/src/vs/base/browser/ui/grid/gridview.ts index 374c9c97fe..fd0abfc651 100644 --- a/src/vs/base/browser/ui/grid/gridview.ts +++ b/src/vs/base/browser/ui/grid/gridview.ts @@ -102,6 +102,22 @@ class BranchNode implements ISplitView, IDisposable { return Math.min(...this.children.map(c => c.maximumOrthogonalSize)); } + get priority(): LayoutPriority { + if (this.children.length === 0) { + return LayoutPriority.Normal; + } + + const priorities = this.children.map(c => typeof c.priority === 'undefined' ? LayoutPriority.Normal : c.priority); + + if (priorities.some(p => p === LayoutPriority.High)) { + return LayoutPriority.High; + } else if (priorities.some(p => p === LayoutPriority.Low)) { + return LayoutPriority.Low; + } + + return LayoutPriority.Normal; + } + get minimumOrthogonalSize(): number { return this.splitview.minimumSize; } diff --git a/src/vs/base/browser/ui/splitview/splitview.ts b/src/vs/base/browser/ui/splitview/splitview.ts index 49f953d46a..b0fb446a2e 100644 --- a/src/vs/base/browser/ui/splitview/splitview.ts +++ b/src/vs/base/browser/ui/splitview/splitview.ts @@ -325,13 +325,13 @@ export class SplitView extends Disposable { container.appendChild(view.element); - let highPriorityIndex: number | undefined; + let highPriorityIndexes: number[] | undefined; if (typeof size !== 'number' && size.type === 'split') { - highPriorityIndex = size.index; + highPriorityIndexes = [size.index]; } - this.relayout(index, highPriorityIndex); + this.relayout([index], highPriorityIndexes); this.state = State.Idle; if (typeof size !== 'number' && size.type === 'distribute') { @@ -420,17 +420,6 @@ export class SplitView extends Disposable { this.layoutViews(); } - private relayout(lowPriorityIndex?: number, highPriorityIndex?: number): void { - const contentSize = this.viewItems.reduce((r, i) => r + i.size, 0); - const lowPriorityIndexes = typeof lowPriorityIndex === 'number' ? [lowPriorityIndex] : undefined; - const highPriorityIndexes = typeof highPriorityIndex === 'number' ? [highPriorityIndex] : undefined; - - this.resize(this.viewItems.length - 1, this.size - contentSize, undefined, lowPriorityIndexes, highPriorityIndexes); - this.distributeEmptySpace(); - this.layoutViews(); - this.saveProportions(); - } - layout(size: number): void { const previousSize = Math.max(this.size, this.contentSize); this.size = size; @@ -582,7 +571,7 @@ export class SplitView extends Disposable { this.layoutViews(); } else { item.size = size; - this.relayout(index, undefined); + this.relayout([index], undefined); } } @@ -597,42 +586,32 @@ export class SplitView extends Disposable { return; } + const indexes = range(this.viewItems.length).filter(i => i !== index); + const lowPriorityIndexes = [...indexes.filter(i => this.viewItems[i].priority === LayoutPriority.Low), index]; + const highPriorityIndexes = indexes.filter(i => this.viewItems[i].priority === LayoutPriority.High); + const item = this.viewItems[index]; size = Math.round(size); - size = clamp(size, item.minimumSize, item.maximumSize); - let delta = size - item.size; + size = clamp(size, item.minimumSize, Math.min(item.maximumSize, this.size)); - if (delta !== 0 && index < this.viewItems.length - 1) { - const downIndexes = range(index + 1, this.viewItems.length); - const collapseDown = downIndexes.reduce((r, i) => r + (this.viewItems[i].size - this.viewItems[i].minimumSize), 0); - const expandDown = downIndexes.reduce((r, i) => r + (this.viewItems[i].maximumSize - this.viewItems[i].size), 0); - const deltaDown = clamp(delta, -expandDown, collapseDown); - - this.resize(index, deltaDown); - delta -= deltaDown; - } - - if (delta !== 0 && index > 0) { - const upIndexes = range(index - 1, -1); - const collapseUp = upIndexes.reduce((r, i) => r + (this.viewItems[i].size - this.viewItems[i].minimumSize), 0); - const expandUp = upIndexes.reduce((r, i) => r + (this.viewItems[i].maximumSize - this.viewItems[i].size), 0); - const deltaUp = clamp(-delta, -collapseUp, expandUp); - - this.resize(index - 1, deltaUp); - } - - this.distributeEmptySpace(); - this.layoutViews(); - this.saveProportions(); + item.size = size; + this.relayout(lowPriorityIndexes, highPriorityIndexes); this.state = State.Idle; } distributeViewSizes(): void { const size = Math.floor(this.size / this.viewItems.length); - for (let i = 0; i < this.viewItems.length - 1; i++) { - this.resizeView(i, size); + for (let i = 0; i < this.viewItems.length; i++) { + const item = this.viewItems[i]; + item.size = clamp(size, item.minimumSize, item.maximumSize); } + + const indexes = range(this.viewItems.length); + const lowPriorityIndexes = indexes.filter(i => this.viewItems[i].priority === LayoutPriority.Low); + const highPriorityIndexes = indexes.filter(i => this.viewItems[i].priority === LayoutPriority.High); + + this.relayout(lowPriorityIndexes, highPriorityIndexes); } getViewSize(index: number): number { @@ -643,6 +622,15 @@ export class SplitView extends Disposable { return this.viewItems[index].size; } + private relayout(lowPriorityIndexes?: number[], highPriorityIndexes?: number[]): void { + const contentSize = this.viewItems.reduce((r, i) => r + i.size, 0); + + this.resize(this.viewItems.length - 1, this.size - contentSize, undefined, lowPriorityIndexes, highPriorityIndexes); + this.distributeEmptySpace(); + this.layoutViews(); + this.saveProportions(); + } + private resize( index: number, delta: number, @@ -830,18 +818,26 @@ export class SplitView extends Disposable { private findFirstSnapIndex(indexes: number[]): number | undefined { // visible views first for (const index of indexes) { - if (!this.viewItems[index].visible) { + const viewItem = this.viewItems[index]; + + if (!viewItem.visible) { continue; } - if (this.viewItems[index].snap) { + if (viewItem.snap) { return index; } } // then, hidden views for (const index of indexes) { - if (!this.viewItems[index].visible && this.viewItems[index].snap) { + const viewItem = this.viewItems[index]; + + if (viewItem.visible && viewItem.maximumSize - viewItem.minimumSize > 0) { + return undefined; + } + + if (!viewItem.visible && viewItem.snap) { return index; } } diff --git a/src/vs/base/browser/ui/tree/compressedObjectTreeModel.ts b/src/vs/base/browser/ui/tree/compressedObjectTreeModel.ts new file mode 100644 index 0000000000..5f37a00c30 --- /dev/null +++ b/src/vs/base/browser/ui/tree/compressedObjectTreeModel.ts @@ -0,0 +1,404 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { ISpliceable } from 'vs/base/common/sequence'; +import { Iterator, ISequence } from 'vs/base/common/iterator'; +import { Event } from 'vs/base/common/event'; +import { ITreeModel, ITreeNode, ITreeElement, ICollapseStateChangeEvent, ITreeModelSpliceEvent } from 'vs/base/browser/ui/tree/tree'; +import { IObjectTreeModelOptions, ObjectTreeModel } from 'vs/base/browser/ui/tree/objectTreeModel'; + +export interface ICompressedTreeElement extends ITreeElement { + readonly children?: Iterator> | ICompressedTreeElement[]; + readonly incompressible?: boolean; +} + +export interface ICompressedTreeNode { + readonly elements: T[]; + readonly incompressible: boolean; +} + +export function compress(element: ICompressedTreeElement): ITreeElement> { + const elements = [element.element]; + const incompressible = element.incompressible || false; + + let childrenIterator: Iterator>; + let children: ITreeElement[]; + + while (true) { + childrenIterator = Iterator.from(element.children); + children = Iterator.collect(childrenIterator, 2); + + if (children.length !== 1) { + break; + } + + element = children[0]; + + if (element.incompressible) { + break; + } + + elements.push(element.element); + } + + return { + element: { elements, incompressible }, + children: Iterator.map(Iterator.concat(Iterator.fromArray(children), childrenIterator), compress) + }; +} + +export function _decompress(element: ITreeElement>, index = 0): ICompressedTreeElement { + let children: Iterator>; + + if (index < element.element.elements.length - 1) { + children = Iterator.single(_decompress(element, index + 1)); + } else { + children = Iterator.map(Iterator.from(element.children), el => _decompress(el, 0)); + } + + if (index === 0 && element.element.incompressible) { + return { element: element.element.elements[index], children, incompressible: true }; + } + + return { element: element.element.elements[index], children }; +} + +export function decompress(element: ITreeElement>): ICompressedTreeElement { + return _decompress(element, 0); +} + +export function splice(treeElement: ICompressedTreeElement, element: T, children: Iterator>): ICompressedTreeElement { + if (treeElement.element === element) { + return { element, children }; + } + + return { + ...treeElement, + children: Iterator.map(Iterator.from(treeElement.children), e => splice(e, element, children)) + }; +} + +export interface ICompressedObjectTreeModelOptions extends IObjectTreeModelOptions, TFilterData> { } + +export class CompressedObjectTreeModel, TFilterData extends NonNullable = void> implements ITreeModel | null, TFilterData, T | null> { + + readonly rootRef = null; + + get onDidSplice(): Event | null, TFilterData>> { return this.model.onDidSplice; } + get onDidChangeCollapseState(): Event, TFilterData>> { return this.model.onDidChangeCollapseState; } + get onDidChangeRenderNodeCount(): Event, TFilterData>> { return this.model.onDidChangeRenderNodeCount; } + + private model: ObjectTreeModel, TFilterData>; + private nodes = new Map>(); + + get size(): number { return this.nodes.size; } + + constructor(list: ISpliceable, TFilterData>>, options: ICompressedObjectTreeModelOptions = {}) { + this.model = new ObjectTreeModel(list, options); + } + + setChildren( + element: T | null, + children: ISequence> | undefined, + onDidCreateNode?: (node: ITreeNode, TFilterData>) => void, + onDidDeleteNode?: (node: ITreeNode, TFilterData>) => void + ): Iterator> { + const insertedElements = new Set(); + const _onDidCreateNode = (node: ITreeNode, TFilterData>) => { + for (const element of node.element.elements) { + insertedElements.add(element); + this.nodes.set(element, node.element); + } + + // if (this.identityProvider) { + // const id = this.identityProvider.getId(node.element).toString(); + // insertedElementIds.add(id); + // this.nodesByIdentity.set(id, node); + // } + + if (onDidCreateNode) { + onDidCreateNode(node); + } + }; + + const _onDidDeleteNode = (node: ITreeNode, TFilterData>) => { + for (const element of node.element.elements) { + if (!insertedElements.has(element)) { + this.nodes.delete(element); + } + } + + // if (this.identityProvider) { + // const id = this.identityProvider.getId(node.element).toString(); + // if (!insertedElementIds.has(id)) { + // this.nodesByIdentity.delete(id); + // } + // } + + if (onDidDeleteNode) { + onDidDeleteNode(node); + } + }; + + if (element === null) { + const compressedChildren = Iterator.map(Iterator.from(children), compress); + const result = this.model.setChildren(null, compressedChildren, _onDidCreateNode, _onDidDeleteNode); + return Iterator.map(result, decompress); + } + + const compressedNode = this.nodes.get(element); + const node = this.model.getNode(compressedNode) as ITreeNode, TFilterData>; + const parent = node.parent!; + + const decompressedElement = decompress(node); + const splicedElement = splice(decompressedElement, element, Iterator.from(children)); + const recompressedElement = compress(splicedElement); + + const parentChildren = parent.children + .map(child => child === node ? recompressedElement : child); + + this.model.setChildren(parent.element, parentChildren, _onDidCreateNode, _onDidDeleteNode); + + // TODO + return Iterator.empty(); + } + + getListIndex(location: T | null): number { + const node = this.getCompressedNode(location); + return this.model.getListIndex(node); + } + + getListRenderCount(location: T | null): number { + const node = this.getCompressedNode(location); + return this.model.getListRenderCount(node); + } + + getNode(location?: T | null | undefined): ITreeNode | null, TFilterData> { + if (typeof location === 'undefined') { + return this.model.getNode(); + } + + const node = this.getCompressedNode(location); + return this.model.getNode(node); + } + + // TODO: review this + getNodeLocation(node: ITreeNode, TFilterData>): T | null { + const compressedNode = this.model.getNodeLocation(node); + + if (compressedNode === null) { + return null; + } + + return compressedNode.elements[compressedNode.elements.length - 1]; + } + + // TODO: review this + getParentNodeLocation(location: T | null): T | null { + const compressedNode = this.getCompressedNode(location); + const parentNode = this.model.getParentNodeLocation(compressedNode); + + if (parentNode === null) { + return null; + } + + return parentNode.elements[parentNode.elements.length - 1]; + } + + getParentElement(location: T | null): ICompressedTreeNode | null { + const compressedNode = this.getCompressedNode(location); + return this.model.getParentElement(compressedNode); + } + + getFirstElementChild(location: T | null): ICompressedTreeNode | null | undefined { + const compressedNode = this.getCompressedNode(location); + return this.model.getFirstElementChild(compressedNode); + } + + getLastElementAncestor(location?: T | null | undefined): ICompressedTreeNode | null | undefined { + const compressedNode = typeof location === 'undefined' ? undefined : this.getCompressedNode(location); + return this.model.getLastElementAncestor(compressedNode); + } + + isCollapsible(location: T | null): boolean { + const compressedNode = this.getCompressedNode(location); + return this.model.isCollapsible(compressedNode); + } + + isCollapsed(location: T | null): boolean { + const compressedNode = this.getCompressedNode(location); + return this.model.isCollapsed(compressedNode); + } + + setCollapsed(location: T | null, collapsed?: boolean | undefined, recursive?: boolean | undefined): boolean { + const compressedNode = this.getCompressedNode(location); + return this.model.setCollapsed(compressedNode, collapsed, recursive); + } + + expandTo(location: T | null): void { + const compressedNode = this.getCompressedNode(location); + this.model.expandTo(compressedNode); + } + + rerender(location: T | null): void { + const compressedNode = this.getCompressedNode(location); + this.model.rerender(compressedNode); + } + + refilter(): void { + this.model.refilter(); + } + + resort(location: T | null = null, recursive = true): void { + const compressedNode = this.getCompressedNode(location); + this.model.resort(compressedNode, recursive); + } + + private getCompressedNode(element: T | null): ICompressedTreeNode | null { + if (element === null) { + return null; + } + + const node = this.nodes.get(element); + + if (!node) { + throw new Error(`Tree element not found: ${element}`); + } + + return node; + } +} + +export type ElementMapper = (elements: T[]) => T; +export const DefaultElementMapper: ElementMapper = elements => elements[elements.length - 1]; + +export type NodeMapper = (node: ITreeNode | null, TFilterData>) => ITreeNode; + +function mapNode(elementMapper: ElementMapper, node: ITreeNode | null, TFilterData>): ITreeNode { + return { + ...node, + element: node.element === null ? null : elementMapper(node.element.elements), + children: node.children.map(child => mapNode(elementMapper, child)), + parent: typeof node.parent === 'undefined' ? node.parent : mapNode(elementMapper, node.parent) + }; +} + +function createNodeMapper(elementMapper: ElementMapper): NodeMapper { + return node => mapNode(elementMapper, node); +} + +export interface ILinearCompressedObjectTreeModelOptions extends ICompressedObjectTreeModelOptions { + readonly elementMapper?: ElementMapper; +} + +export class LinearCompressedObjectTreeModel, TFilterData extends NonNullable = void> implements ITreeModel { + + readonly rootRef = null; + + get onDidSplice(): Event> { + return Event.map(this.model.onDidSplice, ({ insertedNodes, deletedNodes }) => ({ + insertedNodes: insertedNodes.map(this.mapNode), + deletedNodes: deletedNodes.map(this.mapNode), + })); + } + + get onDidChangeCollapseState(): Event> { + return Event.map(this.model.onDidChangeCollapseState, ({ node, deep }) => ({ + node: this.mapNode(node), + deep + })); + } + + get onDidChangeRenderNodeCount(): Event> { + return Event.map(this.model.onDidChangeRenderNodeCount, this.mapNode); + } + + private mapElement: ElementMapper; + private mapNode: NodeMapper; + private model: CompressedObjectTreeModel; + + constructor( + list: ISpliceable, TFilterData>>, + options: ILinearCompressedObjectTreeModelOptions = {} + ) { + this.mapElement = options.elementMapper || DefaultElementMapper; + this.mapNode = createNodeMapper(this.mapElement); + this.model = new CompressedObjectTreeModel(list, options); + } + + getListIndex(location: T | null): number { + return this.model.getListIndex(location); + } + + getListRenderCount(location: T | null): number { + return this.model.getListRenderCount(location); + } + + getNode(location?: T | null | undefined): ITreeNode { + return this.mapNode(this.model.getNode(location)); + } + + getNodeLocation(node: ITreeNode): T | null { + return node.element; + } + + getParentNodeLocation(location: T | null): T | null { + return this.model.getParentNodeLocation(location); + } + + getParentElement(location: T | null): T | null { + const result = this.model.getParentElement(location); + + if (result === null) { + return null; // {{SQL CARBON EDIT}} strict-null-check + } + + return this.mapElement(result.elements); + } + + getFirstElementChild(location: T | null): T | null | undefined { + const result = this.model.getFirstElementChild(location); + + if (result === null || typeof result === 'undefined') { + return null; // {{SQL CARBON EDIT}} strict-null-check + } + + return this.mapElement(result.elements); + } + + getLastElementAncestor(location?: T | null | undefined): T | null | undefined { + const result = this.model.getLastElementAncestor(location); + + if (result === null || typeof result === 'undefined') { + return null; // {{SQL CARBON EDIT}} strict-null-check + } + + return this.mapElement(result.elements); + } + + isCollapsible(location: T | null): boolean { + return this.model.isCollapsible(location); + } + + isCollapsed(location: T | null): boolean { + return this.model.isCollapsed(location); + } + + setCollapsed(location: T | null, collapsed?: boolean | undefined, recursive?: boolean | undefined): boolean { + return this.model.setCollapsed(location, collapsed, recursive); + } + + expandTo(location: T | null): void { + return this.model.expandTo(location); + } + + rerender(location: T | null): void { + return this.model.rerender(location); + } + + refilter(): void { + return this.model.refilter(); + } +} \ No newline at end of file diff --git a/src/vs/base/browser/ui/tree/objectTreeModel.ts b/src/vs/base/browser/ui/tree/objectTreeModel.ts index 881ae62dcd..c96bb9ca76 100644 --- a/src/vs/base/browser/ui/tree/objectTreeModel.ts +++ b/src/vs/base/browser/ui/tree/objectTreeModel.ts @@ -53,7 +53,7 @@ export class ObjectTreeModel, TFilterData extends Non children: ISequence> | undefined, onDidCreateNode?: (node: ITreeNode) => void, onDidDeleteNode?: (node: ITreeNode) => void - ): Iterator> { + ): Iterator> { const location = this.getElementLocation(element); return this._setChildren(location, this.preserveCollapseState(children), onDidCreateNode, onDidDeleteNode); } @@ -63,7 +63,7 @@ export class ObjectTreeModel, TFilterData extends Non children: ISequence> | undefined, onDidCreateNode?: (node: ITreeNode) => void, onDidDeleteNode?: (node: ITreeNode) => void - ): Iterator> { + ): Iterator> { const insertedElements = new Set(); const insertedElementIds = new Set(); @@ -107,7 +107,7 @@ export class ObjectTreeModel, TFilterData extends Non _onDidDeleteNode ); - return result; + return result as Iterator>; } private preserveCollapseState(elements: ISequence> | undefined): ISequence> { @@ -144,7 +144,7 @@ export class ObjectTreeModel, TFilterData extends Non }); } - rerender(element: T): void { + rerender(element: T | null): void { const location = this.getElementLocation(element); this.model.rerender(location); } @@ -190,32 +190,32 @@ export class ObjectTreeModel, TFilterData extends Non return this.model.getLastElementAncestor(location); } - getListIndex(element: T): number { + getListIndex(element: T | null): number { const location = this.getElementLocation(element); return this.model.getListIndex(location); } - getListRenderCount(element: T): number { + getListRenderCount(element: T | null): number { const location = this.getElementLocation(element); return this.model.getListRenderCount(location); } - isCollapsible(element: T): boolean { + isCollapsible(element: T | null): boolean { const location = this.getElementLocation(element); return this.model.isCollapsible(location); } - isCollapsed(element: T): boolean { + isCollapsed(element: T | null): boolean { const location = this.getElementLocation(element); return this.model.isCollapsed(location); } - setCollapsed(element: T, collapsed?: boolean, recursive?: boolean): boolean { + setCollapsed(element: T | null, collapsed?: boolean, recursive?: boolean): boolean { const location = this.getElementLocation(element); return this.model.setCollapsed(location, collapsed, recursive); } - expandTo(element: T): void { + expandTo(element: T | null): void { const location = this.getElementLocation(element); this.model.expandTo(location); } @@ -238,11 +238,15 @@ export class ObjectTreeModel, TFilterData extends Non return node; } - getNodeLocation(node: ITreeNode): T { + getNodeLocation(node: ITreeNode): T | null { return node.element; } - getParentNodeLocation(element: T): T | null { + getParentNodeLocation(element: T | null): T | null { + if (element === null) { + throw new Error(`Invalid getParentNodeLocation call`); + } + const node = this.nodes.get(element); if (!node) { diff --git a/src/vs/base/browser/ui/tree/tree.ts b/src/vs/base/browser/ui/tree/tree.ts index 41c38c1192..1847125b1b 100644 --- a/src/vs/base/browser/ui/tree/tree.ts +++ b/src/vs/base/browser/ui/tree/tree.ts @@ -124,6 +124,7 @@ export interface ITreeModel { setCollapsed(location: TRef, collapsed?: boolean, recursive?: boolean): boolean; expandTo(location: TRef): void; + rerender(location: TRef): void; refilter(): void; } diff --git a/src/vs/base/common/iterator.ts b/src/vs/base/common/iterator.ts index 910bdeae35..09cfdaa47e 100644 --- a/src/vs/base/common/iterator.ts +++ b/src/vs/base/common/iterator.ts @@ -29,6 +29,21 @@ export module Iterator { return _empty; } + export function single(value: T): Iterator { + let done = false; + + return { + next(): IteratorResult { + if (done) { + return FIN; + } + + done = true; + return { done: false, value }; + } + }; + } + export function fromArray(array: T[], index = 0, length = array.length): Iterator { return { next(): IteratorResult { @@ -86,11 +101,47 @@ export module Iterator { } } - export function collect(iterator: Iterator): T[] { + export function collect(iterator: Iterator, atMost: number = Number.POSITIVE_INFINITY): T[] { const result: T[] = []; - forEach(iterator, value => result.push(value)); + + if (atMost === 0) { + return result; + } + + let i = 0; + + for (let next = iterator.next(); !next.done; next = iterator.next()) { + result.push(next.value); + + if (++i >= atMost) { + break; + } + } + return result; } + + export function concat(...iterators: Iterator[]): Iterator { + let i = 0; + + return { + next() { + if (i >= iterators.length) { + return FIN; + } + + const iterator = iterators[i]; + const result = iterator.next(); + + if (result.done) { + i++; + return this.next(); + } + + return result; + } + }; + } } export type ISequence = Iterator | T[]; diff --git a/src/vs/base/test/browser/ui/grid/gridview.test.ts b/src/vs/base/test/browser/ui/grid/gridview.test.ts index dfec571300..86e246ee35 100644 --- a/src/vs/base/test/browser/ui/grid/gridview.test.ts +++ b/src/vs/base/test/browser/ui/grid/gridview.test.ts @@ -107,19 +107,19 @@ suite('Gridview', function () { test('simple layout', function () { gridview.layout(800, 600); - const view1 = new TestView(50, Number.MAX_VALUE, 50, Number.MAX_VALUE); + const view1 = new TestView(50, Number.POSITIVE_INFINITY, 50, Number.POSITIVE_INFINITY); gridview.addView(view1, 200, [0]); assert.deepEqual(view1.size, [800, 600]); assert.deepEqual(gridview.getViewSize([0]), { width: 800, height: 600 }); - const view2 = new TestView(50, Number.MAX_VALUE, 50, Number.MAX_VALUE); + const view2 = new TestView(50, Number.POSITIVE_INFINITY, 50, Number.POSITIVE_INFINITY); gridview.addView(view2, 200, [0]); assert.deepEqual(view1.size, [800, 400]); assert.deepEqual(gridview.getViewSize([1]), { width: 800, height: 400 }); assert.deepEqual(view2.size, [800, 200]); assert.deepEqual(gridview.getViewSize([0]), { width: 800, height: 200 }); - const view3 = new TestView(50, Number.MAX_VALUE, 50, Number.MAX_VALUE); + const view3 = new TestView(50, Number.POSITIVE_INFINITY, 50, Number.POSITIVE_INFINITY); gridview.addView(view3, 200, [1, 1]); assert.deepEqual(view1.size, [600, 400]); assert.deepEqual(gridview.getViewSize([1, 0]), { width: 600, height: 400 }); @@ -128,7 +128,7 @@ suite('Gridview', function () { assert.deepEqual(view3.size, [200, 400]); assert.deepEqual(gridview.getViewSize([1, 1]), { width: 200, height: 400 }); - const view4 = new TestView(50, Number.MAX_VALUE, 50, Number.MAX_VALUE); + const view4 = new TestView(50, Number.POSITIVE_INFINITY, 50, Number.POSITIVE_INFINITY); gridview.addView(view4, 200, [0, 0]); assert.deepEqual(view1.size, [600, 400]); assert.deepEqual(gridview.getViewSize([1, 0]), { width: 600, height: 400 }); @@ -139,7 +139,7 @@ suite('Gridview', function () { assert.deepEqual(view4.size, [200, 200]); assert.deepEqual(gridview.getViewSize([0, 0]), { width: 200, height: 200 }); - const view5 = new TestView(50, Number.MAX_VALUE, 50, Number.MAX_VALUE); + const view5 = new TestView(50, Number.POSITIVE_INFINITY, 50, Number.POSITIVE_INFINITY); gridview.addView(view5, 100, [1, 0, 1]); assert.deepEqual(view1.size, [600, 300]); assert.deepEqual(gridview.getViewSize([1, 0, 0]), { width: 600, height: 300 }); @@ -156,30 +156,30 @@ suite('Gridview', function () { test('simple layout with automatic size distribution', function () { gridview.layout(800, 600); - const view1 = new TestView(50, Number.MAX_VALUE, 50, Number.MAX_VALUE); + const view1 = new TestView(50, Number.POSITIVE_INFINITY, 50, Number.POSITIVE_INFINITY); gridview.addView(view1, Sizing.Distribute, [0]); assert.deepEqual(view1.size, [800, 600]); assert.deepEqual(gridview.getViewSize([0]), { width: 800, height: 600 }); - const view2 = new TestView(50, Number.MAX_VALUE, 50, Number.MAX_VALUE); + const view2 = new TestView(50, Number.POSITIVE_INFINITY, 50, Number.POSITIVE_INFINITY); gridview.addView(view2, Sizing.Distribute, [0]); assert.deepEqual(view1.size, [800, 300]); assert.deepEqual(view2.size, [800, 300]); - const view3 = new TestView(50, Number.MAX_VALUE, 50, Number.MAX_VALUE); + const view3 = new TestView(50, Number.POSITIVE_INFINITY, 50, Number.POSITIVE_INFINITY); gridview.addView(view3, Sizing.Distribute, [1, 1]); assert.deepEqual(view1.size, [400, 300]); assert.deepEqual(view2.size, [800, 300]); assert.deepEqual(view3.size, [400, 300]); - const view4 = new TestView(50, Number.MAX_VALUE, 50, Number.MAX_VALUE); + const view4 = new TestView(50, Number.POSITIVE_INFINITY, 50, Number.POSITIVE_INFINITY); gridview.addView(view4, Sizing.Distribute, [0, 0]); assert.deepEqual(view1.size, [400, 300]); assert.deepEqual(view2.size, [400, 300]); assert.deepEqual(view3.size, [400, 300]); assert.deepEqual(view4.size, [400, 300]); - const view5 = new TestView(50, Number.MAX_VALUE, 50, Number.MAX_VALUE); + const view5 = new TestView(50, Number.POSITIVE_INFINITY, 50, Number.POSITIVE_INFINITY); gridview.addView(view5, Sizing.Distribute, [1, 0, 1]); assert.deepEqual(view1.size, [400, 150]); assert.deepEqual(view2.size, [400, 300]); @@ -190,13 +190,13 @@ suite('Gridview', function () { test('addviews before layout call 1', function () { - const view1 = new TestView(50, Number.MAX_VALUE, 50, Number.MAX_VALUE); + const view1 = new TestView(50, Number.POSITIVE_INFINITY, 50, Number.POSITIVE_INFINITY); gridview.addView(view1, 200, [0]); - const view2 = new TestView(50, Number.MAX_VALUE, 50, Number.MAX_VALUE); + const view2 = new TestView(50, Number.POSITIVE_INFINITY, 50, Number.POSITIVE_INFINITY); gridview.addView(view2, 200, [0]); - const view3 = new TestView(50, Number.MAX_VALUE, 50, Number.MAX_VALUE); + const view3 = new TestView(50, Number.POSITIVE_INFINITY, 50, Number.POSITIVE_INFINITY); gridview.addView(view3, 200, [1, 1]); gridview.layout(800, 600); @@ -207,14 +207,13 @@ suite('Gridview', function () { }); test('addviews before layout call 2', function () { - - const view1 = new TestView(50, Number.MAX_VALUE, 50, Number.MAX_VALUE); + const view1 = new TestView(50, Number.POSITIVE_INFINITY, 50, Number.POSITIVE_INFINITY); gridview.addView(view1, 200, [0]); - const view2 = new TestView(50, Number.MAX_VALUE, 50, Number.MAX_VALUE); + const view2 = new TestView(50, Number.POSITIVE_INFINITY, 50, Number.POSITIVE_INFINITY); gridview.addView(view2, 200, [0]); - const view3 = new TestView(50, Number.MAX_VALUE, 50, Number.MAX_VALUE); + const view3 = new TestView(50, Number.POSITIVE_INFINITY, 50, Number.POSITIVE_INFINITY); gridview.addView(view3, 200, [0, 0]); gridview.layout(800, 600); diff --git a/src/vs/base/test/browser/ui/splitview/splitview.test.ts b/src/vs/base/test/browser/ui/splitview/splitview.test.ts index 1748a32c33..a6cfa772dd 100644 --- a/src/vs/base/test/browser/ui/splitview/splitview.test.ts +++ b/src/vs/base/test/browser/ui/splitview/splitview.test.ts @@ -196,8 +196,8 @@ suite('Splitview', () => { splitview.resizeView(0, 70); assert.equal(view1.size, 70, 'view1 is collapsed'); - assert.equal(view2.size, 110, 'view2 is expanded'); - assert.equal(view3.size, 20, 'view3 stays the same'); + assert.equal(view2.size, 40, 'view2 stays the same'); + assert.equal(view3.size, 90, 'view3 is stretched'); splitview.resizeView(2, 40); @@ -474,10 +474,10 @@ suite('Splitview', () => { splitview.addView(view1, Sizing.Distribute); splitview.addView(view2, Sizing.Distribute); splitview.addView(view3, Sizing.Distribute); - assert.deepEqual([view1.size, view2.size, view3.size], [66, 66, 68]); + assert.deepEqual([view1.size, view2.size, view3.size], [66, 68, 66]); splitview.layout(180); - assert.deepEqual([view1.size, view2.size, view3.size], [66, 46, 68]); + assert.deepEqual([view1.size, view2.size, view3.size], [66, 48, 66]); splitview.layout(124); assert.deepEqual([view1.size, view2.size, view3.size], [66, 20, 38]); @@ -504,13 +504,13 @@ suite('Splitview', () => { splitview.addView(view1, Sizing.Distribute); splitview.addView(view2, Sizing.Distribute); splitview.addView(view3, Sizing.Distribute); - assert.deepEqual([view1.size, view2.size, view3.size], [66, 66, 68]); + assert.deepEqual([view1.size, view2.size, view3.size], [66, 68, 66]); splitview.layout(180); - assert.deepEqual([view1.size, view2.size, view3.size], [66, 46, 68]); + assert.deepEqual([view1.size, view2.size, view3.size], [66, 48, 66]); splitview.layout(132); - assert.deepEqual([view1.size, view2.size, view3.size], [44, 20, 68]); + assert.deepEqual([view1.size, view2.size, view3.size], [46, 20, 66]); splitview.layout(60); assert.deepEqual([view1.size, view2.size, view3.size], [20, 20, 20]); diff --git a/src/vs/base/test/browser/ui/tree/compressedObjectTreeModel.test.ts b/src/vs/base/test/browser/ui/tree/compressedObjectTreeModel.test.ts new file mode 100644 index 0000000000..34cbe612a9 --- /dev/null +++ b/src/vs/base/test/browser/ui/tree/compressedObjectTreeModel.test.ts @@ -0,0 +1,430 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as assert from 'assert'; +import { compress, ICompressedTreeElement, ICompressedTreeNode, decompress, CompressedObjectTreeModel } from 'vs/base/browser/ui/tree/compressedObjectTreeModel'; +import { Iterator } from 'vs/base/common/iterator'; +import { ITreeNode } from 'vs/base/browser/ui/tree/tree'; +import { ISpliceable } from 'vs/base/common/sequence'; + +interface IResolvedCompressedTreeElement extends ICompressedTreeElement { + readonly element: T; + readonly children?: ICompressedTreeElement[]; +} + +function resolve(treeElement: ICompressedTreeElement): IResolvedCompressedTreeElement { + const result: any = { element: treeElement.element }; + const children = Iterator.collect(Iterator.map(Iterator.from(treeElement.children), resolve)); + + if (treeElement.incompressible) { + result.incompressible = true; + } + + if (children.length > 0) { + result.children = children; + } + + return result; +} + +suite('CompressedObjectTree', function () { + + suite('compress & decompress', function () { + + test('small', function () { + const decompressed: ICompressedTreeElement = { element: 1 }; + const compressed: IResolvedCompressedTreeElement> = + { element: { elements: [1], incompressible: false } }; + + assert.deepEqual(resolve(compress(decompressed)), compressed); + assert.deepEqual(resolve(decompress(compressed)), decompressed); + }); + + test('no compression', function () { + const decompressed: ICompressedTreeElement = { + element: 1, children: [ + { element: 11 }, + { element: 12 }, + { element: 13 } + ] + }; + + const compressed: IResolvedCompressedTreeElement> = { + element: { elements: [1], incompressible: false }, + children: [ + { element: { elements: [11], incompressible: false } }, + { element: { elements: [12], incompressible: false } }, + { element: { elements: [13], incompressible: false } } + ] + }; + + assert.deepEqual(resolve(compress(decompressed)), compressed); + assert.deepEqual(resolve(decompress(compressed)), decompressed); + }); + + test('single hierarchy', function () { + const decompressed: ICompressedTreeElement = { + element: 1, children: [ + { + element: 11, children: [ + { + element: 111, children: [ + { element: 1111 } + ] + } + ] + } + ] + }; + + const compressed: IResolvedCompressedTreeElement> = { + element: { elements: [1, 11, 111, 1111], incompressible: false } + }; + + assert.deepEqual(resolve(compress(decompressed)), compressed); + assert.deepEqual(resolve(decompress(compressed)), decompressed); + }); + + test('deep compression', function () { + const decompressed: ICompressedTreeElement = { + element: 1, children: [ + { + element: 11, children: [ + { + element: 111, children: [ + { element: 1111 }, + { element: 1112 }, + { element: 1113 }, + { element: 1114 }, + ] + } + ] + } + ] + }; + + const compressed: IResolvedCompressedTreeElement> = { + element: { elements: [1, 11, 111], incompressible: false }, + children: [ + { element: { elements: [1111], incompressible: false } }, + { element: { elements: [1112], incompressible: false } }, + { element: { elements: [1113], incompressible: false } }, + { element: { elements: [1114], incompressible: false } }, + ] + }; + + assert.deepEqual(resolve(compress(decompressed)), compressed); + assert.deepEqual(resolve(decompress(compressed)), decompressed); + }); + + test('double deep compression', function () { + const decompressed: ICompressedTreeElement = { + element: 1, children: [ + { + element: 11, children: [ + { + element: 111, children: [ + { element: 1112 }, + { element: 1113 }, + ] + } + ] + }, + { + element: 12, children: [ + { + element: 121, children: [ + { element: 1212 }, + { element: 1213 }, + ] + } + ] + } + ] + }; + + const compressed: IResolvedCompressedTreeElement> = { + element: { elements: [1], incompressible: false }, + children: [ + { + element: { elements: [11, 111], incompressible: false }, + children: [ + { element: { elements: [1112], incompressible: false } }, + { element: { elements: [1113], incompressible: false } }, + ] + }, + { + element: { elements: [12, 121], incompressible: false }, + children: [ + { element: { elements: [1212], incompressible: false } }, + { element: { elements: [1213], incompressible: false } }, + ] + } + ] + }; + + assert.deepEqual(resolve(compress(decompressed)), compressed); + assert.deepEqual(resolve(decompress(compressed)), decompressed); + }); + + test('incompressible leaf', function () { + const decompressed: ICompressedTreeElement = { + element: 1, children: [ + { + element: 11, children: [ + { + element: 111, children: [ + { element: 1111, incompressible: true } + ] + } + ] + } + ] + }; + + const compressed: IResolvedCompressedTreeElement> = { + element: { elements: [1, 11, 111], incompressible: false }, + children: [ + { element: { elements: [1111], incompressible: true } } + ] + }; + + assert.deepEqual(resolve(compress(decompressed)), compressed); + assert.deepEqual(resolve(decompress(compressed)), decompressed); + }); + + test('incompressible branch', function () { + const decompressed: ICompressedTreeElement = { + element: 1, children: [ + { + element: 11, children: [ + { + element: 111, incompressible: true, children: [ + { element: 1111 } + ] + } + ] + } + ] + }; + + const compressed: IResolvedCompressedTreeElement> = { + element: { elements: [1, 11], incompressible: false }, + children: [ + { element: { elements: [111, 1111], incompressible: true } } + ] + }; + + assert.deepEqual(resolve(compress(decompressed)), compressed); + assert.deepEqual(resolve(decompress(compressed)), decompressed); + }); + + test('incompressible chain', function () { + const decompressed: ICompressedTreeElement = { + element: 1, children: [ + { + element: 11, children: [ + { + element: 111, incompressible: true, children: [ + { element: 1111, incompressible: true } + ] + } + ] + } + ] + }; + + const compressed: IResolvedCompressedTreeElement> = { + element: { elements: [1, 11], incompressible: false }, + children: [ + { + element: { elements: [111], incompressible: true }, + children: [ + { element: { elements: [1111], incompressible: true } } + ] + } + ] + }; + + assert.deepEqual(resolve(compress(decompressed)), compressed); + assert.deepEqual(resolve(decompress(compressed)), decompressed); + }); + + test('incompressible tree', function () { + const decompressed: ICompressedTreeElement = { + element: 1, children: [ + { + element: 11, incompressible: true, children: [ + { + element: 111, incompressible: true, children: [ + { element: 1111, incompressible: true } + ] + } + ] + } + ] + }; + + const compressed: IResolvedCompressedTreeElement> = { + element: { elements: [1], incompressible: false }, + children: [ + { + element: { elements: [11], incompressible: true }, + children: [ + { + element: { elements: [111], incompressible: true }, + children: [ + { element: { elements: [1111], incompressible: true } } + ] + } + ] + } + ] + }; + + assert.deepEqual(resolve(compress(decompressed)), compressed); + assert.deepEqual(resolve(decompress(compressed)), decompressed); + }); + }); + + function toSpliceable(arr: T[]): ISpliceable { + return { + splice(start: number, deleteCount: number, elements: T[]): void { + arr.splice(start, deleteCount, ...elements); + } + }; + } + + function toArray(list: ITreeNode>[]): T[][] { + return list.map(i => i.element.elements); + } + + suite('CompressedObjectTreeModel', function () { + + test('ctor', () => { + const list: ITreeNode>[] = []; + const model = new CompressedObjectTreeModel(toSpliceable(list)); + assert(model); + assert.equal(list.length, 0); + assert.equal(model.size, 0); + }); + + test('flat', () => { + const list: ITreeNode>[] = []; + const model = new CompressedObjectTreeModel(toSpliceable(list)); + + model.setChildren(null, Iterator.fromArray([ + { element: 0 }, + { element: 1 }, + { element: 2 } + ])); + + assert.deepEqual(toArray(list), [[0], [1], [2]]); + assert.equal(model.size, 3); + + model.setChildren(null, Iterator.fromArray([ + { element: 3 }, + { element: 4 }, + { element: 5 }, + ])); + + assert.deepEqual(toArray(list), [[3], [4], [5]]); + assert.equal(model.size, 3); + + model.setChildren(null, Iterator.empty()); + assert.deepEqual(toArray(list), []); + assert.equal(model.size, 0); + }); + + test('nested', () => { + const list: ITreeNode>[] = []; + const model = new CompressedObjectTreeModel(toSpliceable(list)); + + model.setChildren(null, Iterator.fromArray([ + { + element: 0, children: Iterator.fromArray([ + { element: 10 }, + { element: 11 }, + { element: 12 }, + ]) + }, + { element: 1 }, + { element: 2 } + ])); + + assert.deepEqual(toArray(list), [[0], [10], [11], [12], [1], [2]]); + assert.equal(model.size, 6); + + model.setChildren(12, Iterator.fromArray([ + { element: 120 }, + { element: 121 } + ])); + + assert.deepEqual(toArray(list), [[0], [10], [11], [12], [120], [121], [1], [2]]); + assert.equal(model.size, 8); + + model.setChildren(0, Iterator.empty()); + assert.deepEqual(toArray(list), [[0], [1], [2]]); + assert.equal(model.size, 3); + + model.setChildren(null, Iterator.empty()); + assert.deepEqual(toArray(list), []); + assert.equal(model.size, 0); + }); + + test('compressed', () => { + const list: ITreeNode>[] = []; + const model = new CompressedObjectTreeModel(toSpliceable(list)); + + model.setChildren(null, Iterator.fromArray([ + { + element: 1, children: Iterator.fromArray([{ + element: 11, children: Iterator.fromArray([{ + element: 111, children: Iterator.fromArray([ + { element: 1111 }, + { element: 1112 }, + { element: 1113 }, + ]) + }]) + }]) + } + ])); + + assert.deepEqual(toArray(list), [[1, 11, 111], [1111], [1112], [1113]]); + assert.equal(model.size, 6); + + model.setChildren(11, Iterator.fromArray([ + { element: 111 }, + { element: 112 }, + { element: 113 }, + ])); + + assert.deepEqual(toArray(list), [[1, 11], [111], [112], [113]]); + assert.equal(model.size, 5); + + model.setChildren(113, Iterator.fromArray([ + { element: 1131 } + ])); + + assert.deepEqual(toArray(list), [[1, 11], [111], [112], [113, 1131]]); + assert.equal(model.size, 6); + + model.setChildren(1131, Iterator.fromArray([ + { element: 1132 } + ])); + + assert.deepEqual(toArray(list), [[1, 11], [111], [112], [113, 1131, 1132]]); + assert.equal(model.size, 7); + + model.setChildren(1131, Iterator.fromArray([ + { element: 1132 }, + { element: 1133 }, + ])); + + assert.deepEqual(toArray(list), [[1, 11], [111], [112], [113, 1131], [1132], [1133]]); + assert.equal(model.size, 8); + }); + }); +}); \ No newline at end of file diff --git a/src/vs/base/test/common/iterator.test.ts b/src/vs/base/test/common/iterator.test.ts new file mode 100644 index 0000000000..8f19312388 --- /dev/null +++ b/src/vs/base/test/common/iterator.test.ts @@ -0,0 +1,19 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as assert from 'assert'; +import { Iterator } from 'vs/base/common/iterator'; + +suite('Iterator', () => { + test('concat', () => { + const first = Iterator.fromArray([1, 2, 3]); + const second = Iterator.fromArray([4, 5, 6]); + const third = Iterator.fromArray([7, 8, 9]); + const actualIterator = Iterator.concat(first, second, third); + const actual = Iterator.collect(actualIterator); + + assert.deepEqual(actual, [1, 2, 3, 4, 5, 6, 7, 8, 9]); + }); +}); \ No newline at end of file diff --git a/src/vs/code/browser/workbench/workbench.html b/src/vs/code/browser/workbench/workbench.html index 829ce5e4ae..097dd15b3c 100644 --- a/src/vs/code/browser/workbench/workbench.html +++ b/src/vs/code/browser/workbench/workbench.html @@ -10,7 +10,7 @@ + content="default-src 'none'; img-src 'self' https: data: blob: vscode-remote:; media-src 'none'; frame-src 'self' {{WEBVIEW_ENDPOINT}} https://*.vscode-webview-test.com; script-src 'self' https://az416426.vo.msecnd.net 'unsafe-eval'; style-src 'self' 'unsafe-inline'; connect-src 'self' ws: wss: https:; font-src 'self' blob: vscode-remote:;"> diff --git a/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts b/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts index 50f6ee2ae7..d6e3e908ec 100644 --- a/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts +++ b/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts @@ -38,7 +38,7 @@ import { LocalizationsChannel } from 'vs/platform/localizations/node/localizatio import { DialogChannelClient } from 'vs/platform/dialogs/node/dialogIpc'; import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; import { combinedDisposable, DisposableStore } from 'vs/base/common/lifecycle'; -import { DownloadService } from 'vs/platform/download/node/downloadService'; +import { DownloadService } from 'vs/platform/download/common/downloadService'; import { IDownloadService } from 'vs/platform/download/common/download'; import { IChannel, IServerChannel, StaticRouter } from 'vs/base/parts/ipc/common/ipc'; import { NodeCachedDataCleaner } from 'vs/code/electron-browser/sharedProcess/contrib/nodeCachedDataCleaner'; @@ -115,7 +115,6 @@ async function main(server: Server, initData: ISharedProcessInitData, configurat services.set(ILogService, logService); services.set(IConfigurationService, configurationService); services.set(IRequestService, new SyncDescriptor(RequestService)); - services.set(IDownloadService, new SyncDescriptor(DownloadService)); services.set(IProductService, new SyncDescriptor(ProductService)); const mainProcessService = new MainProcessService(server, mainRouter); @@ -138,6 +137,8 @@ async function main(server: Server, initData: ISharedProcessInitData, configurat disposables.add(diskFileSystemProvider); fileService.registerProvider(Schemas.file, diskFileSystemProvider); + services.set(IDownloadService, new SyncDescriptor(DownloadService)); + const instantiationService = new InstantiationService(services); let telemetryService: ITelemetryService; diff --git a/src/vs/code/electron-browser/workbench/workbench.html b/src/vs/code/electron-browser/workbench/workbench.html index f347dafb70..a8e69a3aab 100644 --- a/src/vs/code/electron-browser/workbench/workbench.html +++ b/src/vs/code/electron-browser/workbench/workbench.html @@ -4,7 +4,7 @@ - + diff --git a/src/vs/editor/browser/controller/coreCommands.ts b/src/vs/editor/browser/controller/coreCommands.ts index 0f2f3327c0..415b4574a4 100644 --- a/src/vs/editor/browser/controller/coreCommands.ts +++ b/src/vs/editor/browser/controller/coreCommands.ts @@ -316,6 +316,9 @@ export namespace CoreNavigationCommands { const result = this._getColumnSelectResult(cursors.context, cursors.getPrimaryCursor(), cursors.getColumnSelectData(), args); cursors.setStates(args.source, CursorChangeReason.Explicit, result.viewStates.map((viewState) => CursorState.fromViewState(viewState))); cursors.setColumnSelectData({ + isReal: true, + fromViewLineNumber: result.fromLineNumber, + fromViewVisualColumn: result.fromVisualColumn, toViewLineNumber: result.toLineNumber, toViewVisualColumn: result.toVisualColumn }); @@ -338,15 +341,15 @@ export namespace CoreNavigationCommands { // validate `args` const validatedPosition = context.model.validatePosition(args.position); + const validatedViewPosition = context.validateViewPosition(new Position(args.viewPosition.lineNumber, args.viewPosition.column), validatedPosition); - let validatedViewPosition: Position; - if (args.viewPosition) { - validatedViewPosition = context.validateViewPosition(new Position(args.viewPosition.lineNumber, args.viewPosition.column), validatedPosition); - } else { - validatedViewPosition = context.convertModelPositionToViewPosition(validatedPosition); + let fromViewLineNumber = prevColumnSelectData.fromViewLineNumber; + let fromViewVisualColumn = prevColumnSelectData.fromViewVisualColumn; + if (!prevColumnSelectData.isReal && args.setAnchorIfNotSet) { + fromViewLineNumber = validatedViewPosition.lineNumber; + fromViewVisualColumn = args.mouseColumn - 1; } - - return ColumnSelection.columnSelect(context.config, context.viewModel, primary.viewState.selection, validatedViewPosition.lineNumber, args.mouseColumn - 1); + return ColumnSelection.columnSelect(context.config, context.viewModel, fromViewLineNumber, fromViewVisualColumn, validatedViewPosition.lineNumber, args.mouseColumn - 1); } }); @@ -365,7 +368,7 @@ export namespace CoreNavigationCommands { } protected _getColumnSelectResult(context: CursorContext, primary: CursorState, prevColumnSelectData: IColumnSelectData, args: any): IColumnSelectResult { - return ColumnSelection.columnSelectLeft(context.config, context.viewModel, primary.viewState, prevColumnSelectData.toViewLineNumber, prevColumnSelectData.toViewVisualColumn); + return ColumnSelection.columnSelectLeft(context.config, context.viewModel, prevColumnSelectData); } }); @@ -384,7 +387,7 @@ export namespace CoreNavigationCommands { } protected _getColumnSelectResult(context: CursorContext, primary: CursorState, prevColumnSelectData: IColumnSelectData, args: any): IColumnSelectResult { - return ColumnSelection.columnSelectRight(context.config, context.viewModel, primary.viewState, prevColumnSelectData.toViewLineNumber, prevColumnSelectData.toViewVisualColumn); + return ColumnSelection.columnSelectRight(context.config, context.viewModel, prevColumnSelectData); } }); @@ -398,7 +401,7 @@ export namespace CoreNavigationCommands { } protected _getColumnSelectResult(context: CursorContext, primary: CursorState, prevColumnSelectData: IColumnSelectData, args: any): IColumnSelectResult { - return ColumnSelection.columnSelectUp(context.config, context.viewModel, primary.viewState, this._isPaged, prevColumnSelectData.toViewLineNumber, prevColumnSelectData.toViewVisualColumn); + return ColumnSelection.columnSelectUp(context.config, context.viewModel, prevColumnSelectData, this._isPaged); } } @@ -436,7 +439,7 @@ export namespace CoreNavigationCommands { } protected _getColumnSelectResult(context: CursorContext, primary: CursorState, prevColumnSelectData: IColumnSelectData, args: any): IColumnSelectResult { - return ColumnSelection.columnSelectDown(context.config, context.viewModel, primary.viewState, this._isPaged, prevColumnSelectData.toViewLineNumber, prevColumnSelectData.toViewVisualColumn); + return ColumnSelection.columnSelectDown(context.config, context.viewModel, prevColumnSelectData, this._isPaged); } } diff --git a/src/vs/editor/browser/view/viewController.ts b/src/vs/editor/browser/view/viewController.ts index 7d4b2ceaed..e6847ff319 100644 --- a/src/vs/editor/browser/view/viewController.ts +++ b/src/vs/editor/browser/view/viewController.ts @@ -133,7 +133,7 @@ export class ViewController { public dispatchMouse(data: IMouseDispatchData): void { if (data.middleButton) { if (data.inSelectionMode) { - this._columnSelect(data.position, data.mouseColumn); + this._columnSelect(data.position, data.mouseColumn, true); } else { this.moveTo(data.position); } @@ -182,7 +182,7 @@ export class ViewController { if (this._hasMulticursorModifier(data)) { if (!this._hasNonMulticursorModifier(data)) { if (data.shiftKey) { - this._columnSelect(data.position, data.mouseColumn); + this._columnSelect(data.position, data.mouseColumn, false); } else { // Do multi-cursor operations only when purely alt is pressed if (data.inSelectionMode) { @@ -195,7 +195,7 @@ export class ViewController { } else { if (data.inSelectionMode) { if (data.altKey) { - this._columnSelect(data.position, data.mouseColumn); + this._columnSelect(data.position, data.mouseColumn, true); } else { this._moveToSelect(data.position); } @@ -222,12 +222,13 @@ export class ViewController { this._execMouseCommand(CoreNavigationCommands.MoveToSelect, this._usualArgs(viewPosition)); } - private _columnSelect(viewPosition: Position, mouseColumn: number): void { + private _columnSelect(viewPosition: Position, mouseColumn: number, setAnchorIfNotSet: boolean): void { viewPosition = this._validateViewColumn(viewPosition); this._execMouseCommand(CoreNavigationCommands.ColumnSelect, { position: this._convertViewToModelPosition(viewPosition), viewPosition: viewPosition, - mouseColumn: mouseColumn + mouseColumn: mouseColumn, + setAnchorIfNotSet: setAnchorIfNotSet }); } diff --git a/src/vs/editor/browser/viewParts/contentWidgets/contentWidgets.ts b/src/vs/editor/browser/viewParts/contentWidgets/contentWidgets.ts index 15f524f373..cdcae0da5f 100644 --- a/src/vs/editor/browser/viewParts/contentWidgets/contentWidgets.ts +++ b/src/vs/editor/browser/viewParts/contentWidgets/contentWidgets.ts @@ -324,11 +324,6 @@ class Widget { const aboveLeft0 = topLeft.left - ctx.scrollLeft; const belowLeft0 = bottomLeft.left - ctx.scrollLeft; - if (aboveLeft0 < 0 || aboveLeft0 > this._contentWidth) { - // Don't render if position is scrolled outside viewport - return null; - } - let aboveTop = topLeft.top - height; let belowTop = bottomLeft.top + this._lineHeight; let aboveLeft = aboveLeft0 + this._contentLeft; diff --git a/src/vs/editor/browser/viewParts/lines/viewLine.ts b/src/vs/editor/browser/viewParts/lines/viewLine.ts index f297bf66af..8cda798ee4 100644 --- a/src/vs/editor/browser/viewParts/lines/viewLine.ts +++ b/src/vs/editor/browser/viewParts/lines/viewLine.ts @@ -12,7 +12,7 @@ import { IStringBuilder } from 'vs/editor/common/core/stringBuilder'; import { IConfiguration } from 'vs/editor/common/editorCommon'; import { HorizontalRange } from 'vs/editor/common/view/renderingContext'; import { LineDecoration } from 'vs/editor/common/viewLayout/lineDecorations'; -import { CharacterMapping, ForeignElementType, RenderLineInput, renderViewLine } from 'vs/editor/common/viewLayout/viewLineRenderer'; +import { CharacterMapping, ForeignElementType, RenderLineInput, renderViewLine, LineRange } from 'vs/editor/common/viewLayout/viewLineRenderer'; import { ViewportData } from 'vs/editor/common/viewLayout/viewLinesViewportData'; import { InlineDecorationType } from 'vs/editor/common/viewModel/viewModel'; import { HIGH_CONTRAST, ThemeType } from 'vs/platform/theme/common/themeService'; @@ -69,7 +69,7 @@ export class DomReadingContext { export class ViewLineOptions { public readonly themeType: ThemeType; - public readonly renderWhitespace: 'none' | 'boundary' | 'all'; + public readonly renderWhitespace: 'none' | 'boundary' | 'selection' | 'all'; public readonly renderControlCharacters: boolean; public readonly spaceWidth: number; public readonly useMonospaceOptimizations: boolean; @@ -152,7 +152,7 @@ export class ViewLine implements IVisibleLine { this._options = newOptions; } public onSelectionChanged(): boolean { - if (alwaysRenderInlineSelection || this._options.themeType === HIGH_CONTRAST) { + if (alwaysRenderInlineSelection || this._options.themeType === HIGH_CONTRAST || this._options.renderWhitespace === 'selection') { this._isMaybeInvalid = true; return true; } @@ -171,7 +171,9 @@ export class ViewLine implements IVisibleLine { const options = this._options; const actualInlineDecorations = LineDecoration.filter(lineData.inlineDecorations, lineNumber, lineData.minColumn, lineData.maxColumn); - if (alwaysRenderInlineSelection || options.themeType === HIGH_CONTRAST) { + // Only send selection information when needed for rendering whitespace + let selectionsOnLine: LineRange[] | null = null; + if (alwaysRenderInlineSelection || options.themeType === HIGH_CONTRAST || this._options.renderWhitespace === 'selection') { const selections = viewportData.selections; for (const selection of selections) { @@ -184,7 +186,15 @@ export class ViewLine implements IVisibleLine { const endColumn = (selection.endLineNumber === lineNumber ? selection.endColumn : lineData.maxColumn); if (startColumn < endColumn) { - actualInlineDecorations.push(new LineDecoration(startColumn, endColumn, 'inline-selected-text', InlineDecorationType.Regular)); + if (this._options.renderWhitespace !== 'selection') { + actualInlineDecorations.push(new LineDecoration(startColumn, endColumn, 'inline-selected-text', InlineDecorationType.Regular)); + } else { + if (!selectionsOnLine) { + selectionsOnLine = []; + } + + selectionsOnLine.push(new LineRange(startColumn - 1, endColumn - 1)); + } } } } @@ -204,7 +214,8 @@ export class ViewLine implements IVisibleLine { options.stopRenderingLineAfter, options.renderWhitespace, options.renderControlCharacters, - options.fontLigatures + options.fontLigatures, + selectionsOnLine ); if (this._renderedViewLine && this._renderedViewLine.input.equals(renderLineInput)) { diff --git a/src/vs/editor/browser/widget/diffEditorWidget.ts b/src/vs/editor/browser/widget/diffEditorWidget.ts index 6511d9265c..6b69e41884 100644 --- a/src/vs/editor/browser/widget/diffEditorWidget.ts +++ b/src/vs/editor/browser/widget/diffEditorWidget.ts @@ -2047,7 +2047,8 @@ class InlineViewZonesComputer extends ViewZonesComputer { config.viewInfo.stopRenderingLineAfter, config.viewInfo.renderWhitespace, config.viewInfo.renderControlCharacters, - config.viewInfo.fontLigatures + config.viewInfo.fontLigatures, + null // Send no selections, original line cannot be selected ), sb); sb.appendASCIIString(''); diff --git a/src/vs/editor/browser/widget/diffReview.ts b/src/vs/editor/browser/widget/diffReview.ts index f0455247f0..33c88f82cf 100644 --- a/src/vs/editor/browser/widget/diffReview.ts +++ b/src/vs/editor/browser/widget/diffReview.ts @@ -780,7 +780,8 @@ export class DiffReview extends Disposable { config.viewInfo.stopRenderingLineAfter, config.viewInfo.renderWhitespace, config.viewInfo.renderControlCharacters, - config.viewInfo.fontLigatures + config.viewInfo.fontLigatures, + null )); return r.html; diff --git a/src/vs/editor/common/config/commonEditorConfig.ts b/src/vs/editor/common/config/commonEditorConfig.ts index e15d8b414a..56cfb4e99e 100644 --- a/src/vs/editor/common/config/commonEditorConfig.ts +++ b/src/vs/editor/common/config/commonEditorConfig.ts @@ -901,10 +901,11 @@ const editorConfiguration: IConfigurationNode = { }, 'editor.renderWhitespace': { 'type': 'string', - 'enum': ['none', 'boundary', 'all'], + 'enum': ['none', 'boundary', 'selection', 'all'], 'enumDescriptions': [ '', nls.localize('renderWhiteSpace.boundary', "Render whitespace characters except for single spaces between words."), + nls.localize('renderWhitespace.selection', "Render whitespace characters only on selected text."), '' ], default: EDITOR_DEFAULTS.viewInfo.renderWhitespace, diff --git a/src/vs/editor/common/config/editorOptions.ts b/src/vs/editor/common/config/editorOptions.ts index d8e5adfd87..61d55a5bc3 100644 --- a/src/vs/editor/common/config/editorOptions.ts +++ b/src/vs/editor/common/config/editorOptions.ts @@ -664,7 +664,7 @@ export interface IEditorOptions { * Enable rendering of whitespace. * Defaults to none. */ - renderWhitespace?: 'none' | 'boundary' | 'all'; + renderWhitespace?: 'none' | 'boundary' | 'selection' | 'all'; /** * Enable rendering of control characters. * Defaults to false. @@ -1005,7 +1005,7 @@ export interface InternalEditorViewOptions { readonly scrollBeyondLastColumn: number; readonly smoothScrolling: boolean; readonly stopRenderingLineAfter: number; - readonly renderWhitespace: 'none' | 'boundary' | 'all'; + readonly renderWhitespace: 'none' | 'boundary' | 'selection' | 'all'; readonly renderControlCharacters: boolean; readonly fontLigatures: boolean; readonly renderIndentGuides: boolean; @@ -2023,7 +2023,7 @@ export class EditorOptionsValidator { } else if (renderWhitespace === false) { renderWhitespace = 'none'; } - renderWhitespace = _stringSet<'none' | 'boundary' | 'all'>(renderWhitespace, defaults.renderWhitespace, ['none', 'boundary', 'all']); + renderWhitespace = _stringSet<'none' | 'boundary' | 'selection' | 'all'>(renderWhitespace, defaults.renderWhitespace, ['none', 'boundary', 'selection', 'all']); } let renderLineHighlight = opts.renderLineHighlight; diff --git a/src/vs/editor/common/controller/cursor.ts b/src/vs/editor/common/controller/cursor.ts index 5dcf817a31..7f2a5863e9 100644 --- a/src/vs/editor/common/controller/cursor.ts +++ b/src/vs/editor/common/controller/cursor.ts @@ -314,9 +314,14 @@ export class Cursor extends viewEvents.ViewEventEmitter implements ICursors { } const primaryCursor = this._cursors.getPrimaryCursor(); const primaryPos = primaryCursor.viewState.position; + const viewLineNumber = primaryPos.lineNumber; + const viewVisualColumn = CursorColumns.visibleColumnFromColumn2(this.context.config, this.context.viewModel, primaryPos); return { - toViewLineNumber: primaryPos.lineNumber, - toViewVisualColumn: CursorColumns.visibleColumnFromColumn2(this.context.config, this.context.viewModel, primaryPos) + isReal: false, + fromViewLineNumber: viewLineNumber, + fromViewVisualColumn: viewVisualColumn, + toViewLineNumber: viewLineNumber, + toViewVisualColumn: viewVisualColumn, }; } diff --git a/src/vs/editor/common/controller/cursorColumnSelection.ts b/src/vs/editor/common/controller/cursorColumnSelection.ts index ecc4c9a88b..9da95f7473 100644 --- a/src/vs/editor/common/controller/cursorColumnSelection.ts +++ b/src/vs/editor/common/controller/cursorColumnSelection.ts @@ -3,21 +3,22 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { CursorColumns, CursorConfiguration, ICursorSimpleModel, SingleCursorState } from 'vs/editor/common/controller/cursorCommon'; +import { CursorColumns, CursorConfiguration, ICursorSimpleModel, SingleCursorState, IColumnSelectData } from 'vs/editor/common/controller/cursorCommon'; import { Position } from 'vs/editor/common/core/position'; import { Range } from 'vs/editor/common/core/range'; -import { Selection } from 'vs/editor/common/core/selection'; export interface IColumnSelectResult { viewStates: SingleCursorState[]; reversed: boolean; + fromLineNumber: number; + fromVisualColumn: number; toLineNumber: number; toVisualColumn: number; } export class ColumnSelection { - private static _columnSelect(config: CursorConfiguration, model: ICursorSimpleModel, fromLineNumber: number, fromVisibleColumn: number, toLineNumber: number, toVisibleColumn: number): IColumnSelectResult { + public static columnSelect(config: CursorConfiguration, model: ICursorSimpleModel, fromLineNumber: number, fromVisibleColumn: number, toLineNumber: number, toVisibleColumn: number): IColumnSelectResult { let lineCount = Math.abs(toLineNumber - fromLineNumber) + 1; let reversed = (fromLineNumber > toLineNumber); let isRTL = (fromVisibleColumn > toVisibleColumn); @@ -61,64 +62,65 @@ export class ColumnSelection { )); } + if (result.length === 0) { + // We are after all the lines, so add cursor at the end of each line + for (let i = 0; i < lineCount; i++) { + const lineNumber = fromLineNumber + (reversed ? -i : i); + const maxColumn = model.getLineMaxColumn(lineNumber); + + result.push(new SingleCursorState( + new Range(lineNumber, maxColumn, lineNumber, maxColumn), 0, + new Position(lineNumber, maxColumn), 0 + )); + } + } + return { viewStates: result, reversed: reversed, + fromLineNumber: fromLineNumber, + fromVisualColumn: fromVisibleColumn, toLineNumber: toLineNumber, toVisualColumn: toVisibleColumn }; } - public static columnSelect(config: CursorConfiguration, model: ICursorSimpleModel, fromViewSelection: Selection, toViewLineNumber: number, toViewVisualColumn: number): IColumnSelectResult { - const fromViewPosition = new Position(fromViewSelection.selectionStartLineNumber, fromViewSelection.selectionStartColumn); - const fromViewVisibleColumn = CursorColumns.visibleColumnFromColumn2(config, model, fromViewPosition); - return ColumnSelection._columnSelect(config, model, fromViewPosition.lineNumber, fromViewVisibleColumn, toViewLineNumber, toViewVisualColumn); - } - - public static columnSelectLeft(config: CursorConfiguration, model: ICursorSimpleModel, cursor: SingleCursorState, toViewLineNumber: number, toViewVisualColumn: number): IColumnSelectResult { + public static columnSelectLeft(config: CursorConfiguration, model: ICursorSimpleModel, prevColumnSelectData: IColumnSelectData): IColumnSelectResult { + let toViewVisualColumn = prevColumnSelectData.toViewVisualColumn; if (toViewVisualColumn > 1) { toViewVisualColumn--; } - return this.columnSelect(config, model, cursor.selection, toViewLineNumber, toViewVisualColumn); + return ColumnSelection.columnSelect(config, model, prevColumnSelectData.fromViewLineNumber, prevColumnSelectData.fromViewVisualColumn, prevColumnSelectData.toViewLineNumber, toViewVisualColumn); } - public static columnSelectRight(config: CursorConfiguration, model: ICursorSimpleModel, cursor: SingleCursorState, toViewLineNumber: number, toViewVisualColumn: number): IColumnSelectResult { + public static columnSelectRight(config: CursorConfiguration, model: ICursorSimpleModel, prevColumnSelectData: IColumnSelectData): IColumnSelectResult { let maxVisualViewColumn = 0; - let minViewLineNumber = Math.min(cursor.position.lineNumber, toViewLineNumber); - let maxViewLineNumber = Math.max(cursor.position.lineNumber, toViewLineNumber); + const minViewLineNumber = Math.min(prevColumnSelectData.fromViewLineNumber, prevColumnSelectData.toViewLineNumber); + const maxViewLineNumber = Math.max(prevColumnSelectData.fromViewLineNumber, prevColumnSelectData.toViewLineNumber); for (let lineNumber = minViewLineNumber; lineNumber <= maxViewLineNumber; lineNumber++) { - let lineMaxViewColumn = model.getLineMaxColumn(lineNumber); - let lineMaxVisualViewColumn = CursorColumns.visibleColumnFromColumn2(config, model, new Position(lineNumber, lineMaxViewColumn)); + const lineMaxViewColumn = model.getLineMaxColumn(lineNumber); + const lineMaxVisualViewColumn = CursorColumns.visibleColumnFromColumn2(config, model, new Position(lineNumber, lineMaxViewColumn)); maxVisualViewColumn = Math.max(maxVisualViewColumn, lineMaxVisualViewColumn); } + let toViewVisualColumn = prevColumnSelectData.toViewVisualColumn; if (toViewVisualColumn < maxVisualViewColumn) { toViewVisualColumn++; } - return this.columnSelect(config, model, cursor.selection, toViewLineNumber, toViewVisualColumn); + return this.columnSelect(config, model, prevColumnSelectData.fromViewLineNumber, prevColumnSelectData.fromViewVisualColumn, prevColumnSelectData.toViewLineNumber, toViewVisualColumn); } - public static columnSelectUp(config: CursorConfiguration, model: ICursorSimpleModel, cursor: SingleCursorState, isPaged: boolean, toViewLineNumber: number, toViewVisualColumn: number): IColumnSelectResult { - let linesCount = isPaged ? config.pageSize : 1; - - toViewLineNumber -= linesCount; - if (toViewLineNumber < 1) { - toViewLineNumber = 1; - } - - return this.columnSelect(config, model, cursor.selection, toViewLineNumber, toViewVisualColumn); + public static columnSelectUp(config: CursorConfiguration, model: ICursorSimpleModel, prevColumnSelectData: IColumnSelectData, isPaged: boolean): IColumnSelectResult { + const linesCount = isPaged ? config.pageSize : 1; + const toViewLineNumber = Math.max(1, prevColumnSelectData.toViewLineNumber - linesCount); + return this.columnSelect(config, model, prevColumnSelectData.fromViewLineNumber, prevColumnSelectData.fromViewVisualColumn, toViewLineNumber, prevColumnSelectData.toViewVisualColumn); } - public static columnSelectDown(config: CursorConfiguration, model: ICursorSimpleModel, cursor: SingleCursorState, isPaged: boolean, toViewLineNumber: number, toViewVisualColumn: number): IColumnSelectResult { - let linesCount = isPaged ? config.pageSize : 1; - - toViewLineNumber += linesCount; - if (toViewLineNumber > model.getLineCount()) { - toViewLineNumber = model.getLineCount(); - } - - return this.columnSelect(config, model, cursor.selection, toViewLineNumber, toViewVisualColumn); + public static columnSelectDown(config: CursorConfiguration, model: ICursorSimpleModel, prevColumnSelectData: IColumnSelectData, isPaged: boolean): IColumnSelectResult { + const linesCount = isPaged ? config.pageSize : 1; + const toViewLineNumber = Math.min(model.getLineCount(), prevColumnSelectData.toViewLineNumber + linesCount); + return this.columnSelect(config, model, prevColumnSelectData.fromViewLineNumber, prevColumnSelectData.fromViewVisualColumn, toViewLineNumber, prevColumnSelectData.toViewVisualColumn); } } diff --git a/src/vs/editor/common/controller/cursorCommon.ts b/src/vs/editor/common/controller/cursorCommon.ts index e86cbc91c1..d54fcd6d40 100644 --- a/src/vs/editor/common/controller/cursorCommon.ts +++ b/src/vs/editor/common/controller/cursorCommon.ts @@ -21,6 +21,9 @@ import { VerticalRevealType } from 'vs/editor/common/view/viewEvents'; import { IViewModel } from 'vs/editor/common/viewModel/viewModel'; export interface IColumnSelectData { + isReal: boolean; + fromViewLineNumber: number; + fromViewVisualColumn: number; toViewLineNumber: number; toViewVisualColumn: number; } diff --git a/src/vs/editor/common/viewLayout/viewLineRenderer.ts b/src/vs/editor/common/viewLayout/viewLineRenderer.ts index ed15ef4c46..801192a143 100644 --- a/src/vs/editor/common/viewLayout/viewLineRenderer.ts +++ b/src/vs/editor/common/viewLayout/viewLineRenderer.ts @@ -13,7 +13,8 @@ import { InlineDecorationType } from 'vs/editor/common/viewModel/viewModel'; export const enum RenderWhitespace { None = 0, Boundary = 1, - All = 2 + Selection = 2, + All = 3 } class LinePart { @@ -31,6 +32,28 @@ class LinePart { } } +export class LineRange { + /** + * Zero-based offset on which the range starts, inclusive. + */ + public readonly startOffset: number; + + /** + * Zero-based offset on which the range ends, inclusive. + */ + public readonly endOffset: number; + + constructor(startIndex: number, endIndex: number) { + this.startOffset = startIndex; + this.endOffset = endIndex; + } + + public equals(otherLineRange: LineRange) { + return this.startOffset === otherLineRange.startOffset + && this.endOffset === otherLineRange.endOffset; + } +} + export class RenderLineInput { public readonly useMonospaceOptimizations: boolean; @@ -49,6 +72,12 @@ export class RenderLineInput { public readonly renderControlCharacters: boolean; public readonly fontLigatures: boolean; + /** + * Defined only when renderWhitespace is 'selection'. Selections are non-overlapping, + * and ordered by position within the line. + */ + public readonly selectionsOnLine: LineRange[] | null; + constructor( useMonospaceOptimizations: boolean, canUseHalfwidthRightwardsArrow: boolean, @@ -62,9 +91,10 @@ export class RenderLineInput { tabSize: number, spaceWidth: number, stopRenderingLineAfter: number, - renderWhitespace: 'none' | 'boundary' | 'all', + renderWhitespace: 'none' | 'boundary' | 'selection' | 'all', renderControlCharacters: boolean, - fontLigatures: boolean + fontLigatures: boolean, + selectionsOnLine: LineRange[] | null ) { this.useMonospaceOptimizations = useMonospaceOptimizations; this.canUseHalfwidthRightwardsArrow = canUseHalfwidthRightwardsArrow; @@ -83,10 +113,35 @@ export class RenderLineInput { ? RenderWhitespace.All : renderWhitespace === 'boundary' ? RenderWhitespace.Boundary - : RenderWhitespace.None + : renderWhitespace === 'selection' + ? RenderWhitespace.Selection + : RenderWhitespace.None ); this.renderControlCharacters = renderControlCharacters; this.fontLigatures = fontLigatures; + this.selectionsOnLine = selectionsOnLine && selectionsOnLine.sort((a, b) => a.startOffset < b.startOffset ? -1 : 1); + } + + private sameSelection(otherSelections: LineRange[] | null): boolean { + if (this.selectionsOnLine === null) { + return otherSelections === null; + } + + if (otherSelections === null) { + return false; + } + + if (otherSelections.length !== this.selectionsOnLine.length) { + return false; + } + + for (let i = 0; i < this.selectionsOnLine.length; i++) { + if (!this.selectionsOnLine[i].equals(otherSelections[i])) { + return false; + } + } + + return true; } public equals(other: RenderLineInput): boolean { @@ -106,6 +161,7 @@ export class RenderLineInput { && this.fontLigatures === other.fontLigatures && LineDecoration.equalsArr(this.lineDecorations, other.lineDecorations) && this.lineTokens.equals(other.lineTokens) + && this.sameSelection(other.selectionsOnLine) ); } } @@ -338,8 +394,8 @@ function resolveRenderLineInput(input: RenderLineInput): ResolvedRenderLineInput } let tokens = transformAndRemoveOverflowing(input.lineTokens, input.fauxIndentLength, len); - if (input.renderWhitespace === RenderWhitespace.All || input.renderWhitespace === RenderWhitespace.Boundary) { - tokens = _applyRenderWhitespace(lineContent, len, input.continuesWithWrappedLine, tokens, input.fauxIndentLength, input.tabSize, useMonospaceOptimizations, input.renderWhitespace === RenderWhitespace.Boundary); + if (input.renderWhitespace === RenderWhitespace.All || input.renderWhitespace === RenderWhitespace.Boundary || (input.renderWhitespace === RenderWhitespace.Selection && !!input.selectionsOnLine)) { + tokens = _applyRenderWhitespace(lineContent, len, input.continuesWithWrappedLine, tokens, input.fauxIndentLength, input.tabSize, useMonospaceOptimizations, input.selectionsOnLine, input.renderWhitespace === RenderWhitespace.Boundary); } let containsForeignElements = ForeignElementType.None; if (input.lineDecorations.length > 0) { @@ -481,7 +537,7 @@ function splitLargeTokens(lineContent: string, tokens: LinePart[], onlyAtSpaces: * Moreover, a token is created for every visual indent because on some fonts the glyphs used for rendering whitespace (→ or ·) do not have the same width as  . * The rendering phase will generate `style="width:..."` for these tokens. */ -function _applyRenderWhitespace(lineContent: string, len: number, continuesWithWrappedLine: boolean, tokens: LinePart[], fauxIndentLength: number, tabSize: number, useMonospaceOptimizations: boolean, onlyBoundary: boolean): LinePart[] { +function _applyRenderWhitespace(lineContent: string, len: number, continuesWithWrappedLine: boolean, tokens: LinePart[], fauxIndentLength: number, tabSize: number, useMonospaceOptimizations: boolean, selections: LineRange[] | null, onlyBoundary: boolean): LinePart[] { let result: LinePart[] = [], resultLen = 0; let tokenIndex = 0; @@ -511,11 +567,17 @@ function _applyRenderWhitespace(lineContent: string, len: number, continuesWithW } } tmpIndent = tmpIndent % tabSize; - let wasInWhitespace = false; + let currentSelectionIndex = 0; + let currentSelection = selections && selections[currentSelectionIndex]; for (let charIndex = fauxIndentLength; charIndex < len; charIndex++) { const chCode = lineContent.charCodeAt(charIndex); + if (currentSelection && charIndex >= currentSelection.endOffset) { + currentSelectionIndex++; + currentSelection = selections && selections[currentSelectionIndex]; + } + let isInWhitespace: boolean; if (charIndex < firstNonWhitespaceIndex || charIndex > lastNonWhitespaceIndex) { // in leading or trailing whitespace @@ -540,6 +602,11 @@ function _applyRenderWhitespace(lineContent: string, len: number, continuesWithW isInWhitespace = false; } + // If rendering whitespace on selection, check that the charIndex falls within a selection + if (isInWhitespace && selections) { + isInWhitespace = !!currentSelection && currentSelection.startOffset <= charIndex && currentSelection.endOffset > charIndex; + } + if (wasInWhitespace) { // was in whitespace token if (!isInWhitespace || (!useMonospaceOptimizations && tmpIndent >= tabSize)) { diff --git a/src/vs/editor/contrib/find/findWidget.ts b/src/vs/editor/contrib/find/findWidget.ts index 279d107d9b..1ae110b1dc 100644 --- a/src/vs/editor/contrib/find/findWidget.ts +++ b/src/vs/editor/contrib/find/findWidget.ts @@ -31,6 +31,7 @@ import { contrastBorder, editorFindMatch, editorFindMatchBorder, editorFindMatch import { ITheme, IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService'; import { ContextScopedFindInput, ContextScopedHistoryInputBox } from 'vs/platform/browser/contextScopedHistoryWidget'; import { AccessibilitySupport } from 'vs/platform/accessibility/common/accessibility'; +import { alert as alertFn } from 'vs/base/browser/ui/aria/aria'; export interface IFindController { replace(): void; @@ -372,13 +373,26 @@ export class FindWidget extends Widget implements IOverlayWidget, IHorizontalSas } else { label = NLS_NO_RESULTS; } + this._matchesCount.appendChild(document.createTextNode(label)); + alertFn(this._getAriaLabel(label, this._state.currentMatch, this._state.searchString), true); MAX_MATCHES_COUNT_WIDTH = Math.max(MAX_MATCHES_COUNT_WIDTH, this._matchesCount.clientWidth); } // ----- actions + private _getAriaLabel(label: string, currentMatch: Range | null, searchString: string): string { + if (label === NLS_NO_RESULTS) { + return searchString === '' + ? nls.localize('ariaSearchNoResultEmpty', "{0} found", label) + : nls.localize('ariaSearchNoResult', "{0} found for {1}", label, searchString); + } + return currentMatch + ? nls.localize('ariaSearchNoResultWithLineNum', "{0} found for {1} at {2}", label, searchString, currentMatch.startLineNumber + ':' + currentMatch.startColumn) + : nls.localize('ariaSearchNoResultWithLineNumNoCurrentMatch', "{0} found for {1}", label, searchString); + } + /** * If 'selection find' is ON we should not disable the button (its function is to cancel 'selection find'). * If 'selection find' is OFF we enable the button only if there is a selection. diff --git a/src/vs/editor/contrib/linesOperations/linesOperations.ts b/src/vs/editor/contrib/linesOperations/linesOperations.ts index 507565b8de..43f6cd1834 100644 --- a/src/vs/editor/contrib/linesOperations/linesOperations.ts +++ b/src/vs/editor/contrib/linesOperations/linesOperations.ts @@ -581,7 +581,7 @@ export class DeleteAllLeftAction extends AbstractDeleteAllToBoundaryAction { return new Range(selection.startLineNumber, 1, selection.startLineNumber, selection.startColumn); } } else { - return selection; + return new Range(selection.startLineNumber, 1, selection.endLineNumber, selection.endColumn); } }); diff --git a/src/vs/editor/contrib/linesOperations/test/linesOperations.test.ts b/src/vs/editor/contrib/linesOperations/test/linesOperations.test.ts index 5e82412597..036e51d296 100644 --- a/src/vs/editor/contrib/linesOperations/test/linesOperations.test.ts +++ b/src/vs/editor/contrib/linesOperations/test/linesOperations.test.ts @@ -268,7 +268,7 @@ suite('Editor Contrib - Line Operations', () => { editor.setSelections([new Selection(2, 2, 2, 2), new Selection(2, 4, 2, 5)]); deleteAllLeftAction.run(null!, editor); - assert.equal(model.getLineContent(2), 'ord', '002'); + assert.equal(model.getLineContent(2), 'd', '002'); editor.setSelections([new Selection(3, 2, 3, 5), new Selection(3, 7, 3, 7)]); deleteAllLeftAction.run(null!, editor); @@ -276,11 +276,11 @@ suite('Editor Contrib - Line Operations', () => { editor.setSelections([new Selection(4, 3, 4, 3), new Selection(4, 5, 5, 4)]); deleteAllLeftAction.run(null!, editor); - assert.equal(model.getLineContent(4), 'lljour', '004'); + assert.equal(model.getLineContent(4), 'jour', '004'); editor.setSelections([new Selection(5, 3, 6, 3), new Selection(6, 5, 7, 5), new Selection(7, 7, 7, 7)]); deleteAllLeftAction.run(null!, editor); - assert.equal(model.getLineContent(5), 'horlworld', '005'); + assert.equal(model.getLineContent(5), 'world', '005'); }); }); diff --git a/src/vs/editor/contrib/links/links.ts b/src/vs/editor/contrib/links/links.ts index 803614d59a..8e246b1254 100644 --- a/src/vs/editor/contrib/links/links.ts +++ b/src/vs/editor/contrib/links/links.ts @@ -25,79 +25,43 @@ import { IOpenerService } from 'vs/platform/opener/common/opener'; import { editorActiveLinkForeground } from 'vs/platform/theme/common/colorRegistry'; import { registerThemingParticipant } from 'vs/platform/theme/common/themeService'; -const HOVER_MESSAGE_GENERAL_META = new MarkdownString().appendText( - platform.isMacintosh - ? nls.localize('links.navigate.mac', "Follow link (cmd + click)") - : nls.localize('links.navigate', "Follow link (ctrl + click)") -); +function getHoverMessage(link: Link, useMetaKey: boolean): MarkdownString { + const executeCmd = link.url && /^command:/i.test(link.url.toString()); -const HOVER_MESSAGE_COMMAND_META = new MarkdownString().appendText( - platform.isMacintosh - ? nls.localize('links.command.mac', "Execute command (cmd + click)") - : nls.localize('links.command', "Execute command (ctrl + click)") -); + const label = link.tooltip + ? link.tooltip + : executeCmd + ? nls.localize('links.navigate.executeCmd', 'Execute command') + : nls.localize('links.navigate.follow', 'Follow link'); -const HOVER_MESSAGE_GENERAL_ALT = new MarkdownString().appendText( - platform.isMacintosh - ? nls.localize('links.navigate.al.mac', "Follow link (option + click)") - : nls.localize('links.navigate.al', "Follow link (alt + click)") -); + const kb = useMetaKey + ? platform.isMacintosh + ? nls.localize('links.navigate.kb.meta.mac', "cmd + click") + : nls.localize('links.navigate.kb.meta', "ctrl + click") + : platform.isMacintosh + ? nls.localize('links.navigate.kb.alt.mac', "option + click") + : nls.localize('links.navigate.kb.alt', "alt + click"); -const HOVER_MESSAGE_COMMAND_ALT = new MarkdownString().appendText( - platform.isMacintosh - ? nls.localize('links.command.al.mac', "Execute command (option + click)") - : nls.localize('links.command.al', "Execute command (alt + click)") -); + if (link.url) { + const hoverMessage = new MarkdownString().appendMarkdown(`[${label}](${link.url.toString()}) (${kb})`); + hoverMessage.isTrusted = true; + return hoverMessage; + } else { + return new MarkdownString().appendText(`${label} (${kb})`); + } +} const decoration = { - meta: ModelDecorationOptions.register({ + general: ModelDecorationOptions.register({ stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges, collapseOnReplaceEdit: true, - inlineClassName: 'detected-link', - hoverMessage: HOVER_MESSAGE_GENERAL_META + inlineClassName: 'detected-link' }), - metaActive: ModelDecorationOptions.register({ + active: ModelDecorationOptions.register({ stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges, collapseOnReplaceEdit: true, - inlineClassName: 'detected-link-active', - hoverMessage: HOVER_MESSAGE_GENERAL_META - }), - alt: ModelDecorationOptions.register({ - stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges, - collapseOnReplaceEdit: true, - inlineClassName: 'detected-link', - hoverMessage: HOVER_MESSAGE_GENERAL_ALT - }), - altActive: ModelDecorationOptions.register({ - stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges, - collapseOnReplaceEdit: true, - inlineClassName: 'detected-link-active', - hoverMessage: HOVER_MESSAGE_GENERAL_ALT - }), - altCommand: ModelDecorationOptions.register({ - stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges, - collapseOnReplaceEdit: true, - inlineClassName: 'detected-link', - hoverMessage: HOVER_MESSAGE_COMMAND_ALT - }), - altCommandActive: ModelDecorationOptions.register({ - stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges, - collapseOnReplaceEdit: true, - inlineClassName: 'detected-link-active', - hoverMessage: HOVER_MESSAGE_COMMAND_ALT - }), - metaCommand: ModelDecorationOptions.register({ - stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges, - collapseOnReplaceEdit: true, - inlineClassName: 'detected-link', - hoverMessage: HOVER_MESSAGE_COMMAND_META - }), - metaCommandActive: ModelDecorationOptions.register({ - stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges, - collapseOnReplaceEdit: true, - inlineClassName: 'detected-link-active', - hoverMessage: HOVER_MESSAGE_COMMAND_META - }), + inlineClassName: 'detected-link-active' + }) }; @@ -111,38 +75,11 @@ class LinkOccurrence { } private static _getOptions(link: Link, useMetaKey: boolean, isActive: boolean): ModelDecorationOptions { - const options = { ...this._getBaseOptions(link, useMetaKey, isActive) }; - if (typeof link.tooltip === 'string') { - const message = new MarkdownString().appendText( - platform.isMacintosh - ? useMetaKey - ? nls.localize('links.custom.mac', "{0} (cmd + click)", link.tooltip) - : nls.localize('links.custom.mac.al', "{0} (option + click)", link.tooltip) - : useMetaKey - ? nls.localize('links.custom', "{0} (ctrl + click)", link.tooltip) - : nls.localize('links.custom.al', "{0} (alt + click)", link.tooltip) - ); - options.hoverMessage = message; - } + const options = { ... (isActive ? decoration.active : decoration.general) }; + options.hoverMessage = getHoverMessage(link, useMetaKey); return options; } - private static _getBaseOptions(link: Link, useMetaKey: boolean, isActive: boolean): ModelDecorationOptions { - if (link.url && /^command:/i.test(link.url.toString())) { - if (useMetaKey) { - return (isActive ? decoration.metaCommandActive : decoration.metaCommand); - } else { - return (isActive ? decoration.altCommandActive : decoration.altCommand); - } - } else { - if (useMetaKey) { - return (isActive ? decoration.metaActive : decoration.meta); - } else { - return (isActive ? decoration.altActive : decoration.alt); - } - } - } - public decorationId: string; public link: Link; diff --git a/src/vs/editor/standalone/browser/colorizer.ts b/src/vs/editor/standalone/browser/colorizer.ts index c84b65a42b..e0ff2588ff 100644 --- a/src/vs/editor/standalone/browser/colorizer.ts +++ b/src/vs/editor/standalone/browser/colorizer.ts @@ -128,7 +128,8 @@ export class Colorizer { -1, 'none', false, - false + false, + null )); return renderResult.html; } @@ -195,7 +196,8 @@ function _fakeColorize(lines: string[], tabSize: number): string { -1, 'none', false, - false + false, + null )); html = html.concat(renderResult.html); @@ -231,7 +233,8 @@ function _actualColorize(lines: string[], tabSize: number, tokenizationSupport: -1, 'none', false, - false + false, + null )); html = html.concat(renderResult.html); diff --git a/src/vs/editor/test/browser/controller/cursor.test.ts b/src/vs/editor/test/browser/controller/cursor.test.ts index 2f3a0b8e4f..8fc1cfcbe3 100644 --- a/src/vs/editor/test/browser/controller/cursor.test.ts +++ b/src/vs/editor/test/browser/controller/cursor.test.ts @@ -710,7 +710,8 @@ suite('Editor Controller - Cursor', () => { CoreNavigationCommands.ColumnSelect.runCoreEditorCommand(cursor, { position: new Position(4, 4), viewPosition: new Position(4, 4), - mouseColumn: 15 + mouseColumn: 15, + setAnchorIfNotSet: false }); let expectedSelections = [ @@ -745,7 +746,8 @@ suite('Editor Controller - Cursor', () => { CoreNavigationCommands.ColumnSelect.runCoreEditorCommand(cursor, { position: new Position(4, 1), viewPosition: new Position(4, 1), - mouseColumn: 1 + mouseColumn: 1, + setAnchorIfNotSet: false }); assertCursor(cursor, [ @@ -784,7 +786,8 @@ suite('Editor Controller - Cursor', () => { CoreNavigationCommands.ColumnSelect.runCoreEditorCommand(cursor, { position: new Position(1, 1), viewPosition: new Position(1, 1), - mouseColumn: 1 + mouseColumn: 1, + setAnchorIfNotSet: false }); assertCursor(cursor, [ new Selection(10, 10, 10, 1), @@ -802,7 +805,8 @@ suite('Editor Controller - Cursor', () => { CoreNavigationCommands.ColumnSelect.runCoreEditorCommand(cursor, { position: new Position(1, 1), viewPosition: new Position(1, 1), - mouseColumn: 1 + mouseColumn: 1, + setAnchorIfNotSet: false }); assertCursor(cursor, [ new Selection(10, 10, 10, 1), diff --git a/src/vs/editor/test/common/viewLayout/viewLineRenderer.test.ts b/src/vs/editor/test/common/viewLayout/viewLineRenderer.test.ts index 0db35f795c..6e3fbb979e 100644 --- a/src/vs/editor/test/common/viewLayout/viewLineRenderer.test.ts +++ b/src/vs/editor/test/common/viewLayout/viewLineRenderer.test.ts @@ -9,7 +9,7 @@ import * as strings from 'vs/base/common/strings'; import { IViewLineTokens } from 'vs/editor/common/core/lineTokens'; import { MetadataConsts } from 'vs/editor/common/modes'; import { LineDecoration } from 'vs/editor/common/viewLayout/lineDecorations'; -import { CharacterMapping, RenderLineInput, renderViewLine2 as renderViewLine } from 'vs/editor/common/viewLayout/viewLineRenderer'; +import { CharacterMapping, RenderLineInput, renderViewLine2 as renderViewLine, LineRange } from 'vs/editor/common/viewLayout/viewLineRenderer'; import { InlineDecorationType } from 'vs/editor/common/viewModel/viewModel'; import { ViewLineToken, ViewLineTokens } from 'vs/editor/test/common/core/viewLineToken'; @@ -41,7 +41,8 @@ suite('viewLineRenderer.renderLine', () => { -1, 'none', false, - false + false, + null )); assert.equal(_actual.html, '' + expected + ''); @@ -90,7 +91,8 @@ suite('viewLineRenderer.renderLine', () => { -1, 'none', false, - false + false, + null )); assert.equal(_actual.html, '' + expected + ''); @@ -142,7 +144,8 @@ suite('viewLineRenderer.renderLine', () => { 6, 'boundary', false, - false + false, + null )); let expectedOutput = [ @@ -233,7 +236,8 @@ suite('viewLineRenderer.renderLine', () => { -1, 'boundary', false, - false + false, + null )); assert.equal(_actual.html, '' + expectedOutput + ''); @@ -295,7 +299,8 @@ suite('viewLineRenderer.renderLine', () => { -1, 'none', false, - false + false, + null )); assert.equal(_actual.html, '' + expectedOutput + ''); @@ -357,7 +362,8 @@ suite('viewLineRenderer.renderLine', () => { -1, 'none', false, - false + false, + null )); assert.equal(_actual.html, '' + expectedOutput + ''); @@ -396,7 +402,8 @@ suite('viewLineRenderer.renderLine', () => { -1, 'none', false, - false + false, + null )); assert.equal(_actual.html, '' + expectedOutput + ''); @@ -426,7 +433,8 @@ suite('viewLineRenderer.renderLine', () => { -1, 'none', false, - false + false, + null )); assert.equal(actual.html, '' + expectedOutput.join('') + '', message); } @@ -526,7 +534,8 @@ suite('viewLineRenderer.renderLine', () => { -1, 'none', false, - true + true, + null )); assert.equal(actual.html, '' + expectedOutput.join('') + '', message); } @@ -564,7 +573,8 @@ suite('viewLineRenderer.renderLine', () => { -1, 'none', false, - false + false, + null )); let expectedOutput = [ 'að ®·ð ®·ð ®·ð ®·ð ®·ð ®·ð ®·ð ®·ð ®·ð ®·ð ®·ð ®·ð ®·ð ®·ð ®·ð ®·ð ®·ð ®·ð ®·ð ®·ð ®·ð ®·ð ®·ð ®·ð ®·ð ®·ð ®·ð ®·ð ®·ð ®·ð ®·ð ®·ð ®·ð ®·ð ®·ð ®·ð ®·ð ®·ð ®·ð ®·ð ®·ð ®·ð ®·ð ®·ð ®·ð ®·ð ®·ð ®·ð ®·ð ®·ð ®·ð ®·ð ®·ð ®·ð ®·ð ®·ð ®·ð ®·ð ®·ð ®·ð ®·ð ®·ð ®·ð ®·ð ®·ð ®·ð ®·ð ®·ð ®·ð ®·ð ®·ð ®·ð ®·ð ®·ð ®·ð ®·ð ®·ð ®·ð ®·ð ®·ð ®·ð ®·ð ®·ð ®·ð ®·ð ®·ð ®·ð ®·ð ®·ð ®·ð ®·ð ®·ð ®·ð ®·ð ®·ð ®·ð ®·ð ®·ð ®·ð ®·ð ®·ð ®·ð ®·ð ®·ð ®·ð ®·ð ®·ð ®·ð ®·ð ®·ð ®·ð ®·ð ®·ð ®·ð ®·ð ®·ð ®·ð ®·ð ®·ð ®·', @@ -593,7 +603,8 @@ suite('viewLineRenderer.renderLine', () => { -1, 'none', false, - false + false, + null )); assert.equal(actual.html, '' + expectedOutput.join('') + ''); assert.equal(actual.containsRTL, true); @@ -639,7 +650,8 @@ suite('viewLineRenderer.renderLine', () => { -1, 'none', false, - false + false, + null )); assert.equal(_actual.html, '' + expectedOutput + ''); @@ -704,7 +716,7 @@ suite('viewLineRenderer.renderLine', () => { suite('viewLineRenderer.renderLine 2', () => { - function testCreateLineParts(fontIsMonospace: boolean, lineContent: string, tokens: ViewLineToken[], fauxIndentLength: number, renderWhitespace: 'none' | 'boundary' | 'all', expected: string): void { + function testCreateLineParts(fontIsMonospace: boolean, lineContent: string, tokens: ViewLineToken[], fauxIndentLength: number, renderWhitespace: 'none' | 'boundary' | 'selection' | 'all', selections: LineRange[] | null, expected: string): void { let actual = renderViewLine(new RenderLineInput( fontIsMonospace, true, @@ -720,7 +732,8 @@ suite('viewLineRenderer.renderLine 2', () => { -1, renderWhitespace, false, - false + false, + selections )); assert.deepEqual(actual.html, expected); @@ -745,7 +758,8 @@ suite('viewLineRenderer.renderLine 2', () => { -1, 'none', false, - false + false, + null )); let expected = [ @@ -784,7 +798,8 @@ suite('viewLineRenderer.renderLine 2', () => { -1, 'none', false, - false + false, + null )); let expected = [ @@ -811,6 +826,7 @@ suite('viewLineRenderer.renderLine 2', () => { ], 0, 'none', + null, [ '', 'Hello\u00a0world!', @@ -828,6 +844,7 @@ suite('viewLineRenderer.renderLine 2', () => { ], 0, 'none', + null, [ '', 'Hello\u00a0', @@ -847,6 +864,7 @@ suite('viewLineRenderer.renderLine 2', () => { ], 0, 'boundary', + null, [ '', '\u00b7\u00b7\u00b7\u00b7', @@ -868,6 +886,7 @@ suite('viewLineRenderer.renderLine 2', () => { ], 0, 'boundary', + null, [ '', '\u00b7\u00b7\u00b7\u00b7', @@ -891,6 +910,7 @@ suite('viewLineRenderer.renderLine 2', () => { ], 0, 'boundary', + null, [ '', '\u2192\u00a0\u00a0\u00a0', @@ -913,6 +933,7 @@ suite('viewLineRenderer.renderLine 2', () => { ], 0, 'boundary', + null, [ '', '\u00b7\u00b7\u2192\u00a0', @@ -940,6 +961,7 @@ suite('viewLineRenderer.renderLine 2', () => { ], 2, 'boundary', + null, [ '', '\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0', @@ -966,6 +988,7 @@ suite('viewLineRenderer.renderLine 2', () => { ], 2, 'boundary', + null, [ '', '\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0', @@ -989,6 +1012,7 @@ suite('viewLineRenderer.renderLine 2', () => { ], 0, 'boundary', + null, [ '', 'it', @@ -1014,6 +1038,7 @@ suite('viewLineRenderer.renderLine 2', () => { ], 0, 'all', + null, [ '', '\u00b7', @@ -1027,6 +1052,147 @@ suite('viewLineRenderer.renderLine 2', () => { ); }); + test('createLineParts render whitespace for selection with no selections', () => { + testCreateLineParts( + false, + ' Hello world!\t', + [ + createPart(4, 0), + createPart(6, 1), + createPart(14, 2) + ], + 0, + 'selection', + null, + [ + '', + '\u00a0Hel', + 'lo', + '\u00a0world!\u00a0\u00a0\u00a0', + '', + ].join('') + ); + }); + + test('createLineParts render whitespace for selection with whole line selection', () => { + testCreateLineParts( + false, + ' Hello world!\t', + [ + createPart(4, 0), + createPart(6, 1), + createPart(14, 2) + ], + 0, + 'selection', + [new LineRange(0, 14)], + [ + '', + '\u00b7', + 'Hel', + 'lo', + '\u00b7', + 'world!', + '\u2192\u00a0\u00a0', + '', + ].join('') + ); + }); + + test('createLineParts render whitespace for selection with selection spanning part of whitespace', () => { + testCreateLineParts( + false, + ' Hello world!\t', + [ + createPart(4, 0), + createPart(6, 1), + createPart(14, 2) + ], + 0, + 'selection', + [new LineRange(0, 5)], + [ + '', + '\u00b7', + 'Hel', + 'lo', + '\u00a0world!\u00a0\u00a0\u00a0', + '', + ].join('') + ); + }); + + + test('createLineParts render whitespace for selection with multiple selections', () => { + testCreateLineParts( + false, + ' Hello world!\t', + [ + createPart(4, 0), + createPart(6, 1), + createPart(14, 2) + ], + 0, + 'selection', + [new LineRange(0, 5), new LineRange(9, 14)], + [ + '', + '\u00b7', + 'Hel', + 'lo', + '\u00a0world!', + '\u2192\u00a0\u00a0', + '', + ].join('') + ); + }); + + + test('createLineParts render whitespace for selection with multiple, initially unsorted selections', () => { + testCreateLineParts( + false, + ' Hello world!\t', + [ + createPart(4, 0), + createPart(6, 1), + createPart(14, 2) + ], + 0, + 'selection', + [new LineRange(9, 14), new LineRange(0, 5)], + [ + '', + '\u00b7', + 'Hel', + 'lo', + '\u00a0world!', + '\u2192\u00a0\u00a0', + '', + ].join('') + ); + }); + + test('createLineParts render whitespace for selection with selections next to each other', () => { + testCreateLineParts( + false, + ' * S', + [ + createPart(4, 0) + ], + 0, + 'selection', + [new LineRange(0, 1), new LineRange(1, 2), new LineRange(2, 3)], + [ + '', + '\u00b7', + '*', + '\u00b7', + 'S', + '', + ].join('') + ); + }); + test('createLineParts can handle unsorted inline decorations', () => { let actual = renderViewLine(new RenderLineInput( false, @@ -1047,7 +1213,8 @@ suite('viewLineRenderer.renderLine 2', () => { -1, 'none', false, - false + false, + null )); // 01234567890 @@ -1087,7 +1254,8 @@ suite('viewLineRenderer.renderLine 2', () => { -1, 'all', false, - true + true, + null )); let expected = [ @@ -1119,7 +1287,8 @@ suite('viewLineRenderer.renderLine 2', () => { -1, 'all', false, - true + true, + null )); let expected = [ @@ -1152,7 +1321,8 @@ suite('viewLineRenderer.renderLine 2', () => { -1, 'all', false, - true + true, + null )); let expected = [ @@ -1181,7 +1351,8 @@ suite('viewLineRenderer.renderLine 2', () => { 10000, 'none', false, - false + false, + null )); let expected = [ @@ -1214,7 +1385,8 @@ suite('viewLineRenderer.renderLine 2', () => { 10000, 'none', false, - false + false, + null )); let expected = [ @@ -1246,7 +1418,8 @@ suite('viewLineRenderer.renderLine 2', () => { 10000, 'none', false, - false + false, + null )); let expected = [ @@ -1276,7 +1449,8 @@ suite('viewLineRenderer.renderLine 2', () => { 10000, 'none', false, - false + false, + null )); let expected = [ @@ -1305,7 +1479,8 @@ suite('viewLineRenderer.renderLine 2', () => { 10000, 'all', false, - false + false, + null )); let expected = [ @@ -1340,7 +1515,8 @@ suite('viewLineRenderer.renderLine 2', () => { 10000, 'none', false, - false + false, + null )); let expected = [ @@ -1369,7 +1545,8 @@ suite('viewLineRenderer.renderLine 2', () => { 10000, 'none', false, - false + false, + null )); let expected = [ @@ -1400,7 +1577,8 @@ suite('viewLineRenderer.renderLine 2', () => { 10000, 'none', false, - false + false, + null )); let expected = [ @@ -1430,7 +1608,8 @@ suite('viewLineRenderer.renderLine 2', () => { 10000, 'boundary', false, - false + false, + null )); let expected = [ @@ -1458,7 +1637,8 @@ suite('viewLineRenderer.renderLine 2', () => { 10000, 'none', false, - true + true, + null )); let expected = [ @@ -1490,7 +1670,8 @@ suite('viewLineRenderer.renderLine 2', () => { 10000, 'none', false, - true + true, + null )); let expected = [ @@ -1518,7 +1699,8 @@ suite('viewLineRenderer.renderLine 2', () => { -1, 'none', false, - false + false, + null )); return (partIndex: number, partLength: number, offset: number, expected: number) => { diff --git a/src/vs/monaco.d.ts b/src/vs/monaco.d.ts index 3022349074..0f5aec0e29 100644 --- a/src/vs/monaco.d.ts +++ b/src/vs/monaco.d.ts @@ -3031,7 +3031,7 @@ declare namespace monaco.editor { * Enable rendering of whitespace. * Defaults to none. */ - renderWhitespace?: 'none' | 'boundary' | 'all'; + renderWhitespace?: 'none' | 'boundary' | 'selection' | 'all'; /** * Enable rendering of control characters. * Defaults to false. @@ -3307,7 +3307,7 @@ declare namespace monaco.editor { readonly scrollBeyondLastColumn: number; readonly smoothScrolling: boolean; readonly stopRenderingLineAfter: number; - readonly renderWhitespace: 'none' | 'boundary' | 'all'; + readonly renderWhitespace: 'none' | 'boundary' | 'selection' | 'all'; readonly renderControlCharacters: boolean; readonly fontLigatures: boolean; readonly renderIndentGuides: boolean; diff --git a/src/vs/platform/debug/common/extensionHostDebugIpc.ts b/src/vs/platform/debug/common/extensionHostDebugIpc.ts index c15072a9c3..5183771e4c 100644 --- a/src/vs/platform/debug/common/extensionHostDebugIpc.ts +++ b/src/vs/platform/debug/common/extensionHostDebugIpc.ts @@ -7,6 +7,7 @@ import { IServerChannel, IChannel } from 'vs/base/parts/ipc/common/ipc'; import { IReloadSessionEvent, ICloseSessionEvent, IAttachSessionEvent, ILogToSessionEvent, ITerminateSessionEvent, IExtensionHostDebugService } from 'vs/platform/debug/common/extensionHostDebug'; import { Event, Emitter } from 'vs/base/common/event'; import { IRemoteConsoleLog } from 'vs/base/common/console'; +import { Disposable } from 'vs/base/common/lifecycle'; export class ExtensionHostDebugBroadcastChannel implements IServerChannel { @@ -51,11 +52,13 @@ export class ExtensionHostDebugBroadcastChannel implements IServerChan } } -export class ExtensionHostDebugChannelClient implements IExtensionHostDebugService { +export class ExtensionHostDebugChannelClient extends Disposable implements IExtensionHostDebugService { _serviceBrand: any; - constructor(private channel: IChannel) { } + constructor(private channel: IChannel) { + super(); + } reload(sessionId: string): void { this.channel.call('reload', [sessionId]); diff --git a/src/vs/platform/download/common/download.ts b/src/vs/platform/download/common/download.ts index c0a3e02362..61c457760e 100644 --- a/src/vs/platform/download/common/download.ts +++ b/src/vs/platform/download/common/download.ts @@ -13,6 +13,6 @@ export interface IDownloadService { _serviceBrand: any; - download(uri: URI, to?: string, cancellationToken?: CancellationToken): Promise; + download(uri: URI, to: URI, cancellationToken?: CancellationToken): Promise; } diff --git a/src/vs/platform/download/common/downloadIpc.ts b/src/vs/platform/download/common/downloadIpc.ts new file mode 100644 index 0000000000..d8223e2506 --- /dev/null +++ b/src/vs/platform/download/common/downloadIpc.ts @@ -0,0 +1,42 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { URI } from 'vs/base/common/uri'; +import { IChannel, IServerChannel } from 'vs/base/parts/ipc/common/ipc'; +import { Event } from 'vs/base/common/event'; +import { IDownloadService } from 'vs/platform/download/common/download'; +import { IURITransformer } from 'vs/base/common/uriIpc'; + +export class DownloadServiceChannel implements IServerChannel { + + constructor(private readonly service: IDownloadService) { } + + listen(_: unknown, event: string, arg?: any): Event { + throw new Error('Invalid listen'); + } + + call(context: any, command: string, args?: any): Promise { + switch (command) { + case 'download': return this.service.download(URI.revive(args[0]), URI.revive(args[1])); + } + throw new Error('Invalid call'); + } +} + +export class DownloadServiceChannelClient implements IDownloadService { + + _serviceBrand: any; + + constructor(private channel: IChannel, private getUriTransformer: () => IURITransformer | null) { } + + async download(from: URI, to: URI): Promise { + const uriTransfomer = this.getUriTransformer(); + if (uriTransfomer) { + from = uriTransfomer.transformOutgoingURI(from); + to = uriTransfomer.transformOutgoingURI(to); + } + await this.channel.call('download', [from, to]); + } +} \ No newline at end of file diff --git a/src/vs/platform/download/node/downloadService.ts b/src/vs/platform/download/common/downloadService.ts similarity index 53% rename from src/vs/platform/download/node/downloadService.ts rename to src/vs/platform/download/common/downloadService.ts index 09e378e9dc..528c5f2a96 100644 --- a/src/vs/platform/download/node/downloadService.ts +++ b/src/vs/platform/download/common/downloadService.ts @@ -5,14 +5,10 @@ import { IDownloadService } from 'vs/platform/download/common/download'; import { URI } from 'vs/base/common/uri'; -import { Schemas } from 'vs/base/common/network'; -import { copy } from 'vs/base/node/pfs'; import { IRequestService, asText } from 'vs/platform/request/common/request'; import { CancellationToken } from 'vs/base/common/cancellation'; -import { join } from 'vs/base/common/path'; -import { tmpdir } from 'os'; -import { generateUuid } from 'vs/base/common/uuid'; import { IFileService } from 'vs/platform/files/common/files'; +import { Schemas } from 'vs/base/common/network'; export class DownloadService implements IDownloadService { @@ -23,18 +19,18 @@ export class DownloadService implements IDownloadService { @IFileService private readonly fileService: IFileService ) { } - download(uri: URI, target: string = join(tmpdir(), generateUuid()), cancellationToken: CancellationToken = CancellationToken.None): Promise { - if (uri.scheme === Schemas.file) { - return copy(uri.fsPath, target).then(() => target); + async download(resource: URI, target: URI, cancellationToken: CancellationToken = CancellationToken.None): Promise { + if (resource.scheme === Schemas.file) { + await this.fileService.copy(resource, target); + return; + } + const options = { type: 'GET', url: resource.toString() }; + const context = await this.requestService.request(options, cancellationToken); + if (context.res.statusCode === 200) { + await this.fileService.writeFile(target, context.stream); + } else { + const message = await asText(context); + return Promise.reject(new Error(`Expected 200, got back ${context.res.statusCode} instead.\n\n${message}`)); } - const options = { type: 'GET', url: uri.toString() }; - return this.requestService.request(options, cancellationToken) - .then(context => { - if (context.res.statusCode === 200) { - return this.fileService.writeFile(URI.file(target), context.stream).then(() => target); - } - return asText(context) - .then(message => Promise.reject(new Error(`Expected 200, got back ${context.res.statusCode} instead.\n\n${message}`))); - }); } } diff --git a/src/vs/platform/download/node/downloadIpc.ts b/src/vs/platform/download/node/downloadIpc.ts deleted file mode 100644 index 38032607a7..0000000000 --- a/src/vs/platform/download/node/downloadIpc.ts +++ /dev/null @@ -1,76 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the Source EULA. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { URI } from 'vs/base/common/uri'; -import * as path from 'vs/base/common/path'; -import * as fs from 'fs'; -import { IChannel, IServerChannel } from 'vs/base/parts/ipc/common/ipc'; -import { Event, Emitter } from 'vs/base/common/event'; -import { IDownloadService } from 'vs/platform/download/common/download'; -import { mkdirp } from 'vs/base/node/pfs'; -import { IURITransformer } from 'vs/base/common/uriIpc'; -import { tmpdir } from 'os'; -import { generateUuid } from 'vs/base/common/uuid'; - -type UploadResponse = Buffer | string | undefined; - -function upload(uri: URI): Event { - const stream = new Emitter(); - const readstream = fs.createReadStream(uri.fsPath); - readstream.on('data', data => stream.fire(data)); - readstream.on('error', error => stream.fire(error.toString())); - readstream.on('close', () => stream.fire(undefined)); - return stream.event; -} - -export class DownloadServiceChannel implements IServerChannel { - - constructor() { } - - listen(_: unknown, event: string, arg?: any): Event { - switch (event) { - case 'upload': return Event.buffer(upload(URI.revive(arg))); - } - - throw new Error(`Event not found: ${event}`); - } - - call(_: unknown, command: string): Promise { - throw new Error(`Call not found: ${command}`); - } -} - -export class DownloadServiceChannelClient implements IDownloadService { - - _serviceBrand: any; - - constructor(private channel: IChannel, private getUriTransformer: () => IURITransformer) { } - - download(from: URI, to: string = path.join(tmpdir(), generateUuid())): Promise { - from = this.getUriTransformer().transformOutgoingURI(from); - const dirName = path.dirname(to); - let out: fs.WriteStream; - return new Promise((c, e) => { - return mkdirp(dirName) - .then(() => { - out = fs.createWriteStream(to); - out.once('close', () => c(to)); - out.once('error', e); - const uploadStream = this.channel.listen('upload', from); - const disposable = uploadStream(result => { - if (result === undefined) { - disposable.dispose(); - out.end(() => c(to)); - } else if (Buffer.isBuffer(result)) { - out.write(result); - } else if (typeof result === 'string') { - disposable.dispose(); - out.end(() => e(result)); - } - }); - }); - }); - } -} \ No newline at end of file diff --git a/src/vs/platform/extensionManagement/common/extensionGalleryService.ts b/src/vs/platform/extensionManagement/common/extensionGalleryService.ts index e48e1d8909..b90c6b9dd7 100644 --- a/src/vs/platform/extensionManagement/common/extensionGalleryService.ts +++ b/src/vs/platform/extensionManagement/common/extensionGalleryService.ts @@ -26,6 +26,7 @@ import { URI } from 'vs/base/common/uri'; import { joinPath } from 'vs/base/common/resources'; import { VSBuffer } from 'vs/base/common/buffer'; import { IProductService } from 'vs/platform/product/common/product'; +import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; interface IRawGalleryExtensionFile { assetType: string; @@ -388,11 +389,12 @@ export class ExtensionGalleryService implements IExtensionGalleryService { @IConfigurationService private configurationService: IConfigurationService, @IFileService private readonly fileService: IFileService, @IProductService private readonly productService: IProductService, + @IStorageService private readonly storageService: IStorageService, ) { const config = productService.extensionsGallery; this.extensionsGalleryUrl = config && config.serviceUrl; this.extensionsControlUrl = config && config.controlUrl; - this.commonHeadersPromise = resolveMarketplaceHeaders(productService.version, this.environmentService, this.fileService); + this.commonHeadersPromise = resolveMarketplaceHeaders(productService.version, this.environmentService, this.fileService, this.storageService); } private api(path = ''): string { @@ -944,7 +946,7 @@ export class ExtensionGalleryService implements IExtensionGalleryService { } } -export async function resolveMarketplaceHeaders(version: string, environmentService: IEnvironmentService, fileService: IFileService): Promise<{ [key: string]: string; }> { +export async function resolveMarketplaceHeaders(version: string, environmentService: IEnvironmentService, fileService: IFileService, storageService?: IStorageService): Promise<{ [key: string]: string; }> { const headers: IHeaders = { 'X-Market-Client-Id': `VSCode ${version}`, 'User-Agent': `VSCode ${version}` @@ -967,8 +969,20 @@ export async function resolveMarketplaceHeaders(version: string, environmentServ //noop } } + } + + if (storageService) { + uuid = storageService.get('marketplace.userid', StorageScope.GLOBAL) || null; + if (!uuid) { + uuid = generateUuid(); + storageService.store('marketplace.userid', uuid, StorageScope.GLOBAL); + } + } + + if (uuid) { headers['X-Market-User-Id'] = uuid; } + return headers; } \ No newline at end of file diff --git a/src/vs/platform/extensionManagement/node/extensionManagementService.ts b/src/vs/platform/extensionManagement/node/extensionManagementService.ts index bbebf60281..0c934fcfa3 100644 --- a/src/vs/platform/extensionManagement/node/extensionManagementService.ts +++ b/src/vs/platform/extensionManagement/node/extensionManagementService.ts @@ -261,7 +261,7 @@ export class ExtensionManagementService extends Disposable implements IExtension throw new Error('Download service is not available'); } const downloadedLocation = path.join(tmpdir(), generateUuid()); - return this.downloadService.download(vsix, downloadedLocation).then(() => URI.file(downloadedLocation)); + return this.downloadService.download(vsix, URI.file(downloadedLocation)).then(() => URI.file(downloadedLocation)); } private installFromZipPath(identifierWithVersion: ExtensionIdentifierWithVersion, zipPath: string, metadata: IGalleryMetadata | null, type: ExtensionType, operation: InstallOperation, token: CancellationToken): Promise { diff --git a/src/vs/platform/remote/common/remoteAgentConnection.ts b/src/vs/platform/remote/common/remoteAgentConnection.ts index 1e98f9510c..5ce67e163b 100644 --- a/src/vs/platform/remote/common/remoteAgentConnection.ts +++ b/src/vs/platform/remote/common/remoteAgentConnection.ts @@ -256,7 +256,7 @@ function sleep(seconds: number): Promise { }); } -export const enum PersistenConnectionEventType { +export const enum PersistentConnectionEventType { ConnectionLost, ReconnectionWait, ReconnectionRunning, @@ -264,22 +264,22 @@ export const enum PersistenConnectionEventType { ConnectionGain } export class ConnectionLostEvent { - public readonly type = PersistenConnectionEventType.ConnectionLost; + public readonly type = PersistentConnectionEventType.ConnectionLost; } export class ReconnectionWaitEvent { - public readonly type = PersistenConnectionEventType.ReconnectionWait; + public readonly type = PersistentConnectionEventType.ReconnectionWait; constructor( public readonly durationSeconds: number ) { } } export class ReconnectionRunningEvent { - public readonly type = PersistenConnectionEventType.ReconnectionRunning; + public readonly type = PersistentConnectionEventType.ReconnectionRunning; } export class ConnectionGainEvent { - public readonly type = PersistenConnectionEventType.ConnectionGain; + public readonly type = PersistentConnectionEventType.ConnectionGain; } export class ReconnectionPermanentFailureEvent { - public readonly type = PersistenConnectionEventType.ReconnectionPermanentFailure; + public readonly type = PersistentConnectionEventType.ReconnectionPermanentFailure; } export type PersistenConnectionEvent = ConnectionGainEvent | ConnectionLostEvent | ReconnectionWaitEvent | ReconnectionRunningEvent | ReconnectionPermanentFailureEvent; diff --git a/src/vs/vscode.d.ts b/src/vs/vscode.d.ts index ad063f43fd..73b2129590 100644 --- a/src/vs/vscode.d.ts +++ b/src/vs/vscode.d.ts @@ -3826,7 +3826,7 @@ declare module 'vscode' { /** * Provide selection ranges for the given positions. * - * Selection ranges should be computed individually and independend for each position. The editor will merge + * Selection ranges should be computed individually and independent for each position. The editor will merge * and deduplicate ranges but providers must return hierarchies of selection ranges so that a range * is [contained](#Range.contains) by its parent. * diff --git a/src/vs/workbench/api/browser/extensionHost.contribution.ts b/src/vs/workbench/api/browser/extensionHost.contribution.ts index 0fff101214..623b6d42af 100644 --- a/src/vs/workbench/api/browser/extensionHost.contribution.ts +++ b/src/vs/workbench/api/browser/extensionHost.contribution.ts @@ -48,6 +48,7 @@ import './mainThreadStorage'; import './mainThreadTelemetry'; import './mainThreadTerminalService'; import './mainThreadTreeViews'; +import './mainThreadDownloadService'; import './mainThreadUrls'; import './mainThreadWindow'; import './mainThreadWebview'; diff --git a/src/vs/workbench/api/browser/mainThreadDownloadService.ts b/src/vs/workbench/api/browser/mainThreadDownloadService.ts new file mode 100644 index 0000000000..a8a80b8dbd --- /dev/null +++ b/src/vs/workbench/api/browser/mainThreadDownloadService.ts @@ -0,0 +1,26 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Disposable } from 'vs/base/common/lifecycle'; +import { MainContext, IExtHostContext, MainThreadDownloadServiceShape } from 'vs/workbench/api/common/extHost.protocol'; +import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers'; +import { IDownloadService } from 'vs/platform/download/common/download'; +import { UriComponents, URI } from 'vs/base/common/uri'; + +@extHostNamedCustomer(MainContext.MainThreadDownloadService) +export class MainThreadDownloadService extends Disposable implements MainThreadDownloadServiceShape { + + constructor( + extHostContext: IExtHostContext, + @IDownloadService private readonly downloadService: IDownloadService + ) { + super(); + } + + $download(uri: UriComponents, to: UriComponents): Promise { + return this.downloadService.download(URI.revive(uri), URI.revive(to)); + } + +} \ No newline at end of file diff --git a/src/vs/workbench/api/browser/mainThreadTreeViews.ts b/src/vs/workbench/api/browser/mainThreadTreeViews.ts index 519bb46e75..16b119ee82 100644 --- a/src/vs/workbench/api/browser/mainThreadTreeViews.ts +++ b/src/vs/workbench/api/browser/mainThreadTreeViews.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { Disposable } from 'vs/base/common/lifecycle'; -import { ExtHostContext, MainThreadTreeViewsShape, ExtHostTreeViewsShape, MainContext, IExtHostContext } from '../common/extHost.protocol'; +import { ExtHostContext, MainThreadTreeViewsShape, ExtHostTreeViewsShape, MainContext, IExtHostContext } from 'vs/workbench/api/common/extHost.protocol'; import { ITreeViewDataProvider, ITreeItem, IViewsService, ITreeView, IViewsRegistry, ITreeViewDescriptor, IRevealOptions, Extensions } from 'vs/workbench/common/views'; import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers'; import { distinct } from 'vs/base/common/arrays'; diff --git a/src/vs/workbench/api/browser/mainThreadWebview.ts b/src/vs/workbench/api/browser/mainThreadWebview.ts index 7778a92da9..d181d029dd 100644 --- a/src/vs/workbench/api/browser/mainThreadWebview.ts +++ b/src/vs/workbench/api/browser/mainThreadWebview.ts @@ -22,6 +22,7 @@ import { IExtensionService } from 'vs/workbench/services/extensions/common/exten import { extHostNamedCustomer } from '../common/extHostCustomers'; import { IProductService } from 'vs/platform/product/common/product'; import { startsWith } from 'vs/base/common/strings'; +import { Webview } from 'vs/workbench/contrib/webview/common/webview'; interface OldMainThreadWebviewState { readonly viewType: string; @@ -41,9 +42,9 @@ export class MainThreadWebviews extends Disposable implements MainThreadWebviews private static revivalPool = 0; - private readonly _proxy: ExtHostWebviewsShape; - private readonly _webviews = new Map(); + private readonly _webviewEditorInputs = new Map(); + private readonly _webviews = new Map(); private readonly _revivers = new Map(); private _activeWebview: WebviewPanelHandle | undefined = undefined; @@ -103,7 +104,8 @@ export class MainThreadWebviews extends Disposable implements MainThreadWebviews }); this.hookupWebviewEventDelegate(handle, webview); - this._webviews.set(handle, webview); + this._webviewEditorInputs.set(handle, webview); + this._webviews.set(handle, webview.webview); /* __GDPR__ "webviews:createWebviewPanel" : { @@ -114,32 +116,32 @@ export class MainThreadWebviews extends Disposable implements MainThreadWebviews } public $disposeWebview(handle: WebviewPanelHandle): void { - const webview = this.getWebview(handle); + const webview = this.getWebviewEditorInput(handle); webview.dispose(); } public $setTitle(handle: WebviewPanelHandle, value: string): void { - const webview = this.getWebview(handle); + const webview = this.getWebviewEditorInput(handle); webview.setName(value); } public $setIconPath(handle: WebviewPanelHandle, value: { light: UriComponents, dark: UriComponents } | undefined): void { - const webview = this.getWebview(handle); + const webview = this.getWebviewEditorInput(handle); webview.iconPath = reviveWebviewIcon(value); } public $setHtml(handle: WebviewPanelHandle, value: string): void { const webview = this.getWebview(handle); - webview.webview.html = value; + webview.html = value; } public $setOptions(handle: WebviewPanelHandle, options: modes.IWebviewOptions): void { const webview = this.getWebview(handle); - webview.webview.contentOptions = reviveWebviewOptions(options as any /*todo@mat */); + webview.contentOptions = reviveWebviewOptions(options as any /*todo@mat */); } public $reveal(handle: WebviewPanelHandle, showOptions: WebviewPanelShowOptions): void { - const webview = this.getWebview(handle); + const webview = this.getWebviewEditorInput(handle); if (webview.isDisposed()) { return; } @@ -152,7 +154,7 @@ export class MainThreadWebviews extends Disposable implements MainThreadWebviews public async $postMessage(handle: WebviewPanelHandle, message: any): Promise { const webview = this.getWebview(handle); - webview.webview.sendMessage(message); + webview.sendMessage(message); return true; } @@ -173,8 +175,10 @@ export class MainThreadWebviews extends Disposable implements MainThreadWebviews } const handle = `revival-${MainThreadWebviews.revivalPool++}`; - this._webviews.set(handle, webviewEditorInput); + this._webviewEditorInputs.set(handle, webviewEditorInput); + this._webviews.set(handle, webviewEditorInput.webview); this.hookupWebviewEventDelegate(handle, webviewEditorInput); + let state = undefined; if (webviewEditorInput.webview.state) { try { @@ -229,11 +233,12 @@ export class MainThreadWebviews extends Disposable implements MainThreadWebviews input.webview.onMessage((message: any) => this._proxy.$onMessage(handle, message)); input.onDispose(() => { this._proxy.$onDidDisposeWebviewPanel(handle).finally(() => { + this._webviewEditorInputs.delete(handle); this._webviews.delete(handle); }); }); input.webview.onDidUpdateState((newState: any) => { - const webview = this.tryGetWebview(handle); + const webview = this.tryGetWebviewEditorInput(handle); if (!webview || webview.isDisposed()) { return; } @@ -245,8 +250,8 @@ export class MainThreadWebviews extends Disposable implements MainThreadWebviews const activeEditor = this._editorService.activeControl; let newActiveWebview: { input: WebviewEditorInput, handle: WebviewPanelHandle } | undefined = undefined; if (activeEditor && activeEditor.input instanceof WebviewEditorInput) { - for (const handle of map.keys(this._webviews)) { - const input = this._webviews.get(handle)!; + for (const handle of map.keys(this._webviewEditorInputs)) { + const input = this._webviewEditorInputs.get(handle)!; if (input.matches(activeEditor.input)) { newActiveWebview = { input, handle }; break; @@ -266,7 +271,7 @@ export class MainThreadWebviews extends Disposable implements MainThreadWebviews // Broadcast view state update for currently active if (typeof this._activeWebview !== 'undefined') { - const oldActiveWebview = this._webviews.get(this._activeWebview); + const oldActiveWebview = this._webviewEditorInputs.get(this._activeWebview); if (oldActiveWebview) { this._proxy.$onDidChangeWebviewPanelViewState(this._activeWebview, { active: false, @@ -290,7 +295,7 @@ export class MainThreadWebviews extends Disposable implements MainThreadWebviews } private onVisibleEditorsChanged(): void { - this._webviews.forEach((input, handle) => { + this._webviewEditorInputs.forEach((input, handle) => { for (const workbenchEditor of this._editorService.visibleControls) { if (workbenchEditor.input && workbenchEditor.input.matches(input)) { const editorPosition = editorGroupToViewColumn(this._editorGroupService, workbenchEditor.group!); @@ -312,7 +317,7 @@ export class MainThreadWebviews extends Disposable implements MainThreadWebviews return; } - const webview = this.getWebview(handle); + const webview = this.getWebviewEditorInput(handle); if (this.isSupportedLink(webview, link)) { this._openerService.open(link); } @@ -328,7 +333,19 @@ export class MainThreadWebviews extends Disposable implements MainThreadWebviews return !!webview.webview.contentOptions.enableCommandUris && link.scheme === 'command'; } - private getWebview(handle: WebviewPanelHandle): WebviewEditorInput { + private getWebviewEditorInput(handle: WebviewPanelHandle): WebviewEditorInput { + const webview = this.tryGetWebviewEditorInput(handle); + if (!webview) { + throw new Error('Unknown webview handle:' + handle); + } + return webview; + } + + private tryGetWebviewEditorInput(handle: WebviewPanelHandle): WebviewEditorInput | undefined { + return this._webviewEditorInputs.get(handle); + } + + private getWebview(handle: WebviewPanelHandle): Webview { const webview = this.tryGetWebview(handle); if (!webview) { throw new Error('Unknown webview handle:' + handle); @@ -336,7 +353,7 @@ export class MainThreadWebviews extends Disposable implements MainThreadWebviews return webview; } - private tryGetWebview(handle: WebviewPanelHandle): WebviewEditorInput | undefined { + private tryGetWebview(handle: WebviewPanelHandle): Webview | undefined { return this._webviews.get(handle); } diff --git a/src/vs/workbench/api/common/apiCommands.ts b/src/vs/workbench/api/common/apiCommands.ts index 2901c7e9ea..8ec65cecf2 100644 --- a/src/vs/workbench/api/common/apiCommands.ts +++ b/src/vs/workbench/api/common/apiCommands.ts @@ -12,7 +12,6 @@ import { EditorViewColumn } from 'vs/workbench/api/common/shared/editor'; import { EditorGroupLayout } from 'vs/workbench/services/editor/common/editorGroupsService'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { IOpenSettings, IURIToOpen, IWindowService } from 'vs/platform/windows/common/windows'; -import { IDownloadService } from 'vs/platform/download/common/download'; import { IWorkspacesService, hasWorkspaceFileExtension } from 'vs/platform/workspaces/common/workspaces'; import { IRecent } from 'vs/platform/history/common/history'; import { Schemas } from 'vs/base/common/network'; @@ -206,9 +205,4 @@ CommandsRegistry.registerCommand({ } }] } -}); - -CommandsRegistry.registerCommand('_workbench.downloadResource', function (accessor: ServicesAccessor, resource: URI) { - const downloadService = accessor.get(IDownloadService); - return downloadService.download(resource).then(location => URI.file(location)); -}); +}); \ No newline at end of file diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index fd158d3ea2..a7e96e337e 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -249,6 +249,10 @@ export interface MainThreadTreeViewsShape extends IDisposable { $setMessage(treeViewId: string, message: string | IMarkdownString): void; } +export interface MainThreadDownloadServiceShape extends IDisposable { + $download(uri: UriComponents, to: UriComponents): Promise; +} + export interface MainThreadErrorsShape extends IDisposable { $onUnexpectedError(err: any | SerializedError): void; } @@ -815,6 +819,9 @@ export interface ExtHostTreeViewsShape { $setVisible(treeViewId: string, visible: boolean): void; } +export interface ExtHostDownloadServiceShape { +} + export interface ExtHostWorkspaceShape { $initializeWorkspace(workspace: IWorkspaceData | null): void; $acceptWorkspaceData(workspace: IWorkspaceData | null): void; @@ -1312,6 +1319,7 @@ export const MainContext = { MainThreadEditorInsets: createMainId('MainThreadEditorInsets'), MainThreadErrors: createMainId('MainThreadErrors'), MainThreadTreeViews: createMainId('MainThreadTreeViews'), + MainThreadDownloadService: createMainId('MainThreadDownloadService'), MainThreadKeytar: createMainId('MainThreadKeytar'), MainThreadLanguageFeatures: createMainId('MainThreadLanguageFeatures'), MainThreadLanguages: createMainId('MainThreadLanguages'), @@ -1347,6 +1355,7 @@ export const ExtHostContext = { ExtHostDocumentSaveParticipant: createExtId('ExtHostDocumentSaveParticipant'), ExtHostEditors: createExtId('ExtHostEditors'), ExtHostTreeViews: createExtId('ExtHostTreeViews'), + ExtHostDownloadService: createExtId('ExtHostDownloadService'), ExtHostFileSystem: createExtId('ExtHostFileSystem'), ExtHostFileSystemEventService: createExtId('ExtHostFileSystemEventService'), ExtHostLanguageFeatures: createExtId('ExtHostLanguageFeatures'), diff --git a/src/vs/workbench/api/node/extHost.api.impl.ts b/src/vs/workbench/api/node/extHost.api.impl.ts index c35a51123d..19a05651e1 100644 --- a/src/vs/workbench/api/node/extHost.api.impl.ts +++ b/src/vs/workbench/api/node/extHost.api.impl.ts @@ -52,6 +52,7 @@ import { ExtHostTask } from 'vs/workbench/api/node/extHostTask'; import { ExtHostTerminalService } from 'vs/workbench/api/node/extHostTerminalService'; import { ExtHostEditors } from 'vs/workbench/api/common/extHostTextEditors'; import { ExtHostTreeViews } from 'vs/workbench/api/common/extHostTreeViews'; +import { ExtHostDownloadService } from 'vs/workbench/api/node/extHostDownloadService'; import * as typeConverters from 'vs/workbench/api/common/extHostTypeConverters'; import * as extHostTypes from 'vs/workbench/api/common/extHostTypes'; import { ExtHostUrls } from 'vs/workbench/api/common/extHostUrls'; @@ -117,6 +118,7 @@ export function createApiFactory( const extHostEditors = rpcProtocol.set(ExtHostContext.ExtHostEditors, new ExtHostEditors(rpcProtocol, extHostDocumentsAndEditors)); const extHostCommands = rpcProtocol.set(ExtHostContext.ExtHostCommands, new ExtHostCommands(rpcProtocol, extHostLogService)); const extHostTreeViews = rpcProtocol.set(ExtHostContext.ExtHostTreeViews, new ExtHostTreeViews(rpcProtocol.getProxy(MainContext.MainThreadTreeViews), extHostCommands, extHostLogService)); + rpcProtocol.set(ExtHostContext.ExtHostDownloadService, new ExtHostDownloadService(rpcProtocol.getProxy(MainContext.MainThreadDownloadService), extHostCommands)); rpcProtocol.set(ExtHostContext.ExtHostWorkspace, extHostWorkspace); rpcProtocol.set(ExtHostContext.ExtHostConfiguration, extHostConfiguration); const extHostEditorInsets = rpcProtocol.set(ExtHostContext.ExtHostEditorInsets, new ExtHostEditorInsets(rpcProtocol.getProxy(MainContext.MainThreadEditorInsets), extHostEditors, initData.environment)); diff --git a/src/vs/workbench/api/node/extHostDownloadService.ts b/src/vs/workbench/api/node/extHostDownloadService.ts new file mode 100644 index 0000000000..be81ee320f --- /dev/null +++ b/src/vs/workbench/api/node/extHostDownloadService.ts @@ -0,0 +1,28 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { join } from 'vs/base/common/path'; +import { tmpdir } from 'os'; +import { generateUuid } from 'vs/base/common/uuid'; +import { ExtHostCommands } from 'vs/workbench/api/common/extHostCommands'; +import { Disposable } from 'vs/base/common/lifecycle'; +import { MainThreadDownloadServiceShape } from 'vs/workbench/api/common/extHost.protocol'; +import { URI } from 'vs/base/common/uri'; + +export class ExtHostDownloadService extends Disposable { + + constructor( + proxy: MainThreadDownloadServiceShape, + commands: ExtHostCommands + ) { + super(); + commands.registerCommand(false, '_workbench.downloadResource', async (resource: URI): Promise => { + const location = URI.file(join(tmpdir(), generateUuid())); + await proxy.$download(resource, location); + return location; + }); + } + +} \ No newline at end of file diff --git a/src/vs/workbench/browser/layout.ts b/src/vs/workbench/browser/layout.ts index 835f16ba2f..b2c3218965 100644 --- a/src/vs/workbench/browser/layout.ts +++ b/src/vs/workbench/browser/layout.ts @@ -27,14 +27,13 @@ import { IWindowService, MenuBarVisibility, getTitleBarStyle } from 'vs/platform import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { IEditorService, IResourceEditor } from 'vs/workbench/services/editor/common/editorService'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; -import { Sizing, Direction, Grid } from 'vs/base/browser/ui/grid/grid'; +import { Sizing, Direction, Grid, SerializableGrid, ISerializableView, ISerializedGrid } from 'vs/base/browser/ui/grid/grid'; import { WorkbenchLegacyLayout } from 'vs/workbench/browser/legacyLayout'; import { IDimension } from 'vs/platform/layout/browser/layoutService'; import { Part } from 'vs/workbench/browser/part'; import { IStatusbarService } from 'vs/platform/statusbar/common/statusbar'; import { IActivityBarService } from 'vs/workbench/services/activityBar/browser/activityBarService'; import { IFileService } from 'vs/platform/files/common/files'; -import { IView } from 'vs/base/browser/ui/grid/gridview'; enum Settings { MENUBAR_VISIBLE = 'window.menuBarVisibility', @@ -57,6 +56,8 @@ enum Storage { ZEN_MODE_ENABLED = 'workbench.zenmode.active', CENTERED_LAYOUT_ENABLED = 'workbench.centerededitorlayout.active', + + GRID_LAYOUT = 'workbench.grid.layout' } export abstract class Layout extends Disposable implements IWorkbenchLayoutService { @@ -89,16 +90,16 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi private parts: Map = new Map(); - private workbenchGrid: Grid | WorkbenchLegacyLayout; + private workbenchGrid: SerializableGrid | WorkbenchLegacyLayout; private disposed: boolean; - private titleBarPartView: IView; - private activityBarPartView: IView; - private sideBarPartView: IView; - private panelPartView: IView; - private editorPartView: IView; - private statusBarPartView: IView; + private titleBarPartView: ISerializableView; + private activityBarPartView: ISerializableView; + private sideBarPartView: ISerializableView; + private panelPartView: ISerializableView; + private editorPartView: ISerializableView; + private statusBarPartView: ISerializableView; private environmentService: IWorkbenchEnvironmentService; private configurationService: IConfigurationService; @@ -144,8 +145,6 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi hidden: false, sizeBeforeMaximize: 0, position: Position.BOTTOM, - height: 350, - width: 350, panelToRestore: undefined as string | undefined }, @@ -706,7 +705,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi if (this.configurationService.getValue('workbench.useExperimentalGridLayout')) { - // Create view wrappers for all parts + // View references for all parts this.titleBarPartView = titleBar; this.sideBarPartView = sideBar; this.activityBarPartView = activityBar; @@ -714,9 +713,63 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi this.panelPartView = panelPart; this.statusBarPartView = statusBar; - this.workbenchGrid = new Grid(this.editorPartView, { proportionalLayout: false }); + let workbenchGrid: SerializableGrid | undefined; - this.container.prepend(this.workbenchGrid.element); + const savedGrid = this.storageService.get(Storage.GRID_LAYOUT, StorageScope.GLOBAL, undefined); + if (savedGrid) { + const parsedGrid: ISerializedGrid = JSON.parse(savedGrid); + + const fromJSON = (serializedPart: { type: Parts } | null) => { + if (serializedPart && serializedPart.type) { + switch (serializedPart.type) { + case Parts.ACTIVITYBAR_PART: + return this.activityBarPartView; + case Parts.TITLEBAR_PART: + return this.titleBarPartView; + case Parts.EDITOR_PART: + return this.editorPartView; + case Parts.PANEL_PART: + return this.panelPartView; + case Parts.SIDEBAR_PART: + return this.sideBarPartView; + case Parts.STATUSBAR_PART: + return this.statusBarPartView; + default: + return this.editorPartView; + } + } else { + return this.editorPartView; + } + }; + + try { + workbenchGrid = SerializableGrid.deserialize(parsedGrid, { fromJSON }, { proportionalLayout: false }); + + // Set some layout state + this.state.sideBar.position = Position.LEFT; + for (let view of workbenchGrid.getNeighborViews(this.sideBarPartView, Direction.Right)) { + if (view === this.activityBarPartView) { + this.state.sideBar.position = Position.RIGHT; + } + } + + this.state.panel.position = Position.BOTTOM; + for (let view of workbenchGrid.getNeighborViews(this.panelPartView, Direction.Left)) { + if (view === this.editorPartView) { + this.state.panel.position = Position.RIGHT; + } + } + } catch (err) { + console.error(err); + } + } + + if (!workbenchGrid) { + workbenchGrid = new SerializableGrid(this.editorPartView, { proportionalLayout: false }); + } + + this.container.prepend(workbenchGrid.element); + this.workbenchGrid = workbenchGrid; this._register((this.sideBarPartView as SidebarPart).onDidVisibilityChange((visible) => { this.setSideBarHidden(!visible, true); @@ -725,6 +778,17 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi this._register((this.panelPartView as PanelPart).onDidVisibilityChange((visible) => { this.setPanelHidden(!visible, true); })); + + this._register(this.lifecycleService.onBeforeShutdown(beforeShutdownEvent => { + beforeShutdownEvent.veto(new Promise((resolve) => { + const grid = this.workbenchGrid as SerializableGrid; + const serializedGrid = grid.serialize(); + + this.storageService.store(Storage.GRID_LAYOUT, JSON.stringify(serializedGrid), StorageScope.GLOBAL); + + resolve(); + })); + })); } else { this.workbenchGrid = instantiationService.createInstance( WorkbenchLegacyLayout, @@ -797,7 +861,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi } if (!panelInGrid) { - this.workbenchGrid.addView(this.panelPartView, this.getPanelDimension(this.state.panel.position) !== undefined ? this.getPanelDimension(this.state.panel.position) : Sizing.Split, this.editorPartView, this.state.panel.position === Position.BOTTOM ? Direction.Down : Direction.Right); + this.workbenchGrid.addView(this.panelPartView, Sizing.Split, this.editorPartView, this.state.panel.position === Position.BOTTOM ? Direction.Down : Direction.Right); panelInGrid = true; } @@ -852,10 +916,6 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi } } - private getPanelDimension(position: Position): number { - return position === Position.BOTTOM ? this.state.panel.height : this.state.panel.width; - } - isEditorLayoutCentered(): boolean { return this.state.editor.centered; } @@ -1148,12 +1208,9 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi setPanelPosition(position: Position): void { const panelPart = this.getPart(Parts.PANEL_PART); - const wasHidden = this.state.panel.hidden; if (this.state.panel.hidden) { this.setPanelHidden(false, true /* Skip Layout */); - } else { - this.savePanelDimension(); } const newPositionValue = (position === Position.BOTTOM) ? 'bottom' : 'right'; @@ -1179,10 +1236,6 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi // Layout if (this.workbenchGrid instanceof Grid) { - if (!wasHidden) { - this.savePanelDimension(); - } - this.workbenchGrid.removeView(this.panelPartView); this.layout(); } else { @@ -1192,18 +1245,6 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi this._onPanelPositionChange.fire(positionToString(this.state.panel.position)); } - private savePanelDimension(): void { - if (!(this.workbenchGrid instanceof Grid)) { - return; - } - - if (this.state.panel.position === Position.BOTTOM) { - this.state.panel.height = this.workbenchGrid.getViewSize(this.panelPartView).height; - } else { - this.state.panel.width = this.workbenchGrid.getViewSize(this.panelPartView).width; - } - } - dispose(): void { super.dispose(); diff --git a/src/vs/workbench/browser/web.simpleservices.ts b/src/vs/workbench/browser/web.simpleservices.ts index 24f2d839c6..c7fc53d235 100644 --- a/src/vs/workbench/browser/web.simpleservices.ts +++ b/src/vs/workbench/browser/web.simpleservices.ts @@ -8,9 +8,6 @@ import * as browser from 'vs/base/browser/browser'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { Event } from 'vs/base/common/event'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; -// tslint:disable-next-line: import-patterns no-standalone-editor -import { IDownloadService } from 'vs/platform/download/common/download'; -import { CancellationToken } from 'vs/base/common/cancellation'; import { IGalleryExtension, IExtensionIdentifier, IReportedExtension, IExtensionManagementService, ILocalExtension, IGalleryMetadata } from 'vs/platform/extensionManagement/common/extensionManagement'; import { IExtensionTipsService, ExtensionRecommendationReason, IExtensionRecommendation } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; import { ExtensionType, ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; @@ -33,7 +30,7 @@ import { IEditorService, IResourceEditor } from 'vs/workbench/services/editor/co import { pathsToEditors } from 'vs/workbench/common/editor'; import { IFileService } from 'vs/platform/files/common/files'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { ParsedArgs } from 'vs/platform/environment/common/environment'; +import { ParsedArgs, IEnvironmentService } from 'vs/platform/environment/common/environment'; import { IProcessEnvironment } from 'vs/base/common/platform'; import { toStoreData, restoreRecentlyOpened } from 'vs/platform/history/common/historyStorage'; import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService'; @@ -41,22 +38,6 @@ import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteA import { IExperimentService, IExperiment, ExperimentActionType, ExperimentState } from 'vs/workbench/contrib/experiments/common/experimentService'; import { ExtensionHostDebugChannelClient, ExtensionHostDebugBroadcastChannel } from 'vs/platform/debug/common/extensionHostDebugIpc'; -//#region Download - -export class SimpleDownloadService implements IDownloadService { - - _serviceBrand: any; - - download(uri: URI, to?: string, cancellationToken?: CancellationToken): Promise { - // @ts-ignore - return Promise.resolve(undefined); - } -} - -registerSingleton(IDownloadService, SimpleDownloadService, true); - -//#endregion - //#region Extension Tips export class SimpleExtensionTipsService implements IExtensionTipsService { @@ -583,7 +564,9 @@ registerSingleton(IWindowService, SimpleWindowService); export class SimpleExtensionHostDebugService extends ExtensionHostDebugChannelClient { constructor( - @IRemoteAgentService remoteAgentService: IRemoteAgentService + @IRemoteAgentService remoteAgentService: IRemoteAgentService, + //@IWindowService windowService: IWindowService, + @IEnvironmentService environmentService: IEnvironmentService ) { const connection = remoteAgentService.getConnection(); @@ -592,6 +575,19 @@ export class SimpleExtensionHostDebugService extends ExtensionHostDebugChannelCl } super(connection.getChannel(ExtensionHostDebugBroadcastChannel.ChannelName)); + + this._register(this.onReload(event => { + if (environmentService.isExtensionDevelopment && environmentService.debugExtensionHost.debugId === event.sessionId) { + //windowService.reloadWindow(); + window.location.reload(); + } + })); + this._register(this.onClose(event => { + if (environmentService.isExtensionDevelopment && environmentService.debugExtensionHost.debugId === event.sessionId) { + //this._windowService.closeWindow(); + window.close(); + } + })); } } registerSingleton(IExtensionHostDebugService, SimpleExtensionHostDebugService); diff --git a/src/vs/workbench/browser/workbench.contribution.ts b/src/vs/workbench/browser/workbench.contribution.ts index e63c52ea5d..b395ea6077 100644 --- a/src/vs/workbench/browser/workbench.contribution.ts +++ b/src/vs/workbench/browser/workbench.contribution.ts @@ -237,7 +237,7 @@ import { isMacintosh, isWindows, isLinux, isWeb } from 'vs/base/common/platform' 'workbench.useExperimentalGridLayout': { 'type': 'boolean', 'description': nls.localize('workbench.useExperimentalGridLayout', "Enables the grid layout for the workbench. This setting may enable additional layout options for workbench components."), - 'default': false, + 'default': true, 'scope': ConfigurationScope.APPLICATION } } diff --git a/src/vs/workbench/contrib/codeEditor/browser/toggleWordWrap.ts b/src/vs/workbench/contrib/codeEditor/browser/toggleWordWrap.ts index 154d9fc2b5..bc554177d2 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/toggleWordWrap.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/toggleWordWrap.ts @@ -18,6 +18,7 @@ import { MenuId, MenuRegistry } from 'vs/platform/actions/common/actions'; import { ContextKeyExpr, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { INotificationService } from 'vs/platform/notification/common/notification'; +import { DefaultSettingsEditorContribution } from 'vs/workbench/contrib/preferences/browser/preferencesEditor'; const transientWordWrapState = 'transientWordWrapState'; const isWordWrapMinifiedKey = 'isWordWrapMinified'; @@ -131,6 +132,10 @@ class ToggleWordWrapAction extends EditorAction { } public run(accessor: ServicesAccessor, editor: ICodeEditor): void { + if (editor.getContribution(DefaultSettingsEditorContribution.ID)) { + // in the settings editor... + return; + } if (!editor.hasModel()) { return; } @@ -201,6 +206,10 @@ class ToggleWordWrapController extends Disposable implements IEditorContribution })); const ensureWordWrapSettings = () => { + if (this.editor.getContribution(DefaultSettingsEditorContribution.ID)) { + // in the settings editor... + return; + } // Ensure correct word wrap settings const newModel = this.editor.getModel(); if (!newModel) { @@ -267,7 +276,7 @@ function canToggleWordWrap(uri: URI): boolean { if (!uri) { return false; } - return (uri.scheme !== 'output' && uri.scheme !== 'vscode'); + return (uri.scheme !== 'output'); } diff --git a/src/vs/workbench/contrib/comments/browser/commentThreadWidget.ts b/src/vs/workbench/contrib/comments/browser/commentThreadWidget.ts index a3c5582a75..8106e8fb3d 100644 --- a/src/vs/workbench/contrib/comments/browser/commentThreadWidget.ts +++ b/src/vs/workbench/contrib/comments/browser/commentThreadWidget.ts @@ -311,7 +311,6 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget let currentComment = commentThread.comments![i]; let oldCommentNode = this._commentElements.filter(commentNode => commentNode.comment.uniqueIdInThread === currentComment.uniqueIdInThread); if (oldCommentNode.length) { - oldCommentNode[0].update(currentComment); lastCommentElement = oldCommentNode[0].domNode; newCommentNodeList.unshift(oldCommentNode[0]); } else { diff --git a/src/vs/workbench/contrib/comments/browser/media/review.css b/src/vs/workbench/contrib/comments/browser/media/review.css index 959731f185..3a757378a6 100644 --- a/src/vs/workbench/contrib/comments/browser/media/review.css +++ b/src/vs/workbench/contrib/comments/browser/media/review.css @@ -399,7 +399,8 @@ } .monaco-editor .review-widget .action-item { - min-width: 16px; + min-width: 18px; + min-height: 18px; margin-left: 4px; } diff --git a/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts b/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts index 11ae3597c1..86ff29d000 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts @@ -28,7 +28,7 @@ import { IExtensionsWorkbenchService, IExtensionsViewlet, VIEWLET_ID, IExtension import { RatingsWidget, InstallCountWidget, RemoteBadgeWidget } from 'vs/workbench/contrib/extensions/browser/extensionsWidgets'; import { EditorOptions } from 'vs/workbench/common/editor'; import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar'; -import { CombinedInstallAction, UpdateAction, ExtensionEditorDropDownAction, ReloadAction, MaliciousStatusLabelAction, IgnoreExtensionRecommendationAction, UndoIgnoreExtensionRecommendationAction, EnableDropDownAction, DisableDropDownAction, StatusLabelAction, SetFileIconThemeAction, SetColorThemeAction, RemoteInstallAction, DisabledLabelAction, SystemDisabledWarningAction, LocalInstallAction } from 'vs/workbench/contrib/extensions/browser/extensionsActions'; +import { CombinedInstallAction, UpdateAction, ExtensionEditorDropDownAction, ReloadAction, MaliciousStatusLabelAction, IgnoreExtensionRecommendationAction, UndoIgnoreExtensionRecommendationAction, EnableDropDownAction, DisableDropDownAction, StatusLabelAction, SetFileIconThemeAction, SetColorThemeAction, RemoteInstallAction, ExtensionToolTipAction, SystemDisabledWarningAction, LocalInstallAction } from 'vs/workbench/contrib/extensions/browser/extensionsActions'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { DomScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableElement'; import { IOpenerService } from 'vs/platform/opener/common/opener'; @@ -391,7 +391,7 @@ export class ExtensionEditor extends BaseEditor { this.instantiationService.createInstance(LocalInstallAction), combinedInstallAction, systemDisabledWarningAction, - this.instantiationService.createInstance(DisabledLabelAction, systemDisabledWarningAction), + this.instantiationService.createInstance(ExtensionToolTipAction, systemDisabledWarningAction, reloadAction), this.instantiationService.createInstance(MaliciousStatusLabelAction, true), ]; const extensionContainers: ExtensionContainers = this.instantiationService.createInstance(ExtensionContainers, [...actions, ...widgets]); diff --git a/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts b/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts index 46983d906e..5134bc1081 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts @@ -64,14 +64,15 @@ Registry.as(Extensions.Quickopen).registerQuickOpenHandler( ); // Editor -const editorDescriptor = new EditorDescriptor( - ExtensionEditor, - ExtensionEditor.ID, - localize('extension', "Extension") -); - -Registry.as(EditorExtensions.Editors) - .registerEditor(editorDescriptor, [new SyncDescriptor(ExtensionsInput)]); +Registry.as(EditorExtensions.Editors).registerEditor( + new EditorDescriptor( + ExtensionEditor, + ExtensionEditor.ID, + localize('extension', "Extension") + ), + [ + new SyncDescriptor(ExtensionsInput) + ]); // Viewlet const viewletDescriptor = new ViewletDescriptor( diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts b/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts index 6f8a93ff97..874c3ac42c 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts @@ -61,11 +61,11 @@ import { IPreferencesService } from 'vs/workbench/services/preferences/common/pr import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; import { IProductService } from 'vs/platform/product/common/product'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; +import { IFileDialogService } from 'vs/platform/dialogs/common/dialogs'; -// {{SQL CARBON EDIT}} -import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; +import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; // {{SQL CARBON EDIT}} -function toExtensionDescription(local: ILocalExtension): IExtensionDescription { +export function toExtensionDescription(local: ILocalExtension): IExtensionDescription { return { identifier: new ExtensionIdentifier(local.identifier.id), isBuiltin: local.type === ExtensionType.System, @@ -76,7 +76,8 @@ function toExtensionDescription(local: ILocalExtension): IExtensionDescription { }; } -const promptDownloadManually = (extension: IGalleryExtension | undefined, message: string, error: Error, instantiationService: IInstantiationService, notificationService: INotificationService, openerService: IOpenerService, productService: IProductService) => { +const promptDownloadManually = (extension: IGalleryExtension | undefined, message: string, error: Error, + instantiationService: IInstantiationService, notificationService: INotificationService, openerService: IOpenerService, productService: IProductService) => { if (!extension || error.name === INSTALL_ERROR_INCOMPATIBLE || error.name === INSTALL_ERROR_MALICIOUS || !productService.extensionsGallery) { return Promise.reject(error); } else { @@ -1182,9 +1183,7 @@ export class ReloadAction extends ExtensionAction { @IWindowService private readonly windowService: IWindowService, @IExtensionService private readonly extensionService: IExtensionService, @IExtensionEnablementService private readonly extensionEnablementService: IExtensionEnablementService, - @IExtensionManagementServerService private readonly extensionManagementServerService: IExtensionManagementServerService, - @IConfigurationService private readonly configurationService: IConfigurationService, - @IProductService private readonly productService: IProductService, + @IExtensionManagementServerService private readonly extensionManagementServerService: IExtensionManagementServerService ) { super('extensions.reload', localize('reloadAction', "Reload"), ReloadAction.DisabledClass, false); this._register(this.extensionService.onDidChangeExtensions(this.updateRunningExtensions, this)); @@ -1232,8 +1231,9 @@ export class ReloadAction extends ExtensionAction { } if (this.extension.local) { const isEnabled = this.extensionEnablementService.isEnabled(this.extension.local); + + // Extension is runningÃŽ if (runningExtension) { - // Extension is running if (isEnabled) { if (!this.extensionService.canAddExtension(toExtensionDescription(this.extension.local))) { if (isSameExtensionRunning) { @@ -1259,39 +1259,27 @@ export class ReloadAction extends ExtensionAction { } } return; - } else { - // Extension is not running + } + + // Extension is not running + else { if (isEnabled && !this.extensionService.canAddExtension(toExtensionDescription(this.extension.local))) { this.enabled = true; this.label = localize('reloadRequired', "Reload Required"); this.tooltip = localize('postEnableTooltip', "Please reload Azure Data Studio to enable this extension."); // {{SQL CARBON EDIT}} - replace Visual Studio Code with Azure Data Studio return; } - if (this.extensionManagementServerService.localExtensionManagementServer && this.extensionManagementServerService.remoteExtensionManagementServer) { - const uiExtension = isUIExtension(this.extension.local.manifest, this.productService, this.configurationService); - // Local Workspace Extension - if (!uiExtension && this.extension.server === this.extensionManagementServerService.localExtensionManagementServer) { - const remoteExtension = this.extensionsWorkbenchService.local.filter(e => areSameExtensions(e.identifier, this.extension.identifier) && e.server === this.extensionManagementServerService.remoteExtensionManagementServer)[0]; - // Extension exist in remote and enabled - if (remoteExtension && remoteExtension.local && this.extensionEnablementService.isEnabled(remoteExtension.local)) { - this.enabled = true; - this.label = localize('reloadRequired', "Reload Required"); - this.tooltip = localize('postEnableTooltip', "Please reload Azure Data Studio to enable this extension.");// {{SQL CARBON EDIT}} - replace Visual Studio Code with Azure Data Studio - alert(localize('installExtensionComplete', "Installing extension {0} is completed. Please reload Azure Data Studio to enable it.", this.extension.displayName)); // {{SQL CARBON EDIT}} - replace Visual Studio Code with Azure Data Studio - return; - } - } - // Remote UI Extension - if (uiExtension && this.extension.server === this.extensionManagementServerService.remoteExtensionManagementServer) { - const localExtension = this.extensionsWorkbenchService.local.filter(e => areSameExtensions(e.identifier, this.extension.identifier) && e.server === this.extensionManagementServerService.localExtensionManagementServer)[0]; - // Extension exist in local and enabled - if (localExtension && localExtension.local && this.extensionEnablementService.isEnabled(localExtension.local)) { - this.enabled = true; - this.label = localize('reloadRequired', "Reload Required"); - this.tooltip = localize('postEnableTooltip', "Please reload Azure Data Studio to enable this extension."); // {{SQL CARBON EDIT}} - replace Visual Studio Code with Azure Data Studio - alert(localize('installExtensionComplete', "Installing extension {0} is completed. Please reload Azure Data Studio to enable it.", this.extension.displayName)); // {{SQL CARBON EDIT}} - replace Visual Studio Code with Azure Data Studio - return; - } + + const otherServer = this.extension.server ? this.extension.server === this.extensionManagementServerService.localExtensionManagementServer ? this.extensionManagementServerService.remoteExtensionManagementServer : this.extensionManagementServerService.localExtensionManagementServer : null; + if (otherServer && this.extension.enablementState === EnablementState.DisabledByExtensionKind) { + const extensionInOtherServer = this.extensionsWorkbenchService.local.filter(e => areSameExtensions(e.identifier, this.extension.identifier) && e.server === otherServer)[0]; + // Same extension in other server exists and + if (extensionInOtherServer && extensionInOtherServer.local && this.extensionEnablementService.isEnabled(extensionInOtherServer.local)) { + this.enabled = true; + this.label = localize('reloadRequired', "Reload Required"); + this.tooltip = localize('postEnableTooltip', "Please reload Azure Data Studio to enable this extension."); // {{SQL CARBON EDIT}} - replace Visual Studio Code with Azure Data Studio + alert(localize('installExtensionComplete', "Installing extension {0} is completed. Please reload Azure Data Studio to enable it.", this.extension.displayName)); // {{SQL CARBON EDIT}} - replace Visual Studio Code with Azure Data Studio + return; } } } @@ -2548,7 +2536,7 @@ export class MaliciousStatusLabelAction extends ExtensionAction { } } -export class DisabledLabelAction extends ExtensionAction { +export class ExtensionToolTipAction extends ExtensionAction { private static readonly Class = 'disable-status'; @@ -2557,10 +2545,12 @@ export class DisabledLabelAction extends ExtensionAction { constructor( private readonly warningAction: SystemDisabledWarningAction, + private readonly reloadAction: ReloadAction, @IExtensionEnablementService private readonly extensionEnablementService: IExtensionEnablementService, @IExtensionService private readonly extensionService: IExtensionService, + @IExtensionManagementServerService private readonly extensionManagementServerService: IExtensionManagementServerService ) { - super('extensions.disabledLabel', warningAction.tooltip, `${DisabledLabelAction.Class} hide`, false); + super('extensions.tooltip', warningAction.tooltip, `${ExtensionToolTipAction.Class} hide`, false); this._register(warningAction.onDidChange(() => this.update(), this)); this._register(this.extensionService.onDidChangeExtensions(this.updateRunningExtensions, this)); this.updateRunningExtensions(); @@ -2571,25 +2561,51 @@ export class DisabledLabelAction extends ExtensionAction { } update(): void { - this.class = `${DisabledLabelAction.Class} hide`; - this.label = ''; + this.label = this.getTooltip(); + this.class = ExtensionToolTipAction.Class; + if (!this.label) { + this.class = `${ExtensionToolTipAction.Class} hide`; + } + } + + private getTooltip(): string { + if (!this.extension) { + return ''; + } + if (this.reloadAction.enabled) { + return this.reloadAction.tooltip; + } if (this.warningAction.tooltip) { - this.class = DisabledLabelAction.Class; - this.label = this.warningAction.tooltip; - return; + return this.warningAction.tooltip; } - if (this.extension && this.extension.local && isLanguagePackExtension(this.extension.local.manifest)) { - return; - } - if (this.extension && this.extension.local && this._runningExtensions) { + if (this.extension && this.extension.local && this.extension.state === ExtensionState.Installed && this._runningExtensions) { + const isRunning = this._runningExtensions.some(e => areSameExtensions({ id: e.identifier.value, uuid: e.uuid }, this.extension.identifier)); const isEnabled = this.extensionEnablementService.isEnabled(this.extension.local); - const isExtensionRunning = this._runningExtensions.some(e => areSameExtensions({ id: e.identifier.value, uuid: e.uuid }, this.extension.identifier)); - if (!isExtensionRunning && !isEnabled && this.extensionEnablementService.canChangeEnablement(this.extension.local)) { - this.class = DisabledLabelAction.Class; - this.label = localize('disabled by user', "This extension is disabled by the user."); - return; + + if (isEnabled && isRunning) { + if (this.extensionManagementServerService.localExtensionManagementServer && this.extensionManagementServerService.remoteExtensionManagementServer) { + if (this.extension.server === this.extensionManagementServerService.remoteExtensionManagementServer) { + return localize('extension enabled on remote', "Extension is enabled on '{0}'", this.extension.server.label); + } + } + if (this.extension.enablementState === EnablementState.EnabledGlobally) { + return localize('globally enabled', "This extension is enabled globally."); + } + if (this.extension.enablementState === EnablementState.EnabledWorkspace) { + return localize('workspace enabled', "This extension is enabled for this workspace by the user."); + } + } + + if (!isEnabled && !isRunning) { + if (this.extension.enablementState === EnablementState.DisabledGlobally) { + return localize('globally disabled', "This extension is disabled globally by the user."); + } + if (this.extension.enablementState === EnablementState.DisabledWorkspace) { + return localize('workspace disabled', "This extension is disabled for this workspace by the user."); + } } } + return ''; } run(): Promise { @@ -2832,6 +2848,7 @@ export class InstallVSIXAction extends Action { @IExtensionsWorkbenchService private readonly extensionsWorkbenchService: IExtensionsWorkbenchService, @INotificationService private readonly notificationService: INotificationService, @IWindowService private readonly windowService: IWindowService, + @IFileDialogService private readonly fileDialogService: IFileDialogService, @IExtensionService private readonly extensionService: IExtensionService, @IInstantiationService private readonly instantiationService: IInstantiationService, // {{SQL CARBON EDIT}} diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsList.ts b/src/vs/workbench/contrib/extensions/browser/extensionsList.ts index 8186007390..292ca665ee 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsList.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsList.ts @@ -13,7 +13,7 @@ import { IPagedRenderer } from 'vs/base/browser/ui/list/listPaging'; import { Event } from 'vs/base/common/event'; import { domEvent } from 'vs/base/browser/event'; import { IExtension, ExtensionContainers, ExtensionState, IExtensionsWorkbenchService } from 'vs/workbench/contrib/extensions/common/extensions'; -import { InstallAction, UpdateAction, ManageExtensionAction, ReloadAction, MaliciousStatusLabelAction, ExtensionActionViewItem, StatusLabelAction, RemoteInstallAction, SystemDisabledWarningAction, DisabledLabelAction, LocalInstallAction } from 'vs/workbench/contrib/extensions/browser/extensionsActions'; +import { InstallAction, UpdateAction, ManageExtensionAction, ReloadAction, MaliciousStatusLabelAction, ExtensionActionViewItem, StatusLabelAction, RemoteInstallAction, SystemDisabledWarningAction, ExtensionToolTipAction, LocalInstallAction } from 'vs/workbench/contrib/extensions/browser/extensionsActions'; import { areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; import { Label, RatingsWidget, InstallCountWidget, RecommendationWidget, RemoteBadgeWidget, TooltipWidget } from 'vs/workbench/contrib/extensions/browser/extensionsWidgets'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; @@ -103,8 +103,8 @@ export class Renderer implements IPagedRenderer { systemDisabledWarningAction, this.instantiationService.createInstance(ManageExtensionAction) ]; - const disabledLabelAction = this.instantiationService.createInstance(DisabledLabelAction, systemDisabledWarningAction); - const tooltipWidget = this.instantiationService.createInstance(TooltipWidget, root, disabledLabelAction, recommendationWidget, reloadAction); + const extensionTooltipAction = this.instantiationService.createInstance(ExtensionToolTipAction, systemDisabledWarningAction, reloadAction); + const tooltipWidget = this.instantiationService.createInstance(TooltipWidget, root, extensionTooltipAction, recommendationWidget); const widgets = [ recommendationWidget, iconRemoteBadgeWidget, @@ -115,10 +115,10 @@ export class Renderer implements IPagedRenderer { // this.instantiationService.createInstance(InstallCountWidget, installCount, true), this.instantiationService.createInstance(RatingsWidget, ratings, true) ]; - const extensionContainers: ExtensionContainers = this.instantiationService.createInstance(ExtensionContainers, [...actions, ...widgets, disabledLabelAction]); + const extensionContainers: ExtensionContainers = this.instantiationService.createInstance(ExtensionContainers, [...actions, ...widgets, extensionTooltipAction]); actionbar.push(actions, actionOptions); - const disposables = combinedDisposable(...actions, ...widgets, actionbar, extensionContainers, disabledLabelAction); + const disposables = combinedDisposable(...actions, ...widgets, actionbar, extensionContainers, extensionTooltipAction); return { // {{SQL CARBON EDIT}} diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsWidgets.ts b/src/vs/workbench/contrib/extensions/browser/extensionsWidgets.ts index 7df5164b5d..8d9caa2380 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsWidgets.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsWidgets.ts @@ -5,13 +5,13 @@ import 'vs/css!./media/extensionsWidgets'; import { Disposable, toDisposable, DisposableStore, MutableDisposable } from 'vs/base/common/lifecycle'; -import { IExtension, IExtensionsWorkbenchService, IExtensionContainer, ExtensionState } from '../common/extensions'; +import { IExtension, IExtensionsWorkbenchService, IExtensionContainer } from 'vs/workbench/contrib/extensions/common/extensions'; import { append, $, addClass } from 'vs/base/browser/dom'; import * as platform from 'vs/base/common/platform'; import { localize } from 'vs/nls'; import { IExtensionTipsService, IExtensionManagementServerService } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; import { ILabelService } from 'vs/platform/label/common/label'; -import { extensionButtonProminentBackground, extensionButtonProminentForeground, DisabledLabelAction, ReloadAction } from 'vs/workbench/contrib/extensions/browser/extensionsActions'; +import { extensionButtonProminentBackground, extensionButtonProminentForeground, ExtensionToolTipAction } from 'vs/workbench/contrib/extensions/browser/extensionsActions'; import { IThemeService, ITheme } from 'vs/platform/theme/common/themeService'; import { EXTENSION_BADGE_REMOTE_BACKGROUND, EXTENSION_BADGE_REMOTE_FOREGROUND } from 'vs/workbench/common/theme'; import { Emitter, Event } from 'vs/base/common/event'; @@ -145,16 +145,13 @@ export class TooltipWidget extends ExtensionWidget { constructor( private readonly parent: HTMLElement, - private readonly disabledLabelAction: DisabledLabelAction, + private readonly tooltipAction: ExtensionToolTipAction, private readonly recommendationWidget: RecommendationWidget, - private readonly reloadAction: ReloadAction, - @IExtensionManagementServerService private readonly extensionManagementServerService: IExtensionManagementServerService, @ILabelService private readonly labelService: ILabelService ) { super(); this._register(Event.any( - this.disabledLabelAction.onDidChange, - this.reloadAction.onDidChange, + this.tooltipAction.onDidChange, this.recommendationWidget.onDidChangeTooltip, this.labelService.onDidChangeFormatters )(() => this.render())); @@ -173,17 +170,8 @@ export class TooltipWidget extends ExtensionWidget { if (!this.extension) { return ''; } - if (this.reloadAction.enabled) { - return this.reloadAction.tooltip; - } - if (this.disabledLabelAction.label) { - return this.disabledLabelAction.label; - } - if (this.extension.local && this.extension.state === ExtensionState.Installed) { - if (this.extension.server === this.extensionManagementServerService.remoteExtensionManagementServer) { - return localize('extension enabled on remote', "Extension is enabled on '{0}'", this.extension.server.label); - } - return localize('extension enabled locally', "Extension is enabled locally."); + if (this.tooltipAction.tooltip) { + return this.tooltipAction.tooltip; } return this.recommendationWidget.tooltip; } diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts b/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts index ee002a91eb..2d5c1593f8 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts @@ -424,7 +424,9 @@ class Extensions extends Disposable { const installingExtension = gallery ? this.installing.filter(e => areSameExtensions(e.identifier, gallery.identifier))[0] : null; this.installing = installingExtension ? this.installing.filter(e => e !== installingExtension) : this.installing; - let extension: Extension | undefined = installingExtension ? installingExtension : zipPath ? this.instantiationService.createInstance(Extension, this.stateProvider, this.server, local, undefined) : undefined; + let extension: Extension | undefined = installingExtension ? installingExtension + : (zipPath || local) ? this.instantiationService.createInstance(Extension, this.stateProvider, this.server, local, undefined) + : undefined; if (extension) { if (local) { const installed = this.installed.filter(e => areSameExtensions(e.identifier, extension!.identifier))[0]; diff --git a/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsActions.test.ts b/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsActions.test.ts index 5291d1e70e..0d310f31f4 100644 --- a/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsActions.test.ts +++ b/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsActions.test.ts @@ -1063,37 +1063,39 @@ suite('ExtensionsActions Test', () => { assert.ok(!testObject.enabled); }); - test('Test ReloadAction when extension state is installing', () => { + test('Test ReloadAction when extension state is installing', async () => { const testObject: ExtensionsActions.ReloadAction = instantiationService.createInstance(ExtensionsActions.ReloadAction); instantiationService.createInstance(ExtensionContainers, [testObject]); const workbenchService = instantiationService.get(IExtensionsWorkbenchService); const gallery = aGalleryExtension('a'); instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(gallery)); - return workbenchService.queryGallery(CancellationToken.None) - .then((paged) => { - testObject.extension = paged.firstPage[0]; - installEvent.fire({ identifier: gallery.identifier, gallery }); + const paged = await workbenchService.queryGallery(CancellationToken.None); + testObject.extension = paged.firstPage[0]; + installEvent.fire({ identifier: gallery.identifier, gallery }); - assert.ok(!testObject.enabled); - }); + assert.ok(!testObject.enabled); }); - test('Test ReloadAction when extension state is uninstalling', () => { + test('Test ReloadAction when extension state is uninstalling', async () => { const testObject: ExtensionsActions.ReloadAction = instantiationService.createInstance(ExtensionsActions.ReloadAction); instantiationService.createInstance(ExtensionContainers, [testObject]); const local = aLocalExtension('a'); instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [local]); - return instantiationService.get(IExtensionsWorkbenchService).queryLocal() - .then(extensions => { - testObject.extension = extensions[0]; - uninstallEvent.fire(local.identifier); - assert.ok(!testObject.enabled); - }); + const extensions = await instantiationService.get(IExtensionsWorkbenchService).queryLocal(); + testObject.extension = extensions[0]; + uninstallEvent.fire(local.identifier); + assert.ok(!testObject.enabled); }); test('Test ReloadAction when extension is newly installed', async () => { - instantiationService.stubPromise(IExtensionService, 'getExtensions', [{ identifier: new ExtensionIdentifier('pub.b'), extensionLocation: URI.file('pub.b') }]); + const onDidChangeExtensionsEmitter: Emitter = new Emitter(); + const runningExtensions = [{ identifier: new ExtensionIdentifier('pub.b'), extensionLocation: URI.file('pub.b') }]; + instantiationService.stub(IExtensionService, >{ + getExtensions: () => Promise.resolve(runningExtensions), + onDidChangeExtensions: onDidChangeExtensionsEmitter.event, + canAddExtension: (extension) => false + }); const testObject: ExtensionsActions.ReloadAction = instantiationService.createInstance(ExtensionsActions.ReloadAction); instantiationService.createInstance(ExtensionContainers, [testObject]); const gallery = aGalleryExtension('a'); @@ -1103,35 +1105,50 @@ suite('ExtensionsActions Test', () => { testObject.extension = paged.firstPage[0]; assert.ok(!testObject.enabled); - return new Promise(c => { - testObject.onDidChange(() => { - // {{SQL CARBON EDIT}} - replace Visual Studio Code with Azure Data Studio - if (testObject.enabled && testObject.tooltip === 'Please reload Azure Data Studio to enable this extension.') { - c(); - } - }); - installEvent.fire({ identifier: gallery.identifier, gallery }); - didInstallEvent.fire({ identifier: gallery.identifier, gallery, operation: InstallOperation.Install, local: aLocalExtension('a', gallery, gallery) }); - }); + installEvent.fire({ identifier: gallery.identifier, gallery }); + didInstallEvent.fire({ identifier: gallery.identifier, gallery, operation: InstallOperation.Install, local: aLocalExtension('a', gallery, gallery) }); + assert.ok(testObject.enabled); + assert.equal(testObject.tooltip, 'Please reload Azure Data Studio to enable this extension.'); // {{SQL CARBON EDIT}} - replace Visual Studio Code with Azure Data Studio }); - test('Test ReloadAction when extension is installed and uninstalled', () => { + test('Test ReloadAction when extension is newly installed and reload is not required', async () => { + const onDidChangeExtensionsEmitter: Emitter = new Emitter(); + const runningExtensions = [{ identifier: new ExtensionIdentifier('pub.b'), extensionLocation: URI.file('pub.b') }]; + instantiationService.stub(IExtensionService, >{ + getExtensions: () => Promise.resolve(runningExtensions), + onDidChangeExtensions: onDidChangeExtensionsEmitter.event, + canAddExtension: (extension) => true + }); + const testObject: ExtensionsActions.ReloadAction = instantiationService.createInstance(ExtensionsActions.ReloadAction); + instantiationService.createInstance(ExtensionContainers, [testObject]); + const gallery = aGalleryExtension('a'); + instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(gallery)); + + const paged = await instantiationService.get(IExtensionsWorkbenchService).queryGallery(CancellationToken.None); + testObject.extension = paged.firstPage[0]; + assert.ok(!testObject.enabled); + + installEvent.fire({ identifier: gallery.identifier, gallery }); + didInstallEvent.fire({ identifier: gallery.identifier, gallery, operation: InstallOperation.Install, local: aLocalExtension('a', gallery, gallery) }); + assert.ok(!testObject.enabled); + }); + + test('Test ReloadAction when extension is installed and uninstalled', async () => { instantiationService.stubPromise(IExtensionService, 'getExtensions', [{ identifier: new ExtensionIdentifier('pub.b'), extensionLocation: URI.file('pub.b') }]); const testObject: ExtensionsActions.ReloadAction = instantiationService.createInstance(ExtensionsActions.ReloadAction); instantiationService.createInstance(ExtensionContainers, [testObject]); const gallery = aGalleryExtension('a'); instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(gallery)); - return instantiationService.get(IExtensionsWorkbenchService).queryGallery(CancellationToken.None) - .then((paged) => { - testObject.extension = paged.firstPage[0]; - const identifier = gallery.identifier; - installEvent.fire({ identifier, gallery }); - didInstallEvent.fire({ identifier, gallery, operation: InstallOperation.Install, local: aLocalExtension('a', gallery, { identifier }) }); - uninstallEvent.fire(identifier); - didUninstallEvent.fire({ identifier }); + const paged = await instantiationService.get(IExtensionsWorkbenchService).queryGallery(CancellationToken.None); - assert.ok(!testObject.enabled); - }); + testObject.extension = paged.firstPage[0]; + const identifier = gallery.identifier; + installEvent.fire({ identifier, gallery }); + didInstallEvent.fire({ identifier, gallery, operation: InstallOperation.Install, local: aLocalExtension('a', gallery, { identifier }) }); + uninstallEvent.fire(identifier); + didUninstallEvent.fire({ identifier }); + + assert.ok(!testObject.enabled); }); test('Test ReloadAction when extension is uninstalled', async () => { @@ -1145,8 +1162,7 @@ suite('ExtensionsActions Test', () => { return new Promise(c => { testObject.onDidChange(() => { - // {{SQL CARBON EDIT}} - replace Visual Studio Code with Azure Data Studio - if (testObject.enabled && testObject.tooltip === 'Please reload Azure Data Studio to complete the uninstallation of this extension.') { + if (testObject.enabled && testObject.tooltip === 'Please reload Azure Data Studio to complete the uninstallation of this extension.') { // {{SQL CARBON EDIT}} - replace Visual Studio Code with Azure Data Studio c(); } }); @@ -1155,25 +1171,24 @@ suite('ExtensionsActions Test', () => { }); }); - test('Test ReloadAction when extension is uninstalled and installed', () => { + test('Test ReloadAction when extension is uninstalled and installed', async () => { instantiationService.stubPromise(IExtensionService, 'getExtensions', [{ identifier: new ExtensionIdentifier('pub.a'), version: '1.0.0', extensionLocation: URI.file('pub.a') }]); const testObject: ExtensionsActions.ReloadAction = instantiationService.createInstance(ExtensionsActions.ReloadAction); instantiationService.createInstance(ExtensionContainers, [testObject]); const local = aLocalExtension('a'); instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [local]); - return instantiationService.get(IExtensionsWorkbenchService).queryLocal() - .then(extensions => { - testObject.extension = extensions[0]; - uninstallEvent.fire(local.identifier); - didUninstallEvent.fire({ identifier: local.identifier }); + const extensions = await instantiationService.get(IExtensionsWorkbenchService).queryLocal(); - const gallery = aGalleryExtension('a'); - const identifier = gallery.identifier; - installEvent.fire({ identifier, gallery }); - didInstallEvent.fire({ identifier, gallery, operation: InstallOperation.Install, local }); + testObject.extension = extensions[0]; + uninstallEvent.fire(local.identifier); + didUninstallEvent.fire({ identifier: local.identifier }); - assert.ok(!testObject.enabled); - }); + const gallery = aGalleryExtension('a'); + const identifier = gallery.identifier; + installEvent.fire({ identifier, gallery }); + didInstallEvent.fire({ identifier, gallery, operation: InstallOperation.Install, local }); + + assert.ok(!testObject.enabled); }); test('Test ReloadAction when extension is updated while running', async () => { @@ -1188,8 +1203,7 @@ suite('ExtensionsActions Test', () => { return new Promise(c => { testObject.onDidChange(() => { - // {{SQL CARBON EDIT}} - replace Visual Studio Code with Azure Data Studio - if (testObject.enabled && testObject.tooltip === 'Please reload Azure Data Studio to enable the updated extension.') { + if (testObject.enabled && testObject.tooltip === 'Please reload Azure Data Studio to enable the updated extension.') { // {{SQL CARBON EDIT}} - replace Visual Studio Code with Azure Data Studio c(); } }); @@ -1199,131 +1213,103 @@ suite('ExtensionsActions Test', () => { }); }); - test('Test ReloadAction when extension is updated when not running', () => { + test('Test ReloadAction when extension is updated when not running', async () => { instantiationService.stubPromise(IExtensionService, 'getExtensions', [{ identifier: new ExtensionIdentifier('pub.b'), extensionLocation: URI.file('pub.b') }]); const local = aLocalExtension('a', { version: '1.0.1' }); - return instantiationService.get(IExtensionEnablementService).setEnablement([local], EnablementState.DisabledGlobally) - .then(() => { - const testObject: ExtensionsActions.ReloadAction = instantiationService.createInstance(ExtensionsActions.ReloadAction); - instantiationService.createInstance(ExtensionContainers, [testObject]); - const workbenchService = instantiationService.get(IExtensionsWorkbenchService); - instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [local]); - return workbenchService.queryLocal() - .then(extensions => { - testObject.extension = extensions[0]; + await instantiationService.get(IExtensionEnablementService).setEnablement([local], EnablementState.DisabledGlobally); + const testObject: ExtensionsActions.ReloadAction = instantiationService.createInstance(ExtensionsActions.ReloadAction); + instantiationService.createInstance(ExtensionContainers, [testObject]); + const workbenchService = instantiationService.get(IExtensionsWorkbenchService); + instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [local]); + const extensions = await workbenchService.queryLocal(); + testObject.extension = extensions[0]; - const gallery = aGalleryExtension('a', { identifier: local.identifier, version: '1.0.2' }); - installEvent.fire({ identifier: gallery.identifier, gallery }); - didInstallEvent.fire({ identifier: gallery.identifier, gallery, operation: InstallOperation.Update, local: aLocalExtension('a', gallery, gallery) }); + const gallery = aGalleryExtension('a', { identifier: local.identifier, version: '1.0.2' }); + installEvent.fire({ identifier: gallery.identifier, gallery }); + didInstallEvent.fire({ identifier: gallery.identifier, gallery, operation: InstallOperation.Update, local: aLocalExtension('a', gallery, gallery) }); - assert.ok(!testObject.enabled); - }); - }); + assert.ok(!testObject.enabled); }); - test('Test ReloadAction when extension is disabled when running', () => { + test('Test ReloadAction when extension is disabled when running', async () => { instantiationService.stubPromise(IExtensionService, 'getExtensions', [{ identifier: new ExtensionIdentifier('pub.a'), extensionLocation: URI.file('pub.a') }]); const testObject: ExtensionsActions.ReloadAction = instantiationService.createInstance(ExtensionsActions.ReloadAction); instantiationService.createInstance(ExtensionContainers, [testObject]); const local = aLocalExtension('a'); const workbenchService = instantiationService.get(IExtensionsWorkbenchService); instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [local]); - return workbenchService.queryLocal().then(extensions => { - testObject.extension = extensions[0]; - return workbenchService.setEnablement(extensions[0], EnablementState.DisabledGlobally) - .then(() => testObject.update()) - .then(() => { - assert.ok(testObject.enabled); - // {{SQL CARBON EDIT}} - replace Visual Studio Code with Azure Data Studio - assert.equal('Please reload Azure Data Studio to disable this extension.', testObject.tooltip); - }); - }); + const extensions = await workbenchService.queryLocal(); + testObject.extension = extensions[0]; + await workbenchService.setEnablement(extensions[0], EnablementState.DisabledGlobally); + await testObject.update(); + + assert.ok(testObject.enabled); + assert.equal('Please reload Azure Data Studio to disable this extension.', testObject.tooltip); // {{SQL CARBON EDIT}} - replace Visual Studio Code with Azure Data Studio }); - test('Test ReloadAction when extension enablement is toggled when running', () => { + test('Test ReloadAction when extension enablement is toggled when running', async () => { instantiationService.stubPromise(IExtensionService, 'getExtensions', [{ identifier: new ExtensionIdentifier('pub.a'), version: '1.0.0', extensionLocation: URI.file('pub.a') }]); const testObject: ExtensionsActions.ReloadAction = instantiationService.createInstance(ExtensionsActions.ReloadAction); instantiationService.createInstance(ExtensionContainers, [testObject]); const local = aLocalExtension('a'); const workbenchService = instantiationService.get(IExtensionsWorkbenchService); instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [local]); - return workbenchService.queryLocal(). - then(extensions => { - testObject.extension = extensions[0]; - return workbenchService.setEnablement(extensions[0], EnablementState.DisabledGlobally) - .then(() => workbenchService.setEnablement(extensions[0], EnablementState.EnabledGlobally)) - .then(() => assert.ok(!testObject.enabled)); - }); + const extensions = await workbenchService.queryLocal(); + testObject.extension = extensions[0]; + await workbenchService.setEnablement(extensions[0], EnablementState.DisabledGlobally); + await workbenchService.setEnablement(extensions[0], EnablementState.EnabledGlobally); + assert.ok(!testObject.enabled); }); - test('Test ReloadAction when extension is enabled when not running', () => { + test('Test ReloadAction when extension is enabled when not running', async () => { instantiationService.stubPromise(IExtensionService, 'getExtensions', [{ identifier: new ExtensionIdentifier('pub.b'), extensionLocation: URI.file('pub.b') }]); const local = aLocalExtension('a'); - return instantiationService.get(IExtensionEnablementService).setEnablement([local], EnablementState.DisabledGlobally) - .then(() => { - const testObject: ExtensionsActions.ReloadAction = instantiationService.createInstance(ExtensionsActions.ReloadAction); - instantiationService.createInstance(ExtensionContainers, [testObject]); - const workbenchService = instantiationService.get(IExtensionsWorkbenchService); - instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [local]); - return workbenchService.queryLocal() - .then(extensions => { - testObject.extension = extensions[0]; - return workbenchService.setEnablement(extensions[0], EnablementState.EnabledGlobally) - .then(() => testObject.update()) - .then(() => { - assert.ok(testObject.enabled); - // {{SQL CARBON EDIT}} - replace Visual Studio Code with Azure Data Studio - assert.equal('Please reload Azure Data Studio to enable this extension.', testObject.tooltip); - }); - }); - }); + await instantiationService.get(IExtensionEnablementService).setEnablement([local], EnablementState.DisabledGlobally); + const testObject: ExtensionsActions.ReloadAction = instantiationService.createInstance(ExtensionsActions.ReloadAction); + instantiationService.createInstance(ExtensionContainers, [testObject]); + const workbenchService = instantiationService.get(IExtensionsWorkbenchService); + instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [local]); + const extensions = await workbenchService.queryLocal(); + testObject.extension = extensions[0]; + await workbenchService.setEnablement(extensions[0], EnablementState.EnabledGlobally); + await testObject.update(); + assert.ok(testObject.enabled); + assert.equal('Please reload Azure Data Studio to enable this extension.', testObject.tooltip); // {{SQL CARBON EDIT}} - replace Visual Studio Code with Azure Data Studio }); - test('Test ReloadAction when extension enablement is toggled when not running', () => { + test('Test ReloadAction when extension enablement is toggled when not running', async () => { instantiationService.stubPromise(IExtensionService, 'getExtensions', [{ identifier: new ExtensionIdentifier('pub.b'), extensionLocation: URI.file('pub.b') }]); const local = aLocalExtension('a'); - return instantiationService.get(IExtensionEnablementService).setEnablement([local], EnablementState.DisabledGlobally) - .then(() => { - const testObject: ExtensionsActions.ReloadAction = instantiationService.createInstance(ExtensionsActions.ReloadAction); - instantiationService.createInstance(ExtensionContainers, [testObject]); - const workbenchService = instantiationService.get(IExtensionsWorkbenchService); - instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [local]); - return workbenchService.queryLocal() - .then(extensions => { - testObject.extension = extensions[0]; - return workbenchService.setEnablement(extensions[0], EnablementState.EnabledGlobally) - .then(() => workbenchService.setEnablement(extensions[0], EnablementState.DisabledGlobally)) - .then(() => assert.ok(!testObject.enabled)); - }); - }); + await instantiationService.get(IExtensionEnablementService).setEnablement([local], EnablementState.DisabledGlobally); + const testObject: ExtensionsActions.ReloadAction = instantiationService.createInstance(ExtensionsActions.ReloadAction); + instantiationService.createInstance(ExtensionContainers, [testObject]); + const workbenchService = instantiationService.get(IExtensionsWorkbenchService); + instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [local]); + const extensions = await workbenchService.queryLocal(); + testObject.extension = extensions[0]; + await workbenchService.setEnablement(extensions[0], EnablementState.EnabledGlobally); + await workbenchService.setEnablement(extensions[0], EnablementState.DisabledGlobally); + assert.ok(!testObject.enabled); }); - test('Test ReloadAction when extension is updated when not running and enabled', () => { + test('Test ReloadAction when extension is updated when not running and enabled', async () => { instantiationService.stubPromise(IExtensionService, 'getExtensions', [{ identifier: new ExtensionIdentifier('pub.b'), extensionLocation: URI.file('pub.b') }]); const local = aLocalExtension('a', { version: '1.0.1' }); - return instantiationService.get(IExtensionEnablementService).setEnablement([local], EnablementState.DisabledGlobally) - .then(() => { - const testObject: ExtensionsActions.ReloadAction = instantiationService.createInstance(ExtensionsActions.ReloadAction); - instantiationService.createInstance(ExtensionContainers, [testObject]); - const workbenchService = instantiationService.get(IExtensionsWorkbenchService); - instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [local]); - return workbenchService.queryLocal() - .then(extensions => { - testObject.extension = extensions[0]; + await instantiationService.get(IExtensionEnablementService).setEnablement([local], EnablementState.DisabledGlobally); + const testObject: ExtensionsActions.ReloadAction = instantiationService.createInstance(ExtensionsActions.ReloadAction); + instantiationService.createInstance(ExtensionContainers, [testObject]); + const workbenchService = instantiationService.get(IExtensionsWorkbenchService); + instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [local]); + const extensions = await workbenchService.queryLocal(); + testObject.extension = extensions[0]; - const gallery = aGalleryExtension('a', { identifier: local.identifier, version: '1.0.2' }); - installEvent.fire({ identifier: gallery.identifier, gallery }); - didInstallEvent.fire({ identifier: gallery.identifier, gallery, operation: InstallOperation.Install, local: aLocalExtension('a', gallery, gallery) }); - return workbenchService.setEnablement(extensions[0], EnablementState.EnabledGlobally) - .then(() => testObject.update()) - .then(() => { - assert.ok(testObject.enabled); - // {{SQL CARBON EDIT}} - replace Visual Studio Code with Azure Data Studio - assert.equal('Please reload Azure Data Studio to enable this extension.', testObject.tooltip); - }); - - }); - }); + const gallery = aGalleryExtension('a', { identifier: local.identifier, version: '1.0.2' }); + installEvent.fire({ identifier: gallery.identifier, gallery }); + didInstallEvent.fire({ identifier: gallery.identifier, gallery, operation: InstallOperation.Install, local: aLocalExtension('a', gallery, gallery) }); + await workbenchService.setEnablement(extensions[0], EnablementState.EnabledGlobally); + await testObject.update(); + assert.ok(testObject.enabled); + assert.equal('Please reload Azure Data Studio to enable this extension.', testObject.tooltip); // {{SQL CARBON EDIT}} - replace Visual Studio Code with Azure Data Studio }); test('Test ReloadAction when a localization extension is newly installed', async () => { @@ -1358,6 +1344,146 @@ suite('ExtensionsActions Test', () => { assert.ok(!testObject.enabled); }); + test('Test ReloadAction when extension is not installed but extension from different server is installed and running', async () => { + // multi server setup + const gallery = aGalleryExtension('a'); + const localExtension = aLocalExtension('a', { extensionKind: 'workspace' }, { location: URI.file('pub.a') }); + const remoteExtension = aLocalExtension('a', { extensionKind: 'workspace' }, { location: URI.file('pub.a').with({ scheme: Schemas.vscodeRemote }) }); + const extensionManagementServerService = aMultiExtensionManagementServerService(instantiationService, createExtensionManagementService([localExtension]), createExtensionManagementService([remoteExtension])); + instantiationService.stub(IExtensionManagementServerService, extensionManagementServerService); + instantiationService.stub(IExtensionEnablementService, new TestExtensionEnablementService(instantiationService)); + const workbenchService: IExtensionsWorkbenchService = instantiationService.createInstance(ExtensionsWorkbenchService); + instantiationService.set(IExtensionsWorkbenchService, workbenchService); + + const onDidChangeExtensionsEmitter: Emitter = new Emitter(); + const runningExtensions = [ExtensionsActions.toExtensionDescription(remoteExtension)]; + instantiationService.stub(IExtensionService, >{ + getExtensions: () => Promise.resolve(runningExtensions), + onDidChangeExtensions: onDidChangeExtensionsEmitter.event, + canAddExtension: (extension) => false + }); + const testObject: ExtensionsActions.ReloadAction = instantiationService.createInstance(ExtensionsActions.ReloadAction); + instantiationService.createInstance(ExtensionContainers, [testObject]); + instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(gallery)); + + await workbenchService.queryGallery(CancellationToken.None); + const extensions = await workbenchService.queryLocal(extensionManagementServerService.localExtensionManagementServer!); + testObject.extension = extensions[0]; + assert.ok(testObject.extension); + assert.ok(!testObject.enabled); + }); + + test('Test ReloadAction when extension is uninstalled but extension from different server is installed and running', async () => { + // multi server setup + const gallery = aGalleryExtension('a'); + const localExtension = aLocalExtension('a', { extensionKind: 'workspace' }, { location: URI.file('pub.a') }); + const remoteExtension = aLocalExtension('a', { extensionKind: 'workspace' }, { location: URI.file('pub.a').with({ scheme: Schemas.vscodeRemote }) }); + const localExtensionManagementService = createExtensionManagementService([localExtension]); + const uninstallEvent = new Emitter(); + const onDidUninstallEvent = new Emitter<{ identifier: IExtensionIdentifier }>(); + localExtensionManagementService.onUninstallExtension = uninstallEvent.event; + localExtensionManagementService.onDidUninstallExtension = onDidUninstallEvent.event; + const extensionManagementServerService = aMultiExtensionManagementServerService(instantiationService, localExtensionManagementService, createExtensionManagementService([remoteExtension])); + instantiationService.stub(IExtensionManagementServerService, extensionManagementServerService); + instantiationService.stub(IExtensionEnablementService, new TestExtensionEnablementService(instantiationService)); + const workbenchService: IExtensionsWorkbenchService = instantiationService.createInstance(ExtensionsWorkbenchService); + instantiationService.set(IExtensionsWorkbenchService, workbenchService); + + const onDidChangeExtensionsEmitter: Emitter = new Emitter(); + const runningExtensions = [ExtensionsActions.toExtensionDescription(remoteExtension)]; + instantiationService.stub(IExtensionService, >{ + getExtensions: () => Promise.resolve(runningExtensions), + onDidChangeExtensions: onDidChangeExtensionsEmitter.event, + canAddExtension: (extension) => false + }); + const testObject: ExtensionsActions.ReloadAction = instantiationService.createInstance(ExtensionsActions.ReloadAction); + instantiationService.createInstance(ExtensionContainers, [testObject]); + instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(gallery)); + + await workbenchService.queryGallery(CancellationToken.None); + const extensions = await workbenchService.queryLocal(extensionManagementServerService.localExtensionManagementServer!); + testObject.extension = extensions[0]; + assert.ok(testObject.extension); + assert.ok(!testObject.enabled); + + uninstallEvent.fire(localExtension.identifier); + didUninstallEvent.fire({ identifier: localExtension.identifier }); + + assert.ok(!testObject.enabled); + }); + + test('Test ReloadAction when workspace extension is disabled on local server and installed in remote server', async () => { + // multi server setup + const gallery = aGalleryExtension('a'); + const remoteExtensionManagementService = createExtensionManagementService([]); + const onDidInstallEvent = new Emitter(); + remoteExtensionManagementService.onDidInstallExtension = onDidInstallEvent.event; + const localExtension = aLocalExtension('a', { extensionKind: 'workspace' }, { location: URI.file('pub.a') }); + const extensionManagementServerService = aMultiExtensionManagementServerService(instantiationService, createExtensionManagementService([localExtension]), remoteExtensionManagementService); + instantiationService.stub(IExtensionManagementServerService, extensionManagementServerService); + instantiationService.stub(IExtensionEnablementService, new TestExtensionEnablementService(instantiationService)); + const workbenchService: IExtensionsWorkbenchService = instantiationService.createInstance(ExtensionsWorkbenchService); + instantiationService.set(IExtensionsWorkbenchService, workbenchService); + + const onDidChangeExtensionsEmitter: Emitter = new Emitter(); + instantiationService.stub(IExtensionService, >{ + getExtensions: () => Promise.resolve([]), + onDidChangeExtensions: onDidChangeExtensionsEmitter.event, + canAddExtension: (extension) => false + }); + const testObject: ExtensionsActions.ReloadAction = instantiationService.createInstance(ExtensionsActions.ReloadAction); + instantiationService.createInstance(ExtensionContainers, [testObject]); + instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(gallery)); + + await workbenchService.queryGallery(CancellationToken.None); + const extensions = await workbenchService.queryLocal(extensionManagementServerService.localExtensionManagementServer!); + testObject.extension = extensions[0]; + assert.ok(testObject.extension); + assert.ok(!testObject.enabled); + + const remoteExtension = aLocalExtension('a', { extensionKind: 'workspace' }, { location: URI.file('pub.a').with({ scheme: Schemas.vscodeRemote }) }); + onDidInstallEvent.fire({ identifier: remoteExtension.identifier, local: remoteExtension, operation: InstallOperation.Install }); + + assert.ok(testObject.enabled); + assert.equal(testObject.tooltip, 'Please reload Azure Data Studio to enable this extension.'); // {{SQL CARBON EDIT}} - replace Visual Studio Code with Azure Data Studio + }); + + test('Test ReloadAction when ui extension is disabled on remote server and installed in local server', async () => { + // multi server setup + const gallery = aGalleryExtension('a'); + const localExtensionManagementService = createExtensionManagementService([]); + const onDidInstallEvent = new Emitter(); + localExtensionManagementService.onDidInstallExtension = onDidInstallEvent.event; + const remoteExtension = aLocalExtension('a', { extensionKind: 'ui' }, { location: URI.file('pub.a').with({ scheme: Schemas.vscodeRemote }) }); + const extensionManagementServerService = aMultiExtensionManagementServerService(instantiationService, localExtensionManagementService, createExtensionManagementService([remoteExtension])); + instantiationService.stub(IExtensionManagementServerService, extensionManagementServerService); + instantiationService.stub(IExtensionEnablementService, new TestExtensionEnablementService(instantiationService)); + const workbenchService: IExtensionsWorkbenchService = instantiationService.createInstance(ExtensionsWorkbenchService); + instantiationService.set(IExtensionsWorkbenchService, workbenchService); + + const onDidChangeExtensionsEmitter: Emitter = new Emitter(); + instantiationService.stub(IExtensionService, >{ + getExtensions: () => Promise.resolve([]), + onDidChangeExtensions: onDidChangeExtensionsEmitter.event, + canAddExtension: (extension) => false + }); + const testObject: ExtensionsActions.ReloadAction = instantiationService.createInstance(ExtensionsActions.ReloadAction); + instantiationService.createInstance(ExtensionContainers, [testObject]); + instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(gallery)); + + await workbenchService.queryGallery(CancellationToken.None); + const extensions = await workbenchService.queryLocal(extensionManagementServerService.remoteExtensionManagementServer!); + testObject.extension = extensions[0]; + assert.ok(testObject.extension); + assert.ok(!testObject.enabled); + + const localExtension = aLocalExtension('a', { extensionKind: 'ui' }, { location: URI.file('pub.a') }); + onDidInstallEvent.fire({ identifier: localExtension.identifier, local: localExtension, operation: InstallOperation.Install }); + + assert.ok(testObject.enabled); + assert.equal(testObject.tooltip, 'Please reload Azure Data Studio to enable this extension.'); // {{SQL CARBON EDIT}} - replace Visual Studio Code with Azure Data Studio + }); + test('Test remote install action is enabled for local workspace extension', async () => { // multi server setup const localWorkspaceExtension = aLocalExtension('a', { extensionKind: 'workspace' }, { location: URI.file(`pub.a`) }); diff --git a/src/vs/workbench/contrib/preferences/browser/preferences.contribution.ts b/src/vs/workbench/contrib/preferences/browser/preferences.contribution.ts index 24d9ef1420..d4c30a25ba 100644 --- a/src/vs/workbench/contrib/preferences/browser/preferences.contribution.ts +++ b/src/vs/workbench/contrib/preferences/browser/preferences.contribution.ts @@ -533,8 +533,8 @@ MenuRegistry.appendMenuItem(MenuId.EditorTitle, { id: OpenGlobalKeybindingsFileAction.ID, title: OpenGlobalKeybindingsFileAction.LABEL, iconLocation: { - light: URI.parse(require.toUrl(`vs/workbench/contrib/preferences/browser/media/edit-json-light.svg`)), - dark: URI.parse(require.toUrl(`vs/workbench/contrib/preferences/browser/media/edit-json-dark.svg`)) + light: URI.parse(require.toUrl(`vs/workbench/contrib/preferences/browser/media/preferences-editor-light.svg`)), + dark: URI.parse(require.toUrl(`vs/workbench/contrib/preferences/browser/media/preferences-editor-dark.svg`)) } }, when: ContextKeyExpr.and(CONTEXT_KEYBINDINGS_EDITOR), @@ -817,8 +817,8 @@ MenuRegistry.appendMenuItem(MenuId.EditorTitle, { id: SETTINGS_EDITOR_COMMAND_SWITCH_TO_JSON, title: nls.localize('openSettingsJson', "Open Settings (JSON)"), iconLocation: { - dark: URI.parse(require.toUrl('vs/workbench/contrib/preferences/browser/media/edit-json-dark.svg')), - light: URI.parse(require.toUrl('vs/workbench/contrib/preferences/browser/media/edit-json-light.svg')) + dark: URI.parse(require.toUrl('vs/workbench/contrib/preferences/browser/media/preferences-editor-dark.svg')), + light: URI.parse(require.toUrl('vs/workbench/contrib/preferences/browser/media/preferences-editor-light.svg')) } }, group: 'navigation', diff --git a/src/vs/workbench/contrib/preferences/browser/preferencesEditor.ts b/src/vs/workbench/contrib/preferences/browser/preferencesEditor.ts index 8d73f8bee1..0e6bc3ce54 100644 --- a/src/vs/workbench/contrib/preferences/browser/preferencesEditor.ts +++ b/src/vs/workbench/contrib/preferences/browser/preferencesEditor.ts @@ -1154,7 +1154,7 @@ abstract class AbstractSettingsEditorContribution extends Disposable implements abstract getId(): string; } -class DefaultSettingsEditorContribution extends AbstractSettingsEditorContribution implements ISettingsEditorContribution { +export class DefaultSettingsEditorContribution extends AbstractSettingsEditorContribution implements ISettingsEditorContribution { static readonly ID: string = 'editor.contrib.defaultsettings'; diff --git a/src/vs/workbench/contrib/preferences/browser/preferencesRenderers.ts b/src/vs/workbench/contrib/preferences/browser/preferencesRenderers.ts index 82297e5e7c..4d76afe8c5 100644 --- a/src/vs/workbench/contrib/preferences/browser/preferencesRenderers.ts +++ b/src/vs/workbench/contrib/preferences/browser/preferencesRenderers.ts @@ -19,7 +19,7 @@ import { IModelDeltaDecoration, TrackedRangeStickiness } from 'vs/editor/common/ import { ModelDecorationOptions } from 'vs/editor/common/model/textModel'; import * as nls from 'vs/nls'; import { ConfigurationTarget, IConfigurationService, overrideIdentifierFromKey } from 'vs/platform/configuration/common/configuration'; -import { ConfigurationScope, Extensions as ConfigurationExtensions, IConfigurationPropertySchema, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry'; +import { ConfigurationScope, Extensions as ConfigurationExtensions, IConfigurationPropertySchema, IConfigurationRegistry, IConfigurationNode } from 'vs/platform/configuration/common/configurationRegistry'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { Registry } from 'vs/platform/registry/common/platform'; @@ -29,6 +29,8 @@ import { RangeHighlightDecorations } from 'vs/workbench/browser/parts/editor/ran import { DefaultSettingsHeaderWidget, EditPreferenceWidget, SettingsGroupTitleWidget, SettingsHeaderWidget } from 'vs/workbench/contrib/preferences/browser/preferencesWidgets'; import { IFilterResult, IPreferencesEditorModel, IPreferencesService, ISetting, ISettingsEditorModel, ISettingsGroup } from 'vs/workbench/services/preferences/common/preferences'; import { DefaultSettingsEditorModel, SettingsEditorModel, WorkspaceConfigurationEditorModel } from 'vs/workbench/services/preferences/common/preferencesModels'; +import { IMarkerService, IMarkerData, MarkerSeverity, MarkerTag } from 'vs/platform/markers/common/markers'; +import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; export interface IPreferencesRenderer extends IDisposable { readonly preferencesModel: IPreferencesEditorModel; @@ -65,6 +67,8 @@ export class UserSettingsRenderer extends Disposable implements IPreferencesRend private readonly _onUpdatePreference = this._register(new Emitter<{ key: string, value: any, source: IIndexedSetting }>()); readonly onUpdatePreference: Event<{ key: string, value: any, source: IIndexedSetting }> = this._onUpdatePreference.event; + private unsupportedSettingsRenderer: UnsupportedSettingsRenderer; + private filterResult: IFilterResult | undefined; constructor(protected editor: ICodeEditor, readonly preferencesModel: SettingsEditorModel, @@ -78,7 +82,7 @@ export class UserSettingsRenderer extends Disposable implements IPreferencesRend this.editSettingActionRenderer = this._register(this.instantiationService.createInstance(EditSettingRenderer, this.editor, this.preferencesModel, this.settingHighlighter)); this._register(this.editSettingActionRenderer.onUpdateSetting(({ key, value, source }) => this._updatePreference(key, value, source))); this._register(this.editor.getModel()!.onDidChangeContent(() => this.modelChangeDelayer.trigger(() => this.onModelChanged()))); - + this.unsupportedSettingsRenderer = this._register(instantiationService.createInstance(UnsupportedSettingsRenderer, editor, preferencesModel)); } getAssociatedPreferencesModel(): IPreferencesEditorModel { @@ -102,6 +106,7 @@ export class UserSettingsRenderer extends Disposable implements IPreferencesRend if (this.filterResult) { this.filterPreferences(this.filterResult); } + this.unsupportedSettingsRenderer.render(); } private _updatePreference(key: string, value: any, source: IIndexedSetting): void { @@ -946,6 +951,151 @@ class SettingHighlighter extends Disposable { } } +class UnsupportedSettingsRenderer extends Disposable { + + private renderingDelayer: Delayer = new Delayer(200); + + constructor( + private editor: ICodeEditor, + private settingsEditorModel: SettingsEditorModel, + @IMarkerService private markerService: IMarkerService, + @IWorkbenchEnvironmentService private workbenchEnvironmentService: IWorkbenchEnvironmentService, + ) { + super(); + this._register(this.editor.getModel()!.onDidChangeContent(() => this.renderingDelayer.trigger(() => this.render()))); + } + + public render(): void { + const markerData: IMarkerData[] = this.generateMarkerData(); + if (markerData.length) { + this.markerService.changeOne('preferencesEditor', this.settingsEditorModel.uri, markerData); + } else { + this.markerService.remove('preferencesEditor', [this.settingsEditorModel.uri]); + } + } + + private generateMarkerData(): IMarkerData[] { + const markerData: IMarkerData[] = []; + const configurationRegistry = Registry.as(ConfigurationExtensions.Configuration).getConfigurationProperties(); + for (const settingsGroup of this.settingsEditorModel.settingsGroups) { + for (const section of settingsGroup.sections) { + for (const setting of section.settings) { + const configuration = configurationRegistry[setting.key]; + if (configuration) { + switch (this.settingsEditorModel.configurationTarget) { + case ConfigurationTarget.USER_LOCAL: + this.handleLocalUserConfiguration(setting, configuration, markerData); + break; + case ConfigurationTarget.USER_REMOTE: + this.handleRemoteUserConfiguration(setting, configuration, markerData); + break; + case ConfigurationTarget.WORKSPACE: + this.handleWorkspaceConfiguration(setting, configuration, markerData); + break; + case ConfigurationTarget.WORKSPACE_FOLDER: + this.handleWorkspaceFolderConfiguration(setting, configuration, markerData); + break; + } + } else { + markerData.push({ + severity: MarkerSeverity.Hint, + tags: [MarkerTag.Unnecessary], + startLineNumber: setting.keyRange.startLineNumber, + startColumn: setting.keyRange.startColumn, + endLineNumber: setting.valueRange.endLineNumber, + endColumn: setting.valueRange.endColumn, + message: nls.localize('unknown configuration setting', "Unknown Configuration Setting") + }); + } + } + } + } + return markerData; + } + + private handleLocalUserConfiguration(setting: ISetting, configuration: IConfigurationNode, markerData: IMarkerData[]): void { + if (this.workbenchEnvironmentService.configuration.remote && configuration.scope === ConfigurationScope.MACHINE) { + markerData.push({ + severity: MarkerSeverity.Hint, + tags: [MarkerTag.Unnecessary], + startLineNumber: setting.keyRange.startLineNumber, + startColumn: setting.keyRange.startColumn, + endLineNumber: setting.valueRange.endLineNumber, + endColumn: setting.valueRange.endColumn, + message: nls.localize('unsupportedRemoteMachineSetting', "This setting can be applied only in remote machine settings") + }); + } + } + + private handleRemoteUserConfiguration(setting: ISetting, configuration: IConfigurationNode, markerData: IMarkerData[]): void { + if (configuration.scope === ConfigurationScope.APPLICATION) { + markerData.push(this.generateUnsupportedApplicationSettingMarker(setting)); + } + } + + private handleWorkspaceConfiguration(setting: ISetting, configuration: IConfigurationNode, markerData: IMarkerData[]): void { + if (configuration.scope === ConfigurationScope.APPLICATION) { + markerData.push(this.generateUnsupportedApplicationSettingMarker(setting)); + } + + if (configuration.scope === ConfigurationScope.MACHINE) { + markerData.push(this.generateUnsupportedMachineSettingMarker(setting)); + } + } + + private handleWorkspaceFolderConfiguration(setting: ISetting, configuration: IConfigurationNode, markerData: IMarkerData[]): void { + if (configuration.scope === ConfigurationScope.APPLICATION) { + markerData.push(this.generateUnsupportedApplicationSettingMarker(setting)); + } + + if (configuration.scope === ConfigurationScope.MACHINE) { + markerData.push(this.generateUnsupportedMachineSettingMarker(setting)); + } + + if (configuration.scope === ConfigurationScope.WINDOW) { + markerData.push({ + severity: MarkerSeverity.Hint, + tags: [MarkerTag.Unnecessary], + startLineNumber: setting.keyRange.startLineNumber, + startColumn: setting.keyRange.startColumn, + endLineNumber: setting.valueRange.endLineNumber, + endColumn: setting.valueRange.endColumn, + message: nls.localize('unsupportedWindowSetting', "This setting cannot be applied now. It will be applied when you open this folder directly.") + }); + } + } + + private generateUnsupportedApplicationSettingMarker(setting: ISetting): IMarkerData { + return { + severity: MarkerSeverity.Hint, + tags: [MarkerTag.Unnecessary], + startLineNumber: setting.keyRange.startLineNumber, + startColumn: setting.keyRange.startColumn, + endLineNumber: setting.keyRange.endLineNumber, + endColumn: setting.keyRange.endColumn, + message: nls.localize('unsupportedApplicationSetting', "This setting can be applied only in application user settings") + }; + } + + private generateUnsupportedMachineSettingMarker(setting: ISetting): IMarkerData { + return { + severity: MarkerSeverity.Hint, + tags: [MarkerTag.Unnecessary], + startLineNumber: setting.keyRange.startLineNumber, + startColumn: setting.keyRange.startColumn, + endLineNumber: setting.valueRange.endLineNumber, + endColumn: setting.valueRange.endColumn, + message: nls.localize('unsupportedMachineSetting', "This setting can be applied only in user settings") + }; + } + + public dispose(): void { + this.markerService.remove('preferencesEditor', [this.settingsEditorModel.uri]); + super.dispose(); + } + +} + class WorkspaceConfigurationRenderer extends Disposable { private decorationIds: string[] = []; diff --git a/src/vs/workbench/contrib/remote/electron-browser/remote.contribution.ts b/src/vs/workbench/contrib/remote/electron-browser/remote.contribution.ts index efdb9d2eb7..7554ecdd8a 100644 --- a/src/vs/workbench/contrib/remote/electron-browser/remote.contribution.ts +++ b/src/vs/workbench/contrib/remote/electron-browser/remote.contribution.ts @@ -24,13 +24,13 @@ import { IQuickInputService, IQuickPickItem, IQuickPickSeparator } from 'vs/plat import { ILogService } from 'vs/platform/log/common/log'; import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; import { DialogChannel } from 'vs/platform/dialogs/node/dialogIpc'; -import { DownloadServiceChannel } from 'vs/platform/download/node/downloadIpc'; +import { DownloadServiceChannel } from 'vs/platform/download/common/downloadIpc'; import { LogLevelSetterChannel } from 'vs/platform/log/common/logIpc'; import { ipcRenderer as ipc } from 'electron'; import { IDiagnosticInfoOptions, IRemoteDiagnosticInfo } from 'vs/platform/diagnostics/common/diagnosticsService'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { IProgressService, IProgress, IProgressStep, ProgressLocation } from 'vs/platform/progress/common/progress'; -import { PersistenConnectionEventType } from 'vs/platform/remote/common/remoteAgentConnection'; +import { PersistentConnectionEventType } from 'vs/platform/remote/common/remoteAgentConnection'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IConfigurationRegistry, Extensions as ConfigurationExtensions } from 'vs/platform/configuration/common/configurationRegistry'; import Severity from 'vs/base/common/severity'; @@ -38,6 +38,7 @@ import { ReloadWindowAction } from 'vs/workbench/browser/actions/windowActions'; import { IRemoteAuthorityResolverService } from 'vs/platform/remote/common/remoteAuthorityResolver'; import { IWindowsService } from 'vs/platform/windows/common/windows'; import { RemoteConnectionState } from 'vs/workbench/browser/contextkeys'; +import { IDownloadService } from 'vs/platform/download/common/download'; const WINDOW_ACTIONS_COMMAND_ID = 'remote.showActions'; const CLOSE_REMOTE_COMMAND_ID = 'remote.closeRemote'; @@ -100,13 +101,13 @@ export class RemoteWindowActiveIndicator extends Disposable implements IWorkbenc if (connection) { this._register(connection.onDidStateChange((e) => { switch (e.type) { - case PersistenConnectionEventType.ConnectionLost: - case PersistenConnectionEventType.ReconnectionPermanentFailure: - case PersistenConnectionEventType.ReconnectionRunning: - case PersistenConnectionEventType.ReconnectionWait: + case PersistentConnectionEventType.ConnectionLost: + case PersistentConnectionEventType.ReconnectionPermanentFailure: + case PersistentConnectionEventType.ReconnectionRunning: + case PersistentConnectionEventType.ReconnectionWait: this.setDisconnected(true); break; - case PersistenConnectionEventType.ConnectionGain: + case PersistentConnectionEventType.ConnectionGain: this.setDisconnected(false); break; } @@ -215,12 +216,13 @@ class RemoteChannelsContribution implements IWorkbenchContribution { constructor( @ILogService logService: ILogService, @IRemoteAgentService remoteAgentService: IRemoteAgentService, - @IDialogService dialogService: IDialogService + @IDialogService dialogService: IDialogService, + @IDownloadService downloadService: IDownloadService ) { const connection = remoteAgentService.getConnection(); if (connection) { connection.registerChannel('dialog', new DialogChannel(dialogService)); - connection.registerChannel('download', new DownloadServiceChannel()); + connection.registerChannel('download', new DownloadServiceChannel(downloadService)); connection.registerChannel('loglevel', new LogLevelSetterChannel(logService)); } } @@ -296,7 +298,7 @@ class RemoteAgentConnectionStatusListener implements IWorkbenchContribution { currentTimer = null; } switch (e.type) { - case PersistenConnectionEventType.ConnectionLost: + case PersistentConnectionEventType.ConnectionLost: if (!currentProgressPromiseResolve) { let promise = new Promise((resolve) => currentProgressPromiseResolve = resolve); progressService!.withProgress( @@ -313,13 +315,13 @@ class RemoteAgentConnectionStatusListener implements IWorkbenchContribution { progressReporter!.report(nls.localize('connectionLost', "Connection Lost")); break; - case PersistenConnectionEventType.ReconnectionWait: + case PersistentConnectionEventType.ReconnectionWait: currentTimer = new ReconnectionTimer(progressReporter!, Date.now() + 1000 * e.durationSeconds); break; - case PersistenConnectionEventType.ReconnectionRunning: + case PersistentConnectionEventType.ReconnectionRunning: progressReporter!.report(nls.localize('reconnectionRunning', "Attempting to reconnect...")); break; - case PersistenConnectionEventType.ReconnectionPermanentFailure: + case PersistentConnectionEventType.ReconnectionPermanentFailure: currentProgressPromiseResolve!(); currentProgressPromiseResolve = null; progressReporter = null; @@ -331,7 +333,7 @@ class RemoteAgentConnectionStatusListener implements IWorkbenchContribution { } }); break; - case PersistenConnectionEventType.ConnectionGain: + case PersistentConnectionEventType.ConnectionGain: currentProgressPromiseResolve!(); currentProgressPromiseResolve = null; progressReporter = null; diff --git a/src/vs/workbench/contrib/webview/browser/dynamicWebviewEditorOverlay.ts b/src/vs/workbench/contrib/webview/browser/dynamicWebviewEditorOverlay.ts new file mode 100644 index 0000000000..5b9498719a --- /dev/null +++ b/src/vs/workbench/contrib/webview/browser/dynamicWebviewEditorOverlay.ts @@ -0,0 +1,166 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Emitter, Event } from 'vs/base/common/event'; +import { Disposable, DisposableStore, MutableDisposable, toDisposable } from 'vs/base/common/lifecycle'; +import { URI } from 'vs/base/common/uri'; +import { IWebviewService, Webview, WebviewContentOptions, WebviewEditorOverlay, WebviewElement, WebviewOptions } from 'vs/workbench/contrib/webview/common/webview'; +import { IWorkbenchLayoutService, Parts } from 'vs/workbench/services/layout/browser/layoutService'; +import { memoize } from 'vs/base/common/decorators'; + +/** + * Webview editor overlay that creates and destroys the underlying webview as needed. + */ +export class DynamicWebviewEditorOverlay extends Disposable implements WebviewEditorOverlay { + + private readonly _pendingMessages = new Set(); + private readonly _webview = this._register(new MutableDisposable()); + private readonly _webviewEvents = this._register(new DisposableStore()); + + private _html: string = ''; + private _initialScrollProgress: number = 0; + private _state: string | undefined = undefined; + private _owner: any = undefined; + + public constructor( + private readonly id: string, + public readonly options: WebviewOptions, + private _contentOptions: WebviewContentOptions, + @IWorkbenchLayoutService private readonly _layoutService: IWorkbenchLayoutService, + @IWebviewService private readonly _webviewService: IWebviewService + ) { + super(); + + this._register(toDisposable(() => this.container.remove())); + } + + @memoize + public get container() { + const container = document.createElement('div'); + container.id = `webview-${this.id}`; + this._layoutService.getContainer(Parts.EDITOR_PART).appendChild(container); + return container; + } + + public claim(owner: any) { + this._owner = owner; + this.show(); + } + + public release(owner: any) { + if (this._owner !== owner) { + return; + } + this._owner = undefined; + this.container.style.visibility = 'hidden'; + if (!this.options.retainContextWhenHidden) { + this._webview.clear(); + this._webviewEvents.clear(); + } + } + + private show() { + if (!this._webview.value) { + const webview = this._webviewService.createWebview(this.id, this.options, this._contentOptions); + this._webview.value = webview; + webview.state = this._state; + webview.html = this._html; + if (this.options.tryRestoreScrollPosition) { + webview.initialScrollProgress = this._initialScrollProgress; + } + this._webview.value.mountTo(this.container); + this._webviewEvents.clear(); + + webview.onDidFocus(() => { this._onDidFocus.fire(); }, undefined, this._webviewEvents); + webview.onDidClickLink(x => { this._onDidClickLink.fire(x); }, undefined, this._webviewEvents); + webview.onMessage(x => { this._onMessage.fire(x); }, undefined, this._webviewEvents); + + webview.onDidScroll(x => { + this._initialScrollProgress = x.scrollYPercentage; + this._onDidScroll.fire(x); + }, undefined, this._webviewEvents); + + webview.onDidUpdateState(state => { + this._state = state; + this._onDidUpdateState.fire(state); + }, undefined, this._webviewEvents); + + this._pendingMessages.forEach(msg => webview.sendMessage(msg)); + this._pendingMessages.clear(); + } + this.container.style.visibility = 'visible'; + } + + public get html(): string { return this._html; } + public set html(value: string) { + this._html = value; + this.withWebview(webview => webview.html = value); + } + + public get initialScrollProgress(): number { return this._initialScrollProgress; } + public set initialScrollProgress(value: number) { + this._initialScrollProgress = value; + this.withWebview(webview => webview.initialScrollProgress = value); + } + + public get state(): string | undefined { return this._state; } + public set state(value: string | undefined) { + this._state = value; + this.withWebview(webview => webview.state = value); + } + + public get contentOptions(): WebviewContentOptions { return this._contentOptions; } + public set contentOptions(value: WebviewContentOptions) { + this._contentOptions = value; + this.withWebview(webview => webview.contentOptions = value); + } + + private readonly _onDidFocus = this._register(new Emitter()); + public readonly onDidFocus: Event = this._onDidFocus.event; + + private readonly _onDidClickLink = this._register(new Emitter()); + public readonly onDidClickLink: Event = this._onDidClickLink.event; + + private readonly _onDidScroll = this._register(new Emitter<{ scrollYPercentage: number; }>()); + public readonly onDidScroll: Event<{ scrollYPercentage: number; }> = this._onDidScroll.event; + + private readonly _onDidUpdateState = this._register(new Emitter()); + public readonly onDidUpdateState: Event = this._onDidUpdateState.event; + + private readonly _onMessage = this._register(new Emitter()); + public readonly onMessage: Event = this._onMessage.event; + + sendMessage(data: any): void { + if (this._webview.value) { + this._webview.value.sendMessage(data); + } else { + this._pendingMessages.add(data); + } + } + + update(html: string, options: WebviewContentOptions, retainContextWhenHidden: boolean): void { + this._contentOptions = options; + this._html = html; + this.withWebview(webview => { + webview.update(html, options, retainContextWhenHidden); + }); + } + + layout(): void { this.withWebview(webview => webview.layout()); } + focus(): void { this.withWebview(webview => webview.focus()); } + reload(): void { this.withWebview(webview => webview.reload()); } + showFind(): void { this.withWebview(webview => webview.showFind()); } + hideFind(): void { this.withWebview(webview => webview.hideFind()); } + + public getInnerWebview() { + return this._webview.value; + } + + private withWebview(f: (webview: Webview) => void): void { + if (this._webview.value) { + f(this._webview.value); + } + } +} diff --git a/src/vs/workbench/contrib/webview/browser/webviewElement.ts b/src/vs/workbench/contrib/webview/browser/webviewElement.ts index f8ee04030a..f54e53635d 100644 --- a/src/vs/workbench/contrib/webview/browser/webviewElement.ts +++ b/src/vs/workbench/contrib/webview/browser/webviewElement.ts @@ -45,7 +45,9 @@ export class IFrameWebview extends Disposable implements Webview { @IConfigurationService private readonly _configurationService: IConfigurationService, ) { super(); - if (typeof environmentService.webviewEndpoint !== 'string') { + const useExternalEndpoint = this._configurationService.getValue('webview.experimental.useExternalEndpoint'); + + if (typeof environmentService.webviewEndpoint !== 'string' && !useExternalEndpoint) { throw new Error('To use iframe based webviews, you must configure `environmentService.webviewEndpoint`'); } @@ -142,7 +144,9 @@ export class IFrameWebview extends Disposable implements Webview { } private get endpoint(): string { - const endpoint = this.environmentService.webviewEndpoint!.replace('{{uuid}}', this.id); + const useExternalEndpoint = this._configurationService.getValue('webview.experimental.useExternalEndpoint'); + const baseEndpoint = useExternalEndpoint ? 'https://{{uuid}}.vscode-webview-test.com/8fa811108f0f0524c473020ef57b6620f6c201e1' : this.environmentService.webviewEndpoint!; + const endpoint = baseEndpoint.replace('{{uuid}}', this.id); if (endpoint[endpoint.length - 1] === '/') { return endpoint.slice(0, endpoint.length - 1); } diff --git a/src/vs/workbench/contrib/webview/browser/webviewService.ts b/src/vs/workbench/contrib/webview/browser/webviewService.ts index 3ccda53900..4aa51c0116 100644 --- a/src/vs/workbench/contrib/webview/browser/webviewService.ts +++ b/src/vs/workbench/contrib/webview/browser/webviewService.ts @@ -3,14 +3,10 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Emitter, Event } from 'vs/base/common/event'; -import { Disposable, DisposableStore, MutableDisposable, toDisposable } from 'vs/base/common/lifecycle'; -import { URI } from 'vs/base/common/uri'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IFrameWebview } from 'vs/workbench/contrib/webview/browser/webviewElement'; -import { IWebviewService, Webview, WebviewContentOptions, WebviewEditorOverlay, WebviewElement, WebviewOptions } from 'vs/workbench/contrib/webview/common/webview'; -import { IWorkbenchLayoutService, Parts } from 'vs/workbench/services/layout/browser/layoutService'; -import { memoize } from 'vs/base/common/decorators'; +import { IWebviewService, WebviewContentOptions, WebviewEditorOverlay, WebviewElement, WebviewOptions } from 'vs/workbench/contrib/webview/common/webview'; +import { DynamicWebviewEditorOverlay } from './dynamicWebviewEditorOverlay'; export class WebviewService implements IWebviewService { _serviceBrand: any; @@ -35,166 +31,3 @@ export class WebviewService implements IWebviewService { return this._instantiationService.createInstance(DynamicWebviewEditorOverlay, id, options, contentOptions); } } - -/** - * Webview editor overlay that creates and destroys the underlying webview as needed. - */ -class DynamicWebviewEditorOverlay extends Disposable implements WebviewEditorOverlay { - - private readonly _pendingMessages = new Set(); - private readonly _webview = this._register(new MutableDisposable()); - private readonly _webviewEvents = this._register(new DisposableStore()); - - private _html: string = ''; - private _initialScrollProgress: number = 0; - private _state: string | undefined = undefined; - private _owner: any = undefined; - - public constructor( - private readonly id: string, - public readonly options: WebviewOptions, - private _contentOptions: WebviewContentOptions, - @IWorkbenchLayoutService private readonly _layoutService: IWorkbenchLayoutService, - @IWebviewService private readonly _webviewService: IWebviewService, - ) { - super(); - - this._register(toDisposable(() => this.container.remove())); - } - - @memoize - public get container() { - const container = document.createElement('div'); - container.id = `webview-${this.id}`; - this._layoutService.getContainer(Parts.EDITOR_PART).appendChild(container); - return container; - } - - public claim(owner: any) { - this._owner = owner; - this.show(); - } - - public release(owner: any) { - if (this._owner !== owner) { - return; - } - - this._owner = undefined; - this.container.style.visibility = 'hidden'; - if (!this.options.retainContextWhenHidden) { - this._webview.clear(); - this._webviewEvents.clear(); - } - } - - private show() { - if (!this._webview.value) { - const webview = this._webviewService.createWebview(this.id, this.options, this._contentOptions); - this._webview.value = webview; - webview.state = this._state; - webview.html = this._html; - - if (this.options.tryRestoreScrollPosition) { - webview.initialScrollProgress = this._initialScrollProgress; - } - - this._webview.value.mountTo(this.container); - - this._webviewEvents.clear(); - - webview.onDidFocus(() => { - this._onDidFocus.fire(); - }, undefined, this._webviewEvents); - - webview.onDidClickLink(x => { - this._onDidClickLink.fire(x); - }, undefined, this._webviewEvents); - - webview.onDidScroll(x => { - this._initialScrollProgress = x.scrollYPercentage; - this._onDidScroll.fire(x); - }, undefined, this._webviewEvents); - - webview.onDidUpdateState(state => { - this._state = state; - this._onDidUpdateState.fire(state); - }, undefined, this._webviewEvents); - - webview.onMessage(x => { - this._onMessage.fire(x); - }, undefined, this._webviewEvents); - - this._pendingMessages.forEach(msg => webview.sendMessage(msg)); - this._pendingMessages.clear(); - } - this.container.style.visibility = 'visible'; - } - - public get html(): string { return this._html; } - public set html(value: string) { - this._html = value; - this.withWebview(webview => webview.html = value); - } - - public get initialScrollProgress(): number { return this._initialScrollProgress; } - public set initialScrollProgress(value: number) { - this._initialScrollProgress = value; - this.withWebview(webview => webview.initialScrollProgress = value); - } - - public get state(): string | undefined { return this._state; } - public set state(value: string | undefined) { - this._state = value; - this.withWebview(webview => webview.state = value); - } - - public get contentOptions(): WebviewContentOptions { return this._contentOptions; } - public set contentOptions(value: WebviewContentOptions) { - this._contentOptions = value; - this.withWebview(webview => webview.contentOptions = value); - } - - private readonly _onDidFocus = this._register(new Emitter()); - public readonly onDidFocus: Event = this._onDidFocus.event; - - private readonly _onDidClickLink = this._register(new Emitter()); - public readonly onDidClickLink: Event = this._onDidClickLink.event; - - private readonly _onDidScroll = this._register(new Emitter<{ scrollYPercentage: number; }>()); - public readonly onDidScroll: Event<{ scrollYPercentage: number; }> = this._onDidScroll.event; - - private readonly _onDidUpdateState = this._register(new Emitter()); - public readonly onDidUpdateState: Event = this._onDidUpdateState.event; - - private readonly _onMessage = this._register(new Emitter()); - public readonly onMessage: Event = this._onMessage.event; - - sendMessage(data: any): void { - if (this._webview.value) { - this._webview.value.sendMessage(data); - } else { - this._pendingMessages.add(data); - } - } - - update(html: string, options: WebviewContentOptions, retainContextWhenHidden: boolean): void { - this._contentOptions = options; - this._html = html; - this.withWebview(webview => { - webview.update(html, options, retainContextWhenHidden); - }); - } - - layout(): void { this.withWebview(webview => webview.layout()); } - focus(): void { this.withWebview(webview => webview.focus()); } - reload(): void { this.withWebview(webview => webview.reload()); } - showFind(): void { this.withWebview(webview => webview.showFind()); } - hideFind(): void { this.withWebview(webview => webview.hideFind()); } - - private withWebview(f: (webview: Webview) => void): void { - if (this._webview.value) { - f(this._webview.value); - } - } -} \ No newline at end of file diff --git a/src/vs/workbench/contrib/webview/common/webview.ts b/src/vs/workbench/contrib/webview/common/webview.ts index 7616ceab2a..2b584fd092 100644 --- a/src/vs/workbench/contrib/webview/common/webview.ts +++ b/src/vs/workbench/contrib/webview/common/webview.ts @@ -97,6 +97,8 @@ export interface WebviewEditorOverlay extends Webview { claim(owner: any): void; release(owner: any): void; + + getInnerWebview(): Webview | undefined; } export const webviewDeveloperCategory = nls.localize('developer', "Developer"); diff --git a/src/vs/workbench/contrib/webview/electron-browser/webview.contribution.ts b/src/vs/workbench/contrib/webview/electron-browser/webview.contribution.ts index 1b1d9eebf2..19a80db001 100644 --- a/src/vs/workbench/contrib/webview/electron-browser/webview.contribution.ts +++ b/src/vs/workbench/contrib/webview/electron-browser/webview.contribution.ts @@ -15,9 +15,9 @@ import { Extensions as ActionExtensions, IWorkbenchActionRegistry } from 'vs/wor import { WebviewEditor } from 'vs/workbench/contrib/webview/browser/webviewEditor'; import { IWebviewService, webviewDeveloperCategory } from 'vs/workbench/contrib/webview/common/webview'; import * as webviewCommands from 'vs/workbench/contrib/webview/electron-browser/webviewCommands'; -import { WebviewService } from 'vs/workbench/contrib/webview/electron-browser/webviewService'; +import { ElectronWebviewService } from 'vs/workbench/contrib/webview/electron-browser/webviewService'; -registerSingleton(IWebviewService, WebviewService, true); +registerSingleton(IWebviewService, ElectronWebviewService, true); const actionRegistry = Registry.as(ActionExtensions.WorkbenchActions); diff --git a/src/vs/workbench/contrib/webview/electron-browser/webviewCommands.ts b/src/vs/workbench/contrib/webview/electron-browser/webviewCommands.ts index 3456f99e02..e8c55b2e3a 100644 --- a/src/vs/workbench/contrib/webview/electron-browser/webviewCommands.ts +++ b/src/vs/workbench/contrib/webview/electron-browser/webviewCommands.ts @@ -9,6 +9,7 @@ import { Command, ServicesAccessor } from 'vs/editor/browser/editorExtensions'; import { WebviewEditor } from 'vs/workbench/contrib/webview/browser/webviewEditor'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { ElectronWebviewBasedWebview } from 'vs/workbench/contrib/webview/electron-browser/webviewElement'; +import { WebviewEditorOverlay } from 'vs/workbench/contrib/webview/common/webview'; export class OpenWebviewDeveloperToolsAction extends Action { static readonly ID = 'workbench.action.webview.openDeveloperTools'; @@ -92,6 +93,11 @@ function withActiveWebviewBasedWebview(accessor: ServicesAccessor, f: (webview: webViewEditor.withWebview(webview => { if (webview instanceof ElectronWebviewBasedWebview) { f(webview); + } else if ((webview as WebviewEditorOverlay).getInnerWebview) { + const innerWebview = (webview as WebviewEditorOverlay).getInnerWebview(); + if (innerWebview instanceof ElectronWebviewBasedWebview) { + f(innerWebview); + } } }); } diff --git a/src/vs/workbench/contrib/webview/electron-browser/webviewService.ts b/src/vs/workbench/contrib/webview/electron-browser/webviewService.ts index 53a7098aff..f22a28a23a 100644 --- a/src/vs/workbench/contrib/webview/electron-browser/webviewService.ts +++ b/src/vs/workbench/contrib/webview/electron-browser/webviewService.ts @@ -3,25 +3,39 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { WebviewService as BrowserWebviewService } from 'vs/workbench/contrib/webview/browser/webviewService'; -import { IWebviewService, WebviewContentOptions, WebviewElement, WebviewOptions } from 'vs/workbench/contrib/webview/common/webview'; +import { DynamicWebviewEditorOverlay } from 'vs/workbench/contrib/webview/browser/dynamicWebviewEditorOverlay'; +import { IFrameWebview } from 'vs/workbench/contrib/webview/browser/webviewElement'; +import { IWebviewService, WebviewContentOptions, WebviewEditorOverlay, WebviewElement, WebviewOptions } from 'vs/workbench/contrib/webview/common/webview'; import { ElectronWebviewBasedWebview } from 'vs/workbench/contrib/webview/electron-browser/webviewElement'; -export class WebviewService extends BrowserWebviewService implements IWebviewService { +export class ElectronWebviewService implements IWebviewService { _serviceBrand: any; constructor( - @IInstantiationService private readonly instantiationService: IInstantiationService, - ) { - super(instantiationService); - } + @IInstantiationService private readonly _instantiationService: IInstantiationService, + @IConfigurationService private readonly _configService: IConfigurationService, + ) { } createWebview( - _id: string, + id: string, options: WebviewOptions, contentOptions: WebviewContentOptions ): WebviewElement { - return this.instantiationService.createInstance(ElectronWebviewBasedWebview, options, contentOptions); + const useExternalEndpoint = this._configService.getValue('webview.experimental.useExternalEndpoint'); + if (useExternalEndpoint) { + return this._instantiationService.createInstance(IFrameWebview, id, options, contentOptions); + } else { + return this._instantiationService.createInstance(ElectronWebviewBasedWebview, options, contentOptions); + } + } + + createWebviewEditorOverlay( + id: string, + options: WebviewOptions, + contentOptions: WebviewContentOptions, + ): WebviewEditorOverlay { + return this._instantiationService.createInstance(DynamicWebviewEditorOverlay, id, options, contentOptions); } } \ No newline at end of file diff --git a/src/vs/workbench/services/configuration/browser/configurationService.ts b/src/vs/workbench/services/configuration/browser/configurationService.ts index f74b2766b1..0d898eee98 100644 --- a/src/vs/workbench/services/configuration/browser/configurationService.ts +++ b/src/vs/workbench/services/configuration/browser/configurationService.ts @@ -23,7 +23,6 @@ import { ConfigurationEditingService, EditableConfigurationTarget } from 'vs/wor import { WorkspaceConfiguration, FolderConfiguration, RemoteUserConfiguration, UserConfiguration } from 'vs/workbench/services/configuration/browser/configuration'; import { JSONEditingService } from 'vs/workbench/services/configuration/common/jsonEditingService'; import { IJSONSchema, IJSONSchemaMap } from 'vs/base/common/jsonSchema'; -import { localize } from 'vs/nls'; import { isEqual, dirname } from 'vs/base/common/resources'; import { mark } from 'vs/base/common/performance'; import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService'; @@ -507,29 +506,29 @@ export class WorkspaceService extends Disposable implements IConfigurationServic private registerConfigurationSchemas(): void { if (this.workspace) { const jsonRegistry = Registry.as(JSONExtensions.JSONContribution); - const convertToNotSuggestedProperties = (properties: IJSONSchemaMap, errorMessage: string): IJSONSchemaMap => { + const convertToNotSuggestedProperties = (properties: IJSONSchemaMap): IJSONSchemaMap => { return Object.keys(properties).reduce((result: IJSONSchemaMap, property) => { result[property] = deepClone(properties[property]); - result[property].deprecationMessage = errorMessage; + result[property].doNotSuggest = true; return result; }, {}); }; - const unsupportedApplicationSettings = convertToNotSuggestedProperties(applicationSettings.properties, localize('unsupportedApplicationSetting', "This setting can be applied only in application user settings")); - const unsupportedMachineSettings = convertToNotSuggestedProperties(machineSettings.properties, localize('unsupportedMachineSetting', "This setting can be applied only in user settings")); - const unsupportedRemoteMachineSettings = convertToNotSuggestedProperties(machineSettings.properties, localize('unsupportedRemoteMachineSetting', "This setting can be applied only in remote machine settings")); - const allSettingsSchema: IJSONSchema = { properties: allSettings.properties, patternProperties: allSettings.patternProperties, additionalProperties: false, errorMessage: 'Unknown configuration setting' }; - const userSettingsSchema: IJSONSchema = this.remoteUserConfiguration ? { properties: { ...applicationSettings.properties, ...unsupportedRemoteMachineSettings, ...windowSettings.properties, ...resourceSettings.properties }, patternProperties: allSettings.patternProperties, additionalProperties: false, errorMessage: 'Unknown configuration setting' } : allSettingsSchema; - const machineSettingsSchema: IJSONSchema = { properties: { ...unsupportedApplicationSettings, ...windowSettings.properties, ...resourceSettings.properties }, patternProperties: allSettings.patternProperties, additionalProperties: false, errorMessage: 'Unknown configuration setting' }; - const workspaceSettingsSchema: IJSONSchema = { properties: { ...unsupportedApplicationSettings, ...unsupportedMachineSettings, ...windowSettings.properties, ...resourceSettings.properties }, patternProperties: allSettings.patternProperties, additionalProperties: false, errorMessage: 'Unknown configuration setting' }; + const unsupportedApplicationSettings = convertToNotSuggestedProperties(applicationSettings.properties); + const unsupportedMachineSettings = convertToNotSuggestedProperties(machineSettings.properties); + const unsupportedRemoteMachineSettings = convertToNotSuggestedProperties(machineSettings.properties); + const allSettingsSchema: IJSONSchema = { properties: allSettings.properties, patternProperties: allSettings.patternProperties, additionalProperties: true }; + const userSettingsSchema: IJSONSchema = this.remoteUserConfiguration ? { properties: { ...applicationSettings.properties, ...unsupportedRemoteMachineSettings, ...windowSettings.properties, ...resourceSettings.properties }, patternProperties: allSettings.patternProperties, additionalProperties: true } : allSettingsSchema; + const machineSettingsSchema: IJSONSchema = { properties: { ...unsupportedApplicationSettings, ...windowSettings.properties, ...resourceSettings.properties }, patternProperties: allSettings.patternProperties, additionalProperties: true }; + const workspaceSettingsSchema: IJSONSchema = { properties: { ...unsupportedApplicationSettings, ...unsupportedMachineSettings, ...windowSettings.properties, ...resourceSettings.properties }, patternProperties: allSettings.patternProperties, additionalProperties: true }; jsonRegistry.registerSchema(defaultSettingsSchemaId, allSettingsSchema); jsonRegistry.registerSchema(userSettingsSchemaId, userSettingsSchema); jsonRegistry.registerSchema(machineSettingsSchemaId, machineSettingsSchema); if (WorkbenchState.WORKSPACE === this.getWorkbenchState()) { - const unsupportedWindowSettings = convertToNotSuggestedProperties(windowSettings.properties, localize('unsupportedWindowSetting', "This setting cannot be applied now. It will be applied when you open this folder directly.")); - const folderSettingsSchema: IJSONSchema = { properties: { ...unsupportedApplicationSettings, ...unsupportedMachineSettings, ...unsupportedWindowSettings, ...resourceSettings.properties }, patternProperties: allSettings.patternProperties, additionalProperties: false, errorMessage: 'Unknown configuration setting' }; + const unsupportedWindowSettings = convertToNotSuggestedProperties(windowSettings.properties); + const folderSettingsSchema: IJSONSchema = { properties: { ...unsupportedApplicationSettings, ...unsupportedMachineSettings, ...unsupportedWindowSettings, ...resourceSettings.properties }, patternProperties: allSettings.patternProperties, additionalProperties: true }; jsonRegistry.registerSchema(workspaceSettingsSchemaId, workspaceSettingsSchema); jsonRegistry.registerSchema(folderSettingsSchemaId, folderSettingsSchema); } else { diff --git a/src/vs/workbench/services/configuration/test/electron-browser/configurationService.test.ts b/src/vs/workbench/services/configuration/test/electron-browser/configurationService.test.ts index 9f2ea8b1dc..bd644021b1 100644 --- a/src/vs/workbench/services/configuration/test/electron-browser/configurationService.test.ts +++ b/src/vs/workbench/services/configuration/test/electron-browser/configurationService.test.ts @@ -42,7 +42,6 @@ import { DiskFileSystemProvider } from 'vs/platform/files/node/diskFileSystemPro import { ConfigurationCache } from 'vs/workbench/services/configuration/node/configurationCache'; import { IRemoteAgentEnvironment } from 'vs/platform/remote/common/remoteAgentEnvironment'; import { IConfigurationCache } from 'vs/workbench/services/configuration/common/configuration'; -// import { VSBuffer } from 'vs/base/common/buffer'; import { SignService } from 'vs/platform/sign/browser/signService'; import { FileUserDataProvider } from 'vs/workbench/services/userData/common/fileUserDataProvider'; import { IKeybindingEditingService, KeybindingsEditingService } from 'vs/workbench/services/keybinding/common/keybindingEditing'; @@ -276,7 +275,7 @@ suite('WorkspaceConfigurationService - Remote Folder', () => { // } // }); // }); - // await instantiationService.get(IFileService).writeFile(URI.file(remoteSettingsFile), VSBuffer.fromString('{ "configurationService.remote.machineSetting": "remoteValue" }')); + // fs.writeFileSync(remoteSettingsFile, '{ "configurationService.remote.machineSetting": "remoteValue" }'); // return promise; // }); diff --git a/src/vs/workbench/services/decorations/browser/decorationsService.ts b/src/vs/workbench/services/decorations/browser/decorationsService.ts index 45fc694a4e..c3c233a680 100644 --- a/src/vs/workbench/services/decorations/browser/decorationsService.ts +++ b/src/vs/workbench/services/decorations/browser/decorationsService.ts @@ -7,7 +7,7 @@ import { URI } from 'vs/base/common/uri'; import { Event, Emitter } from 'vs/base/common/event'; import { IDecorationsService, IDecoration, IResourceDecorationChangeEvent, IDecorationsProvider, IDecorationData } from './decorations'; import { TernarySearchTree } from 'vs/base/common/map'; -import { Disposable, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; +import { Disposable, IDisposable, toDisposable, dispose } from 'vs/base/common/lifecycle'; import { isThenable } from 'vs/base/common/async'; import { LinkedList } from 'vs/base/common/linkedList'; import { createStyleSheet, createCSSRule, removeCSSRulesContainingSelector } from 'vs/base/browser/dom'; @@ -334,14 +334,15 @@ class DecorationProviderWrapper { } } -export class FileDecorationsService extends Disposable implements IDecorationsService { +export class FileDecorationsService implements IDecorationsService { _serviceBrand: any; private readonly _data = new LinkedList(); - private readonly _onDidChangeDecorationsDelayed = this._register(new Emitter()); - private readonly _onDidChangeDecorations = this._register(new Emitter()); + private readonly _onDidChangeDecorationsDelayed = new Emitter(); + private readonly _onDidChangeDecorations = new Emitter(); private readonly _decorationStyles: DecorationStyles; + private readonly _disposables: IDisposable[]; readonly onDidChangeDecorations: Event = Event.any( this._onDidChangeDecorations.event, @@ -355,17 +356,27 @@ export class FileDecorationsService extends Disposable implements IDecorationsSe constructor( @IThemeService themeService: IThemeService ) { - super(); - this._decorationStyles = this._register(new DecorationStyles(themeService)); + this._decorationStyles = new DecorationStyles(themeService); // every so many events we check if there are // css styles that we don't need anymore let count = 0; - this._register(this.onDidChangeDecorations(() => { + let reg = this.onDidChangeDecorations(() => { if (++count % 17 === 0) { this._decorationStyles.cleanUp(this._data.iterator()); } - })); + }); + + this._disposables = [ + reg, + this._decorationStyles + ]; + } + + dispose(): void { + dispose(this._disposables); + dispose(this._onDidChangeDecorations); + dispose(this._onDidChangeDecorationsDelayed); } registerDecorationsProvider(provider: IDecorationsProvider): IDisposable { diff --git a/src/vs/workbench/services/extensions/common/remoteExtensionHostClient.ts b/src/vs/workbench/services/extensions/common/remoteExtensionHostClient.ts index 434c78f21f..f4f7afe4e8 100644 --- a/src/vs/workbench/services/extensions/common/remoteExtensionHostClient.ts +++ b/src/vs/workbench/services/extensions/common/remoteExtensionHostClient.ts @@ -162,6 +162,11 @@ export class RemoteExtensionHostClient extends Disposable implements IExtensionH } private _onExtHostConnectionLost(): void { + + if (this._isExtensionDevHost && this._environmentService.debugExtensionHost.debugId) { + this._extensionHostDebugService.close(this._environmentService.debugExtensionHost.debugId); + } + if (this._terminating) { // Expected termination path (we asked the process to terminate) return; diff --git a/src/vs/workbench/services/extensions/electron-browser/cachedExtensionScanner.ts b/src/vs/workbench/services/extensions/electron-browser/cachedExtensionScanner.ts index da42951505..74d7f936f4 100644 --- a/src/vs/workbench/services/extensions/electron-browser/cachedExtensionScanner.ts +++ b/src/vs/workbench/services/extensions/electron-browser/cachedExtensionScanner.ts @@ -101,10 +101,6 @@ export class CachedExtensionScanner { development.forEach(developedExtension => { log.info('', nls.localize('extensionUnderDevelopment', "Loading development extension at {0}", developedExtension.extensionLocation.fsPath)); const extensionKey = ExtensionIdentifier.toKey(developedExtension.identifier); - const extension = result.get(extensionKey); - if (extension) { - log.warn(developedExtension.extensionLocation.fsPath, nls.localize('overwritingExtension', "Overwriting extension {0} with {1}.", extension.extensionLocation.fsPath, developedExtension.extensionLocation.fsPath)); - } result.set(extensionKey, developedExtension); }); let r: IExtensionDescription[] = []; diff --git a/src/vs/workbench/services/extensions/electron-browser/extensionService.ts b/src/vs/workbench/services/extensions/electron-browser/extensionService.ts index 87cd720218..68ed608a32 100644 --- a/src/vs/workbench/services/extensions/electron-browser/extensionService.ts +++ b/src/vs/workbench/services/extensions/electron-browser/extensionService.ts @@ -32,7 +32,7 @@ import { ExtensionHostProcessManager } from 'vs/workbench/services/extensions/co import { ExtensionIdentifier, IExtension, ExtensionType, IExtensionDescription } from 'vs/platform/extensions/common/extensions'; import { Schemas } from 'vs/base/common/network'; import { IFileService } from 'vs/platform/files/common/files'; -import { PersistenConnectionEventType } from 'vs/platform/remote/common/remoteAgentConnection'; +import { PersistentConnectionEventType } from 'vs/platform/remote/common/remoteAgentConnection'; import { IProductService } from 'vs/platform/product/common/product'; import { Logger } from 'vs/workbench/services/extensions/common/extensionPoints'; @@ -478,7 +478,7 @@ export class ExtensionService extends AbstractExtensionService implements IExten if (!remoteAuthority) { return; } - if (e.type === PersistenConnectionEventType.ConnectionLost) { + if (e.type === PersistentConnectionEventType.ConnectionLost) { this._remoteAuthorityResolverService.clearResolvedAuthority(remoteAuthority); } }); diff --git a/src/vs/workbench/workbench.main.ts b/src/vs/workbench/workbench.main.ts index 55aee3699b..f0492d7f94 100644 --- a/src/vs/workbench/workbench.main.ts +++ b/src/vs/workbench/workbench.main.ts @@ -54,7 +54,7 @@ import { IMarkerDecorationsService } from 'vs/editor/common/services/markersDeco import { IMarkerService } from 'vs/platform/markers/common/markers'; import { MarkerService } from 'vs/platform/markers/common/markerService'; import { IDownloadService } from 'vs/platform/download/common/download'; -import { DownloadService } from 'vs/platform/download/node/downloadService'; +import { DownloadService } from 'vs/platform/download/common/downloadService'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; import { ClipboardService } from 'vs/platform/clipboard/electron-browser/clipboardService'; import { ContextKeyService } from 'vs/platform/contextkey/browser/contextKeyService'; diff --git a/src/vs/workbench/workbench.web.main.ts b/src/vs/workbench/workbench.web.main.ts index 04f25a0234..ed6445c33c 100644 --- a/src/vs/workbench/workbench.web.main.ts +++ b/src/vs/workbench/workbench.web.main.ts @@ -51,8 +51,8 @@ import { MarkerDecorationsService } from 'vs/editor/common/services/markerDecora import { IMarkerDecorationsService } from 'vs/editor/common/services/markersDecorationService'; import { IMarkerService } from 'vs/platform/markers/common/markers'; import { MarkerService } from 'vs/platform/markers/common/markerService'; -// import { IDownloadService } from 'vs/platform/download/common/download'; -// import { DownloadService } from 'vs/platform/download/node/downloadService'; +import { IDownloadService } from 'vs/platform/download/common/download'; +import { DownloadService } from 'vs/platform/download/common/downloadService'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; import { BrowserClipboardService } from 'vs/platform/clipboard/browser/clipboardService'; import { ContextKeyService } from 'vs/platform/contextkey/browser/contextKeyService'; @@ -145,7 +145,7 @@ registerSingleton(IOpenerService, OpenerService, true); registerSingleton(IEditorWorkerService, EditorWorkerServiceImpl); registerSingleton(IMarkerDecorationsService, MarkerDecorationsService); registerSingleton(IMarkerService, MarkerService, true); -// registerSingleton(IDownloadService, DownloadService, true); +registerSingleton(IDownloadService, DownloadService, true); registerSingleton(IClipboardService, BrowserClipboardService, true); registerSingleton(IContextKeyService, ContextKeyService); registerSingleton(IModelService, ModelServiceImpl, true);