mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-05 01:25:38 -05:00
Merge from vscode 64980ea1f3f532c82bb6c28d27bba9ef2c5b4463 (#7206)
* Merge from vscode 64980ea1f3f532c82bb6c28d27bba9ef2c5b4463 * fix config changes * fix strictnull checks
This commit is contained in:
@@ -111,4 +111,4 @@ export interface IStaticDND {
|
||||
|
||||
export const StaticDND: IStaticDND = {
|
||||
CurrentDragAndDropData: undefined
|
||||
};
|
||||
};
|
||||
|
||||
@@ -1193,7 +1193,7 @@ export function asDomUri(uri: URI): URI {
|
||||
return uri;
|
||||
}
|
||||
if (Schemas.vscodeRemote === uri.scheme) {
|
||||
return RemoteAuthorities.rewrite(uri.authority, uri.path);
|
||||
return RemoteAuthorities.rewrite(uri);
|
||||
}
|
||||
return uri;
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ import { parse } from 'vs/base/common/marshalling';
|
||||
import { cloneAndChange } from 'vs/base/common/objects';
|
||||
import { escape } from 'vs/base/common/strings';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { Schemas } from 'vs/base/common/network';
|
||||
|
||||
export interface MarkdownRenderOptions extends FormattedTextRenderOptions {
|
||||
codeBlockRenderer?: (modeId: string, value: string) => Promise<string>;
|
||||
@@ -186,9 +187,9 @@ export function renderMarkdown(markdown: IMarkdownString, options: MarkdownRende
|
||||
renderer
|
||||
};
|
||||
|
||||
const allowedSchemes = ['http', 'https', 'mailto', 'data'];
|
||||
const allowedSchemes = [Schemas.http, Schemas.https, Schemas.mailto, Schemas.data, Schemas.file, Schemas.vscodeRemote];
|
||||
if (markdown.isTrusted) {
|
||||
allowedSchemes.push('command');
|
||||
allowedSchemes.push(Schemas.command);
|
||||
}
|
||||
|
||||
const renderedMarkdown = marked.parse(markdown.value, markedOptions);
|
||||
|
||||
@@ -55,13 +55,17 @@ export class Dialog extends Disposable {
|
||||
private buttonGroup: ButtonGroup | undefined;
|
||||
private styles: IDialogStyles | undefined;
|
||||
private focusToReturn: HTMLElement | undefined;
|
||||
private checkboxHasFocus: boolean = false;
|
||||
private buttons: string[];
|
||||
|
||||
constructor(private container: HTMLElement, private message: string, private buttons: string[], private options: IDialogOptions) {
|
||||
constructor(private container: HTMLElement, private message: string, buttons: string[], private options: IDialogOptions) {
|
||||
super();
|
||||
this.modal = this.container.appendChild($(`.dialog-modal-block${options.type === 'pending' ? '.dimmed' : ''}`));
|
||||
this.element = this.modal.appendChild($('.dialog-box'));
|
||||
hide(this.element);
|
||||
|
||||
// If no button is provided, default to OK
|
||||
this.buttons = buttons.length ? buttons : [nls.localize('ok', "OK")];
|
||||
const buttonsRowElement = this.element.appendChild($('.dialog-buttons-row'));
|
||||
this.buttonsContainer = buttonsRowElement.appendChild($('.dialog-buttons'));
|
||||
|
||||
@@ -88,8 +92,6 @@ export class Dialog extends Disposable {
|
||||
checkboxMessageElement.innerText = this.options.checkboxLabel;
|
||||
}
|
||||
|
||||
|
||||
|
||||
const toolbarRowElement = this.element.appendChild($('.dialog-toolbar-row'));
|
||||
this.toolbarContainer = toolbarRowElement.appendChild($('.dialog-toolbar'));
|
||||
}
|
||||
@@ -139,14 +141,27 @@ export class Dialog extends Disposable {
|
||||
|
||||
let eventHandled = false;
|
||||
if (evt.equals(KeyMod.Shift | KeyCode.Tab) || evt.equals(KeyCode.LeftArrow)) {
|
||||
focusedButton = focusedButton + buttonGroup.buttons.length - 1;
|
||||
focusedButton = focusedButton % buttonGroup.buttons.length;
|
||||
buttonGroup.buttons[focusedButton].focus();
|
||||
if (!this.checkboxHasFocus && focusedButton === 0) {
|
||||
this.checkbox!.domNode.focus();
|
||||
this.checkboxHasFocus = true;
|
||||
} else {
|
||||
focusedButton = (this.checkboxHasFocus ? 0 : focusedButton) + buttonGroup.buttons.length - 1;
|
||||
focusedButton = focusedButton % buttonGroup.buttons.length;
|
||||
buttonGroup.buttons[focusedButton].focus();
|
||||
this.checkboxHasFocus = false;
|
||||
}
|
||||
|
||||
eventHandled = true;
|
||||
} else if (evt.equals(KeyCode.Tab) || evt.equals(KeyCode.RightArrow)) {
|
||||
focusedButton++;
|
||||
focusedButton = focusedButton % buttonGroup.buttons.length;
|
||||
buttonGroup.buttons[focusedButton].focus();
|
||||
if (!this.checkboxHasFocus && focusedButton === buttonGroup.buttons.length - 1) {
|
||||
this.checkbox!.domNode.focus();
|
||||
this.checkboxHasFocus = true;
|
||||
} else {
|
||||
focusedButton = this.checkboxHasFocus ? 0 : focusedButton + 1;
|
||||
focusedButton = focusedButton % buttonGroup.buttons.length;
|
||||
buttonGroup.buttons[focusedButton].focus();
|
||||
this.checkboxHasFocus = false;
|
||||
}
|
||||
eventHandled = true;
|
||||
}
|
||||
|
||||
|
||||
@@ -596,8 +596,8 @@ export class SerializableGrid<T extends ISerializableView> extends Grid<T> {
|
||||
super.layout(width, height);
|
||||
|
||||
if (this.initialLayoutContext) {
|
||||
this.gridview.trySet2x2();
|
||||
this.initialLayoutContext = false;
|
||||
this.gridview.trySet2x2();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -250,6 +250,9 @@ class BranchNode implements ISplitView, IDisposable {
|
||||
const onDidSashReset = Event.map(this.splitview.onDidSashReset, i => [i]);
|
||||
this.splitviewSashResetDisposable = onDidSashReset(this._onDidSashReset.fire, this._onDidSashReset);
|
||||
|
||||
const onDidChildrenChange = Event.map(Event.any(...this.children.map(c => c.onDidChange)), () => undefined);
|
||||
this.childrenChangeDisposable = onDidChildrenChange(this._onDidChange.fire, this._onDidChange);
|
||||
|
||||
const onDidChildrenSashReset = Event.any(...this.children.map((c, i) => Event.map(c.onDidSashReset, location => [i, ...location])));
|
||||
this.childrenSashResetDisposable = onDidChildrenSashReset(this._onDidSashReset.fire, this._onDidSashReset);
|
||||
}
|
||||
@@ -422,7 +425,6 @@ class BranchNode implements ISplitView, IDisposable {
|
||||
}
|
||||
|
||||
this.splitview.setViewVisible(index, visible);
|
||||
this._onDidChange.fire(undefined);
|
||||
}
|
||||
|
||||
getChildCachedVisibleSize(index: number): number | undefined {
|
||||
|
||||
@@ -27,11 +27,11 @@ export interface IIconLabelValueOptions {
|
||||
}
|
||||
|
||||
class FastLabelNode {
|
||||
private disposed: boolean;
|
||||
private _textContent: string;
|
||||
private _className: string;
|
||||
private _title: string;
|
||||
private _empty: boolean;
|
||||
private disposed: boolean | undefined;
|
||||
private _textContent: string | undefined;
|
||||
private _className: string | undefined;
|
||||
private _title: string | undefined;
|
||||
private _empty: boolean | undefined;
|
||||
|
||||
constructor(private _element: HTMLElement) {
|
||||
}
|
||||
@@ -89,7 +89,7 @@ export class IconLabel extends Disposable {
|
||||
private domNode: FastLabelNode;
|
||||
private labelDescriptionContainer: FastLabelNode;
|
||||
private labelNode: FastLabelNode | HighlightedLabel;
|
||||
private descriptionNode: FastLabelNode | HighlightedLabel;
|
||||
private descriptionNode: FastLabelNode | HighlightedLabel | undefined;
|
||||
private descriptionNodeFactory: () => FastLabelNode | HighlightedLabel;
|
||||
|
||||
constructor(container: HTMLElement, options?: IIconLabelCreationOptions) {
|
||||
|
||||
@@ -64,6 +64,10 @@
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.monaco-inputbox > .wrapper > textarea.input.empty {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.monaco-inputbox > .wrapper > textarea.input::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@@ -171,7 +171,7 @@ export class InputBox extends Widget {
|
||||
let tagName = this.options.flexibleHeight ? 'textarea' : 'input';
|
||||
|
||||
let wrapper = dom.append(this.element, $('.wrapper'));
|
||||
this.input = dom.append(wrapper, $(tagName + '.input'));
|
||||
this.input = dom.append(wrapper, $(tagName + '.input.empty'));
|
||||
this.input.setAttribute('autocorrect', 'off');
|
||||
this.input.setAttribute('autocapitalize', 'off');
|
||||
this.input.setAttribute('spellcheck', 'false');
|
||||
@@ -242,13 +242,7 @@ export class InputBox extends Widget {
|
||||
});
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
if (!this.input) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.updateMirror();
|
||||
}, 0);
|
||||
setTimeout(() => this.updateMirror(), 0);
|
||||
|
||||
// Support actions
|
||||
if (this.options.actions) {
|
||||
@@ -270,21 +264,18 @@ export class InputBox extends Widget {
|
||||
}
|
||||
|
||||
public setPlaceHolder(placeHolder: string): void {
|
||||
if (this.input) {
|
||||
this.input.setAttribute('placeholder', placeHolder);
|
||||
this.input.title = placeHolder;
|
||||
}
|
||||
this.placeholder = placeHolder;
|
||||
this.input.setAttribute('placeholder', placeHolder);
|
||||
this.input.title = placeHolder;
|
||||
}
|
||||
|
||||
public setAriaLabel(label: string): void {
|
||||
this.ariaLabel = label;
|
||||
|
||||
if (this.input) {
|
||||
if (label) {
|
||||
this.input.setAttribute('aria-label', this.ariaLabel);
|
||||
} else {
|
||||
this.input.removeAttribute('aria-label');
|
||||
}
|
||||
if (label) {
|
||||
this.input.setAttribute('aria-label', this.ariaLabel);
|
||||
} else {
|
||||
this.input.removeAttribute('aria-label');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -550,6 +541,7 @@ export class InputBox extends Widget {
|
||||
|
||||
this.validate();
|
||||
this.updateMirror();
|
||||
dom.toggleClass(this.input, 'empty', !this.value);
|
||||
|
||||
if (this.state === 'open' && this.contextViewProvider) {
|
||||
this.contextViewProvider.layout();
|
||||
@@ -561,7 +553,7 @@ export class InputBox extends Widget {
|
||||
return;
|
||||
}
|
||||
|
||||
const value = this.value || this.placeholder;
|
||||
const value = this.value;
|
||||
const lastCharCode = value.charCodeAt(value.length - 1);
|
||||
const suffix = lastCharCode === 10 ? ' ' : '';
|
||||
const mirrorTextContent = value + suffix;
|
||||
@@ -594,20 +586,18 @@ export class InputBox extends Widget {
|
||||
}
|
||||
|
||||
protected applyStyles(): void {
|
||||
if (this.element) {
|
||||
const background = this.inputBackground ? this.inputBackground.toString() : null;
|
||||
const foreground = this.inputForeground ? this.inputForeground.toString() : null;
|
||||
const border = this.inputBorder ? this.inputBorder.toString() : null;
|
||||
const background = this.inputBackground ? this.inputBackground.toString() : null;
|
||||
const foreground = this.inputForeground ? this.inputForeground.toString() : null;
|
||||
const border = this.inputBorder ? this.inputBorder.toString() : null;
|
||||
|
||||
this.element.style.backgroundColor = background;
|
||||
this.element.style.color = foreground;
|
||||
this.input.style.backgroundColor = background;
|
||||
this.input.style.color = foreground;
|
||||
this.element.style.backgroundColor = background;
|
||||
this.element.style.color = foreground;
|
||||
this.input.style.backgroundColor = background;
|
||||
this.input.style.color = foreground;
|
||||
|
||||
this.element.style.borderWidth = border ? '1px' : null;
|
||||
this.element.style.borderStyle = border ? 'solid' : null;
|
||||
this.element.style.borderColor = border;
|
||||
}
|
||||
this.element.style.borderWidth = border ? '1px' : null;
|
||||
this.element.style.borderStyle = border ? 'solid' : null;
|
||||
this.element.style.borderColor = border;
|
||||
}
|
||||
|
||||
public layout(): void {
|
||||
@@ -625,16 +615,27 @@ export class InputBox extends Widget {
|
||||
}
|
||||
}
|
||||
|
||||
public insertAtCursor(text: string): void {
|
||||
const inputElement = this.inputElement;
|
||||
const start = inputElement.selectionStart;
|
||||
const end = inputElement.selectionEnd;
|
||||
const content = inputElement.value;
|
||||
|
||||
if (start !== null && end !== null) {
|
||||
this.value = content.substr(0, start) + text + content.substr(end);
|
||||
inputElement.setSelectionRange(start + 1, start + 1);
|
||||
this.layout();
|
||||
}
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
this._hideMessage();
|
||||
|
||||
this.element = null!; // StrictNullOverride: nulling out ok in dispose
|
||||
this.input = null!; // StrictNullOverride: nulling out ok in dispose
|
||||
this.contextViewProvider = undefined;
|
||||
this.message = null;
|
||||
this.validation = undefined;
|
||||
this.state = null!; // StrictNullOverride: nulling out ok in dispose
|
||||
this.actionbar = undefined;
|
||||
|
||||
if (this.actionbar) {
|
||||
this.actionbar.dispose();
|
||||
}
|
||||
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@@ -105,3 +105,10 @@ export interface IListDragAndDrop<T> {
|
||||
onDragOver(data: IDragAndDropData, targetElement: T | undefined, targetIndex: number | undefined, originalEvent: DragEvent): boolean | IListDragOverReaction;
|
||||
drop(data: IDragAndDropData, targetElement: T | undefined, targetIndex: number | undefined, originalEvent: DragEvent): void;
|
||||
}
|
||||
|
||||
export class ListError extends Error {
|
||||
|
||||
constructor(user: string, message: string) {
|
||||
super(`ListError [${user}] ${message}`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -76,13 +76,14 @@ export class PagedList<T> implements IDisposable {
|
||||
private _model!: IPagedModel<T>;
|
||||
|
||||
constructor(
|
||||
user: string,
|
||||
container: HTMLElement,
|
||||
virtualDelegate: IListVirtualDelegate<number>,
|
||||
renderers: IPagedRenderer<T, any>[],
|
||||
options: IListOptions<any> = {}
|
||||
) {
|
||||
const pagedRenderers = renderers.map(r => new PagedRenderer<T, ITemplateData<T>>(r, () => this.model));
|
||||
this.list = new List(container, virtualDelegate, pagedRenderers, options);
|
||||
this.list = new List(user, container, virtualDelegate, pagedRenderers, options);
|
||||
}
|
||||
|
||||
getHTMLElement(): HTMLElement {
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { getOrDefault } from 'vs/base/common/objects';
|
||||
import { IDisposable, dispose, Disposable, toDisposable } from 'vs/base/common/lifecycle';
|
||||
import { IDisposable, dispose, Disposable, toDisposable, DisposableStore } from 'vs/base/common/lifecycle';
|
||||
import { Gesture, EventType as TouchEventType, GestureEvent } from 'vs/base/browser/touch';
|
||||
import * as DOM from 'vs/base/browser/dom';
|
||||
import { Event, Emitter } from 'vs/base/common/event';
|
||||
@@ -188,7 +188,7 @@ export class ListView<T> implements ISpliceable<T>, IDisposable {
|
||||
private currentDragFeedbackDisposable: IDisposable = Disposable.None;
|
||||
private onDragLeaveTimeout: IDisposable = Disposable.None;
|
||||
|
||||
private disposables: IDisposable[];
|
||||
private readonly disposables: DisposableStore = new DisposableStore();
|
||||
|
||||
private _onDidChangeContentHeight = new Emitter<number>();
|
||||
readonly onDidChangeContentHeight: Event<number> = Event.latch(this._onDidChangeContentHeight.event);
|
||||
@@ -214,7 +214,7 @@ export class ListView<T> implements ISpliceable<T>, IDisposable {
|
||||
this.renderers.set(renderer.templateId, renderer);
|
||||
}
|
||||
|
||||
this.cache = new RowCache(this.renderers);
|
||||
this.cache = this.disposables.add(new RowCache(this.renderers));
|
||||
|
||||
this.lastRenderTop = 0;
|
||||
this.lastRenderHeight = 0;
|
||||
@@ -238,18 +238,16 @@ export class ListView<T> implements ISpliceable<T>, IDisposable {
|
||||
this.rowsContainer.className = 'monaco-list-rows';
|
||||
Gesture.addTarget(this.rowsContainer);
|
||||
|
||||
this.scrollableElement = new ScrollableElement(this.rowsContainer, {
|
||||
this.scrollableElement = this.disposables.add(new ScrollableElement(this.rowsContainer, {
|
||||
alwaysConsumeMouseWheel: true,
|
||||
horizontal: this.horizontalScrolling ? ScrollbarVisibility.Auto : ScrollbarVisibility.Hidden,
|
||||
vertical: getOrDefault(options, o => o.verticalScrollMode, DefaultOptions.verticalScrollMode),
|
||||
useShadows: getOrDefault(options, o => o.useShadows, DefaultOptions.useShadows)
|
||||
});
|
||||
}));
|
||||
|
||||
this.domNode.appendChild(this.scrollableElement.getDomNode());
|
||||
container.appendChild(this.domNode);
|
||||
|
||||
this.disposables = [this.rangeMap, this.scrollableElement, this.cache];
|
||||
|
||||
this.scrollableElement.onScroll(this.onScroll, this, this.disposables);
|
||||
domEvent(this.rowsContainer, TouchEventType.Change)(this.onTouchChange, this, this.disposables);
|
||||
|
||||
@@ -1178,6 +1176,6 @@ export class ListView<T> implements ISpliceable<T>, IDisposable {
|
||||
this.domNode.parentNode.removeChild(this.domNode);
|
||||
}
|
||||
|
||||
this.disposables = dispose(this.disposables);
|
||||
dispose(this.disposables);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ import { KeyCode } from 'vs/base/common/keyCodes';
|
||||
import { StandardKeyboardEvent, IKeyboardEvent } from 'vs/base/browser/keyboardEvent';
|
||||
import { Event, Emitter, EventBufferer } from 'vs/base/common/event';
|
||||
import { domEvent } from 'vs/base/browser/event';
|
||||
import { IListVirtualDelegate, IListRenderer, IListEvent, IListContextMenuEvent, IListMouseEvent, IListTouchEvent, IListGestureEvent, IIdentityProvider, IKeyboardNavigationLabelProvider, IListDragAndDrop, IListDragOverReaction, ListAriaRootRole } from './list';
|
||||
import { IListVirtualDelegate, IListRenderer, IListEvent, IListContextMenuEvent, IListMouseEvent, IListTouchEvent, IListGestureEvent, IIdentityProvider, IKeyboardNavigationLabelProvider, IListDragAndDrop, IListDragOverReaction, ListAriaRootRole, ListError } from './list';
|
||||
import { ListView, IListViewOptions, IListViewDragAndDrop, IAriaProvider } from './listView';
|
||||
import { Color } from 'vs/base/common/color';
|
||||
import { mixin } from 'vs/base/common/objects';
|
||||
@@ -862,7 +862,7 @@ export interface IListStyles {
|
||||
}
|
||||
|
||||
const defaultStyles: IListStyles = {
|
||||
listFocusBackground: Color.fromHex('#073655'),
|
||||
listFocusBackground: Color.fromHex('#7FB0D0'),
|
||||
listActiveSelectionBackground: Color.fromHex('#0E639C'),
|
||||
listActiveSelectionForeground: Color.fromHex('#FFFFFF'),
|
||||
listFocusAndSelectionBackground: Color.fromHex('#094771'),
|
||||
@@ -1170,6 +1170,7 @@ export class List<T> implements ISpliceable<T>, IDisposable {
|
||||
readonly onDidDispose: Event<void> = this._onDidDispose.event;
|
||||
|
||||
constructor(
|
||||
private user: string,
|
||||
container: HTMLElement,
|
||||
virtualDelegate: IListVirtualDelegate<T>,
|
||||
renderers: IListRenderer<any /* TODO@joao */, any>[],
|
||||
@@ -1261,11 +1262,11 @@ export class List<T> implements ISpliceable<T>, IDisposable {
|
||||
|
||||
splice(start: number, deleteCount: number, elements: T[] = []): void {
|
||||
if (start < 0 || start > this.view.length) {
|
||||
throw new Error(`Invalid start index: ${start}`);
|
||||
throw new ListError(this.user, `Invalid start index: ${start}`);
|
||||
}
|
||||
|
||||
if (deleteCount < 0) {
|
||||
throw new Error(`Invalid delete count: ${deleteCount}`);
|
||||
throw new ListError(this.user, `Invalid delete count: ${deleteCount}`);
|
||||
}
|
||||
|
||||
if (deleteCount === 0 && elements.length === 0) {
|
||||
@@ -1348,7 +1349,7 @@ export class List<T> implements ISpliceable<T>, IDisposable {
|
||||
setSelection(indexes: number[], browserEvent?: UIEvent): void {
|
||||
for (const index of indexes) {
|
||||
if (index < 0 || index >= this.length) {
|
||||
throw new Error(`Invalid index ${index}`);
|
||||
throw new ListError(this.user, `Invalid index ${index}`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1366,7 +1367,7 @@ export class List<T> implements ISpliceable<T>, IDisposable {
|
||||
setFocus(indexes: number[], browserEvent?: UIEvent): void {
|
||||
for (const index of indexes) {
|
||||
if (index < 0 || index >= this.length) {
|
||||
throw new Error(`Invalid index ${index}`);
|
||||
throw new ListError(this.user, `Invalid index ${index}`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1518,7 +1519,7 @@ export class List<T> implements ISpliceable<T>, IDisposable {
|
||||
|
||||
reveal(index: number, relativeTop?: number): void {
|
||||
if (index < 0 || index >= this.length) {
|
||||
throw new Error(`Invalid index ${index}`);
|
||||
throw new ListError(this.user, `Invalid index ${index}`);
|
||||
}
|
||||
|
||||
const scrollTop = this.view.getScrollTop();
|
||||
@@ -1547,7 +1548,7 @@ export class List<T> implements ISpliceable<T>, IDisposable {
|
||||
*/
|
||||
getRelativeTop(index: number): number | null {
|
||||
if (index < 0 || index >= this.length) {
|
||||
throw new Error(`Invalid index ${index}`);
|
||||
throw new ListError(this.user, `Invalid index ${index}`);
|
||||
}
|
||||
|
||||
const scrollTop = this.view.getScrollTop();
|
||||
@@ -1574,7 +1575,7 @@ export class List<T> implements ISpliceable<T>, IDisposable {
|
||||
open(indexes: number[], browserEvent?: UIEvent): void {
|
||||
for (const index of indexes) {
|
||||
if (index < 0 || index >= this.length) {
|
||||
throw new Error(`Invalid index ${index}`);
|
||||
throw new ListError(this.user, `Invalid index ${index}`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1584,7 +1585,7 @@ export class List<T> implements ISpliceable<T>, IDisposable {
|
||||
pin(indexes: number[]): void {
|
||||
for (const index of indexes) {
|
||||
if (index < 0 || index >= this.length) {
|
||||
throw new Error(`Invalid index ${index}`);
|
||||
throw new ListError(this.user, `Invalid index ${index}`);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -186,8 +186,4 @@ export class RangeMap {
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
dispose() {
|
||||
this.groups = null!; // StrictNullOverride: nulling out ok in dispose
|
||||
}
|
||||
}
|
||||
|
||||
@@ -79,11 +79,7 @@ export class RowCache<T> implements IDisposable {
|
||||
return result;
|
||||
}
|
||||
|
||||
private garbageCollect(): void {
|
||||
if (!this.renderers) {
|
||||
return;
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
this.cache.forEach((cachedRows, templateId) => {
|
||||
for (const cachedRow of cachedRows) {
|
||||
const renderer = this.getRenderer(templateId);
|
||||
@@ -96,12 +92,6 @@ export class RowCache<T> implements IDisposable {
|
||||
this.cache.clear();
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
this.garbageCollect();
|
||||
this.cache.clear();
|
||||
this.renderers = null!; // StrictNullOverride: nulling out ok in dispose
|
||||
}
|
||||
|
||||
private getRenderer(templateId: string): IListRenderer<T, any> {
|
||||
const renderer = this.renderers.get(templateId);
|
||||
if (!renderer) {
|
||||
@@ -109,4 +99,4 @@ export class RowCache<T> implements IDisposable {
|
||||
}
|
||||
return renderer;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -758,7 +758,7 @@ export class MenuBar extends Disposable {
|
||||
if (menuBarMenu.titleElement.children.length) {
|
||||
let child = menuBarMenu.titleElement.children.item(0) as HTMLElement;
|
||||
if (child) {
|
||||
child.style.textDecoration = (this.options.alwaysOnMnemonics || visible) ? 'underline' : null;
|
||||
child.style.textDecoration = (this.options.alwaysOnMnemonics || visible) ? 'underline' : '';
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -10,5 +10,5 @@
|
||||
}
|
||||
|
||||
.octicon-animation-spin {
|
||||
animation: octicon-spin 2s linear infinite;
|
||||
animation: octicon-spin 1.5s linear infinite;
|
||||
}
|
||||
|
||||
@@ -10,10 +10,6 @@ body {
|
||||
font-family: var(--version) !important;
|
||||
}
|
||||
|
||||
body[data-octicons-update="enabled"] .monaco-workbench .part.statusbar > .items-container > .statusbar-item span.octicon {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
body[data-octicons-update="enabled"] .monaco-workbench .part.statusbar > .items-container > .statusbar-item > a {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
@font-face {
|
||||
font-family: "octicons2";
|
||||
src: url("./octicons2.ttf?81972c7f658ef56eeefe672e514d35a3") format("truetype"),
|
||||
url("./octicons2.svg?81972c7f658ef56eeefe672e514d35a3#octicons2") format("svg");
|
||||
src: url("./octicons2.ttf?90586c9ac0aa804395e9c9c0d15d1094") format("truetype");
|
||||
}
|
||||
|
||||
.octicon, .mega-octicon {
|
||||
@@ -247,5 +246,6 @@ body[data-octicons-update="enabled"] .octicon-warning:before { content: "\f02d"
|
||||
body[data-octicons-update="enabled"] .octicon-controls:before { content: "\26ad" }
|
||||
body[data-octicons-update="enabled"] .octicon-event:before { content: "\26ae" }
|
||||
body[data-octicons-update="enabled"] .octicon-record-keys:before { content: "\26af" }
|
||||
body[data-octicons-update="enabled"] .octicon-archive:before { content: "\f101" }
|
||||
body[data-octicons-update="enabled"] .octicon-arrow-both:before { content: "\f102" }
|
||||
body[data-octicons-update="enabled"] .octicon-Vector:before { content: "\f101" }
|
||||
body[data-octicons-update="enabled"] .octicon-archive:before { content: "\f102" }
|
||||
body[data-octicons-update="enabled"] .octicon-arrow-both:before { content: "\f103" }
|
||||
|
||||
File diff suppressed because one or more lines are too long
|
Before Width: | Height: | Size: 227 KiB |
Binary file not shown.
@@ -36,8 +36,8 @@ const defaultOpts = {
|
||||
export class ProgressBar extends Disposable {
|
||||
private options: IProgressBarOptions;
|
||||
private workedVal: number;
|
||||
private element: HTMLElement;
|
||||
private bit: HTMLElement;
|
||||
private element!: HTMLElement;
|
||||
private bit!: HTMLElement;
|
||||
private totalWork: number | undefined;
|
||||
private progressBarBackground: Color | undefined;
|
||||
private showDelayedScheduler: RunOnceScheduler;
|
||||
@@ -232,4 +232,4 @@ export class ProgressBar extends Disposable {
|
||||
this.bit.style.backgroundColor = background;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -143,7 +143,7 @@ export class Sash extends Disposable {
|
||||
this._register(domEvent(this.el, EventType.Start)(this.onTouchStart, this));
|
||||
|
||||
if (isIPad) {
|
||||
// see also http://ux.stackexchange.com/questions/39023/what-is-the-optimum-button-size-of-touch-screen-applications
|
||||
// see also https://ux.stackexchange.com/questions/39023/what-is-the-optimum-button-size-of-touch-screen-applications
|
||||
addClass(this.el, 'touch');
|
||||
}
|
||||
|
||||
@@ -179,27 +179,8 @@ export class Sash extends Disposable {
|
||||
|
||||
let isMultisashResize = false;
|
||||
|
||||
if (this.linkedSash && !(e as any).__linkedSashEvent) {
|
||||
(e as any).__linkedSashEvent = true;
|
||||
this.linkedSash.onMouseDown(e);
|
||||
}
|
||||
|
||||
if (!(e as any).__orthogonalSashEvent) {
|
||||
let orthogonalSash: Sash | undefined;
|
||||
|
||||
if (this.orientation === Orientation.VERTICAL) {
|
||||
if (e.offsetY <= 4) {
|
||||
orthogonalSash = this.orthogonalStartSash;
|
||||
} else if (e.offsetY >= this.el.clientHeight - 4) {
|
||||
orthogonalSash = this.orthogonalEndSash;
|
||||
}
|
||||
} else {
|
||||
if (e.offsetX <= 4) {
|
||||
orthogonalSash = this.orthogonalStartSash;
|
||||
} else if (e.offsetX >= this.el.clientWidth - 4) {
|
||||
orthogonalSash = this.orthogonalEndSash;
|
||||
}
|
||||
}
|
||||
const orthogonalSash = this.getOrthogonalSash(e);
|
||||
|
||||
if (orthogonalSash) {
|
||||
isMultisashResize = true;
|
||||
@@ -208,6 +189,11 @@ export class Sash extends Disposable {
|
||||
}
|
||||
}
|
||||
|
||||
if (this.linkedSash && !(e as any).__linkedSashEvent) {
|
||||
(e as any).__linkedSashEvent = true;
|
||||
this.linkedSash.onMouseDown(e);
|
||||
}
|
||||
|
||||
if (!this.state) {
|
||||
return;
|
||||
}
|
||||
@@ -295,7 +281,17 @@ export class Sash extends Disposable {
|
||||
domEvent(window, 'mouseup')(onMouseUp, null, disposables);
|
||||
}
|
||||
|
||||
private onMouseDoubleClick(event: MouseEvent): void {
|
||||
private onMouseDoubleClick(e: MouseEvent): void {
|
||||
const orthogonalSash = this.getOrthogonalSash(e);
|
||||
|
||||
if (orthogonalSash) {
|
||||
orthogonalSash._onDidReset.fire();
|
||||
}
|
||||
|
||||
if (this.linkedSash) {
|
||||
this.linkedSash._onDidReset.fire();
|
||||
}
|
||||
|
||||
this._onDidReset.fire();
|
||||
}
|
||||
|
||||
@@ -386,13 +382,26 @@ export class Sash extends Disposable {
|
||||
toggleClass(this.el, 'orthogonal-end', state !== SashState.Disabled);
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
super.dispose();
|
||||
|
||||
if (this.el && this.el.parentElement) {
|
||||
this.el.parentElement.removeChild(this.el);
|
||||
private getOrthogonalSash(e: MouseEvent): Sash | undefined {
|
||||
if (this.orientation === Orientation.VERTICAL) {
|
||||
if (e.offsetY <= 4) {
|
||||
return this.orthogonalStartSash;
|
||||
} else if (e.offsetY >= this.el.clientHeight - 4) {
|
||||
return this.orthogonalEndSash;
|
||||
}
|
||||
} else {
|
||||
if (e.offsetX <= 4) {
|
||||
return this.orthogonalStartSash;
|
||||
} else if (e.offsetX >= this.el.clientWidth - 4) {
|
||||
return this.orthogonalEndSash;
|
||||
}
|
||||
}
|
||||
|
||||
this.el = null!; // StrictNullOverride: nulling out ok in dispose
|
||||
return undefined;
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
super.dispose();
|
||||
this.el.remove();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -737,7 +737,7 @@ export class SelectBoxList extends Disposable implements ISelectBoxDelegate, ILi
|
||||
|
||||
this.listRenderer = new SelectListRenderer();
|
||||
|
||||
this.selectList = new List(this.selectDropDownListContainer, this, [this.listRenderer], {
|
||||
this.selectList = new List('SelectBoxCustom', this.selectDropDownListContainer, this, [this.listRenderer], {
|
||||
ariaLabel: this.selectBoxOptions.ariaLabel,
|
||||
useShadows: false,
|
||||
verticalScrollMode: ScrollbarVisibility.Visible,
|
||||
|
||||
@@ -21,24 +21,15 @@
|
||||
}
|
||||
|
||||
.monaco-split-view2 > .split-view-container {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.monaco-split-view2.vertical > .split-view-container {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.monaco-split-view2.horizontal > .split-view-container {
|
||||
flex-direction: row;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.monaco-split-view2 > .split-view-container > .split-view-view {
|
||||
white-space: initial;
|
||||
flex: none;
|
||||
position: relative;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.monaco-split-view2 > .split-view-container > .split-view-view:not(.visible) {
|
||||
@@ -51,7 +42,6 @@
|
||||
|
||||
.monaco-split-view2.horizontal > .split-view-container > .split-view-view {
|
||||
height: 100%;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.monaco-split-view2.separator-border > .split-view-container > .split-view-view:not(:first-child)::before {
|
||||
|
||||
@@ -125,15 +125,13 @@ abstract class ViewItem {
|
||||
}
|
||||
}
|
||||
|
||||
layout(_orthogonalSize: number | undefined): void {
|
||||
this.container.scrollTop = 0;
|
||||
this.container.scrollLeft = 0;
|
||||
}
|
||||
|
||||
layoutView(orthogonalSize: number | undefined): void {
|
||||
layout(position: number, orthogonalSize: number | undefined): void {
|
||||
this.layoutContainer(position);
|
||||
this.view.layout(this.size, orthogonalSize);
|
||||
}
|
||||
|
||||
abstract layoutContainer(position: number): void;
|
||||
|
||||
dispose(): IView {
|
||||
this.disposable.dispose();
|
||||
return this.view;
|
||||
@@ -142,19 +140,17 @@ abstract class ViewItem {
|
||||
|
||||
class VerticalViewItem extends ViewItem {
|
||||
|
||||
layout(orthogonalSize: number | undefined): void {
|
||||
super.layout(orthogonalSize);
|
||||
layoutContainer(position: number): void {
|
||||
this.container.style.top = `${position}px`;
|
||||
this.container.style.height = `${this.size}px`;
|
||||
this.layoutView(orthogonalSize);
|
||||
}
|
||||
}
|
||||
|
||||
class HorizontalViewItem extends ViewItem {
|
||||
|
||||
layout(orthogonalSize: number | undefined): void {
|
||||
super.layout(orthogonalSize);
|
||||
layoutContainer(position: number): void {
|
||||
this.container.style.left = `${position}px`;
|
||||
this.container.style.width = `${this.size}px`;
|
||||
this.layoutView(orthogonalSize);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -853,7 +849,12 @@ export class SplitView extends Disposable {
|
||||
this.contentSize = this.viewItems.reduce((r, i) => r + i.size, 0);
|
||||
|
||||
// Layout views
|
||||
this.viewItems.forEach(item => item.layout(this.orthogonalSize));
|
||||
let position = 0;
|
||||
|
||||
for (const viewItem of this.viewItems) {
|
||||
viewItem.layout(position, this.orthogonalSize);
|
||||
position += viewItem.size;
|
||||
}
|
||||
|
||||
// Layout sashes
|
||||
this.sashItems.forEach(item => item.sash.layout());
|
||||
@@ -910,7 +911,7 @@ export class SplitView extends Disposable {
|
||||
position += this.viewItems[i].size;
|
||||
|
||||
if (this.sashItems[i].sash === sash) {
|
||||
return position;
|
||||
return Math.min(position, this.contentSize - 2);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -98,9 +98,11 @@ class TreeNodeListDragAndDrop<T, TFilterData, TRef> implements IListDragAndDrop<
|
||||
}
|
||||
|
||||
if (result.bubble === TreeDragOverBubble.Up) {
|
||||
const parentNode = targetNode.parent;
|
||||
const model = this.modelProvider();
|
||||
const parentIndex = parentNode && model.getListIndex(model.getNodeLocation(parentNode));
|
||||
const ref = model.getNodeLocation(targetNode);
|
||||
const parentRef = model.getParentNodeLocation(ref);
|
||||
const parentNode = model.getNode(parentRef);
|
||||
const parentIndex = parentRef && model.getListIndex(parentRef);
|
||||
|
||||
return this.onDragOver(data, parentNode, parentIndex, originalEvent, false);
|
||||
}
|
||||
@@ -155,7 +157,12 @@ function asListOptions<T, TFilterData, TRef>(modelProvider: () => ITreeModel<T,
|
||||
enableKeyboardNavigation: options.simpleKeyboardNavigation,
|
||||
ariaProvider: {
|
||||
getSetSize(node) {
|
||||
return node.parent!.visibleChildrenCount;
|
||||
const model = modelProvider();
|
||||
const ref = model.getNodeLocation(node);
|
||||
const parentRef = model.getParentNodeLocation(ref);
|
||||
const parentNode = model.getNode(parentRef);
|
||||
|
||||
return parentNode.visibleChildrenCount;
|
||||
},
|
||||
getPosInSet(node) {
|
||||
return node.visibleChildIndex + 1;
|
||||
@@ -233,7 +240,7 @@ class EventCollection<T> implements Collection<T> {
|
||||
}
|
||||
}
|
||||
|
||||
class TreeRenderer<T, TFilterData, TTemplateData> implements IListRenderer<ITreeNode<T, TFilterData>, ITreeListTemplateData<TTemplateData>> {
|
||||
class TreeRenderer<T, TFilterData, TRef, TTemplateData> implements IListRenderer<ITreeNode<T, TFilterData>, ITreeListTemplateData<TTemplateData>> {
|
||||
|
||||
private static DefaultIndent = 8;
|
||||
|
||||
@@ -251,6 +258,7 @@ class TreeRenderer<T, TFilterData, TTemplateData> implements IListRenderer<ITree
|
||||
|
||||
constructor(
|
||||
private renderer: ITreeRenderer<T, TFilterData, TTemplateData>,
|
||||
private modelProvider: () => ITreeModel<T, TFilterData, TRef>,
|
||||
onDidChangeCollapseState: Event<ICollapseStateChangeEvent<T, TFilterData>>,
|
||||
private activeNodes: Collection<ITreeNode<T, TFilterData>>,
|
||||
options: ITreeRendererOptions = {}
|
||||
@@ -381,10 +389,19 @@ class TreeRenderer<T, TFilterData, TTemplateData> implements IListRenderer<ITree
|
||||
}
|
||||
|
||||
const disposableStore = new DisposableStore();
|
||||
const model = this.modelProvider();
|
||||
|
||||
let node = target;
|
||||
|
||||
while (node.parent && node.parent.parent) {
|
||||
const parent = node.parent;
|
||||
while (true) {
|
||||
const ref = model.getNodeLocation(node);
|
||||
const parentRef = model.getParentNodeLocation(ref);
|
||||
|
||||
if (!parentRef) {
|
||||
break;
|
||||
}
|
||||
|
||||
const parent = model.getNode(parentRef);
|
||||
const guide = $<HTMLDivElement>('.indent-guide', { style: `width: ${this.indent}px` });
|
||||
|
||||
if (this.activeIndentNodes.has(parent)) {
|
||||
@@ -412,12 +429,16 @@ class TreeRenderer<T, TFilterData, TTemplateData> implements IListRenderer<ITree
|
||||
}
|
||||
|
||||
const set = new Set<ITreeNode<T, TFilterData>>();
|
||||
const model = this.modelProvider();
|
||||
|
||||
nodes.forEach(node => {
|
||||
const ref = model.getNodeLocation(node);
|
||||
const parentRef = model.getParentNodeLocation(ref);
|
||||
|
||||
if (node.collapsible && node.children.length > 0 && !node.collapsed) {
|
||||
set.add(node);
|
||||
} else if (node.parent) {
|
||||
set.add(node.parent);
|
||||
} else if (parentRef) {
|
||||
set.add(model.getNode(parentRef));
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1053,13 +1074,15 @@ class TreeNodeListMouseController<T, TFilterData, TRef> extends MouseController<
|
||||
return super.onPointer(e);
|
||||
}
|
||||
|
||||
const model = ((this.tree as any).model as ITreeModel<T, TFilterData, TRef>); // internal
|
||||
const location = model.getNodeLocation(node);
|
||||
const recursive = e.browserEvent.altKey;
|
||||
model.setCollapsed(location, undefined, recursive);
|
||||
if (node.collapsible) {
|
||||
const model = ((this.tree as any).model as ITreeModel<T, TFilterData, TRef>); // internal
|
||||
const location = model.getNodeLocation(node);
|
||||
const recursive = e.browserEvent.altKey;
|
||||
model.setCollapsed(location, undefined, recursive);
|
||||
|
||||
if (expandOnlyOnTwistieClick && onTwistie) {
|
||||
return;
|
||||
if (expandOnlyOnTwistieClick && onTwistie) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
super.onPointer(e);
|
||||
@@ -1087,6 +1110,7 @@ interface ITreeNodeListOptions<T, TFilterData, TRef> extends IListOptions<ITreeN
|
||||
class TreeNodeList<T, TFilterData, TRef> extends List<ITreeNode<T, TFilterData>> {
|
||||
|
||||
constructor(
|
||||
user: string,
|
||||
container: HTMLElement,
|
||||
virtualDelegate: IListVirtualDelegate<ITreeNode<T, TFilterData>>,
|
||||
renderers: IListRenderer<any /* TODO@joao */, any>[],
|
||||
@@ -1094,7 +1118,7 @@ class TreeNodeList<T, TFilterData, TRef> extends List<ITreeNode<T, TFilterData>>
|
||||
private selectionTrait: Trait<T>,
|
||||
options: ITreeNodeListOptions<T, TFilterData, TRef>
|
||||
) {
|
||||
super(container, virtualDelegate, renderers, options);
|
||||
super(user, container, virtualDelegate, renderers, options);
|
||||
}
|
||||
|
||||
protected createMouseController(options: ITreeNodeListOptions<T, TFilterData, TRef>): MouseController<ITreeNode<T, TFilterData>> {
|
||||
@@ -1150,7 +1174,7 @@ class TreeNodeList<T, TFilterData, TRef> extends List<ITreeNode<T, TFilterData>>
|
||||
export abstract class AbstractTree<T, TFilterData, TRef> implements IDisposable {
|
||||
|
||||
protected view: TreeNodeList<T, TFilterData, TRef>;
|
||||
private renderers: TreeRenderer<T, TFilterData, any>[];
|
||||
private renderers: TreeRenderer<T, TFilterData, TRef, any>[];
|
||||
protected model: ITreeModel<T, TFilterData, TRef>;
|
||||
private focus: Trait<T>;
|
||||
private selection: Trait<T>;
|
||||
@@ -1195,6 +1219,7 @@ export abstract class AbstractTree<T, TFilterData, TRef> implements IDisposable
|
||||
get onDidDispose(): Event<void> { return this.view.onDidDispose; }
|
||||
|
||||
constructor(
|
||||
user: string,
|
||||
container: HTMLElement,
|
||||
delegate: IListVirtualDelegate<T>,
|
||||
renderers: ITreeRenderer<T, TFilterData, any>[],
|
||||
@@ -1207,7 +1232,7 @@ export abstract class AbstractTree<T, TFilterData, TRef> implements IDisposable
|
||||
const activeNodes = new EventCollection(onDidChangeActiveNodes.event);
|
||||
this.disposables.push(activeNodes);
|
||||
|
||||
this.renderers = renderers.map(r => new TreeRenderer<T, TFilterData, any>(r, onDidChangeCollapseStateRelay.event, activeNodes, _options));
|
||||
this.renderers = renderers.map(r => new TreeRenderer<T, TFilterData, TRef, any>(r, () => this.model, onDidChangeCollapseStateRelay.event, activeNodes, _options));
|
||||
this.disposables.push(...this.renderers);
|
||||
|
||||
let filter: TypeFilter<T> | undefined;
|
||||
@@ -1220,9 +1245,9 @@ export abstract class AbstractTree<T, TFilterData, TRef> implements IDisposable
|
||||
|
||||
this.focus = new Trait(_options.identityProvider);
|
||||
this.selection = new Trait(_options.identityProvider);
|
||||
this.view = new TreeNodeList(container, treeDelegate, this.renderers, this.focus, this.selection, { ...asListOptions(() => this.model, _options), tree: this });
|
||||
this.view = new TreeNodeList(user, container, treeDelegate, this.renderers, this.focus, this.selection, { ...asListOptions(() => this.model, _options), tree: this });
|
||||
|
||||
this.model = this.createModel(this.view, _options);
|
||||
this.model = this.createModel(user, this.view, _options);
|
||||
onDidChangeCollapseStateRelay.input = this.model.onDidChangeCollapseState;
|
||||
|
||||
this.model.onDidSplice(e => {
|
||||
@@ -1379,7 +1404,9 @@ export abstract class AbstractTree<T, TFilterData, TRef> implements IDisposable
|
||||
// Tree navigation
|
||||
|
||||
getParentElement(location: TRef): T {
|
||||
return this.model.getParentElement(location);
|
||||
const parentRef = this.model.getParentNodeLocation(location);
|
||||
const parentNode = this.model.getNode(parentRef);
|
||||
return parentNode.element;
|
||||
}
|
||||
|
||||
getFirstElementChild(location: TRef): T | undefined {
|
||||
@@ -1416,6 +1443,10 @@ export abstract class AbstractTree<T, TFilterData, TRef> implements IDisposable
|
||||
return this.model.isCollapsible(location);
|
||||
}
|
||||
|
||||
setCollapsible(location: TRef, collapsible?: boolean): boolean {
|
||||
return this.model.setCollapsible(location, collapsible);
|
||||
}
|
||||
|
||||
isCollapsed(location: TRef): boolean {
|
||||
return this.model.isCollapsed(location);
|
||||
}
|
||||
@@ -1531,7 +1562,7 @@ export abstract class AbstractTree<T, TFilterData, TRef> implements IDisposable
|
||||
if (!didChange) {
|
||||
const parentLocation = this.model.getParentNodeLocation(location);
|
||||
|
||||
if (parentLocation === null) {
|
||||
if (!parentLocation) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1586,7 +1617,7 @@ export abstract class AbstractTree<T, TFilterData, TRef> implements IDisposable
|
||||
this.model.setCollapsed(location, undefined, recursive);
|
||||
}
|
||||
|
||||
protected abstract createModel(view: ISpliceable<ITreeNode<T, TFilterData>>, options: IAbstractTreeOptions<T, TFilterData>): ITreeModel<T, TFilterData, TRef>;
|
||||
protected abstract createModel(user: string, view: ISpliceable<ITreeNode<T, TFilterData>>, options: IAbstractTreeOptions<T, TFilterData>): ITreeModel<T, TFilterData, TRef>;
|
||||
|
||||
navigate(start?: TRef): ITreeNavigator<T> {
|
||||
return new TreeNavigator(this.view, this.model, start);
|
||||
@@ -1633,22 +1664,6 @@ class TreeNavigator<T extends NonNullable<any>, TFilterData, TRef> implements IT
|
||||
return this.current();
|
||||
}
|
||||
|
||||
parent(): T | null {
|
||||
if (this.index < 0 || this.index >= this.view.length) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const node = this.view.element(this.index);
|
||||
|
||||
if (!node.parent) {
|
||||
this.index = -1;
|
||||
return this.current();
|
||||
}
|
||||
|
||||
this.index = this.model.getListIndex(this.model.getNodeLocation(node.parent));
|
||||
return this.current();
|
||||
}
|
||||
|
||||
first(): T | null {
|
||||
this.index = 0;
|
||||
return this.current();
|
||||
|
||||
@@ -4,9 +4,9 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { ComposedTreeDelegate, IAbstractTreeOptions, IAbstractTreeOptionsUpdate } from 'vs/base/browser/ui/tree/abstractTree';
|
||||
import { ObjectTree, IObjectTreeOptions } from 'vs/base/browser/ui/tree/objectTree';
|
||||
import { ObjectTree, IObjectTreeOptions, CompressibleObjectTree, ICompressibleTreeRenderer } from 'vs/base/browser/ui/tree/objectTree';
|
||||
import { IListVirtualDelegate, IIdentityProvider, IListDragAndDrop, IListDragOverReaction } from 'vs/base/browser/ui/list/list';
|
||||
import { ITreeElement, ITreeNode, ITreeRenderer, ITreeEvent, ITreeMouseEvent, ITreeContextMenuEvent, ITreeSorter, ICollapseStateChangeEvent, IAsyncDataSource, ITreeDragAndDrop } from 'vs/base/browser/ui/tree/tree';
|
||||
import { ITreeElement, ITreeNode, ITreeRenderer, ITreeEvent, ITreeMouseEvent, ITreeContextMenuEvent, ITreeSorter, ICollapseStateChangeEvent, IAsyncDataSource, ITreeDragAndDrop, TreeError, WeakMapper } from 'vs/base/browser/ui/tree/tree';
|
||||
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
|
||||
import { Emitter, Event } from 'vs/base/common/event';
|
||||
import { timeout, CancelablePromise, createCancelablePromise } from 'vs/base/common/async';
|
||||
@@ -18,13 +18,14 @@ import { isPromiseCanceledError, onUnexpectedError } from 'vs/base/common/errors
|
||||
import { toggleClass } from 'vs/base/browser/dom';
|
||||
import { values } from 'vs/base/common/map';
|
||||
import { ScrollEvent } from 'vs/base/common/scrollable';
|
||||
import { ICompressedTreeNode, ICompressedTreeElement } from 'vs/base/browser/ui/tree/compressedObjectTreeModel';
|
||||
|
||||
interface IAsyncDataTreeNode<TInput, T> {
|
||||
element: TInput | T;
|
||||
readonly parent: IAsyncDataTreeNode<TInput, T> | null;
|
||||
readonly children: IAsyncDataTreeNode<TInput, T>[];
|
||||
readonly id?: string | null;
|
||||
loading: boolean;
|
||||
refreshPromise: Promise<void> | undefined;
|
||||
hasChildren: boolean;
|
||||
stale: boolean;
|
||||
slow: boolean;
|
||||
@@ -41,7 +42,7 @@ function createAsyncDataTreeNode<TInput, T>(props: IAsyncDataTreeNodeRequiredPro
|
||||
return {
|
||||
...props,
|
||||
children: [],
|
||||
loading: false,
|
||||
refreshPromise: undefined,
|
||||
stale: true,
|
||||
slow: false,
|
||||
collapsedByDefault: undefined
|
||||
@@ -66,10 +67,11 @@ interface IDataTreeListTemplateData<T> {
|
||||
templateData: T;
|
||||
}
|
||||
|
||||
type AsyncDataTreeNodeMapper<TInput, T, TFilterData> = WeakMapper<ITreeNode<IAsyncDataTreeNode<TInput, T> | null, TFilterData>, ITreeNode<TInput | T, TFilterData>>;
|
||||
|
||||
class AsyncDataTreeNodeWrapper<TInput, T, TFilterData> implements ITreeNode<TInput | T, TFilterData> {
|
||||
|
||||
get element(): T { return this.node.element!.element as T; }
|
||||
get parent(): ITreeNode<T, TFilterData> | undefined { return this.node.parent && new AsyncDataTreeNodeWrapper(this.node.parent); }
|
||||
get children(): ITreeNode<T, TFilterData>[] { return this.node.children.map(node => new AsyncDataTreeNodeWrapper(node)); }
|
||||
get depth(): number { return this.node.depth; }
|
||||
get visibleChildrenCount(): number { return this.node.visibleChildrenCount; }
|
||||
@@ -82,14 +84,15 @@ class AsyncDataTreeNodeWrapper<TInput, T, TFilterData> implements ITreeNode<TInp
|
||||
constructor(private node: ITreeNode<IAsyncDataTreeNode<TInput, T> | null, TFilterData>) { }
|
||||
}
|
||||
|
||||
class DataTreeRenderer<TInput, T, TFilterData, TTemplateData> implements ITreeRenderer<IAsyncDataTreeNode<TInput, T>, TFilterData, IDataTreeListTemplateData<TTemplateData>> {
|
||||
class AsyncDataTreeRenderer<TInput, T, TFilterData, TTemplateData> implements ITreeRenderer<IAsyncDataTreeNode<TInput, T>, TFilterData, IDataTreeListTemplateData<TTemplateData>> {
|
||||
|
||||
readonly templateId: string;
|
||||
private renderedNodes = new Map<IAsyncDataTreeNode<TInput, T>, IDataTreeListTemplateData<TTemplateData>>();
|
||||
private disposables: IDisposable[] = [];
|
||||
|
||||
constructor(
|
||||
private renderer: ITreeRenderer<T, TFilterData, TTemplateData>,
|
||||
protected renderer: ITreeRenderer<T, TFilterData, TTemplateData>,
|
||||
protected nodeMapper: AsyncDataTreeNodeMapper<TInput, T, TFilterData>,
|
||||
readonly onDidChangeTwistieState: Event<IAsyncDataTreeNode<TInput, T>>
|
||||
) {
|
||||
this.templateId = renderer.templateId;
|
||||
@@ -101,7 +104,7 @@ class DataTreeRenderer<TInput, T, TFilterData, TTemplateData> implements ITreeRe
|
||||
}
|
||||
|
||||
renderElement(node: ITreeNode<IAsyncDataTreeNode<TInput, T>, TFilterData>, index: number, templateData: IDataTreeListTemplateData<TTemplateData>, height: number | undefined): void {
|
||||
this.renderer.renderElement(new AsyncDataTreeNodeWrapper(node), index, templateData.templateData, height);
|
||||
this.renderer.renderElement(this.nodeMapper.map(node) as ITreeNode<T, TFilterData>, index, templateData.templateData, height);
|
||||
}
|
||||
|
||||
renderTwistie(element: IAsyncDataTreeNode<TInput, T>, twistieElement: HTMLElement): boolean {
|
||||
@@ -111,7 +114,7 @@ class DataTreeRenderer<TInput, T, TFilterData, TTemplateData> implements ITreeRe
|
||||
|
||||
disposeElement(node: ITreeNode<IAsyncDataTreeNode<TInput, T>, TFilterData>, index: number, templateData: IDataTreeListTemplateData<TTemplateData>, height: number | undefined): void {
|
||||
if (this.renderer.disposeElement) {
|
||||
this.renderer.disposeElement(new AsyncDataTreeNodeWrapper(node), index, templateData.templateData, height);
|
||||
this.renderer.disposeElement(this.nodeMapper.map(node) as ITreeNode<T, TFilterData>, index, templateData.templateData, height);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -243,25 +246,6 @@ function asObjectTreeOptions<TInput, T, TFilterData>(options?: IAsyncDataTreeOpt
|
||||
};
|
||||
}
|
||||
|
||||
function asTreeElement<TInput, T>(node: IAsyncDataTreeNode<TInput, T>, viewStateContext?: IAsyncDataTreeViewStateContext<TInput, T>): ITreeElement<IAsyncDataTreeNode<TInput, T>> {
|
||||
let collapsed: boolean | undefined;
|
||||
|
||||
if (viewStateContext && viewStateContext.viewState.expanded && node.id && viewStateContext.viewState.expanded.indexOf(node.id) > -1) {
|
||||
collapsed = false;
|
||||
} else {
|
||||
collapsed = node.collapsedByDefault;
|
||||
}
|
||||
|
||||
node.collapsedByDefault = undefined;
|
||||
|
||||
return {
|
||||
element: node,
|
||||
children: node.hasChildren ? Iterator.map(Iterator.fromArray(node.children), child => asTreeElement(child, viewStateContext)) : [],
|
||||
collapsible: node.hasChildren,
|
||||
collapsed
|
||||
};
|
||||
}
|
||||
|
||||
export interface IAsyncDataTreeOptionsUpdate extends IAbstractTreeOptionsUpdate { }
|
||||
|
||||
export interface IAsyncDataTreeOptions<T, TFilterData = void> extends IAsyncDataTreeOptionsUpdate, Pick<IAbstractTreeOptions<T, TFilterData>, Exclude<keyof IAbstractTreeOptions<T, TFilterData>, 'collapseByDefault'>> {
|
||||
@@ -304,7 +288,9 @@ export class AsyncDataTree<TInput, T, TFilterData = void> implements IDisposable
|
||||
private readonly autoExpandSingleChildren: boolean;
|
||||
|
||||
private readonly _onDidRender = new Emitter<void>();
|
||||
private readonly _onDidChangeNodeSlowState = new Emitter<IAsyncDataTreeNode<TInput, T>>();
|
||||
protected readonly _onDidChangeNodeSlowState = new Emitter<IAsyncDataTreeNode<TInput, T>>();
|
||||
|
||||
protected readonly nodeMapper: AsyncDataTreeNodeMapper<TInput, T, TFilterData> = new WeakMapper(node => new AsyncDataTreeNodeWrapper(node));
|
||||
|
||||
protected readonly disposables: IDisposable[] = [];
|
||||
|
||||
@@ -339,6 +325,7 @@ export class AsyncDataTree<TInput, T, TFilterData = void> implements IDisposable
|
||||
get onDidDispose(): Event<void> { return this.tree.onDidDispose; }
|
||||
|
||||
constructor(
|
||||
private user: string,
|
||||
container: HTMLElement,
|
||||
delegate: IListVirtualDelegate<T>,
|
||||
renderers: ITreeRenderer<T, TFilterData, any>[],
|
||||
@@ -350,11 +337,7 @@ export class AsyncDataTree<TInput, T, TFilterData = void> implements IDisposable
|
||||
this.sorter = options.sorter;
|
||||
this.collapseByDefault = options.collapseByDefault;
|
||||
|
||||
const objectTreeDelegate = new ComposedTreeDelegate<TInput | T, IAsyncDataTreeNode<TInput, T>>(delegate);
|
||||
const objectTreeRenderers = renderers.map(r => new DataTreeRenderer(r, this._onDidChangeNodeSlowState.event));
|
||||
const objectTreeOptions = asObjectTreeOptions<TInput, T, TFilterData>(options) || {};
|
||||
|
||||
this.tree = new ObjectTree(container, objectTreeDelegate, objectTreeRenderers, objectTreeOptions);
|
||||
this.tree = this.createTree(user, container, delegate, renderers, options);
|
||||
|
||||
this.root = createAsyncDataTreeNode({
|
||||
element: undefined!,
|
||||
@@ -374,6 +357,20 @@ export class AsyncDataTree<TInput, T, TFilterData = void> implements IDisposable
|
||||
this.tree.onDidChangeCollapseState(this._onDidChangeCollapseState, this, this.disposables);
|
||||
}
|
||||
|
||||
protected createTree(
|
||||
user: string,
|
||||
container: HTMLElement,
|
||||
delegate: IListVirtualDelegate<T>,
|
||||
renderers: ITreeRenderer<T, TFilterData, any>[],
|
||||
options: IAsyncDataTreeOptions<T, TFilterData>
|
||||
): ObjectTree<IAsyncDataTreeNode<TInput, T>, TFilterData> {
|
||||
const objectTreeDelegate = new ComposedTreeDelegate<TInput | T, IAsyncDataTreeNode<TInput, T>>(delegate);
|
||||
const objectTreeRenderers = renderers.map(r => new AsyncDataTreeRenderer(r, this.nodeMapper, this._onDidChangeNodeSlowState.event));
|
||||
const objectTreeOptions = asObjectTreeOptions<TInput, T, TFilterData>(options) || {};
|
||||
|
||||
return new ObjectTree(user, container, objectTreeDelegate, objectTreeRenderers, objectTreeOptions);
|
||||
}
|
||||
|
||||
updateOptions(options: IAsyncDataTreeOptionsUpdate = {}): void {
|
||||
this.tree.updateOptions(options);
|
||||
}
|
||||
@@ -450,7 +447,7 @@ export class AsyncDataTree<TInput, T, TFilterData = void> implements IDisposable
|
||||
|
||||
const viewStateContext = viewState && { viewState, focus: [], selection: [] } as IAsyncDataTreeViewStateContext<TInput, T>;
|
||||
|
||||
await this.updateChildren(input, true, viewStateContext);
|
||||
await this._updateChildren(input, true, viewStateContext);
|
||||
|
||||
if (viewStateContext) {
|
||||
this.tree.setFocus(viewStateContext.focus);
|
||||
@@ -462,13 +459,17 @@ export class AsyncDataTree<TInput, T, TFilterData = void> implements IDisposable
|
||||
}
|
||||
}
|
||||
|
||||
async updateChildren(element: TInput | T = this.root.element, recursive = true, viewStateContext?: IAsyncDataTreeViewStateContext<TInput, T>): Promise<void> {
|
||||
async updateChildren(element: TInput | T = this.root.element, recursive = true): Promise<void> {
|
||||
await this._updateChildren(element, recursive);
|
||||
}
|
||||
|
||||
private async _updateChildren(element: TInput | T = this.root.element, recursive = true, viewStateContext?: IAsyncDataTreeViewStateContext<TInput, T>): Promise<void> {
|
||||
if (typeof this.root.element === 'undefined') {
|
||||
throw new Error('Tree input not set');
|
||||
throw new TreeError(this.user, 'Tree input not set');
|
||||
}
|
||||
|
||||
if (this.root.loading) {
|
||||
await this.subTreeRefreshPromises.get(this.root)!;
|
||||
if (this.root.refreshPromise) {
|
||||
await this.root.refreshPromise;
|
||||
await Event.toPromise(this._onDidRender.event);
|
||||
}
|
||||
|
||||
@@ -505,7 +506,7 @@ export class AsyncDataTree<TInput, T, TFilterData = void> implements IDisposable
|
||||
getNode(element: TInput | T = this.root.element): ITreeNode<TInput | T, TFilterData> {
|
||||
const dataNode = this.getDataNode(element);
|
||||
const node = this.tree.getNode(dataNode === this.root ? null : dataNode);
|
||||
return new AsyncDataTreeNodeWrapper<TInput, T, TFilterData>(node);
|
||||
return this.nodeMapper.map(node);
|
||||
}
|
||||
|
||||
collapse(element: T, recursive: boolean = false): boolean {
|
||||
@@ -515,24 +516,29 @@ export class AsyncDataTree<TInput, T, TFilterData = void> implements IDisposable
|
||||
|
||||
async expand(element: T, recursive: boolean = false): Promise<boolean> {
|
||||
if (typeof this.root.element === 'undefined') {
|
||||
throw new Error('Tree input not set');
|
||||
throw new TreeError(this.user, 'Tree input not set');
|
||||
}
|
||||
|
||||
if (this.root.loading) {
|
||||
await this.subTreeRefreshPromises.get(this.root)!;
|
||||
if (this.root.refreshPromise) {
|
||||
await this.root.refreshPromise;
|
||||
await Event.toPromise(this._onDidRender.event);
|
||||
}
|
||||
|
||||
const node = this.getDataNode(element);
|
||||
|
||||
if (node !== this.root && !node.loading && !this.tree.isCollapsed(node)) {
|
||||
if (node.refreshPromise) {
|
||||
await this.root.refreshPromise;
|
||||
await Event.toPromise(this._onDidRender.event);
|
||||
}
|
||||
|
||||
if (node !== this.root && !node.refreshPromise && !this.tree.isCollapsed(node)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const result = this.tree.expand(node === this.root ? null : node, recursive);
|
||||
|
||||
if (node.loading) {
|
||||
await this.subTreeRefreshPromises.get(node)!;
|
||||
if (node.refreshPromise) {
|
||||
await this.root.refreshPromise;
|
||||
await Event.toPromise(this._onDidRender.event);
|
||||
}
|
||||
|
||||
@@ -643,7 +649,7 @@ export class AsyncDataTree<TInput, T, TFilterData = void> implements IDisposable
|
||||
const node: IAsyncDataTreeNode<TInput, T> | undefined = this.nodes.get((element === this.root.element ? null : element) as T);
|
||||
|
||||
if (!node) {
|
||||
throw new Error(`Data tree node not found: ${element}`);
|
||||
throw new TreeError(this.user, `Data tree node not found: ${element}`);
|
||||
}
|
||||
|
||||
return node;
|
||||
@@ -676,18 +682,18 @@ export class AsyncDataTree<TInput, T, TFilterData = void> implements IDisposable
|
||||
return result;
|
||||
}
|
||||
|
||||
result = this.doRefreshSubTree(node, recursive, viewStateContext);
|
||||
this.subTreeRefreshPromises.set(node, result);
|
||||
|
||||
try {
|
||||
await result;
|
||||
} finally {
|
||||
this.subTreeRefreshPromises.delete(node);
|
||||
}
|
||||
return this.doRefreshSubTree(node, recursive, viewStateContext);
|
||||
}
|
||||
|
||||
private async doRefreshSubTree(node: IAsyncDataTreeNode<TInput, T>, recursive: boolean, viewStateContext?: IAsyncDataTreeViewStateContext<TInput, T>): Promise<void> {
|
||||
node.loading = true;
|
||||
let done: () => void;
|
||||
node.refreshPromise = new Promise(c => done = c);
|
||||
this.subTreeRefreshPromises.set(node, node.refreshPromise);
|
||||
|
||||
node.refreshPromise.finally(() => {
|
||||
node.refreshPromise = undefined;
|
||||
this.subTreeRefreshPromises.delete(node);
|
||||
});
|
||||
|
||||
try {
|
||||
const childrenToRefresh = await this.doRefreshNode(node, recursive, viewStateContext);
|
||||
@@ -695,7 +701,7 @@ export class AsyncDataTree<TInput, T, TFilterData = void> implements IDisposable
|
||||
|
||||
await Promise.all(childrenToRefresh.map(child => this.doRefreshSubTree(child, recursive, viewStateContext)));
|
||||
} finally {
|
||||
node.loading = false;
|
||||
done!();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -866,16 +872,40 @@ export class AsyncDataTree<TInput, T, TFilterData = void> implements IDisposable
|
||||
}
|
||||
|
||||
private render(node: IAsyncDataTreeNode<TInput, T>, viewStateContext?: IAsyncDataTreeViewStateContext<TInput, T>): void {
|
||||
const children = node.children.map(c => asTreeElement(c, viewStateContext));
|
||||
const children = node.children.map(node => this.asTreeElement(node, viewStateContext));
|
||||
this.tree.setChildren(node === this.root ? null : node, children);
|
||||
|
||||
if (node !== this.root) {
|
||||
this.tree.setCollapsible(node, node.hasChildren);
|
||||
}
|
||||
|
||||
this._onDidRender.fire();
|
||||
}
|
||||
|
||||
protected asTreeElement(node: IAsyncDataTreeNode<TInput, T>, viewStateContext?: IAsyncDataTreeViewStateContext<TInput, T>): ITreeElement<IAsyncDataTreeNode<TInput, T>> {
|
||||
let collapsed: boolean | undefined;
|
||||
|
||||
if (viewStateContext && viewStateContext.viewState.expanded && node.id && viewStateContext.viewState.expanded.indexOf(node.id) > -1) {
|
||||
collapsed = false;
|
||||
} else {
|
||||
collapsed = node.collapsedByDefault;
|
||||
}
|
||||
|
||||
node.collapsedByDefault = undefined;
|
||||
|
||||
return {
|
||||
element: node,
|
||||
children: node.hasChildren ? Iterator.map(Iterator.fromArray(node.children), child => this.asTreeElement(child, viewStateContext)) : [],
|
||||
collapsible: node.hasChildren,
|
||||
collapsed
|
||||
};
|
||||
}
|
||||
|
||||
// view state
|
||||
|
||||
getViewState(): IAsyncDataTreeViewState {
|
||||
if (!this.identityProvider) {
|
||||
throw new Error('Can\'t get tree view state without an identity provider');
|
||||
throw new TreeError(this.user, 'Can\'t get tree view state without an identity provider');
|
||||
}
|
||||
|
||||
const getId = (element: T) => this.identityProvider!.getId(element).toString();
|
||||
@@ -903,3 +933,117 @@ export class AsyncDataTree<TInput, T, TFilterData = void> implements IDisposable
|
||||
dispose(this.disposables);
|
||||
}
|
||||
}
|
||||
|
||||
type CompressibleAsyncDataTreeNodeMapper<TInput, T, TFilterData> = WeakMapper<ITreeNode<ICompressedTreeNode<IAsyncDataTreeNode<TInput, T>>, TFilterData>, ITreeNode<ICompressedTreeNode<TInput | T>, TFilterData>>;
|
||||
|
||||
class CompressibleAsyncDataTreeNodeWrapper<TInput, T, TFilterData> implements ITreeNode<ICompressedTreeNode<TInput | T>, TFilterData> {
|
||||
|
||||
get element(): ICompressedTreeNode<TInput | T> {
|
||||
return {
|
||||
elements: this.node.element.elements.map(e => e.element),
|
||||
incompressible: this.node.element.incompressible
|
||||
};
|
||||
}
|
||||
|
||||
get children(): ITreeNode<ICompressedTreeNode<TInput | T>, TFilterData>[] { return this.node.children.map(node => new CompressibleAsyncDataTreeNodeWrapper(node)); }
|
||||
get depth(): number { return this.node.depth; }
|
||||
get visibleChildrenCount(): number { return this.node.visibleChildrenCount; }
|
||||
get visibleChildIndex(): number { return this.node.visibleChildIndex; }
|
||||
get collapsible(): boolean { return this.node.collapsible; }
|
||||
get collapsed(): boolean { return this.node.collapsed; }
|
||||
get visible(): boolean { return this.node.visible; }
|
||||
get filterData(): TFilterData | undefined { return this.node.filterData; }
|
||||
|
||||
constructor(private node: ITreeNode<ICompressedTreeNode<IAsyncDataTreeNode<TInput, T>>, TFilterData>) { }
|
||||
}
|
||||
|
||||
class CompressibleAsyncDataTreeRenderer<TInput, T, TFilterData, TTemplateData> implements ICompressibleTreeRenderer<IAsyncDataTreeNode<TInput, T>, TFilterData, IDataTreeListTemplateData<TTemplateData>> {
|
||||
|
||||
readonly templateId: string;
|
||||
private renderedNodes = new Map<IAsyncDataTreeNode<TInput, T>, IDataTreeListTemplateData<TTemplateData>>();
|
||||
private disposables: IDisposable[] = [];
|
||||
|
||||
constructor(
|
||||
protected renderer: ICompressibleTreeRenderer<T, TFilterData, TTemplateData>,
|
||||
protected nodeMapper: AsyncDataTreeNodeMapper<TInput, T, TFilterData>,
|
||||
private compressibleNodeMapperProvider: () => CompressibleAsyncDataTreeNodeMapper<TInput, T, TFilterData>,
|
||||
readonly onDidChangeTwistieState: Event<IAsyncDataTreeNode<TInput, T>>
|
||||
) {
|
||||
this.templateId = renderer.templateId;
|
||||
}
|
||||
|
||||
renderTemplate(container: HTMLElement): IDataTreeListTemplateData<TTemplateData> {
|
||||
const templateData = this.renderer.renderTemplate(container);
|
||||
return { templateData };
|
||||
}
|
||||
|
||||
renderElement(node: ITreeNode<IAsyncDataTreeNode<TInput, T>, TFilterData>, index: number, templateData: IDataTreeListTemplateData<TTemplateData>, height: number | undefined): void {
|
||||
this.renderer.renderElement(this.nodeMapper.map(node) as ITreeNode<T, TFilterData>, index, templateData.templateData, height);
|
||||
}
|
||||
|
||||
renderCompressedElements(node: ITreeNode<ICompressedTreeNode<IAsyncDataTreeNode<TInput, T>>, TFilterData>, index: number, templateData: IDataTreeListTemplateData<TTemplateData>, height: number | undefined): void {
|
||||
this.renderer.renderCompressedElements(this.compressibleNodeMapperProvider().map(node) as ITreeNode<ICompressedTreeNode<T>, TFilterData>, index, templateData.templateData, height);
|
||||
}
|
||||
|
||||
renderTwistie(element: IAsyncDataTreeNode<TInput, T>, twistieElement: HTMLElement): boolean {
|
||||
toggleClass(twistieElement, 'loading', element.slow);
|
||||
return false;
|
||||
}
|
||||
|
||||
disposeElement(node: ITreeNode<IAsyncDataTreeNode<TInput, T>, TFilterData>, index: number, templateData: IDataTreeListTemplateData<TTemplateData>, height: number | undefined): void {
|
||||
if (this.renderer.disposeElement) {
|
||||
this.renderer.disposeElement(this.nodeMapper.map(node) as ITreeNode<T, TFilterData>, index, templateData.templateData, height);
|
||||
}
|
||||
}
|
||||
|
||||
disposeTemplate(templateData: IDataTreeListTemplateData<TTemplateData>): void {
|
||||
this.renderer.disposeTemplate(templateData.templateData);
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
this.renderedNodes.clear();
|
||||
this.disposables = dispose(this.disposables);
|
||||
}
|
||||
}
|
||||
|
||||
export interface ITreeCompressionDelegate<T> {
|
||||
isIncompressible(element: T): boolean;
|
||||
}
|
||||
|
||||
export class CompressibleAsyncDataTree<TInput, T, TFilterData = void> extends AsyncDataTree<TInput, T, TFilterData> {
|
||||
|
||||
protected readonly compressibleNodeMapper: CompressibleAsyncDataTreeNodeMapper<TInput, T, TFilterData> = new WeakMapper(node => new CompressibleAsyncDataTreeNodeWrapper(node));
|
||||
|
||||
constructor(
|
||||
user: string,
|
||||
container: HTMLElement,
|
||||
virtualDelegate: IListVirtualDelegate<T>,
|
||||
private compressionDelegate: ITreeCompressionDelegate<T>,
|
||||
renderers: ICompressibleTreeRenderer<T, TFilterData, any>[],
|
||||
dataSource: IAsyncDataSource<TInput, T>,
|
||||
options: IAsyncDataTreeOptions<T, TFilterData> = {}
|
||||
) {
|
||||
super(user, container, virtualDelegate, renderers, dataSource, options);
|
||||
}
|
||||
|
||||
protected createTree(
|
||||
user: string,
|
||||
container: HTMLElement,
|
||||
delegate: IListVirtualDelegate<T>,
|
||||
renderers: ICompressibleTreeRenderer<T, TFilterData, any>[],
|
||||
options: IAsyncDataTreeOptions<T, TFilterData>
|
||||
): ObjectTree<IAsyncDataTreeNode<TInput, T>, TFilterData> {
|
||||
const objectTreeDelegate = new ComposedTreeDelegate<TInput | T, IAsyncDataTreeNode<TInput, T>>(delegate);
|
||||
const objectTreeRenderers = renderers.map(r => new CompressibleAsyncDataTreeRenderer(r, this.nodeMapper, () => this.compressibleNodeMapper, this._onDidChangeNodeSlowState.event));
|
||||
const objectTreeOptions = asObjectTreeOptions<TInput, T, TFilterData>(options) || {};
|
||||
|
||||
return new CompressibleObjectTree(user, container, objectTreeDelegate, objectTreeRenderers, objectTreeOptions);
|
||||
}
|
||||
|
||||
protected asTreeElement(node: IAsyncDataTreeNode<TInput, T>, viewStateContext?: IAsyncDataTreeViewStateContext<TInput, T>): ICompressedTreeElement<IAsyncDataTreeNode<TInput, T>> {
|
||||
return {
|
||||
incompressible: this.compressionDelegate.isIncompressible(node.element as T),
|
||||
...super.asTreeElement(node, viewStateContext)
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,56 +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 { Iterator, ISequence } from 'vs/base/common/iterator';
|
||||
import { AbstractTree, IAbstractTreeOptions } from 'vs/base/browser/ui/tree/abstractTree';
|
||||
import { ISpliceable } from 'vs/base/common/sequence';
|
||||
import { ITreeNode, ITreeModel, ITreeElement, ITreeRenderer, ITreeSorter, ICollapseStateChangeEvent } from 'vs/base/browser/ui/tree/tree';
|
||||
import { IListVirtualDelegate } from 'vs/base/browser/ui/list/list';
|
||||
import { Event } from 'vs/base/common/event';
|
||||
import { CompressedTreeModel, ICompressedTreeNode } from 'vs/base/browser/ui/tree/compressedObjectTreeModel';
|
||||
|
||||
export interface IObjectTreeOptions<T, TFilterData = void> extends IAbstractTreeOptions<T, TFilterData> {
|
||||
sorter?: ITreeSorter<T>;
|
||||
}
|
||||
|
||||
export class CompressedObjectTree<T extends NonNullable<any>, TFilterData = void> extends AbstractTree<ICompressedTreeNode<T> | null, TFilterData, T | null> {
|
||||
|
||||
protected model!: CompressedTreeModel<T, TFilterData>;
|
||||
|
||||
get onDidChangeCollapseState(): Event<ICollapseStateChangeEvent<ICompressedTreeNode<T> | null, TFilterData>> { return this.model.onDidChangeCollapseState; }
|
||||
|
||||
constructor(
|
||||
container: HTMLElement,
|
||||
delegate: IListVirtualDelegate<ICompressedTreeNode<T>>,
|
||||
renderers: ITreeRenderer<ICompressedTreeNode<T>, TFilterData, any>[],
|
||||
options: IObjectTreeOptions<ICompressedTreeNode<T>, TFilterData> = {}
|
||||
) {
|
||||
super(container, delegate, renderers, options);
|
||||
}
|
||||
|
||||
setChildren(
|
||||
element: T | null,
|
||||
children?: ISequence<ITreeElement<T>>
|
||||
): Iterator<ITreeElement<T | null>> {
|
||||
return this.model.setChildren(element, children);
|
||||
}
|
||||
|
||||
rerender(element?: T): void {
|
||||
if (element === undefined) {
|
||||
this.view.rerender();
|
||||
return;
|
||||
}
|
||||
|
||||
this.model.rerender(element);
|
||||
}
|
||||
|
||||
resort(element: T, recursive = true): void {
|
||||
this.model.resort(element, recursive);
|
||||
}
|
||||
|
||||
protected createModel(view: ISpliceable<ITreeNode<ICompressedTreeNode<T>, TFilterData>>, options: IObjectTreeOptions<ICompressedTreeNode<T>, TFilterData>): ITreeModel<ICompressedTreeNode<T> | null, TFilterData, T | null> {
|
||||
return new CompressedTreeModel(view, options);
|
||||
}
|
||||
}
|
||||
@@ -6,19 +6,32 @@
|
||||
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 { ITreeModel, ITreeNode, ITreeElement, ICollapseStateChangeEvent, ITreeModelSpliceEvent, TreeError, TreeFilterResult, TreeVisibility, WeakMapper } from 'vs/base/browser/ui/tree/tree';
|
||||
import { IObjectTreeModelOptions, ObjectTreeModel, IObjectTreeModel } from 'vs/base/browser/ui/tree/objectTreeModel';
|
||||
|
||||
// Exported only for test reasons, do not use directly
|
||||
export interface ICompressedTreeElement<T> extends ITreeElement<T> {
|
||||
readonly children?: Iterator<ICompressedTreeElement<T>> | ICompressedTreeElement<T>[];
|
||||
readonly incompressible?: boolean;
|
||||
}
|
||||
|
||||
// Exported only for test reasons, do not use directly
|
||||
export interface ICompressedTreeNode<T> {
|
||||
readonly elements: T[];
|
||||
readonly incompressible: boolean;
|
||||
}
|
||||
|
||||
function noCompress<T>(element: ICompressedTreeElement<T>): ITreeElement<ICompressedTreeNode<T>> {
|
||||
const elements = [element.element];
|
||||
const incompressible = element.incompressible || false;
|
||||
|
||||
return {
|
||||
element: { elements, incompressible },
|
||||
children: Iterator.map(Iterator.from(element.children), noCompress)
|
||||
};
|
||||
}
|
||||
|
||||
// Exported only for test reasons, do not use directly
|
||||
export function compress<T>(element: ICompressedTreeElement<T>): ITreeElement<ICompressedTreeNode<T>> {
|
||||
const elements = [element.element];
|
||||
const incompressible = element.incompressible || false;
|
||||
@@ -49,7 +62,7 @@ export function compress<T>(element: ICompressedTreeElement<T>): ITreeElement<IC
|
||||
};
|
||||
}
|
||||
|
||||
export function _decompress<T>(element: ITreeElement<ICompressedTreeNode<T>>, index = 0): ICompressedTreeElement<T> {
|
||||
function _decompress<T>(element: ITreeElement<ICompressedTreeNode<T>>, index = 0): ICompressedTreeElement<T> {
|
||||
let children: Iterator<ICompressedTreeElement<T>>;
|
||||
|
||||
if (index < element.element.elements.length - 1) {
|
||||
@@ -65,11 +78,12 @@ export function _decompress<T>(element: ITreeElement<ICompressedTreeNode<T>>, in
|
||||
return { element: element.element.elements[index], children };
|
||||
}
|
||||
|
||||
// Exported only for test reasons, do not use directly
|
||||
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> {
|
||||
function splice<T>(treeElement: ICompressedTreeElement<T>, element: T, children: Iterator<ICompressedTreeElement<T>>): ICompressedTreeElement<T> {
|
||||
if (treeElement.element === element) {
|
||||
return { element, children };
|
||||
}
|
||||
@@ -80,9 +94,10 @@ export function splice<T>(treeElement: ICompressedTreeElement<T>, element: T, ch
|
||||
};
|
||||
}
|
||||
|
||||
export interface ICompressedTreeModelOptions<T, TFilterData> extends IObjectTreeModelOptions<ICompressedTreeNode<T>, TFilterData> { }
|
||||
interface ICompressedObjectTreeModelOptions<T, TFilterData> extends IObjectTreeModelOptions<ICompressedTreeNode<T>, TFilterData> { }
|
||||
|
||||
export class CompressedTreeModel<T extends NonNullable<any>, TFilterData extends NonNullable<any> = void> implements ITreeModel<ICompressedTreeNode<T> | null, TFilterData, T | null> {
|
||||
// Exported only for test reasons, do not use directly
|
||||
export class CompressedObjectTreeModel<T extends NonNullable<any>, TFilterData extends NonNullable<any> = void> implements ITreeModel<ICompressedTreeNode<T> | null, TFilterData, T | null> {
|
||||
|
||||
readonly rootRef = null;
|
||||
|
||||
@@ -92,35 +107,77 @@ export class CompressedTreeModel<T extends NonNullable<any>, TFilterData extends
|
||||
|
||||
private model: ObjectTreeModel<ICompressedTreeNode<T>, TFilterData>;
|
||||
private nodes = new Map<T | null, ICompressedTreeNode<T>>();
|
||||
private enabled: boolean = true;
|
||||
|
||||
get size(): number { return this.nodes.size; }
|
||||
|
||||
constructor(list: ISpliceable<ITreeNode<ICompressedTreeNode<T>, TFilterData>>, options: ICompressedTreeModelOptions<T, TFilterData> = {}) {
|
||||
this.model = new ObjectTreeModel(list, options);
|
||||
constructor(
|
||||
private user: string,
|
||||
list: ISpliceable<ITreeNode<ICompressedTreeNode<T>, TFilterData>>,
|
||||
options: ICompressedObjectTreeModelOptions<T, TFilterData> = {}
|
||||
) {
|
||||
this.model = new ObjectTreeModel(user, 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>> {
|
||||
children: ISequence<ICompressedTreeElement<T>> | undefined
|
||||
): void {
|
||||
|
||||
if (element === null) {
|
||||
const compressedChildren = Iterator.map(Iterator.from(children), this.enabled ? compress : noCompress);
|
||||
this._setChildren(null, compressedChildren);
|
||||
return;
|
||||
}
|
||||
|
||||
const compressedNode = this.nodes.get(element);
|
||||
|
||||
if (!compressedNode) {
|
||||
throw new Error('Unknown compressed tree node');
|
||||
}
|
||||
|
||||
const node = this.model.getNode(compressedNode) as ITreeNode<ICompressedTreeNode<T>, TFilterData>;
|
||||
const compressedParentNode = this.model.getParentNodeLocation(compressedNode);
|
||||
const parent = this.model.getNode(compressedParentNode) as ITreeNode<ICompressedTreeNode<T>, TFilterData>;
|
||||
|
||||
const decompressedElement = decompress(node);
|
||||
const splicedElement = splice(decompressedElement, element, Iterator.from(children));
|
||||
const recompressedElement = (this.enabled ? compress : noCompress)(splicedElement);
|
||||
|
||||
const parentChildren = parent.children
|
||||
.map(child => child === node ? recompressedElement : child);
|
||||
|
||||
this._setChildren(parent.element, parentChildren);
|
||||
}
|
||||
|
||||
isCompressionEnabled(): boolean {
|
||||
return this.enabled;
|
||||
}
|
||||
|
||||
setCompressionEnabled(enabled: boolean): void {
|
||||
if (enabled === this.enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.enabled = enabled;
|
||||
|
||||
const root = this.model.getNode();
|
||||
const rootChildren = Iterator.from((root.children as unknown) as ITreeNode<ICompressedTreeNode<T>>[]); // {{SQL CARBON EDIT}} strict-null-checks
|
||||
const decompressedRootChildren = Iterator.map(rootChildren, decompress);
|
||||
const recompressedRootChildren = Iterator.map(decompressedRootChildren, enabled ? compress : noCompress);
|
||||
this._setChildren(null, recompressedRootChildren);
|
||||
}
|
||||
|
||||
private _setChildren(
|
||||
node: ICompressedTreeNode<T> | null,
|
||||
children: ISequence<ITreeElement<ICompressedTreeNode<T>>> | undefined
|
||||
): void {
|
||||
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>) => {
|
||||
@@ -129,40 +186,9 @@ export class CompressedTreeModel<T extends NonNullable<any>, TFilterData extends
|
||||
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();
|
||||
this.model.setChildren(node, children, _onDidCreateNode, _onDidDeleteNode);
|
||||
}
|
||||
|
||||
getListIndex(location: T | null): number {
|
||||
@@ -207,11 +233,6 @@ export class CompressedTreeModel<T extends NonNullable<any>, TFilterData extends
|
||||
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);
|
||||
@@ -227,6 +248,11 @@ export class CompressedTreeModel<T extends NonNullable<any>, TFilterData extends
|
||||
return this.model.isCollapsible(compressedNode);
|
||||
}
|
||||
|
||||
setCollapsible(location: T | null, collapsible?: boolean): boolean {
|
||||
const compressedNode = this.getCompressedNode(location);
|
||||
return this.model.setCollapsible(compressedNode, collapsible);
|
||||
}
|
||||
|
||||
isCollapsed(location: T | null): boolean {
|
||||
const compressedNode = this.getCompressedNode(location);
|
||||
return this.model.isCollapsed(compressedNode);
|
||||
@@ -256,7 +282,7 @@ export class CompressedTreeModel<T extends NonNullable<any>, TFilterData extends
|
||||
this.model.resort(compressedNode, recursive);
|
||||
}
|
||||
|
||||
private getCompressedNode(element: T | null): ICompressedTreeNode<T> | null {
|
||||
getCompressedNode(element: T | null): ICompressedTreeNode<T> | null {
|
||||
if (element === null) {
|
||||
return null;
|
||||
}
|
||||
@@ -264,78 +290,120 @@ export class CompressedTreeModel<T extends NonNullable<any>, TFilterData extends
|
||||
const node = this.nodes.get(element);
|
||||
|
||||
if (!node) {
|
||||
throw new Error(`Tree element not found: ${element}`);
|
||||
throw new TreeError(this.user, `Tree element not found: ${element}`);
|
||||
}
|
||||
|
||||
return node;
|
||||
}
|
||||
}
|
||||
|
||||
// Compressible Object Tree
|
||||
|
||||
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>;
|
||||
export type CompressedNodeUnwrapper<T> = (node: ICompressedTreeNode<T>) => T;
|
||||
type CompressedNodeWeakMapper<T, TFilterData> = WeakMapper<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> {
|
||||
class CompressedTreeNodeWrapper<T, TFilterData> implements ITreeNode<T | null, TFilterData> {
|
||||
|
||||
get element(): T | null { return this.node.element === null ? null : this.unwrapper(this.node.element); }
|
||||
get children(): ITreeNode<T | null, TFilterData>[] { return this.node.children.map(node => new CompressedTreeNodeWrapper(this.unwrapper, node)); }
|
||||
get depth(): number { return this.node.depth; }
|
||||
get visibleChildrenCount(): number { return this.node.visibleChildrenCount; }
|
||||
get visibleChildIndex(): number { return this.node.visibleChildIndex; }
|
||||
get collapsible(): boolean { return this.node.collapsible; }
|
||||
get collapsed(): boolean { return this.node.collapsed; }
|
||||
get visible(): boolean { return this.node.visible; }
|
||||
get filterData(): TFilterData | undefined { return this.node.filterData; }
|
||||
|
||||
constructor(
|
||||
private unwrapper: CompressedNodeUnwrapper<T>,
|
||||
private node: ITreeNode<ICompressedTreeNode<T> | null, TFilterData>
|
||||
) { }
|
||||
}
|
||||
|
||||
function mapList<T, TFilterData>(nodeMapper: CompressedNodeWeakMapper<T, TFilterData>, list: ISpliceable<ITreeNode<T, TFilterData>>): ISpliceable<ITreeNode<ICompressedTreeNode<T>, 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)
|
||||
splice(start: number, deleteCount: number, toInsert: ITreeNode<ICompressedTreeNode<T>, TFilterData>[]): void {
|
||||
list.splice(start, deleteCount, toInsert.map(node => nodeMapper.map(node)) as ITreeNode<T, TFilterData>[]);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function createNodeMapper<T, TFilterData>(elementMapper: ElementMapper<T>): NodeMapper<T, TFilterData> {
|
||||
return node => mapNode(elementMapper, node);
|
||||
function mapOptions<T, TFilterData>(compressedNodeUnwrapper: CompressedNodeUnwrapper<T>, options: ICompressibleObjectTreeModelOptions<T, TFilterData>): ICompressedObjectTreeModelOptions<T, TFilterData> {
|
||||
return {
|
||||
...options,
|
||||
sorter: options.sorter && {
|
||||
compare(node: ICompressedTreeNode<T>, otherNode: ICompressedTreeNode<T>): number {
|
||||
return options.sorter!.compare(compressedNodeUnwrapper(node), compressedNodeUnwrapper(otherNode));
|
||||
}
|
||||
},
|
||||
identityProvider: options.identityProvider && {
|
||||
getId(node: ICompressedTreeNode<T>): { toString(): string; } {
|
||||
return options.identityProvider!.getId(compressedNodeUnwrapper(node));
|
||||
}
|
||||
},
|
||||
filter: options.filter && {
|
||||
filter(node: ICompressedTreeNode<T>, parentVisibility: TreeVisibility): TreeFilterResult<TFilterData> {
|
||||
return options.filter!.filter(compressedNodeUnwrapper(node), parentVisibility);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export interface ICompressedObjectTreeModelOptions<T, TFilterData> extends ICompressedTreeModelOptions<T, TFilterData> {
|
||||
export interface ICompressibleObjectTreeModelOptions<T, TFilterData> extends IObjectTreeModelOptions<T, TFilterData> {
|
||||
readonly elementMapper?: ElementMapper<T>;
|
||||
}
|
||||
|
||||
export class CompressedObjectTreeModel<T extends NonNullable<any>, TFilterData extends NonNullable<any> = void> implements IObjectTreeModel<T, TFilterData> {
|
||||
export class CompressibleObjectTreeModel<T extends NonNullable<any>, TFilterData extends NonNullable<any> = void> implements IObjectTreeModel<T, TFilterData> {
|
||||
|
||||
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),
|
||||
insertedNodes: insertedNodes.map(node => this.nodeMapper.map(node)),
|
||||
deletedNodes: deletedNodes.map(node => this.nodeMapper.map(node)),
|
||||
}));
|
||||
}
|
||||
|
||||
get onDidChangeCollapseState(): Event<ICollapseStateChangeEvent<T | null, TFilterData>> {
|
||||
return Event.map(this.model.onDidChangeCollapseState, ({ node, deep }) => ({
|
||||
node: this.mapNode(node),
|
||||
node: this.nodeMapper.map(node),
|
||||
deep
|
||||
}));
|
||||
}
|
||||
|
||||
get onDidChangeRenderNodeCount(): Event<ITreeNode<T | null, TFilterData>> {
|
||||
return Event.map(this.model.onDidChangeRenderNodeCount, this.mapNode);
|
||||
return Event.map(this.model.onDidChangeRenderNodeCount, node => this.nodeMapper.map(node));
|
||||
}
|
||||
|
||||
private mapElement: ElementMapper<T | null>;
|
||||
private mapNode: NodeMapper<T | null, TFilterData>;
|
||||
private model: CompressedTreeModel<T, TFilterData>;
|
||||
private elementMapper: ElementMapper<T>;
|
||||
private nodeMapper: CompressedNodeWeakMapper<T, TFilterData>;
|
||||
private model: CompressedObjectTreeModel<T, TFilterData>;
|
||||
|
||||
constructor(
|
||||
list: ISpliceable<ITreeNode<ICompressedTreeNode<T>, TFilterData>>,
|
||||
options: ICompressedObjectTreeModelOptions<T, TFilterData> = {}
|
||||
user: string,
|
||||
list: ISpliceable<ITreeNode<T, TFilterData>>,
|
||||
options: ICompressibleObjectTreeModelOptions<T, TFilterData> = {}
|
||||
) {
|
||||
this.mapElement = options.elementMapper || DefaultElementMapper;
|
||||
this.mapNode = createNodeMapper(this.mapElement);
|
||||
this.model = new CompressedTreeModel(list, options);
|
||||
this.elementMapper = options.elementMapper || DefaultElementMapper;
|
||||
const compressedNodeUnwrapper: CompressedNodeUnwrapper<T> = node => this.elementMapper(node.elements);
|
||||
this.nodeMapper = new WeakMapper(node => new CompressedTreeNodeWrapper(compressedNodeUnwrapper, node));
|
||||
|
||||
this.model = new CompressedObjectTreeModel(user, mapList(this.nodeMapper, list), mapOptions(compressedNodeUnwrapper, options));
|
||||
}
|
||||
|
||||
setChildren(
|
||||
element: T | null,
|
||||
children: ISequence<ITreeElement<T>> | undefined
|
||||
): Iterator<ITreeElement<T>> {
|
||||
setChildren(element: T | null, children?: ISequence<ICompressedTreeElement<T>>): void {
|
||||
this.model.setChildren(element, children);
|
||||
}
|
||||
|
||||
// TODO
|
||||
return Iterator.empty();
|
||||
isCompressionEnabled(): boolean {
|
||||
return this.model.isCompressionEnabled();
|
||||
}
|
||||
|
||||
setCompressionEnabled(enabled: boolean): void {
|
||||
this.model.setCompressionEnabled(enabled);
|
||||
}
|
||||
|
||||
getListIndex(location: T | null): number {
|
||||
@@ -347,7 +415,7 @@ export class CompressedObjectTreeModel<T extends NonNullable<any>, TFilterData e
|
||||
}
|
||||
|
||||
getNode(location?: T | null | undefined): ITreeNode<T | null, any> {
|
||||
return this.mapNode(this.model.getNode(location));
|
||||
return this.nodeMapper.map(this.model.getNode(location));
|
||||
}
|
||||
|
||||
getNodeLocation(node: ITreeNode<T | null, any>): T | null {
|
||||
@@ -358,16 +426,6 @@ export class CompressedObjectTreeModel<T extends NonNullable<any>, TFilterData e
|
||||
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);
|
||||
|
||||
@@ -375,7 +433,7 @@ export class CompressedObjectTreeModel<T extends NonNullable<any>, TFilterData e
|
||||
return null; // {{SQL CARBON EDIT}} strict-null-check
|
||||
}
|
||||
|
||||
return this.mapElement(result.elements);
|
||||
return this.elementMapper(result.elements);
|
||||
}
|
||||
|
||||
getLastElementAncestor(location?: T | null | undefined): T | null | undefined {
|
||||
@@ -385,13 +443,17 @@ export class CompressedObjectTreeModel<T extends NonNullable<any>, TFilterData e
|
||||
return null; // {{SQL CARBON EDIT}} strict-null-check
|
||||
}
|
||||
|
||||
return this.mapElement(result.elements);
|
||||
return this.elementMapper(result.elements);
|
||||
}
|
||||
|
||||
isCollapsible(location: T | null): boolean {
|
||||
return this.model.isCollapsible(location);
|
||||
}
|
||||
|
||||
setCollapsible(location: T | null, collapsed?: boolean): boolean {
|
||||
return this.model.setCollapsible(location, collapsed);
|
||||
}
|
||||
|
||||
isCollapsed(location: T | null): boolean {
|
||||
return this.model.isCollapsed(location);
|
||||
}
|
||||
@@ -415,4 +477,8 @@ export class CompressedObjectTreeModel<T extends NonNullable<any>, TFilterData e
|
||||
resort(element: T | null = null, recursive = true): void {
|
||||
return this.model.resort(element, recursive);
|
||||
}
|
||||
}
|
||||
|
||||
getCompressedTreeNode(element: T): ITreeNode<ICompressedTreeNode<T>, TFilterData> {
|
||||
return this.model.getNode(element) as ITreeNode<ICompressedTreeNode<T>, TFilterData>;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
import { AbstractTree, IAbstractTreeOptions } from 'vs/base/browser/ui/tree/abstractTree';
|
||||
import { ISpliceable } from 'vs/base/common/sequence';
|
||||
import { ITreeNode, ITreeModel, ITreeElement, ITreeRenderer, ITreeSorter, IDataSource } from 'vs/base/browser/ui/tree/tree';
|
||||
import { ITreeNode, ITreeModel, ITreeElement, ITreeRenderer, ITreeSorter, IDataSource, TreeError } from 'vs/base/browser/ui/tree/tree';
|
||||
import { ObjectTreeModel } from 'vs/base/browser/ui/tree/objectTreeModel';
|
||||
import { IListVirtualDelegate, IIdentityProvider } from 'vs/base/browser/ui/list/list';
|
||||
import { Iterator } from 'vs/base/common/iterator';
|
||||
@@ -30,13 +30,14 @@ export class DataTree<TInput, T, TFilterData = void> extends AbstractTree<T | nu
|
||||
private nodesByIdentity = new Map<string, ITreeNode<T, TFilterData>>();
|
||||
|
||||
constructor(
|
||||
private user: string,
|
||||
container: HTMLElement,
|
||||
delegate: IListVirtualDelegate<T>,
|
||||
renderers: ITreeRenderer<T, TFilterData, any>[],
|
||||
private dataSource: IDataSource<TInput, T>,
|
||||
options: IDataTreeOptions<T, TFilterData> = {}
|
||||
) {
|
||||
super(container, delegate, renderers, options);
|
||||
super(user, container, delegate, renderers, options);
|
||||
this.identityProvider = options.identityProvider;
|
||||
}
|
||||
|
||||
@@ -48,7 +49,7 @@ export class DataTree<TInput, T, TFilterData = void> extends AbstractTree<T | nu
|
||||
|
||||
setInput(input: TInput, viewState?: IDataTreeViewState): void {
|
||||
if (viewState && !this.identityProvider) {
|
||||
throw new Error('Can\'t restore tree view state without an identity provider');
|
||||
throw new TreeError(this.user, 'Can\'t restore tree view state without an identity provider');
|
||||
}
|
||||
|
||||
this.input = input;
|
||||
@@ -89,7 +90,7 @@ export class DataTree<TInput, T, TFilterData = void> extends AbstractTree<T | nu
|
||||
|
||||
updateChildren(element: TInput | T = this.input!): void {
|
||||
if (typeof this.input === 'undefined') {
|
||||
throw new Error('Tree input not set');
|
||||
throw new TreeError(this.user, 'Tree input not set');
|
||||
}
|
||||
|
||||
let isCollapsed: ((el: T) => boolean | undefined) | undefined;
|
||||
@@ -169,15 +170,15 @@ export class DataTree<TInput, T, TFilterData = void> extends AbstractTree<T | nu
|
||||
return { elements, size: children.length };
|
||||
}
|
||||
|
||||
protected createModel(view: ISpliceable<ITreeNode<T, TFilterData>>, options: IDataTreeOptions<T, TFilterData>): ITreeModel<T | null, TFilterData, T | null> {
|
||||
return new ObjectTreeModel(view, options);
|
||||
protected createModel(user: string, view: ISpliceable<ITreeNode<T, TFilterData>>, options: IDataTreeOptions<T, TFilterData>): ITreeModel<T | null, TFilterData, T | null> {
|
||||
return new ObjectTreeModel(user, view, options);
|
||||
}
|
||||
|
||||
// view state
|
||||
|
||||
getViewState(): IDataTreeViewState {
|
||||
if (!this.identityProvider) {
|
||||
throw new Error('Can\'t get tree view state without an identity provider');
|
||||
throw new TreeError(this.user, 'Can\'t get tree view state without an identity provider');
|
||||
}
|
||||
|
||||
const getId = (element: T) => this.identityProvider!.getId(element).toString();
|
||||
|
||||
@@ -18,17 +18,18 @@ export class IndexTree<T, TFilterData = void> extends AbstractTree<T, TFilterDat
|
||||
protected model!: IndexTreeModel<T, TFilterData>;
|
||||
|
||||
constructor(
|
||||
user: string,
|
||||
container: HTMLElement,
|
||||
delegate: IListVirtualDelegate<T>,
|
||||
renderers: ITreeRenderer<T, TFilterData, any>[],
|
||||
private rootElement: T,
|
||||
options: IIndexTreeOptions<T, TFilterData> = {}
|
||||
) {
|
||||
super(container, delegate, renderers, options);
|
||||
super(user, container, delegate, renderers, options);
|
||||
}
|
||||
|
||||
splice(location: number[], deleteCount: number, toInsert: ISequence<ITreeElement<T>> = Iterator.empty()): Iterator<ITreeElement<T>> {
|
||||
return this.model.splice(location, deleteCount, toInsert);
|
||||
splice(location: number[], deleteCount: number, toInsert: ISequence<ITreeElement<T>> = Iterator.empty()): void {
|
||||
this.model.splice(location, deleteCount, toInsert);
|
||||
}
|
||||
|
||||
rerender(location?: number[]): void {
|
||||
@@ -40,7 +41,7 @@ export class IndexTree<T, TFilterData = void> extends AbstractTree<T, TFilterDat
|
||||
this.model.rerender(location);
|
||||
}
|
||||
|
||||
protected createModel(view: ISpliceable<ITreeNode<T, TFilterData>>, options: IIndexTreeOptions<T, TFilterData>): ITreeModel<T, TFilterData, number[]> {
|
||||
return new IndexTreeModel(view, this.rootElement, options);
|
||||
protected createModel(user: string, view: ISpliceable<ITreeNode<T, TFilterData>>, options: IIndexTreeOptions<T, TFilterData>): ITreeModel<T, TFilterData, number[]> {
|
||||
return new IndexTreeModel(user, view, this.rootElement, options);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,15 +3,16 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { ICollapseStateChangeEvent, ITreeElement, ITreeFilter, ITreeFilterDataResult, ITreeModel, ITreeNode, TreeVisibility, ITreeModelSpliceEvent } from 'vs/base/browser/ui/tree/tree';
|
||||
import { ICollapseStateChangeEvent, ITreeElement, ITreeFilter, ITreeFilterDataResult, ITreeModel, ITreeNode, TreeVisibility, ITreeModelSpliceEvent, TreeError } from 'vs/base/browser/ui/tree/tree';
|
||||
import { tail2 } from 'vs/base/common/arrays';
|
||||
import { Emitter, Event, EventBufferer } from 'vs/base/common/event';
|
||||
import { ISequence, Iterator } from 'vs/base/common/iterator';
|
||||
import { ISpliceable } from 'vs/base/common/sequence';
|
||||
|
||||
interface IMutableTreeNode<T, TFilterData> extends ITreeNode<T, TFilterData> {
|
||||
readonly parent: IMutableTreeNode<T, TFilterData> | undefined;
|
||||
readonly children: IMutableTreeNode<T, TFilterData>[];
|
||||
// Exported for tests
|
||||
export interface IIndexTreeNode<T, TFilterData = void> extends ITreeNode<T, TFilterData> {
|
||||
readonly parent: IIndexTreeNode<T, TFilterData> | undefined;
|
||||
readonly children: IIndexTreeNode<T, TFilterData>[];
|
||||
visibleChildrenCount: number;
|
||||
visibleChildIndex: number;
|
||||
collapsible: boolean;
|
||||
@@ -33,24 +34,32 @@ export function getVisibleState(visibility: boolean | TreeVisibility): TreeVisib
|
||||
}
|
||||
}
|
||||
|
||||
function treeNodeToElement<T>(node: IMutableTreeNode<T, any>): ITreeElement<T> {
|
||||
const { element, collapsed } = node;
|
||||
const children = Iterator.map(Iterator.fromArray(node.children), treeNodeToElement);
|
||||
|
||||
return { element, children, collapsed };
|
||||
}
|
||||
|
||||
export interface IIndexTreeModelOptions<T, TFilterData> {
|
||||
readonly collapseByDefault?: boolean; // defaults to false
|
||||
readonly filter?: ITreeFilter<T, TFilterData>;
|
||||
readonly autoExpandSingleChildren?: boolean;
|
||||
}
|
||||
|
||||
interface CollapsibleStateUpdate {
|
||||
readonly collapsible: boolean;
|
||||
}
|
||||
|
||||
interface CollapsedStateUpdate {
|
||||
readonly collapsed: boolean;
|
||||
readonly recursive: boolean;
|
||||
}
|
||||
|
||||
type CollapseStateUpdate = CollapsibleStateUpdate | CollapsedStateUpdate;
|
||||
|
||||
function isCollapsibleStateUpdate(update: CollapseStateUpdate): update is CollapsibleStateUpdate {
|
||||
return typeof (update as any).collapsible === 'boolean';
|
||||
}
|
||||
|
||||
export class IndexTreeModel<T extends Exclude<any, undefined>, TFilterData = void> implements ITreeModel<T, TFilterData, number[]> {
|
||||
|
||||
readonly rootRef = [];
|
||||
|
||||
private root: IMutableTreeNode<T, TFilterData>;
|
||||
private root: IIndexTreeNode<T, TFilterData>;
|
||||
private eventBufferer = new EventBufferer();
|
||||
|
||||
private _onDidChangeCollapseState = new Emitter<ICollapseStateChangeEvent<T, TFilterData>>();
|
||||
@@ -66,7 +75,12 @@ export class IndexTreeModel<T extends Exclude<any, undefined>, TFilterData = voi
|
||||
private _onDidSplice = new Emitter<ITreeModelSpliceEvent<T, TFilterData>>();
|
||||
readonly onDidSplice = this._onDidSplice.event;
|
||||
|
||||
constructor(private list: ISpliceable<ITreeNode<T, TFilterData>>, rootElement: T, options: IIndexTreeModelOptions<T, TFilterData> = {}) {
|
||||
constructor(
|
||||
private user: string,
|
||||
private list: ISpliceable<ITreeNode<T, TFilterData>>,
|
||||
rootElement: T,
|
||||
options: IIndexTreeModelOptions<T, TFilterData> = {}
|
||||
) {
|
||||
this.collapseByDefault = typeof options.collapseByDefault === 'undefined' ? false : options.collapseByDefault;
|
||||
this.filter = options.filter;
|
||||
this.autoExpandSingleChildren = typeof options.autoExpandSingleChildren === 'undefined' ? false : options.autoExpandSingleChildren;
|
||||
@@ -92,9 +106,9 @@ export class IndexTreeModel<T extends Exclude<any, undefined>, TFilterData = voi
|
||||
toInsert?: ISequence<ITreeElement<T>>,
|
||||
onDidCreateNode?: (node: ITreeNode<T, TFilterData>) => void,
|
||||
onDidDeleteNode?: (node: ITreeNode<T, TFilterData>) => void
|
||||
): Iterator<ITreeElement<T>> {
|
||||
): void {
|
||||
if (location.length === 0) {
|
||||
throw new Error('Invalid tree location');
|
||||
throw new TreeError(this.user, 'Invalid tree location');
|
||||
}
|
||||
|
||||
const { parentNode, listIndex, revealed, visible } = this.getParentNodeWithListIndex(location);
|
||||
@@ -116,7 +130,7 @@ export class IndexTreeModel<T extends Exclude<any, undefined>, TFilterData = voi
|
||||
}
|
||||
}
|
||||
|
||||
const nodesToInsert: IMutableTreeNode<T, TFilterData>[] = [];
|
||||
const nodesToInsert: IIndexTreeNode<T, TFilterData>[] = [];
|
||||
let insertedVisibleChildrenCount = 0;
|
||||
let renderNodeCount = 0;
|
||||
|
||||
@@ -170,14 +184,12 @@ export class IndexTreeModel<T extends Exclude<any, undefined>, TFilterData = voi
|
||||
deletedNodes.forEach(visit);
|
||||
}
|
||||
|
||||
const result = Iterator.map(Iterator.fromArray(deletedNodes), treeNodeToElement);
|
||||
this._onDidSplice.fire({ insertedNodes: nodesToInsert, deletedNodes });
|
||||
return result;
|
||||
}
|
||||
|
||||
rerender(location: number[]): void {
|
||||
if (location.length === 0) {
|
||||
throw new Error('Invalid tree location');
|
||||
throw new TreeError(this.user, 'Invalid tree location');
|
||||
}
|
||||
|
||||
const { node, listIndex, revealed } = this.getTreeNodeWithListIndex(location);
|
||||
@@ -200,6 +212,17 @@ export class IndexTreeModel<T extends Exclude<any, undefined>, TFilterData = voi
|
||||
return this.getTreeNode(location).collapsible;
|
||||
}
|
||||
|
||||
setCollapsible(location: number[], collapsible?: boolean): boolean {
|
||||
const node = this.getTreeNode(location);
|
||||
|
||||
if (typeof collapsible === 'undefined') {
|
||||
collapsible = !node.collapsible;
|
||||
}
|
||||
|
||||
const update: CollapsibleStateUpdate = { collapsible };
|
||||
return this.eventBufferer.bufferEvents(() => this._setCollapseState(location, update));
|
||||
}
|
||||
|
||||
isCollapsed(location: number[]): boolean {
|
||||
return this.getTreeNode(location).collapsed;
|
||||
}
|
||||
@@ -211,15 +234,16 @@ export class IndexTreeModel<T extends Exclude<any, undefined>, TFilterData = voi
|
||||
collapsed = !node.collapsed;
|
||||
}
|
||||
|
||||
return this.eventBufferer.bufferEvents(() => this._setCollapsed(location, collapsed!, recursive));
|
||||
const update: CollapsedStateUpdate = { collapsed, recursive: recursive || false };
|
||||
return this.eventBufferer.bufferEvents(() => this._setCollapseState(location, update));
|
||||
}
|
||||
|
||||
private _setCollapsed(location: number[], collapsed: boolean, recursive?: boolean): boolean {
|
||||
private _setCollapseState(location: number[], update: CollapseStateUpdate): boolean {
|
||||
const { node, listIndex, revealed } = this.getTreeNodeWithListIndex(location);
|
||||
|
||||
const result = this._setListNodeCollapsed(node, listIndex, revealed, collapsed!, recursive || false);
|
||||
const result = this._setListNodeCollapseState(node, listIndex, revealed, update);
|
||||
|
||||
if (node !== this.root && this.autoExpandSingleChildren && !collapsed! && !recursive) {
|
||||
if (node !== this.root && this.autoExpandSingleChildren && result && !isCollapsibleStateUpdate(update) && node.collapsible && !node.collapsed && !update.recursive) {
|
||||
let onlyVisibleChildIndex = -1;
|
||||
|
||||
for (let i = 0; i < node.children.length; i++) {
|
||||
@@ -236,17 +260,17 @@ export class IndexTreeModel<T extends Exclude<any, undefined>, TFilterData = voi
|
||||
}
|
||||
|
||||
if (onlyVisibleChildIndex > -1) {
|
||||
this._setCollapsed([...location, onlyVisibleChildIndex], false, false);
|
||||
this._setCollapseState([...location, onlyVisibleChildIndex], update);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private _setListNodeCollapsed(node: IMutableTreeNode<T, TFilterData>, listIndex: number, revealed: boolean, collapsed: boolean, recursive: boolean): boolean {
|
||||
const result = this._setNodeCollapsed(node, collapsed, recursive, false);
|
||||
private _setListNodeCollapseState(node: IIndexTreeNode<T, TFilterData>, listIndex: number, revealed: boolean, update: CollapseStateUpdate): boolean {
|
||||
const result = this._setNodeCollapseState(node, update, false);
|
||||
|
||||
if (!revealed || !node.visible) {
|
||||
if (!revealed || !node.visible || !result) {
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -258,20 +282,28 @@ export class IndexTreeModel<T extends Exclude<any, undefined>, TFilterData = voi
|
||||
return result;
|
||||
}
|
||||
|
||||
private _setNodeCollapsed(node: IMutableTreeNode<T, TFilterData>, collapsed: boolean, recursive: boolean, deep: boolean): boolean {
|
||||
let result = node.collapsible && node.collapsed !== collapsed;
|
||||
private _setNodeCollapseState(node: IIndexTreeNode<T, TFilterData>, update: CollapseStateUpdate, deep: boolean): boolean {
|
||||
let result: boolean;
|
||||
|
||||
if (node.collapsible) {
|
||||
node.collapsed = collapsed;
|
||||
if (node === this.root) {
|
||||
result = false;
|
||||
} else {
|
||||
if (isCollapsibleStateUpdate(update)) {
|
||||
result = node.collapsible !== update.collapsible;
|
||||
node.collapsible = update.collapsible;
|
||||
} else {
|
||||
result = node.collapsed !== update.collapsed;
|
||||
node.collapsed = update.collapsed;
|
||||
}
|
||||
|
||||
if (result) {
|
||||
this._onDidChangeCollapseState.fire({ node, deep });
|
||||
}
|
||||
}
|
||||
|
||||
if (recursive) {
|
||||
if (!isCollapsibleStateUpdate(update) && update.recursive) {
|
||||
for (const child of node.children) {
|
||||
result = this._setNodeCollapsed(child, collapsed, true, true) || result;
|
||||
result = this._setNodeCollapseState(child, update, true) || result;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -287,7 +319,7 @@ export class IndexTreeModel<T extends Exclude<any, undefined>, TFilterData = voi
|
||||
location = location.slice(0, location.length - 1);
|
||||
|
||||
if (node.collapsed) {
|
||||
this._setCollapsed(location, false);
|
||||
this._setCollapseState(location, { collapsed: false, recursive: false });
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -301,13 +333,13 @@ export class IndexTreeModel<T extends Exclude<any, undefined>, TFilterData = voi
|
||||
|
||||
private createTreeNode(
|
||||
treeElement: ITreeElement<T>,
|
||||
parent: IMutableTreeNode<T, TFilterData>,
|
||||
parent: IIndexTreeNode<T, TFilterData>,
|
||||
parentVisibility: TreeVisibility,
|
||||
revealed: boolean,
|
||||
treeListElements: ITreeNode<T, TFilterData>[],
|
||||
onDidCreateNode?: (node: ITreeNode<T, TFilterData>) => void
|
||||
): IMutableTreeNode<T, TFilterData> {
|
||||
const node: IMutableTreeNode<T, TFilterData> = {
|
||||
): IIndexTreeNode<T, TFilterData> {
|
||||
const node: IIndexTreeNode<T, TFilterData> = {
|
||||
parent,
|
||||
element: treeElement.element,
|
||||
children: [],
|
||||
@@ -364,7 +396,7 @@ export class IndexTreeModel<T extends Exclude<any, undefined>, TFilterData = voi
|
||||
return node;
|
||||
}
|
||||
|
||||
private updateNodeAfterCollapseChange(node: IMutableTreeNode<T, TFilterData>): ITreeNode<T, TFilterData>[] {
|
||||
private updateNodeAfterCollapseChange(node: IIndexTreeNode<T, TFilterData>): ITreeNode<T, TFilterData>[] {
|
||||
const previousRenderNodeCount = node.renderNodeCount;
|
||||
const result: ITreeNode<T, TFilterData>[] = [];
|
||||
|
||||
@@ -374,7 +406,7 @@ export class IndexTreeModel<T extends Exclude<any, undefined>, TFilterData = voi
|
||||
return result;
|
||||
}
|
||||
|
||||
private _updateNodeAfterCollapseChange(node: IMutableTreeNode<T, TFilterData>, result: ITreeNode<T, TFilterData>[]): number {
|
||||
private _updateNodeAfterCollapseChange(node: IIndexTreeNode<T, TFilterData>, result: ITreeNode<T, TFilterData>[]): number {
|
||||
if (node.visible === false) {
|
||||
return 0;
|
||||
}
|
||||
@@ -392,7 +424,7 @@ export class IndexTreeModel<T extends Exclude<any, undefined>, TFilterData = voi
|
||||
return node.renderNodeCount;
|
||||
}
|
||||
|
||||
private updateNodeAfterFilterChange(node: IMutableTreeNode<T, TFilterData>): ITreeNode<T, TFilterData>[] {
|
||||
private updateNodeAfterFilterChange(node: IIndexTreeNode<T, TFilterData>): ITreeNode<T, TFilterData>[] {
|
||||
const previousRenderNodeCount = node.renderNodeCount;
|
||||
const result: ITreeNode<T, TFilterData>[] = [];
|
||||
|
||||
@@ -402,7 +434,7 @@ export class IndexTreeModel<T extends Exclude<any, undefined>, TFilterData = voi
|
||||
return result;
|
||||
}
|
||||
|
||||
private _updateNodeAfterFilterChange(node: IMutableTreeNode<T, TFilterData>, parentVisibility: TreeVisibility, result: ITreeNode<T, TFilterData>[], revealed = true): boolean {
|
||||
private _updateNodeAfterFilterChange(node: IIndexTreeNode<T, TFilterData>, parentVisibility: TreeVisibility, result: ITreeNode<T, TFilterData>[], revealed = true): boolean {
|
||||
let visibility: TreeVisibility;
|
||||
|
||||
if (node !== this.root) {
|
||||
@@ -456,7 +488,7 @@ export class IndexTreeModel<T extends Exclude<any, undefined>, TFilterData = voi
|
||||
return node.visible;
|
||||
}
|
||||
|
||||
private _updateAncestorsRenderNodeCount(node: IMutableTreeNode<T, TFilterData> | undefined, diff: number): void {
|
||||
private _updateAncestorsRenderNodeCount(node: IIndexTreeNode<T, TFilterData> | undefined, diff: number): void {
|
||||
if (diff === 0) {
|
||||
return;
|
||||
}
|
||||
@@ -468,7 +500,7 @@ export class IndexTreeModel<T extends Exclude<any, undefined>, TFilterData = voi
|
||||
}
|
||||
}
|
||||
|
||||
private _filterNode(node: IMutableTreeNode<T, TFilterData>, parentVisibility: TreeVisibility): TreeVisibility {
|
||||
private _filterNode(node: IIndexTreeNode<T, TFilterData>, parentVisibility: TreeVisibility): TreeVisibility {
|
||||
const result = this.filter ? this.filter.filter(node.element, parentVisibility) : TreeVisibility.Visible;
|
||||
|
||||
if (typeof result === 'boolean') {
|
||||
@@ -484,7 +516,7 @@ export class IndexTreeModel<T extends Exclude<any, undefined>, TFilterData = voi
|
||||
}
|
||||
|
||||
// cheap
|
||||
private getTreeNode(location: number[], node: IMutableTreeNode<T, TFilterData> = this.root): IMutableTreeNode<T, TFilterData> {
|
||||
private getTreeNode(location: number[], node: IIndexTreeNode<T, TFilterData> = this.root): IIndexTreeNode<T, TFilterData> {
|
||||
if (!location || location.length === 0) {
|
||||
return node;
|
||||
}
|
||||
@@ -492,14 +524,14 @@ export class IndexTreeModel<T extends Exclude<any, undefined>, TFilterData = voi
|
||||
const [index, ...rest] = location;
|
||||
|
||||
if (index < 0 || index > node.children.length) {
|
||||
throw new Error('Invalid tree location');
|
||||
throw new TreeError(this.user, 'Invalid tree location');
|
||||
}
|
||||
|
||||
return this.getTreeNode(rest, node.children[index]);
|
||||
}
|
||||
|
||||
// expensive
|
||||
private getTreeNodeWithListIndex(location: number[]): { node: IMutableTreeNode<T, TFilterData>, listIndex: number, revealed: boolean, visible: boolean } {
|
||||
private getTreeNodeWithListIndex(location: number[]): { node: IIndexTreeNode<T, TFilterData>, listIndex: number, revealed: boolean, visible: boolean } {
|
||||
if (location.length === 0) {
|
||||
return { node: this.root, listIndex: -1, revealed: true, visible: false };
|
||||
}
|
||||
@@ -508,7 +540,7 @@ export class IndexTreeModel<T extends Exclude<any, undefined>, TFilterData = voi
|
||||
const index = location[location.length - 1];
|
||||
|
||||
if (index < 0 || index > parentNode.children.length) {
|
||||
throw new Error('Invalid tree location');
|
||||
throw new TreeError(this.user, 'Invalid tree location');
|
||||
}
|
||||
|
||||
const node = parentNode.children[index];
|
||||
@@ -516,11 +548,11 @@ export class IndexTreeModel<T extends Exclude<any, undefined>, TFilterData = voi
|
||||
return { node, listIndex, revealed, visible: visible && node.visible };
|
||||
}
|
||||
|
||||
private getParentNodeWithListIndex(location: number[], node: IMutableTreeNode<T, TFilterData> = this.root, listIndex: number = 0, revealed = true, visible = true): { parentNode: IMutableTreeNode<T, TFilterData>; listIndex: number; revealed: boolean; visible: boolean; } {
|
||||
private getParentNodeWithListIndex(location: number[], node: IIndexTreeNode<T, TFilterData> = this.root, listIndex: number = 0, revealed = true, visible = true): { parentNode: IIndexTreeNode<T, TFilterData>; listIndex: number; revealed: boolean; visible: boolean; } {
|
||||
const [index, ...rest] = location;
|
||||
|
||||
if (index < 0 || index > node.children.length) {
|
||||
throw new Error('Invalid tree location');
|
||||
throw new TreeError(this.user, 'Invalid tree location');
|
||||
}
|
||||
|
||||
// TODO@joao perf!
|
||||
@@ -545,27 +577,24 @@ export class IndexTreeModel<T extends Exclude<any, undefined>, TFilterData = voi
|
||||
// TODO@joao perf!
|
||||
getNodeLocation(node: ITreeNode<T, TFilterData>): number[] {
|
||||
const location: number[] = [];
|
||||
let indexTreeNode = node as IIndexTreeNode<T, TFilterData>; // typing woes
|
||||
|
||||
while (node.parent) {
|
||||
location.push(node.parent.children.indexOf(node));
|
||||
node = node.parent;
|
||||
while (indexTreeNode.parent) {
|
||||
location.push(indexTreeNode.parent.children.indexOf(indexTreeNode));
|
||||
indexTreeNode = indexTreeNode.parent;
|
||||
}
|
||||
|
||||
return location.reverse();
|
||||
}
|
||||
|
||||
getParentNodeLocation(location: number[]): number[] {
|
||||
if (location.length <= 1) {
|
||||
getParentNodeLocation(location: number[]): number[] | undefined {
|
||||
if (location.length === 0) {
|
||||
return undefined;
|
||||
} else if (location.length === 1) {
|
||||
return [];
|
||||
} else {
|
||||
return tail2(location)[0];
|
||||
}
|
||||
|
||||
return tail2(location)[0];
|
||||
}
|
||||
|
||||
getParentElement(location: number[]): T {
|
||||
const parentLocation = this.getParentNodeLocation(location);
|
||||
const node = this.getTreeNode(parentLocation);
|
||||
return node.element;
|
||||
}
|
||||
|
||||
getFirstElementChild(location: number[]): T | undefined {
|
||||
|
||||
@@ -3,13 +3,14 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Iterator, ISequence } from 'vs/base/common/iterator';
|
||||
import { ISequence } from 'vs/base/common/iterator';
|
||||
import { AbstractTree, IAbstractTreeOptions } from 'vs/base/browser/ui/tree/abstractTree';
|
||||
import { ISpliceable } from 'vs/base/common/sequence';
|
||||
import { ITreeNode, ITreeModel, ITreeElement, ITreeRenderer, ITreeSorter, ICollapseStateChangeEvent } from 'vs/base/browser/ui/tree/tree';
|
||||
import { ObjectTreeModel, IObjectTreeModel } from 'vs/base/browser/ui/tree/objectTreeModel';
|
||||
import { IListVirtualDelegate } from 'vs/base/browser/ui/list/list';
|
||||
import { Event } from 'vs/base/common/event';
|
||||
import { CompressibleObjectTreeModel, ElementMapper, ICompressedTreeNode, ICompressedTreeElement } from 'vs/base/browser/ui/tree/compressedObjectTreeModel';
|
||||
|
||||
export interface IObjectTreeOptions<T, TFilterData = void> extends IAbstractTreeOptions<T, TFilterData> {
|
||||
sorter?: ITreeSorter<T>;
|
||||
@@ -22,19 +23,17 @@ export class ObjectTree<T extends NonNullable<any>, TFilterData = void> extends
|
||||
get onDidChangeCollapseState(): Event<ICollapseStateChangeEvent<T | null, TFilterData>> { return this.model.onDidChangeCollapseState; }
|
||||
|
||||
constructor(
|
||||
user: string,
|
||||
container: HTMLElement,
|
||||
delegate: IListVirtualDelegate<T>,
|
||||
renderers: ITreeRenderer<T, TFilterData, any>[],
|
||||
options: IObjectTreeOptions<T, TFilterData> = {}
|
||||
) {
|
||||
super(container, delegate, renderers, options);
|
||||
super(user, container, delegate, renderers, options);
|
||||
}
|
||||
|
||||
setChildren(
|
||||
element: T | null,
|
||||
children?: ISequence<ITreeElement<T>>
|
||||
): Iterator<ITreeElement<T | null>> {
|
||||
return this.model.setChildren(element, children);
|
||||
setChildren(element: T | null, children?: ISequence<ITreeElement<T>>): void {
|
||||
this.model.setChildren(element, children);
|
||||
}
|
||||
|
||||
rerender(element?: T): void {
|
||||
@@ -50,7 +49,117 @@ export class ObjectTree<T extends NonNullable<any>, TFilterData = void> extends
|
||||
this.model.resort(element, recursive);
|
||||
}
|
||||
|
||||
protected createModel(view: ISpliceable<ITreeNode<T, TFilterData>>, options: IObjectTreeOptions<T, TFilterData>): ITreeModel<T | null, TFilterData, T | null> {
|
||||
return new ObjectTreeModel(view, options);
|
||||
protected createModel(user: string, view: ISpliceable<ITreeNode<T, TFilterData>>, options: IObjectTreeOptions<T, TFilterData>): ITreeModel<T | null, TFilterData, T | null> {
|
||||
return new ObjectTreeModel(user, view, options);
|
||||
}
|
||||
}
|
||||
|
||||
interface ICompressedTreeNodeProvider<T, TFilterData> {
|
||||
getCompressedTreeNode(element: T): ITreeNode<ICompressedTreeNode<T>, TFilterData>;
|
||||
}
|
||||
|
||||
export interface ICompressibleObjectTreeOptions<T, TFilterData = void> extends IObjectTreeOptions<T, TFilterData> {
|
||||
readonly elementMapper?: ElementMapper<T>;
|
||||
}
|
||||
|
||||
export interface ICompressibleTreeRenderer<T, TFilterData = void, TTemplateData = void> extends ITreeRenderer<T, TFilterData, TTemplateData> {
|
||||
renderCompressedElements(node: ITreeNode<ICompressedTreeNode<T>, TFilterData>, index: number, templateData: TTemplateData, height: number | undefined): void;
|
||||
disposeCompressedElements?(node: ITreeNode<ICompressedTreeNode<T>, TFilterData>, index: number, templateData: TTemplateData, height: number | undefined): void;
|
||||
}
|
||||
|
||||
interface CompressibleTemplateData<T, TFilterData, TTemplateData> {
|
||||
compressedTreeNode: ITreeNode<ICompressedTreeNode<T>, TFilterData> | undefined;
|
||||
readonly data: TTemplateData;
|
||||
}
|
||||
|
||||
class CompressibleRenderer<T, TFilterData, TTemplateData> implements ITreeRenderer<T, TFilterData, CompressibleTemplateData<T, TFilterData, TTemplateData>> {
|
||||
|
||||
readonly templateId: string;
|
||||
readonly onDidChangeTwistieState: Event<T> | undefined;
|
||||
|
||||
compressedTreeNodeProvider: ICompressedTreeNodeProvider<T, TFilterData>;
|
||||
|
||||
constructor(private renderer: ICompressibleTreeRenderer<T, TFilterData, TTemplateData>) {
|
||||
this.templateId = renderer.templateId;
|
||||
|
||||
if (renderer.onDidChangeTwistieState) {
|
||||
this.onDidChangeTwistieState = renderer.onDidChangeTwistieState;
|
||||
}
|
||||
}
|
||||
|
||||
renderTemplate(container: HTMLElement): CompressibleTemplateData<T, TFilterData, TTemplateData> {
|
||||
const data = this.renderer.renderTemplate(container);
|
||||
return { compressedTreeNode: undefined, data };
|
||||
}
|
||||
|
||||
renderElement(node: ITreeNode<T, TFilterData>, index: number, templateData: CompressibleTemplateData<T, TFilterData, TTemplateData>, height: number | undefined): void {
|
||||
const compressedTreeNode = this.compressedTreeNodeProvider.getCompressedTreeNode(node.element);
|
||||
|
||||
if (compressedTreeNode.element.elements.length === 1) {
|
||||
templateData.compressedTreeNode = undefined;
|
||||
this.renderer.renderElement(node, index, templateData.data, height);
|
||||
} else {
|
||||
templateData.compressedTreeNode = compressedTreeNode;
|
||||
this.renderer.renderCompressedElements(compressedTreeNode, index, templateData.data, height);
|
||||
}
|
||||
}
|
||||
|
||||
disposeElement(node: ITreeNode<T, TFilterData>, index: number, templateData: CompressibleTemplateData<T, TFilterData, TTemplateData>, height: number | undefined): void {
|
||||
if (templateData.compressedTreeNode) {
|
||||
if (this.renderer.disposeCompressedElements) {
|
||||
this.renderer.disposeCompressedElements(templateData.compressedTreeNode, index, templateData.data, height);
|
||||
}
|
||||
} else {
|
||||
if (this.renderer.disposeElement) {
|
||||
this.renderer.disposeElement(node, index, templateData.data, height);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
disposeTemplate(templateData: CompressibleTemplateData<T, TFilterData, TTemplateData>): void {
|
||||
this.renderer.disposeTemplate(templateData.data);
|
||||
}
|
||||
|
||||
renderTwistie?(element: T, twistieElement: HTMLElement): void {
|
||||
if (this.renderer.renderTwistie) {
|
||||
this.renderer.renderTwistie(element, twistieElement);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class CompressibleObjectTree<T extends NonNullable<any>, TFilterData = void> extends ObjectTree<T, TFilterData> {
|
||||
|
||||
protected model: CompressibleObjectTreeModel<T, TFilterData>;
|
||||
|
||||
constructor(
|
||||
user: string,
|
||||
container: HTMLElement,
|
||||
delegate: IListVirtualDelegate<T>,
|
||||
renderers: ICompressibleTreeRenderer<T, TFilterData, any>[],
|
||||
options: IObjectTreeOptions<T, TFilterData> = {}
|
||||
) {
|
||||
const compressibleRenderers = renderers.map(r => new CompressibleRenderer(r));
|
||||
super(user, container, delegate, compressibleRenderers, options);
|
||||
compressibleRenderers.forEach(r => r.compressedTreeNodeProvider = this);
|
||||
}
|
||||
|
||||
setChildren(element: T | null, children?: ISequence<ICompressedTreeElement<T>>): void {
|
||||
this.model.setChildren(element, children);
|
||||
}
|
||||
|
||||
protected createModel(user: string, view: ISpliceable<ITreeNode<T, TFilterData>>, options: ICompressibleObjectTreeOptions<T, TFilterData>): ITreeModel<T | null, TFilterData, T | null> {
|
||||
return new CompressibleObjectTreeModel(user, view, options);
|
||||
}
|
||||
|
||||
isCompressionEnabled(): boolean {
|
||||
return this.model.isCompressionEnabled();
|
||||
}
|
||||
|
||||
setCompressionEnabled(enabled: boolean): void {
|
||||
this.model.setCompressionEnabled(enabled);
|
||||
}
|
||||
|
||||
getCompressedTreeNode(element: T): ITreeNode<ICompressedTreeNode<T>, TFilterData> {
|
||||
return this.model.getCompressedTreeNode(element)!;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,13 +7,13 @@ import { ISpliceable } from 'vs/base/common/sequence';
|
||||
import { Iterator, ISequence, getSequenceIterator } from 'vs/base/common/iterator';
|
||||
import { IndexTreeModel, IIndexTreeModelOptions } from 'vs/base/browser/ui/tree/indexTreeModel';
|
||||
import { Event } from 'vs/base/common/event';
|
||||
import { ITreeModel, ITreeNode, ITreeElement, ITreeSorter, ICollapseStateChangeEvent, ITreeModelSpliceEvent } from 'vs/base/browser/ui/tree/tree';
|
||||
import { ITreeModel, ITreeNode, ITreeElement, ITreeSorter, ICollapseStateChangeEvent, ITreeModelSpliceEvent, TreeError } from 'vs/base/browser/ui/tree/tree';
|
||||
import { IIdentityProvider } from 'vs/base/browser/ui/list/list';
|
||||
|
||||
export type ITreeNodeCallback<T, TFilterData> = (node: ITreeNode<T, TFilterData>) => void;
|
||||
|
||||
export interface IObjectTreeModel<T extends NonNullable<any>, TFilterData extends NonNullable<any> = void> extends ITreeModel<T | null, TFilterData, T | null> {
|
||||
setChildren(element: T | null, children: ISequence<ITreeElement<T>> | undefined): Iterator<ITreeElement<T>>;
|
||||
setChildren(element: T | null, children: ISequence<ITreeElement<T>> | undefined): void;
|
||||
resort(element?: T | null, recursive?: boolean): void;
|
||||
}
|
||||
|
||||
@@ -38,8 +38,12 @@ export class ObjectTreeModel<T extends NonNullable<any>, TFilterData extends Non
|
||||
|
||||
get size(): number { return this.nodes.size; }
|
||||
|
||||
constructor(list: ISpliceable<ITreeNode<T, TFilterData>>, options: IObjectTreeModelOptions<T, TFilterData> = {}) {
|
||||
this.model = new IndexTreeModel(list, null, options);
|
||||
constructor(
|
||||
private user: string,
|
||||
list: ISpliceable<ITreeNode<T, TFilterData>>,
|
||||
options: IObjectTreeModelOptions<T, TFilterData> = {}
|
||||
) {
|
||||
this.model = new IndexTreeModel(user, list, null, options);
|
||||
this.onDidSplice = this.model.onDidSplice;
|
||||
this.onDidChangeCollapseState = this.model.onDidChangeCollapseState as Event<ICollapseStateChangeEvent<T, TFilterData>>;
|
||||
this.onDidChangeRenderNodeCount = this.model.onDidChangeRenderNodeCount as Event<ITreeNode<T, TFilterData>>;
|
||||
@@ -60,9 +64,9 @@ export class ObjectTreeModel<T extends NonNullable<any>, TFilterData extends Non
|
||||
children: ISequence<ITreeElement<T>> | undefined,
|
||||
onDidCreateNode?: ITreeNodeCallback<T, TFilterData>,
|
||||
onDidDeleteNode?: ITreeNodeCallback<T, TFilterData>
|
||||
): Iterator<ITreeElement<T>> {
|
||||
): void {
|
||||
const location = this.getElementLocation(element);
|
||||
return this._setChildren(location, this.preserveCollapseState(children), onDidCreateNode, onDidDeleteNode);
|
||||
this._setChildren(location, this.preserveCollapseState(children), onDidCreateNode, onDidDeleteNode);
|
||||
}
|
||||
|
||||
private _setChildren(
|
||||
@@ -70,7 +74,7 @@ export class ObjectTreeModel<T extends NonNullable<any>, TFilterData extends Non
|
||||
children: ISequence<ITreeElement<T>> | undefined,
|
||||
onDidCreateNode?: ITreeNodeCallback<T, TFilterData>,
|
||||
onDidDeleteNode?: ITreeNodeCallback<T, TFilterData>
|
||||
): Iterator<ITreeElement<T>> {
|
||||
): void {
|
||||
const insertedElements = new Set<T | null>();
|
||||
const insertedElementIds = new Set<string>();
|
||||
|
||||
@@ -106,15 +110,13 @@ export class ObjectTreeModel<T extends NonNullable<any>, TFilterData extends Non
|
||||
}
|
||||
};
|
||||
|
||||
const result = this.model.splice(
|
||||
this.model.splice(
|
||||
[...location, 0],
|
||||
Number.MAX_VALUE,
|
||||
children,
|
||||
_onDidCreateNode,
|
||||
_onDidDeleteNode
|
||||
);
|
||||
|
||||
return result as Iterator<ITreeElement<T>>;
|
||||
}
|
||||
|
||||
private preserveCollapseState(elements: ISequence<ITreeElement<T>> | undefined): ISequence<ITreeElement<T>> {
|
||||
@@ -182,11 +184,6 @@ export class ObjectTreeModel<T extends NonNullable<any>, TFilterData extends Non
|
||||
}));
|
||||
}
|
||||
|
||||
getParentElement(ref: T | null = null): T | null {
|
||||
const location = this.getElementLocation(ref);
|
||||
return this.model.getParentElement(location);
|
||||
}
|
||||
|
||||
getFirstElementChild(ref: T | null = null): T | null | undefined {
|
||||
const location = this.getElementLocation(ref);
|
||||
return this.model.getFirstElementChild(location);
|
||||
@@ -212,6 +209,11 @@ export class ObjectTreeModel<T extends NonNullable<any>, TFilterData extends Non
|
||||
return this.model.isCollapsible(location);
|
||||
}
|
||||
|
||||
setCollapsible(element: T | null, collapsible?: boolean): boolean {
|
||||
const location = this.getElementLocation(element);
|
||||
return this.model.setCollapsible(location, collapsible);
|
||||
}
|
||||
|
||||
isCollapsed(element: T | null): boolean {
|
||||
const location = this.getElementLocation(element);
|
||||
return this.model.isCollapsed(location);
|
||||
@@ -239,7 +241,7 @@ export class ObjectTreeModel<T extends NonNullable<any>, TFilterData extends Non
|
||||
const node = this.nodes.get(element);
|
||||
|
||||
if (!node) {
|
||||
throw new Error(`Tree element not found: ${element}`);
|
||||
throw new TreeError(this.user, `Tree element not found: ${element}`);
|
||||
}
|
||||
|
||||
return node;
|
||||
@@ -251,16 +253,15 @@ export class ObjectTreeModel<T extends NonNullable<any>, TFilterData extends Non
|
||||
|
||||
getParentNodeLocation(element: T | null): T | null {
|
||||
if (element === null) {
|
||||
throw new Error(`Invalid getParentNodeLocation call`);
|
||||
throw new TreeError(this.user, `Invalid getParentNodeLocation call`);
|
||||
}
|
||||
|
||||
const node = this.nodes.get(element);
|
||||
const node = this.nodes.get(element)!;
|
||||
const location = this.model.getNodeLocation(node);
|
||||
const parentLocation = this.model.getParentNodeLocation(location);
|
||||
const parent = this.model.getNode(parentLocation);
|
||||
|
||||
if (!node) {
|
||||
throw new Error(`Tree element not found: ${element}`);
|
||||
}
|
||||
|
||||
return node.parent!.element;
|
||||
return parent.element;
|
||||
}
|
||||
|
||||
private getElementLocation(element: T | null): number[] {
|
||||
@@ -271,7 +272,7 @@ export class ObjectTreeModel<T extends NonNullable<any>, TFilterData extends Non
|
||||
const node = this.nodes.get(element);
|
||||
|
||||
if (!node) {
|
||||
throw new Error(`Tree element not found: ${element}`);
|
||||
throw new TreeError(this.user, `Tree element not found: ${element}`);
|
||||
}
|
||||
|
||||
return this.model.getNodeLocation(node);
|
||||
|
||||
@@ -81,7 +81,6 @@ export interface ITreeElement<T> {
|
||||
|
||||
export interface ITreeNode<T, TFilterData = void> {
|
||||
readonly element: T;
|
||||
readonly parent: ITreeNode<T, TFilterData> | undefined;
|
||||
readonly children: ITreeNode<T, TFilterData>[];
|
||||
readonly depth: number;
|
||||
readonly visibleChildrenCount: number;
|
||||
@@ -113,13 +112,13 @@ export interface ITreeModel<T, TFilterData, TRef> {
|
||||
getListRenderCount(location: TRef): number;
|
||||
getNode(location?: TRef): ITreeNode<T, any>;
|
||||
getNodeLocation(node: ITreeNode<T, any>): TRef;
|
||||
getParentNodeLocation(location: TRef): TRef;
|
||||
getParentNodeLocation(location: TRef): TRef | undefined;
|
||||
|
||||
getParentElement(location: TRef): T;
|
||||
getFirstElementChild(location: TRef): T | undefined;
|
||||
getLastElementAncestor(location?: TRef): T | undefined;
|
||||
|
||||
isCollapsible(location: TRef): boolean;
|
||||
setCollapsible(location: TRef, collapsible?: boolean): boolean;
|
||||
isCollapsed(location: TRef): boolean;
|
||||
setCollapsed(location: TRef, collapsed?: boolean, recursive?: boolean): boolean;
|
||||
expandTo(location: TRef): void;
|
||||
@@ -159,7 +158,6 @@ export interface ITreeContextMenuEvent<T> {
|
||||
export interface ITreeNavigator<T> {
|
||||
current(): T | null;
|
||||
previous(): T | null;
|
||||
parent(): T | null;
|
||||
first(): T | null;
|
||||
last(): T | null;
|
||||
next(): T | null;
|
||||
@@ -194,3 +192,28 @@ export const TreeDragOverReactions = {
|
||||
export interface ITreeDragAndDrop<T> extends IListDragAndDrop<T> {
|
||||
onDragOver(data: IDragAndDropData, targetElement: T | undefined, targetIndex: number | undefined, originalEvent: DragEvent): boolean | ITreeDragOverReaction;
|
||||
}
|
||||
|
||||
export class TreeError extends Error {
|
||||
|
||||
constructor(user: string, message: string) {
|
||||
super(`TreeError [${user}] ${message}`);
|
||||
}
|
||||
}
|
||||
|
||||
export class WeakMapper<K extends object, V> {
|
||||
|
||||
constructor(private fn: (k: K) => V) { }
|
||||
|
||||
private _map = new WeakMap<K, V>();
|
||||
|
||||
map(key: K): V {
|
||||
let result = this._map.get(key);
|
||||
|
||||
if (!result) {
|
||||
result = this.fn(key);
|
||||
this._map.set(key, result);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,3 +8,11 @@ import { URI } from 'vs/base/common/uri';
|
||||
export function getPathFromAmdModule(requirefn: typeof require, relativePath: string): string {
|
||||
return URI.parse(requirefn.toUrl(relativePath)).fsPath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reference a resource that might be inlined.
|
||||
* Do not rename this method unless you adopt the build scripts.
|
||||
*/
|
||||
export function registerAndGetAmdImageURL(absolutePath: string): string {
|
||||
return require.toUrl(absolutePath);
|
||||
}
|
||||
|
||||
@@ -7,17 +7,14 @@
|
||||
* An interface for a JavaScript object that
|
||||
* acts a dictionary. The keys are strings.
|
||||
*/
|
||||
export interface IStringDictionary<V> {
|
||||
[name: string]: V;
|
||||
}
|
||||
export type IStringDictionary<V> = Record<string, V>;
|
||||
|
||||
|
||||
/**
|
||||
* An interface for a JavaScript object that
|
||||
* acts a dictionary. The keys are numbers.
|
||||
*/
|
||||
export interface INumberDictionary<V> {
|
||||
[idx: number]: V;
|
||||
}
|
||||
export type INumberDictionary<V> = Record<number, V>;
|
||||
|
||||
const hasOwnProperty = Object.prototype.hasOwnProperty;
|
||||
|
||||
@@ -138,4 +135,4 @@ export class SetMap<K, V> {
|
||||
|
||||
values.forEach(fn);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -114,9 +114,9 @@ export function log(entry: IRemoteConsoleLog, label: string): void {
|
||||
// First arg is a string
|
||||
if (typeof args[0] === 'string') {
|
||||
if (topFrame && isOneStringArg) {
|
||||
consoleArgs = [`%c[${label}] %c${args[0]} %c${topFrame}`, color('blue'), color('black'), color('grey')];
|
||||
consoleArgs = [`%c[${label}] %c${args[0]} %c${topFrame}`, color('blue'), color(''), color('grey')];
|
||||
} else {
|
||||
consoleArgs = [`%c[${label}] %c${args[0]}`, color('blue'), color('black'), ...args.slice(1)];
|
||||
consoleArgs = [`%c[${label}] %c${args[0]}`, color('blue'), color(''), ...args.slice(1)];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -139,4 +139,4 @@ export function log(entry: IRemoteConsoleLog, label: string): void {
|
||||
|
||||
function color(color: string): string {
|
||||
return `color: ${color}`;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -455,6 +455,14 @@ function printTable(table: number[][], pattern: string, patternLen: number, word
|
||||
return ret;
|
||||
}
|
||||
|
||||
function printTables(pattern: string, patternStart: number, word: string, wordStart: number): void {
|
||||
pattern = pattern.substr(patternStart);
|
||||
word = word.substr(wordStart);
|
||||
console.log(printTable(_table, pattern, pattern.length, word, word.length));
|
||||
console.log(printTable(_arrows, pattern, pattern.length, word, word.length));
|
||||
console.log(printTable(_scores, pattern, pattern.length, word, word.length));
|
||||
}
|
||||
|
||||
function isSeparatorAtPos(value: string, index: number): boolean {
|
||||
if (index < 0 || index >= value.length) {
|
||||
return false;
|
||||
@@ -530,130 +538,127 @@ export interface FuzzyScorer {
|
||||
(pattern: string, lowPattern: string, patternPos: number, word: string, lowWord: string, wordPos: number, firstMatchCanBeWeak: boolean): FuzzyScore | undefined;
|
||||
}
|
||||
|
||||
export function fuzzyScore(pattern: string, patternLow: string, patternPos: number, word: string, wordLow: string, wordPos: number, firstMatchCanBeWeak: boolean): FuzzyScore | undefined {
|
||||
|
||||
if (patternPos > 0) {
|
||||
pattern = pattern.substr(patternPos);
|
||||
patternLow = patternLow.substr(patternPos);
|
||||
patternPos = 0;
|
||||
}
|
||||
export function fuzzyScore(pattern: string, patternLow: string, patternStart: number, word: string, wordLow: string, wordStart: number, firstMatchCanBeWeak: boolean): FuzzyScore | undefined {
|
||||
|
||||
const patternLen = pattern.length > _maxLen ? _maxLen : pattern.length;
|
||||
const wordLen = word.length > _maxLen ? _maxLen : word.length;
|
||||
|
||||
if (patternPos >= patternLen || wordPos >= wordLen || patternLen > wordLen) {
|
||||
if (patternStart >= patternLen || wordStart >= wordLen || patternLen > wordLen) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// Run a simple check if the characters of pattern occur
|
||||
// (in order) at all in word. If that isn't the case we
|
||||
// stop because no match will be possible
|
||||
if (!isPatternInWord(patternLow, patternPos, patternLen, wordLow, wordPos, wordLen)) {
|
||||
if (!isPatternInWord(patternLow, patternStart, patternLen, wordLow, wordStart, wordLen)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const patternStartPos = patternPos;
|
||||
const wordStartPos = wordPos;
|
||||
let row: number = 1;
|
||||
let column: number = 1;
|
||||
let patternPos = patternStart;
|
||||
let wordPos = wordStart;
|
||||
|
||||
// There will be a match, fill in tables
|
||||
for (patternPos = patternStartPos + 1; patternPos <= patternLen; patternPos++) {
|
||||
for (row = 1, patternPos = patternStart; patternPos < patternLen; row++ , patternPos++) {
|
||||
|
||||
for (wordPos = 1; wordPos <= wordLen; wordPos++) {
|
||||
for (column = 1, wordPos = wordStart; wordPos < wordLen; column++ , wordPos++) {
|
||||
|
||||
let score = -1;
|
||||
if (patternLow[patternPos - 1] === wordLow[wordPos - 1]) {
|
||||
const score = _doScore(pattern, patternLow, patternPos, patternStart, word, wordLow, wordPos);
|
||||
|
||||
if (wordPos === (patternPos - patternStartPos)) {
|
||||
// common prefix: `foobar <-> foobaz`
|
||||
// ^^^^^
|
||||
if (pattern[patternPos - 1] === word[wordPos - 1]) {
|
||||
score = 7;
|
||||
} else {
|
||||
score = 5;
|
||||
}
|
||||
} else if (isUpperCaseAtPos(wordPos - 1, word, wordLow) && (wordPos === 1 || !isUpperCaseAtPos(wordPos - 2, word, wordLow))) {
|
||||
// hitting upper-case: `foo <-> forOthers`
|
||||
// ^^ ^
|
||||
if (pattern[patternPos - 1] === word[wordPos - 1]) {
|
||||
score = 7;
|
||||
} else {
|
||||
score = 5;
|
||||
}
|
||||
} else if (isSeparatorAtPos(wordLow, wordPos - 1) && (wordPos === 1 || !isSeparatorAtPos(wordLow, wordPos - 2))) {
|
||||
// hitting a separator: `. <-> foo.bar`
|
||||
// ^
|
||||
score = 5;
|
||||
_scores[row][column] = score;
|
||||
|
||||
} else if (isSeparatorAtPos(wordLow, wordPos - 2) || isWhitespaceAtPos(wordLow, wordPos - 2)) {
|
||||
// post separator: `foo <-> bar_foo`
|
||||
// ^^^
|
||||
score = 5;
|
||||
|
||||
} else {
|
||||
score = 1;
|
||||
}
|
||||
}
|
||||
|
||||
_scores[patternPos][wordPos] = score;
|
||||
|
||||
const diag = _table[patternPos - 1][wordPos - 1] + (score > 1 ? 1 : score);
|
||||
const top = _table[patternPos - 1][wordPos] + -1;
|
||||
const left = _table[patternPos][wordPos - 1] + -1;
|
||||
const diag = _table[row - 1][column - 1] + (score > 1 ? 1 : score);
|
||||
const top = _table[row - 1][column] + -1;
|
||||
const left = _table[row][column - 1] + -1;
|
||||
|
||||
if (left >= top) {
|
||||
// left or diag
|
||||
if (left > diag) {
|
||||
_table[patternPos][wordPos] = left;
|
||||
_arrows[patternPos][wordPos] = Arrow.Left;
|
||||
_table[row][column] = left;
|
||||
_arrows[row][column] = Arrow.Left;
|
||||
} else if (left === diag) {
|
||||
_table[patternPos][wordPos] = left;
|
||||
_arrows[patternPos][wordPos] = Arrow.Left | Arrow.Diag;
|
||||
_table[row][column] = left;
|
||||
_arrows[row][column] = Arrow.Left | Arrow.Diag;
|
||||
} else {
|
||||
_table[patternPos][wordPos] = diag;
|
||||
_arrows[patternPos][wordPos] = Arrow.Diag;
|
||||
_table[row][column] = diag;
|
||||
_arrows[row][column] = Arrow.Diag;
|
||||
}
|
||||
} else {
|
||||
// top or diag
|
||||
if (top > diag) {
|
||||
_table[patternPos][wordPos] = top;
|
||||
_arrows[patternPos][wordPos] = Arrow.Top;
|
||||
_table[row][column] = top;
|
||||
_arrows[row][column] = Arrow.Top;
|
||||
} else if (top === diag) {
|
||||
_table[patternPos][wordPos] = top;
|
||||
_arrows[patternPos][wordPos] = Arrow.Top | Arrow.Diag;
|
||||
_table[row][column] = top;
|
||||
_arrows[row][column] = Arrow.Top | Arrow.Diag;
|
||||
} else {
|
||||
_table[patternPos][wordPos] = diag;
|
||||
_arrows[patternPos][wordPos] = Arrow.Diag;
|
||||
_table[row][column] = diag;
|
||||
_arrows[row][column] = Arrow.Diag;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (_debug) {
|
||||
console.log(printTable(_table, pattern, patternLen, word, wordLen));
|
||||
console.log(printTable(_arrows, pattern, patternLen, word, wordLen));
|
||||
console.log(printTable(_scores, pattern, patternLen, word, wordLen));
|
||||
printTables(pattern, patternStart, word, wordStart);
|
||||
}
|
||||
|
||||
_matchesCount = 0;
|
||||
_topScore = -100;
|
||||
_patternStartPos = patternStartPos;
|
||||
_wordStart = wordStart;
|
||||
_firstMatchCanBeWeak = firstMatchCanBeWeak;
|
||||
_findAllMatches2(patternLen, wordLen, patternLen === wordLen ? 1 : 0, 0, false);
|
||||
|
||||
_findAllMatches2(row - 1, column - 1, patternLen === wordLen ? 1 : 0, 0, false);
|
||||
if (_matchesCount === 0) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return [_topScore, _topMatch2, wordStartPos];
|
||||
return [_topScore, _topMatch2, wordStart];
|
||||
}
|
||||
|
||||
function _doScore(pattern: string, patternLow: string, patternPos: number, patternStart: number, word: string, wordLow: string, wordPos: number) {
|
||||
if (patternLow[patternPos] !== wordLow[wordPos]) {
|
||||
return -1;
|
||||
}
|
||||
if (wordPos === (patternPos - patternStart)) {
|
||||
// common prefix: `foobar <-> foobaz`
|
||||
// ^^^^^
|
||||
if (pattern[patternPos] === word[wordPos]) {
|
||||
return 7;
|
||||
} else {
|
||||
return 5;
|
||||
}
|
||||
} else if (isUpperCaseAtPos(wordPos, word, wordLow) && (wordPos === 0 || !isUpperCaseAtPos(wordPos - 1, word, wordLow))) {
|
||||
// hitting upper-case: `foo <-> forOthers`
|
||||
// ^^ ^
|
||||
if (pattern[patternPos] === word[wordPos]) {
|
||||
return 7;
|
||||
} else {
|
||||
return 5;
|
||||
}
|
||||
} else if (isSeparatorAtPos(wordLow, wordPos) && (wordPos === 0 || !isSeparatorAtPos(wordLow, wordPos - 1))) {
|
||||
// hitting a separator: `. <-> foo.bar`
|
||||
// ^
|
||||
return 5;
|
||||
|
||||
} else if (isSeparatorAtPos(wordLow, wordPos - 1) || isWhitespaceAtPos(wordLow, wordPos - 1)) {
|
||||
// post separator: `foo <-> bar_foo`
|
||||
// ^^^
|
||||
return 5;
|
||||
|
||||
} else {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
let _matchesCount: number = 0;
|
||||
let _topMatch2: number = 0;
|
||||
let _topScore: number = 0;
|
||||
let _patternStartPos: number = 0;
|
||||
let _wordStart: number = 0;
|
||||
let _firstMatchCanBeWeak: boolean = false;
|
||||
|
||||
function _findAllMatches2(patternPos: number, wordPos: number, total: number, matches: number, lastMatched: boolean): void {
|
||||
function _findAllMatches2(row: number, column: number, total: number, matches: number, lastMatched: boolean): void {
|
||||
|
||||
if (_matchesCount >= 10 || total < -25) {
|
||||
// stop when having already 10 results, or
|
||||
@@ -663,14 +668,14 @@ function _findAllMatches2(patternPos: number, wordPos: number, total: number, ma
|
||||
|
||||
let simpleMatchCount = 0;
|
||||
|
||||
while (patternPos > _patternStartPos && wordPos > 0) {
|
||||
while (row > 0 && column > 0) {
|
||||
|
||||
const score = _scores[patternPos][wordPos];
|
||||
const arrow = _arrows[patternPos][wordPos];
|
||||
const score = _scores[row][column];
|
||||
const arrow = _arrows[row][column];
|
||||
|
||||
if (arrow === Arrow.Left) {
|
||||
// left -> no match, skip a word character
|
||||
wordPos -= 1;
|
||||
column -= 1;
|
||||
if (lastMatched) {
|
||||
total -= 5; // new gap penalty
|
||||
} else if (matches !== 0) {
|
||||
@@ -684,8 +689,8 @@ function _findAllMatches2(patternPos: number, wordPos: number, total: number, ma
|
||||
if (arrow & Arrow.Left) {
|
||||
// left
|
||||
_findAllMatches2(
|
||||
patternPos,
|
||||
wordPos - 1,
|
||||
row,
|
||||
column - 1,
|
||||
matches !== 0 ? total - 1 : total, // gap penalty after first match
|
||||
matches,
|
||||
lastMatched
|
||||
@@ -694,12 +699,12 @@ function _findAllMatches2(patternPos: number, wordPos: number, total: number, ma
|
||||
|
||||
// diag
|
||||
total += score;
|
||||
patternPos -= 1;
|
||||
wordPos -= 1;
|
||||
row -= 1;
|
||||
column -= 1;
|
||||
lastMatched = true;
|
||||
|
||||
// match -> set a 1 at the word pos
|
||||
matches += 2 ** wordPos;
|
||||
matches += 2 ** (column + _wordStart);
|
||||
|
||||
// count simple matches and boost a row of
|
||||
// simple matches when they yield in a
|
||||
@@ -707,7 +712,7 @@ function _findAllMatches2(patternPos: number, wordPos: number, total: number, ma
|
||||
if (score === 1) {
|
||||
simpleMatchCount += 1;
|
||||
|
||||
if (patternPos === _patternStartPos && !_firstMatchCanBeWeak) {
|
||||
if (row === 0 && !_firstMatchCanBeWeak) {
|
||||
// when the first match is a weak
|
||||
// match we discard it
|
||||
return undefined;
|
||||
@@ -724,7 +729,7 @@ function _findAllMatches2(patternPos: number, wordPos: number, total: number, ma
|
||||
}
|
||||
}
|
||||
|
||||
total -= wordPos >= 3 ? 9 : wordPos * 3; // late start penalty
|
||||
total -= column >= 3 ? 9 : column * 3; // late start penalty
|
||||
|
||||
// dynamically keep track of the current top score
|
||||
// and insert the current best score at head, the rest at tail
|
||||
|
||||
@@ -207,17 +207,17 @@ export function createScanner(text: string, ignoreTrivia: boolean = false): JSON
|
||||
|
||||
function scanHexDigits(count: number): number {
|
||||
let digits = 0;
|
||||
let value = 0;
|
||||
let hexValue = 0;
|
||||
while (digits < count) {
|
||||
const ch = text.charCodeAt(pos);
|
||||
if (ch >= CharacterCodes._0 && ch <= CharacterCodes._9) {
|
||||
value = value * 16 + ch - CharacterCodes._0;
|
||||
hexValue = hexValue * 16 + ch - CharacterCodes._0;
|
||||
}
|
||||
else if (ch >= CharacterCodes.A && ch <= CharacterCodes.F) {
|
||||
value = value * 16 + ch - CharacterCodes.A + 10;
|
||||
hexValue = hexValue * 16 + ch - CharacterCodes.A + 10;
|
||||
}
|
||||
else if (ch >= CharacterCodes.a && ch <= CharacterCodes.f) {
|
||||
value = value * 16 + ch - CharacterCodes.a + 10;
|
||||
hexValue = hexValue * 16 + ch - CharacterCodes.a + 10;
|
||||
}
|
||||
else {
|
||||
break;
|
||||
@@ -226,9 +226,9 @@ export function createScanner(text: string, ignoreTrivia: boolean = false): JSON
|
||||
digits++;
|
||||
}
|
||||
if (digits < count) {
|
||||
value = -1;
|
||||
hexValue = -1;
|
||||
}
|
||||
return value;
|
||||
return hexValue;
|
||||
}
|
||||
|
||||
function setPosition(newPosition: number) {
|
||||
@@ -291,7 +291,7 @@ export function createScanner(text: string, ignoreTrivia: boolean = false): JSON
|
||||
scanError = ScanError.UnexpectedEndOfString;
|
||||
break;
|
||||
}
|
||||
let ch = text.charCodeAt(pos);
|
||||
const ch = text.charCodeAt(pos);
|
||||
if (ch === CharacterCodes.doubleQuote) {
|
||||
result += text.substring(start, pos);
|
||||
pos++;
|
||||
@@ -304,8 +304,8 @@ export function createScanner(text: string, ignoreTrivia: boolean = false): JSON
|
||||
scanError = ScanError.UnexpectedEndOfString;
|
||||
break;
|
||||
}
|
||||
ch = text.charCodeAt(pos++);
|
||||
switch (ch) {
|
||||
const ch2 = text.charCodeAt(pos++);
|
||||
switch (ch2) {
|
||||
case CharacterCodes.doubleQuote:
|
||||
result += '\"';
|
||||
break;
|
||||
@@ -331,9 +331,9 @@ export function createScanner(text: string, ignoreTrivia: boolean = false): JSON
|
||||
result += '\t';
|
||||
break;
|
||||
case CharacterCodes.u:
|
||||
const ch = scanHexDigits(4);
|
||||
if (ch >= 0) {
|
||||
result += String.fromCharCode(ch);
|
||||
const ch3 = scanHexDigits(4);
|
||||
if (ch3 >= 0) {
|
||||
result += String.fromCharCode(ch3);
|
||||
} else {
|
||||
scanError = ScanError.InvalidUnicode;
|
||||
}
|
||||
@@ -1340,4 +1340,4 @@ function getLiteralNodeType(value: any): NodeType {
|
||||
case 'string': return 'string';
|
||||
default: return 'null';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -144,11 +144,11 @@ function withFormatting(text: string, edit: Edit, formattingOptions: FormattingO
|
||||
|
||||
// apply the formatting edits and track the begin and end offsets of the changes
|
||||
for (let i = edits.length - 1; i >= 0; i--) {
|
||||
const edit = edits[i];
|
||||
newText = applyEdit(newText, edit);
|
||||
begin = Math.min(begin, edit.offset);
|
||||
end = Math.max(end, edit.offset + edit.length);
|
||||
end += edit.content.length - edit.length;
|
||||
const curr = edits[i];
|
||||
newText = applyEdit(newText, curr);
|
||||
begin = Math.min(begin, curr.offset);
|
||||
end = Math.max(end, curr.offset + curr.length);
|
||||
end += curr.content.length - curr.length;
|
||||
}
|
||||
// create a single edit with all changes
|
||||
const editLength = text.length - (newText.length - end) - begin;
|
||||
@@ -182,4 +182,4 @@ export function applyEdits(text: string, edits: Edit[]): string {
|
||||
|
||||
export function isWS(text: string, offset: number) {
|
||||
return '\r\n \t'.indexOf(text.charAt(offset)) !== -1;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,6 +61,7 @@ export interface IJSONSchema {
|
||||
markdownDescription?: string; // VSCode extension
|
||||
doNotSuggest?: boolean; // VSCode extension
|
||||
allowComments?: boolean; // VSCode extension
|
||||
allowsTrailingCommas?: boolean; // VSCode extension
|
||||
}
|
||||
|
||||
export interface IJSONSchemaMap {
|
||||
|
||||
@@ -283,7 +283,7 @@ interface ISegment {
|
||||
* @param value string to which templating is applied
|
||||
* @param values the values of the templates to use
|
||||
*/
|
||||
export function template(template: string, values: { [key: string]: string | ISeparator | null } = Object.create(null)): string {
|
||||
export function template(template: string, values: { [key: string]: string | ISeparator | undefined | null } = Object.create(null)): string {
|
||||
const segments: ISegment[] = [];
|
||||
|
||||
let inVariable = false;
|
||||
|
||||
@@ -18,7 +18,7 @@ export function values<V>(forEachable: { forEach(callback: (value: V, ...more: a
|
||||
|
||||
export function keys<K, V>(map: Map<K, V>): K[] {
|
||||
const result: K[] = [];
|
||||
map.forEach((value, key) => result.push(key));
|
||||
map.forEach((_value, key) => result.push(key));
|
||||
|
||||
return result;
|
||||
}
|
||||
@@ -482,8 +482,6 @@ export class ResourceMap<T> {
|
||||
}
|
||||
}
|
||||
|
||||
// We should fold BoundedMap and LinkedMap. See https://github.com/Microsoft/vscode/issues/28496
|
||||
|
||||
interface Item<K, V> {
|
||||
previous: Item<K, V> | undefined;
|
||||
next: Item<K, V> | undefined;
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { URI, UriComponents } from 'vs/base/common/uri';
|
||||
import * as platform from 'vs/base/common/platform';
|
||||
|
||||
export namespace Schemas {
|
||||
@@ -60,18 +60,24 @@ class RemoteAuthoritiesImpl {
|
||||
private readonly _ports: { [authority: string]: number; };
|
||||
private readonly _connectionTokens: { [authority: string]: string; };
|
||||
private _preferredWebSchema: 'http' | 'https';
|
||||
private _delegate: ((uri: URI) => UriComponents) | null;
|
||||
|
||||
constructor() {
|
||||
this._hosts = Object.create(null);
|
||||
this._ports = Object.create(null);
|
||||
this._connectionTokens = Object.create(null);
|
||||
this._preferredWebSchema = 'http';
|
||||
this._delegate = null;
|
||||
}
|
||||
|
||||
public setPreferredWebSchema(schema: 'http' | 'https') {
|
||||
this._preferredWebSchema = schema;
|
||||
}
|
||||
|
||||
public setDelegate(delegate: (uri: URI) => UriComponents): void {
|
||||
this._delegate = delegate;
|
||||
}
|
||||
|
||||
public set(authority: string, host: string, port: number): void {
|
||||
this._hosts[authority] = host;
|
||||
this._ports[authority] = port;
|
||||
@@ -81,7 +87,12 @@ class RemoteAuthoritiesImpl {
|
||||
this._connectionTokens[authority] = connectionToken;
|
||||
}
|
||||
|
||||
public rewrite(authority: string, path: string): URI {
|
||||
public rewrite(uri: URI): URI {
|
||||
if (this._delegate) {
|
||||
const result = this._delegate(uri);
|
||||
return URI.revive(result);
|
||||
}
|
||||
const authority = uri.authority;
|
||||
const host = this._hosts[authority];
|
||||
const port = this._ports[authority];
|
||||
const connectionToken = this._connectionTokens[authority];
|
||||
@@ -89,7 +100,7 @@ class RemoteAuthoritiesImpl {
|
||||
scheme: platform.isWeb ? this._preferredWebSchema : Schemas.vscodeRemoteResource,
|
||||
authority: `${host}:${port}`,
|
||||
path: `/vscode-remote-resource`,
|
||||
query: `path=${encodeURIComponent(path)}&tkn=${encodeURIComponent(connectionToken)}`
|
||||
query: `path=${encodeURIComponent(uri.path)}&tkn=${encodeURIComponent(connectionToken)}`
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -156,20 +156,21 @@ export const translationsConfigFile = _translationsConfigFile;
|
||||
const _globals = (typeof self === 'object' ? self : typeof global === 'object' ? global : {} as any);
|
||||
export const globals: any = _globals;
|
||||
|
||||
let _setImmediate: ((callback: (...args: any[]) => void) => number) | null = null;
|
||||
export function setImmediate(callback: (...args: any[]) => void): number {
|
||||
if (_setImmediate === null) {
|
||||
if (globals.setImmediate) {
|
||||
_setImmediate = globals.setImmediate.bind(globals);
|
||||
} else if (typeof process !== 'undefined' && typeof process.nextTick === 'function') {
|
||||
_setImmediate = process.nextTick.bind(process);
|
||||
} else {
|
||||
_setImmediate = globals.setTimeout.bind(globals);
|
||||
}
|
||||
}
|
||||
return _setImmediate!(callback);
|
||||
interface ISetImmediate {
|
||||
(callback: (...args: any[]) => void): void;
|
||||
}
|
||||
|
||||
export const setImmediate: ISetImmediate = (function defineSetImmediate() {
|
||||
if (globals.setImmediate) {
|
||||
return globals.setImmediate.bind(globals);
|
||||
}
|
||||
if (typeof process !== 'undefined' && typeof process.nextTick === 'function') {
|
||||
return process.nextTick.bind(process);
|
||||
}
|
||||
const _promise = Promise.resolve();
|
||||
return (callback: (...args: any[]) => void) => _promise.then(callback);
|
||||
})();
|
||||
|
||||
export const enum OperatingSystem {
|
||||
Windows = 1,
|
||||
Macintosh = 2,
|
||||
|
||||
@@ -10,7 +10,7 @@ interface IProcess {
|
||||
env: IProcessEnvironment;
|
||||
|
||||
cwd(): string;
|
||||
nextTick(callback: (...args: any[]) => void): number;
|
||||
nextTick(callback: (...args: any[]) => void): void;
|
||||
}
|
||||
|
||||
declare const process: IProcess;
|
||||
@@ -18,7 +18,7 @@ const safeProcess: IProcess = (typeof process === 'undefined') ? {
|
||||
cwd(): string { return '/'; },
|
||||
env: Object.create(null),
|
||||
get platform(): string { return isWindows ? 'win32' : isMacintosh ? 'darwin' : 'linux'; },
|
||||
nextTick(callback: (...args: any[]) => void): number { return setImmediate(callback); }
|
||||
nextTick(callback: (...args: any[]) => void): void { return setImmediate(callback); }
|
||||
} : process;
|
||||
|
||||
export const cwd = safeProcess.cwd;
|
||||
|
||||
@@ -12,8 +12,12 @@ export function buildReplaceStringWithCasePreserved(matches: string[] | null, pa
|
||||
} else if (matches[0].toLowerCase() === matches[0]) {
|
||||
return pattern.toLowerCase();
|
||||
} else if (strings.containsUppercaseCharacter(matches[0][0])) {
|
||||
if (validateSpecificSpecialCharacter(matches, pattern, '-')) {
|
||||
const containsHyphens = validateSpecificSpecialCharacter(matches, pattern, '-');
|
||||
const containsUnderscores = validateSpecificSpecialCharacter(matches, pattern, '_');
|
||||
if (containsHyphens && !containsUnderscores) {
|
||||
return buildReplaceStringForSpecificSpecialCharacter(matches, pattern, '-');
|
||||
} else if (!containsHyphens && containsUnderscores) {
|
||||
return buildReplaceStringForSpecificSpecialCharacter(matches, pattern, '_');
|
||||
} else {
|
||||
return pattern[0].toUpperCase() + pattern.substr(1);
|
||||
}
|
||||
@@ -27,8 +31,8 @@ export function buildReplaceStringWithCasePreserved(matches: string[] | null, pa
|
||||
}
|
||||
|
||||
function validateSpecificSpecialCharacter(matches: string[], pattern: string, specialCharacter: string): boolean {
|
||||
const doesConatinSpecialCharacter = matches[0].indexOf(specialCharacter) !== -1 && pattern.indexOf(specialCharacter) !== -1;
|
||||
return doesConatinSpecialCharacter && matches[0].split(specialCharacter).length === pattern.split(specialCharacter).length;
|
||||
const doesContainSpecialCharacter = matches[0].indexOf(specialCharacter) !== -1 && pattern.indexOf(specialCharacter) !== -1;
|
||||
return doesContainSpecialCharacter && matches[0].split(specialCharacter).length === pattern.split(specialCharacter).length;
|
||||
}
|
||||
|
||||
function buildReplaceStringForSpecificSpecialCharacter(matches: string[], pattern: string, specialCharacter: string): string {
|
||||
|
||||
@@ -5,11 +5,6 @@
|
||||
|
||||
import { CharCode } from 'vs/base/common/charCode';
|
||||
|
||||
/**
|
||||
* The empty string.
|
||||
*/
|
||||
export const empty = '';
|
||||
|
||||
export function isFalsyOrWhitespace(str: string | undefined): boolean {
|
||||
if (!str || typeof str !== 'string') {
|
||||
return true;
|
||||
@@ -70,7 +65,7 @@ export function escape(html: string): string {
|
||||
* Escapes regular expression characters in a given string
|
||||
*/
|
||||
export function escapeRegExpCharacters(value: string): string {
|
||||
return value.replace(/[\-\\\{\}\*\+\?\|\^\$\.\[\]\(\)\#]/g, '\\$&');
|
||||
return value.replace(/[\\\{\}\*\+\?\|\^\$\.\[\]\(\)]/g, '\\$&');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -608,7 +603,7 @@ export function lcut(text: string, n: number) {
|
||||
re.lastIndex += 1;
|
||||
}
|
||||
|
||||
return text.substring(i).replace(/^\s/, empty);
|
||||
return text.substring(i).replace(/^\s/, '');
|
||||
}
|
||||
|
||||
// Escape codes
|
||||
@@ -636,7 +631,7 @@ export const removeAccents: (str: string) => string = (function () {
|
||||
// see: https://stackoverflow.com/questions/990904/remove-accents-diacritics-in-a-string-in-javascript/37511463#37511463
|
||||
const regex = /[\u0300-\u036f]/g;
|
||||
return function (str: string) {
|
||||
return (str as any).normalize('NFD').replace(regex, empty);
|
||||
return (str as any).normalize('NFD').replace(regex, '');
|
||||
};
|
||||
}
|
||||
})();
|
||||
|
||||
@@ -42,11 +42,11 @@ export interface IConfigOptions<T> {
|
||||
* - configurable defaults
|
||||
*/
|
||||
export class ConfigWatcher<T> extends Disposable implements IConfigWatcher<T> {
|
||||
private cache: T;
|
||||
private parseErrors: json.ParseError[];
|
||||
private disposed: boolean;
|
||||
private loaded: boolean;
|
||||
private timeoutHandle: NodeJS.Timer | null;
|
||||
private cache: T | undefined;
|
||||
private parseErrors: json.ParseError[] | undefined;
|
||||
private disposed: boolean | undefined;
|
||||
private loaded: boolean | undefined;
|
||||
private timeoutHandle: NodeJS.Timer | null | undefined;
|
||||
private readonly _onDidUpdateConfiguration: Emitter<IConfigurationChangeEvent<T>>;
|
||||
|
||||
constructor(private _path: string, private options: IConfigOptions<T> = { defaultConfig: Object.create(null), onError: error => console.error(error) }) {
|
||||
@@ -62,7 +62,7 @@ export class ConfigWatcher<T> extends Disposable implements IConfigWatcher<T> {
|
||||
}
|
||||
|
||||
get hasParseErrors(): boolean {
|
||||
return this.parseErrors && this.parseErrors.length > 0;
|
||||
return !!this.parseErrors && this.parseErrors.length > 0;
|
||||
}
|
||||
|
||||
get onDidUpdateConfiguration(): Event<IConfigurationChangeEvent<T>> {
|
||||
@@ -161,7 +161,7 @@ export class ConfigWatcher<T> extends Disposable implements IConfigWatcher<T> {
|
||||
if (!objects.equals(currentConfig, this.cache)) {
|
||||
this.updateCache(currentConfig);
|
||||
|
||||
this._onDidUpdateConfiguration.fire({ config: this.cache });
|
||||
this._onDidUpdateConfiguration.fire({ config: currentConfig });
|
||||
}
|
||||
|
||||
if (callback) {
|
||||
@@ -173,7 +173,7 @@ export class ConfigWatcher<T> extends Disposable implements IConfigWatcher<T> {
|
||||
getConfig(): T {
|
||||
this.ensureLoaded();
|
||||
|
||||
return this.cache;
|
||||
return this.cache!;
|
||||
}
|
||||
|
||||
private ensureLoaded(): void {
|
||||
@@ -186,4 +186,4 @@ export class ConfigWatcher<T> extends Disposable implements IConfigWatcher<T> {
|
||||
this.disposed = true;
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -268,10 +268,10 @@ function factory(nodeRequire, path, fs, perf) {
|
||||
const packData = JSON.parse(values[1]).contents;
|
||||
const bundles = Object.keys(metadata.bundles);
|
||||
const writes = [];
|
||||
for (let bundle of bundles) {
|
||||
for (const bundle of bundles) {
|
||||
const modules = metadata.bundles[bundle];
|
||||
const target = Object.create(null);
|
||||
for (let module of modules) {
|
||||
for (const module of modules) {
|
||||
const keys = metadata.keys[module];
|
||||
const defaultMessages = metadata.messages[module];
|
||||
const translations = packData[module];
|
||||
|
||||
@@ -258,15 +258,15 @@ export class QuickOpenWidget extends Disposable implements IModelProvider {
|
||||
filter: new Filter(this),
|
||||
accessibilityProvider: new AccessibilityProvider(this)
|
||||
}, {
|
||||
twistiePixels: 11,
|
||||
indentPixels: 0,
|
||||
alwaysFocused: true,
|
||||
verticalScrollMode: ScrollbarVisibility.Visible,
|
||||
horizontalScrollMode: ScrollbarVisibility.Hidden,
|
||||
ariaLabel: nls.localize('treeAriaLabel', "Quick Picker"),
|
||||
keyboardSupport: this.options.keyboardSupport,
|
||||
preventRootFocus: false
|
||||
}));
|
||||
twistiePixels: 11,
|
||||
indentPixels: 0,
|
||||
alwaysFocused: true,
|
||||
verticalScrollMode: ScrollbarVisibility.Visible,
|
||||
horizontalScrollMode: ScrollbarVisibility.Hidden,
|
||||
ariaLabel: nls.localize('treeAriaLabel', "Quick Picker"),
|
||||
keyboardSupport: this.options.keyboardSupport,
|
||||
preventRootFocus: false
|
||||
}));
|
||||
|
||||
this.treeElement = this.tree.getHTMLElement();
|
||||
|
||||
|
||||
@@ -420,8 +420,8 @@ export class SQLiteStorageDatabase implements IStorageDatabase {
|
||||
}
|
||||
|
||||
class SQLiteStorageDatabaseLogger {
|
||||
private readonly logTrace: (msg: string) => void;
|
||||
private readonly logError: (error: string | Error) => void;
|
||||
private readonly logTrace: ((msg: string) => void) | undefined;
|
||||
private readonly logError: ((error: string | Error) => void) | undefined;
|
||||
|
||||
constructor(options?: ISQLiteStorageDatabaseLoggingOptions) {
|
||||
if (options && typeof options.logTrace === 'function') {
|
||||
|
||||
@@ -110,15 +110,15 @@ export class Tree implements _.ITree {
|
||||
}
|
||||
|
||||
get onDidFocus(): Event<void> {
|
||||
return this.view && this.view.onDOMFocus;
|
||||
return this.view.onDOMFocus;
|
||||
}
|
||||
|
||||
get onDidBlur(): Event<void> {
|
||||
return this.view && this.view.onDOMBlur;
|
||||
return this.view.onDOMBlur;
|
||||
}
|
||||
|
||||
get onDidScroll(): Event<void> {
|
||||
return this.view && this.view.onDidScroll;
|
||||
return this.view.onDidScroll;
|
||||
}
|
||||
|
||||
public getHTMLElement(): HTMLElement {
|
||||
@@ -300,16 +300,8 @@ export class Tree implements _.ITree {
|
||||
|
||||
public dispose(): void {
|
||||
this._onDispose.fire();
|
||||
|
||||
if (this.model !== null) {
|
||||
this.model.dispose();
|
||||
this.model = null!; // StrictNullOverride Nulling out ok in dispose
|
||||
}
|
||||
if (this.view !== null) {
|
||||
this.view.dispose();
|
||||
this.view = null!; // StrictNullOverride Nulling out ok in dispose
|
||||
}
|
||||
|
||||
this.model.dispose();
|
||||
this.view.dispose();
|
||||
this._onDidChangeFocus.dispose();
|
||||
this._onDidChangeSelection.dispose();
|
||||
this._onHighlightChange.dispose();
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
import * as Assert from 'vs/base/common/assert';
|
||||
import { onUnexpectedError } from 'vs/base/common/errors';
|
||||
import { IDisposable, combinedDisposable } from 'vs/base/common/lifecycle';
|
||||
import { IDisposable, combinedDisposable, Disposable } from 'vs/base/common/lifecycle';
|
||||
import { INavigator } from 'vs/base/common/iterator';
|
||||
import * as _ from './tree';
|
||||
import { Event, Emitter, EventMultiplexer, Relay } from 'vs/base/common/event';
|
||||
@@ -192,7 +192,7 @@ export class ItemRegistry {
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
this.items = null!; // StrictNullOverride: nulling out ok in dispose
|
||||
this.items = {};
|
||||
|
||||
this._onDidRevealItem.dispose();
|
||||
this._onExpandItem.dispose();
|
||||
@@ -864,8 +864,8 @@ export class TreeModel {
|
||||
private context: _.ITreeContext;
|
||||
private lock!: Lock;
|
||||
private input: Item | null;
|
||||
private registry!: ItemRegistry;
|
||||
private registryDisposable!: IDisposable;
|
||||
private registry: ItemRegistry = new ItemRegistry();
|
||||
private registryDisposable: IDisposable = Disposable.None;
|
||||
private traitsToItems: ITraitMap;
|
||||
|
||||
private _onSetInput = new Emitter<IInputEvent>();
|
||||
@@ -1471,11 +1471,7 @@ export class TreeModel {
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
if (this.registry) {
|
||||
this.registry.dispose();
|
||||
this.registry = null!; // StrictNullOverride: nulling out ok in dispose
|
||||
}
|
||||
|
||||
this.registry.dispose();
|
||||
this._onSetInput.dispose();
|
||||
this._onDidSetInput.dispose();
|
||||
this._onRefresh.dispose();
|
||||
|
||||
@@ -927,9 +927,9 @@ export class TreeView extends HeightMap {
|
||||
getLength: () => previousChildrenIds.length,
|
||||
getElementAtIndex: (i: number) => previousChildrenIds[i]
|
||||
}, {
|
||||
getLength: () => afterModelItems.length,
|
||||
getElementAtIndex: (i: number) => afterModelItems[i].id
|
||||
},
|
||||
getLength: () => afterModelItems.length,
|
||||
getElementAtIndex: (i: number) => afterModelItems[i].id
|
||||
},
|
||||
null
|
||||
);
|
||||
|
||||
|
||||
@@ -14,10 +14,6 @@ suite('RangeMap', () => {
|
||||
rangeMap = new RangeMap();
|
||||
});
|
||||
|
||||
teardown(() => {
|
||||
rangeMap.dispose();
|
||||
});
|
||||
|
||||
test('intersection', () => {
|
||||
assert.deepEqual(Range.intersect({ start: 0, end: 0 }, { start: 0, end: 0 }), { start: 0, end: 0 });
|
||||
assert.deepEqual(Range.intersect({ start: 0, end: 0 }, { start: 5, end: 5 }), { start: 0, end: 0 });
|
||||
@@ -346,4 +342,4 @@ suite('RangeMap', () => {
|
||||
assert.equal(rangeMap.positionAt(4), -1);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -8,6 +8,7 @@ import { ITreeNode, ITreeRenderer, IAsyncDataSource } from 'vs/base/browser/ui/t
|
||||
import { AsyncDataTree } from 'vs/base/browser/ui/tree/asyncDataTree';
|
||||
import { IListVirtualDelegate, IIdentityProvider } from 'vs/base/browser/ui/list/list';
|
||||
import { hasClass } from 'vs/base/browser/dom';
|
||||
import { timeout } from 'vs/base/common/async';
|
||||
|
||||
interface Element {
|
||||
id: string;
|
||||
@@ -26,104 +27,89 @@ function find(elements: Element[] | undefined, id: string): Element {
|
||||
throw new Error('element not found');
|
||||
}
|
||||
|
||||
class Renderer implements ITreeRenderer<Element, void, HTMLElement> {
|
||||
readonly templateId = 'default';
|
||||
renderTemplate(container: HTMLElement): HTMLElement {
|
||||
return container;
|
||||
}
|
||||
renderElement(element: ITreeNode<Element, void>, index: number, templateData: HTMLElement): void {
|
||||
templateData.textContent = element.element.id;
|
||||
}
|
||||
disposeTemplate(templateData: HTMLElement): void {
|
||||
// noop
|
||||
}
|
||||
}
|
||||
|
||||
class IdentityProvider implements IIdentityProvider<Element> {
|
||||
getId(element: Element) {
|
||||
return element.id;
|
||||
}
|
||||
}
|
||||
|
||||
class VirtualDelegate implements IListVirtualDelegate<Element> {
|
||||
getHeight() { return 20; }
|
||||
getTemplateId(element: Element): string { return 'default'; }
|
||||
}
|
||||
|
||||
class DataSource implements IAsyncDataSource<Element, Element> {
|
||||
hasChildren(element: Element): boolean {
|
||||
return !!element.children && element.children.length > 0;
|
||||
}
|
||||
getChildren(element: Element): Promise<Element[]> {
|
||||
return Promise.resolve(element.children || []);
|
||||
}
|
||||
}
|
||||
|
||||
class Model {
|
||||
|
||||
constructor(readonly root: Element) { }
|
||||
|
||||
get(id: string): Element {
|
||||
return find(this.root.children, id);
|
||||
}
|
||||
}
|
||||
|
||||
suite('AsyncDataTree', function () {
|
||||
|
||||
test('Collapse state should be preserved across refresh calls', async () => {
|
||||
const container = document.createElement('div');
|
||||
container.style.width = '200px';
|
||||
container.style.height = '200px';
|
||||
|
||||
const delegate = new class implements IListVirtualDelegate<Element> {
|
||||
getHeight() { return 20; }
|
||||
getTemplateId(element: Element): string { return 'default'; }
|
||||
};
|
||||
|
||||
const renderer = new class implements ITreeRenderer<Element, void, HTMLElement> {
|
||||
readonly templateId = 'default';
|
||||
renderTemplate(container: HTMLElement): HTMLElement {
|
||||
return container;
|
||||
}
|
||||
renderElement(element: ITreeNode<Element, void>, index: number, templateData: HTMLElement): void {
|
||||
templateData.textContent = element.element.id;
|
||||
}
|
||||
disposeTemplate(templateData: HTMLElement): void {
|
||||
// noop
|
||||
}
|
||||
};
|
||||
|
||||
const dataSource = new class implements IAsyncDataSource<Element, Element> {
|
||||
hasChildren(element: Element): boolean {
|
||||
return !!element.children && element.children.length > 0;
|
||||
}
|
||||
getChildren(element: Element): Promise<Element[]> {
|
||||
return Promise.resolve(element.children || []);
|
||||
}
|
||||
};
|
||||
|
||||
const identityProvider = new class implements IIdentityProvider<Element> {
|
||||
getId(element: Element) {
|
||||
return element.id;
|
||||
}
|
||||
};
|
||||
|
||||
const root: Element = {
|
||||
const model = new Model({
|
||||
id: 'root',
|
||||
children: [{
|
||||
id: 'a'
|
||||
}]
|
||||
};
|
||||
});
|
||||
|
||||
const _: (id: string) => Element = find.bind(null, root.children);
|
||||
|
||||
const tree = new AsyncDataTree<Element, Element>(container, delegate, [renderer], dataSource, { identityProvider });
|
||||
const tree = new AsyncDataTree<Element, Element>('test', container, new VirtualDelegate(), [new Renderer()], new DataSource(), { identityProvider: new IdentityProvider() });
|
||||
tree.layout(200);
|
||||
assert.equal(container.querySelectorAll('.monaco-list-row').length, 0);
|
||||
|
||||
await tree.setInput(root);
|
||||
await tree.setInput(model.root);
|
||||
assert.equal(container.querySelectorAll('.monaco-list-row').length, 1);
|
||||
let twistie = container.querySelector('.monaco-list-row:first-child .monaco-tl-twistie') as HTMLElement;
|
||||
assert(!hasClass(twistie, 'collapsible'));
|
||||
assert(!hasClass(twistie, 'collapsed'));
|
||||
|
||||
_('a').children = [
|
||||
model.get('a').children = [
|
||||
{ id: 'aa' },
|
||||
{ id: 'ab' },
|
||||
{ id: 'ac' }
|
||||
];
|
||||
|
||||
await tree.updateChildren(root);
|
||||
await tree.updateChildren(model.root);
|
||||
assert.equal(container.querySelectorAll('.monaco-list-row').length, 1);
|
||||
|
||||
await tree.expand(_('a'));
|
||||
await tree.expand(model.get('a'));
|
||||
assert.equal(container.querySelectorAll('.monaco-list-row').length, 4);
|
||||
|
||||
_('a').children = [];
|
||||
await tree.updateChildren(root);
|
||||
model.get('a').children = [];
|
||||
await tree.updateChildren(model.root);
|
||||
assert.equal(container.querySelectorAll('.monaco-list-row').length, 1);
|
||||
});
|
||||
|
||||
test('issue #68648', async () => {
|
||||
const container = document.createElement('div');
|
||||
container.style.width = '200px';
|
||||
container.style.height = '200px';
|
||||
|
||||
const delegate = new class implements IListVirtualDelegate<Element> {
|
||||
getHeight() { return 20; }
|
||||
getTemplateId(element: Element): string { return 'default'; }
|
||||
};
|
||||
|
||||
const renderer = new class implements ITreeRenderer<Element, void, HTMLElement> {
|
||||
readonly templateId = 'default';
|
||||
renderTemplate(container: HTMLElement): HTMLElement {
|
||||
return container;
|
||||
}
|
||||
renderElement(element: ITreeNode<Element, void>, index: number, templateData: HTMLElement): void {
|
||||
templateData.textContent = element.element.id;
|
||||
}
|
||||
disposeTemplate(templateData: HTMLElement): void {
|
||||
// noop
|
||||
}
|
||||
};
|
||||
|
||||
const getChildrenCalls: string[] = [];
|
||||
const dataSource = new class implements IAsyncDataSource<Element, Element> {
|
||||
@@ -136,25 +122,17 @@ suite('AsyncDataTree', function () {
|
||||
}
|
||||
};
|
||||
|
||||
const identityProvider = new class implements IIdentityProvider<Element> {
|
||||
getId(element: Element) {
|
||||
return element.id;
|
||||
}
|
||||
};
|
||||
|
||||
const root: Element = {
|
||||
const model = new Model({
|
||||
id: 'root',
|
||||
children: [{
|
||||
id: 'a'
|
||||
}]
|
||||
};
|
||||
});
|
||||
|
||||
const _: (id: string) => Element = find.bind(null, root.children);
|
||||
|
||||
const tree = new AsyncDataTree<Element, Element>(container, delegate, [renderer], dataSource, { identityProvider });
|
||||
const tree = new AsyncDataTree<Element, Element>('test', container, new VirtualDelegate(), [new Renderer()], dataSource, { identityProvider: new IdentityProvider() });
|
||||
tree.layout(200);
|
||||
|
||||
await tree.setInput(root);
|
||||
await tree.setInput(model.root);
|
||||
assert.deepStrictEqual(getChildrenCalls, ['root']);
|
||||
|
||||
let twistie = container.querySelector('.monaco-list-row:first-child .monaco-tl-twistie') as HTMLElement;
|
||||
@@ -162,8 +140,8 @@ suite('AsyncDataTree', function () {
|
||||
assert(!hasClass(twistie, 'collapsed'));
|
||||
assert(tree.getNode().children[0].collapsed);
|
||||
|
||||
_('a').children = [{ id: 'aa' }, { id: 'ab' }, { id: 'ac' }];
|
||||
await tree.updateChildren(root);
|
||||
model.get('a').children = [{ id: 'aa' }, { id: 'ab' }, { id: 'ac' }];
|
||||
await tree.updateChildren(model.root);
|
||||
|
||||
assert.deepStrictEqual(getChildrenCalls, ['root', 'root']);
|
||||
twistie = container.querySelector('.monaco-list-row:first-child .monaco-tl-twistie') as HTMLElement;
|
||||
@@ -171,8 +149,8 @@ suite('AsyncDataTree', function () {
|
||||
assert(hasClass(twistie, 'collapsed'));
|
||||
assert(tree.getNode().children[0].collapsed);
|
||||
|
||||
_('a').children = [];
|
||||
await tree.updateChildren(root);
|
||||
model.get('a').children = [];
|
||||
await tree.updateChildren(model.root);
|
||||
|
||||
assert.deepStrictEqual(getChildrenCalls, ['root', 'root', 'root']);
|
||||
twistie = container.querySelector('.monaco-list-row:first-child .monaco-tl-twistie') as HTMLElement;
|
||||
@@ -180,8 +158,8 @@ suite('AsyncDataTree', function () {
|
||||
assert(!hasClass(twistie, 'collapsed'));
|
||||
assert(tree.getNode().children[0].collapsed);
|
||||
|
||||
_('a').children = [{ id: 'aa' }, { id: 'ab' }, { id: 'ac' }];
|
||||
await tree.updateChildren(root);
|
||||
model.get('a').children = [{ id: 'aa' }, { id: 'ab' }, { id: 'ac' }];
|
||||
await tree.updateChildren(model.root);
|
||||
|
||||
assert.deepStrictEqual(getChildrenCalls, ['root', 'root', 'root', 'root']);
|
||||
twistie = container.querySelector('.monaco-list-row:first-child .monaco-tl-twistie') as HTMLElement;
|
||||
@@ -192,26 +170,6 @@ suite('AsyncDataTree', function () {
|
||||
|
||||
test('issue #67722 - once resolved, refreshed collapsed nodes should only get children when expanded', async () => {
|
||||
const container = document.createElement('div');
|
||||
container.style.width = '200px';
|
||||
container.style.height = '200px';
|
||||
|
||||
const delegate = new class implements IListVirtualDelegate<Element> {
|
||||
getHeight() { return 20; }
|
||||
getTemplateId(element: Element): string { return 'default'; }
|
||||
};
|
||||
|
||||
const renderer = new class implements ITreeRenderer<Element, void, HTMLElement> {
|
||||
readonly templateId = 'default';
|
||||
renderTemplate(container: HTMLElement): HTMLElement {
|
||||
return container;
|
||||
}
|
||||
renderElement(element: ITreeNode<Element, void>, index: number, templateData: HTMLElement): void {
|
||||
templateData.textContent = element.element.id;
|
||||
}
|
||||
disposeTemplate(templateData: HTMLElement): void {
|
||||
// noop
|
||||
}
|
||||
};
|
||||
|
||||
const getChildrenCalls: string[] = [];
|
||||
const dataSource = new class implements IAsyncDataSource<Element, Element> {
|
||||
@@ -224,131 +182,66 @@ suite('AsyncDataTree', function () {
|
||||
}
|
||||
};
|
||||
|
||||
const identityProvider = new class implements IIdentityProvider<Element> {
|
||||
getId(element: Element) {
|
||||
return element.id;
|
||||
}
|
||||
};
|
||||
|
||||
const root: Element = {
|
||||
const model = new Model({
|
||||
id: 'root',
|
||||
children: [{
|
||||
id: 'a', children: [{ id: 'aa' }, { id: 'ab' }, { id: 'ac' }]
|
||||
}]
|
||||
};
|
||||
});
|
||||
|
||||
const _: (id: string) => Element = find.bind(null, root.children);
|
||||
|
||||
const tree = new AsyncDataTree<Element, Element>(container, delegate, [renderer], dataSource, { identityProvider });
|
||||
const tree = new AsyncDataTree<Element, Element>('test', container, new VirtualDelegate(), [new Renderer()], dataSource, { identityProvider: new IdentityProvider() });
|
||||
tree.layout(200);
|
||||
|
||||
await tree.setInput(root);
|
||||
assert(tree.getNode(_('a')).collapsed);
|
||||
await tree.setInput(model.root);
|
||||
assert(tree.getNode(model.get('a')).collapsed);
|
||||
assert.deepStrictEqual(getChildrenCalls, ['root']);
|
||||
|
||||
await tree.expand(_('a'));
|
||||
assert(!tree.getNode(_('a')).collapsed);
|
||||
await tree.expand(model.get('a'));
|
||||
assert(!tree.getNode(model.get('a')).collapsed);
|
||||
assert.deepStrictEqual(getChildrenCalls, ['root', 'a']);
|
||||
|
||||
tree.collapse(_('a'));
|
||||
assert(tree.getNode(_('a')).collapsed);
|
||||
tree.collapse(model.get('a'));
|
||||
assert(tree.getNode(model.get('a')).collapsed);
|
||||
assert.deepStrictEqual(getChildrenCalls, ['root', 'a']);
|
||||
|
||||
await tree.updateChildren();
|
||||
assert(tree.getNode(_('a')).collapsed);
|
||||
assert(tree.getNode(model.get('a')).collapsed);
|
||||
assert.deepStrictEqual(getChildrenCalls, ['root', 'a', 'root'], 'a should not be refreshed, since it\' collapsed');
|
||||
});
|
||||
|
||||
test('resolved collapsed nodes which lose children should lose twistie as well', async () => {
|
||||
const container = document.createElement('div');
|
||||
container.style.width = '200px';
|
||||
container.style.height = '200px';
|
||||
|
||||
const delegate = new class implements IListVirtualDelegate<Element> {
|
||||
getHeight() { return 20; }
|
||||
getTemplateId(element: Element): string { return 'default'; }
|
||||
};
|
||||
|
||||
const renderer = new class implements ITreeRenderer<Element, void, HTMLElement> {
|
||||
readonly templateId = 'default';
|
||||
renderTemplate(container: HTMLElement): HTMLElement {
|
||||
return container;
|
||||
}
|
||||
renderElement(element: ITreeNode<Element, void>, index: number, templateData: HTMLElement): void {
|
||||
templateData.textContent = element.element.id;
|
||||
}
|
||||
disposeTemplate(templateData: HTMLElement): void {
|
||||
// noop
|
||||
}
|
||||
};
|
||||
|
||||
const dataSource = new class implements IAsyncDataSource<Element, Element> {
|
||||
hasChildren(element: Element): boolean {
|
||||
return !!element.children && element.children.length > 0;
|
||||
}
|
||||
getChildren(element: Element): Promise<Element[]> {
|
||||
return Promise.resolve(element.children || []);
|
||||
}
|
||||
};
|
||||
|
||||
const identityProvider = new class implements IIdentityProvider<Element> {
|
||||
getId(element: Element) {
|
||||
return element.id;
|
||||
}
|
||||
};
|
||||
|
||||
const root: Element = {
|
||||
const model = new Model({
|
||||
id: 'root',
|
||||
children: [{
|
||||
id: 'a', children: [{ id: 'aa' }, { id: 'ab' }, { id: 'ac' }]
|
||||
}]
|
||||
};
|
||||
});
|
||||
|
||||
const _: (id: string) => Element = find.bind(null, root.children);
|
||||
|
||||
const tree = new AsyncDataTree<Element, Element>(container, delegate, [renderer], dataSource, { identityProvider });
|
||||
const tree = new AsyncDataTree<Element, Element>('test', container, new VirtualDelegate(), [new Renderer()], new DataSource(), { identityProvider: new IdentityProvider() });
|
||||
tree.layout(200);
|
||||
|
||||
await tree.setInput(root);
|
||||
await tree.expand(_('a'));
|
||||
await tree.setInput(model.root);
|
||||
await tree.expand(model.get('a'));
|
||||
|
||||
let twistie = container.querySelector('.monaco-list-row:first-child .monaco-tl-twistie') as HTMLElement;
|
||||
assert(hasClass(twistie, 'collapsible'));
|
||||
assert(!hasClass(twistie, 'collapsed'));
|
||||
assert(!tree.getNode(_('a')).collapsed);
|
||||
assert(!tree.getNode(model.get('a')).collapsed);
|
||||
|
||||
tree.collapse(_('a'));
|
||||
_('a').children = [];
|
||||
await tree.updateChildren(root);
|
||||
tree.collapse(model.get('a'));
|
||||
model.get('a').children = [];
|
||||
await tree.updateChildren(model.root);
|
||||
|
||||
twistie = container.querySelector('.monaco-list-row:first-child .monaco-tl-twistie') as HTMLElement;
|
||||
assert(!hasClass(twistie, 'collapsible'));
|
||||
assert(!hasClass(twistie, 'collapsed'));
|
||||
assert(tree.getNode(_('a')).collapsed);
|
||||
assert(tree.getNode(model.get('a')).collapsed);
|
||||
});
|
||||
|
||||
test('support default collapse state per element', async () => {
|
||||
const container = document.createElement('div');
|
||||
container.style.width = '200px';
|
||||
container.style.height = '200px';
|
||||
|
||||
const delegate = new class implements IListVirtualDelegate<Element> {
|
||||
getHeight() { return 20; }
|
||||
getTemplateId(element: Element): string { return 'default'; }
|
||||
};
|
||||
|
||||
const renderer = new class implements ITreeRenderer<Element, void, HTMLElement> {
|
||||
readonly templateId = 'default';
|
||||
renderTemplate(container: HTMLElement): HTMLElement {
|
||||
return container;
|
||||
}
|
||||
renderElement(element: ITreeNode<Element, void>, index: number, templateData: HTMLElement): void {
|
||||
templateData.textContent = element.element.id;
|
||||
}
|
||||
disposeTemplate(templateData: HTMLElement): void {
|
||||
// noop
|
||||
}
|
||||
};
|
||||
|
||||
const getChildrenCalls: string[] = [];
|
||||
const dataSource = new class implements IAsyncDataSource<Element, Element> {
|
||||
@@ -361,22 +254,139 @@ suite('AsyncDataTree', function () {
|
||||
}
|
||||
};
|
||||
|
||||
const root: Element = {
|
||||
const model = new Model({
|
||||
id: 'root',
|
||||
children: [{
|
||||
id: 'a', children: [{ id: 'aa' }, { id: 'ab' }, { id: 'ac' }]
|
||||
}]
|
||||
};
|
||||
});
|
||||
|
||||
const _: (id: string) => Element = find.bind(null, root.children);
|
||||
|
||||
const tree = new AsyncDataTree<Element, Element>(container, delegate, [renderer], dataSource, {
|
||||
const tree = new AsyncDataTree<Element, Element>('test', container, new VirtualDelegate(), [new Renderer()], dataSource, {
|
||||
collapseByDefault: el => el.id !== 'a'
|
||||
});
|
||||
tree.layout(200);
|
||||
|
||||
await tree.setInput(root);
|
||||
assert(!tree.getNode(_('a')).collapsed);
|
||||
await tree.setInput(model.root);
|
||||
assert(!tree.getNode(model.get('a')).collapsed);
|
||||
assert.deepStrictEqual(getChildrenCalls, ['root', 'a']);
|
||||
});
|
||||
});
|
||||
|
||||
test('issue #80098 - concurrent refresh and expand', async () => {
|
||||
const container = document.createElement('div');
|
||||
|
||||
const calls: Function[] = [];
|
||||
const dataSource = new class implements IAsyncDataSource<Element, Element> {
|
||||
hasChildren(element: Element): boolean {
|
||||
return !!element.children && element.children.length > 0;
|
||||
}
|
||||
getChildren(element: Element): Promise<Element[]> {
|
||||
return new Promise(c => calls.push(() => c(element.children)));
|
||||
}
|
||||
};
|
||||
|
||||
const model = new Model({
|
||||
id: 'root',
|
||||
children: [{
|
||||
id: 'a', children: [{
|
||||
id: 'aa'
|
||||
}]
|
||||
}]
|
||||
});
|
||||
|
||||
const tree = new AsyncDataTree<Element, Element>('test', container, new VirtualDelegate(), [new Renderer()], dataSource, { identityProvider: new IdentityProvider() });
|
||||
tree.layout(200);
|
||||
|
||||
const pSetInput = tree.setInput(model.root);
|
||||
calls.pop()!(); // resolve getChildren(root)
|
||||
await pSetInput;
|
||||
|
||||
const pUpdateChildrenA = tree.updateChildren(model.get('a'));
|
||||
const pExpandA = tree.expand(model.get('a'));
|
||||
assert.equal(calls.length, 1, 'expand(a) still hasn\'t called getChildren(a)');
|
||||
|
||||
calls.pop()!();
|
||||
assert.equal(calls.length, 0, 'no pending getChildren calls');
|
||||
|
||||
await pUpdateChildrenA;
|
||||
assert.equal(calls.length, 0, 'expand(a) should not have forced a second refresh');
|
||||
|
||||
const result = await pExpandA;
|
||||
assert.equal(result, true, 'expand(a) should be done');
|
||||
});
|
||||
|
||||
test('issue #80098 - first expand should call getChildren', async () => {
|
||||
const container = document.createElement('div');
|
||||
|
||||
const calls: Function[] = [];
|
||||
const dataSource = new class implements IAsyncDataSource<Element, Element> {
|
||||
hasChildren(element: Element): boolean {
|
||||
return !!element.children && element.children.length > 0;
|
||||
}
|
||||
getChildren(element: Element): Promise<Element[]> {
|
||||
return new Promise(c => calls.push(() => c(element.children)));
|
||||
}
|
||||
};
|
||||
|
||||
const model = new Model({
|
||||
id: 'root',
|
||||
children: [{
|
||||
id: 'a', children: [{
|
||||
id: 'aa'
|
||||
}]
|
||||
}]
|
||||
});
|
||||
|
||||
const tree = new AsyncDataTree<Element, Element>('test', container, new VirtualDelegate(), [new Renderer()], dataSource, { identityProvider: new IdentityProvider() });
|
||||
tree.layout(200);
|
||||
|
||||
const pSetInput = tree.setInput(model.root);
|
||||
calls.pop()!(); // resolve getChildren(root)
|
||||
await pSetInput;
|
||||
|
||||
const pExpandA = tree.expand(model.get('a'));
|
||||
assert.equal(calls.length, 1, 'expand(a) should\'ve called getChildren(a)');
|
||||
|
||||
let race = await Promise.race([pExpandA.then(() => 'expand'), timeout(1).then(() => 'timeout')]);
|
||||
assert.equal(race, 'timeout', 'expand(a) should not be yet done');
|
||||
|
||||
calls.pop()!();
|
||||
assert.equal(calls.length, 0, 'no pending getChildren calls');
|
||||
|
||||
race = await Promise.race([pExpandA.then(() => 'expand'), timeout(1).then(() => 'timeout')]);
|
||||
assert.equal(race, 'expand', 'expand(a) should now be done');
|
||||
});
|
||||
|
||||
test('issue #78388 - tree should react to hasChildren toggles', async () => {
|
||||
const container = document.createElement('div');
|
||||
const model = new Model({
|
||||
id: 'root',
|
||||
children: [{
|
||||
id: 'a'
|
||||
}]
|
||||
});
|
||||
|
||||
const tree = new AsyncDataTree<Element, Element>('test', container, new VirtualDelegate(), [new Renderer()], new DataSource(), { identityProvider: new IdentityProvider() });
|
||||
tree.layout(200);
|
||||
|
||||
await tree.setInput(model.root);
|
||||
assert.equal(container.querySelectorAll('.monaco-list-row').length, 1);
|
||||
|
||||
let twistie = container.querySelector('.monaco-list-row:first-child .monaco-tl-twistie') as HTMLElement;
|
||||
assert(!hasClass(twistie, 'collapsible'));
|
||||
assert(!hasClass(twistie, 'collapsed'));
|
||||
|
||||
model.get('a').children = [{ id: 'aa' }];
|
||||
await tree.updateChildren(model.get('a'), false);
|
||||
assert.equal(container.querySelectorAll('.monaco-list-row').length, 1);
|
||||
twistie = container.querySelector('.monaco-list-row:first-child .monaco-tl-twistie') as HTMLElement;
|
||||
assert(hasClass(twistie, 'collapsible'));
|
||||
assert(hasClass(twistie, 'collapsed'));
|
||||
|
||||
model.get('a').children = [];
|
||||
await tree.updateChildren(model.get('a'), false);
|
||||
assert.equal(container.querySelectorAll('.monaco-list-row').length, 1);
|
||||
twistie = container.querySelector('.monaco-list-row:first-child .monaco-tl-twistie') as HTMLElement;
|
||||
assert(!hasClass(twistie, 'collapsible'));
|
||||
assert(!hasClass(twistie, 'collapsed'));
|
||||
});
|
||||
});
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as assert from 'assert';
|
||||
import { compress, ICompressedTreeElement, ICompressedTreeNode, decompress, CompressedTreeModel } from 'vs/base/browser/ui/tree/compressedObjectTreeModel';
|
||||
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';
|
||||
@@ -305,7 +305,7 @@ suite('CompressedObjectTree', function () {
|
||||
|
||||
test('ctor', () => {
|
||||
const list: ITreeNode<ICompressedTreeNode<number>>[] = [];
|
||||
const model = new CompressedTreeModel<number>(toSpliceable(list));
|
||||
const model = new CompressedObjectTreeModel<number>('test', toSpliceable(list));
|
||||
assert(model);
|
||||
assert.equal(list.length, 0);
|
||||
assert.equal(model.size, 0);
|
||||
@@ -313,7 +313,7 @@ suite('CompressedObjectTree', function () {
|
||||
|
||||
test('flat', () => {
|
||||
const list: ITreeNode<ICompressedTreeNode<number>>[] = [];
|
||||
const model = new CompressedTreeModel<number>(toSpliceable(list));
|
||||
const model = new CompressedObjectTreeModel<number>('test', toSpliceable(list));
|
||||
|
||||
model.setChildren(null, Iterator.fromArray([
|
||||
{ element: 0 },
|
||||
@@ -340,7 +340,7 @@ suite('CompressedObjectTree', function () {
|
||||
|
||||
test('nested', () => {
|
||||
const list: ITreeNode<ICompressedTreeNode<number>>[] = [];
|
||||
const model = new CompressedTreeModel<number>(toSpliceable(list));
|
||||
const model = new CompressedObjectTreeModel<number>('test', toSpliceable(list));
|
||||
|
||||
model.setChildren(null, Iterator.fromArray([
|
||||
{
|
||||
@@ -376,7 +376,7 @@ suite('CompressedObjectTree', function () {
|
||||
|
||||
test('compressed', () => {
|
||||
const list: ITreeNode<ICompressedTreeNode<number>>[] = [];
|
||||
const model = new CompressedTreeModel<number>(toSpliceable(list));
|
||||
const model = new CompressedObjectTreeModel<number>('test', toSpliceable(list));
|
||||
|
||||
model.setChildren(null, Iterator.fromArray([
|
||||
{
|
||||
@@ -427,4 +427,4 @@ suite('CompressedObjectTree', function () {
|
||||
assert.equal(model.size, 8);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -63,7 +63,7 @@ suite('DataTree', function () {
|
||||
}
|
||||
};
|
||||
|
||||
tree = new DataTree<E, E>(container, delegate, [renderer], dataSource, {
|
||||
tree = new DataTree<E, E>('test', container, delegate, [renderer], dataSource, {
|
||||
identityProvider
|
||||
});
|
||||
tree.layout(200);
|
||||
@@ -145,4 +145,4 @@ suite('DataTree', function () {
|
||||
assert.deepEqual(tree.getSelection(), [root.children![1]]);
|
||||
assert.deepEqual(tree.getFocus(), [root.children![2]]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -7,7 +7,7 @@ import * as assert from 'assert';
|
||||
import { ITreeNode, ITreeFilter, TreeVisibility } from 'vs/base/browser/ui/tree/tree';
|
||||
import { ISpliceable } from 'vs/base/common/sequence';
|
||||
import { Iterator } from 'vs/base/common/iterator';
|
||||
import { IndexTreeModel } from 'vs/base/browser/ui/tree/indexTreeModel';
|
||||
import { IndexTreeModel, IIndexTreeNode } from 'vs/base/browser/ui/tree/indexTreeModel';
|
||||
|
||||
function toSpliceable<T>(arr: T[]): ISpliceable<T> {
|
||||
return {
|
||||
@@ -25,14 +25,14 @@ suite('IndexTreeModel', function () {
|
||||
|
||||
test('ctor', () => {
|
||||
const list: ITreeNode<number>[] = [];
|
||||
const model = new IndexTreeModel<number>(toSpliceable(list), -1);
|
||||
const model = new IndexTreeModel<number>('test', toSpliceable(list), -1);
|
||||
assert(model);
|
||||
assert.equal(list.length, 0);
|
||||
});
|
||||
|
||||
test('insert', () => {
|
||||
const list: ITreeNode<number>[] = [];
|
||||
const model = new IndexTreeModel<number>(toSpliceable(list), -1);
|
||||
const model = new IndexTreeModel<number>('test', toSpliceable(list), -1);
|
||||
|
||||
model.splice([0], 0, Iterator.fromArray([
|
||||
{ element: 0 },
|
||||
@@ -54,7 +54,7 @@ suite('IndexTreeModel', function () {
|
||||
|
||||
test('deep insert', function () {
|
||||
const list: ITreeNode<number>[] = [];
|
||||
const model = new IndexTreeModel<number>(toSpliceable(list), -1);
|
||||
const model = new IndexTreeModel<number>('test', toSpliceable(list), -1);
|
||||
|
||||
model.splice([0], 0, Iterator.fromArray([
|
||||
{
|
||||
@@ -91,7 +91,7 @@ suite('IndexTreeModel', function () {
|
||||
|
||||
test('deep insert collapsed', function () {
|
||||
const list: ITreeNode<number>[] = [];
|
||||
const model = new IndexTreeModel<number>(toSpliceable(list), -1);
|
||||
const model = new IndexTreeModel<number>('test', toSpliceable(list), -1);
|
||||
|
||||
model.splice([0], 0, Iterator.fromArray([
|
||||
{
|
||||
@@ -119,7 +119,7 @@ suite('IndexTreeModel', function () {
|
||||
|
||||
test('delete', () => {
|
||||
const list: ITreeNode<number>[] = [];
|
||||
const model = new IndexTreeModel<number>(toSpliceable(list), -1);
|
||||
const model = new IndexTreeModel<number>('test', toSpliceable(list), -1);
|
||||
|
||||
model.splice([0], 0, Iterator.fromArray([
|
||||
{ element: 0 },
|
||||
@@ -144,7 +144,7 @@ suite('IndexTreeModel', function () {
|
||||
|
||||
test('nested delete', function () {
|
||||
const list: ITreeNode<number>[] = [];
|
||||
const model = new IndexTreeModel<number>(toSpliceable(list), -1);
|
||||
const model = new IndexTreeModel<number>('test', toSpliceable(list), -1);
|
||||
|
||||
model.splice([0], 0, Iterator.fromArray([
|
||||
{
|
||||
@@ -178,7 +178,7 @@ suite('IndexTreeModel', function () {
|
||||
|
||||
test('deep delete', function () {
|
||||
const list: ITreeNode<number>[] = [];
|
||||
const model = new IndexTreeModel<number>(toSpliceable(list), -1);
|
||||
const model = new IndexTreeModel<number>('test', toSpliceable(list), -1);
|
||||
|
||||
model.splice([0], 0, Iterator.fromArray([
|
||||
{
|
||||
@@ -206,7 +206,7 @@ suite('IndexTreeModel', function () {
|
||||
|
||||
test('hidden delete', function () {
|
||||
const list: ITreeNode<number>[] = [];
|
||||
const model = new IndexTreeModel<number>(toSpliceable(list), -1);
|
||||
const model = new IndexTreeModel<number>('test', toSpliceable(list), -1);
|
||||
|
||||
model.splice([0], 0, Iterator.fromArray([
|
||||
{
|
||||
@@ -231,7 +231,7 @@ suite('IndexTreeModel', function () {
|
||||
|
||||
test('collapse', () => {
|
||||
const list: ITreeNode<number>[] = [];
|
||||
const model = new IndexTreeModel<number>(toSpliceable(list), -1);
|
||||
const model = new IndexTreeModel<number>('test', toSpliceable(list), -1);
|
||||
|
||||
model.splice([0], 0, Iterator.fromArray([
|
||||
{
|
||||
@@ -262,7 +262,7 @@ suite('IndexTreeModel', function () {
|
||||
|
||||
test('expand', () => {
|
||||
const list: ITreeNode<number>[] = [];
|
||||
const model = new IndexTreeModel<number>(toSpliceable(list), -1);
|
||||
const model = new IndexTreeModel<number>('test', toSpliceable(list), -1);
|
||||
|
||||
model.splice([0], 0, Iterator.fromArray([
|
||||
{
|
||||
@@ -302,7 +302,7 @@ suite('IndexTreeModel', function () {
|
||||
|
||||
test('collapse should recursively adjust visible count', function () {
|
||||
const list: ITreeNode<number>[] = [];
|
||||
const model = new IndexTreeModel<number>(toSpliceable(list), -1);
|
||||
const model = new IndexTreeModel<number>('test', toSpliceable(list), -1);
|
||||
|
||||
model.splice([0], 0, Iterator.fromArray([
|
||||
{
|
||||
@@ -333,6 +333,67 @@ suite('IndexTreeModel', function () {
|
||||
assert.deepEqual(toArray(list), [1, 11, 2]);
|
||||
});
|
||||
|
||||
test('setCollapsible', () => {
|
||||
const list: ITreeNode<number>[] = [];
|
||||
const model = new IndexTreeModel<number>('test', toSpliceable(list), -1);
|
||||
|
||||
model.splice([0], 0, Iterator.fromArray([
|
||||
{
|
||||
element: 0, children: Iterator.fromArray([
|
||||
{ element: 10 }
|
||||
])
|
||||
}
|
||||
]));
|
||||
|
||||
assert.deepEqual(list.length, 2);
|
||||
|
||||
model.setCollapsible([0], false);
|
||||
assert.deepEqual(list.length, 2);
|
||||
assert.deepEqual(list[0].element, 0);
|
||||
assert.deepEqual(list[0].collapsible, false);
|
||||
assert.deepEqual(list[0].collapsed, false);
|
||||
assert.deepEqual(list[1].element, 10);
|
||||
assert.deepEqual(list[1].collapsible, false);
|
||||
assert.deepEqual(list[1].collapsed, false);
|
||||
|
||||
model.setCollapsed([0], true);
|
||||
assert.deepEqual(list.length, 1);
|
||||
assert.deepEqual(list[0].element, 0);
|
||||
assert.deepEqual(list[0].collapsible, false);
|
||||
assert.deepEqual(list[0].collapsed, true);
|
||||
|
||||
model.setCollapsed([0], false);
|
||||
assert.deepEqual(list[0].element, 0);
|
||||
assert.deepEqual(list[0].collapsible, false);
|
||||
assert.deepEqual(list[0].collapsed, false);
|
||||
assert.deepEqual(list[1].element, 10);
|
||||
assert.deepEqual(list[1].collapsible, false);
|
||||
assert.deepEqual(list[1].collapsed, false);
|
||||
|
||||
model.setCollapsible([0], true);
|
||||
assert.deepEqual(list.length, 2);
|
||||
assert.deepEqual(list[0].element, 0);
|
||||
assert.deepEqual(list[0].collapsible, true);
|
||||
assert.deepEqual(list[0].collapsed, false);
|
||||
assert.deepEqual(list[1].element, 10);
|
||||
assert.deepEqual(list[1].collapsible, false);
|
||||
assert.deepEqual(list[1].collapsed, false);
|
||||
|
||||
model.setCollapsed([0], true);
|
||||
assert.deepEqual(list.length, 1);
|
||||
assert.deepEqual(list[0].element, 0);
|
||||
assert.deepEqual(list[0].collapsible, true);
|
||||
assert.deepEqual(list[0].collapsed, true);
|
||||
|
||||
model.setCollapsed([0], false);
|
||||
assert.deepEqual(list[0].element, 0);
|
||||
assert.deepEqual(list[0].collapsible, true);
|
||||
assert.deepEqual(list[0].collapsed, false);
|
||||
assert.deepEqual(list[1].element, 10);
|
||||
assert.deepEqual(list[1].collapsible, false);
|
||||
assert.deepEqual(list[1].collapsed, false);
|
||||
});
|
||||
|
||||
test('simple filter', function () {
|
||||
const list: ITreeNode<number>[] = [];
|
||||
const filter = new class implements ITreeFilter<number> {
|
||||
@@ -341,7 +402,7 @@ suite('IndexTreeModel', function () {
|
||||
}
|
||||
};
|
||||
|
||||
const model = new IndexTreeModel<number>(toSpliceable(list), -1, { filter });
|
||||
const model = new IndexTreeModel<number>('test', toSpliceable(list), -1, { filter });
|
||||
|
||||
model.splice([0], 0, Iterator.fromArray([
|
||||
{
|
||||
@@ -375,7 +436,7 @@ suite('IndexTreeModel', function () {
|
||||
}
|
||||
};
|
||||
|
||||
const model = new IndexTreeModel<number>(toSpliceable(list), -1, { filter });
|
||||
const model = new IndexTreeModel<number>('test', toSpliceable(list), -1, { filter });
|
||||
|
||||
model.splice([0], 0, Iterator.fromArray([
|
||||
{
|
||||
@@ -398,7 +459,7 @@ suite('IndexTreeModel', function () {
|
||||
}
|
||||
};
|
||||
|
||||
const model = new IndexTreeModel<number>(toSpliceable(list), -1, { filter });
|
||||
const model = new IndexTreeModel<number>('test', toSpliceable(list), -1, { filter });
|
||||
|
||||
model.splice([0], 0, Iterator.fromArray([
|
||||
{
|
||||
@@ -437,7 +498,7 @@ suite('IndexTreeModel', function () {
|
||||
}
|
||||
};
|
||||
|
||||
const model = new IndexTreeModel<string>(toSpliceable(list), 'root', { filter });
|
||||
const model = new IndexTreeModel<string>('test', toSpliceable(list), 'root', { filter });
|
||||
|
||||
model.splice([0], 0, Iterator.fromArray([
|
||||
{
|
||||
@@ -483,7 +544,7 @@ suite('IndexTreeModel', function () {
|
||||
}
|
||||
};
|
||||
|
||||
const model = new IndexTreeModel<string>(toSpliceable(list), 'root', { filter });
|
||||
const model = new IndexTreeModel<string>('test', toSpliceable(list), 'root', { filter });
|
||||
|
||||
model.splice([0], 0, Iterator.fromArray([
|
||||
{
|
||||
@@ -529,7 +590,7 @@ suite('IndexTreeModel', function () {
|
||||
}
|
||||
};
|
||||
|
||||
const model = new IndexTreeModel<string>(toSpliceable(list), 'root', { filter });
|
||||
const model = new IndexTreeModel<string>('test', toSpliceable(list), 'root', { filter });
|
||||
|
||||
model.splice([0], 0, Iterator.fromArray([
|
||||
{
|
||||
@@ -576,8 +637,8 @@ suite('IndexTreeModel', function () {
|
||||
suite('getNodeLocation', function () {
|
||||
|
||||
test('simple', function () {
|
||||
const list: ITreeNode<number>[] = [];
|
||||
const model = new IndexTreeModel<number>(toSpliceable(list), -1);
|
||||
const list: IIndexTreeNode<number>[] = [];
|
||||
const model = new IndexTreeModel<number>('test', toSpliceable(list), -1);
|
||||
|
||||
model.splice([0], 0, Iterator.fromArray([
|
||||
{
|
||||
@@ -600,14 +661,14 @@ suite('IndexTreeModel', function () {
|
||||
});
|
||||
|
||||
test('with filter', function () {
|
||||
const list: ITreeNode<number>[] = [];
|
||||
const list: IIndexTreeNode<number>[] = [];
|
||||
const filter = new class implements ITreeFilter<number> {
|
||||
filter(element: number): TreeVisibility {
|
||||
return element % 2 === 0 ? TreeVisibility.Visible : TreeVisibility.Hidden;
|
||||
}
|
||||
};
|
||||
|
||||
const model = new IndexTreeModel<number>(toSpliceable(list), -1, { filter });
|
||||
const model = new IndexTreeModel<number>('test', toSpliceable(list), -1, { filter });
|
||||
|
||||
model.splice([0], 0, Iterator.fromArray([
|
||||
{
|
||||
@@ -629,4 +690,4 @@ suite('IndexTreeModel', function () {
|
||||
assert.deepEqual(model.getNodeLocation(list[3]), [0, 5]);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -6,8 +6,9 @@
|
||||
import * as assert from 'assert';
|
||||
import { ITreeNode, ITreeRenderer } from 'vs/base/browser/ui/tree/tree';
|
||||
import { IListVirtualDelegate, IIdentityProvider } from 'vs/base/browser/ui/list/list';
|
||||
import { ObjectTree } from 'vs/base/browser/ui/tree/objectTree';
|
||||
import { ObjectTree, CompressibleObjectTree, ICompressibleTreeRenderer } from 'vs/base/browser/ui/tree/objectTree';
|
||||
import { Iterator } from 'vs/base/common/iterator';
|
||||
import { ICompressedTreeNode } from 'vs/base/browser/ui/tree/compressedObjectTreeModel';
|
||||
|
||||
suite('ObjectTree', function () {
|
||||
suite('TreeNavigator', function () {
|
||||
@@ -35,7 +36,7 @@ suite('ObjectTree', function () {
|
||||
disposeTemplate(): void { }
|
||||
};
|
||||
|
||||
tree = new ObjectTree<number>(container, delegate, [renderer], { filter: { filter: (el) => filter(el) } });
|
||||
tree = new ObjectTree<number>('test', container, delegate, [renderer], { filter: { filter: (el) => filter(el) } });
|
||||
tree.layout(200);
|
||||
});
|
||||
|
||||
@@ -81,8 +82,6 @@ suite('ObjectTree', function () {
|
||||
assert.equal(navigator.previous(), null);
|
||||
assert.equal(navigator.next(), 0);
|
||||
assert.equal(navigator.next(), 10);
|
||||
assert.equal(navigator.parent(), 0);
|
||||
assert.equal(navigator.parent(), null);
|
||||
assert.equal(navigator.first(), 0);
|
||||
assert.equal(navigator.last(), 2);
|
||||
});
|
||||
@@ -112,7 +111,6 @@ suite('ObjectTree', function () {
|
||||
assert.equal(navigator.previous(), 0);
|
||||
assert.equal(navigator.previous(), null);
|
||||
assert.equal(navigator.next(), 0);
|
||||
assert.equal(navigator.parent(), null);
|
||||
assert.equal(navigator.first(), 0);
|
||||
assert.equal(navigator.last(), 2);
|
||||
});
|
||||
@@ -147,8 +145,6 @@ suite('ObjectTree', function () {
|
||||
assert.equal(navigator.previous(), null);
|
||||
assert.equal(navigator.next(), 0);
|
||||
assert.equal(navigator.next(), 10);
|
||||
assert.equal(navigator.parent(), 0);
|
||||
assert.equal(navigator.parent(), null);
|
||||
assert.equal(navigator.first(), 0);
|
||||
assert.equal(navigator.last(), 2);
|
||||
});
|
||||
@@ -180,8 +176,6 @@ suite('ObjectTree', function () {
|
||||
assert.equal(navigator.previous(), null);
|
||||
assert.equal(navigator.next(), 0);
|
||||
assert.equal(navigator.next(), 10);
|
||||
assert.equal(navigator.parent(), 0);
|
||||
assert.equal(navigator.parent(), null);
|
||||
assert.equal(navigator.first(), 0);
|
||||
assert.equal(navigator.last(), 2);
|
||||
});
|
||||
@@ -214,7 +208,7 @@ suite('ObjectTree', function () {
|
||||
}
|
||||
};
|
||||
|
||||
const tree = new ObjectTree<number>(container, delegate, [renderer], { identityProvider });
|
||||
const tree = new ObjectTree<number>('test', container, delegate, [renderer], { identityProvider });
|
||||
tree.layout(200);
|
||||
|
||||
tree.setChildren(null, [{ element: 0 }, { element: 1 }, { element: 2 }, { element: 3 }]);
|
||||
@@ -224,4 +218,161 @@ suite('ObjectTree', function () {
|
||||
tree.setChildren(null, [{ element: 100 }, { element: 101 }, { element: 102 }, { element: 103 }]);
|
||||
assert.deepStrictEqual(tree.getFocus(), [101]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function toArray(list: NodeList): Node[] {
|
||||
const result: Node[] = [];
|
||||
list.forEach(node => result.push(node));
|
||||
return result;
|
||||
}
|
||||
|
||||
suite('CompressibleObjectTree', function () {
|
||||
|
||||
class Delegate implements IListVirtualDelegate<number> {
|
||||
getHeight() { return 20; }
|
||||
getTemplateId(): string { return 'default'; }
|
||||
}
|
||||
|
||||
class Renderer implements ICompressibleTreeRenderer<number, void, HTMLElement> {
|
||||
readonly templateId = 'default';
|
||||
renderTemplate(container: HTMLElement): HTMLElement {
|
||||
return container;
|
||||
}
|
||||
renderElement(node: ITreeNode<number, void>, _: number, templateData: HTMLElement): void {
|
||||
templateData.textContent = `${node.element}`;
|
||||
}
|
||||
renderCompressedElements(node: ITreeNode<ICompressedTreeNode<number>, void>, _: number, templateData: HTMLElement): void {
|
||||
templateData.textContent = `${node.element.elements.join('/')}`;
|
||||
}
|
||||
disposeTemplate(): void { }
|
||||
}
|
||||
|
||||
test('empty', function () {
|
||||
const container = document.createElement('div');
|
||||
container.style.width = '200px';
|
||||
container.style.height = '200px';
|
||||
|
||||
const tree = new CompressibleObjectTree<number>('test', container, new Delegate(), [new Renderer()]);
|
||||
tree.layout(200);
|
||||
|
||||
const rows = toArray(container.querySelectorAll('.monaco-tl-contents'));
|
||||
assert.equal(rows.length, 0);
|
||||
});
|
||||
|
||||
test('simple', function () {
|
||||
const container = document.createElement('div');
|
||||
container.style.width = '200px';
|
||||
container.style.height = '200px';
|
||||
|
||||
const tree = new CompressibleObjectTree<number>('test', container, new Delegate(), [new Renderer()]);
|
||||
tree.layout(200);
|
||||
|
||||
tree.setChildren(null, [
|
||||
{
|
||||
element: 0, children: [
|
||||
{ element: 10 },
|
||||
{ element: 11 },
|
||||
{ element: 12 },
|
||||
]
|
||||
},
|
||||
{ element: 1 },
|
||||
{ element: 2 }
|
||||
]);
|
||||
|
||||
const rows = toArray(container.querySelectorAll('.monaco-tl-contents')).map(row => row.textContent);
|
||||
assert.deepEqual(rows, ['0', '10', '11', '12', '1', '2']);
|
||||
});
|
||||
|
||||
test('compressed', () => {
|
||||
const container = document.createElement('div');
|
||||
container.style.width = '200px';
|
||||
container.style.height = '200px';
|
||||
|
||||
const tree = new CompressibleObjectTree<number>('test', container, new Delegate(), [new Renderer()]);
|
||||
tree.layout(200);
|
||||
|
||||
tree.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 },
|
||||
])
|
||||
}])
|
||||
}])
|
||||
}
|
||||
]));
|
||||
|
||||
let rows = toArray(container.querySelectorAll('.monaco-tl-contents')).map(row => row.textContent);
|
||||
assert.deepEqual(rows, ['1/11/111', '1111', '1112', '1113']);
|
||||
|
||||
tree.setChildren(11, Iterator.fromArray([
|
||||
{ element: 111 },
|
||||
{ element: 112 },
|
||||
{ element: 113 },
|
||||
]));
|
||||
|
||||
rows = toArray(container.querySelectorAll('.monaco-tl-contents')).map(row => row.textContent);
|
||||
assert.deepEqual(rows, ['1/11', '111', '112', '113']);
|
||||
|
||||
tree.setChildren(113, Iterator.fromArray([
|
||||
{ element: 1131 }
|
||||
]));
|
||||
|
||||
rows = toArray(container.querySelectorAll('.monaco-tl-contents')).map(row => row.textContent);
|
||||
assert.deepEqual(rows, ['1/11', '111', '112', '113/1131']);
|
||||
|
||||
tree.setChildren(1131, Iterator.fromArray([
|
||||
{ element: 1132 }
|
||||
]));
|
||||
|
||||
rows = toArray(container.querySelectorAll('.monaco-tl-contents')).map(row => row.textContent);
|
||||
assert.deepEqual(rows, ['1/11', '111', '112', '113/1131/1132']);
|
||||
|
||||
tree.setChildren(1131, Iterator.fromArray([
|
||||
{ element: 1132 },
|
||||
{ element: 1133 },
|
||||
]));
|
||||
|
||||
rows = toArray(container.querySelectorAll('.monaco-tl-contents')).map(row => row.textContent);
|
||||
assert.deepEqual(rows, ['1/11', '111', '112', '113/1131', '1132', '1133']);
|
||||
});
|
||||
|
||||
test('enableCompression', () => {
|
||||
const container = document.createElement('div');
|
||||
container.style.width = '200px';
|
||||
container.style.height = '200px';
|
||||
|
||||
const tree = new CompressibleObjectTree<number>('test', container, new Delegate(), [new Renderer()]);
|
||||
tree.layout(200);
|
||||
|
||||
assert.equal(tree.isCompressionEnabled(), true);
|
||||
|
||||
tree.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 },
|
||||
])
|
||||
}])
|
||||
}])
|
||||
}
|
||||
]));
|
||||
|
||||
let rows = toArray(container.querySelectorAll('.monaco-tl-contents')).map(row => row.textContent);
|
||||
assert.deepEqual(rows, ['1/11/111', '1111', '1112', '1113']);
|
||||
|
||||
tree.setCompressionEnabled(false);
|
||||
rows = toArray(container.querySelectorAll('.monaco-tl-contents')).map(row => row.textContent);
|
||||
assert.deepEqual(rows, ['1', '11', '111', '1111', '1112', '1113']);
|
||||
|
||||
tree.setCompressionEnabled(true);
|
||||
rows = toArray(container.querySelectorAll('.monaco-tl-contents')).map(row => row.textContent);
|
||||
assert.deepEqual(rows, ['1/11/111', '1111', '1112', '1113']);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -25,7 +25,7 @@ suite('ObjectTreeModel', function () {
|
||||
|
||||
test('ctor', () => {
|
||||
const list: ITreeNode<number>[] = [];
|
||||
const model = new ObjectTreeModel<number>(toSpliceable(list));
|
||||
const model = new ObjectTreeModel<number>('test', toSpliceable(list));
|
||||
assert(model);
|
||||
assert.equal(list.length, 0);
|
||||
assert.equal(model.size, 0);
|
||||
@@ -33,7 +33,7 @@ suite('ObjectTreeModel', function () {
|
||||
|
||||
test('flat', () => {
|
||||
const list: ITreeNode<number>[] = [];
|
||||
const model = new ObjectTreeModel<number>(toSpliceable(list));
|
||||
const model = new ObjectTreeModel<number>('test', toSpliceable(list));
|
||||
|
||||
model.setChildren(null, Iterator.fromArray([
|
||||
{ element: 0 },
|
||||
@@ -60,7 +60,7 @@ suite('ObjectTreeModel', function () {
|
||||
|
||||
test('nested', () => {
|
||||
const list: ITreeNode<number>[] = [];
|
||||
const model = new ObjectTreeModel<number>(toSpliceable(list));
|
||||
const model = new ObjectTreeModel<number>('test', toSpliceable(list));
|
||||
|
||||
model.setChildren(null, Iterator.fromArray([
|
||||
{
|
||||
@@ -96,7 +96,7 @@ suite('ObjectTreeModel', function () {
|
||||
|
||||
test('setChildren on collapsed node', () => {
|
||||
const list: ITreeNode<number>[] = [];
|
||||
const model = new ObjectTreeModel<number>(toSpliceable(list));
|
||||
const model = new ObjectTreeModel<number>('test', toSpliceable(list));
|
||||
|
||||
model.setChildren(null, Iterator.fromArray([
|
||||
{ element: 0, collapsed: true }
|
||||
@@ -117,7 +117,7 @@ suite('ObjectTreeModel', function () {
|
||||
|
||||
test('setChildren on expanded, unrevealed node', () => {
|
||||
const list: ITreeNode<number>[] = [];
|
||||
const model = new ObjectTreeModel<number>(toSpliceable(list));
|
||||
const model = new ObjectTreeModel<number>('test', toSpliceable(list));
|
||||
|
||||
model.setChildren(null, [
|
||||
{
|
||||
@@ -143,7 +143,7 @@ suite('ObjectTreeModel', function () {
|
||||
|
||||
test('collapse state is preserved with strict identity', () => {
|
||||
const list: ITreeNode<string>[] = [];
|
||||
const model = new ObjectTreeModel<string>(toSpliceable(list), { collapseByDefault: true });
|
||||
const model = new ObjectTreeModel<string>('test', toSpliceable(list), { collapseByDefault: true });
|
||||
const data = [{ element: 'father', children: [{ element: 'child' }] }];
|
||||
|
||||
model.setChildren(null, data);
|
||||
@@ -173,7 +173,7 @@ suite('ObjectTreeModel', function () {
|
||||
let compare: (a: string, b: string) => number = (a, b) => a < b ? -1 : 1;
|
||||
|
||||
const list: ITreeNode<string>[] = [];
|
||||
const model = new ObjectTreeModel<string>(toSpliceable(list), { sorter: { compare(a, b) { return compare(a, b); } } });
|
||||
const model = new ObjectTreeModel<string>('test', toSpliceable(list), { sorter: { compare(a, b) { return compare(a, b); } } });
|
||||
const data = [
|
||||
{ element: 'cars', children: [{ element: 'sedan' }, { element: 'convertible' }, { element: 'compact' }] },
|
||||
{ element: 'airplanes', children: [{ element: 'passenger' }, { element: 'jet' }] },
|
||||
@@ -188,7 +188,7 @@ suite('ObjectTreeModel', function () {
|
||||
let compare: (a: string, b: string) => number = () => 0;
|
||||
|
||||
const list: ITreeNode<string>[] = [];
|
||||
const model = new ObjectTreeModel<string>(toSpliceable(list), { sorter: { compare(a, b) { return compare(a, b); } } });
|
||||
const model = new ObjectTreeModel<string>('test', toSpliceable(list), { sorter: { compare(a, b) { return compare(a, b); } } });
|
||||
const data = [
|
||||
{ element: 'cars', children: [{ element: 'sedan' }, { element: 'convertible' }, { element: 'compact' }] },
|
||||
{ element: 'airplanes', children: [{ element: 'passenger' }, { element: 'jet' }] },
|
||||
@@ -223,7 +223,7 @@ suite('ObjectTreeModel', function () {
|
||||
|
||||
test('expandTo', () => {
|
||||
const list: ITreeNode<number>[] = [];
|
||||
const model = new ObjectTreeModel<number>(toSpliceable(list), { collapseByDefault: true });
|
||||
const model = new ObjectTreeModel<number>('test', toSpliceable(list), { collapseByDefault: true });
|
||||
|
||||
model.setChildren(null, [
|
||||
{
|
||||
@@ -241,4 +241,4 @@ suite('ObjectTreeModel', function () {
|
||||
model.expandTo(1000);
|
||||
assert.deepEqual(toArray(list), [0, 10, 100, 1000, 11, 12, 1, 2]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -392,6 +392,10 @@ suite('Filters', () => {
|
||||
test('patternPos isn\'t working correctly #79815', function () {
|
||||
assertMatches(':p'.substr(1), 'prop', '^prop', fuzzyScore, { patternPos: 0 });
|
||||
assertMatches(':p', 'prop', '^prop', fuzzyScore, { patternPos: 1 });
|
||||
assertMatches(':p', 'prop', undefined, fuzzyScore, { patternPos: 2 });
|
||||
assertMatches(':p', 'proP', 'pro^P', fuzzyScore, { patternPos: 1, wordPos: 1 });
|
||||
assertMatches(':p', 'aprop', 'a^prop', fuzzyScore, { patternPos: 1, firstMatchCanBeWeak: true });
|
||||
assertMatches(':p', 'aprop', undefined, fuzzyScore, { patternPos: 1, firstMatchCanBeWeak: false });
|
||||
});
|
||||
|
||||
function assertTopScore(filter: typeof fuzzyScore, pattern: string, expected: number, ...words: string[]) {
|
||||
|
||||
@@ -812,10 +812,10 @@ suite('Glob', () => {
|
||||
'{**/bar/**,**/baz/**}': true,
|
||||
'**/bulb/**': false
|
||||
}, ['foo', 'bar', 'baz'], [
|
||||
['bar/foo', '**/foo/**'],
|
||||
['foo/bar', '{**/bar/**,**/baz/**}'],
|
||||
['bar/nope', null!]
|
||||
]);
|
||||
['bar/foo', '**/foo/**'],
|
||||
['foo/bar', '{**/bar/**,**/baz/**}'],
|
||||
['bar/nope', null!]
|
||||
]);
|
||||
|
||||
const siblings = ['baz', 'baz.zip', 'nope'];
|
||||
const hasSibling = (name: string) => siblings.indexOf(name) !== -1;
|
||||
@@ -823,15 +823,15 @@ suite('Glob', () => {
|
||||
'**/foo/**': { when: '$(basename).zip' },
|
||||
'**/bar/**': true
|
||||
}, ['bar'], [
|
||||
['bar/foo', null!],
|
||||
['bar/foo/baz', null!],
|
||||
['bar/foo/nope', null!],
|
||||
['foo/bar', '**/bar/**'],
|
||||
], [
|
||||
null!,
|
||||
hasSibling,
|
||||
hasSibling
|
||||
]);
|
||||
['bar/foo', null!],
|
||||
['bar/foo/baz', null!],
|
||||
['bar/foo/nope', null!],
|
||||
['foo/bar', '**/bar/**'],
|
||||
], [
|
||||
null!,
|
||||
hasSibling,
|
||||
hasSibling
|
||||
]);
|
||||
});
|
||||
|
||||
function testOptimizationForBasenames(pattern: string | glob.IExpression, basenameTerms: string[], matches: [string, string | boolean][], siblingsFns: ((name: string) => boolean)[] = []) {
|
||||
@@ -917,11 +917,11 @@ suite('Glob', () => {
|
||||
// '{**/bar/bar/**,**/baz/bar/**}': true,
|
||||
'**/bulb/bar/**': false
|
||||
}, ['*/foo/bar'], [
|
||||
[nativeSep('bar/foo/bar'), '**/foo/bar/**'],
|
||||
// Not supported
|
||||
// [nativeSep('foo/bar/bar'), '{**/bar/bar/**,**/baz/bar/**}'],
|
||||
[nativeSep('/foo/bar/nope'), null!]
|
||||
]);
|
||||
[nativeSep('bar/foo/bar'), '**/foo/bar/**'],
|
||||
// Not supported
|
||||
// [nativeSep('foo/bar/bar'), '{**/bar/bar/**,**/baz/bar/**}'],
|
||||
[nativeSep('/foo/bar/nope'), null!]
|
||||
]);
|
||||
|
||||
const siblings = ['baz', 'baz.zip', 'nope'];
|
||||
let hasSibling = (name: string) => siblings.indexOf(name) !== -1;
|
||||
@@ -929,15 +929,15 @@ suite('Glob', () => {
|
||||
'**/foo/123/**': { when: '$(basename).zip' },
|
||||
'**/bar/123/**': true
|
||||
}, ['*/bar/123'], [
|
||||
[nativeSep('bar/foo/123'), null!],
|
||||
[nativeSep('bar/foo/123/baz'), null!],
|
||||
[nativeSep('bar/foo/123/nope'), null!],
|
||||
[nativeSep('foo/bar/123'), '**/bar/123/**'],
|
||||
], [
|
||||
null!,
|
||||
hasSibling,
|
||||
hasSibling
|
||||
]);
|
||||
[nativeSep('bar/foo/123'), null!],
|
||||
[nativeSep('bar/foo/123/baz'), null!],
|
||||
[nativeSep('bar/foo/123/nope'), null!],
|
||||
[nativeSep('foo/bar/123'), '**/bar/123/**'],
|
||||
], [
|
||||
null!,
|
||||
hasSibling,
|
||||
hasSibling
|
||||
]);
|
||||
});
|
||||
|
||||
function testOptimizationForPaths(pattern: string | glob.IExpression, pathTerms: string[], matches: [string, string | boolean][], siblingsFns: ((name: string) => boolean)[] = []) {
|
||||
|
||||
@@ -18,6 +18,8 @@ suite('Keytar', () => {
|
||||
const name = `VSCode Test ${Math.floor(Math.random() * 1e9)}`;
|
||||
try {
|
||||
await keytar.setPassword(name, 'foo', 'bar');
|
||||
assert.equal(await keytar.findPassword(name), 'bar');
|
||||
assert.equal((await keytar.findCredentials(name)).length, 1);
|
||||
assert.equal(await keytar.getPassword(name, 'foo'), 'bar');
|
||||
await keytar.deletePassword(name, 'foo');
|
||||
assert.equal(await keytar.getPassword(name, 'foo'), undefined);
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
(<any>self).postMessage(msg, transfer);
|
||||
}, null);
|
||||
|
||||
self.onmessage = (e) => messageHandler.onmessage(e.data);
|
||||
self.onmessage = (e: MessageEvent) => messageHandler.onmessage(e.data);
|
||||
while (beforeReadyMessages.length > 0) {
|
||||
self.onmessage(beforeReadyMessages.shift()!);
|
||||
}
|
||||
@@ -34,7 +34,7 @@
|
||||
|
||||
let isFirstMessage = true;
|
||||
let beforeReadyMessages: MessageEvent[] = [];
|
||||
self.onmessage = (message) => {
|
||||
self.onmessage = (message: MessageEvent) => {
|
||||
if (!isFirstMessage) {
|
||||
beforeReadyMessages.push(message);
|
||||
return;
|
||||
|
||||
Reference in New Issue
Block a user