Merge from vscode e1d3dd53d17fb1529a002e4d6fb066db0a0bd385 (#6460)

* Merge from vscode e1d3dd53d17fb1529a002e4d6fb066db0a0bd385

* fix servers icon

* fix tests
This commit is contained in:
Anthony Dresser
2019-07-22 18:28:21 -07:00
committed by GitHub
parent f2afacd8b2
commit 15fc7a077a
91 changed files with 2562 additions and 972 deletions
@@ -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';
@@ -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;
}
@@ -0,0 +1 @@
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><defs><style>.cls-1{fill:#fff;}</style></defs><title>server_inverse_16x16</title><path class="cls-1" d="M2.73,0V16H13.26V0Zm1,1h8.53v9H3.73ZM12.26,15H3.73V11h8.53Z"/><path class="cls-1" d="M7.21,4.23H4.75V1.77H7.21Zm-2-.5H6.71V2.27H5.25Z"/></svg>

After

Width:  |  Height:  |  Size: 339 B

+3 -2
View File
@@ -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}`;
(<CSSStyleSheet>style.sheet).insertRule(selector + '{' + cssText + '}', 0);
}
export function removeCSSRulesContainingSelector(ruleName: string, style: HTMLStyleElement = getSharedStyleSheet()): void {
+16
View File
@@ -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;
}
+40 -44
View File
@@ -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;
}
}
@@ -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<T> extends ITreeElement<T> {
readonly children?: Iterator<ICompressedTreeElement<T>> | ICompressedTreeElement<T>[];
readonly incompressible?: boolean;
}
export interface ICompressedTreeNode<T> {
readonly elements: T[];
readonly incompressible: boolean;
}
export function compress<T>(element: ICompressedTreeElement<T>): ITreeElement<ICompressedTreeNode<T>> {
const elements = [element.element];
const incompressible = element.incompressible || false;
let childrenIterator: Iterator<ITreeElement<T>>;
let children: ITreeElement<T>[];
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<T>(element: ITreeElement<ICompressedTreeNode<T>>, index = 0): ICompressedTreeElement<T> {
let children: Iterator<ICompressedTreeElement<T>>;
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<T>(element: ITreeElement<ICompressedTreeNode<T>>): ICompressedTreeElement<T> {
return _decompress(element, 0);
}
export function splice<T>(treeElement: ICompressedTreeElement<T>, element: T, children: Iterator<ICompressedTreeElement<T>>): ICompressedTreeElement<T> {
if (treeElement.element === element) {
return { element, children };
}
return {
...treeElement,
children: Iterator.map(Iterator.from(treeElement.children), e => splice(e, element, children))
};
}
export interface ICompressedObjectTreeModelOptions<T, TFilterData> extends IObjectTreeModelOptions<ICompressedTreeNode<T>, TFilterData> { }
export class CompressedObjectTreeModel<T extends NonNullable<any>, TFilterData extends NonNullable<any> = void> implements ITreeModel<ICompressedTreeNode<T> | null, TFilterData, T | null> {
readonly rootRef = null;
get onDidSplice(): Event<ITreeModelSpliceEvent<ICompressedTreeNode<T> | null, TFilterData>> { return this.model.onDidSplice; }
get onDidChangeCollapseState(): Event<ICollapseStateChangeEvent<ICompressedTreeNode<T>, TFilterData>> { return this.model.onDidChangeCollapseState; }
get onDidChangeRenderNodeCount(): Event<ITreeNode<ICompressedTreeNode<T>, TFilterData>> { return this.model.onDidChangeRenderNodeCount; }
private model: ObjectTreeModel<ICompressedTreeNode<T>, TFilterData>;
private nodes = new Map<T | null, ICompressedTreeNode<T>>();
get size(): number { return this.nodes.size; }
constructor(list: ISpliceable<ITreeNode<ICompressedTreeNode<T>, TFilterData>>, options: ICompressedObjectTreeModelOptions<T, TFilterData> = {}) {
this.model = new ObjectTreeModel(list, options);
}
setChildren(
element: T | null,
children: ISequence<ICompressedTreeElement<T>> | undefined,
onDidCreateNode?: (node: ITreeNode<ICompressedTreeNode<T>, TFilterData>) => void,
onDidDeleteNode?: (node: ITreeNode<ICompressedTreeNode<T>, TFilterData>) => void
): Iterator<ITreeElement<T | null>> {
const insertedElements = new Set<T | null>();
const _onDidCreateNode = (node: ITreeNode<ICompressedTreeNode<T>, 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<ICompressedTreeNode<T>, 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<ICompressedTreeNode<T>, 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<ICompressedTreeNode<T> | 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<ICompressedTreeNode<T>, 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<T> | null {
const compressedNode = this.getCompressedNode(location);
return this.model.getParentElement(compressedNode);
}
getFirstElementChild(location: T | null): ICompressedTreeNode<T> | null | undefined {
const compressedNode = this.getCompressedNode(location);
return this.model.getFirstElementChild(compressedNode);
}
getLastElementAncestor(location?: T | null | undefined): ICompressedTreeNode<T> | 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<T> | 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<T> = (elements: T[]) => T;
export const DefaultElementMapper: ElementMapper<any> = elements => elements[elements.length - 1];
export type NodeMapper<T, TFilterData> = (node: ITreeNode<ICompressedTreeNode<T> | null, TFilterData>) => ITreeNode<T | null, TFilterData>;
function mapNode<T, TFilterData>(elementMapper: ElementMapper<T>, node: ITreeNode<ICompressedTreeNode<T> | null, TFilterData>): ITreeNode<T | null, TFilterData> {
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<T, TFilterData>(elementMapper: ElementMapper<T>): NodeMapper<T, TFilterData> {
return node => mapNode(elementMapper, node);
}
export interface ILinearCompressedObjectTreeModelOptions<T, TFilterData> extends ICompressedObjectTreeModelOptions<T, TFilterData> {
readonly elementMapper?: ElementMapper<T>;
}
export class LinearCompressedObjectTreeModel<T extends NonNullable<any>, TFilterData extends NonNullable<any> = void> implements ITreeModel<T | null, TFilterData, T | null> {
readonly rootRef = null;
get onDidSplice(): Event<ITreeModelSpliceEvent<T | null, TFilterData>> {
return Event.map(this.model.onDidSplice, ({ insertedNodes, deletedNodes }) => ({
insertedNodes: insertedNodes.map(this.mapNode),
deletedNodes: deletedNodes.map(this.mapNode),
}));
}
get onDidChangeCollapseState(): Event<ICollapseStateChangeEvent<T | null, TFilterData>> {
return Event.map(this.model.onDidChangeCollapseState, ({ node, deep }) => ({
node: this.mapNode(node),
deep
}));
}
get onDidChangeRenderNodeCount(): Event<ITreeNode<T | null, TFilterData>> {
return Event.map(this.model.onDidChangeRenderNodeCount, this.mapNode);
}
private mapElement: ElementMapper<T | null>;
private mapNode: NodeMapper<T | null, TFilterData>;
private model: CompressedObjectTreeModel<T, TFilterData>;
constructor(
list: ISpliceable<ITreeNode<ICompressedTreeNode<T>, TFilterData>>,
options: ILinearCompressedObjectTreeModelOptions<T, TFilterData> = {}
) {
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<T | null, any> {
return this.mapNode(this.model.getNode(location));
}
getNodeLocation(node: ITreeNode<T | null, any>): 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();
}
}
+16 -12
View File
@@ -53,7 +53,7 @@ export class ObjectTreeModel<T extends NonNullable<any>, TFilterData extends Non
children: ISequence<ITreeElement<T>> | undefined,
onDidCreateNode?: (node: ITreeNode<T, TFilterData>) => void,
onDidDeleteNode?: (node: ITreeNode<T, TFilterData>) => void
): Iterator<ITreeElement<T | null>> {
): Iterator<ITreeElement<T>> {
const location = this.getElementLocation(element);
return this._setChildren(location, this.preserveCollapseState(children), onDidCreateNode, onDidDeleteNode);
}
@@ -63,7 +63,7 @@ export class ObjectTreeModel<T extends NonNullable<any>, TFilterData extends Non
children: ISequence<ITreeElement<T>> | undefined,
onDidCreateNode?: (node: ITreeNode<T, TFilterData>) => void,
onDidDeleteNode?: (node: ITreeNode<T, TFilterData>) => void
): Iterator<ITreeElement<T | null>> {
): Iterator<ITreeElement<T>> {
const insertedElements = new Set<T | null>();
const insertedElementIds = new Set<string>();
@@ -107,7 +107,7 @@ export class ObjectTreeModel<T extends NonNullable<any>, TFilterData extends Non
_onDidDeleteNode
);
return result;
return result as Iterator<ITreeElement<T>>;
}
private preserveCollapseState(elements: ISequence<ITreeElement<T>> | undefined): ISequence<ITreeElement<T>> {
@@ -144,7 +144,7 @@ export class ObjectTreeModel<T extends NonNullable<any>, 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<T extends NonNullable<any>, 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<T extends NonNullable<any>, TFilterData extends Non
return node;
}
getNodeLocation(node: ITreeNode<T, TFilterData>): T {
getNodeLocation(node: ITreeNode<T, TFilterData>): 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) {
+1
View File
@@ -124,6 +124,7 @@ export interface ITreeModel<T, TFilterData, TRef> {
setCollapsed(location: TRef, collapsed?: boolean, recursive?: boolean): boolean;
expandTo(location: TRef): void;
rerender(location: TRef): void;
refilter(): void;
}
+53 -2
View File
@@ -29,6 +29,21 @@ export module Iterator {
return _empty;
}
export function single<T>(value: T): Iterator<T> {
let done = false;
return {
next(): IteratorResult<T> {
if (done) {
return FIN;
}
done = true;
return { done: false, value };
}
};
}
export function fromArray<T>(array: T[], index = 0, length = array.length): Iterator<T> {
return {
next(): IteratorResult<T> {
@@ -86,11 +101,47 @@ export module Iterator {
}
}
export function collect<T>(iterator: Iterator<T>): T[] {
export function collect<T>(iterator: Iterator<T>, 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<T>(...iterators: Iterator<T>[]): Iterator<T> {
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<T> = Iterator<T> | T[];
@@ -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);
@@ -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]);
@@ -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<T> extends ICompressedTreeElement<T> {
readonly element: T;
readonly children?: ICompressedTreeElement<T>[];
}
function resolve<T>(treeElement: ICompressedTreeElement<T>): IResolvedCompressedTreeElement<T> {
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<number> = { element: 1 };
const compressed: IResolvedCompressedTreeElement<ICompressedTreeNode<number>> =
{ element: { elements: [1], incompressible: false } };
assert.deepEqual(resolve(compress(decompressed)), compressed);
assert.deepEqual(resolve(decompress(compressed)), decompressed);
});
test('no compression', function () {
const decompressed: ICompressedTreeElement<number> = {
element: 1, children: [
{ element: 11 },
{ element: 12 },
{ element: 13 }
]
};
const compressed: IResolvedCompressedTreeElement<ICompressedTreeNode<number>> = {
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<number> = {
element: 1, children: [
{
element: 11, children: [
{
element: 111, children: [
{ element: 1111 }
]
}
]
}
]
};
const compressed: IResolvedCompressedTreeElement<ICompressedTreeNode<number>> = {
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<number> = {
element: 1, children: [
{
element: 11, children: [
{
element: 111, children: [
{ element: 1111 },
{ element: 1112 },
{ element: 1113 },
{ element: 1114 },
]
}
]
}
]
};
const compressed: IResolvedCompressedTreeElement<ICompressedTreeNode<number>> = {
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<number> = {
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<ICompressedTreeNode<number>> = {
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<number> = {
element: 1, children: [
{
element: 11, children: [
{
element: 111, children: [
{ element: 1111, incompressible: true }
]
}
]
}
]
};
const compressed: IResolvedCompressedTreeElement<ICompressedTreeNode<number>> = {
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<number> = {
element: 1, children: [
{
element: 11, children: [
{
element: 111, incompressible: true, children: [
{ element: 1111 }
]
}
]
}
]
};
const compressed: IResolvedCompressedTreeElement<ICompressedTreeNode<number>> = {
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<number> = {
element: 1, children: [
{
element: 11, children: [
{
element: 111, incompressible: true, children: [
{ element: 1111, incompressible: true }
]
}
]
}
]
};
const compressed: IResolvedCompressedTreeElement<ICompressedTreeNode<number>> = {
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<number> = {
element: 1, children: [
{
element: 11, incompressible: true, children: [
{
element: 111, incompressible: true, children: [
{ element: 1111, incompressible: true }
]
}
]
}
]
};
const compressed: IResolvedCompressedTreeElement<ICompressedTreeNode<number>> = {
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<T>(arr: T[]): ISpliceable<T> {
return {
splice(start: number, deleteCount: number, elements: T[]): void {
arr.splice(start, deleteCount, ...elements);
}
};
}
function toArray<T>(list: ITreeNode<ICompressedTreeNode<T>>[]): T[][] {
return list.map(i => i.element.elements);
}
suite('CompressedObjectTreeModel', function () {
test('ctor', () => {
const list: ITreeNode<ICompressedTreeNode<number>>[] = [];
const model = new CompressedObjectTreeModel<number>(toSpliceable(list));
assert(model);
assert.equal(list.length, 0);
assert.equal(model.size, 0);
});
test('flat', () => {
const list: ITreeNode<ICompressedTreeNode<number>>[] = [];
const model = new CompressedObjectTreeModel<number>(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<ICompressedTreeNode<number>>[] = [];
const model = new CompressedObjectTreeModel<number>(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<ICompressedTreeNode<number>>[] = [];
const model = new CompressedObjectTreeModel<number>(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);
});
});
});
+19
View File
@@ -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]);
});
});
+1 -1
View File
@@ -10,7 +10,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, user-scalable=no">
<meta http-equiv="Content-Security-Policy"
content="default-src 'none'; img-src 'self' https: data: blob: vscode-remote:; media-src 'none'; child-src 'self' {{WEBVIEW_ENDPOINT}}; 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:;">
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:;">
<meta id="vscode-workbench-web-configuration" data-settings="{{WORKBENCH_WEB_CONGIGURATION}}">
@@ -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;
@@ -4,7 +4,7 @@
<head>
<meta charset="utf-8" />
<!-- {{SQL CARBON EDIT}} @anthonydresser add 'unsafe-eval' under script src; since its required by angular -->
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; img-src 'self' https: data: blob: vscode-remote:; media-src 'none'; child-src 'self'; object-src 'self'; script-src 'self' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; connect-src 'self' https:; font-src 'self' https: vscode-remote:;">
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; img-src 'self' https: data: blob: vscode-remote:; media-src 'none'; frame-src 'self' https://*.vscode-webview-test.com; object-src 'self'; script-src 'self' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; connect-src 'self' https:; font-src 'self' https: vscode-remote:;">
</head>
<body class="vs-dark" aria-label="">
</body>
@@ -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);
}
}
+6 -5
View File
@@ -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
});
}
@@ -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;
@@ -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)) {
@@ -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('</div>');
+2 -1
View File
@@ -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;
@@ -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,
+3 -3
View File
@@ -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 (<any>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;
+7 -2
View File
@@ -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,
};
}
@@ -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);
}
}
@@ -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;
}
@@ -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 (&rarr; or &middot;) do not have the same width as &nbsp;.
* 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)) {
+14
View File
@@ -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.
@@ -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);
}
});
@@ -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');
});
});
+29 -92
View File
@@ -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;
@@ -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);
@@ -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),
@@ -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, '<span><span class="mtk0">' + expected + '</span></span>');
@@ -90,7 +91,8 @@ suite('viewLineRenderer.renderLine', () => {
-1,
'none',
false,
false
false,
null
));
assert.equal(_actual.html, '<span>' + expected + '</span>');
@@ -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, '<span>' + expectedOutput + '</span>');
@@ -295,7 +299,8 @@ suite('viewLineRenderer.renderLine', () => {
-1,
'none',
false,
false
false,
null
));
assert.equal(_actual.html, '<span>' + expectedOutput + '</span>');
@@ -357,7 +362,8 @@ suite('viewLineRenderer.renderLine', () => {
-1,
'none',
false,
false
false,
null
));
assert.equal(_actual.html, '<span>' + expectedOutput + '</span>');
@@ -396,7 +402,8 @@ suite('viewLineRenderer.renderLine', () => {
-1,
'none',
false,
false
false,
null
));
assert.equal(_actual.html, '<span>' + expectedOutput + '</span>');
@@ -426,7 +433,8 @@ suite('viewLineRenderer.renderLine', () => {
-1,
'none',
false,
false
false,
null
));
assert.equal(actual.html, '<span>' + expectedOutput.join('') + '</span>', message);
}
@@ -526,7 +534,8 @@ suite('viewLineRenderer.renderLine', () => {
-1,
'none',
false,
true
true,
null
));
assert.equal(actual.html, '<span>' + expectedOutput.join('') + '</span>', message);
}
@@ -564,7 +573,8 @@ suite('viewLineRenderer.renderLine', () => {
-1,
'none',
false,
false
false,
null
));
let expectedOutput = [
'<span class="mtk1">a𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷</span>',
@@ -593,7 +603,8 @@ suite('viewLineRenderer.renderLine', () => {
-1,
'none',
false,
false
false,
null
));
assert.equal(actual.html, '<span>' + expectedOutput.join('') + '</span>');
assert.equal(actual.containsRTL, true);
@@ -639,7 +650,8 @@ suite('viewLineRenderer.renderLine', () => {
-1,
'none',
false,
false
false,
null
));
assert.equal(_actual.html, '<span>' + expectedOutput + '</span>');
@@ -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,
[
'<span>',
'<span class="mtk1">Hello\u00a0world!</span>',
@@ -828,6 +844,7 @@ suite('viewLineRenderer.renderLine 2', () => {
],
0,
'none',
null,
[
'<span>',
'<span class="mtk1">Hello\u00a0</span>',
@@ -847,6 +864,7 @@ suite('viewLineRenderer.renderLine 2', () => {
],
0,
'boundary',
null,
[
'<span>',
'<span class="vs-whitespace" style="width:40px">\u00b7\u00b7\u00b7\u00b7</span>',
@@ -868,6 +886,7 @@ suite('viewLineRenderer.renderLine 2', () => {
],
0,
'boundary',
null,
[
'<span>',
'<span class="vs-whitespace" style="width:40px">\u00b7\u00b7\u00b7\u00b7</span>',
@@ -891,6 +910,7 @@ suite('viewLineRenderer.renderLine 2', () => {
],
0,
'boundary',
null,
[
'<span>',
'<span class="vs-whitespace" style="width:40px">\u2192\u00a0\u00a0\u00a0</span>',
@@ -913,6 +933,7 @@ suite('viewLineRenderer.renderLine 2', () => {
],
0,
'boundary',
null,
[
'<span>',
'<span class="vs-whitespace" style="width:40px">\u00b7\u00b7\u2192\u00a0</span>',
@@ -940,6 +961,7 @@ suite('viewLineRenderer.renderLine 2', () => {
],
2,
'boundary',
null,
[
'<span>',
'<span class="">\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0</span>',
@@ -966,6 +988,7 @@ suite('viewLineRenderer.renderLine 2', () => {
],
2,
'boundary',
null,
[
'<span>',
'<span class="">\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0</span>',
@@ -989,6 +1012,7 @@ suite('viewLineRenderer.renderLine 2', () => {
],
0,
'boundary',
null,
[
'<span>',
'<span class="mtk1">it</span>',
@@ -1014,6 +1038,7 @@ suite('viewLineRenderer.renderLine 2', () => {
],
0,
'all',
null,
[
'<span>',
'<span class="vs-whitespace" style="width:10px">\u00b7</span>',
@@ -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,
[
'<span>',
'<span class="mtk0">\u00a0Hel</span>',
'<span class="mtk1">lo</span>',
'<span class="mtk2">\u00a0world!\u00a0\u00a0\u00a0</span>',
'</span>',
].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)],
[
'<span>',
'<span class="vs-whitespace" style="width:10px">\u00b7</span>',
'<span class="mtk0">Hel</span>',
'<span class="mtk1">lo</span>',
'<span class="vs-whitespace" style="width:10px">\u00b7</span>',
'<span class="mtk2">world!</span>',
'<span class="vs-whitespace" style="width:30px">\u2192\u00a0\u00a0</span>',
'</span>',
].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)],
[
'<span>',
'<span class="vs-whitespace" style="width:10px">\u00b7</span>',
'<span class="mtk0">Hel</span>',
'<span class="mtk1">lo</span>',
'<span class="mtk2">\u00a0world!\u00a0\u00a0\u00a0</span>',
'</span>',
].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)],
[
'<span>',
'<span class="vs-whitespace" style="width:10px">\u00b7</span>',
'<span class="mtk0">Hel</span>',
'<span class="mtk1">lo</span>',
'<span class="mtk2">\u00a0world!</span>',
'<span class="vs-whitespace" style="width:30px">\u2192\u00a0\u00a0</span>',
'</span>',
].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)],
[
'<span>',
'<span class="vs-whitespace" style="width:10px">\u00b7</span>',
'<span class="mtk0">Hel</span>',
'<span class="mtk1">lo</span>',
'<span class="mtk2">\u00a0world!</span>',
'<span class="vs-whitespace" style="width:30px">\u2192\u00a0\u00a0</span>',
'</span>',
].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)],
[
'<span>',
'<span class="vs-whitespace" style="width:10px">\u00b7</span>',
'<span class="mtk0">*</span>',
'<span class="vs-whitespace" style="width:10px">\u00b7</span>',
'<span class="mtk0">S</span>',
'</span>',
].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) => {
+2 -2
View File
@@ -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;
@@ -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<TContext> implements IServerChannel<TContext> {
@@ -51,11 +52,13 @@ export class ExtensionHostDebugBroadcastChannel<TContext> 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]);
+1 -1
View File
@@ -13,6 +13,6 @@ export interface IDownloadService {
_serviceBrand: any;
download(uri: URI, to?: string, cancellationToken?: CancellationToken): Promise<string>;
download(uri: URI, to: URI, cancellationToken?: CancellationToken): Promise<void>;
}
@@ -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<any> {
throw new Error('Invalid listen');
}
call(context: any, command: string, args?: any): Promise<any> {
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<void> {
const uriTransfomer = this.getUriTransformer();
if (uriTransfomer) {
from = uriTransfomer.transformOutgoingURI(from);
to = uriTransfomer.transformOutgoingURI(to);
}
await this.channel.call('download', [from, to]);
}
}
@@ -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<string> {
if (uri.scheme === Schemas.file) {
return copy(uri.fsPath, target).then(() => target);
async download(resource: URI, target: URI, cancellationToken: CancellationToken = CancellationToken.None): Promise<void> {
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}`)));
});
}
}
@@ -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<UploadResponse> {
const stream = new Emitter<UploadResponse>();
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<any> {
switch (event) {
case 'upload': return Event.buffer(upload(URI.revive(arg)));
}
throw new Error(`Event not found: ${event}`);
}
call(_: unknown, command: string): Promise<any> {
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<string> {
from = this.getUriTransformer().transformOutgoingURI(from);
const dirName = path.dirname(to);
let out: fs.WriteStream;
return new Promise<string>((c, e) => {
return mkdirp(dirName)
.then(() => {
out = fs.createWriteStream(to);
out.once('close', () => c(to));
out.once('error', e);
const uploadStream = this.channel.listen<UploadResponse>('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));
}
});
});
});
}
}
@@ -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;
}
@@ -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<ILocalExtension> {
@@ -256,7 +256,7 @@ function sleep(seconds: number): Promise<void> {
});
}
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;
+1 -1
View File
@@ -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.
*
@@ -48,6 +48,7 @@ import './mainThreadStorage';
import './mainThreadTelemetry';
import './mainThreadTerminalService';
import './mainThreadTreeViews';
import './mainThreadDownloadService';
import './mainThreadUrls';
import './mainThreadWindow';
import './mainThreadWebview';
@@ -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<void> {
return this.downloadService.download(URI.revive(uri), URI.revive(to));
}
}
@@ -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';
@@ -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<WebviewPanelHandle, WebviewEditorInput>();
private readonly _webviewEditorInputs = new Map<string, WebviewEditorInput>();
private readonly _webviews = new Map<string, Webview>();
private readonly _revivers = new Map<string, IDisposable>();
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<boolean> {
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);
}
+1 -7
View File
@@ -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));
});
});
@@ -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<void>;
}
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<MainThreadEditorInsetsShape>('MainThreadEditorInsets'),
MainThreadErrors: createMainId<MainThreadErrorsShape>('MainThreadErrors'),
MainThreadTreeViews: createMainId<MainThreadTreeViewsShape>('MainThreadTreeViews'),
MainThreadDownloadService: createMainId<MainThreadDownloadServiceShape>('MainThreadDownloadService'),
MainThreadKeytar: createMainId<MainThreadKeytarShape>('MainThreadKeytar'),
MainThreadLanguageFeatures: createMainId<MainThreadLanguageFeaturesShape>('MainThreadLanguageFeatures'),
MainThreadLanguages: createMainId<MainThreadLanguagesShape>('MainThreadLanguages'),
@@ -1347,6 +1355,7 @@ export const ExtHostContext = {
ExtHostDocumentSaveParticipant: createExtId<ExtHostDocumentSaveParticipantShape>('ExtHostDocumentSaveParticipant'),
ExtHostEditors: createExtId<ExtHostEditorsShape>('ExtHostEditors'),
ExtHostTreeViews: createExtId<ExtHostTreeViewsShape>('ExtHostTreeViews'),
ExtHostDownloadService: createExtId<ExtHostDownloadServiceShape>('ExtHostDownloadService'),
ExtHostFileSystem: createExtId<ExtHostFileSystemShape>('ExtHostFileSystem'),
ExtHostFileSystemEventService: createExtId<ExtHostFileSystemEventServiceShape>('ExtHostFileSystemEventService'),
ExtHostLanguageFeatures: createExtId<ExtHostLanguageFeaturesShape>('ExtHostLanguageFeatures'),
@@ -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));
@@ -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<any> => {
const location = URI.file(join(tmpdir(), generateUuid()));
await proxy.$download(resource, location);
return location;
});
}
}
+79 -38
View File
@@ -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<string, Part> = new Map<string, Part>();
private workbenchGrid: Grid | WorkbenchLegacyLayout;
private workbenchGrid: SerializableGrid<ISerializableView> | 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<ISerializableView> | 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<ISerializableView>;
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();
+17 -21
View File
@@ -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<string> {
// @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);
@@ -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
}
}
@@ -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');
}
@@ -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 {
@@ -399,7 +399,8 @@
}
.monaco-editor .review-widget .action-item {
min-width: 16px;
min-width: 18px;
min-height: 18px;
margin-left: 4px;
}
@@ -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]);
@@ -64,14 +64,15 @@ Registry.as<IQuickOpenRegistry>(Extensions.Quickopen).registerQuickOpenHandler(
);
// Editor
const editorDescriptor = new EditorDescriptor(
ExtensionEditor,
ExtensionEditor.ID,
localize('extension', "Extension")
);
Registry.as<IEditorRegistry>(EditorExtensions.Editors)
.registerEditor(editorDescriptor, [new SyncDescriptor(ExtensionsInput)]);
Registry.as<IEditorRegistry>(EditorExtensions.Editors).registerEditor(
new EditorDescriptor(
ExtensionEditor,
ExtensionEditor.ID,
localize('extension', "Extension")
),
[
new SyncDescriptor(ExtensionsInput)
]);
// Viewlet
const viewletDescriptor = new ViewletDescriptor(
@@ -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<any> {
@@ -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}}
@@ -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<IExtension, ITemplateData> {
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<IExtension, ITemplateData> {
// 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}}
@@ -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<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;
}
@@ -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];
@@ -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<void> = new Emitter<void>();
const runningExtensions = [{ identifier: new ExtensionIdentifier('pub.b'), extensionLocation: URI.file('pub.b') }];
instantiationService.stub(IExtensionService, <Partial<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<void> = new Emitter<void>();
const runningExtensions = [{ identifier: new ExtensionIdentifier('pub.b'), extensionLocation: URI.file('pub.b') }];
instantiationService.stub(IExtensionService, <Partial<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<void> = new Emitter<void>();
const runningExtensions = [ExtensionsActions.toExtensionDescription(remoteExtension)];
instantiationService.stub(IExtensionService, <Partial<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<IExtensionIdentifier>();
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<void> = new Emitter<void>();
const runningExtensions = [ExtensionsActions.toExtensionDescription(remoteExtension)];
instantiationService.stub(IExtensionService, <Partial<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<DidInstallExtensionEvent>();
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<void> = new Emitter<void>();
instantiationService.stub(IExtensionService, <Partial<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<DidInstallExtensionEvent>();
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<void> = new Emitter<void>();
instantiationService.stub(IExtensionService, <Partial<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`) });
@@ -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',
@@ -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';
@@ -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<T> extends IDisposable {
readonly preferencesModel: IPreferencesEditorModel<T>;
@@ -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<ISetting> {
@@ -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<void> = new Delayer<void>(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<IConfigurationRegistry>(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[] = [];
@@ -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<void>((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;
@@ -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<any>();
private readonly _webview = this._register(new MutableDisposable<WebviewElement>());
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<void>());
public readonly onDidFocus: Event<void> = this._onDidFocus.event;
private readonly _onDidClickLink = this._register(new Emitter<URI>());
public readonly onDidClickLink: Event<URI> = 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<string | undefined>());
public readonly onDidUpdateState: Event<string | undefined> = this._onDidUpdateState.event;
private readonly _onMessage = this._register(new Emitter<any>());
public readonly onMessage: Event<any> = 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);
}
}
}
@@ -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<string>('webview.experimental.useExternalEndpoint');
if (typeof environmentService.webviewEndpoint !== 'string' && !useExternalEndpoint) {
throw new Error('To use iframe based webviews, you must configure `environmentService.webviewEndpoint`');
}
@@ -142,7 +144,9 @@ export class IFrameWebview extends Disposable implements Webview {
}
private get endpoint(): string {
const endpoint = this.environmentService.webviewEndpoint!.replace('{{uuid}}', this.id);
const useExternalEndpoint = this._configurationService.getValue<string>('webview.experimental.useExternalEndpoint');
const baseEndpoint = useExternalEndpoint ? 'https://{{uuid}}.vscode-webview-test.com/8fa811108f0f0524c473020ef57b6620f6c201e1' : this.environmentService.webviewEndpoint!;
const endpoint = baseEndpoint.replace('{{uuid}}', this.id);
if (endpoint[endpoint.length - 1] === '/') {
return endpoint.slice(0, endpoint.length - 1);
}
@@ -3,14 +3,10 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Emitter, Event } from 'vs/base/common/event';
import { Disposable, DisposableStore, MutableDisposable, toDisposable } from 'vs/base/common/lifecycle';
import { URI } from 'vs/base/common/uri';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IFrameWebview } from 'vs/workbench/contrib/webview/browser/webviewElement';
import { IWebviewService, Webview, WebviewContentOptions, WebviewEditorOverlay, WebviewElement, WebviewOptions } from 'vs/workbench/contrib/webview/common/webview';
import { IWorkbenchLayoutService, Parts } from 'vs/workbench/services/layout/browser/layoutService';
import { memoize } from 'vs/base/common/decorators';
import { IWebviewService, WebviewContentOptions, WebviewEditorOverlay, WebviewElement, WebviewOptions } from 'vs/workbench/contrib/webview/common/webview';
import { DynamicWebviewEditorOverlay } from './dynamicWebviewEditorOverlay';
export class WebviewService implements IWebviewService {
_serviceBrand: any;
@@ -35,166 +31,3 @@ export class WebviewService implements IWebviewService {
return this._instantiationService.createInstance(DynamicWebviewEditorOverlay, id, options, contentOptions);
}
}
/**
* Webview editor overlay that creates and destroys the underlying webview as needed.
*/
class DynamicWebviewEditorOverlay extends Disposable implements WebviewEditorOverlay {
private readonly _pendingMessages = new Set<any>();
private readonly _webview = this._register(new MutableDisposable<WebviewElement>());
private readonly _webviewEvents = this._register(new DisposableStore());
private _html: string = '';
private _initialScrollProgress: number = 0;
private _state: string | undefined = undefined;
private _owner: any = undefined;
public constructor(
private readonly id: string,
public readonly options: WebviewOptions,
private _contentOptions: WebviewContentOptions,
@IWorkbenchLayoutService private readonly _layoutService: IWorkbenchLayoutService,
@IWebviewService private readonly _webviewService: IWebviewService,
) {
super();
this._register(toDisposable(() => this.container.remove()));
}
@memoize
public get container() {
const container = document.createElement('div');
container.id = `webview-${this.id}`;
this._layoutService.getContainer(Parts.EDITOR_PART).appendChild(container);
return container;
}
public claim(owner: any) {
this._owner = owner;
this.show();
}
public release(owner: any) {
if (this._owner !== owner) {
return;
}
this._owner = undefined;
this.container.style.visibility = 'hidden';
if (!this.options.retainContextWhenHidden) {
this._webview.clear();
this._webviewEvents.clear();
}
}
private show() {
if (!this._webview.value) {
const webview = this._webviewService.createWebview(this.id, this.options, this._contentOptions);
this._webview.value = webview;
webview.state = this._state;
webview.html = this._html;
if (this.options.tryRestoreScrollPosition) {
webview.initialScrollProgress = this._initialScrollProgress;
}
this._webview.value.mountTo(this.container);
this._webviewEvents.clear();
webview.onDidFocus(() => {
this._onDidFocus.fire();
}, undefined, this._webviewEvents);
webview.onDidClickLink(x => {
this._onDidClickLink.fire(x);
}, undefined, this._webviewEvents);
webview.onDidScroll(x => {
this._initialScrollProgress = x.scrollYPercentage;
this._onDidScroll.fire(x);
}, undefined, this._webviewEvents);
webview.onDidUpdateState(state => {
this._state = state;
this._onDidUpdateState.fire(state);
}, undefined, this._webviewEvents);
webview.onMessage(x => {
this._onMessage.fire(x);
}, undefined, this._webviewEvents);
this._pendingMessages.forEach(msg => webview.sendMessage(msg));
this._pendingMessages.clear();
}
this.container.style.visibility = 'visible';
}
public get html(): string { return this._html; }
public set html(value: string) {
this._html = value;
this.withWebview(webview => webview.html = value);
}
public get initialScrollProgress(): number { return this._initialScrollProgress; }
public set initialScrollProgress(value: number) {
this._initialScrollProgress = value;
this.withWebview(webview => webview.initialScrollProgress = value);
}
public get state(): string | undefined { return this._state; }
public set state(value: string | undefined) {
this._state = value;
this.withWebview(webview => webview.state = value);
}
public get contentOptions(): WebviewContentOptions { return this._contentOptions; }
public set contentOptions(value: WebviewContentOptions) {
this._contentOptions = value;
this.withWebview(webview => webview.contentOptions = value);
}
private readonly _onDidFocus = this._register(new Emitter<void>());
public readonly onDidFocus: Event<void> = this._onDidFocus.event;
private readonly _onDidClickLink = this._register(new Emitter<URI>());
public readonly onDidClickLink: Event<URI> = 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<string | undefined>());
public readonly onDidUpdateState: Event<string | undefined> = this._onDidUpdateState.event;
private readonly _onMessage = this._register(new Emitter<any>());
public readonly onMessage: Event<any> = this._onMessage.event;
sendMessage(data: any): void {
if (this._webview.value) {
this._webview.value.sendMessage(data);
} else {
this._pendingMessages.add(data);
}
}
update(html: string, options: WebviewContentOptions, retainContextWhenHidden: boolean): void {
this._contentOptions = options;
this._html = html;
this.withWebview(webview => {
webview.update(html, options, retainContextWhenHidden);
});
}
layout(): void { this.withWebview(webview => webview.layout()); }
focus(): void { this.withWebview(webview => webview.focus()); }
reload(): void { this.withWebview(webview => webview.reload()); }
showFind(): void { this.withWebview(webview => webview.showFind()); }
hideFind(): void { this.withWebview(webview => webview.hideFind()); }
private withWebview(f: (webview: Webview) => void): void {
if (this._webview.value) {
f(this._webview.value);
}
}
}
@@ -97,6 +97,8 @@ export interface WebviewEditorOverlay extends Webview {
claim(owner: any): void;
release(owner: any): void;
getInnerWebview(): Webview | undefined;
}
export const webviewDeveloperCategory = nls.localize('developer', "Developer");
@@ -15,9 +15,9 @@ import { Extensions as ActionExtensions, IWorkbenchActionRegistry } from 'vs/wor
import { WebviewEditor } from 'vs/workbench/contrib/webview/browser/webviewEditor';
import { IWebviewService, webviewDeveloperCategory } from 'vs/workbench/contrib/webview/common/webview';
import * as webviewCommands from 'vs/workbench/contrib/webview/electron-browser/webviewCommands';
import { WebviewService } from 'vs/workbench/contrib/webview/electron-browser/webviewService';
import { ElectronWebviewService } from 'vs/workbench/contrib/webview/electron-browser/webviewService';
registerSingleton(IWebviewService, WebviewService, true);
registerSingleton(IWebviewService, ElectronWebviewService, true);
const actionRegistry = Registry.as<IWorkbenchActionRegistry>(ActionExtensions.WorkbenchActions);
@@ -9,6 +9,7 @@ import { Command, ServicesAccessor } from 'vs/editor/browser/editorExtensions';
import { WebviewEditor } from 'vs/workbench/contrib/webview/browser/webviewEditor';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { ElectronWebviewBasedWebview } from 'vs/workbench/contrib/webview/electron-browser/webviewElement';
import { WebviewEditorOverlay } from 'vs/workbench/contrib/webview/common/webview';
export class OpenWebviewDeveloperToolsAction extends Action {
static readonly ID = 'workbench.action.webview.openDeveloperTools';
@@ -92,6 +93,11 @@ function withActiveWebviewBasedWebview(accessor: ServicesAccessor, f: (webview:
webViewEditor.withWebview(webview => {
if (webview instanceof ElectronWebviewBasedWebview) {
f(webview);
} else if ((webview as WebviewEditorOverlay).getInnerWebview) {
const innerWebview = (webview as WebviewEditorOverlay).getInnerWebview();
if (innerWebview instanceof ElectronWebviewBasedWebview) {
f(innerWebview);
}
}
});
}
@@ -3,25 +3,39 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { WebviewService as BrowserWebviewService } from 'vs/workbench/contrib/webview/browser/webviewService';
import { IWebviewService, WebviewContentOptions, WebviewElement, WebviewOptions } from 'vs/workbench/contrib/webview/common/webview';
import { DynamicWebviewEditorOverlay } from 'vs/workbench/contrib/webview/browser/dynamicWebviewEditorOverlay';
import { IFrameWebview } from 'vs/workbench/contrib/webview/browser/webviewElement';
import { IWebviewService, WebviewContentOptions, WebviewEditorOverlay, WebviewElement, WebviewOptions } from 'vs/workbench/contrib/webview/common/webview';
import { ElectronWebviewBasedWebview } from 'vs/workbench/contrib/webview/electron-browser/webviewElement';
export class WebviewService extends BrowserWebviewService implements IWebviewService {
export class ElectronWebviewService implements IWebviewService {
_serviceBrand: any;
constructor(
@IInstantiationService private readonly instantiationService: IInstantiationService,
) {
super(instantiationService);
}
@IInstantiationService private readonly _instantiationService: IInstantiationService,
@IConfigurationService private readonly _configService: IConfigurationService,
) { }
createWebview(
_id: string,
id: string,
options: WebviewOptions,
contentOptions: WebviewContentOptions
): WebviewElement {
return this.instantiationService.createInstance(ElectronWebviewBasedWebview, options, contentOptions);
const useExternalEndpoint = this._configService.getValue<string>('webview.experimental.useExternalEndpoint');
if (useExternalEndpoint) {
return this._instantiationService.createInstance(IFrameWebview, id, options, contentOptions);
} else {
return this._instantiationService.createInstance(ElectronWebviewBasedWebview, options, contentOptions);
}
}
createWebviewEditorOverlay(
id: string,
options: WebviewOptions,
contentOptions: WebviewContentOptions,
): WebviewEditorOverlay {
return this._instantiationService.createInstance(DynamicWebviewEditorOverlay, id, options, contentOptions);
}
}
@@ -23,7 +23,6 @@ import { ConfigurationEditingService, EditableConfigurationTarget } from 'vs/wor
import { WorkspaceConfiguration, FolderConfiguration, RemoteUserConfiguration, UserConfiguration } from 'vs/workbench/services/configuration/browser/configuration';
import { JSONEditingService } from 'vs/workbench/services/configuration/common/jsonEditingService';
import { IJSONSchema, IJSONSchemaMap } from 'vs/base/common/jsonSchema';
import { localize } from 'vs/nls';
import { isEqual, dirname } from 'vs/base/common/resources';
import { mark } from 'vs/base/common/performance';
import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService';
@@ -507,29 +506,29 @@ export class WorkspaceService extends Disposable implements IConfigurationServic
private registerConfigurationSchemas(): void {
if (this.workspace) {
const jsonRegistry = Registry.as<IJSONContributionRegistry>(JSONExtensions.JSONContribution);
const convertToNotSuggestedProperties = (properties: IJSONSchemaMap, errorMessage: string): IJSONSchemaMap => {
const convertToNotSuggestedProperties = (properties: IJSONSchemaMap): IJSONSchemaMap => {
return Object.keys(properties).reduce((result: IJSONSchemaMap, property) => {
result[property] = deepClone(properties[property]);
result[property].deprecationMessage = errorMessage;
result[property].doNotSuggest = true;
return result;
}, {});
};
const unsupportedApplicationSettings = convertToNotSuggestedProperties(applicationSettings.properties, localize('unsupportedApplicationSetting', "This setting can be applied only in application user settings"));
const unsupportedMachineSettings = convertToNotSuggestedProperties(machineSettings.properties, localize('unsupportedMachineSetting', "This setting can be applied only in user settings"));
const unsupportedRemoteMachineSettings = convertToNotSuggestedProperties(machineSettings.properties, localize('unsupportedRemoteMachineSetting', "This setting can be applied only in remote machine settings"));
const allSettingsSchema: IJSONSchema = { properties: allSettings.properties, patternProperties: allSettings.patternProperties, additionalProperties: false, errorMessage: 'Unknown configuration setting' };
const userSettingsSchema: IJSONSchema = this.remoteUserConfiguration ? { properties: { ...applicationSettings.properties, ...unsupportedRemoteMachineSettings, ...windowSettings.properties, ...resourceSettings.properties }, patternProperties: allSettings.patternProperties, additionalProperties: false, errorMessage: 'Unknown configuration setting' } : allSettingsSchema;
const machineSettingsSchema: IJSONSchema = { properties: { ...unsupportedApplicationSettings, ...windowSettings.properties, ...resourceSettings.properties }, patternProperties: allSettings.patternProperties, additionalProperties: false, errorMessage: 'Unknown configuration setting' };
const workspaceSettingsSchema: IJSONSchema = { properties: { ...unsupportedApplicationSettings, ...unsupportedMachineSettings, ...windowSettings.properties, ...resourceSettings.properties }, patternProperties: allSettings.patternProperties, additionalProperties: false, errorMessage: 'Unknown configuration setting' };
const unsupportedApplicationSettings = convertToNotSuggestedProperties(applicationSettings.properties);
const unsupportedMachineSettings = convertToNotSuggestedProperties(machineSettings.properties);
const unsupportedRemoteMachineSettings = convertToNotSuggestedProperties(machineSettings.properties);
const allSettingsSchema: IJSONSchema = { properties: allSettings.properties, patternProperties: allSettings.patternProperties, additionalProperties: true };
const userSettingsSchema: IJSONSchema = this.remoteUserConfiguration ? { properties: { ...applicationSettings.properties, ...unsupportedRemoteMachineSettings, ...windowSettings.properties, ...resourceSettings.properties }, patternProperties: allSettings.patternProperties, additionalProperties: true } : allSettingsSchema;
const machineSettingsSchema: IJSONSchema = { properties: { ...unsupportedApplicationSettings, ...windowSettings.properties, ...resourceSettings.properties }, patternProperties: allSettings.patternProperties, additionalProperties: true };
const workspaceSettingsSchema: IJSONSchema = { properties: { ...unsupportedApplicationSettings, ...unsupportedMachineSettings, ...windowSettings.properties, ...resourceSettings.properties }, patternProperties: allSettings.patternProperties, additionalProperties: true };
jsonRegistry.registerSchema(defaultSettingsSchemaId, allSettingsSchema);
jsonRegistry.registerSchema(userSettingsSchemaId, userSettingsSchema);
jsonRegistry.registerSchema(machineSettingsSchemaId, machineSettingsSchema);
if (WorkbenchState.WORKSPACE === this.getWorkbenchState()) {
const unsupportedWindowSettings = convertToNotSuggestedProperties(windowSettings.properties, localize('unsupportedWindowSetting', "This setting cannot be applied now. It will be applied when you open this folder directly."));
const folderSettingsSchema: IJSONSchema = { properties: { ...unsupportedApplicationSettings, ...unsupportedMachineSettings, ...unsupportedWindowSettings, ...resourceSettings.properties }, patternProperties: allSettings.patternProperties, additionalProperties: false, errorMessage: 'Unknown configuration setting' };
const unsupportedWindowSettings = convertToNotSuggestedProperties(windowSettings.properties);
const folderSettingsSchema: IJSONSchema = { properties: { ...unsupportedApplicationSettings, ...unsupportedMachineSettings, ...unsupportedWindowSettings, ...resourceSettings.properties }, patternProperties: allSettings.patternProperties, additionalProperties: true };
jsonRegistry.registerSchema(workspaceSettingsSchemaId, workspaceSettingsSchema);
jsonRegistry.registerSchema(folderSettingsSchemaId, folderSettingsSchema);
} else {
@@ -42,7 +42,6 @@ import { DiskFileSystemProvider } from 'vs/platform/files/node/diskFileSystemPro
import { ConfigurationCache } from 'vs/workbench/services/configuration/node/configurationCache';
import { IRemoteAgentEnvironment } from 'vs/platform/remote/common/remoteAgentEnvironment';
import { IConfigurationCache } from 'vs/workbench/services/configuration/common/configuration';
// import { VSBuffer } from 'vs/base/common/buffer';
import { SignService } from 'vs/platform/sign/browser/signService';
import { FileUserDataProvider } from 'vs/workbench/services/userData/common/fileUserDataProvider';
import { IKeybindingEditingService, KeybindingsEditingService } from 'vs/workbench/services/keybinding/common/keybindingEditing';
@@ -276,7 +275,7 @@ suite('WorkspaceConfigurationService - Remote Folder', () => {
// }
// });
// });
// await instantiationService.get(IFileService).writeFile(URI.file(remoteSettingsFile), VSBuffer.fromString('{ "configurationService.remote.machineSetting": "remoteValue" }'));
// fs.writeFileSync(remoteSettingsFile, '{ "configurationService.remote.machineSetting": "remoteValue" }');
// return promise;
// });
@@ -7,7 +7,7 @@ import { URI } from 'vs/base/common/uri';
import { Event, Emitter } from 'vs/base/common/event';
import { IDecorationsService, IDecoration, IResourceDecorationChangeEvent, IDecorationsProvider, IDecorationData } from './decorations';
import { TernarySearchTree } from 'vs/base/common/map';
import { Disposable, IDisposable, toDisposable } from 'vs/base/common/lifecycle';
import { Disposable, IDisposable, toDisposable, dispose } from 'vs/base/common/lifecycle';
import { isThenable } from 'vs/base/common/async';
import { LinkedList } from 'vs/base/common/linkedList';
import { createStyleSheet, createCSSRule, removeCSSRulesContainingSelector } from 'vs/base/browser/dom';
@@ -334,14 +334,15 @@ class DecorationProviderWrapper {
}
}
export class FileDecorationsService extends Disposable implements IDecorationsService {
export class FileDecorationsService implements IDecorationsService {
_serviceBrand: any;
private readonly _data = new LinkedList<DecorationProviderWrapper>();
private readonly _onDidChangeDecorationsDelayed = this._register(new Emitter<URI | URI[]>());
private readonly _onDidChangeDecorations = this._register(new Emitter<IResourceDecorationChangeEvent>());
private readonly _onDidChangeDecorationsDelayed = new Emitter<URI | URI[]>();
private readonly _onDidChangeDecorations = new Emitter<IResourceDecorationChangeEvent>();
private readonly _decorationStyles: DecorationStyles;
private readonly _disposables: IDisposable[];
readonly onDidChangeDecorations: Event<IResourceDecorationChangeEvent> = Event.any(
this._onDidChangeDecorations.event,
@@ -355,17 +356,27 @@ export class FileDecorationsService extends Disposable implements IDecorationsSe
constructor(
@IThemeService themeService: IThemeService
) {
super();
this._decorationStyles = this._register(new DecorationStyles(themeService));
this._decorationStyles = new DecorationStyles(themeService);
// every so many events we check if there are
// css styles that we don't need anymore
let count = 0;
this._register(this.onDidChangeDecorations(() => {
let reg = this.onDidChangeDecorations(() => {
if (++count % 17 === 0) {
this._decorationStyles.cleanUp(this._data.iterator());
}
}));
});
this._disposables = [
reg,
this._decorationStyles
];
}
dispose(): void {
dispose(this._disposables);
dispose(this._onDidChangeDecorations);
dispose(this._onDidChangeDecorationsDelayed);
}
registerDecorationsProvider(provider: IDecorationsProvider): IDisposable {
@@ -162,6 +162,11 @@ export class RemoteExtensionHostClient extends Disposable implements IExtensionH
}
private _onExtHostConnectionLost(): void {
if (this._isExtensionDevHost && this._environmentService.debugExtensionHost.debugId) {
this._extensionHostDebugService.close(this._environmentService.debugExtensionHost.debugId);
}
if (this._terminating) {
// Expected termination path (we asked the process to terminate)
return;
@@ -101,10 +101,6 @@ export class CachedExtensionScanner {
development.forEach(developedExtension => {
log.info('', nls.localize('extensionUnderDevelopment', "Loading development extension at {0}", developedExtension.extensionLocation.fsPath));
const extensionKey = ExtensionIdentifier.toKey(developedExtension.identifier);
const extension = result.get(extensionKey);
if (extension) {
log.warn(developedExtension.extensionLocation.fsPath, nls.localize('overwritingExtension', "Overwriting extension {0} with {1}.", extension.extensionLocation.fsPath, developedExtension.extensionLocation.fsPath));
}
result.set(extensionKey, developedExtension);
});
let r: IExtensionDescription[] = [];
@@ -32,7 +32,7 @@ import { ExtensionHostProcessManager } from 'vs/workbench/services/extensions/co
import { ExtensionIdentifier, IExtension, ExtensionType, IExtensionDescription } from 'vs/platform/extensions/common/extensions';
import { Schemas } from 'vs/base/common/network';
import { IFileService } from 'vs/platform/files/common/files';
import { PersistenConnectionEventType } from 'vs/platform/remote/common/remoteAgentConnection';
import { PersistentConnectionEventType } from 'vs/platform/remote/common/remoteAgentConnection';
import { IProductService } from 'vs/platform/product/common/product';
import { Logger } from 'vs/workbench/services/extensions/common/extensionPoints';
@@ -478,7 +478,7 @@ export class ExtensionService extends AbstractExtensionService implements IExten
if (!remoteAuthority) {
return;
}
if (e.type === PersistenConnectionEventType.ConnectionLost) {
if (e.type === PersistentConnectionEventType.ConnectionLost) {
this._remoteAuthorityResolverService.clearResolvedAuthority(remoteAuthority);
}
});
+1 -1
View File
@@ -54,7 +54,7 @@ import { IMarkerDecorationsService } from 'vs/editor/common/services/markersDeco
import { IMarkerService } from 'vs/platform/markers/common/markers';
import { MarkerService } from 'vs/platform/markers/common/markerService';
import { IDownloadService } from 'vs/platform/download/common/download';
import { DownloadService } from 'vs/platform/download/node/downloadService';
import { DownloadService } from 'vs/platform/download/common/downloadService';
import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService';
import { ClipboardService } from 'vs/platform/clipboard/electron-browser/clipboardService';
import { ContextKeyService } from 'vs/platform/contextkey/browser/contextKeyService';
+3 -3
View File
@@ -51,8 +51,8 @@ import { MarkerDecorationsService } from 'vs/editor/common/services/markerDecora
import { IMarkerDecorationsService } from 'vs/editor/common/services/markersDecorationService';
import { IMarkerService } from 'vs/platform/markers/common/markers';
import { MarkerService } from 'vs/platform/markers/common/markerService';
// import { IDownloadService } from 'vs/platform/download/common/download';
// import { DownloadService } from 'vs/platform/download/node/downloadService';
import { IDownloadService } from 'vs/platform/download/common/download';
import { DownloadService } from 'vs/platform/download/common/downloadService';
import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService';
import { BrowserClipboardService } from 'vs/platform/clipboard/browser/clipboardService';
import { ContextKeyService } from 'vs/platform/contextkey/browser/contextKeyService';
@@ -145,7 +145,7 @@ registerSingleton(IOpenerService, OpenerService, true);
registerSingleton(IEditorWorkerService, EditorWorkerServiceImpl);
registerSingleton(IMarkerDecorationsService, MarkerDecorationsService);
registerSingleton(IMarkerService, MarkerService, true);
// registerSingleton(IDownloadService, DownloadService, true);
registerSingleton(IDownloadService, DownloadService, true);
registerSingleton(IClipboardService, BrowserClipboardService, true);
registerSingleton(IContextKeyService, ContextKeyService);
registerSingleton(IModelService, ModelServiceImpl, true);