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 @@
+
\ 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