mirror of
https://github.com/Microsoft/vscode
synced 2024-09-13 21:55:38 +00:00
Merge branch 'joao/scm-view'
This commit is contained in:
commit
5c94be9df2
|
@ -729,10 +729,10 @@ export class Repository implements Disposable {
|
|||
this.updateInputBoxPlaceholder();
|
||||
this.disposables.push(this.onDidRunGitStatus(() => this.updateInputBoxPlaceholder()));
|
||||
|
||||
this._mergeGroup = this._sourceControl.createResourceGroup('merge', localize('merge changes', "MERGE CHANGES"));
|
||||
this._indexGroup = this._sourceControl.createResourceGroup('index', localize('staged changes', "STAGED CHANGES"));
|
||||
this._workingTreeGroup = this._sourceControl.createResourceGroup('workingTree', localize('changes', "CHANGES"));
|
||||
this._untrackedGroup = this._sourceControl.createResourceGroup('untracked', localize('untracked changes', "UNTRACKED CHANGES"));
|
||||
this._mergeGroup = this._sourceControl.createResourceGroup('merge', localize('merge changes', "Merge Changes"));
|
||||
this._indexGroup = this._sourceControl.createResourceGroup('index', localize('staged changes', "Staged Changes"));
|
||||
this._workingTreeGroup = this._sourceControl.createResourceGroup('workingTree', localize('changes', "Changes"));
|
||||
this._untrackedGroup = this._sourceControl.createResourceGroup('untracked', localize('untracked changes', "Untracked Changes"));
|
||||
|
||||
const updateIndexGroupVisibility = () => {
|
||||
const config = workspace.getConfiguration('git', root);
|
||||
|
|
|
@ -11,7 +11,7 @@ import { IDisposable } from 'vs/base/common/lifecycle';
|
|||
import { IContextViewProvider, IAnchor, AnchorAlignment } from 'vs/base/browser/ui/contextview/contextview';
|
||||
import { IMenuOptions } from 'vs/base/browser/ui/menu/menu';
|
||||
import { ResolvedKeybinding, KeyCode } from 'vs/base/common/keyCodes';
|
||||
import { EventHelper, EventType, removeClass, addClass, append, $, addDisposableListener, addClasses } from 'vs/base/browser/dom';
|
||||
import { EventHelper, EventType, removeClass, addClass, append, $, addDisposableListener, addClasses, DOMEvent } from 'vs/base/browser/dom';
|
||||
import { IContextMenuDelegate } from 'vs/base/browser/contextmenu';
|
||||
import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
|
||||
import { Emitter } from 'vs/base/common/event';
|
||||
|
@ -122,7 +122,7 @@ export class BaseDropdown extends ActionRunner {
|
|||
return !!this.visible;
|
||||
}
|
||||
|
||||
protected onEvent(e: Event, activeElement: HTMLElement): void {
|
||||
protected onEvent(e: DOMEvent, activeElement: HTMLElement): void {
|
||||
this.hide();
|
||||
}
|
||||
|
||||
|
@ -294,6 +294,9 @@ export class DropdownMenuActionViewItem extends BaseActionViewItem {
|
|||
private anchorAlignmentProvider: (() => AnchorAlignment) | undefined;
|
||||
private menuAsChild?: boolean;
|
||||
|
||||
private _onDidChangeVisibility = this._register(new Emitter<boolean>());
|
||||
readonly onDidChangeVisibility = this._onDidChangeVisibility.event;
|
||||
|
||||
constructor(action: IAction, menuActions: ReadonlyArray<IAction>, contextMenuProvider: IContextMenuProvider, actionViewItemProvider: IActionViewItemProvider | undefined, actionRunner: IActionRunner, keybindings: ((action: IAction) => ResolvedKeybinding | undefined) | undefined, clazz: string | undefined, anchorAlignmentProvider?: () => AnchorAlignment, menuAsChild?: boolean);
|
||||
constructor(action: IAction, actionProvider: IActionProvider, contextMenuProvider: IContextMenuProvider, actionViewItemProvider: IActionViewItemProvider | undefined, actionRunner: IActionRunner, keybindings: ((action: IAction) => ResolvedKeybinding) | undefined, clazz: string | undefined, anchorAlignmentProvider?: () => AnchorAlignment, menuAsChild?: boolean);
|
||||
constructor(action: IAction, menuActionsOrProvider: ReadonlyArray<IAction> | IActionProvider, contextMenuProvider: IContextMenuProvider, actionViewItemProvider: IActionViewItemProvider | undefined, actionRunner: IActionRunner, keybindings: ((action: IAction) => ResolvedKeybinding | undefined) | undefined, clazz: string | undefined, anchorAlignmentProvider?: () => AnchorAlignment, menuAsChild?: boolean) {
|
||||
|
@ -339,7 +342,10 @@ export class DropdownMenuActionViewItem extends BaseActionViewItem {
|
|||
}
|
||||
|
||||
this.dropdownMenu = this._register(new DropdownMenu(container, options));
|
||||
this._register(this.dropdownMenu.onDidChangeVisibility(visible => this.element?.setAttribute('aria-expanded', `${visible}`)));
|
||||
this._register(this.dropdownMenu.onDidChangeVisibility(visible => {
|
||||
this.element?.setAttribute('aria-expanded', `${visible}`);
|
||||
this._onDidChangeVisibility.fire(visible);
|
||||
}));
|
||||
|
||||
this.dropdownMenu.menuOptions = {
|
||||
actionViewItemProvider: this.actionViewItemProvider,
|
||||
|
|
|
@ -376,6 +376,10 @@ export class ListView<T> implements ISpliceable<T>, IDisposable {
|
|||
}
|
||||
|
||||
updateElementHeight(index: number, size: number, anchorIndex: number | null): void {
|
||||
if (index < 0 || index >= this.items.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.items[index].size === size) {
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -225,10 +225,22 @@ class TraitSpliceable<T> implements ISpliceable<T> {
|
|||
}
|
||||
}
|
||||
|
||||
function isInputElement(e: HTMLElement): boolean {
|
||||
export function isInputElement(e: HTMLElement): boolean {
|
||||
return e.tagName === 'INPUT' || e.tagName === 'TEXTAREA';
|
||||
}
|
||||
|
||||
export function isMonacoEditor(e: HTMLElement): boolean {
|
||||
if (DOM.hasClass(e, 'monaco-editor')) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!e.parentElement) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return isMonacoEditor(e.parentElement);
|
||||
}
|
||||
|
||||
class KeyboardController<T> implements IDisposable {
|
||||
|
||||
private readonly disposables = new DisposableStore();
|
||||
|
@ -572,12 +584,20 @@ export class MouseController<T> implements IDisposable {
|
|||
}
|
||||
|
||||
private onMouseDown(e: IListMouseEvent<T> | IListTouchEvent<T>): void {
|
||||
if (isMonacoEditor(e.browserEvent.target as HTMLElement)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (document.activeElement !== e.browserEvent.target) {
|
||||
this.list.domFocus();
|
||||
}
|
||||
}
|
||||
|
||||
private onContextMenu(e: IListContextMenuEvent<T>): void {
|
||||
if (isMonacoEditor(e.browserEvent.target as HTMLElement)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const focus = typeof e.index === 'undefined' ? [] : [e.index];
|
||||
this.list.setFocus(focus, e.browserEvent);
|
||||
}
|
||||
|
@ -587,7 +607,7 @@ export class MouseController<T> implements IDisposable {
|
|||
return;
|
||||
}
|
||||
|
||||
if (isInputElement(e.browserEvent.target as HTMLElement)) {
|
||||
if (isInputElement(e.browserEvent.target as HTMLElement) || isMonacoEditor(e.browserEvent.target as HTMLElement)) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -621,7 +641,7 @@ export class MouseController<T> implements IDisposable {
|
|||
}
|
||||
|
||||
protected onDoubleClick(e: IListMouseEvent<T>): void {
|
||||
if (isInputElement(e.browserEvent.target as HTMLElement)) {
|
||||
if (isInputElement(e.browserEvent.target as HTMLElement) || isMonacoEditor(e.browserEvent.target as HTMLElement)) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
@ -9,10 +9,11 @@ import { Action, IActionRunner, IAction } from 'vs/base/common/actions';
|
|||
import { ActionBar, ActionsOrientation, IActionViewItemProvider } from 'vs/base/browser/ui/actionbar/actionbar';
|
||||
import { IContextMenuProvider, DropdownMenuActionViewItem } from 'vs/base/browser/ui/dropdown/dropdown';
|
||||
import { ResolvedKeybinding } from 'vs/base/common/keyCodes';
|
||||
import { Disposable, MutableDisposable } from 'vs/base/common/lifecycle';
|
||||
import { Disposable, IDisposable, combinedDisposable } from 'vs/base/common/lifecycle';
|
||||
import { AnchorAlignment } from 'vs/base/browser/ui/contextview/contextview';
|
||||
import { withNullAsUndefined } from 'vs/base/common/types';
|
||||
import { Codicon, registerIcon } from 'vs/base/common/codicons';
|
||||
import { Emitter } from 'vs/base/common/event';
|
||||
|
||||
export const CONTEXT = 'context.toolbar';
|
||||
|
||||
|
@ -35,17 +36,21 @@ export class ToolBar extends Disposable {
|
|||
private options: IToolBarOptions;
|
||||
private actionBar: ActionBar;
|
||||
private toggleMenuAction: ToggleMenuAction;
|
||||
private toggleMenuActionViewItem = this._register(new MutableDisposable<DropdownMenuActionViewItem>());
|
||||
private toggleMenuActionViewItem: DropdownMenuActionViewItem | undefined;
|
||||
private toggleMenuActionViewItemDisposable: IDisposable = Disposable.None;
|
||||
private hasSecondaryActions: boolean = false;
|
||||
private lookupKeybindings: boolean;
|
||||
|
||||
private _onDidChangeDropdownVisibility = this._register(new Emitter<boolean>());
|
||||
readonly onDidChangeDropdownVisibility = this._onDidChangeDropdownVisibility.event;
|
||||
|
||||
constructor(container: HTMLElement, contextMenuProvider: IContextMenuProvider, options: IToolBarOptions = { orientation: ActionsOrientation.HORIZONTAL }) {
|
||||
super();
|
||||
|
||||
this.options = options;
|
||||
this.lookupKeybindings = typeof this.options.getKeyBinding === 'function';
|
||||
|
||||
this.toggleMenuAction = this._register(new ToggleMenuAction(() => this.toggleMenuActionViewItem.value && this.toggleMenuActionViewItem.value.show(), options.toggleMenuTitle));
|
||||
this.toggleMenuAction = this._register(new ToggleMenuAction(() => this.toggleMenuActionViewItem?.show(), options.toggleMenuTitle));
|
||||
|
||||
let element = document.createElement('div');
|
||||
element.className = 'monaco-toolbar';
|
||||
|
@ -60,8 +65,10 @@ export class ToolBar extends Disposable {
|
|||
// Return special action item for the toggle menu action
|
||||
if (action.id === ToggleMenuAction.ID) {
|
||||
|
||||
this.toggleMenuActionViewItemDisposable.dispose();
|
||||
|
||||
// Create new
|
||||
this.toggleMenuActionViewItem.value = new DropdownMenuActionViewItem(
|
||||
this.toggleMenuActionViewItem = new DropdownMenuActionViewItem(
|
||||
action,
|
||||
(<ToggleMenuAction>action).menuActions,
|
||||
contextMenuProvider,
|
||||
|
@ -72,9 +79,14 @@ export class ToolBar extends Disposable {
|
|||
this.options.anchorAlignmentProvider,
|
||||
true
|
||||
);
|
||||
this.toggleMenuActionViewItem.value.setActionContext(this.actionBar.context);
|
||||
this.toggleMenuActionViewItem.setActionContext(this.actionBar.context);
|
||||
|
||||
return this.toggleMenuActionViewItem.value;
|
||||
this.toggleMenuActionViewItemDisposable = combinedDisposable(
|
||||
this.toggleMenuActionViewItem,
|
||||
this.toggleMenuActionViewItem.onDidChangeVisibility(e => this._onDidChangeDropdownVisibility.fire(e))
|
||||
);
|
||||
|
||||
return this.toggleMenuActionViewItem;
|
||||
}
|
||||
|
||||
return options.actionViewItemProvider ? options.actionViewItemProvider(action) : undefined;
|
||||
|
@ -92,8 +104,8 @@ export class ToolBar extends Disposable {
|
|||
|
||||
set context(context: unknown) {
|
||||
this.actionBar.context = context;
|
||||
if (this.toggleMenuActionViewItem.value) {
|
||||
this.toggleMenuActionViewItem.value.setActionContext(context);
|
||||
if (this.toggleMenuActionViewItem) {
|
||||
this.toggleMenuActionViewItem.setActionContext(context);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -151,6 +163,11 @@ export class ToolBar extends Disposable {
|
|||
}
|
||||
};
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
super.dispose();
|
||||
this.toggleMenuActionViewItemDisposable.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
class ToggleMenuAction extends Action {
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
|
||||
import 'vs/css!./media/tree';
|
||||
import { IDisposable, dispose, Disposable, toDisposable, DisposableStore } from 'vs/base/common/lifecycle';
|
||||
import { IListOptions, List, IListStyles, MouseController, DefaultKeyboardNavigationDelegate } from 'vs/base/browser/ui/list/listWidget';
|
||||
import { IListOptions, List, IListStyles, MouseController, DefaultKeyboardNavigationDelegate, isInputElement, isMonacoEditor } from 'vs/base/browser/ui/list/listWidget';
|
||||
import { IListVirtualDelegate, IListRenderer, IListMouseEvent, IListContextMenuEvent, IListDragAndDrop, IListDragOverReaction, IKeyboardNavigationLabelProvider, IIdentityProvider, IKeyboardNavigationDelegate } from 'vs/base/browser/ui/list/list';
|
||||
import { append, $, toggleClass, getDomNodePagePosition, removeClass, addClass, hasClass, hasParentWithClass, createStyleSheet, clearNode, addClasses, removeClasses } from 'vs/base/browser/dom';
|
||||
import { Event, Relay, Emitter, EventBufferer } from 'vs/base/common/event';
|
||||
|
@ -917,10 +917,6 @@ class TypeFilterController<T, TFilterData> implements IDisposable {
|
|||
}
|
||||
}
|
||||
|
||||
function isInputElement(e: HTMLElement): boolean {
|
||||
return e.tagName === 'INPUT' || e.tagName === 'TEXTAREA';
|
||||
}
|
||||
|
||||
function asTreeMouseEvent<T>(event: IListMouseEvent<ITreeNode<T, any>>): ITreeMouseEvent<T> {
|
||||
let target: TreeMouseEventTarget = TreeMouseEventTarget.Unknown;
|
||||
|
||||
|
@ -1084,7 +1080,7 @@ class TreeNodeListMouseController<T, TFilterData, TRef> extends MouseController<
|
|||
}
|
||||
|
||||
protected onViewPointer(e: IListMouseEvent<ITreeNode<T, TFilterData>>): void {
|
||||
if (isInputElement(e.browserEvent.target as HTMLElement)) {
|
||||
if (isInputElement(e.browserEvent.target as HTMLElement) || isMonacoEditor(e.browserEvent.target as HTMLElement)) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
@ -3,11 +3,11 @@
|
|||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { ISpliceable } from 'vs/base/common/sequence';
|
||||
import { Iterable } from 'vs/base/common/iterator';
|
||||
import { Event } from 'vs/base/common/event';
|
||||
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';
|
||||
import { IList } from 'vs/base/browser/ui/tree/indexTreeModel';
|
||||
|
||||
// Exported only for test reasons, do not use directly
|
||||
export interface ICompressedTreeElement<T> extends ITreeElement<T> {
|
||||
|
@ -126,7 +126,7 @@ export class CompressedObjectTreeModel<T extends NonNullable<any>, TFilterData e
|
|||
|
||||
constructor(
|
||||
private user: string,
|
||||
list: ISpliceable<ITreeNode<ICompressedTreeNode<T>, TFilterData>>,
|
||||
list: IList<ITreeNode<ICompressedTreeNode<T>, TFilterData>>,
|
||||
options: ICompressedObjectTreeModelOptions<T, TFilterData> = {}
|
||||
) {
|
||||
this.model = new ObjectTreeModel(user, list, options);
|
||||
|
@ -290,6 +290,16 @@ export class CompressedObjectTreeModel<T extends NonNullable<any>, TFilterData e
|
|||
this.model.rerender(compressedNode);
|
||||
}
|
||||
|
||||
updateElementHeight(element: T, height: number): void {
|
||||
const compressedNode = this.getCompressedNode(element);
|
||||
|
||||
if (!compressedNode) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.model.updateElementHeight(compressedNode, height);
|
||||
}
|
||||
|
||||
refilter(): void {
|
||||
this.model.refilter();
|
||||
}
|
||||
|
@ -340,10 +350,13 @@ class CompressedTreeNodeWrapper<T, TFilterData> implements ITreeNode<T | null, T
|
|||
) { }
|
||||
}
|
||||
|
||||
function mapList<T, TFilterData>(nodeMapper: CompressedNodeWeakMapper<T, TFilterData>, list: ISpliceable<ITreeNode<T, TFilterData>>): ISpliceable<ITreeNode<ICompressedTreeNode<T>, TFilterData>> {
|
||||
function mapList<T, TFilterData>(nodeMapper: CompressedNodeWeakMapper<T, TFilterData>, list: IList<ITreeNode<T, TFilterData>>): IList<ITreeNode<ICompressedTreeNode<T>, TFilterData>> {
|
||||
return {
|
||||
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>[]);
|
||||
},
|
||||
updateElementHeight(index: number, height: number): void {
|
||||
list.updateElementHeight(index, height);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -402,7 +415,7 @@ export class CompressibleObjectTreeModel<T extends NonNullable<any>, TFilterData
|
|||
|
||||
constructor(
|
||||
user: string,
|
||||
list: ISpliceable<ITreeNode<T, TFilterData>>,
|
||||
list: IList<ITreeNode<T, TFilterData>>,
|
||||
options: ICompressibleObjectTreeModelOptions<T, TFilterData> = {}
|
||||
) {
|
||||
this.elementMapper = options.elementMapper || DefaultElementMapper;
|
||||
|
@ -492,6 +505,10 @@ export class CompressibleObjectTreeModel<T extends NonNullable<any>, TFilterData
|
|||
return this.model.rerender(location);
|
||||
}
|
||||
|
||||
updateElementHeight(element: T, height: number): void {
|
||||
this.model.updateElementHeight(element, height);
|
||||
}
|
||||
|
||||
refilter(): void {
|
||||
return this.model.refilter();
|
||||
}
|
||||
|
|
|
@ -4,11 +4,11 @@
|
|||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { AbstractTree, IAbstractTreeOptions } from 'vs/base/browser/ui/tree/abstractTree';
|
||||
import { ISpliceable } from 'vs/base/common/sequence';
|
||||
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 { Iterable } from 'vs/base/common/iterator';
|
||||
import { IList } from 'vs/base/browser/ui/tree/indexTreeModel';
|
||||
|
||||
export interface IDataTreeOptions<T, TFilterData = void> extends IAbstractTreeOptions<T, TFilterData> {
|
||||
readonly sorter?: ITreeSorter<T>;
|
||||
|
@ -171,7 +171,7 @@ export class DataTree<TInput, T, TFilterData = void> extends AbstractTree<T | nu
|
|||
return { elements, size: children.length };
|
||||
}
|
||||
|
||||
protected createModel(user: string, view: ISpliceable<ITreeNode<T, TFilterData>>, options: IDataTreeOptions<T, TFilterData>): ITreeModel<T | null, TFilterData, T | null> {
|
||||
protected createModel(user: string, view: IList<ITreeNode<T, TFilterData>>, options: IDataTreeOptions<T, TFilterData>): ITreeModel<T | null, TFilterData, T | null> {
|
||||
return new ObjectTreeModel(user, view, options);
|
||||
}
|
||||
|
||||
|
|
|
@ -6,8 +6,7 @@
|
|||
import 'vs/css!./media/tree';
|
||||
import { Iterable } from 'vs/base/common/iterator';
|
||||
import { AbstractTree, IAbstractTreeOptions } from 'vs/base/browser/ui/tree/abstractTree';
|
||||
import { ISpliceable } from 'vs/base/common/sequence';
|
||||
import { IndexTreeModel } from 'vs/base/browser/ui/tree/indexTreeModel';
|
||||
import { IndexTreeModel, IList } from 'vs/base/browser/ui/tree/indexTreeModel';
|
||||
import { ITreeElement, ITreeModel, ITreeNode, ITreeRenderer } from 'vs/base/browser/ui/tree/tree';
|
||||
import { IListVirtualDelegate } from 'vs/base/browser/ui/list/list';
|
||||
|
||||
|
@ -41,7 +40,11 @@ export class IndexTree<T, TFilterData = void> extends AbstractTree<T, TFilterDat
|
|||
this.model.rerender(location);
|
||||
}
|
||||
|
||||
protected createModel(user: string, view: ISpliceable<ITreeNode<T, TFilterData>>, options: IIndexTreeOptions<T, TFilterData>): ITreeModel<T, TFilterData, number[]> {
|
||||
updateElementHeight(location: number[], height: number): void {
|
||||
this.model.updateElementHeight(location, height);
|
||||
}
|
||||
|
||||
protected createModel(user: string, view: IList<ITreeNode<T, TFilterData>>, options: IIndexTreeOptions<T, TFilterData>): ITreeModel<T, TFilterData, number[]> {
|
||||
return new IndexTreeModel(user, view, this.rootElement, options);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -56,6 +56,10 @@ function isCollapsibleStateUpdate(update: CollapseStateUpdate): update is Collap
|
|||
return typeof (update as any).collapsible === 'boolean';
|
||||
}
|
||||
|
||||
export interface IList<T> extends ISpliceable<T> {
|
||||
updateElementHeight(index: number, height: number): void;
|
||||
}
|
||||
|
||||
export class IndexTreeModel<T extends Exclude<any, undefined>, TFilterData = void> implements ITreeModel<T, TFilterData, number[]> {
|
||||
|
||||
readonly rootRef = [];
|
||||
|
@ -78,7 +82,7 @@ export class IndexTreeModel<T extends Exclude<any, undefined>, TFilterData = voi
|
|||
|
||||
constructor(
|
||||
private user: string,
|
||||
private list: ISpliceable<ITreeNode<T, TFilterData>>,
|
||||
private list: IList<ITreeNode<T, TFilterData>>,
|
||||
rootElement: T,
|
||||
options: IIndexTreeModelOptions<T, TFilterData> = {}
|
||||
) {
|
||||
|
@ -212,6 +216,15 @@ export class IndexTreeModel<T extends Exclude<any, undefined>, TFilterData = voi
|
|||
}
|
||||
}
|
||||
|
||||
updateElementHeight(location: number[], height: number): void {
|
||||
if (location.length === 0) {
|
||||
throw new TreeError(this.user, 'Invalid tree location');
|
||||
}
|
||||
|
||||
const { listIndex } = this.getTreeNodeWithListIndex(location);
|
||||
this.list.updateElementHeight(listIndex, height);
|
||||
}
|
||||
|
||||
has(location: number[]): boolean {
|
||||
return this.hasTreeNode(location);
|
||||
}
|
||||
|
|
|
@ -5,13 +5,13 @@
|
|||
|
||||
import { Iterable } from 'vs/base/common/iterator';
|
||||
import { AbstractTree, IAbstractTreeOptions, IAbstractTreeOptionsUpdate } 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, IKeyboardNavigationLabelProvider } 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';
|
||||
import { memoize } from 'vs/base/common/decorators';
|
||||
import { IList } from 'vs/base/browser/ui/tree/indexTreeModel';
|
||||
|
||||
export interface IObjectTreeOptions<T, TFilterData = void> extends IAbstractTreeOptions<T, TFilterData> {
|
||||
readonly sorter?: ITreeSorter<T>;
|
||||
|
@ -46,6 +46,10 @@ export class ObjectTree<T extends NonNullable<any>, TFilterData = void> extends
|
|||
this.model.rerender(element);
|
||||
}
|
||||
|
||||
updateElementHeight(element: T, height: number): void {
|
||||
this.model.updateElementHeight(element, height);
|
||||
}
|
||||
|
||||
resort(element: T, recursive = true): void {
|
||||
this.model.resort(element, recursive);
|
||||
}
|
||||
|
@ -54,7 +58,7 @@ export class ObjectTree<T extends NonNullable<any>, TFilterData = void> extends
|
|||
return this.model.has(element);
|
||||
}
|
||||
|
||||
protected createModel(user: string, view: ISpliceable<ITreeNode<T, TFilterData>>, options: IObjectTreeOptions<T, TFilterData>): ITreeModel<T | null, TFilterData, T | null> {
|
||||
protected createModel(user: string, view: IList<ITreeNode<T, TFilterData>>, options: IObjectTreeOptions<T, TFilterData>): ITreeModel<T | null, TFilterData, T | null> {
|
||||
return new ObjectTreeModel(user, view, options);
|
||||
}
|
||||
}
|
||||
|
@ -188,7 +192,7 @@ export class CompressibleObjectTree<T extends NonNullable<any>, TFilterData = vo
|
|||
this.model.setChildren(element, children);
|
||||
}
|
||||
|
||||
protected createModel(user: string, view: ISpliceable<ITreeNode<T, TFilterData>>, options: ICompressibleObjectTreeOptions<T, TFilterData>): ITreeModel<T | null, TFilterData, T | null> {
|
||||
protected createModel(user: string, view: IList<ITreeNode<T, TFilterData>>, options: ICompressibleObjectTreeOptions<T, TFilterData>): ITreeModel<T | null, TFilterData, T | null> {
|
||||
return new CompressibleObjectTreeModel(user, view, options);
|
||||
}
|
||||
|
||||
|
|
|
@ -3,9 +3,8 @@
|
|||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { ISpliceable } from 'vs/base/common/sequence';
|
||||
import { Iterable } from 'vs/base/common/iterator';
|
||||
import { IndexTreeModel, IIndexTreeModelOptions } from 'vs/base/browser/ui/tree/indexTreeModel';
|
||||
import { IndexTreeModel, IIndexTreeModelOptions, IList } from 'vs/base/browser/ui/tree/indexTreeModel';
|
||||
import { Event } from 'vs/base/common/event';
|
||||
import { ITreeModel, ITreeNode, ITreeElement, ITreeSorter, ICollapseStateChangeEvent, ITreeModelSpliceEvent, TreeError } from 'vs/base/browser/ui/tree/tree';
|
||||
import { IIdentityProvider } from 'vs/base/browser/ui/list/list';
|
||||
|
@ -16,6 +15,7 @@ export type ITreeNodeCallback<T, TFilterData> = (node: ITreeNode<T, TFilterData>
|
|||
export interface IObjectTreeModel<T extends NonNullable<any>, TFilterData extends NonNullable<any> = void> extends ITreeModel<T | null, TFilterData, T | null> {
|
||||
setChildren(element: T | null, children: Iterable<ITreeElement<T>> | undefined): void;
|
||||
resort(element?: T | null, recursive?: boolean): void;
|
||||
updateElementHeight(element: T, height: number): void;
|
||||
}
|
||||
|
||||
export interface IObjectTreeModelOptions<T, TFilterData> extends IIndexTreeModelOptions<T, TFilterData> {
|
||||
|
@ -41,7 +41,7 @@ export class ObjectTreeModel<T extends NonNullable<any>, TFilterData extends Non
|
|||
|
||||
constructor(
|
||||
private user: string,
|
||||
list: ISpliceable<ITreeNode<T, TFilterData>>,
|
||||
list: IList<ITreeNode<T, TFilterData>>,
|
||||
options: IObjectTreeModelOptions<T, TFilterData> = {}
|
||||
) {
|
||||
this.model = new IndexTreeModel(user, list, null, options);
|
||||
|
@ -169,6 +169,11 @@ export class ObjectTreeModel<T extends NonNullable<any>, TFilterData extends Non
|
|||
this.model.rerender(location);
|
||||
}
|
||||
|
||||
updateElementHeight(element: T, height: number): void {
|
||||
const location = this.getElementLocation(element);
|
||||
this.model.updateElementHeight(location, height);
|
||||
}
|
||||
|
||||
resort(element: T | null = null, recursive = true): void {
|
||||
if (!this.sorter) {
|
||||
return;
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Event, Emitter } from 'vs/base/common/event';
|
||||
import { IDisposable } from 'vs/base/common/lifecycle';
|
||||
|
||||
export interface ISplice<T> {
|
||||
readonly start: number;
|
||||
|
@ -32,3 +33,26 @@ export class Sequence<T> implements ISequence<T>, ISpliceable<T> {
|
|||
this._onDidSplice.fire({ start, deleteCount, toInsert });
|
||||
}
|
||||
}
|
||||
|
||||
export class SimpleSequence<T> implements ISequence<T> {
|
||||
|
||||
private _elements: T[];
|
||||
get elements(): T[] { return this._elements; }
|
||||
|
||||
readonly onDidSplice: Event<ISplice<T>>;
|
||||
private disposable: IDisposable;
|
||||
|
||||
constructor(elements: T[], onDidAdd: Event<T>, onDidRemove: Event<T>) {
|
||||
this._elements = [...elements];
|
||||
this.onDidSplice = Event.any(
|
||||
Event.map(onDidAdd, e => ({ start: this.elements.length, deleteCount: 0, toInsert: [e] })),
|
||||
Event.map(Event.filter(Event.map(onDidRemove, e => this.elements.indexOf(e)), i => i > -1), i => ({ start: i, deleteCount: 1, toInsert: [] }))
|
||||
);
|
||||
|
||||
this.disposable = this.onDidSplice(({ start, deleteCount, toInsert }) => this._elements.splice(start, deleteCount, ...toInsert));
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
this.disposable.dispose();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,7 +7,7 @@ import * as assert from 'assert';
|
|||
import { compress, ICompressedTreeElement, ICompressedTreeNode, decompress, CompressedObjectTreeModel } from 'vs/base/browser/ui/tree/compressedObjectTreeModel';
|
||||
import { Iterable } from 'vs/base/common/iterator';
|
||||
import { ITreeNode } from 'vs/base/browser/ui/tree/tree';
|
||||
import { ISpliceable } from 'vs/base/common/sequence';
|
||||
import { IList } from 'vs/base/browser/ui/tree/indexTreeModel';
|
||||
|
||||
interface IResolvedCompressedTreeElement<T> extends ICompressedTreeElement<T> {
|
||||
readonly element: T;
|
||||
|
@ -289,11 +289,12 @@ suite('CompressedObjectTree', function () {
|
|||
});
|
||||
});
|
||||
|
||||
function toSpliceable<T>(arr: T[]): ISpliceable<T> {
|
||||
function toList<T>(arr: T[]): IList<T> {
|
||||
return {
|
||||
splice(start: number, deleteCount: number, elements: T[]): void {
|
||||
arr.splice(start, deleteCount, ...elements);
|
||||
}
|
||||
},
|
||||
updateElementHeight() { }
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -305,7 +306,7 @@ suite('CompressedObjectTree', function () {
|
|||
|
||||
test('ctor', () => {
|
||||
const list: ITreeNode<ICompressedTreeNode<number>>[] = [];
|
||||
const model = new CompressedObjectTreeModel<number>('test', toSpliceable(list));
|
||||
const model = new CompressedObjectTreeModel<number>('test', toList(list));
|
||||
assert(model);
|
||||
assert.equal(list.length, 0);
|
||||
assert.equal(model.size, 0);
|
||||
|
@ -313,7 +314,7 @@ suite('CompressedObjectTree', function () {
|
|||
|
||||
test('flat', () => {
|
||||
const list: ITreeNode<ICompressedTreeNode<number>>[] = [];
|
||||
const model = new CompressedObjectTreeModel<number>('test', toSpliceable(list));
|
||||
const model = new CompressedObjectTreeModel<number>('test', toList(list));
|
||||
|
||||
model.setChildren(null, [
|
||||
{ element: 0 },
|
||||
|
@ -340,7 +341,7 @@ suite('CompressedObjectTree', function () {
|
|||
|
||||
test('nested', () => {
|
||||
const list: ITreeNode<ICompressedTreeNode<number>>[] = [];
|
||||
const model = new CompressedObjectTreeModel<number>('test', toSpliceable(list));
|
||||
const model = new CompressedObjectTreeModel<number>('test', toList(list));
|
||||
|
||||
model.setChildren(null, [
|
||||
{
|
||||
|
@ -376,7 +377,7 @@ suite('CompressedObjectTree', function () {
|
|||
|
||||
test('compressed', () => {
|
||||
const list: ITreeNode<ICompressedTreeNode<number>>[] = [];
|
||||
const model = new CompressedObjectTreeModel<number>('test', toSpliceable(list));
|
||||
const model = new CompressedObjectTreeModel<number>('test', toList(list));
|
||||
|
||||
model.setChildren(null, [
|
||||
{
|
||||
|
|
|
@ -5,14 +5,14 @@
|
|||
|
||||
import * as assert from 'assert';
|
||||
import { ITreeNode, ITreeFilter, TreeVisibility } from 'vs/base/browser/ui/tree/tree';
|
||||
import { ISpliceable } from 'vs/base/common/sequence';
|
||||
import { IndexTreeModel, IIndexTreeNode } from 'vs/base/browser/ui/tree/indexTreeModel';
|
||||
import { IndexTreeModel, IIndexTreeNode, IList } from 'vs/base/browser/ui/tree/indexTreeModel';
|
||||
|
||||
function toSpliceable<T>(arr: T[]): ISpliceable<T> {
|
||||
function toList<T>(arr: T[]): IList<T> {
|
||||
return {
|
||||
splice(start: number, deleteCount: number, elements: T[]): void {
|
||||
arr.splice(start, deleteCount, ...elements);
|
||||
}
|
||||
},
|
||||
updateElementHeight() { }
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -24,14 +24,14 @@ suite('IndexTreeModel', function () {
|
|||
|
||||
test('ctor', () => {
|
||||
const list: ITreeNode<number>[] = [];
|
||||
const model = new IndexTreeModel<number>('test', toSpliceable(list), -1);
|
||||
const model = new IndexTreeModel<number>('test', toList(list), -1);
|
||||
assert(model);
|
||||
assert.equal(list.length, 0);
|
||||
});
|
||||
|
||||
test('insert', () => {
|
||||
const list: ITreeNode<number>[] = [];
|
||||
const model = new IndexTreeModel<number>('test', toSpliceable(list), -1);
|
||||
const model = new IndexTreeModel<number>('test', toList(list), -1);
|
||||
|
||||
model.splice([0], 0, [
|
||||
{ element: 0 },
|
||||
|
@ -53,7 +53,7 @@ suite('IndexTreeModel', function () {
|
|||
|
||||
test('deep insert', function () {
|
||||
const list: ITreeNode<number>[] = [];
|
||||
const model = new IndexTreeModel<number>('test', toSpliceable(list), -1);
|
||||
const model = new IndexTreeModel<number>('test', toList(list), -1);
|
||||
|
||||
model.splice([0], 0, [
|
||||
{
|
||||
|
@ -90,7 +90,7 @@ suite('IndexTreeModel', function () {
|
|||
|
||||
test('deep insert collapsed', function () {
|
||||
const list: ITreeNode<number>[] = [];
|
||||
const model = new IndexTreeModel<number>('test', toSpliceable(list), -1);
|
||||
const model = new IndexTreeModel<number>('test', toList(list), -1);
|
||||
|
||||
model.splice([0], 0, [
|
||||
{
|
||||
|
@ -118,7 +118,7 @@ suite('IndexTreeModel', function () {
|
|||
|
||||
test('delete', () => {
|
||||
const list: ITreeNode<number>[] = [];
|
||||
const model = new IndexTreeModel<number>('test', toSpliceable(list), -1);
|
||||
const model = new IndexTreeModel<number>('test', toList(list), -1);
|
||||
|
||||
model.splice([0], 0, [
|
||||
{ element: 0 },
|
||||
|
@ -143,7 +143,7 @@ suite('IndexTreeModel', function () {
|
|||
|
||||
test('nested delete', function () {
|
||||
const list: ITreeNode<number>[] = [];
|
||||
const model = new IndexTreeModel<number>('test', toSpliceable(list), -1);
|
||||
const model = new IndexTreeModel<number>('test', toList(list), -1);
|
||||
|
||||
model.splice([0], 0, [
|
||||
{
|
||||
|
@ -177,7 +177,7 @@ suite('IndexTreeModel', function () {
|
|||
|
||||
test('deep delete', function () {
|
||||
const list: ITreeNode<number>[] = [];
|
||||
const model = new IndexTreeModel<number>('test', toSpliceable(list), -1);
|
||||
const model = new IndexTreeModel<number>('test', toList(list), -1);
|
||||
|
||||
model.splice([0], 0, [
|
||||
{
|
||||
|
@ -205,7 +205,7 @@ suite('IndexTreeModel', function () {
|
|||
|
||||
test('hidden delete', function () {
|
||||
const list: ITreeNode<number>[] = [];
|
||||
const model = new IndexTreeModel<number>('test', toSpliceable(list), -1);
|
||||
const model = new IndexTreeModel<number>('test', toList(list), -1);
|
||||
|
||||
model.splice([0], 0, [
|
||||
{
|
||||
|
@ -230,7 +230,7 @@ suite('IndexTreeModel', function () {
|
|||
|
||||
test('collapse', () => {
|
||||
const list: ITreeNode<number>[] = [];
|
||||
const model = new IndexTreeModel<number>('test', toSpliceable(list), -1);
|
||||
const model = new IndexTreeModel<number>('test', toList(list), -1);
|
||||
|
||||
model.splice([0], 0, [
|
||||
{
|
||||
|
@ -261,7 +261,7 @@ suite('IndexTreeModel', function () {
|
|||
|
||||
test('expand', () => {
|
||||
const list: ITreeNode<number>[] = [];
|
||||
const model = new IndexTreeModel<number>('test', toSpliceable(list), -1);
|
||||
const model = new IndexTreeModel<number>('test', toList(list), -1);
|
||||
|
||||
model.splice([0], 0, [
|
||||
{
|
||||
|
@ -301,7 +301,7 @@ suite('IndexTreeModel', function () {
|
|||
|
||||
test('collapse should recursively adjust visible count', function () {
|
||||
const list: ITreeNode<number>[] = [];
|
||||
const model = new IndexTreeModel<number>('test', toSpliceable(list), -1);
|
||||
const model = new IndexTreeModel<number>('test', toList(list), -1);
|
||||
|
||||
model.splice([0], 0, [
|
||||
{
|
||||
|
@ -334,7 +334,7 @@ suite('IndexTreeModel', function () {
|
|||
|
||||
test('setCollapsible', () => {
|
||||
const list: ITreeNode<number>[] = [];
|
||||
const model = new IndexTreeModel<number>('test', toSpliceable(list), -1);
|
||||
const model = new IndexTreeModel<number>('test', toList(list), -1);
|
||||
|
||||
model.splice([0], 0, [
|
||||
{
|
||||
|
@ -403,7 +403,7 @@ suite('IndexTreeModel', function () {
|
|||
}
|
||||
};
|
||||
|
||||
const model = new IndexTreeModel<number>('test', toSpliceable(list), -1, { filter });
|
||||
const model = new IndexTreeModel<number>('test', toList(list), -1, { filter });
|
||||
|
||||
model.splice([0], 0, [
|
||||
{
|
||||
|
@ -437,7 +437,7 @@ suite('IndexTreeModel', function () {
|
|||
}
|
||||
};
|
||||
|
||||
const model = new IndexTreeModel<number>('test', toSpliceable(list), -1, { filter });
|
||||
const model = new IndexTreeModel<number>('test', toList(list), -1, { filter });
|
||||
|
||||
model.splice([0], 0, [
|
||||
{
|
||||
|
@ -460,7 +460,7 @@ suite('IndexTreeModel', function () {
|
|||
}
|
||||
};
|
||||
|
||||
const model = new IndexTreeModel<number>('test', toSpliceable(list), -1, { filter });
|
||||
const model = new IndexTreeModel<number>('test', toList(list), -1, { filter });
|
||||
|
||||
model.splice([0], 0, [
|
||||
{
|
||||
|
@ -499,7 +499,7 @@ suite('IndexTreeModel', function () {
|
|||
}
|
||||
};
|
||||
|
||||
const model = new IndexTreeModel<string>('test', toSpliceable(list), 'root', { filter });
|
||||
const model = new IndexTreeModel<string>('test', toList(list), 'root', { filter });
|
||||
|
||||
model.splice([0], 0, [
|
||||
{
|
||||
|
@ -545,7 +545,7 @@ suite('IndexTreeModel', function () {
|
|||
}
|
||||
};
|
||||
|
||||
const model = new IndexTreeModel<string>('test', toSpliceable(list), 'root', { filter });
|
||||
const model = new IndexTreeModel<string>('test', toList(list), 'root', { filter });
|
||||
|
||||
model.splice([0], 0, [
|
||||
{
|
||||
|
@ -591,7 +591,7 @@ suite('IndexTreeModel', function () {
|
|||
}
|
||||
};
|
||||
|
||||
const model = new IndexTreeModel<string>('test', toSpliceable(list), 'root', { filter });
|
||||
const model = new IndexTreeModel<string>('test', toList(list), 'root', { filter });
|
||||
|
||||
model.splice([0], 0, [
|
||||
{
|
||||
|
@ -639,7 +639,7 @@ suite('IndexTreeModel', function () {
|
|||
|
||||
test('simple', function () {
|
||||
const list: IIndexTreeNode<number>[] = [];
|
||||
const model = new IndexTreeModel<number>('test', toSpliceable(list), -1);
|
||||
const model = new IndexTreeModel<number>('test', toList(list), -1);
|
||||
|
||||
model.splice([0], 0, [
|
||||
{
|
||||
|
@ -669,7 +669,7 @@ suite('IndexTreeModel', function () {
|
|||
}
|
||||
};
|
||||
|
||||
const model = new IndexTreeModel<number>('test', toSpliceable(list), -1, { filter });
|
||||
const model = new IndexTreeModel<number>('test', toList(list), -1, { filter });
|
||||
|
||||
model.splice([0], 0, [
|
||||
{
|
||||
|
@ -701,7 +701,7 @@ suite('IndexTreeModel', function () {
|
|||
}
|
||||
};
|
||||
|
||||
const model = new IndexTreeModel<string>('test', toSpliceable(list), 'root', { filter });
|
||||
const model = new IndexTreeModel<string>('test', toList(list), 'root', { filter });
|
||||
|
||||
model.splice([0], 0, [
|
||||
{ element: 'silver' },
|
||||
|
@ -735,7 +735,7 @@ suite('IndexTreeModel', function () {
|
|||
}
|
||||
};
|
||||
|
||||
const model = new IndexTreeModel<string>('test', toSpliceable(list), 'root', { filter });
|
||||
const model = new IndexTreeModel<string>('test', toList(list), 'root', { filter });
|
||||
|
||||
model.splice([0], 0, [
|
||||
{ element: 'a', children: [{ element: 'aa' }] },
|
||||
|
|
|
@ -5,15 +5,16 @@
|
|||
|
||||
import * as assert from 'assert';
|
||||
import { ITreeNode, ITreeFilter, TreeVisibility } from 'vs/base/browser/ui/tree/tree';
|
||||
import { ISpliceable } from 'vs/base/common/sequence';
|
||||
import { ObjectTreeModel } from 'vs/base/browser/ui/tree/objectTreeModel';
|
||||
import { IList } from 'vs/base/browser/ui/tree/indexTreeModel';
|
||||
|
||||
function toSpliceable<T>(arr: T[]): ISpliceable<T> {
|
||||
function toList<T>(arr: T[]): IList<T> {
|
||||
return {
|
||||
splice(start: number, deleteCount: number, elements: T[]): void {
|
||||
// console.log(`splice (${start}, ${deleteCount}, ${elements.length} [${elements.join(', ')}] )`); // debugging
|
||||
arr.splice(start, deleteCount, ...elements);
|
||||
}
|
||||
},
|
||||
updateElementHeight() { }
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -25,7 +26,7 @@ suite('ObjectTreeModel', function () {
|
|||
|
||||
test('ctor', () => {
|
||||
const list: ITreeNode<number>[] = [];
|
||||
const model = new ObjectTreeModel<number>('test', toSpliceable(list));
|
||||
const model = new ObjectTreeModel<number>('test', toList(list));
|
||||
assert(model);
|
||||
assert.equal(list.length, 0);
|
||||
assert.equal(model.size, 0);
|
||||
|
@ -33,7 +34,7 @@ suite('ObjectTreeModel', function () {
|
|||
|
||||
test('flat', () => {
|
||||
const list: ITreeNode<number>[] = [];
|
||||
const model = new ObjectTreeModel<number>('test', toSpliceable(list));
|
||||
const model = new ObjectTreeModel<number>('test', toList(list));
|
||||
|
||||
model.setChildren(null, [
|
||||
{ element: 0 },
|
||||
|
@ -60,7 +61,7 @@ suite('ObjectTreeModel', function () {
|
|||
|
||||
test('nested', () => {
|
||||
const list: ITreeNode<number>[] = [];
|
||||
const model = new ObjectTreeModel<number>('test', toSpliceable(list));
|
||||
const model = new ObjectTreeModel<number>('test', toList(list));
|
||||
|
||||
model.setChildren(null, [
|
||||
{
|
||||
|
@ -96,7 +97,7 @@ suite('ObjectTreeModel', function () {
|
|||
|
||||
test('setChildren on collapsed node', () => {
|
||||
const list: ITreeNode<number>[] = [];
|
||||
const model = new ObjectTreeModel<number>('test', toSpliceable(list));
|
||||
const model = new ObjectTreeModel<number>('test', toList(list));
|
||||
|
||||
model.setChildren(null, [
|
||||
{ element: 0, collapsed: true }
|
||||
|
@ -117,7 +118,7 @@ suite('ObjectTreeModel', function () {
|
|||
|
||||
test('setChildren on expanded, unrevealed node', () => {
|
||||
const list: ITreeNode<number>[] = [];
|
||||
const model = new ObjectTreeModel<number>('test', toSpliceable(list));
|
||||
const model = new ObjectTreeModel<number>('test', toList(list));
|
||||
|
||||
model.setChildren(null, [
|
||||
{
|
||||
|
@ -143,7 +144,7 @@ suite('ObjectTreeModel', function () {
|
|||
|
||||
test('collapse state is preserved with strict identity', () => {
|
||||
const list: ITreeNode<string>[] = [];
|
||||
const model = new ObjectTreeModel<string>('test', toSpliceable(list), { collapseByDefault: true });
|
||||
const model = new ObjectTreeModel<string>('test', toList(list), { collapseByDefault: true });
|
||||
const data = [{ element: 'father', children: [{ element: 'child' }] }];
|
||||
|
||||
model.setChildren(null, data);
|
||||
|
@ -173,7 +174,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>('test', toSpliceable(list), { sorter: { compare(a, b) { return compare(a, b); } } });
|
||||
const model = new ObjectTreeModel<string>('test', toList(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 +189,7 @@ suite('ObjectTreeModel', function () {
|
|||
let compare: (a: string, b: string) => number = () => 0;
|
||||
|
||||
const list: ITreeNode<string>[] = [];
|
||||
const model = new ObjectTreeModel<string>('test', toSpliceable(list), { sorter: { compare(a, b) { return compare(a, b); } } });
|
||||
const model = new ObjectTreeModel<string>('test', toList(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 +224,7 @@ suite('ObjectTreeModel', function () {
|
|||
|
||||
test('expandTo', () => {
|
||||
const list: ITreeNode<number>[] = [];
|
||||
const model = new ObjectTreeModel<number>('test', toSpliceable(list), { collapseByDefault: true });
|
||||
const model = new ObjectTreeModel<number>('test', toList(list), { collapseByDefault: true });
|
||||
|
||||
model.setChildren(null, [
|
||||
{
|
||||
|
@ -254,7 +255,7 @@ suite('ObjectTreeModel', function () {
|
|||
return fn(element) ? TreeVisibility.Visible : parentVisibility;
|
||||
}
|
||||
};
|
||||
const model = new ObjectTreeModel<string>('test', toSpliceable(list), { filter });
|
||||
const model = new ObjectTreeModel<string>('test', toList(list), { filter });
|
||||
|
||||
model.setChildren(null, [{ element: 'file', children: [{ element: 'hello' }] }]);
|
||||
assert.deepEqual(toArray(list), ['file', 'hello']);
|
||||
|
|
|
@ -22,6 +22,7 @@ import { regExpFlags } from 'vs/base/common/strings';
|
|||
import { isNonEmptyArray } from 'vs/base/common/arrays';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
import { StopWatch } from 'vs/base/common/stopwatch';
|
||||
import { canceled } from 'vs/base/common/errors';
|
||||
|
||||
/**
|
||||
* Stop syncing a model to the worker if it was not needed for 1 min.
|
||||
|
@ -380,6 +381,7 @@ export class EditorWorkerClient extends Disposable {
|
|||
private _worker: IWorkerClient<EditorSimpleWorker> | null;
|
||||
private readonly _workerFactory: DefaultWorkerFactory;
|
||||
private _modelManager: EditorModelManager | null;
|
||||
private _disposed = false;
|
||||
|
||||
constructor(modelService: IModelService, keepIdleModels: boolean, label: string | undefined) {
|
||||
super();
|
||||
|
@ -427,6 +429,9 @@ export class EditorWorkerClient extends Disposable {
|
|||
}
|
||||
|
||||
protected _withSyncedResources(resources: URI[]): Promise<EditorSimpleWorker> {
|
||||
if (this._disposed) {
|
||||
return Promise.reject(canceled());
|
||||
}
|
||||
return this._getProxy().then((proxy) => {
|
||||
this._getOrCreateModelManager(proxy).ensureSyncedResources(resources);
|
||||
return proxy;
|
||||
|
@ -495,4 +500,9 @@ export class EditorWorkerClient extends Disposable {
|
|||
return proxy.navigateValueSet(resource.toString(), range, up, wordDef, wordDefFlags);
|
||||
});
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
super.dispose();
|
||||
this._disposed = true;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -28,7 +28,6 @@ import { Emitter, Event } from 'vs/base/common/event';
|
|||
import { KeyCode } from 'vs/base/common/keyCodes';
|
||||
import { Disposable, DisposableStore, dispose } from 'vs/base/common/lifecycle';
|
||||
import { isIOS } from 'vs/base/common/platform';
|
||||
import { ISpliceable } from 'vs/base/common/sequence';
|
||||
import { escapeRegExpCharacters, startsWith } from 'vs/base/common/strings';
|
||||
import { isArray, isDefined, isUndefinedOrNull } from 'vs/base/common/types';
|
||||
import { localize } from 'vs/nls';
|
||||
|
@ -54,6 +53,7 @@ import { getInvalidTypeError } from 'vs/workbench/services/preferences/common/pr
|
|||
import { Codicon } from 'vs/base/common/codicons';
|
||||
import { CodiconLabel } from 'vs/base/browser/ui/codicons/codiconLabel';
|
||||
import { IJSONSchema } from 'vs/base/common/jsonSchema';
|
||||
import { IList } from 'vs/base/browser/ui/tree/indexTreeModel';
|
||||
|
||||
const $ = DOM.$;
|
||||
|
||||
|
@ -1849,7 +1849,7 @@ export class SettingsTree extends ObjectTree<SettingsTreeElement> {
|
|||
}));
|
||||
}
|
||||
|
||||
protected createModel(user: string, view: ISpliceable<ITreeNode<SettingsTreeGroupChild>>, options: IObjectTreeOptions<SettingsTreeGroupChild>): ITreeModel<SettingsTreeGroupChild | null, void, SettingsTreeGroupChild | null> {
|
||||
protected createModel(user: string, view: IList<ITreeNode<SettingsTreeGroupChild>>, options: IObjectTreeOptions<SettingsTreeGroupChild>): ITreeModel<SettingsTreeGroupChild | null, void, SettingsTreeGroupChild | null> {
|
||||
return new NonCollapsibleObjectTreeModel<SettingsTreeGroupChild>(user, view, options);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import 'vs/css!./media/scmViewlet';
|
||||
import 'vs/css!./media/scm';
|
||||
import { localize } from 'vs/nls';
|
||||
import { Event, Emitter } from 'vs/base/common/event';
|
||||
import { basename } from 'vs/base/common/resources';
|
||||
|
|
222
src/vs/workbench/contrib/scm/browser/media/scm.css
Normal file
222
src/vs/workbench/contrib/scm/browser/media/scm.css
Normal file
|
@ -0,0 +1,222 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
.scm-view {
|
||||
height: 100%;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.scm-view .monaco-tl-contents > div {
|
||||
margin-right: 12px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.scm-view .count {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.scm-view .count {
|
||||
margin-left: 6px;
|
||||
}
|
||||
|
||||
.scm-view .count.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.scm-view .scm-provider {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
align-items: center;
|
||||
flex-flow: nowrap;
|
||||
}
|
||||
|
||||
.scm-view .scm-provider > .label {
|
||||
display: flex;
|
||||
flex-shrink: 1;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.scm-view .scm-provider > .actions {
|
||||
flex: 1;
|
||||
padding-left: 10px;
|
||||
overflow: hidden;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.scm-view .scm-provider > .actions .monaco-action-bar .action-item {
|
||||
margin-left: 4px;
|
||||
text-overflow: ellipsis;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
min-width: 14px;
|
||||
}
|
||||
|
||||
.scm-view .scm-provider > .actions .monaco-action-bar .action-label {
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
min-width: 14px; /* for flex */
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.scm-view .scm-provider > .actions .monaco-action-bar .action-label .codicon {
|
||||
vertical-align: sub;
|
||||
display: inline-flex;
|
||||
}
|
||||
|
||||
.scm-view .scm-provider > .actions .monaco-action-bar .action-item:last-of-type {
|
||||
padding-right: 0;
|
||||
}
|
||||
|
||||
.scm-view .scm-provider > .body {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.scm-view .monaco-list-row {
|
||||
line-height: 22px;
|
||||
}
|
||||
|
||||
.scm-view .monaco-list-row .resource-group {
|
||||
display: flex;
|
||||
height: 100%;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.scm-view .monaco-list-row .resource-group > .name {
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.scm-view .monaco-list-row .resource {
|
||||
display: flex;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.scm-view .monaco-list-row .resource.faded {
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.scm-view .monaco-list-row .resource > .name {
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.scm-view .monaco-list-row .resource > .name > .monaco-icon-label::after {
|
||||
padding-right: 3px;
|
||||
}
|
||||
|
||||
.scm-view .monaco-list-row .resource > .name.strike-through > .monaco-icon-label > .monaco-icon-label-container > .monaco-icon-name-container > .label-name {
|
||||
text-decoration: line-through;
|
||||
}
|
||||
|
||||
.scm-view .monaco-list-row .resource > .decoration-icon {
|
||||
width: 16px;
|
||||
height: 100%;
|
||||
background-repeat: no-repeat;
|
||||
background-position: 50% 50%;
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.scm-view .monaco-list .monaco-list-row .resource > .name > .monaco-icon-label > .actions {
|
||||
flex-grow: 100;
|
||||
}
|
||||
|
||||
.scm-view .monaco-list .monaco-list-row .resource-group > .actions,
|
||||
.scm-view .monaco-list .monaco-list-row .resource > .name > .monaco-icon-label > .actions {
|
||||
display: none;
|
||||
max-width: fit-content;
|
||||
}
|
||||
|
||||
.scm-view .monaco-list .monaco-list-row:hover .resource-group > .actions,
|
||||
.scm-view .monaco-list .monaco-list-row.selected .resource-group > .actions,
|
||||
.scm-view .monaco-list .monaco-list-row.focused .resource-group > .actions,
|
||||
.scm-view .monaco-list .monaco-list-row:hover .resource > .name > .monaco-icon-label > .actions,
|
||||
.scm-view .monaco-list .monaco-list-row.selected .resource > .name > .monaco-icon-label > .actions,
|
||||
.scm-view .monaco-list .monaco-list-row.focused .resource > .name > .monaco-icon-label > .actions,
|
||||
.scm-view .monaco-list:not(.selection-multiple) .monaco-list-row .resource:hover > .actions {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.scm-view .scm-status.show-actions .scm-provider > .actions,
|
||||
.scm-view .scm-status.show-actions > .monaco-list .monaco-list-row .resource-group > .actions,
|
||||
.scm-view .scm-status.show-actions > .monaco-list .monaco-list-row .resource > .name > .monaco-icon-label > .actions {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.scm-view .monaco-list-row .resource > .name > .monaco-icon-label > .actions .action-label,
|
||||
.scm-view .monaco-list-row .resource-group > .actions .action-label {
|
||||
width: 16px;
|
||||
height: 100%;
|
||||
background-position: 50% 50%;
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
|
||||
.scm-view .monaco-list-row .resource > .name > .monaco-icon-label > .actions .action-label.codicon {
|
||||
line-height: 22px;
|
||||
}
|
||||
|
||||
.scm-view .scm-input {
|
||||
height: 100%;
|
||||
margin-left: 9px;
|
||||
}
|
||||
|
||||
.scm-view .scm-editor {
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.scm-view .scm-editor.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.scm-view .scm-editor-container {
|
||||
position: relative;
|
||||
box-sizing: border-box;
|
||||
padding: 1px;
|
||||
outline-offset: -1px;
|
||||
}
|
||||
|
||||
.scm-view .scm-editor-container > .scm-editor-validation {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
z-index: 10;
|
||||
left: 0px;
|
||||
box-sizing: border-box;
|
||||
font-size: 0.9em;
|
||||
padding: 1px 3px;
|
||||
display: none;
|
||||
top: calc(100%);
|
||||
}
|
||||
|
||||
.scm-view .scm-editor-container.synthetic-focus > .scm-editor-validation {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.scm-view .scm-editor-placeholder {
|
||||
position: absolute;
|
||||
pointer-events: none;
|
||||
z-index: 1;
|
||||
padding: 2px 4px 3px 4px;
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.scm-view .scm-editor-placeholder.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.scm-view .scm-editor-container .monaco-editor-background,
|
||||
.scm-view .scm-editor-container .monaco-editor,
|
||||
.scm-view .scm-editor-container .mtk1 {
|
||||
color: inherit;
|
||||
}
|
|
@ -1,215 +0,0 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
.monaco-workbench .viewlet.scm-viewlet .collapsible.header .actions {
|
||||
width: initial;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.scm-viewlet .scm-status {
|
||||
height: 100%;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.scm-viewlet .monaco-list-row > .scm-provider {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
height: 100%;
|
||||
padding: 0 12px 0 20px;
|
||||
}
|
||||
|
||||
.scm-viewlet .monaco-list-row > .scm-provider > .monaco-action-bar {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.scm-viewlet .monaco-list-row > .scm-provider > .monaco-action-bar .action-item {
|
||||
padding: 0 4px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
min-width: 14px;
|
||||
}
|
||||
|
||||
.scm-viewlet .monaco-list-row > .scm-provider > .monaco-action-bar .action-label {
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
min-width: 14px; /* minimum size of icons */
|
||||
}
|
||||
|
||||
.scm-viewlet .monaco-list-row > .scm-provider > .monaco-action-bar .action-label .codicon {
|
||||
font-size: 14px;
|
||||
vertical-align: sub;
|
||||
display: inline-flex;
|
||||
}
|
||||
|
||||
.scm-viewlet .monaco-list-row > .scm-provider > .monaco-action-bar .action-item:last-of-type {
|
||||
padding-right: 0;
|
||||
}
|
||||
|
||||
.scm-viewlet .scm-provider > .name,
|
||||
.scm-viewlet .scm-provider > .count {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.scm-viewlet .scm-provider > .count {
|
||||
margin: 0 0.5em;
|
||||
}
|
||||
|
||||
.scm-viewlet .scm-provider > .count.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.scm-viewlet .scm-provider > .type,
|
||||
.scm-viewlet .scm-provider > .name > .type {
|
||||
opacity: 0.7;
|
||||
margin-left: 0.5em;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
.scm-viewlet .monaco-list-row {
|
||||
line-height: 22px;
|
||||
}
|
||||
|
||||
.scm-viewlet .monaco-list-row .resource-group {
|
||||
display: flex;
|
||||
height: 100%;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.scm-viewlet .monaco-list-row .resource-group > .name {
|
||||
flex: 1;
|
||||
font-size: 11px;
|
||||
font-weight: bold;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.scm-viewlet .monaco-list-row .resource {
|
||||
display: flex;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.scm-viewlet .monaco-list-row .resource.faded {
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.scm-viewlet .monaco-list-row .resource > .name {
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.scm-viewlet .monaco-list-row .resource > .name.strike-through > .monaco-icon-label > .monaco-icon-label-container > .monaco-icon-name-container > .label-name {
|
||||
text-decoration: line-through;
|
||||
}
|
||||
|
||||
.scm-viewlet .monaco-list-row .resource-group > .count {
|
||||
padding: 0 12px 0 8px;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.scm-viewlet .monaco-list-row .resource > .decoration-icon {
|
||||
width: 16px;
|
||||
height: 100%;
|
||||
background-repeat: no-repeat;
|
||||
background-position: 50% 50%;
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.scm-viewlet .monaco-list .monaco-list-row .resource > .name > .monaco-icon-label > .actions {
|
||||
flex-grow: 100;
|
||||
}
|
||||
|
||||
.scm-viewlet .monaco-list .monaco-list-row .resource-group > .actions,
|
||||
.scm-viewlet .monaco-list .monaco-list-row .resource > .name > .monaco-icon-label > .actions {
|
||||
display: none;
|
||||
max-width: fit-content;
|
||||
}
|
||||
|
||||
.scm-viewlet .monaco-list .monaco-list-row:hover .resource-group > .actions,
|
||||
.scm-viewlet .monaco-list .monaco-list-row:hover .resource > .name > .monaco-icon-label > .actions,
|
||||
.scm-viewlet .monaco-list .monaco-list-row.selected .resource-group > .actions,
|
||||
.scm-viewlet .monaco-list .monaco-list-row.focused .resource-group > .actions,
|
||||
.scm-viewlet .monaco-list .monaco-list-row.selected .resource > .name > .monaco-icon-label > .actions,
|
||||
.scm-viewlet .monaco-list .monaco-list-row.focused .resource > .name > .monaco-icon-label > .actions,
|
||||
.scm-viewlet .monaco-list:not(.selection-multiple) .monaco-list-row .resource:hover > .actions {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.scm-viewlet .scm-status.show-actions > .monaco-list .monaco-list-row .resource-group > .actions,
|
||||
.scm-viewlet .scm-status.show-actions > .monaco-list .monaco-list-row .resource > .name > .monaco-icon-label > .actions {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.scm-viewlet .monaco-list-row .resource > .name > .monaco-icon-label > .actions .action-label,
|
||||
.scm-viewlet .monaco-list-row .resource-group > .actions .action-label {
|
||||
width: 16px;
|
||||
height: 100%;
|
||||
background-position: 50% 50%;
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
|
||||
.scm-viewlet .monaco-list-row .resource > .name > .monaco-icon-label > .actions .action-label.codicon {
|
||||
line-height: 22px;
|
||||
}
|
||||
|
||||
.scm-viewlet .scm-editor {
|
||||
box-sizing: border-box;
|
||||
padding: 5px 12px 5px 16px;
|
||||
}
|
||||
|
||||
.scm-viewlet .scm-editor.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.scm-viewlet .scm-editor-container {
|
||||
padding: 1px;
|
||||
position: relative;
|
||||
outline-offset: -1px;
|
||||
}
|
||||
|
||||
.scm-viewlet .scm-editor-container > .scm-editor-validation {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
z-index: 10;
|
||||
left: 0px;
|
||||
box-sizing: border-box;
|
||||
font-size: 0.9em;
|
||||
padding: 1px 3px;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.scm-viewlet .scm-editor-container.synthetic-focus > .scm-editor-validation {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.scm-viewlet .scm-editor-placeholder {
|
||||
position: absolute;
|
||||
pointer-events: none;
|
||||
z-index: 1;
|
||||
margin: 1px;
|
||||
padding: 3px 4px;
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.scm-viewlet .scm-editor-placeholder.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.scm-viewlet .scm-editor-container .monaco-editor-background,
|
||||
.scm-viewlet .scm-editor-container .monaco-editor,
|
||||
.scm-viewlet .scm-editor-container .mtk1 {
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
.scm-viewlet .scm-status.show-file-icons.hide-arrows.tree-view-mode .monaco-tl-indent .indent-guide:first-child {
|
||||
border: none;
|
||||
}
|
|
@ -3,18 +3,21 @@
|
|||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import 'vs/css!./media/scmViewlet';
|
||||
import 'vs/css!./media/scm';
|
||||
import { Event, Emitter } from 'vs/base/common/event';
|
||||
import { IDisposable, Disposable, DisposableStore, combinedDisposable } from 'vs/base/common/lifecycle';
|
||||
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { IMenuService, MenuId, IMenu } from 'vs/platform/actions/common/actions';
|
||||
import { IAction } from 'vs/base/common/actions';
|
||||
import { IAction, Action } from 'vs/base/common/actions';
|
||||
import { createAndFillInContextMenuActions, createAndFillInActionBarActions } from 'vs/platform/actions/browser/menuEntryActionViewItem';
|
||||
import { ISCMProvider, ISCMResource, ISCMResourceGroup } from 'vs/workbench/contrib/scm/common/scm';
|
||||
import { ISCMResource, ISCMResourceGroup, ISCMProvider, ISCMRepository } from 'vs/workbench/contrib/scm/common/scm';
|
||||
import { isSCMResource } from './util';
|
||||
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
|
||||
import { equals } from 'vs/base/common/arrays';
|
||||
import { ISplice } from 'vs/base/common/sequence';
|
||||
import { ISplice, ISequence } from 'vs/base/common/sequence';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { localize } from 'vs/nls';
|
||||
import { ICommandService } from 'vs/platform/commands/common/commands';
|
||||
|
||||
function actionEquals(a: IAction, b: IAction): boolean {
|
||||
return a.id === b.id;
|
||||
|
@ -35,11 +38,11 @@ export function getSCMResourceContextKey(resource: ISCMResourceGroup | ISCMResou
|
|||
return isSCMResource(resource) ? resource.resourceGroup.id : resource.id;
|
||||
}
|
||||
|
||||
export class SCMMenus implements IDisposable {
|
||||
export class SCMRepositoryMenus implements IDisposable {
|
||||
|
||||
private contextKeyService: IContextKeyService;
|
||||
private titleMenu: IMenu;
|
||||
|
||||
readonly titleMenu: IMenu;
|
||||
private titleActionDisposable: IDisposable = Disposable.None;
|
||||
private titleActions: IAction[] = [];
|
||||
private titleSecondaryActions: IAction[] = [];
|
||||
|
@ -53,8 +56,9 @@ export class SCMMenus implements IDisposable {
|
|||
private readonly disposables = new DisposableStore();
|
||||
|
||||
constructor(
|
||||
provider: ISCMProvider | undefined,
|
||||
readonly provider: ISCMProvider | undefined,
|
||||
@IContextKeyService contextKeyService: IContextKeyService,
|
||||
@ICommandService private readonly commandService: ICommandService,
|
||||
@IMenuService private readonly menuService: IMenuService,
|
||||
@IContextMenuService private readonly contextMenuService: IContextMenuService
|
||||
) {
|
||||
|
@ -63,15 +67,14 @@ export class SCMMenus implements IDisposable {
|
|||
|
||||
if (provider) {
|
||||
scmProviderKey.set(provider.contextValue);
|
||||
this.onDidSpliceGroups({ start: 0, deleteCount: 0, toInsert: provider.groups.elements });
|
||||
provider.groups.onDidSplice(this.onDidSpliceGroups, this, this.disposables);
|
||||
this.onDidSpliceGroups({ start: 0, deleteCount: 0, toInsert: provider.groups.elements });
|
||||
} else {
|
||||
scmProviderKey.set('');
|
||||
}
|
||||
|
||||
this.titleMenu = this.menuService.createMenu(MenuId.SCMTitle, this.contextKeyService);
|
||||
this.disposables.add(this.titleMenu);
|
||||
|
||||
this.titleMenu.onDidChange(this.updateTitleActions, this, this.disposables);
|
||||
this.updateTitleActions();
|
||||
}
|
||||
|
@ -103,6 +106,34 @@ export class SCMMenus implements IDisposable {
|
|||
return this.titleSecondaryActions;
|
||||
}
|
||||
|
||||
getRepositoryContextActions(): IAction[] {
|
||||
if (!this.provider) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const contextKeyService = this.contextKeyService.createScoped();
|
||||
const scmProviderKey = contextKeyService.createKey<string | undefined>('scmProvider', undefined);
|
||||
scmProviderKey.set(this.provider.contextValue);
|
||||
|
||||
const menu = this.menuService.createMenu(MenuId.SCMSourceControl, contextKeyService);
|
||||
const primary: IAction[] = [];
|
||||
const secondary: IAction[] = [];
|
||||
const result = { primary, secondary };
|
||||
const disposable = createAndFillInContextMenuActions(menu, { shouldForwardArgs: true }, result, this.contextMenuService, g => g === 'inline');
|
||||
|
||||
disposable.dispose();
|
||||
menu.dispose();
|
||||
contextKeyService.dispose();
|
||||
|
||||
if (this.provider.rootUri) {
|
||||
secondary.push(new Action('_openInTerminal', localize('open in terminal', "Open In Terminal"), undefined, true, async () => {
|
||||
await this.commandService.executeCommand('openInTerminal', this.provider!.rootUri);
|
||||
}));
|
||||
}
|
||||
|
||||
return secondary;
|
||||
}
|
||||
|
||||
getResourceGroupContextActions(group: ISCMResourceGroup): IAction[] {
|
||||
return this.getActions(MenuId.SCMResourceGroupContext, group).secondary;
|
||||
}
|
||||
|
@ -183,3 +214,49 @@ export class SCMMenus implements IDisposable {
|
|||
this.resourceGroupMenuEntries.forEach(e => e.disposable.dispose());
|
||||
}
|
||||
}
|
||||
|
||||
export class SCMMenus {
|
||||
|
||||
private readonly disposables = new DisposableStore();
|
||||
private readonly entries: { repository: ISCMRepository, dispose: () => void }[] = [];
|
||||
private readonly menus = new Map<ISCMProvider, SCMRepositoryMenus>();
|
||||
|
||||
constructor(
|
||||
repositories: ISequence<ISCMRepository>,
|
||||
@IInstantiationService private instantiationService: IInstantiationService
|
||||
) {
|
||||
repositories.onDidSplice(this.onDidSplice, this, this.disposables);
|
||||
this.onDidSplice({ start: 0, deleteCount: 0, toInsert: repositories.elements });
|
||||
}
|
||||
|
||||
getRepositoryMenus(provider: ISCMProvider): SCMRepositoryMenus {
|
||||
if (!this.menus.has(provider)) {
|
||||
throw new Error('SCM Repository menu not found');
|
||||
}
|
||||
|
||||
return this.menus.get(provider)!;
|
||||
}
|
||||
|
||||
private onDidSplice({ start, deleteCount, toInsert }: ISplice<ISCMRepository>): void {
|
||||
const entriesToInsert = toInsert.map(repository => {
|
||||
const menus = this.instantiationService.createInstance(SCMRepositoryMenus, repository.provider);
|
||||
const dispose = () => {
|
||||
menus.dispose();
|
||||
this.menus.delete(repository.provider);
|
||||
};
|
||||
|
||||
this.menus.set(repository.provider, menus);
|
||||
return { repository, dispose };
|
||||
});
|
||||
|
||||
const deletedEntries = this.entries.splice(start, deleteCount, ...entriesToInsert);
|
||||
|
||||
for (const entry of deletedEntries) {
|
||||
entry.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
this.disposables.dispose();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,7 +8,7 @@ import { Registry } from 'vs/platform/registry/common/platform';
|
|||
import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions';
|
||||
import { DirtyDiffWorkbenchController } from './dirtydiffDecorator';
|
||||
import { ShowViewletAction } from 'vs/workbench/browser/viewlet';
|
||||
import { VIEWLET_ID, ISCMRepository, ISCMService } from 'vs/workbench/contrib/scm/common/scm';
|
||||
import { VIEWLET_ID, ISCMRepository, ISCMService, VIEW_PANE_ID } from 'vs/workbench/contrib/scm/common/scm';
|
||||
import { IWorkbenchActionRegistry, Extensions as WorkbenchActionExtensions } from 'vs/workbench/common/actions';
|
||||
import { KeyMod, KeyCode } from 'vs/base/common/keyCodes';
|
||||
import { SyncActionDescriptor, MenuRegistry, MenuId } from 'vs/platform/actions/common/actions';
|
||||
|
@ -23,11 +23,12 @@ import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/co
|
|||
import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService';
|
||||
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
|
||||
import { SCMService } from 'vs/workbench/contrib/scm/common/scmService';
|
||||
import { IViewContainersRegistry, ViewContainerLocation, Extensions as ViewContainerExtensions } from 'vs/workbench/common/views';
|
||||
import { SCMViewPaneContainer } from 'vs/workbench/contrib/scm/browser/scmViewlet';
|
||||
import { IViewContainersRegistry, ViewContainerLocation, Extensions as ViewContainerExtensions, IViewsRegistry } from 'vs/workbench/common/views';
|
||||
import { SCMViewPaneContainer } from 'vs/workbench/contrib/scm/browser/scmViewPaneContainer';
|
||||
import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors';
|
||||
import { ModesRegistry } from 'vs/editor/common/modes/modesRegistry';
|
||||
import { Codicon } from 'vs/base/common/codicons';
|
||||
import { SCMViewPane } from 'vs/workbench/contrib/scm/browser/scmViewPane';
|
||||
|
||||
class OpenSCMViewletAction extends ShowViewletAction {
|
||||
|
||||
|
@ -48,7 +49,7 @@ ModesRegistry.registerLanguage({
|
|||
Registry.as<IWorkbenchContributionsRegistry>(WorkbenchExtensions.Workbench)
|
||||
.registerWorkbenchContribution(DirtyDiffWorkbenchController, LifecyclePhase.Restored);
|
||||
|
||||
Registry.as<IViewContainersRegistry>(ViewContainerExtensions.ViewContainersRegistry).registerViewContainer({
|
||||
const viewContainer = Registry.as<IViewContainersRegistry>(ViewContainerExtensions.ViewContainersRegistry).registerViewContainer({
|
||||
id: VIEWLET_ID,
|
||||
name: localize('source control', "Source Control"),
|
||||
ctorDescriptor: new SyncDescriptor(SCMViewPaneContainer),
|
||||
|
@ -58,6 +59,22 @@ Registry.as<IViewContainersRegistry>(ViewContainerExtensions.ViewContainersRegis
|
|||
order: 2
|
||||
}, ViewContainerLocation.Sidebar);
|
||||
|
||||
const viewsRegistry = Registry.as<IViewsRegistry>(ViewContainerExtensions.ViewsRegistry);
|
||||
|
||||
viewsRegistry.registerViewWelcomeContent(VIEW_PANE_ID, {
|
||||
content: localize('no open repo', "No source control providers registered."),
|
||||
when: 'default'
|
||||
});
|
||||
|
||||
viewsRegistry.registerViews([{
|
||||
id: VIEW_PANE_ID,
|
||||
name: localize('source control', "Source Control"),
|
||||
ctorDescriptor: new SyncDescriptor(SCMViewPane),
|
||||
canToggleVisibility: true,
|
||||
workspace: true,
|
||||
canMoveView: true
|
||||
}], viewContainer);
|
||||
|
||||
Registry.as<IWorkbenchContributionsRegistry>(WorkbenchExtensions.Workbench)
|
||||
.registerWorkbenchContribution(SCMStatusController, LifecyclePhase.Restored);
|
||||
|
||||
|
@ -80,11 +97,6 @@ Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Configuration).regis
|
|||
type: 'object',
|
||||
scope: ConfigurationScope.RESOURCE,
|
||||
properties: {
|
||||
'scm.alwaysShowProviders': {
|
||||
type: 'boolean',
|
||||
description: localize('alwaysShowProviders', "Controls whether to show the Source Control Provider section even when there's only one Provider registered."),
|
||||
default: false
|
||||
},
|
||||
'scm.providers.visible': {
|
||||
type: 'number',
|
||||
description: localize('providersVisible', "Controls how many providers are visible in the Source Control Provider section. Set to `0` to be able to manually resize the view."),
|
||||
|
|
File diff suppressed because it is too large
Load diff
64
src/vs/workbench/contrib/scm/browser/scmViewPaneContainer.ts
Normal file
64
src/vs/workbench/contrib/scm/browser/scmViewPaneContainer.ts
Normal file
|
@ -0,0 +1,64 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import 'vs/css!./media/scm';
|
||||
import { localize } from 'vs/nls';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { VIEWLET_ID, ISCMService } from 'vs/workbench/contrib/scm/common/scm';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IContextViewService, IContextMenuService } from 'vs/platform/contextview/browser/contextView';
|
||||
import { ICommandService } from 'vs/platform/commands/common/commands';
|
||||
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
|
||||
import { SCMRepositoryMenus } from './menus';
|
||||
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
import { IStorageService } from 'vs/platform/storage/common/storage';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { INotificationService } from 'vs/platform/notification/common/notification';
|
||||
import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService';
|
||||
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
|
||||
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
|
||||
import { IViewDescriptorService } from 'vs/workbench/common/views';
|
||||
import { ViewPaneContainer } from 'vs/workbench/browser/parts/views/viewPaneContainer';
|
||||
import { addClass } from 'vs/base/browser/dom';
|
||||
|
||||
export class SCMViewPaneContainer extends ViewPaneContainer {
|
||||
|
||||
constructor(
|
||||
@IWorkbenchLayoutService layoutService: IWorkbenchLayoutService,
|
||||
@ITelemetryService telemetryService: ITelemetryService,
|
||||
@ISCMService protected scmService: ISCMService,
|
||||
@IInstantiationService protected instantiationService: IInstantiationService,
|
||||
@IContextViewService protected contextViewService: IContextViewService,
|
||||
@IKeybindingService protected keybindingService: IKeybindingService,
|
||||
@INotificationService protected notificationService: INotificationService,
|
||||
@IContextMenuService protected contextMenuService: IContextMenuService,
|
||||
@IThemeService protected themeService: IThemeService,
|
||||
@ICommandService protected commandService: ICommandService,
|
||||
@IStorageService storageService: IStorageService,
|
||||
@IConfigurationService configurationService: IConfigurationService,
|
||||
@IExtensionService extensionService: IExtensionService,
|
||||
@IWorkspaceContextService protected contextService: IWorkspaceContextService,
|
||||
@IViewDescriptorService viewDescriptorService: IViewDescriptorService
|
||||
) {
|
||||
super(VIEWLET_ID, { mergeViewWithContainerWhenSingleView: true }, instantiationService, configurationService, layoutService, contextMenuService, telemetryService, extensionService, themeService, storageService, contextService, viewDescriptorService);
|
||||
|
||||
const menus = instantiationService.createInstance(SCMRepositoryMenus, undefined);
|
||||
this._register(menus);
|
||||
this._register(menus.onDidChangeTitle(this.updateTitleArea, this));
|
||||
}
|
||||
|
||||
create(parent: HTMLElement): void {
|
||||
super.create(parent);
|
||||
addClass(parent, 'scm-viewlet');
|
||||
}
|
||||
|
||||
getOptimalWidth(): number {
|
||||
return 400;
|
||||
}
|
||||
|
||||
getTitle(): string {
|
||||
return localize('source control', "Source Control");
|
||||
}
|
||||
}
|
|
@ -1,313 +0,0 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import 'vs/css!./media/scmViewlet';
|
||||
import { localize } from 'vs/nls';
|
||||
import { Event, Emitter } from 'vs/base/common/event';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { VIEWLET_ID, ISCMService, ISCMRepository } from 'vs/workbench/contrib/scm/common/scm';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IContextViewService, IContextMenuService } from 'vs/platform/contextview/browser/contextView';
|
||||
import { IContextKeyService, IContextKey, ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { ICommandService } from 'vs/platform/commands/common/commands';
|
||||
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
|
||||
import { MenuItemAction } from 'vs/platform/actions/common/actions';
|
||||
import { IAction, IActionViewItem } from 'vs/base/common/actions';
|
||||
import { ContextAwareMenuEntryActionViewItem } from 'vs/platform/actions/browser/menuEntryActionViewItem';
|
||||
import { SCMMenus } from './menus';
|
||||
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
import { IStorageService } from 'vs/platform/storage/common/storage';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { INotificationService } from 'vs/platform/notification/common/notification';
|
||||
import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService';
|
||||
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
|
||||
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
|
||||
import { IViewsRegistry, Extensions, IViewDescriptorService, IViewDescriptor, IAddedViewDescriptorRef, IViewDescriptorRef } from 'vs/workbench/common/views';
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { RepositoryPane, RepositoryViewDescriptor } from 'vs/workbench/contrib/scm/browser/repositoryPane';
|
||||
import { MainPaneDescriptor, MainPane, IViewModel } from 'vs/workbench/contrib/scm/browser/mainPane';
|
||||
import { ViewPaneContainer, IViewPaneOptions, ViewPane } from 'vs/workbench/browser/parts/views/viewPaneContainer';
|
||||
import { debounce } from 'vs/base/common/decorators';
|
||||
import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors';
|
||||
import { IOpenerService } from 'vs/platform/opener/common/opener';
|
||||
import { addClass } from 'vs/base/browser/dom';
|
||||
import { Codicon } from 'vs/base/common/codicons';
|
||||
|
||||
export interface ISpliceEvent<T> {
|
||||
index: number;
|
||||
deleteCount: number;
|
||||
elements: T[];
|
||||
}
|
||||
|
||||
export class EmptyPane extends ViewPane {
|
||||
|
||||
static readonly ID = 'workbench.scm';
|
||||
static readonly TITLE = localize('scm', "Source Control");
|
||||
|
||||
constructor(
|
||||
options: IViewPaneOptions,
|
||||
@IKeybindingService keybindingService: IKeybindingService,
|
||||
@IContextMenuService contextMenuService: IContextMenuService,
|
||||
@IConfigurationService configurationService: IConfigurationService,
|
||||
@IContextKeyService contextKeyService: IContextKeyService,
|
||||
@IViewDescriptorService viewDescriptorService: IViewDescriptorService,
|
||||
@IInstantiationService instantiationService: IInstantiationService,
|
||||
@IOpenerService openerService: IOpenerService,
|
||||
@IThemeService themeService: IThemeService,
|
||||
@ITelemetryService telemetryService: ITelemetryService,
|
||||
) {
|
||||
super(options, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, telemetryService);
|
||||
}
|
||||
|
||||
shouldShowWelcome(): boolean {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
export class EmptyPaneDescriptor implements IViewDescriptor {
|
||||
readonly id = EmptyPane.ID;
|
||||
readonly name = EmptyPane.TITLE;
|
||||
readonly containerIcon = Codicon.sourceControl.classNames;
|
||||
readonly ctorDescriptor = new SyncDescriptor(EmptyPane);
|
||||
readonly canToggleVisibility = true;
|
||||
readonly hideByDefault = false;
|
||||
readonly order = -1000;
|
||||
readonly workspace = true;
|
||||
readonly when = ContextKeyExpr.equals('scm.providerCount', 0);
|
||||
}
|
||||
|
||||
export class SCMViewPaneContainer extends ViewPaneContainer implements IViewModel {
|
||||
|
||||
private menus: SCMMenus;
|
||||
private _repositories: ISCMRepository[] = [];
|
||||
|
||||
private repositoryCountKey: IContextKey<number>;
|
||||
private viewDescriptors: RepositoryViewDescriptor[] = [];
|
||||
|
||||
private readonly _onDidSplice = new Emitter<ISpliceEvent<ISCMRepository>>();
|
||||
readonly onDidSplice: Event<ISpliceEvent<ISCMRepository>> = this._onDidSplice.event;
|
||||
|
||||
private _height: number | undefined = undefined;
|
||||
get height(): number | undefined { return this._height; }
|
||||
|
||||
get repositories(): ISCMRepository[] {
|
||||
return this._repositories;
|
||||
}
|
||||
|
||||
get visibleRepositories(): ISCMRepository[] {
|
||||
return this.panes.filter(pane => pane instanceof RepositoryPane)
|
||||
.map(pane => (pane as RepositoryPane).repository);
|
||||
}
|
||||
|
||||
get onDidChangeVisibleRepositories(): Event<ISCMRepository[]> {
|
||||
const modificationEvent = Event.debounce(Event.any(this.viewContainerModel.onDidAddVisibleViewDescriptors, this.viewContainerModel.onDidRemoveVisibleViewDescriptors), () => null, 0);
|
||||
return Event.map(modificationEvent, () => this.visibleRepositories);
|
||||
}
|
||||
|
||||
constructor(
|
||||
@IWorkbenchLayoutService layoutService: IWorkbenchLayoutService,
|
||||
@ITelemetryService telemetryService: ITelemetryService,
|
||||
@ISCMService protected scmService: ISCMService,
|
||||
@IInstantiationService protected instantiationService: IInstantiationService,
|
||||
@IContextViewService protected contextViewService: IContextViewService,
|
||||
@IKeybindingService protected keybindingService: IKeybindingService,
|
||||
@INotificationService protected notificationService: INotificationService,
|
||||
@IContextMenuService protected contextMenuService: IContextMenuService,
|
||||
@IThemeService protected themeService: IThemeService,
|
||||
@ICommandService protected commandService: ICommandService,
|
||||
@IStorageService storageService: IStorageService,
|
||||
@IConfigurationService configurationService: IConfigurationService,
|
||||
@IExtensionService extensionService: IExtensionService,
|
||||
@IWorkspaceContextService protected contextService: IWorkspaceContextService,
|
||||
@IContextKeyService contextKeyService: IContextKeyService,
|
||||
@IViewDescriptorService viewDescriptorService: IViewDescriptorService
|
||||
) {
|
||||
super(VIEWLET_ID, { mergeViewWithContainerWhenSingleView: true }, instantiationService, configurationService, layoutService, contextMenuService, telemetryService, extensionService, themeService, storageService, contextService, viewDescriptorService);
|
||||
|
||||
this.menus = instantiationService.createInstance(SCMMenus, undefined);
|
||||
this._register(this.menus.onDidChangeTitle(this.updateTitleArea, this));
|
||||
|
||||
const viewsRegistry = Registry.as<IViewsRegistry>(Extensions.ViewsRegistry);
|
||||
|
||||
viewsRegistry.registerViewWelcomeContent(EmptyPane.ID, {
|
||||
content: localize('no open repo', "No source control providers registered."),
|
||||
when: 'default'
|
||||
});
|
||||
|
||||
viewsRegistry.registerViews([new EmptyPaneDescriptor()], this.viewContainer);
|
||||
viewsRegistry.registerViews([new MainPaneDescriptor(this)], this.viewContainer);
|
||||
|
||||
this._register(configurationService.onDidChangeConfiguration(e => {
|
||||
if (e.affectsConfiguration('scm.alwaysShowProviders') && configurationService.getValue<boolean>('scm.alwaysShowProviders')) {
|
||||
this.viewContainerModel.setVisible(MainPane.ID, true);
|
||||
}
|
||||
}));
|
||||
|
||||
this.repositoryCountKey = contextKeyService.createKey('scm.providerCount', 0);
|
||||
|
||||
this._register(this.viewContainerModel.onDidAddVisibleViewDescriptors(this.onDidShowView, this));
|
||||
this._register(this.viewContainerModel.onDidRemoveVisibleViewDescriptors(this.onDidHideView, this));
|
||||
}
|
||||
|
||||
create(parent: HTMLElement): void {
|
||||
super.create(parent);
|
||||
addClass(parent, 'scm-viewlet');
|
||||
this._register(this.scmService.onDidAddRepository(this.onDidAddRepository, this));
|
||||
this._register(this.scmService.onDidRemoveRepository(this.onDidRemoveRepository, this));
|
||||
this.scmService.repositories.forEach(r => this.onDidAddRepository(r));
|
||||
}
|
||||
|
||||
private onDidAddRepository(repository: ISCMRepository): void {
|
||||
const index = this._repositories.length;
|
||||
this._repositories.push(repository);
|
||||
|
||||
const viewDescriptor = new RepositoryViewDescriptor(repository, false);
|
||||
Registry.as<IViewsRegistry>(Extensions.ViewsRegistry).registerViews([viewDescriptor], this.viewContainer);
|
||||
this.viewDescriptors.push(viewDescriptor);
|
||||
|
||||
this._onDidSplice.fire({ index, deleteCount: 0, elements: [repository] });
|
||||
this.updateTitleArea();
|
||||
|
||||
this.onDidChangeRepositories();
|
||||
}
|
||||
|
||||
private onDidRemoveRepository(repository: ISCMRepository): void {
|
||||
const index = this._repositories.indexOf(repository);
|
||||
|
||||
if (index === -1) {
|
||||
return;
|
||||
}
|
||||
|
||||
Registry.as<IViewsRegistry>(Extensions.ViewsRegistry).deregisterViews([this.viewDescriptors[index]], this.viewContainer);
|
||||
|
||||
this._repositories.splice(index, 1);
|
||||
this.viewDescriptors.splice(index, 1);
|
||||
|
||||
this._onDidSplice.fire({ index, deleteCount: 1, elements: [] });
|
||||
this.updateTitleArea();
|
||||
|
||||
this.onDidChangeRepositories();
|
||||
}
|
||||
|
||||
private onDidChangeRepositories(): void {
|
||||
this.repositoryCountKey.set(this.repositories.length);
|
||||
}
|
||||
|
||||
private onDidShowView(e: IAddedViewDescriptorRef[]): void {
|
||||
for (const ref of e) {
|
||||
if (ref.viewDescriptor instanceof RepositoryViewDescriptor) {
|
||||
ref.viewDescriptor.repository.setSelected(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private onDidHideView(e: IViewDescriptorRef[]): void {
|
||||
for (const ref of e) {
|
||||
if (ref.viewDescriptor instanceof RepositoryViewDescriptor) {
|
||||
ref.viewDescriptor.repository.setSelected(false);
|
||||
}
|
||||
}
|
||||
|
||||
this.afterOnDidHideView();
|
||||
}
|
||||
|
||||
@debounce(0)
|
||||
private afterOnDidHideView(): void {
|
||||
if (this.repositoryCountKey.get()! > 0 && this.viewDescriptors.every(d => !this.viewContainerModel.isVisible(d.id))) {
|
||||
this.viewContainerModel.setVisible(this.viewDescriptors[0].id, true);
|
||||
}
|
||||
}
|
||||
|
||||
focus(): void {
|
||||
const repository = this.visibleRepositories[0];
|
||||
|
||||
if (repository) {
|
||||
const pane = this.panes
|
||||
.filter(pane => pane instanceof RepositoryPane && pane.repository === repository)[0] as RepositoryPane | undefined;
|
||||
|
||||
if (pane) {
|
||||
pane.focus();
|
||||
} else {
|
||||
super.focus();
|
||||
}
|
||||
} else {
|
||||
super.focus();
|
||||
}
|
||||
}
|
||||
|
||||
getOptimalWidth(): number {
|
||||
return 400;
|
||||
}
|
||||
|
||||
getTitle(): string {
|
||||
const title = localize('source control', "Source Control");
|
||||
|
||||
if (this.visibleRepositories.length === 1) {
|
||||
const [repository] = this.repositories;
|
||||
return localize('viewletTitle', "{0}: {1}", title, repository.provider.label);
|
||||
} else {
|
||||
return title;
|
||||
}
|
||||
}
|
||||
|
||||
getActionViewItem(action: IAction): IActionViewItem | undefined {
|
||||
if (!(action instanceof MenuItemAction)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return new ContextAwareMenuEntryActionViewItem(action, this.keybindingService, this.notificationService, this.contextMenuService);
|
||||
}
|
||||
|
||||
getActions(): IAction[] {
|
||||
if (this.repositories.length > 0) {
|
||||
return super.getActions();
|
||||
}
|
||||
|
||||
return this.menus.getTitleActions();
|
||||
}
|
||||
|
||||
getSecondaryActions(): IAction[] {
|
||||
if (this.repositories.length > 0) {
|
||||
return super.getSecondaryActions();
|
||||
}
|
||||
|
||||
return this.menus.getTitleSecondaryActions();
|
||||
}
|
||||
|
||||
getActionsContext(): any {
|
||||
if (this.visibleRepositories.length === 1) {
|
||||
return this.repositories[0].provider;
|
||||
}
|
||||
}
|
||||
|
||||
setVisibleRepositories(repositories: ISCMRepository[]): void {
|
||||
const visibleViewDescriptors = this.viewContainerModel.visibleViewDescriptors;
|
||||
|
||||
const toSetVisible = this.viewContainerModel.activeViewDescriptors
|
||||
.filter((d): d is RepositoryViewDescriptor => d instanceof RepositoryViewDescriptor && repositories.indexOf(d.repository) > -1 && visibleViewDescriptors.indexOf(d) === -1);
|
||||
|
||||
const toSetInvisible = visibleViewDescriptors
|
||||
.filter((d): d is RepositoryViewDescriptor => d instanceof RepositoryViewDescriptor && repositories.indexOf(d.repository) === -1);
|
||||
|
||||
let size: number | undefined;
|
||||
const oneToOne = toSetVisible.length === 1 && toSetInvisible.length === 1;
|
||||
|
||||
for (const viewDescriptor of toSetInvisible) {
|
||||
if (oneToOne) {
|
||||
const pane = this.panes.filter(pane => pane.id === viewDescriptor.id)[0];
|
||||
|
||||
if (pane) {
|
||||
size = this.getPaneSize(pane);
|
||||
}
|
||||
}
|
||||
|
||||
this.viewContainerModel.setVisible(viewDescriptor.id, false);
|
||||
}
|
||||
|
||||
for (const viewDescriptor of toSetVisible) {
|
||||
this.viewContainerModel.setVisible(viewDescriptor.id, true, size);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -3,7 +3,7 @@
|
|||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { ISCMResource, ISCMRepository, ISCMResourceGroup } from 'vs/workbench/contrib/scm/common/scm';
|
||||
import { ISCMResource, ISCMRepository, ISCMResourceGroup, ISCMInput } from 'vs/workbench/contrib/scm/common/scm';
|
||||
import { IMenu } from 'vs/platform/actions/common/actions';
|
||||
import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar';
|
||||
import { IDisposable, Disposable, combinedDisposable, toDisposable } from 'vs/base/common/lifecycle';
|
||||
|
@ -15,6 +15,10 @@ export function isSCMRepository(element: any): element is ISCMRepository {
|
|||
return !!(element as ISCMRepository).provider && typeof (element as ISCMRepository).setSelected === 'function';
|
||||
}
|
||||
|
||||
export function isSCMInput(element: any): element is ISCMInput {
|
||||
return !!(element as ISCMInput).validateInput && typeof (element as ISCMInput).value === 'string';
|
||||
}
|
||||
|
||||
export function isSCMResourceGroup(element: any): element is ISCMResourceGroup {
|
||||
return !!(element as ISCMResourceGroup).provider && !!(element as ISCMResourceGroup).elements;
|
||||
}
|
||||
|
@ -23,31 +27,42 @@ export function isSCMResource(element: any): element is ISCMResource {
|
|||
return !!(element as ISCMResource).sourceUri && isSCMResourceGroup((element as ISCMResource).resourceGroup);
|
||||
}
|
||||
|
||||
export function connectPrimaryMenuToInlineActionBar(menu: IMenu, actionBar: ActionBar): IDisposable {
|
||||
const compareActions = (a: IAction, b: IAction) => a.id === b.id;
|
||||
|
||||
export function connectPrimaryMenu(menu: IMenu, callback: (primary: IAction[], secondary: IAction[]) => void, isPrimaryGroup?: (group: string) => boolean): IDisposable {
|
||||
let cachedDisposable: IDisposable = Disposable.None;
|
||||
let cachedPrimary: IAction[] = [];
|
||||
let cachedSecondary: IAction[] = [];
|
||||
|
||||
const updateActions = () => {
|
||||
const primary: IAction[] = [];
|
||||
const secondary: IAction[] = [];
|
||||
|
||||
const disposable = createAndFillInActionBarActions(menu, { shouldForwardArgs: true }, { primary, secondary }, g => /^inline/.test(g));
|
||||
const disposable = createAndFillInActionBarActions(menu, { shouldForwardArgs: true }, { primary, secondary }, isPrimaryGroup);
|
||||
|
||||
if (equals(cachedPrimary, primary, (a, b) => a.id === b.id)) {
|
||||
if (equals(cachedPrimary, primary, compareActions) && equals(cachedSecondary, secondary, compareActions)) {
|
||||
disposable.dispose();
|
||||
return;
|
||||
}
|
||||
|
||||
cachedDisposable = disposable;
|
||||
cachedPrimary = primary;
|
||||
cachedSecondary = secondary;
|
||||
|
||||
actionBar.clear();
|
||||
actionBar.push(primary, { icon: true, label: false });
|
||||
callback(primary, secondary);
|
||||
};
|
||||
|
||||
updateActions();
|
||||
|
||||
return combinedDisposable(menu.onDidChange(updateActions), toDisposable(() => {
|
||||
cachedDisposable.dispose();
|
||||
}));
|
||||
return combinedDisposable(
|
||||
menu.onDidChange(updateActions),
|
||||
toDisposable(() => cachedDisposable.dispose())
|
||||
);
|
||||
}
|
||||
|
||||
export function connectPrimaryMenuToInlineActionBar(menu: IMenu, actionBar: ActionBar): IDisposable {
|
||||
return connectPrimaryMenu(menu, (primary) => {
|
||||
actionBar.clear();
|
||||
actionBar.push(primary, { icon: true, label: false });
|
||||
}, g => /^inline/.test(g));
|
||||
}
|
||||
|
|
|
@ -11,6 +11,7 @@ import { Command } from 'vs/editor/common/modes';
|
|||
import { ISequence } from 'vs/base/common/sequence';
|
||||
|
||||
export const VIEWLET_ID = 'workbench.view.scm';
|
||||
export const VIEW_PANE_ID = 'workbench.scm';
|
||||
|
||||
export interface IBaselineResourceProvider {
|
||||
getBaselineResource(resource: URI): Promise<URI>;
|
||||
|
@ -79,6 +80,8 @@ export interface IInputValidator {
|
|||
}
|
||||
|
||||
export interface ISCMInput {
|
||||
readonly repository: ISCMRepository;
|
||||
|
||||
value: string;
|
||||
readonly onDidChange: Event<string>;
|
||||
|
||||
|
|
|
@ -70,6 +70,8 @@ class SCMInput implements ISCMInput {
|
|||
|
||||
private readonly _onDidChangeValidateInput = new Emitter<void>();
|
||||
readonly onDidChangeValidateInput: Event<void> = this._onDidChangeValidateInput.event;
|
||||
|
||||
constructor(readonly repository: ISCMRepository) { }
|
||||
}
|
||||
|
||||
class SCMRepository implements ISCMRepository {
|
||||
|
@ -85,7 +87,7 @@ class SCMRepository implements ISCMRepository {
|
|||
private readonly _onDidChangeSelection = new Emitter<boolean>();
|
||||
readonly onDidChangeSelection: Event<boolean> = this._onDidChangeSelection.event;
|
||||
|
||||
readonly input: ISCMInput = new SCMInput();
|
||||
readonly input: ISCMInput = new SCMInput(this);
|
||||
|
||||
constructor(
|
||||
public readonly provider: ISCMProvider,
|
||||
|
|
|
@ -7,7 +7,8 @@
|
|||
|
||||
.file-icon-themable-tree.align-icons-and-twisties .monaco-tl-twistie:not(.force-twistie):not(.collapsible),
|
||||
.file-icon-themable-tree .align-icon-with-twisty .monaco-tl-twistie:not(.force-twistie):not(.collapsible),
|
||||
.file-icon-themable-tree.hide-arrows .monaco-tl-twistie:not(.force-twistie) {
|
||||
.file-icon-themable-tree.hide-arrows .monaco-tl-twistie:not(.force-twistie),
|
||||
.file-icon-themable-tree .monaco-tl-twistie.force-no-twistie {
|
||||
background-image: none !important;
|
||||
width: 0 !important;
|
||||
padding-right: 0 !important;
|
||||
|
|
|
@ -176,7 +176,6 @@ import 'vs/workbench/contrib/sash/browser/sash.contribution';
|
|||
|
||||
// SCM
|
||||
import 'vs/workbench/contrib/scm/browser/scm.contribution';
|
||||
import 'vs/workbench/contrib/scm/browser/scmViewlet';
|
||||
|
||||
// Debug
|
||||
import 'vs/workbench/contrib/debug/browser/debug.contribution';
|
||||
|
|
Loading…
Reference in a new issue