未验证 提交 a8a04eb7 编写于 作者: A Alex Ross 提交者: GitHub

joao/table (#117026)

* 💄

* table: intro

* 💄

* more table progress

* table: layout

* table widget: fix overflow behavior

* table: fix initial cell sizing

* table: css

* simplify ITunnelViewModel

* further TunnelViewModel simplification

* splitview: getSashOrthogonalSize

* table: use getSashOrthogonalSize

* table: styles, domFocus

* tunnel view: start to adopt table

* Improve tunnel label and process description

* table: rename

* workbench table

* adopt WorkbenchTable in tunnel view

* Rendering for local address and label

* More actions on cells

* cleanup workbench lists

* reenable more tunnel view functionality

* reenable tunnel view list options

* tunnel view: enable context menu clicking

* update comment

* remove unused imports

* privacy and source columns

* Use container in renderTemplate

* Hide privacy column

* Some clean up in naming

* Show detected ports and add input box

* Source -> Origin and added a menu

* table: fix weights

* table: column header tooltip

* table: column size constraints

* Add tooltips to port cells and some cleanup

* Add port header tooltips

* Use column weight in ports table

* More clean up and fix icons

* table: optional tooltip

* table hover feedback

* Fix hygiene issue in breakpoints view

* Add padding-right to port cell icon

* Add tooltip to icon in ports view

* Add icon column
Co-authored-by: NJoão Moreno <joao.moreno@microsoft.com>
......@@ -15,7 +15,7 @@ export interface IListVirtualDelegate<T> {
}
export interface IListRenderer<T, TTemplateData> {
templateId: string;
readonly templateId: string;
renderTemplate(container: HTMLElement): TTemplateData;
renderElement(element: T, index: number, templateData: TTemplateData, height: number | undefined): void;
disposeElement?(element: T, index: number, templateData: TTemplateData, height: number | undefined): void;
......
......@@ -831,6 +831,14 @@ export class DefaultStyleController implements IStyleController {
content.push(`.monaco-list-type-filter { box-shadow: 1px 1px 1px ${styles.listMatchesShadow}; }`);
}
if (styles.tableColumnsBorder) {
content.push(`
.monaco-table:hover > .monaco-split-view2,
.monaco-table:hover > .monaco-split-view2 .monaco-sash.vertical::before {
border-color: ${styles.tableColumnsBorder};
}`);
}
this.styleElement.textContent = content.join('\n');
}
}
......@@ -886,6 +894,7 @@ export interface IListStyles {
listFilterWidgetNoMatchesOutline?: Color;
listMatchesShadow?: Color;
treeIndentGuidesStroke?: Color;
tableColumnsBorder?: Color;
}
const defaultStyles: IListStyles = {
......@@ -897,7 +906,8 @@ const defaultStyles: IListStyles = {
listInactiveSelectionBackground: Color.fromHex('#3F3F46'),
listHoverBackground: Color.fromHex('#2A2D2E'),
listDropBackground: Color.fromHex('#383B3D'),
treeIndentGuidesStroke: Color.fromHex('#a9a9a9')
treeIndentGuidesStroke: Color.fromHex('#a9a9a9'),
tableColumnsBorder: Color.fromHex('#cccccc').transparent(0.2)
};
const DefaultOptions: IListOptions<any> = {
......
......@@ -33,6 +33,8 @@ export interface ISplitViewOptions<TLayoutContext = undefined> {
readonly inverseAltBehavior?: boolean;
readonly proportionalLayout?: boolean; // default true,
readonly descriptor?: ISplitViewDescriptor<TLayoutContext>;
readonly scrollbarVisibility?: ScrollbarVisibility;
readonly getSashOrthogonalSize?: () => number;
}
/**
......@@ -200,7 +202,7 @@ export namespace Sizing {
export function Invisible(cachedVisibleSize: number): InvisibleSizing { return { type: 'invisible', cachedVisibleSize }; }
}
export interface ISplitViewDescriptor<TLayoutContext> {
export interface ISplitViewDescriptor<TLayoutContext = undefined> {
size: number;
views: {
visible?: boolean;
......@@ -227,6 +229,7 @@ export class SplitView<TLayoutContext = undefined> extends Disposable {
private state: State = State.Idle;
private inverseAltBehavior: boolean;
private proportionalLayout: boolean;
private readonly getSashOrthogonalSize: { (): number } | undefined;
private _onDidSashChange = this._register(new Emitter<number>());
readonly onDidSashChange = this._onDidSashChange.event;
......@@ -298,6 +301,7 @@ export class SplitView<TLayoutContext = undefined> extends Disposable {
this.orientation = types.isUndefined(options.orientation) ? Orientation.VERTICAL : options.orientation;
this.inverseAltBehavior = !!options.inverseAltBehavior;
this.proportionalLayout = types.isUndefined(options.proportionalLayout) ? true : !!options.proportionalLayout;
this.getSashOrthogonalSize = options.getSashOrthogonalSize;
this.el = document.createElement('div');
this.el.classList.add('monaco-split-view2');
......@@ -309,8 +313,8 @@ export class SplitView<TLayoutContext = undefined> extends Disposable {
this.scrollable = new Scrollable(125, scheduleAtNextAnimationFrame);
this.scrollableElement = this._register(new SmoothScrollableElement(this.viewContainer, {
vertical: this.orientation === Orientation.VERTICAL ? ScrollbarVisibility.Auto : ScrollbarVisibility.Hidden,
horizontal: this.orientation === Orientation.HORIZONTAL ? ScrollbarVisibility.Auto : ScrollbarVisibility.Hidden
vertical: this.orientation === Orientation.VERTICAL ? (options.scrollbarVisibility ?? ScrollbarVisibility.Auto) : ScrollbarVisibility.Hidden,
horizontal: this.orientation === Orientation.HORIZONTAL ? (options.scrollbarVisibility ?? ScrollbarVisibility.Auto) : ScrollbarVisibility.Hidden
}, this.scrollable));
this._register(this.scrollableElement.onScroll(e => {
......@@ -706,17 +710,11 @@ export class SplitView<TLayoutContext = undefined> extends Disposable {
// Add sash
if (this.viewItems.length > 1) {
let opts = { orthogonalStartSash: this.orthogonalStartSash, orthogonalEndSash: this.orthogonalEndSash };
const sash = this.orientation === Orientation.VERTICAL
? new Sash(this.sashContainer, { getHorizontalSashTop: (sash: Sash) => this.getSashPosition(sash) }, {
orientation: Orientation.HORIZONTAL,
orthogonalStartSash: this.orthogonalStartSash,
orthogonalEndSash: this.orthogonalEndSash
})
: new Sash(this.sashContainer, { getVerticalSashLeft: (sash: Sash) => this.getSashPosition(sash) }, {
orientation: Orientation.VERTICAL,
orthogonalStartSash: this.orthogonalStartSash,
orthogonalEndSash: this.orthogonalEndSash
});
? new Sash(this.sashContainer, { getHorizontalSashTop: s => this.getSashPosition(s), getHorizontalSashWidth: this.getSashOrthogonalSize }, { ...opts, orientation: Orientation.HORIZONTAL })
: new Sash(this.sashContainer, { getVerticalSashLeft: s => this.getSashPosition(s), getVerticalSashHeight: this.getSashOrthogonalSize }, { ...opts, orientation: Orientation.VERTICAL });
const sashEventMapper = this.orientation === Orientation.VERTICAL
? (e: IBaseSashEvent) => ({ sash, start: e.startY, current: e.currentY, alt: e.altKey })
......
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
.monaco-table {
display: flex;
flex-direction: column;
position: relative;
height: 100%;
width: 100%;
white-space: nowrap;
}
.monaco-table > .monaco-split-view2 {
border-bottom: 1px solid transparent;
}
.monaco-table > .monaco-list {
flex: 1;
}
.monaco-table-tr {
display: flex;
}
.monaco-table-th {
width: 100%;
height: 100%;
font-weight: bold;
overflow: hidden;
text-overflow: ellipsis;
}
.monaco-table-th,
.monaco-table-td {
box-sizing: border-box;
padding-left: 10px;
flex-shrink: 0;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
.monaco-table-th[data-col-index="0"],
.monaco-table-td[data-col-index="0"] {
padding-left: 20px;
}
.monaco-table > .monaco-split-view2 .monaco-sash.vertical::before {
content: "";
position: absolute;
left: calc(var(--sash-size) / 2);
width: 0;
border-left: 1px solid transparent;
}
.monaco-table > .monaco-split-view2,
.monaco-table > .monaco-split-view2 .monaco-sash.vertical::before {
transition: border-color 0.2s ease-out;
}
/*
.monaco-table:hover > .monaco-split-view2,
.monaco-table:hover > .monaco-split-view2 .monaco-sash.vertical::before {
border-color: rgba(204, 204, 204, 0.2);
} */
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IListContextMenuEvent, IListEvent, IListGestureEvent, IListMouseEvent, IListRenderer, IListTouchEvent } from 'vs/base/browser/ui/list/list';
import { Event } from 'vs/base/common/event';
export interface ITableColumn<TRow, TCell> {
readonly label: string;
readonly tooltip?: string;
readonly weight: number;
readonly templateId: string;
readonly minimumWidth?: number;
readonly maximumWidth?: number;
readonly onDidChangeWidthConstraints?: Event<void>;
project(row: TRow): TCell;
}
export interface ITableVirtualDelegate<TRow> {
readonly headerRowHeight: number;
getHeight(row: TRow): number;
}
export interface ITableRenderer<TCell, TTemplateData> extends IListRenderer<TCell, TTemplateData> { }
export interface ITableEvent<TRow> extends IListEvent<TRow> { }
export interface ITableMouseEvent<TRow> extends IListMouseEvent<TRow> { }
export interface ITableTouchEvent<TRow> extends IListTouchEvent<TRow> { }
export interface ITableGestureEvent<TRow> extends IListGestureEvent<TRow> { }
export interface ITableContextMenuEvent<TRow> extends IListContextMenuEvent<TRow> { }
export class TableError extends Error {
constructor(user: string, message: string) {
super(`TableError [${user}] ${message}`);
}
}
/*---------------------------------------------------------------------------------------------
* 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!./table';
import { IListOptions, IListOptionsUpdate, IListStyles, List } from 'vs/base/browser/ui/list/listWidget';
import { ITableColumn, ITableContextMenuEvent, ITableEvent, ITableGestureEvent, ITableMouseEvent, ITableRenderer, ITableTouchEvent, ITableVirtualDelegate } from 'vs/base/browser/ui/table/table';
import { ISpliceable } from 'vs/base/common/sequence';
import { IThemable } from 'vs/base/common/styler';
import { IDisposable } from 'vs/base/common/lifecycle';
import { $, append, clearNode, createStyleSheet, getContentHeight, getContentWidth } from 'vs/base/browser/dom';
import { ISplitViewDescriptor, IView, Orientation, SplitView } from 'vs/base/browser/ui/splitview/splitview';
import { IListRenderer, IListVirtualDelegate } from 'vs/base/browser/ui/list/list';
import { Emitter, Event } from 'vs/base/common/event';
import { ScrollbarVisibility, ScrollEvent } from 'vs/base/common/scrollable';
// TODO@joao
type TCell = any;
interface RowTemplateData {
readonly container: HTMLElement;
readonly cellContainers: HTMLElement[];
readonly cellTemplateData: unknown[];
}
class TableListRenderer<TRow> implements IListRenderer<TRow, RowTemplateData> {
static TemplateId = 'row';
readonly templateId = TableListRenderer.TemplateId;
private renderers: ITableRenderer<TCell, unknown>[];
private renderedTemplates = new Set<RowTemplateData>();
constructor(
private columns: ITableColumn<TRow, TCell>[],
renderers: ITableRenderer<TCell, unknown>[],
private getColumnSize: (index: number) => number
) {
const rendererMap = new Map(renderers.map(r => [r.templateId, r]));
this.renderers = [];
for (const column of columns) {
const renderer = rendererMap.get(column.templateId);
if (!renderer) {
throw new Error(`Table cell renderer for template id ${column.templateId} not found.`);
}
this.renderers.push(renderer);
}
}
renderTemplate(container: HTMLElement) {
const rowContainer = append(container, $('.monaco-table-tr'));
const cellContainers: HTMLElement[] = [];
const cellTemplateData: unknown[] = [];
for (let i = 0; i < this.columns.length; i++) {
const renderer = this.renderers[i];
const cellContainer = append(rowContainer, $('.monaco-table-td', { 'data-col-index': i }));
cellContainer.style.width = `${this.getColumnSize(i)}px`;
cellContainers.push(cellContainer);
cellTemplateData.push(renderer.renderTemplate(cellContainer));
}
const result = { container, cellContainers, cellTemplateData };
this.renderedTemplates.add(result);
return result;
}
renderElement(element: TRow, index: number, templateData: RowTemplateData, height: number | undefined): void {
for (let i = 0; i < this.columns.length; i++) {
const column = this.columns[i];
const cell = column.project(element);
const renderer = this.renderers[i];
renderer.renderElement(cell, index, templateData.cellTemplateData[i], height);
}
}
disposeElement(element: TRow, index: number, templateData: RowTemplateData, height: number | undefined): void {
for (let i = 0; i < this.columns.length; i++) {
const renderer = this.renderers[i];
if (renderer.disposeElement) {
const column = this.columns[i];
const cell = column.project(element);
renderer.disposeElement(cell, index, templateData.cellTemplateData[i], height);
}
}
}
disposeTemplate(templateData: RowTemplateData): void {
for (let i = 0; i < this.columns.length; i++) {
const renderer = this.renderers[i];
renderer.disposeTemplate(templateData.cellTemplateData[i]);
}
clearNode(templateData.container);
this.renderedTemplates.delete(templateData);
}
layoutColumn(index: number, size: number): void {
for (const { cellContainers } of this.renderedTemplates) {
cellContainers[index].style.width = `${size}px`;
}
}
}
function asListVirtualDelegate<TRow>(delegate: ITableVirtualDelegate<TRow>): IListVirtualDelegate<TRow> {
return {
getHeight(row) { return delegate.getHeight(row); },
getTemplateId() { return TableListRenderer.TemplateId; },
};
}
class ColumnHeader<TRow, TCell> implements IView {
readonly element: HTMLElement;
get minimumSize() { return this.column.minimumWidth ?? 120; }
get maximumSize() { return this.column.maximumWidth ?? Number.POSITIVE_INFINITY; }
get onDidChange() { return this.column.onDidChangeWidthConstraints ?? Event.None; }
private _onDidLayout = new Emitter<[number, number]>();
readonly onDidLayout = this._onDidLayout.event;
constructor(readonly column: ITableColumn<TRow, TCell>, private index: number) {
this.element = $('.monaco-table-th', { 'data-col-index': index, title: column.tooltip }, column.label);
}
layout(size: number): void {
this._onDidLayout.fire([this.index, size]);
}
}
export interface ITableOptions<TRow> extends IListOptions<TRow> { }
export interface ITableOptionsUpdate extends IListOptionsUpdate { }
export interface ITableStyles extends IListStyles { }
export class Table<TRow> implements ISpliceable<TRow>, IThemable, IDisposable {
private static InstanceCount = 0;
readonly domId = `table_id_${++Table.InstanceCount}`;
readonly domNode: HTMLElement;
private splitview: SplitView;
private list: List<TRow>;
private columnLayoutDisposable: IDisposable;
private cachedHeight: number = 0;
private styleElement: HTMLStyleElement;
get onDidChangeFocus(): Event<ITableEvent<TRow>> { return this.list.onDidChangeFocus; }
get onDidChangeSelection(): Event<ITableEvent<TRow>> { return this.list.onDidChangeSelection; }
get onDidScroll(): Event<ScrollEvent> { return this.list.onDidScroll; }
get onMouseClick(): Event<ITableMouseEvent<TRow>> { return this.list.onMouseClick; }
get onMouseDblClick(): Event<ITableMouseEvent<TRow>> { return this.list.onMouseDblClick; }
get onMouseMiddleClick(): Event<ITableMouseEvent<TRow>> { return this.list.onMouseMiddleClick; }
get onPointer(): Event<ITableMouseEvent<TRow>> { return this.list.onPointer; }
get onMouseUp(): Event<ITableMouseEvent<TRow>> { return this.list.onMouseUp; }
get onMouseDown(): Event<ITableMouseEvent<TRow>> { return this.list.onMouseDown; }
get onMouseOver(): Event<ITableMouseEvent<TRow>> { return this.list.onMouseOver; }
get onMouseMove(): Event<ITableMouseEvent<TRow>> { return this.list.onMouseMove; }
get onMouseOut(): Event<ITableMouseEvent<TRow>> { return this.list.onMouseOut; }
get onTouchStart(): Event<ITableTouchEvent<TRow>> { return this.list.onTouchStart; }
get onTap(): Event<ITableGestureEvent<TRow>> { return this.list.onTap; }
get onContextMenu(): Event<ITableContextMenuEvent<TRow>> { return this.list.onContextMenu; }
get onDidFocus(): Event<void> { return this.list.onDidFocus; }
get onDidBlur(): Event<void> { return this.list.onDidBlur; }
get scrollTop(): number { return this.list.scrollTop; }
set scrollTop(scrollTop: number) { this.list.scrollTop = scrollTop; }
get scrollLeft(): number { return this.list.scrollLeft; }
set scrollLeft(scrollLeft: number) { this.list.scrollLeft = scrollLeft; }
get scrollHeight(): number { return this.list.scrollHeight; }
get renderHeight(): number { return this.list.renderHeight; }
get onDidDispose(): Event<void> { return this.list.onDidDispose; }
constructor(
user: string,
container: HTMLElement,
private virtualDelegate: ITableVirtualDelegate<TRow>,
columns: ITableColumn<TRow, TCell>[],
renderers: ITableRenderer<TCell, unknown>[],
_options?: ITableOptions<TRow>
) {
this.domNode = append(container, $(`.monaco-table.${this.domId}`));
const headers = columns.map((c, i) => new ColumnHeader(c, i));
const descriptor: ISplitViewDescriptor = {
size: headers.reduce((a, b) => a + b.column.weight, 0),
views: headers.map(view => ({ size: view.column.weight, view }))
};
this.splitview = new SplitView(this.domNode, {
orientation: Orientation.HORIZONTAL,
scrollbarVisibility: ScrollbarVisibility.Hidden,
getSashOrthogonalSize: () => this.cachedHeight,
descriptor
});
this.splitview.el.style.height = `${virtualDelegate.headerRowHeight}px`;
this.splitview.el.style.lineHeight = `${virtualDelegate.headerRowHeight}px`;
const renderer = new TableListRenderer(columns, renderers, i => this.splitview.getViewSize(i));
this.list = new List(user, this.domNode, asListVirtualDelegate(virtualDelegate), [renderer], _options);
this.columnLayoutDisposable = Event.any(...headers.map(h => h.onDidLayout))
(([index, size]) => renderer.layoutColumn(index, size));
this.styleElement = createStyleSheet(this.domNode);
this.style({});
}
updateOptions(options: ITableOptionsUpdate): void {
this.list.updateOptions(options);
}
splice(start: number, deleteCount: number, elements: TRow[] = []): void {
this.list.splice(start, deleteCount, elements);
}
rerender(): void {
this.list.rerender();
}
row(index: number): TRow {
return this.list.element(index);
}
indexOf(element: TRow): number {
return this.list.indexOf(element);
}
get length(): number {
return this.list.length;
}
getHTMLElement(): HTMLElement {
return this.domNode;
}
layout(height?: number, width?: number): void {
height = height ?? getContentHeight(this.domNode);
width = width ?? getContentWidth(this.domNode);
this.cachedHeight = height;
this.splitview.layout(width);
this.list.layout(height - this.virtualDelegate.headerRowHeight, width);
}
toggleKeyboardNavigation(): void {
this.list.toggleKeyboardNavigation();
}
style(styles: ITableStyles): void {
const content: string[] = [];
content.push(`.monaco-table.${this.domId} > .monaco-split-view2 .monaco-sash.vertical::before {
top: ${this.virtualDelegate.headerRowHeight + 1}px;
height: calc(100% - ${this.virtualDelegate.headerRowHeight}px);
}`);
this.styleElement.textContent = content.join('\n');
this.list.style(styles);
}
domFocus(): void {
this.list.domFocus();
}
getSelectedElements(): TRow[] {
return this.list.getSelectedElements();
}
setSelection(indexes: number[], browserEvent?: UIEvent): void {
this.list.setSelection(indexes, browserEvent);
}
getSelection(): number[] {
return this.list.getSelection();
}
setFocus(indexes: number[], browserEvent?: UIEvent): void {
this.list.setFocus(indexes, browserEvent);
}
focusNext(n = 1, loop = false, browserEvent?: UIEvent): void {
this.list.focusNext(n, loop, browserEvent);
}
focusPrevious(n = 1, loop = false, browserEvent?: UIEvent): void {
this.list.focusPrevious(n, loop, browserEvent);
}
focusNextPage(browserEvent?: UIEvent): void {
this.list.focusNextPage(browserEvent);
}
focusPreviousPage(browserEvent?: UIEvent): void {
this.list.focusPreviousPage(browserEvent);
}
focusFirst(browserEvent?: UIEvent): void {
this.list.focusFirst(browserEvent);
}
focusLast(browserEvent?: UIEvent): void {
this.list.focusLast(browserEvent);
}
getFocus(): number[] {
return this.list.getFocus();
}
reveal(index: number, relativeTop?: number): void {
this.list.reveal(index, relativeTop);
}
dispose(): void {
this.splitview.dispose();
this.list.dispose();
this.columnLayoutDisposable.dispose();
}
}
......@@ -125,8 +125,10 @@ export class MenuId {
static readonly TouchBarContext = new MenuId('TouchBarContext');
static readonly TitleBarContext = new MenuId('TitleBarContext');
static readonly TunnelContext = new MenuId('TunnelContext');
static readonly TunnelInline = new MenuId('TunnelInline');
static readonly TunnelPortInline = new MenuId('TunnelInline');
static readonly TunnelTitle = new MenuId('TunnelTitle');
static readonly TunnelLocalAddressInline = new MenuId('TunnelLocalAddressInline');
static readonly TunnelOriginInline = new MenuId('TunnelOriginInline');
static readonly ViewItemContext = new MenuId('ViewItemContext');
static readonly ViewContainerTitle = new MenuId('ViewContainerTitle');
static readonly ViewContainerTitleContext = new MenuId('ViewContainerTitleContext');
......
......@@ -26,9 +26,11 @@ import { AsyncDataTree, IAsyncDataTreeOptions, CompressibleAsyncDataTree, ITreeC
import { DataTree, IDataTreeOptions } from 'vs/base/browser/ui/tree/dataTree';
import { IKeyboardNavigationEventFilter, IAbstractTreeOptions, RenderIndentGuides, IAbstractTreeOptionsUpdate } from 'vs/base/browser/ui/tree/abstractTree';
import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility';
import { ITableOptions, ITableOptionsUpdate, Table } from 'vs/base/browser/ui/table/tableWidget';
import { ITableColumn, ITableRenderer, ITableVirtualDelegate } from 'vs/base/browser/ui/table/table';
export type ListWidget = List<any> | PagedList<any> | ObjectTree<any, any> | DataTree<any, any, any> | AsyncDataTree<any, any, any>;
export type WorkbenchListWidget = WorkbenchList<any> | WorkbenchPagedList<any> | WorkbenchObjectTree<any, any> | WorkbenchCompressibleObjectTree<any, any> | WorkbenchDataTree<any, any, any> | WorkbenchAsyncDataTree<any, any, any> | WorkbenchCompressibleAsyncDataTree<any, any, any>;
export type ListWidget = List<any> | PagedList<any> | ObjectTree<any, any> | DataTree<any, any, any> | AsyncDataTree<any, any, any> | Table<any>;
export type WorkbenchListWidget = WorkbenchList<any> | WorkbenchPagedList<any> | WorkbenchObjectTree<any, any> | WorkbenchCompressibleObjectTree<any, any> | WorkbenchDataTree<any, any, any> | WorkbenchAsyncDataTree<any, any, any> | WorkbenchCompressibleAsyncDataTree<any, any, any> | WorkbenchTable<any>;
export const IListService = createDecorator<IListService>('listService');
......@@ -189,22 +191,20 @@ export interface IWorkbenchListOptionsUpdate extends IListOptionsUpdate {
readonly overrideStyles?: IColorMapping;
}
export interface IWorkbenchListOptions<T> extends IWorkbenchListOptionsUpdate, IListOptions<T> {
readonly accessibilityProvider: IListAccessibilityProvider<T>;
}
export interface IWorkbenchListOptions<T> extends IWorkbenchListOptionsUpdate, IResourceNavigatorOptions, IListOptions<T> { }
export class WorkbenchList<T> extends List<T> {
readonly contextKeyService: IContextKeyService;
private readonly themeService: IThemeService;
private listHasSelectionOrFocus: IContextKey<boolean>;
private listDoubleSelection: IContextKey<boolean>;
private listMultiSelection: IContextKey<boolean>;
private horizontalScrolling: boolean | undefined;
private _styler: IDisposable | undefined;
private _useAltAsMultipleSelectionModifier: boolean;
private navigator: ListResourceNavigator<T>;
get onDidOpen(): Event<IOpenEvent<T | undefined>> { return this.navigator.onDidOpen; }
constructor(
user: string,
......@@ -287,6 +287,9 @@ export class WorkbenchList<T> extends List<T> {
this.updateOptions(options);
}
}));
this.navigator = new ListResourceNavigator(this, { configurationService, ...options });
this.disposables.add(this.navigator);
}
updateOptions(options: IWorkbenchListOptionsUpdate): void {
......@@ -297,38 +300,33 @@ export class WorkbenchList<T> extends List<T> {
}
}
dispose(): void {
super.dispose();
if (this._styler) {
this._styler.dispose();
}
}
private updateStyles(styles: IColorMapping): void {
if (this._styler) {
this._styler.dispose();
}
this._styler?.dispose();
this._styler = attachListStyler(this, this.themeService, styles);
}
get useAltAsMultipleSelectionModifier(): boolean {
return this._useAltAsMultipleSelectionModifier;
}
}
export interface IWorkbenchPagedListOptions<T> extends IWorkbenchListOptionsUpdate, IPagedListOptions<T> {
readonly accessibilityProvider: IListAccessibilityProvider<T>;
dispose(): void {
this._styler?.dispose();
super.dispose();
}
}
export interface IWorkbenchPagedListOptions<T> extends IWorkbenchListOptionsUpdate, IResourceNavigatorOptions, IPagedListOptions<T> { }
export class WorkbenchPagedList<T> extends PagedList<T> {
readonly contextKeyService: IContextKeyService;
private readonly themeService: IThemeService;
private readonly disposables: DisposableStore;
private _useAltAsMultipleSelectionModifier: boolean;
private horizontalScrolling: boolean | undefined;
private _styler: IDisposable | undefined;
private navigator: ListResourceNavigator<T>;
get onDidOpen(): Event<IOpenEvent<T | undefined>> { return this.navigator.onDidOpen; }
constructor(
user: string,
......@@ -357,6 +355,8 @@ export class WorkbenchPagedList<T> extends PagedList<T> {
this.disposables.add(workbenchListOptionsDisposable);
this.contextKeyService = createScopedContextKeyService(contextKeyService, this);
this.themeService = themeService;
this.horizontalScrolling = options.horizontalScrolling;
const listSupportsMultiSelect = WorkbenchListSupportsMultiSelectContextKey.bindTo(this.contextKeyService);
......@@ -367,6 +367,10 @@ export class WorkbenchPagedList<T> extends PagedList<T> {
this.disposables.add(this.contextKeyService);
this.disposables.add((listService as ListService).register(this));
if (options.overrideStyles) {
this.updateStyles(options.overrideStyles);
}
if (options.overrideStyles) {
this.disposables.add(attachListStyler(this, themeService, options.overrideStyles));
}
......@@ -390,6 +394,22 @@ export class WorkbenchPagedList<T> extends PagedList<T> {
this.updateOptions(options);
}
}));
this.navigator = new ListResourceNavigator(this, { configurationService, ...options });
this.disposables.add(this.navigator);
}
updateOptions(options: IWorkbenchListOptionsUpdate): void {
super.updateOptions(options);
if (options.overrideStyles) {
this.updateStyles(options.overrideStyles);
}
}
private updateStyles(styles: IColorMapping): void {
this._styler?.dispose();
this._styler = attachListStyler(this, this.themeService, styles);
}
get useAltAsMultipleSelectionModifier(): boolean {
......@@ -397,9 +417,141 @@ export class WorkbenchPagedList<T> extends PagedList<T> {
}
dispose(): void {
this._styler?.dispose();
this.disposables.dispose();
super.dispose();
}
}
export interface IWorkbenchTableOptionsUpdate extends ITableOptionsUpdate {
readonly overrideStyles?: IColorMapping;
}
export interface IWorkbenchTableOptions<T> extends IWorkbenchTableOptionsUpdate, ITableOptions<T> { }
export class WorkbenchTable<TRow> extends Table<TRow> {
readonly contextKeyService: IContextKeyService;
private readonly themeService: IThemeService;
private listHasSelectionOrFocus: IContextKey<boolean>;
private listDoubleSelection: IContextKey<boolean>;
private listMultiSelection: IContextKey<boolean>;
private horizontalScrolling: boolean | undefined;
private _styler: IDisposable | undefined;
private _useAltAsMultipleSelectionModifier: boolean;
private readonly disposables: DisposableStore;
private navigator: TableResourceNavigator<TRow>;
get onDidOpen(): Event<IOpenEvent<TRow | undefined>> { return this.navigator.onDidOpen; }
constructor(
user: string,
container: HTMLElement,
delegate: ITableVirtualDelegate<TRow>,
columns: ITableColumn<TRow, any>[],
renderers: ITableRenderer<TRow, any>[],
options: IWorkbenchTableOptions<TRow>,
@IContextKeyService contextKeyService: IContextKeyService,
@IListService listService: IListService,
@IThemeService themeService: IThemeService,
@IConfigurationService configurationService: IConfigurationService,
@IKeybindingService keybindingService: IKeybindingService
) {
const horizontalScrolling = typeof options.horizontalScrolling !== 'undefined' ? options.horizontalScrolling : configurationService.getValue<boolean>(horizontalScrollingKey);
const [workbenchListOptions, workbenchListOptionsDisposable] = toWorkbenchListOptions(options, configurationService, keybindingService);
super(user, container, delegate, columns, renderers,
{
keyboardSupport: false,
...computeStyles(themeService.getColorTheme(), defaultListStyles),
...workbenchListOptions,
horizontalScrolling
}
);
this.disposables = new DisposableStore();
this.disposables.add(workbenchListOptionsDisposable);
this.contextKeyService = createScopedContextKeyService(contextKeyService, this);
this.themeService = themeService;
const listSupportsMultiSelect = WorkbenchListSupportsMultiSelectContextKey.bindTo(this.contextKeyService);
listSupportsMultiSelect.set(!(options.multipleSelectionSupport === false));
this.listHasSelectionOrFocus = WorkbenchListHasSelectionOrFocus.bindTo(this.contextKeyService);
this.listDoubleSelection = WorkbenchListDoubleSelection.bindTo(this.contextKeyService);
this.listMultiSelection = WorkbenchListMultiSelection.bindTo(this.contextKeyService);
this.horizontalScrolling = options.horizontalScrolling;
this._useAltAsMultipleSelectionModifier = useAltAsMultipleSelectionModifier(configurationService);
this.disposables.add(this.contextKeyService);
this.disposables.add((listService as ListService).register(this));
if (options.overrideStyles) {
this.updateStyles(options.overrideStyles);
}
this.disposables.add(this.onDidChangeSelection(() => {
const selection = this.getSelection();
const focus = this.getFocus();
this.contextKeyService.bufferChangeEvents(() => {
this.listHasSelectionOrFocus.set(selection.length > 0 || focus.length > 0);
this.listMultiSelection.set(selection.length > 1);
this.listDoubleSelection.set(selection.length === 2);
});
}));
this.disposables.add(this.onDidChangeFocus(() => {
const selection = this.getSelection();
const focus = this.getFocus();
this.listHasSelectionOrFocus.set(selection.length > 0 || focus.length > 0);
}));
this.disposables.add(configurationService.onDidChangeConfiguration(e => {
if (e.affectsConfiguration(multiSelectModifierSettingKey)) {
this._useAltAsMultipleSelectionModifier = useAltAsMultipleSelectionModifier(configurationService);
}
let options: IListOptionsUpdate = {};
if (e.affectsConfiguration(horizontalScrollingKey) && this.horizontalScrolling === undefined) {
const horizontalScrolling = configurationService.getValue<boolean>(horizontalScrollingKey);
options = { ...options, horizontalScrolling };
}
if (e.affectsConfiguration(listSmoothScrolling)) {
const smoothScrolling = configurationService.getValue<boolean>(listSmoothScrolling);
options = { ...options, smoothScrolling };
}
if (Object.keys(options).length > 0) {
this.updateOptions(options);
}
}));
this.navigator = new TableResourceNavigator(this, { configurationService, ...options });
this.disposables.add(this.navigator);
}
updateOptions(options: IWorkbenchTableOptionsUpdate): void {
super.updateOptions(options);
if (options.overrideStyles) {
this.updateStyles(options.overrideStyles);
}
}
private updateStyles(styles: IColorMapping): void {
this._styler?.dispose();
this._styler = attachListStyler(this, this.themeService, styles);
}
get useAltAsMultipleSelectionModifier(): boolean {
return this._useAltAsMultipleSelectionModifier;
}
dispose(): void {
this._styler?.dispose();
this.disposables.dispose();
super.dispose();
}
}
......@@ -552,11 +704,11 @@ abstract class ResourceNavigator<T> extends Disposable {
abstract getSelectedElement(): T | undefined;
}
export class ListResourceNavigator<T> extends ResourceNavigator<T> {
class ListResourceNavigator<T> extends ResourceNavigator<T> {
constructor(
protected readonly widget: List<T> | PagedList<T>,
options?: IResourceNavigatorOptions
options: IResourceNavigatorOptions
) {
super(widget, options);
}
......@@ -566,6 +718,20 @@ export class ListResourceNavigator<T> extends ResourceNavigator<T> {
}
}
class TableResourceNavigator<TRow> extends ResourceNavigator<TRow> {
constructor(
protected readonly widget: Table<TRow>,
options: IResourceNavigatorOptions
) {
super(widget, options);
}
getSelectedElement(): TRow | undefined {
return this.widget.getSelectedElements()[0];
}
}
class TreeResourceNavigator<T, TFilterData> extends ResourceNavigator<T> {
constructor(
......
......@@ -385,6 +385,7 @@ export const listFilterWidgetNoMatchesOutline = registerColor('listFilterWidget.
export const listFilterMatchHighlight = registerColor('list.filterMatchBackground', { dark: editorFindMatchHighlight, light: editorFindMatchHighlight, hc: null }, nls.localize('listFilterMatchHighlight', 'Background color of the filtered match.'));
export const listFilterMatchHighlightBorder = registerColor('list.filterMatchBorder', { dark: editorFindMatchHighlightBorder, light: editorFindMatchHighlightBorder, hc: contrastBorder }, nls.localize('listFilterMatchHighlightBorder', 'Border color of the filtered match.'));
export const treeIndentGuidesStroke = registerColor('tree.indentGuidesStroke', { dark: '#585858', light: '#a9a9a9', hc: '#a9a9a9' }, nls.localize('treeIndentGuidesStroke', "Tree stroke color for the indentation guides."));
export const tableColumnsBorder = registerColor('tree.tableColumnsBorder', { dark: '#CCCCCC20', light: '#61616120', hc: null }, nls.localize('treeIndentGuidesStroke', "Tree stroke color for the indentation guides."));
export const listDeemphasizedForeground = registerColor('list.deemphasizedForeground', { dark: '#8C8C8C', light: '#8E8E90', hc: '#A7A8A9' }, nls.localize('listDeemphasizedForeground', "List/Tree foreground color for items that are deemphasized. "));
/**
......
......@@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import { IColorTheme, IThemeService } from 'vs/platform/theme/common/themeService';
import { focusBorder, inputBackground, inputForeground, ColorIdentifier, selectForeground, selectBackground, selectListBackground, selectBorder, inputBorder, foreground, editorBackground, contrastBorder, inputActiveOptionBorder, inputActiveOptionBackground, inputActiveOptionForeground, listFocusBackground, listFocusForeground, listActiveSelectionBackground, listActiveSelectionForeground, listInactiveSelectionForeground, listInactiveSelectionBackground, listInactiveFocusBackground, listHoverBackground, listHoverForeground, listDropBackground, pickerGroupBorder, pickerGroupForeground, widgetShadow, inputValidationInfoBorder, inputValidationInfoBackground, inputValidationWarningBorder, inputValidationWarningBackground, inputValidationErrorBorder, inputValidationErrorBackground, activeContrastBorder, buttonForeground, buttonBackground, buttonHoverBackground, ColorFunction, badgeBackground, badgeForeground, progressBarBackground, breadcrumbsForeground, breadcrumbsFocusForeground, breadcrumbsActiveSelectionForeground, breadcrumbsBackground, editorWidgetBorder, inputValidationInfoForeground, inputValidationWarningForeground, inputValidationErrorForeground, menuForeground, menuBackground, menuSelectionForeground, menuSelectionBackground, menuSelectionBorder, menuBorder, menuSeparatorBackground, darken, listFilterWidgetOutline, listFilterWidgetNoMatchesOutline, listFilterWidgetBackground, editorWidgetBackground, treeIndentGuidesStroke, editorWidgetForeground, simpleCheckboxBackground, simpleCheckboxBorder, simpleCheckboxForeground, ColorValue, resolveColorValue, textLinkForeground, problemsWarningIconForeground, problemsErrorIconForeground, problemsInfoIconForeground, buttonSecondaryBackground, buttonSecondaryForeground, buttonSecondaryHoverBackground, listFocusOutline, listInactiveFocusOutline } from 'vs/platform/theme/common/colorRegistry';
import { focusBorder, inputBackground, inputForeground, ColorIdentifier, selectForeground, selectBackground, selectListBackground, selectBorder, inputBorder, foreground, editorBackground, contrastBorder, inputActiveOptionBorder, inputActiveOptionBackground, inputActiveOptionForeground, listFocusBackground, listFocusForeground, listActiveSelectionBackground, listActiveSelectionForeground, listInactiveSelectionForeground, listInactiveSelectionBackground, listInactiveFocusBackground, listHoverBackground, listHoverForeground, listDropBackground, pickerGroupBorder, pickerGroupForeground, widgetShadow, inputValidationInfoBorder, inputValidationInfoBackground, inputValidationWarningBorder, inputValidationWarningBackground, inputValidationErrorBorder, inputValidationErrorBackground, activeContrastBorder, buttonForeground, buttonBackground, buttonHoverBackground, ColorFunction, badgeBackground, badgeForeground, progressBarBackground, breadcrumbsForeground, breadcrumbsFocusForeground, breadcrumbsActiveSelectionForeground, breadcrumbsBackground, editorWidgetBorder, inputValidationInfoForeground, inputValidationWarningForeground, inputValidationErrorForeground, menuForeground, menuBackground, menuSelectionForeground, menuSelectionBackground, menuSelectionBorder, menuBorder, menuSeparatorBackground, darken, listFilterWidgetOutline, listFilterWidgetNoMatchesOutline, listFilterWidgetBackground, editorWidgetBackground, treeIndentGuidesStroke, editorWidgetForeground, simpleCheckboxBackground, simpleCheckboxBorder, simpleCheckboxForeground, ColorValue, resolveColorValue, textLinkForeground, problemsWarningIconForeground, problemsErrorIconForeground, problemsInfoIconForeground, buttonSecondaryBackground, buttonSecondaryForeground, buttonSecondaryHoverBackground, listFocusOutline, listInactiveFocusOutline, tableColumnsBorder } from 'vs/platform/theme/common/colorRegistry';
import { IDisposable } from 'vs/base/common/lifecycle';
import { Color } from 'vs/base/common/color';
import { IThemable, styleFn } from 'vs/base/common/styler';
......@@ -229,6 +229,7 @@ export interface IListStyleOverrides extends IStyleOverrides {
listFilterWidgetNoMatchesOutline?: ColorIdentifier;
listMatchesShadow?: ColorIdentifier;
treeIndentGuidesStroke?: ColorIdentifier;
tableColumnsBorder?: ColorIdentifier;
}
export function attachListStyler(widget: IThemable, themeService: IThemeService, overrides?: IColorMapping): IDisposable {
......@@ -256,7 +257,8 @@ export const defaultListStyles: IColorMapping = {
listFilterWidgetOutline,
listFilterWidgetNoMatchesOutline,
listMatchesShadow: widgetShadow,
treeIndentGuidesStroke
treeIndentGuidesStroke,
tableColumnsBorder
};
export interface IButtonStyleOverrides extends IStyleOverrides {
......
......@@ -191,6 +191,11 @@ const apiMenus: IAPIMenu[] = [
key: 'ports/item/context',
id: MenuId.TunnelContext,
description: localize('view.tunnelContext', "The Ports view item context menu")
},
{
key: 'ports/item/origin/inline',
id: MenuId.TunnelOriginInline,
description: localize('view.tunnelOriginInline', "The Ports view item origin inline menu")
}
];
......
......@@ -7,7 +7,7 @@ import { KeyMod, KeyCode } from 'vs/base/common/keyCodes';
import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
import { List } from 'vs/base/browser/ui/list/listWidget';
import { WorkbenchListFocusContextKey, IListService, WorkbenchListSupportsMultiSelectContextKey, ListWidget, WorkbenchListHasSelectionOrFocus, getSelectionKeyboardEvent } from 'vs/platform/list/browser/listService';
import { WorkbenchListFocusContextKey, IListService, WorkbenchListSupportsMultiSelectContextKey, ListWidget, WorkbenchListHasSelectionOrFocus, getSelectionKeyboardEvent, WorkbenchListWidget } from 'vs/platform/list/browser/listService';
import { PagedList } from 'vs/base/browser/ui/list/listPaging';
import { range } from 'vs/base/common/arrays';
import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
......@@ -16,6 +16,7 @@ import { AsyncDataTree } from 'vs/base/browser/ui/tree/asyncDataTree';
import { DataTree } from 'vs/base/browser/ui/tree/dataTree';
import { ITreeNode } from 'vs/base/browser/ui/tree/tree';
import { CommandsRegistry } from 'vs/platform/commands/common/commands';
import { Table } from 'vs/base/browser/ui/table/tableWidget';
function ensureDOMFocus(widget: ListWidget | undefined): void {
// it can happen that one of the commands is executed while
......@@ -32,7 +33,7 @@ function focusDown(accessor: ServicesAccessor, arg2?: number, loop: boolean = fa
const count = typeof arg2 === 'number' ? arg2 : 1;
// List
if (focused instanceof List || focused instanceof PagedList) {
if (focused instanceof List || focused instanceof PagedList || focused instanceof Table) {
const list = focused;
list.focusNext(count);
......@@ -71,10 +72,10 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({
handler: (accessor, arg2) => focusDown(accessor, arg2)
});
function expandMultiSelection(focused: List<unknown> | PagedList<unknown> | ObjectTree<unknown, unknown> | DataTree<unknown, unknown, unknown> | AsyncDataTree<unknown, unknown, unknown>, previousFocus: unknown): void {
function expandMultiSelection(focused: WorkbenchListWidget, previousFocus: unknown): void {
// List
if (focused instanceof List || focused instanceof PagedList) {
if (focused instanceof List || focused instanceof PagedList || focused instanceof Table) {
const list = focused;
const focus = list.getFocus() ? list.getFocus()[0] : undefined;
......@@ -118,7 +119,7 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({
const focused = accessor.get(IListService).lastFocusedList;
// List / Tree
if (focused instanceof List || focused instanceof PagedList || focused instanceof ObjectTree || focused instanceof DataTree || focused instanceof AsyncDataTree) {
if (focused instanceof List || focused instanceof PagedList || focused instanceof Table || focused instanceof ObjectTree || focused instanceof DataTree || focused instanceof AsyncDataTree) {
const list = focused;
// Focus down first
......@@ -136,7 +137,7 @@ function focusUp(accessor: ServicesAccessor, arg2?: number, loop: boolean = fals
const count = typeof arg2 === 'number' ? arg2 : 1;
// List
if (focused instanceof List || focused instanceof PagedList) {
if (focused instanceof List || focused instanceof PagedList || focused instanceof Table) {
const list = focused;
list.focusPrevious(count);
......@@ -184,7 +185,7 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({
const focused = accessor.get(IListService).lastFocusedList;
// List / Tree
if (focused instanceof List || focused instanceof PagedList || focused instanceof ObjectTree || focused instanceof DataTree || focused instanceof AsyncDataTree) {
if (focused instanceof List || focused instanceof PagedList || focused instanceof Table || focused instanceof ObjectTree || focused instanceof DataTree || focused instanceof AsyncDataTree) {
const list = focused;
// Focus up first
......@@ -210,7 +211,7 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({
const focused = accessor.get(IListService).lastFocusedList;
// Tree only
if (focused && !(focused instanceof List || focused instanceof PagedList)) {
if (focused && !(focused instanceof List || focused instanceof PagedList || focused instanceof Table)) {
if (focused instanceof ObjectTree || focused instanceof DataTree || focused instanceof AsyncDataTree) {
const tree = focused;
const focusedElements = tree.getFocus();
......@@ -245,10 +246,10 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({
secondary: [KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.UpArrow]
},
handler: (accessor) => {
const focusedTree = accessor.get(IListService).lastFocusedList;
const focused = accessor.get(IListService).lastFocusedList;
if (focusedTree && !(focusedTree instanceof List || focusedTree instanceof PagedList)) {
focusedTree.collapseAll();
if (focused && !(focused instanceof List || focused instanceof PagedList || focused instanceof Table)) {
focused.collapseAll();
}
}
});
......@@ -261,7 +262,7 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({
handler: (accessor) => {
const focused = accessor.get(IListService).lastFocusedList;
if (!focused || focused instanceof List || focused instanceof PagedList) {
if (!focused || focused instanceof List || focused instanceof PagedList || focused instanceof Table) {
return;
}
......@@ -291,7 +292,7 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({
const focused = accessor.get(IListService).lastFocusedList;
// Tree only
if (focused && !(focused instanceof List || focused instanceof PagedList)) {
if (focused && !(focused instanceof List || focused instanceof PagedList || focused instanceof Table)) {
if (focused instanceof ObjectTree || focused instanceof DataTree) {
// TODO@Joao: instead of doing this here, just delegate to a tree method
const tree = focused;
......@@ -355,7 +356,7 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({
const focused = accessor.get(IListService).lastFocusedList;
// List
if (focused instanceof List || focused instanceof PagedList) {
if (focused instanceof List || focused instanceof PagedList || focused instanceof Table) {
focused.focusPreviousPage();
}
......@@ -379,7 +380,7 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({
const focused = accessor.get(IListService).lastFocusedList;
// List
if (focused instanceof List || focused instanceof PagedList) {
if (focused instanceof List || focused instanceof PagedList || focused instanceof Table) {
focused.focusNextPage();
}
......@@ -414,7 +415,7 @@ function listFocusFirst(accessor: ServicesAccessor, options?: { fromFocused: boo
const focused = accessor.get(IListService).lastFocusedList;
// List
if (focused instanceof List || focused instanceof PagedList) {
if (focused instanceof List || focused instanceof PagedList || focused instanceof Table) {
const list = focused;
list.setFocus([0]);
......@@ -458,7 +459,7 @@ function listFocusLast(accessor: ServicesAccessor, options?: { fromFocused: bool
const focused = accessor.get(IListService).lastFocusedList;
// List
if (focused instanceof List || focused instanceof PagedList) {
if (focused instanceof List || focused instanceof PagedList || focused instanceof Table) {
const list = focused;
list.setFocus([list.length - 1]);
......@@ -487,7 +488,7 @@ function focusElement(accessor: ServicesAccessor, retainCurrentFocus: boolean):
const focused = accessor.get(IListService).lastFocusedList;
const fakeKeyboardEvent = getSelectionKeyboardEvent('keydown', retainCurrentFocus);
// List
if (focused instanceof List || focused instanceof PagedList) {
if (focused instanceof List || focused instanceof PagedList || focused instanceof Table) {
const list = focused;
list.setSelection(list.getFocus(), fakeKeyboardEvent);
}
......@@ -546,7 +547,7 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({
const focused = accessor.get(IListService).lastFocusedList;
// List
if (focused instanceof List || focused instanceof PagedList) {
if (focused instanceof List || focused instanceof PagedList || focused instanceof Table) {
const list = focused;
const fakeKeyboardEvent = new KeyboardEvent('keydown');
list.setSelection(range(list.length), fakeKeyboardEvent);
......@@ -666,7 +667,7 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({
const focused = accessor.get(IListService).lastFocusedList;
// List
if (focused instanceof List || focused instanceof PagedList) {
if (focused instanceof List || focused instanceof PagedList || focused instanceof Table) {
const list = focused;
list.setSelection([]);
......@@ -690,7 +691,7 @@ CommandsRegistry.registerCommand({
const focused = accessor.get(IListService).lastFocusedList;
// List
if (focused instanceof List || focused instanceof PagedList) {
if (focused instanceof List || focused instanceof PagedList || focused instanceof Table) {
const list = focused;
list.toggleKeyboardNavigation();
}
......@@ -709,7 +710,7 @@ CommandsRegistry.registerCommand({
const focused = accessor.get(IListService).lastFocusedList;
// List
if (focused instanceof List || focused instanceof PagedList) {
if (focused instanceof List || focused instanceof PagedList || focused instanceof Table) {
// TODO@joao
}
......
......@@ -19,7 +19,7 @@ import { IEditorPane } from 'vs/workbench/common/editor';
import { InputBox } from 'vs/base/browser/ui/inputbox/inputBox';
import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent';
import { KeyCode } from 'vs/base/common/keyCodes';
import { WorkbenchList, ListResourceNavigator } from 'vs/platform/list/browser/listService';
import { WorkbenchList } from 'vs/platform/list/browser/listService';
import { IViewletViewOptions } from 'vs/workbench/browser/parts/views/viewsViewlet';
import { attachInputBoxStyler } from 'vs/platform/theme/common/styler';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
......@@ -111,7 +111,7 @@ export class BreakpointsView extends ViewPane {
container.classList.add('debug-breakpoints');
const delegate = new BreakpointsDelegate(this);
this.list = <WorkbenchList<BreakpointItem>>this.instantiationService.createInstance(WorkbenchList, 'Breakpoints', container, delegate, [
this.list = this.instantiationService.createInstance(WorkbenchList, 'Breakpoints', container, delegate, [
this.instantiationService.createInstance(BreakpointsRenderer, this.menu, this.breakpointSupportsCondition),
new ExceptionBreakpointsRenderer(this.menu, this.breakpointSupportsCondition, this.debugService),
new ExceptionBreakpointInputRenderer(this, this.debugService, this.contextViewService, this.themeService),
......@@ -126,7 +126,7 @@ export class BreakpointsView extends ViewPane {
overrideStyles: {
listBackground: this.getBackgroundColor()
}
});
}) as WorkbenchList<BreakpointItem>;
CONTEXT_BREAKPOINTS_FOCUSED.bindTo(this.list.contextKeyService);
......@@ -142,8 +142,7 @@ export class BreakpointsView extends ViewPane {
}
});
const resourceNavigator = this._register(new ListResourceNavigator(this.list, { configurationService: this.configurationService }));
this._register(resourceNavigator.onDidOpen(async e => {
this._register(this.list.onDidOpen(async e => {
if (!e.element) {
return;
}
......
......@@ -26,7 +26,7 @@ import { IViewletViewOptions } from 'vs/workbench/browser/parts/views/viewsViewl
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { CountBadge } from 'vs/base/browser/ui/countBadge/countBadge';
import { ManageExtensionAction, getContextMenuActions, ExtensionAction } from 'vs/workbench/contrib/extensions/browser/extensionsActions';
import { WorkbenchPagedList, ListResourceNavigator } from 'vs/platform/list/browser/listService';
import { WorkbenchPagedList } from 'vs/platform/list/browser/listService';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { INotificationService, Severity } from 'vs/platform/notification/common/notification';
import { ViewPane, IViewPaneOptions } from 'vs/workbench/browser/parts/views/viewPane';
......@@ -156,7 +156,7 @@ export class ExtensionsListView extends ViewPane {
const delegate = new Delegate();
const extensionsViewState = new ExtensionsViewState();
const renderer = this.instantiationService.createInstance(Renderer, extensionsViewState);
this.list = this.instantiationService.createInstance<typeof WorkbenchPagedList, WorkbenchPagedList<IExtension>>(WorkbenchPagedList, 'Extensions', extensionsList, delegate, [renderer], {
this.list = this.instantiationService.createInstance(WorkbenchPagedList, 'Extensions', extensionsList, delegate, [renderer], {
multipleSelectionSupport: false,
setRowLineHeight: false,
horizontalScrolling: false,
......@@ -170,15 +170,15 @@ export class ExtensionsListView extends ViewPane {
},
overrideStyles: {
listBackground: SIDE_BAR_BACKGROUND
}
});
},
openOnSingleClick: true
}) as WorkbenchPagedList<IExtension>;
this._register(this.list.onContextMenu(e => this.onContextMenu(e), this));
this._register(this.list.onDidChangeFocus(e => extensionsViewState.onFocusChange(coalesce(e.elements)), this));
this._register(this.list);
this._register(extensionsViewState);
const resourceNavigator = this._register(new ListResourceNavigator(this.list, { openOnSingleClick: true }));
this._register(Event.debounce(Event.filter(resourceNavigator.onDidOpen, e => e.element !== null), (_, event) => event, 75, true)(options => {
this._register(Event.debounce(Event.filter(this.list.onDidOpen, e => e.element !== null), (_, event) => event, 75, true)(options => {
this.openExtension(options.element!, { sideByside: options.sideBySide, ...options.editorOptions });
}));
......
......@@ -21,7 +21,7 @@ import { IContextKeyService, IContextKey, ContextKeyEqualsExpr } from 'vs/platfo
import { attachStylerCallback } from 'vs/platform/theme/common/styler';
import { IThemeService } from 'vs/platform/theme/common/themeService';
import { badgeBackground, badgeForeground, contrastBorder } from 'vs/platform/theme/common/colorRegistry';
import { WorkbenchList, ListResourceNavigator } from 'vs/platform/list/browser/listService';
import { WorkbenchList } from 'vs/platform/list/browser/listService';
import { IListVirtualDelegate, IListRenderer, IListContextMenuEvent, IListDragAndDrop, IListDragOverReaction } from 'vs/base/browser/ui/list/list';
import { ResourceLabels, IResourceLabel } from 'vs/workbench/browser/labels';
import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar';
......@@ -229,7 +229,7 @@ export class OpenEditorsView extends ViewPane {
this.listLabels.clear();
}
this.listLabels = this.instantiationService.createInstance(ResourceLabels, { onDidChangeVisibility: this.onDidChangeBodyVisibility });
this.list = <WorkbenchList<OpenEditor | IEditorGroup>>this.instantiationService.createInstance(WorkbenchList, 'OpenEditors', container, delegate, [
this.list = this.instantiationService.createInstance(WorkbenchList, 'OpenEditors', container, delegate, [
new EditorGroupRenderer(this.keybindingService, this.instantiationService),
new OpenEditorRenderer(this.listLabels, this.instantiationService, this.keybindingService, this.configurationService)
], {
......@@ -239,7 +239,7 @@ export class OpenEditorsView extends ViewPane {
listBackground: this.getBackgroundColor()
},
accessibilityProvider: new OpenEditorsAccessibilityProvider()
});
}) as WorkbenchList<OpenEditor | IEditorGroup>;
this._register(this.list);
this._register(this.listLabels);
......@@ -281,8 +281,7 @@ export class OpenEditorsView extends ViewPane {
e.element.group.closeEditor(e.element.editor, { preserveFocus: true });
}
}));
const resourceNavigator = this._register(new ListResourceNavigator(this.list, { configurationService: this.configurationService }));
this._register(resourceNavigator.onDidOpen(e => {
this._register(this.list.onDidOpen(e => {
if (!e.element) {
return;
} else if (e.element instanceof OpenEditor) {
......
......@@ -30,3 +30,60 @@
.pane-body.wide .ports-view .monaco-action-bar {
margin-left: 10px;
}
.ports-view .monaco-list .monaco-list-row .ports-view-actionbar-cell {
display: flex;
flex: 1;
text-overflow: ellipsis;
overflow: hidden;
flex-wrap: nowrap;
}
.ports-view .monaco-list .monaco-list-row .ports-view-actionbar-cell .monaco-inputbox {
line-height: normal;
flex: 1;
}
.ports-view .monaco-list .monaco-list-row .ports-view-actionbar-cell > .ports-view-actionbar-cell-icon.codicon {
margin-top: 3px;
padding-right: 3px;
}
.ports-view .monaco-list .monaco-list-row.selected .ports-view-actionbar-cell > .ports-view-actionbar-cell-icon.codicon {
color: currentColor !important;
}
.ports-view .monaco-list .monaco-list-row .ports-view-actionbar-cell .ports-view-actionbar-cell-resourceLabel .monaco-icon-label-container > .monaco-icon-name-container {
flex: 1;
}
.ports-view .monaco-list .monaco-list-row .ports-view-actionbar-cell .ports-view-actionbar-cell-resourceLabel::after {
padding-right: 0px;
}
.ports-view .monaco-list .monaco-list-row .ports-view-actionbar-cell .actions {
display: none;
}
.ports-view .monaco-list .monaco-list-row:hover .ports-view-actionbar-cell .actions,
.ports-view .monaco-list .monaco-list-row.selected .ports-view-actionbar-cell .actions,
.ports-view .monaco-list .monaco-list-row.focused .ports-view-actionbar-cell .actions {
display: block;
}
.ports-view .monaco-list .ports-view-actionbar-cell .actions .action-label {
width: 16px;
height: 100%;
background-size: 16px;
background-position: 50% 50%;
background-repeat: no-repeat;
}
.ports-view .monaco-list .ports-view-actionbar-cell .actions .action-label.codicon {
line-height: 22px;
height: 22px;
}
.ports-view .monaco-list .ports-view-actionbar-cell .actions .action-label.codicon::before {
vertical-align: middle;
}
......@@ -47,7 +47,6 @@ export class ForwardedPortsView extends Disposable implements IWorkbenchContribu
@IViewDescriptorService private readonly viewDescriptorService: IViewDescriptorService,
@IActivityService private readonly activityService: IActivityService,
@IStatusbarService private readonly statusbarService: IStatusbarService,
@IConfigurationService private readonly configurationService: IConfigurationService,
@optional(ITASExperimentService) tasExperimentService: ITASExperimentService,
) {
super();
......@@ -92,7 +91,7 @@ export class ForwardedPortsView extends Disposable implements IWorkbenchContribu
if (this.environmentService.remoteAuthority && viewEnabled) {
const viewContainer = await this.getViewContainer();
const tunnelPanelDescriptor = new TunnelPanelDescriptor(new TunnelViewModel(this.remoteExplorerService, this.configurationService), this.environmentService);
const tunnelPanelDescriptor = new TunnelPanelDescriptor(new TunnelViewModel(this.remoteExplorerService), this.environmentService);
const viewsRegistry = Registry.as<IViewsRegistry>(Extensions.ViewsRegistry);
if (viewContainer) {
this.remoteExplorerService.enablePortsFeatures();
......
......@@ -24,3 +24,6 @@ export const forwardPortIcon = registerIcon('ports-forward-icon', Codicon.plus,
export const stopForwardIcon = registerIcon('ports-stop-forward-icon', Codicon.x, nls.localize('stopForwardIcon', 'Icon for the stop forwarding action.'));
export const openBrowserIcon = registerIcon('ports-open-browser-icon', Codicon.globe, nls.localize('openBrowserIcon', 'Icon for the open browser action.'));
export const openPreviewIcon = registerIcon('ports-open-preview-icon', Codicon.openPreview, nls.localize('openPreviewIcon', 'Icon for the open preview action.'));
export const copyAddressIcon = registerIcon('ports-copy-address-icon', Codicon.clippy, nls.localize('copyAddressIcon', 'Icon for the copy local address action.'));
export const forwardedPortWithoutProcessIcon = registerIcon('ports-forwarded-without-process-icon', Codicon.circleOutline, nls.localize('forwardedPortWithoutProcessIcon', 'Icon for forwarded ports that don\'t have a running process.'));
export const forwardedPortWithProcessIcon = registerIcon('ports-forwarded-with-process-icon', Codicon.circleFilled, nls.localize('forwardedPortWithProcessIcon', 'Icon for forwarded ports that do have a running process.'));
......@@ -49,12 +49,18 @@ export interface ITunnelItem {
localPort?: number;
name?: string;
closeable?: boolean;
source: string;
privacy?: TunnelPrivacy;
description?: string;
wideDescription?: string;
processDescription?: string;
readonly icon?: ThemeIcon;
readonly label: string;
readonly wideLabel: string;
}
export enum TunnelEditId {
None = 0,
New = 1,
Label = 2,
LocalPort = 3
}
export interface Tunnel {
......@@ -68,7 +74,7 @@ export interface Tunnel {
runningProcess: string | undefined;
pid: number | undefined;
source?: string;
restore: boolean;
userForwarded: boolean;
}
export function makeAddress(host: string, port: number): string {
......@@ -282,7 +288,7 @@ export class TunnelModel extends Disposable {
runningProcess: matchingCandidate?.detail,
pid: matchingCandidate?.pid,
privacy: this.makeTunnelPrivacy(tunnel.public),
restore: true
userForwarded: true
});
this.remoteTunnels.set(key, tunnel);
}
......@@ -303,7 +309,7 @@ export class TunnelModel extends Disposable {
runningProcess: matchingCandidate?.detail,
pid: matchingCandidate?.pid,
privacy: this.makeTunnelPrivacy(tunnel.public),
restore: true
userForwarded: true
});
}
await this.storeForwarded();
......@@ -369,7 +375,7 @@ export class TunnelModel extends Disposable {
private async storeForwarded() {
if (this.configurationService.getValue('remote.restoreForwardedPorts')) {
this.storageService.store(await this.getStorageKey(), JSON.stringify(Array.from(this.forwarded.values()).filter(value => value.restore)), StorageScope.GLOBAL, StorageTarget.USER);
this.storageService.store(await this.getStorageKey(), JSON.stringify(Array.from(this.forwarded.values()).filter(value => value.userForwarded)), StorageScope.GLOBAL, StorageTarget.USER);
}
}
......@@ -397,7 +403,7 @@ export class TunnelModel extends Disposable {
pid: matchingCandidate?.pid,
source,
privacy: this.makeTunnelPrivacy(tunnel.public),
restore
userForwarded: restore
};
const key = makeAddress(remote.host, remote.port);
this.forwarded.set(key, newForward);
......@@ -454,7 +460,7 @@ export class TunnelModel extends Disposable {
runningProcess: matchingCandidate?.detail,
pid: matchingCandidate?.pid,
privacy: TunnelPrivacy.ConstantPrivate,
restore: false
userForwarded: false
});
});
}
......@@ -550,9 +556,9 @@ export interface IRemoteExplorerService {
onDidChangeTargetType: Event<string[]>;
targetType: string[];
readonly tunnelModel: TunnelModel;
onDidChangeEditable: Event<ITunnelItem | undefined>;
setEditable(tunnelItem: ITunnelItem | undefined, data: IEditableData | null): void;
getEditableData(tunnelItem: ITunnelItem | undefined): IEditableData | undefined;
onDidChangeEditable: Event<{ tunnel: ITunnelItem, editId: TunnelEditId } | undefined>;
setEditable(tunnelItem: ITunnelItem | undefined, editId: TunnelEditId, data: IEditableData | null): void;
getEditableData(tunnelItem: ITunnelItem | undefined, editId?: TunnelEditId): IEditableData | undefined;
forward(remote: { host: string, port: number }, localPort?: number, name?: string, source?: string, elevateIfNeeded?: boolean, isPublic?: boolean, restore?: boolean): Promise<RemoteTunnel | void>;
close(remote: { host: string, port: number }): Promise<void>;
setTunnelInformation(tunnelInformation: TunnelInformation | undefined): void;
......@@ -571,9 +577,9 @@ class RemoteExplorerService implements IRemoteExplorerService {
private readonly _onDidChangeTargetType: Emitter<string[]> = new Emitter<string[]>();
public readonly onDidChangeTargetType: Event<string[]> = this._onDidChangeTargetType.event;
private _tunnelModel: TunnelModel;
private _editable: { tunnelItem: ITunnelItem | undefined, data: IEditableData } | undefined;
private readonly _onDidChangeEditable: Emitter<ITunnelItem | undefined> = new Emitter();
public readonly onDidChangeEditable: Event<ITunnelItem | undefined> = this._onDidChangeEditable.event;
private _editable: { tunnelItem: ITunnelItem | undefined, editId: TunnelEditId, data: IEditableData } | undefined;
private readonly _onDidChangeEditable: Emitter<{ tunnel: ITunnelItem, editId: TunnelEditId } | undefined> = new Emitter();
public readonly onDidChangeEditable: Event<{ tunnel: ITunnelItem, editId: TunnelEditId } | undefined> = this._onDidChangeEditable.event;
private readonly _onEnabledPortsFeatures: Emitter<void> = new Emitter();
public readonly onEnabledPortsFeatures: Event<void> = this._onEnabledPortsFeatures.event;
private _portsFeaturesEnabled: boolean = false;
......@@ -621,19 +627,20 @@ class RemoteExplorerService implements IRemoteExplorerService {
this.tunnelModel.addEnvironmentTunnels(tunnelInformation?.environmentTunnels);
}
setEditable(tunnelItem: ITunnelItem | undefined, data: IEditableData | null): void {
setEditable(tunnelItem: ITunnelItem | undefined, editId: TunnelEditId, data: IEditableData | null): void {
if (!data) {
this._editable = undefined;
} else {
this._editable = { tunnelItem, data };
this._editable = { tunnelItem, data, editId };
}
this._onDidChangeEditable.fire(tunnelItem);
this._onDidChangeEditable.fire(tunnelItem ? { tunnel: tunnelItem, editId } : undefined);
}
getEditableData(tunnelItem: ITunnelItem | undefined): IEditableData | undefined {
getEditableData(tunnelItem: ITunnelItem | undefined, editId: TunnelEditId): IEditableData | undefined {
return (this._editable &&
((!tunnelItem && (tunnelItem === this._editable.tunnelItem)) ||
(tunnelItem && (this._editable.tunnelItem?.remotePort === tunnelItem.remotePort) && (this._editable.tunnelItem.remoteHost === tunnelItem.remoteHost)))) ?
(tunnelItem && (this._editable.tunnelItem?.remotePort === tunnelItem.remotePort) && (this._editable.tunnelItem.remoteHost === tunnelItem.remoteHost)
&& (this._editable.editId === editId)))) ?
this._editable.data : undefined;
}
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册