From 484ec6bcf5a3feeee3c575e95cb5dc5d91264df0 Mon Sep 17 00:00:00 2001 From: Brian Lambert Date: Thu, 29 Feb 2024 11:32:52 -0700 Subject: [PATCH] Updates to the Data Explorer... (#2360) * Adding Positron Data Explorer data type icons * Updates for table summary UI and other UI improvements * Update data explorer border, further rope off selection when it's disabled * Splitter fit and finish work, start of expandable columns in table summary * Basic expand/collapse of table summary rows * Work on expand/collapse of table summary rows * UI updates * Update splitter layout * Work on summary cell * Work. * Remove debug output * Work on data explorer cache * Update z-index of horizontal splitter * Fix comment --- .../lib/stylelint/vscode-known-variables.json | 1 + .../browser/ui/codicons/codicon/codicon.ttf | Bin 89764 -> 91456 bytes .../positronComponents/horizontalSplitter.css | 2 +- .../positronComponents/verticalSplitter.css | 2 +- .../components/tableDataPanel.tsx | 1 - .../components/tableSummaryPanel.tsx | 1 - .../dataExplorerPanel/dataExplorerPanel.css | 6 +- .../dataExplorerPanel/dataExplorerPanel.tsx | 2 +- .../classes/dataGridInstance.ts | 167 +++++++++-- .../components/dataGridColumnHeader.css | 2 +- .../components/dataGridCornerTopLeft.css | 2 +- .../components/dataGridRowCell.css | 35 +-- .../components/dataGridRowCell.tsx | 31 +- .../components/dataGridRowHeaders.css | 2 +- .../components/dataGridWaffle.tsx | 89 ++++-- .../ui/positronDataGrid/positronDataGrid.css | 8 - .../ui/positronDataGrid/positronDataGrid.tsx | 11 +- src/vs/base/common/codicons.ts | 8 + src/vs/workbench/common/theme.ts | 8 + .../browser/components/columnSummaryCell.css | 197 +++++++++++- .../browser/components/columnSummaryCell.tsx | 146 ++++++++- .../browser/components/profileNumber.css | 3 + .../browser/components/profileNumber.tsx | 53 ++++ .../browser/components/profileString.css | 3 + .../browser/components/profileString.tsx | 44 +++ .../browser/components/tableDataCell.css | 10 +- .../browser/tableDataDataGridInstance.tsx | 244 ++++----------- .../browser/tableSummaryDataGridInstance.tsx | 190 ++++++++---- .../common/dataExplorerCache.ts | 281 ++++++++++++++++++ 29 files changed, 1174 insertions(+), 375 deletions(-) create mode 100644 src/vs/workbench/services/positronDataExplorer/browser/components/profileNumber.css create mode 100644 src/vs/workbench/services/positronDataExplorer/browser/components/profileNumber.tsx create mode 100644 src/vs/workbench/services/positronDataExplorer/browser/components/profileString.css create mode 100644 src/vs/workbench/services/positronDataExplorer/browser/components/profileString.tsx create mode 100644 src/vs/workbench/services/positronDataExplorer/common/dataExplorerCache.ts diff --git a/build/lib/stylelint/vscode-known-variables.json b/build/lib/stylelint/vscode-known-variables.json index be0088783e4..b9f6516c70d 100644 --- a/build/lib/stylelint/vscode-known-variables.json +++ b/build/lib/stylelint/vscode-known-variables.json @@ -523,6 +523,7 @@ "--vscode-positronDataExplorer-border", "--vscode-positronDataExplorer-contrastBackground", "--vscode-positronDataExplorer-foreground", + "--vscode-positronDataExplorer-selectionBackground", "--vscode-positronDataGrid-background", "--vscode-positronDataGrid-border", "--vscode-positronDataGrid-contrastBackground", diff --git a/src/vs/base/browser/ui/codicons/codicon/codicon.ttf b/src/vs/base/browser/ui/codicons/codicon/codicon.ttf index 0d85e83a578f3cbf58aeed252e4a279fc06d364d..c873215b89e029233e8a38604d9b2c2963ef68b9 100644 GIT binary patch delta 5547 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 MpL(klueu=r1EEP@I{*Lx delta 3899 zcmXZf30xHQ8^`hItAb{NXa`s#D41j>XdZZ

l~_aw#{0a*2p|E^>)xhE0)~nW>SH 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 { instance={context.instance.tableDataDataGridInstance} width={props.width} height={props.height} - borderTop={true} /> diff --git a/src/vs/base/browser/ui/positronDataExplorer/components/dataExplorerPanel/components/tableSummaryPanel.tsx b/src/vs/base/browser/ui/positronDataExplorer/components/dataExplorerPanel/components/tableSummaryPanel.tsx index 528a728aa03..3bcd5cf81f6 100644 --- a/src/vs/base/browser/ui/positronDataExplorer/components/dataExplorerPanel/components/tableSummaryPanel.tsx +++ b/src/vs/base/browser/ui/positronDataExplorer/components/dataExplorerPanel/components/tableSummaryPanel.tsx @@ -39,7 +39,6 @@ export const TableSummaryPanel = (props: TableSummaryPanelProps) => { instance={context.instance.tableSchemaDataGridInstance} width={props.width} height={props.height} - borderTop={true} /> diff --git a/src/vs/base/browser/ui/positronDataExplorer/components/dataExplorerPanel/dataExplorerPanel.css b/src/vs/base/browser/ui/positronDataExplorer/components/dataExplorerPanel/dataExplorerPanel.css index aa77c49988b..8abbcc76666 100644 --- a/src/vs/base/browser/ui/positronDataExplorer/components/dataExplorerPanel/dataExplorerPanel.css +++ b/src/vs/base/browser/ui/positronDataExplorer/components/dataExplorerPanel/dataExplorerPanel.css @@ -9,9 +9,9 @@ .data-explorer-container .data-explorer-actions { - /* Place the data-explorer in the data-explorer-container. */ padding: 8px; grid-row: data-explorer-actions / data-explorer; + border-bottom: 1px solid var(--vscode-positronDataExplorer-border); } .data-explorer-container @@ -54,10 +54,6 @@ .data-explorer-container .data-explorer .column-2 { - /* Bordered. */ - /* border-radius: 4px; */ - /* border: 1px solid var(--vscode-positronDataExplorer-border); */ - /* Prevent 1fr from exceeding the parent height. */ min-height: 0; diff --git a/src/vs/base/browser/ui/positronDataExplorer/components/dataExplorerPanel/dataExplorerPanel.tsx b/src/vs/base/browser/ui/positronDataExplorer/components/dataExplorerPanel/dataExplorerPanel.tsx index 1e97b3539ed..5fb5fb3e0e0 100644 --- a/src/vs/base/browser/ui/positronDataExplorer/components/dataExplorerPanel/dataExplorerPanel.tsx +++ b/src/vs/base/browser/ui/positronDataExplorer/components/dataExplorerPanel/dataExplorerPanel.tsx @@ -23,7 +23,7 @@ import { PositronDataExplorerLayout } from 'vs/workbench/services/positronDataEx /** * Constants. */ -const MIN_COLUMN_WIDTH = 200; +const MIN_COLUMN_WIDTH = 275; const ACTIONS_HEIGHT = 64; const SUMMARY_HEIGHT = 24; diff --git a/src/vs/base/browser/ui/positronDataGrid/classes/dataGridInstance.ts b/src/vs/base/browser/ui/positronDataGrid/classes/dataGridInstance.ts index a15c7c57d96..1304587de1b 100644 --- a/src/vs/base/browser/ui/positronDataGrid/classes/dataGridInstance.ts +++ b/src/vs/base/browser/ui/positronDataGrid/classes/dataGridInstance.ts @@ -85,8 +85,26 @@ type ScrollbarOptions = | { /** * DisplayOptions type. */ -type DisplayOptions = { - cellBorder: boolean; +type DisplayOptions = | { + cellBorders?: boolean; +}; + +/** + * CursorOptions type. + */ +type CursorOptions = | { + cursor: false; + cursorOffset?: never; +} | { + cursor: true; + cursorOffset: number; +}; + +/** + * SelectionOptions type. + */ +type SelectionOptions = | { + selection?: boolean; }; /** @@ -99,7 +117,9 @@ type DataGridOptions = ColumnResizeOptions & RowResizeOptions & ScrollbarOptions & - DisplayOptions; + DisplayOptions & + CursorOptions & + SelectionOptions; /** * ExtendColumnSelectionBy enumeration. @@ -346,19 +366,24 @@ export abstract class DataGridInstance extends Disposable { private readonly _scrollbarWidth: number; /** - * Gets the column widths. + * Gets a value which indicates whether to show cell borders. */ - private readonly _columnWidths = new Map(); + private readonly _cellBorders: boolean; /** - * Gets the row heights. + * Gets a value which indicates whether to show the cursor. */ - private readonly _rowHeights = new Map(); + private readonly _cursor: boolean; /** - * Gets the column sort keys. + * Gets the cursor offset. */ - private readonly _columnSortKeys = new Map(); + private readonly _cursorOffset: number; + + /** + * Gets a value which indicates whether selection is enabled. + */ + private readonly _selection: boolean; /** * Gets or sets the width. @@ -373,12 +398,12 @@ export abstract class DataGridInstance extends Disposable { /** * Gets or sets the first column index. */ - protected _firstColumnIndex = 0; + private _firstColumnIndex = 0; /** * Gets or sets the first row index. */ - protected _firstRowIndex = 0; + private _firstRowIndex = 0; /** * Gets or sets the cursor column index. @@ -415,12 +440,31 @@ export abstract class DataGridInstance extends Disposable { */ private readonly _rowSelectionIndexes = new Set(); + /** + * Gets the column widths. + */ + private readonly _columnWidths = new Map(); + + /** + * Gets the row heights. + */ + private readonly _rowHeights = new Map(); + + /** + * Gets the column sort keys. + */ + private readonly _columnSortKeys = new Map(); + + //#endregion Private Properties + + //#region Protected Events + /** * The onDidUpdate event emitter. */ protected readonly _onDidUpdateEmitter = this._register(new Emitter); - //#endregion Private Properties + //#endregion Protected Events //#region Constructor & Dispose @@ -444,14 +488,21 @@ export abstract class DataGridInstance extends Disposable { this._defaultRowHeight = options.defaultRowHeight; this._columnResize = options.columnResize || false; - this._minimumColumnWidth = options.minimumColumnWidth ?? 0; + this._minimumColumnWidth = options.minimumColumnWidth ?? options.defaultColumnWidth; this._rowResize = options.rowResize || false; - this._minimumRowHeight = options.minimumRowHeight ?? 0; + this._minimumRowHeight = options.minimumRowHeight ?? options.defaultRowHeight; this._horizontalScrollbar = options.horizontalScrollbar || false; this._verticalScrollbar = options.verticalScrollbar || false; this._scrollbarWidth = options.scrollbarWidth ?? 0; + + this._cellBorders = options.cellBorders ?? true; + + this._cursor = options.cursor ?? true; + this._cursorOffset = this._cursor ? options.cursorOffset ?? 0 : 0; + + this._selection = options.selection ?? true; } //#endregion Constructor & Dispose @@ -556,6 +607,34 @@ export abstract class DataGridInstance extends Disposable { return this._scrollbarWidth; } + /** + * Gets a value which indicates whether to show cell borders. + */ + get cellBorder() { + return this._cellBorders; + } + + /** + * Gets a value which indicates whether to show the cursor. + */ + get cursor() { + return this._cursor; + } + + /** + * Gets the cursor offset. + */ + get cursorOffset() { + return this._cursorOffset; + } + + /** + * Gets a value which indicates whether selection is enabled. + */ + get selection() { + return this._selection; + } + /** * Gets the number of columns. */ @@ -594,6 +673,13 @@ export abstract class DataGridInstance extends Disposable { return layoutHeight; } + /** + * Gets the screen columns. + */ + get screenColumns() { + return Math.trunc(this._width / this._minimumColumnWidth) + 1; + } + /** * Gets the visible columns. */ @@ -623,6 +709,13 @@ export abstract class DataGridInstance extends Disposable { return Math.max(visibleColumns, 1); } + /** + * Gets the screen rows. + */ + get screenRows() { + return Math.trunc(this._height / this._minimumRowHeight) + 1; + } + /** * Gets the visible rows. */ @@ -736,6 +829,15 @@ export abstract class DataGridInstance extends Disposable { //#endregion Public Properties + //#region Public Events + + /** + * onDidUpdate event. + */ + readonly onDidUpdate = this._onDidUpdateEmitter.event; + + //#endregion Public Events + //#region Public Methods /** @@ -1824,20 +1926,16 @@ export abstract class DataGridInstance extends Disposable { return this._columnSortKeys.get(columnIndex); } - /** - * TODO. - */ - abstract initialize(): void; - /** * Sorts the data. * @param columnSorts The array of column sorts. * @returns A Promise that resolves when the data is sorted. */ - abstract sortData(columnSorts: IColumnSortKey[]): Promise; + async sortData(columnSorts: IColumnSortKey[]): Promise { + } /** - * + * Fetches data. */ abstract fetchData(): void; @@ -1853,7 +1951,9 @@ export abstract class DataGridInstance extends Disposable { * @param rowIndex The row index. * @returns The row header, or, undefined. */ - abstract rowHeader(rowIndex: number): JSX.Element | undefined; + rowHeader(rowIndex: number): JSX.Element | undefined { + return undefined; + } /** * Gets a data cell. @@ -1863,12 +1963,29 @@ export abstract class DataGridInstance extends Disposable { */ abstract cell(columnIndex: number, rowIndex: number): JSX.Element | undefined; + //#endregion Public Methods + + //#region Protected Methods + /** - * onDidUpdate event. + * Performs a soft reset of the data grid. */ - readonly onDidUpdate = this._onDidUpdateEmitter.event; + protected softReset() { + this._firstColumnIndex = 0; + this._firstRowIndex = 0; + this._cursorColumnIndex = 0; + this._cursorRowIndex = 0; + this._cellSelectionRange = undefined; + this._columnSelectionRange = undefined; + this._columnSelectionIndexes.clear(); + this._rowSelectionRange = undefined; + this._rowSelectionIndexes.clear(); + this._columnWidths.clear(); + this._rowHeights.clear(); + this._columnSortKeys.clear(); + } - //#endregion Public Methods + //#endregion Protected Methods //#region Private Methods diff --git a/src/vs/base/browser/ui/positronDataGrid/components/dataGridColumnHeader.css b/src/vs/base/browser/ui/positronDataGrid/components/dataGridColumnHeader.css index 932c6797127..df1407456d0 100644 --- a/src/vs/base/browser/ui/positronDataGrid/components/dataGridColumnHeader.css +++ b/src/vs/base/browser/ui/positronDataGrid/components/dataGridColumnHeader.css @@ -6,7 +6,7 @@ top: 0; bottom: 0; display: grid; - overflow: hidden; + /* overflow: hidden; */ position: absolute; background-color: var(--vscode-positronDataGrid-contrastBackground); grid-template-columns: [content] 1fr [right-gutter] 1px [end]; diff --git a/src/vs/base/browser/ui/positronDataGrid/components/dataGridCornerTopLeft.css b/src/vs/base/browser/ui/positronDataGrid/components/dataGridCornerTopLeft.css index 73400daebb9..f817781fc71 100644 --- a/src/vs/base/browser/ui/positronDataGrid/components/dataGridCornerTopLeft.css +++ b/src/vs/base/browser/ui/positronDataGrid/components/dataGridCornerTopLeft.css @@ -9,8 +9,8 @@ grid-row: headers / waffle; grid-column: headers / waffle; grid-template-columns: [start] 1fr [splitter] 1px [end]; - border-bottom: 1px solid var(--vscode-positronDataGrid-border); border-right: 1px solid var(--vscode-positronDataGrid-border); + border-bottom: 1px solid var(--vscode-positronDataGrid-border); background-color: var(--vscode-positronDataGrid-contrastBackground); } diff --git a/src/vs/base/browser/ui/positronDataGrid/components/dataGridRowCell.css b/src/vs/base/browser/ui/positronDataGrid/components/dataGridRowCell.css index 86c43b7d62d..3c300fe6c62 100644 --- a/src/vs/base/browser/ui/positronDataGrid/components/dataGridRowCell.css +++ b/src/vs/base/browser/ui/positronDataGrid/components/dataGridRowCell.css @@ -3,13 +3,7 @@ *--------------------------------------------------------------------------------------------*/ .data-grid-row-cell { - top: 0; - bottom: 0; - display: grid; position: absolute; - align-items: center; - grid-template-rows: [content] 1fr [splitter] 1px [end]; - grid-template-columns: [left-gutter] 7px [content] 1fr [right-gutter] 7px [splitter] 1px [end]; } .data-grid-row-cell.selected { @@ -24,8 +18,6 @@ left: 0; position: absolute; box-sizing: border-box; - /* border-right: 1px solid var(--vscode-positronDataGrid-border); */ - /* border-bottom: 1px solid var(--vscode-positronDataGrid-border); */ } .data-grid-row-cell @@ -34,7 +26,6 @@ border-bottom: 1px solid var(--vscode-positronDataGrid-border); } - .data-grid-row-cell .data-grid-row-cell-border-overlay.selected { border-right: 1px solid var(--vscode-positronDataGrid-selectionInnerBorder); @@ -63,10 +54,6 @@ .data-grid-row-cell .cursor-border { - top: 0; - left: 0; - right: 0; - bottom: 0; position: absolute; box-sizing: border-box; border: 1.5px solid var(--vscode-positronDataGrid-cursorBorder); @@ -74,18 +61,28 @@ .data-grid-row-cell .content { - margin-bottom: 1px; /* Adjust for the bottom border. */ - grid-column: content / right-gutter; + top: 0; + right: 0; + bottom: 0; + left: 0; + overflow: hidden; + position: absolute; } .data-grid-row-cell .vertical-splitter { - grid-row: content / end; - grid-column: splitter / end; + top: 0; + right: 0; + bottom: 0; + width: 1px; + position: absolute; } .data-grid-row-cell .horizontal-splitter { - grid-row: splitter / end; - grid-column: left-gutter / end; + right: 0; + bottom: 0; + left: 0; + height: 1px; + position: absolute; } diff --git a/src/vs/base/browser/ui/positronDataGrid/components/dataGridRowCell.tsx b/src/vs/base/browser/ui/positronDataGrid/components/dataGridRowCell.tsx index 5f32a62e736..ba48d361015 100644 --- a/src/vs/base/browser/ui/positronDataGrid/components/dataGridRowCell.tsx +++ b/src/vs/base/browser/ui/positronDataGrid/components/dataGridRowCell.tsx @@ -41,13 +41,21 @@ export const DataGridRowCell = (props: DataGridRowCellProps) => { */ const mouseDownHandler = (e: MouseEvent) => { if (isMacintosh ? e.metaKey : e.ctrlKey) { - // Individual cell selection is not supported. - } else if (e.shiftKey) { - context.instance.mouseSelectCell(props.columnIndex, props.rowIndex); - } else { + return; + } + + // If selection is enabled, process selection. + if (context.instance.selection) { + if (e.shiftKey) { + context.instance.mouseSelectCell(props.columnIndex, props.rowIndex); + return; + } + context.instance.clearSelection(); - context.instance.setCursorPosition(props.columnIndex, props.rowIndex); } + + // Set the cursor position. + context.instance.setCursorPosition(props.columnIndex, props.rowIndex); }; // Get the selection states. @@ -75,7 +83,7 @@ export const DataGridRowCell = (props: DataGridRowCellProps) => { className={ positronClassNames( 'data-grid-row-cell-border-overlay', - { 'bordered': context.instance.columnResize }, + { 'bordered': context.instance.cellBorder }, { 'selected': cellSelectionState & CellSelectionState.Selected }, { 'selected-top': cellSelectionState & CellSelectionState.SelectedTop }, { 'selected-bottom': cellSelectionState & CellSelectionState.SelectedBottom }, @@ -84,9 +92,18 @@ export const DataGridRowCell = (props: DataGridRowCellProps) => { )} > { + context.instance.cursor && props.columnIndex === context.instance.cursorColumnIndex && props.rowIndex === context.instance.cursorRowIndex && -

+
}
diff --git a/src/vs/base/browser/ui/positronDataGrid/components/dataGridRowHeaders.css b/src/vs/base/browser/ui/positronDataGrid/components/dataGridRowHeaders.css index 5c7f40996ff..631a6595e69 100644 --- a/src/vs/base/browser/ui/positronDataGrid/components/dataGridRowHeaders.css +++ b/src/vs/base/browser/ui/positronDataGrid/components/dataGridRowHeaders.css @@ -3,7 +3,7 @@ *--------------------------------------------------------------------------------------------*/ .data-grid-row-headers { - overflow: hidden; + /* overflow: hidden; */ position: relative; grid-row: waffle / end-waffle; grid-column: headers / waffle; diff --git a/src/vs/base/browser/ui/positronDataGrid/components/dataGridWaffle.tsx b/src/vs/base/browser/ui/positronDataGrid/components/dataGridWaffle.tsx index bf01ed6cbad..ec0a4079d1f 100644 --- a/src/vs/base/browser/ui/positronDataGrid/components/dataGridWaffle.tsx +++ b/src/vs/base/browser/ui/positronDataGrid/components/dataGridWaffle.tsx @@ -23,8 +23,6 @@ import { DataGridColumnHeaders } from 'vs/base/browser/ui/positronDataGrid/compo import { DataGridScrollbarCorner } from 'vs/base/browser/ui/positronDataGrid/components/dataGridScrollbarCorner'; import { ExtendColumnSelectionBy, ExtendRowSelectionBy } from 'vs/base/browser/ui/positronDataGrid/classes/dataGridInstance'; -let renderCounter = 0; - /** * Constants. */ @@ -55,8 +53,6 @@ export const DataGridWaffle = (props: DataGridWaffleProps) => { // Main useEffect. This is where we set up event handlers. useEffect(() => { - context.instance.initialize(); - // Set the initial screen size. context.instance.setScreenSize(props.width, props.height); @@ -97,12 +93,18 @@ export const DataGridWaffle = (props: DataGridWaffleProps) => { switch (e.code) { // Space key. case 'Space': { - if (e.ctrlKey && !e.shiftKey) { - context.instance.selectColumn(context.instance.cursorColumnIndex); - } else if (e.shiftKey && !e.ctrlKey) { - context.instance.selectRow(context.instance.cursorRowIndex); - } if (isMacintosh ? e.metaKey : e.ctrlKey && e.shiftKey) { - context.instance.selectAll(); + // Consume the event. + consumeEvent(); + + // If selection is enabled, process the key. + if (context.instance.selection) { + if (e.ctrlKey && !e.shiftKey) { + context.instance.selectColumn(context.instance.cursorColumnIndex); + } else if (e.shiftKey && !e.ctrlKey) { + context.instance.selectRow(context.instance.cursorRowIndex); + } if (isMacintosh ? e.metaKey : e.ctrlKey && e.shiftKey) { + context.instance.selectAll(); + } } break; } @@ -243,14 +245,19 @@ export const DataGridWaffle = (props: DataGridWaffleProps) => { return; } - // Range selection. - if (e.shiftKey) { - context.instance.extendRowSelectionUp(ExtendRowSelectionBy.Row); - return; + // When selection is enabled, perform selection processing. + if (context.instance.selection) { + // Extend selection up. + if (e.shiftKey) { + context.instance.extendRowSelectionUp(ExtendRowSelectionBy.Row); + return; + } + + // Clear selection. + context.instance.clearSelection(); } - // ArrowUp clears the selection and moves the cursor up. - context.instance.clearSelection(); + // Move the cursor up. if (context.instance.cursorRowIndex > 0) { context.instance.setCursorRow(context.instance.cursorRowIndex - 1); context.instance.scrollToCursor(); @@ -268,14 +275,19 @@ export const DataGridWaffle = (props: DataGridWaffleProps) => { return; } - // Range selection. - if (e.shiftKey) { - context.instance.extendRowSelectionDown(ExtendRowSelectionBy.Row); - return; + // When selection is enabled, perform selection processing. + if (context.instance.selection) { + // Extend selection down. + if (e.shiftKey) { + context.instance.extendRowSelectionDown(ExtendRowSelectionBy.Row); + return; + } + + // Clear selection. + context.instance.clearSelection(); } - // ArrowUp clears the selection and moves the cursor up. - context.instance.clearSelection(); + // Move the cursor down. if (context.instance.cursorRowIndex < context.instance.rows - 1) { context.instance.setCursorRow(context.instance.cursorRowIndex + 1); context.instance.scrollToCursor(); @@ -293,13 +305,19 @@ export const DataGridWaffle = (props: DataGridWaffleProps) => { return; } - if (e.shiftKey) { - context.instance.extendColumnSelectionLeft(ExtendColumnSelectionBy.Column); - return; + // When selection is enabled, perform selection processing. + if (context.instance.selection) { + // Extend selection left. + if (e.shiftKey) { + context.instance.extendColumnSelectionLeft(ExtendColumnSelectionBy.Column); + return; + } + + // Clear selection. + context.instance.clearSelection(); } - // ArrowLeft clears the selection and moves the cursor to the left. - context.instance.clearSelection(); + // Moves the cursor left. if (context.instance.cursorColumnIndex > 0) { context.instance.setCursorColumn(context.instance.cursorColumnIndex - 1); context.instance.scrollToCursor(); @@ -317,12 +335,19 @@ export const DataGridWaffle = (props: DataGridWaffleProps) => { return; } - if (e.shiftKey) { - context.instance.extendColumnSelectionRight(ExtendColumnSelectionBy.Column); - return; + // When selection is enabled, perform selection processing. + if (context.instance.selection) { + // Extend selection right. + if (e.shiftKey) { + context.instance.extendColumnSelectionRight(ExtendColumnSelectionBy.Column); + return; + } + + // Clear selection. + context.instance.clearSelection(); } - // ArrowRight clears the selection and moves the cursor to the right. + // Move the cursor right. context.instance.clearSelection(); if (context.instance.cursorColumnIndex < context.instance.columns - 1) { context.instance.setCursorColumn(context.instance.cursorColumnIndex + 1); @@ -411,8 +436,6 @@ export const DataGridWaffle = (props: DataGridWaffleProps) => { top += context.instance.getRowHeight(rowIndex); } - console.log(`Render number #${++renderCounter}`); - // Render. return (
{ // Render. return ( -
+
diff --git a/src/vs/base/common/codicons.ts b/src/vs/base/common/codicons.ts index f7f7b0526e5..d3982d31738 100644 --- a/src/vs/base/common/codicons.ts +++ b/src/vs/base/common/codicons.ts @@ -645,6 +645,14 @@ export const Codicon = { positronEllipsis: register('positron-ellipsis', 0xf261), positronCheckMark: register('positron-check-mark', 0xf262), positronVerticalEllipsis: register('positron-vertical-ellipsis', 0xf263), + positronDataTypeArray: register('positron-data-type-array', 0xf264), + positronDataTypeBoolean: register('positron-data-type-boolean', 0xf265), + positronDataTypeDateTime: register('positron-data-type-date-time', 0xf266), + positronDataTypeDate: register('positron-data-type-date', 0xf267), + positronDataTypeNumber: register('positron-data-type-number', 0xf268), + positronDataTypeString: register('positron-data-type-string', 0xf269), + positronDataTypeStruct: register('positron-data-type-struct', 0xf26a), + positronDataTypeTime: register('positron-data-type-time', 0xf26b), // --- End Positron --- diff --git a/src/vs/workbench/common/theme.ts b/src/vs/workbench/common/theme.ts index 5257e7946ab..e615ad3818d 100644 --- a/src/vs/workbench/common/theme.ts +++ b/src/vs/workbench/common/theme.ts @@ -1830,6 +1830,14 @@ export const POSITRON_DATA_EXPLORER_CONTRAST_BACKGROUND_COLOR = registerColor('p hcLight: editorBackground }, localize('positronDataExplorer.contrastBackground', "Positron data explorer contrast background color.")); +// Positron data explorer selection background color. +export const POSITRON_DATA_EXPLORER_SELECTION_BACKGROUND_COLOR = registerColor('positronDataExplorer.selectionBackground', { + dark: lighten(editorBackground, 0.2), + light: darken(editorBackground, 0.05), + hcDark: editorBackground, + hcLight: editorBackground +}, localize('positronDataExplorer.selectionBackground', "Positron data explorer selection background color.")); + // Positron data explorer foreground color. export const POSITRON_DATA_EXPLORER_FOREGROUND_COLOR = registerColor('positronDataExplorer.foreground', { dark: editorForeground, diff --git a/src/vs/workbench/services/positronDataExplorer/browser/components/columnSummaryCell.css b/src/vs/workbench/services/positronDataExplorer/browser/components/columnSummaryCell.css index cf026bb3815..3ac88e1b28a 100644 --- a/src/vs/workbench/services/positronDataExplorer/browser/components/columnSummaryCell.css +++ b/src/vs/workbench/services/positronDataExplorer/browser/components/columnSummaryCell.css @@ -3,6 +3,201 @@ *--------------------------------------------------------------------------------------------*/ .data-grid-row-cell -.content { +.content +.column-summary { + top: 0; + right: 0; + bottom: 0; + left: 0; + z-index: 0; + display: grid; + overflow: hidden; + position: absolute; + grid-template-rows: [basic-info] 34px [profile-info] 1fr [end]; +} + +.data-grid-row-cell +.content +.column-summary +.cursor-background { + top: 2px; + right: 2px; + bottom: 2px; + left: 2px; + z-index: -1; + position: absolute; + border-radius: 4px; + background-color: var(--vscode-positronDataExplorer-selectionBackground); +} + +.data-grid-row-cell +.content +.column-summary +.basic-info { + display: grid; + align-items: center; + grid-row: basic-info / profile-info; + grid-template-columns: [left-gutter] 4px [expand-collapse] 25px [icon] 25px [title] 1fr [missing-values] min-content [right-gutter] 12px [end]; +} + +.data-grid-row-cell +.content +.column-summary +.basic-info +.expand-collapse-button { + width: 25px; + height: 25px; + display: flex; + cursor: pointer; + align-items: center; + justify-content: center; + grid-column: expand-collapse / icon; +} + +.data-grid-row-cell +.content +.column-summary +.basic-info +.expand-collapse-button:focus { + outline: none !important; +} + +.data-grid-row-cell +.content +.column-summary +.basic-info +.expand-collapse-button:focus-visible { + border-radius: 6px; + outline: 1px solid var(--vscode-focusBorder) !important; +} + +.data-grid-row-cell +.content +.column-summary +.basic-info +.data-type-icon { + width: 25px; + height: 25px; + opacity: 80%; + display: flex; + align-items: center; + justify-content: left; + grid-column: icon / title; +} + +.data-grid-row-cell +.content +.column-summary +.basic-info +.column-name { + display: flex; + font-weight: 600; + align-items: center; + justify-content: left; + grid-column: title / missing-values; +} +.data-grid-row-cell +.content +.column-summary +.basic-info +.missing-values { + display: flex; + font-size: 90%; + align-items: center; + justify-content: left; + grid-column: missing-values / right-gutter; +} + +.data-grid-row-cell +.content +.column-summary +.profile-info { + display: grid; + grid-row: profile-info / end; + grid-template-columns: [left-gutter] 54px [tabular-info] 1fr [right-gutter] 12px [end]; +} + +.data-grid-row-cell +.content +.column-summary +.profile-info +.tabular-info { + display: grid; + overflow: hidden; + grid-column: tabular-info / right-gutter; + grid-template-columns: [labels] min-content [values] min-content [end]; +} + +.data-grid-row-cell +.content +.column-summary +.profile-info +.tabular-info +.labels +{ + margin-right: 10px; + grid-column: labels / values; +} + +.data-grid-row-cell +.content +.column-summary +.profile-info +.tabular-info +.labels +.label { + height: 20px; +} + +.data-grid-row-cell +.content +.column-summary +.profile-info +.tabular-info +.values +{ + display: grid; + font-weight: 600; + text-align: left; + grid-column: values / end; + grid-template-columns: [whole-number-value] min-content [fractional-value] min-content [end]; +} + +.data-grid-row-cell +.content +.column-summary +.profile-info +.tabular-info +.values +.values-left +{ + font-weight: 600; + text-align: right; + font-variant-numeric: tabular-nums; + grid-column: whole-number-value / fractional-value; +} + +.data-grid-row-cell +.content +.column-summary +.profile-info +.tabular-info +.values +.values-right +{ + font-weight: 600; + text-align: left; + font-variant-numeric: tabular-nums; + grid-column: fractional-value / end; +} + +.data-grid-row-cell +.content +.column-summary +.profile-info +.tabular-info +.values +.value { + height: 20px; } diff --git a/src/vs/workbench/services/positronDataExplorer/browser/components/columnSummaryCell.tsx b/src/vs/workbench/services/positronDataExplorer/browser/components/columnSummaryCell.tsx index 3d91f68b08a..751275606b6 100644 --- a/src/vs/workbench/services/positronDataExplorer/browser/components/columnSummaryCell.tsx +++ b/src/vs/workbench/services/positronDataExplorer/browser/components/columnSummaryCell.tsx @@ -9,25 +9,161 @@ import 'vs/css!./columnSummaryCell'; import * as React from 'react'; // Other dependencies. -import { ColumnSchema } from 'vs/workbench/services/languageRuntime/common/positronDataExplorerComm'; +import { ProfileNumber } from 'vs/workbench/services/positronDataExplorer/browser/components/profileNumber'; +import { ProfileString } from 'vs/workbench/services/positronDataExplorer/browser/components/profileString'; +import { ColumnSchema, ColumnSchemaTypeDisplay } from 'vs/workbench/services/languageRuntime/common/positronDataExplorerComm'; +import { TableSummaryDataGridInstance } from 'vs/workbench/services/positronDataExplorer/browser/tableSummaryDataGridInstance'; /** * ColumnSummaryCellProps interface. */ interface ColumnSummaryCellProps { + instance: TableSummaryDataGridInstance; columnSchema: ColumnSchema; + columnIndex: number; } /** - * TableDataCell component. - * @param props A TableDataCellProps that contains the component properties. + * ColumnSummaryCell component. + * @param props A ColumnSummaryCellProps that contains the component properties. * @returns The rendered component. */ export const ColumnSummaryCell = (props: ColumnSummaryCellProps) => { + /** + * Returns the data type icon for the column schema. + * @returns The data type icon. + */ + const dataTypeIcon = () => { + // Determine the alignment based on type. + switch (props.columnSchema.type_display) { + case ColumnSchemaTypeDisplay.Number: + return 'codicon-positron-data-type-number'; + + case ColumnSchemaTypeDisplay.Boolean: + return 'codicon-positron-data-type-boolean'; + + case ColumnSchemaTypeDisplay.String: + return 'codicon-positron-data-type-string'; + + case ColumnSchemaTypeDisplay.Date: + return 'codicon-positron-data-type-date'; + + case ColumnSchemaTypeDisplay.Datetime: + return 'codicon-positron-data-type-date-time'; + + case ColumnSchemaTypeDisplay.Time: + return 'codicon-positron-data-type-time'; + + case ColumnSchemaTypeDisplay.Array: + return 'codicon-positron-data-type-array'; + + case ColumnSchemaTypeDisplay.Struct: + return 'codicon-positron-data-type-struct'; + + case ColumnSchemaTypeDisplay.Unknown: + return 'codicon-positron-data-type-unknown'; + + // This shouldn't ever happen. + default: + return 'codicon-question'; + } + }; + + /** + * Returns the profile component for the column. + * @returns The profile component. + */ + const profile = () => { + // Determine the alignment based on type. + switch (props.columnSchema.type_display) { + case ColumnSchemaTypeDisplay.Number: + return ; + + case ColumnSchemaTypeDisplay.Boolean: + return null; + + case ColumnSchemaTypeDisplay.String: + return ; + + case ColumnSchemaTypeDisplay.Date: + return null; + + case ColumnSchemaTypeDisplay.Datetime: + return null; + + case ColumnSchemaTypeDisplay.Time: + return null; + + case ColumnSchemaTypeDisplay.Array: + return null; + + case ColumnSchemaTypeDisplay.Struct: + return null; + + case ColumnSchemaTypeDisplay.Unknown: + return null; + + // This shouldn't ever happen. + default: + return null; + } + }; + + // Get the expanded state of the column. + const expanded = props.instance.isColumnExpanded(props.columnIndex); + // Render. return ( -
- {props.columnSchema.column_name} +
+ props.instance.toggleExpandedColumn(props.columnIndex) + } + > + {props.columnIndex === props.instance.cursorRowIndex && +
+ } +
+ {/* + + props.instance.toggleExpandedColumn(props.columnIndex) + } + > + {expanded ? +
: +
+ } + + */} + +
+ props.instance.toggleExpandedColumn(props.columnIndex) + } + > + {expanded ? +
: +
+ } +
+ +
+
+ {props.columnSchema.column_name} +
+
+ 29% +
+ +
+ {expanded && +
+ {profile()} +
+ }
); }; diff --git a/src/vs/workbench/services/positronDataExplorer/browser/components/profileNumber.css b/src/vs/workbench/services/positronDataExplorer/browser/components/profileNumber.css new file mode 100644 index 00000000000..65b2d6e3c49 --- /dev/null +++ b/src/vs/workbench/services/positronDataExplorer/browser/components/profileNumber.css @@ -0,0 +1,3 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (C) 2024 Posit Software, PBC. All rights reserved. + *--------------------------------------------------------------------------------------------*/ diff --git a/src/vs/workbench/services/positronDataExplorer/browser/components/profileNumber.tsx b/src/vs/workbench/services/positronDataExplorer/browser/components/profileNumber.tsx new file mode 100644 index 00000000000..679e9a7cdbe --- /dev/null +++ b/src/vs/workbench/services/positronDataExplorer/browser/components/profileNumber.tsx @@ -0,0 +1,53 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (C) 2024 Posit Software, PBC. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// CSS. +import 'vs/css!./profileNumber'; + +// React. +import * as React from 'react'; + +/** + * ProfileNumberProps interface. + */ +interface ProfileNumberProps { +} + +/** + * ProfileNumber component. + * @param props A ProfileNumberProps that contains the component properties. + * @returns The rendered component. + */ +export const ProfileNumber = (props: ProfileNumberProps) => { + return ( +
+
+
NA
+
Median
+
Mean
+
SD
+
Min
+
Max
+
+
+
+
12
+
1
+
4
+
2
+
5
+
102
+
+
+
 
+
.51
+
.20
+
.24
+
 
+
.44
+
+
+
+ ); +}; diff --git a/src/vs/workbench/services/positronDataExplorer/browser/components/profileString.css b/src/vs/workbench/services/positronDataExplorer/browser/components/profileString.css new file mode 100644 index 00000000000..65b2d6e3c49 --- /dev/null +++ b/src/vs/workbench/services/positronDataExplorer/browser/components/profileString.css @@ -0,0 +1,3 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (C) 2024 Posit Software, PBC. All rights reserved. + *--------------------------------------------------------------------------------------------*/ diff --git a/src/vs/workbench/services/positronDataExplorer/browser/components/profileString.tsx b/src/vs/workbench/services/positronDataExplorer/browser/components/profileString.tsx new file mode 100644 index 00000000000..229205cc65b --- /dev/null +++ b/src/vs/workbench/services/positronDataExplorer/browser/components/profileString.tsx @@ -0,0 +1,44 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (C) 2024 Posit Software, PBC. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// CSS. +import 'vs/css!./profileString'; + +// React. +import * as React from 'react'; + +/** + * ProfileStringProps interface. + */ +interface ProfileStringProps { +} + +/** + * ProfileString component. + * @param props A ProfileStringProps that contains the component properties. + * @returns The rendered component. + */ +export const ProfileString = (props: ProfileStringProps) => { + return ( +
+
+
NA
+
Empty
+
Unique:
+
+
+
+
12
+
1
+
4
+
+
+
 
+
.51
+
.20
+
+
+
+ ); +}; diff --git a/src/vs/workbench/services/positronDataExplorer/browser/components/tableDataCell.css b/src/vs/workbench/services/positronDataExplorer/browser/components/tableDataCell.css index 45a9e77bf19..bca5378463a 100644 --- a/src/vs/workbench/services/positronDataExplorer/browser/components/tableDataCell.css +++ b/src/vs/workbench/services/positronDataExplorer/browser/components/tableDataCell.css @@ -5,7 +5,11 @@ .data-grid-row-cell .content .text { + height: 100%; + display: flex; + margin: 0 7px; overflow: hidden; + align-items: center; white-space: nowrap; line-height: normal; text-overflow: ellipsis; @@ -14,18 +18,18 @@ .data-grid-row-cell .content .text.left { - text-align: left; + justify-content: left; } .data-grid-row-cell .content .text.center { - text-align: center; + justify-content: center; } .data-grid-row-cell .content .text.right { - text-align: right; + justify-content: right; font-variant-numeric: tabular-nums; } diff --git a/src/vs/workbench/services/positronDataExplorer/browser/tableDataDataGridInstance.tsx b/src/vs/workbench/services/positronDataExplorer/browser/tableDataDataGridInstance.tsx index aa0dcf020f8..c230fbe1543 100644 --- a/src/vs/workbench/services/positronDataExplorer/browser/tableDataDataGridInstance.tsx +++ b/src/vs/workbench/services/positronDataExplorer/browser/tableDataDataGridInstance.tsx @@ -8,19 +8,12 @@ import * as React from 'react'; // Other dependencies. import { IColumnSortKey } from 'vs/base/browser/ui/positronDataGrid/interfaces/columnSortKey'; import { DataGridInstance } from 'vs/base/browser/ui/positronDataGrid/classes/dataGridInstance'; +import { DataExplorerCache } from 'vs/workbench/services/positronDataExplorer/common/dataExplorerCache'; import { TableDataCell } from 'vs/workbench/services/positronDataExplorer/browser/components/tableDataCell'; import { TableDataRowHeader } from 'vs/workbench/services/positronDataExplorer/browser/components/tableDataRowHeader'; +import { ColumnSortKey, SchemaUpdateEvent } from 'vs/workbench/services/languageRuntime/common/positronDataExplorerComm'; import { PositronDataExplorerColumn } from 'vs/workbench/services/positronDataExplorer/browser/positronDataExplorerColumn'; import { DataExplorerClientInstance } from 'vs/workbench/services/languageRuntime/common/languageRuntimeDataExplorerClient'; -import { ColumnSortKey, SchemaUpdateEvent } from 'vs/workbench/services/languageRuntime/common/positronDataExplorerComm'; -import { - DataFetchRange, - FetchedData, - FetchedSchema, - TableDataCache, - TableSchemaCache, - SchemaFetchRange -} from 'vs/workbench/services/positronDataExplorer/common/positronDataExplorerCache'; /** * TableDataDataGridInstance class. @@ -33,13 +26,10 @@ export class TableDataDataGridInstance extends DataGridInstance { */ private readonly _dataExplorerClientInstance: DataExplorerClientInstance; - private tableShape?: [number, number]; - - private _dataCache?: TableDataCache; - private _lastFetchedData?: FetchedData; - - private _schemaCache?: TableSchemaCache; - private _lastFetchedSchema?: FetchedSchema; + /** + * Gets the data explorer cache. + */ + private readonly _dataExplorerCache: DataExplorerCache; //#endregion Private Properties @@ -54,108 +44,66 @@ export class TableDataDataGridInstance extends DataGridInstance { super({ columnHeaders: true, columnHeadersHeight: 34, - rowHeaders: true, rowHeadersWidth: 55, rowHeadersResize: true, - defaultColumnWidth: 200, defaultRowHeight: 24, - columnResize: true, minimumColumnWidth: 100, - - rowResize: false, - + rowResize: true, + minimumRowHeight: 24, horizontalScrollbar: true, verticalScrollbar: true, scrollbarWidth: 14, - - cellBorder: true + cellBorders: true, + cursor: true, + cursorOffset: 0.5, }); - // Set the data explorer client instance. + // Setup the data explorer client instance. this._dataExplorerClientInstance = dataExplorerClientInstance; - this._dataExplorerClientInstance.onDidSchemaUpdate(async (e: SchemaUpdateEvent) => { - this._lastFetchedData = undefined; - this._lastFetchedSchema = undefined; - - // Reset cursor to top left - // TODO: These attributes were made protected to allow this. Add a method to - // reset these without firing an update request which we don't want here yet. - this._firstColumnIndex = 0; - this._firstRowIndex = 0; + // Allocate and initialize the DataExplorerCache. + this._dataExplorerCache = new DataExplorerCache(dataExplorerClientInstance); + this._dataExplorerCache.onDidUpdateCache(() => this._onDidUpdateEmitter.fire()); - // Resets data schema, fetches table shape, initial schema, and data - this.initialize(); + this._dataExplorerClientInstance.onDidSchemaUpdate(async (e: SchemaUpdateEvent) => { + this.softReset(); + this.fetchData(); }); - - this._dataExplorerClientInstance.onDidDataUpdate(async (_evt) => { - this._lastFetchedData = undefined; - - const state = await this._dataExplorerClientInstance.getState(); - this.tableShape = [state.table_shape.num_rows, state.table_shape.num_columns]; - - this._dataCache?.clear(); + this._dataExplorerClientInstance.onDidDataUpdate(async () => { this.fetchData(); }); } //#endregion Constructor + //#region DataGridInstance Properties + /** * Gets the number of columns. */ get columns() { - return this.tableShape ? this.tableShape[1] : 0; + return this._dataExplorerCache.columns; } /** * Gets the number of rows. */ get rows() { - return this.tableShape ? this.tableShape[0] : 0; + return this._dataExplorerCache.rows; } - /** - * - */ - async initialize() { - const state = await this._dataExplorerClientInstance.getState(); - this.tableShape = [state.table_shape.num_rows, state.table_shape.num_columns]; - this._schemaCache = new TableSchemaCache( - this.tableShape, - async (req: SchemaFetchRange) => { - return this._dataExplorerClientInstance.getSchema(req.startIndex, - req.endIndex - req.startIndex); - } - ); - this._lastFetchedSchema = await this._schemaCache?.fetch({ startIndex: 0, endIndex: 1000 }); - this._dataCache = new TableDataCache( - this.tableShape, - async (req: DataFetchRange) => { - // Build the column indices to fetch. - const columnIndices: number[] = []; - for (let i = req.columnStartIndex; i < req.columnEndIndex; i++) { - columnIndices.push(i); - } - return this._dataExplorerClientInstance.getDataValues( - req.rowStartIndex, - req.rowEndIndex - req.rowStartIndex, - columnIndices - ); - }); + //#endregion DataGridInstance Properties - // Fetch data. - this.fetchData(); - } + //#region DataGridInstance Methods /** * Sorts the data. * @returns A Promise that resolves when the data is sorted. */ - async sortData(columnSorts: IColumnSortKey[]): Promise { + override async sortData(columnSorts: IColumnSortKey[]): Promise { // Set the sort columns. await this._dataExplorerClientInstance.setSortColumns(columnSorts.map(columnSort => ( { @@ -164,17 +112,22 @@ export class TableDataDataGridInstance extends DataGridInstance { } satisfies ColumnSortKey ))); - // Refetch data. - this.resetCache(); - await this.doFetchData(); + // Clear the data cache and fetch new data. + this._dataExplorerCache.invalidateDataCache(); + this.fetchData(); } - fetchData() { - this.doFetchData().then(() => { - - }).catch(x => { - console.log(x); - }); + /** + * Fetches data. + */ + override fetchData() { + // Update the cache. + this._dataExplorerCache.updateCache( + this.firstColumnIndex, + this.screenColumns, + this.firstRowIndex, + this.screenRows + ); } /** @@ -183,18 +136,14 @@ export class TableDataDataGridInstance extends DataGridInstance { * @returns The column. */ column(columnIndex: number) { - if (!this._lastFetchedSchema) { - return undefined; - } - - if (columnIndex < this._lastFetchedSchema.startIndex || - columnIndex >= this._lastFetchedSchema.endIndex) { + // Get the column schema. + const columnSchema = this._dataExplorerCache.getColumnSchema(columnIndex); + if (!columnSchema) { return undefined; } - const cachedSchemaIndex = columnIndex - this._lastFetchedSchema.startIndex; - - return new PositronDataExplorerColumn(this._lastFetchedSchema.schema.columns[cachedSchemaIndex]); + // Return the column. + return new PositronDataExplorerColumn(columnSchema); } /** @@ -202,37 +151,9 @@ export class TableDataDataGridInstance extends DataGridInstance { * @param rowIndex The row index. * @returns The row label, or, undefined. */ - rowHeader(rowIndex: number) { - // If the table schema hasn't been loaded, return undefined. - if (!this._lastFetchedSchema) { - return undefined; - } - - // If there isn't any cached data, return undefined. - if (!this._lastFetchedData) { - return undefined; - } - - // If the row isn't cached, return undefined. - if (rowIndex < this._lastFetchedData.rowStartIndex || - rowIndex > this._lastFetchedData.rowEndIndex - ) { - return undefined; - } - - // If there are no row labels, return the TableDataRowHeader. - if (!this._lastFetchedData.data.row_labels) { - return ( - - ); - } - - // Calculate the cached row index. - const cachedRowIndex = rowIndex - this._lastFetchedData.rowStartIndex; - - // Return the TableDataRowHeader. + override rowHeader(rowIndex: number) { return ( - + ); } @@ -243,78 +164,23 @@ export class TableDataDataGridInstance extends DataGridInstance { * @returns The cell value. */ cell(columnIndex: number, rowIndex: number): JSX.Element | undefined { - // We need the data and schema to render the cell - if (!this._lastFetchedData || !this._lastFetchedSchema) { + // Get the column. + const column = this.column(columnIndex); + if (!column) { return undefined; } - // Check that we have the schema and data values for this cell - if (columnIndex < this._lastFetchedSchema.startIndex || - columnIndex >= this._lastFetchedSchema.endIndex || - columnIndex < this._lastFetchedData.columnStartIndex || - columnIndex >= this._lastFetchedData.columnEndIndex || - rowIndex < this._lastFetchedData.rowStartIndex || - rowIndex >= this._lastFetchedData.rowEndIndex) { + // Get the cell. + const cell = this._dataExplorerCache.getCellValue(columnIndex, rowIndex); + if (!cell) { return undefined; } - // Calculate the cache indices. - const cachedSchemaIndex = columnIndex - this._lastFetchedSchema.startIndex; - const cachedColumnIndex = columnIndex - this._lastFetchedData.columnStartIndex; - const cachedRowIndex = rowIndex - this._lastFetchedData.rowStartIndex; - - // Get the column schema. - const columnSchema = this._lastFetchedSchema.schema.columns[cachedSchemaIndex]; - - // Get the cached value. - const value = this._lastFetchedData.data.columns[cachedColumnIndex][cachedRowIndex]; - // Return the TableDataCell. return ( - + ); } - //#region Private Methods - - private resetCache() { - // Clear the data cache - this._dataCache?.clear(); - this._lastFetchedData = undefined; - } - - private async doFetchData(): Promise { - const schemaRange: SchemaFetchRange = { - startIndex: this.firstColumnIndex, - endIndex: this.firstColumnIndex + this.visibleColumns + 1 - }; - - if (!this._lastFetchedSchema || - !this._schemaCache?.rangeIncludes(schemaRange, this._lastFetchedSchema)) { - this._lastFetchedSchema = await this._schemaCache?.fetch(schemaRange); - } - - const dataRange: DataFetchRange = { - rowStartIndex: this.firstRowIndex, - rowEndIndex: this.firstRowIndex + this.visibleRows, - columnStartIndex: this.firstColumnIndex, - - // TODO: column edge detection can cause visibleColumns to be one less than what the - // user actually sees, so we fudge this for now - columnEndIndex: this.firstColumnIndex + this.visibleColumns + 1 - }; - - if (!this._lastFetchedData || - !this._dataCache?.rangeIncludes(dataRange, this._lastFetchedData)) { - this._lastFetchedData = await this._dataCache?.fetch(dataRange); - } - - // Fire the onDidUpdate event. - this._onDidUpdateEmitter.fire(); - } - - //#endregion Private Methods + //#endregion DataGridInstance Methods } diff --git a/src/vs/workbench/services/positronDataExplorer/browser/tableSummaryDataGridInstance.tsx b/src/vs/workbench/services/positronDataExplorer/browser/tableSummaryDataGridInstance.tsx index d9ea76a77aa..cd49d6e241d 100644 --- a/src/vs/workbench/services/positronDataExplorer/browser/tableSummaryDataGridInstance.tsx +++ b/src/vs/workbench/services/positronDataExplorer/browser/tableSummaryDataGridInstance.tsx @@ -6,12 +6,17 @@ import * as React from 'react'; // Other dependencies. -import { IColumnSortKey } from 'vs/base/browser/ui/positronDataGrid/interfaces/columnSortKey'; import { DataGridInstance } from 'vs/base/browser/ui/positronDataGrid/classes/dataGridInstance'; -import { TableSchema } from 'vs/workbench/services/languageRuntime/common/positronDataExplorerComm'; -import { PositronDataExplorerColumn } from 'vs/workbench/services/positronDataExplorer/browser/positronDataExplorerColumn'; -import { DataExplorerClientInstance } from 'vs/workbench/services/languageRuntime/common/languageRuntimeDataExplorerClient'; +import { DataExplorerCache } from 'vs/workbench/services/positronDataExplorer/common/dataExplorerCache'; +import { ColumnSchemaTypeDisplay } from 'vs/workbench/services/languageRuntime/common/positronDataExplorerComm'; import { ColumnSummaryCell } from 'vs/workbench/services/positronDataExplorer/browser/components/columnSummaryCell'; +import { DataExplorerClientInstance } from 'vs/workbench/services/languageRuntime/common/languageRuntimeDataExplorerClient'; + +/** + * Constants. + */ +const SUMMARY_HEIGHT = 34; +const PROFILE_LINE_HEIGHT = 20; /** * TableSummaryDataGridInstance class. @@ -24,7 +29,15 @@ export class TableSummaryDataGridInstance extends DataGridInstance { */ private readonly _dataExplorerClientInstance: DataExplorerClientInstance; - private _tableSchema?: TableSchema; + /** + * Gets the data explorer cache. + */ + private readonly _dataExplorerCache: DataExplorerCache; + + /** + * Gets the expanded columns set. + */ + private readonly _expandedColumns = new Set(); //#endregion Private Properties @@ -38,28 +51,39 @@ export class TableSummaryDataGridInstance extends DataGridInstance { // Call the base class's constructor. super({ columnHeaders: false, - rowHeaders: false, - defaultColumnWidth: 200, - defaultRowHeight: 34, - + defaultRowHeight: SUMMARY_HEIGHT, columnResize: false, rowResize: false, - horizontalScrollbar: false, verticalScrollbar: true, - scrollbarWidth: 8, - - cellBorder: false + scrollbarWidth: 14, + cellBorders: false, + cursor: false, + selection: false }); // Set the data explorer client instance. this._dataExplorerClientInstance = dataExplorerClientInstance; + + // Allocate and initialize the DataExplorerCache. + this._dataExplorerCache = new DataExplorerCache(dataExplorerClientInstance); + this._dataExplorerCache.onDidUpdateCache(() => this._onDidUpdateEmitter.fire()); + + // Add the onDidSchemaUpdate event handler. + this._dataExplorerClientInstance.onDidSchemaUpdate(async () => { + this.setScreenPosition(0, 0); + this._expandedColumns.clear(); + this.fetchData(); + }); + } //#endregion Constructor + //#region DataGridInstance Properties + /** * Gets the number of columns. */ @@ -71,34 +95,18 @@ export class TableSummaryDataGridInstance extends DataGridInstance { * Gets the number of rows. */ get rows() { - return this._tableSchema ? this._tableSchema.columns.length : 0; + return this._dataExplorerCache.columns; } - /** - * - */ - initialize() { - this._dataExplorerClientInstance.getSchema(0, 1000).then(tableSchema => { - - console.log(`++++++++++ Schema returned with ${tableSchema.columns.length} columns`); - - this._tableSchema = tableSchema; - - this._onDidUpdateEmitter.fire(); + //#endregion DataGridInstance Properties - }).catch(x => { - - }); - } + //#region DataGridInstance Methods /** - * Sorts the data. - * @returns A Promise that resolves when the data is sorted. + * Fetches data. */ - async sortData(columnSorts: IColumnSortKey[]): Promise { - } - - fetchData() { + override fetchData() { + this._dataExplorerCache.updateCache(this.firstRowIndex, this.screenRows); } /** @@ -110,37 +118,83 @@ export class TableSummaryDataGridInstance extends DataGridInstance { } /** - * Gets a column. - * @param columnIndex The column index. - * @returns The column. + * Gets the the height of a row. + * @param rowIndex The row index. */ - column(columnIndex: number) { - // If the table schema hasn't been loaded, return undefined. - if (!this._tableSchema) { - return undefined; + override getRowHeight(rowIndex: number): number { + // If the column isn't expanded, return the summary height. + if (!this.isColumnExpanded(rowIndex)) { + return SUMMARY_HEIGHT; } - if (columnIndex < 0 || columnIndex > this._tableSchema.columns.length) { - return undefined; + // Get the column schema. If it hasn't been loaded yet, return the summary height. + const columnSchema = this._dataExplorerCache.getColumnSchema(rowIndex); + if (!columnSchema) { + return SUMMARY_HEIGHT; } - return new PositronDataExplorerColumn(this._tableSchema.columns[columnIndex]); + /** + * Returns the row height with the specified number of lines. + * @param profileLines + * @returns + */ + const rowHeight = (profileLines: number) => { + if (profileLines === 0) { + return SUMMARY_HEIGHT; + } else { + return SUMMARY_HEIGHT + (profileLines * PROFILE_LINE_HEIGHT) + 10; + } + }; + + // Return the row height. + switch (columnSchema.type_display) { + case ColumnSchemaTypeDisplay.Number: + return rowHeight(6); + + case ColumnSchemaTypeDisplay.Boolean: + return rowHeight(3); + + case ColumnSchemaTypeDisplay.String: + return rowHeight(3); + + case ColumnSchemaTypeDisplay.Date: + return rowHeight(7); + + case ColumnSchemaTypeDisplay.Datetime: + return rowHeight(7); + + case ColumnSchemaTypeDisplay.Time: + return rowHeight(7); + + case ColumnSchemaTypeDisplay.Array: + return rowHeight(2); + + case ColumnSchemaTypeDisplay.Struct: + return rowHeight(2); + + case ColumnSchemaTypeDisplay.Unknown: + return rowHeight(2); + + // This shouldn't ever happen. + default: + return rowHeight(0); + } } /** - * Gets a row header. - * @param rowIndex The row index. - * @returns The row header, or, undefined. + * Gets a column. + * @param columnIndex The column index. + * @returns The column. */ - rowHeader(rowIndex: number) { + column(columnIndex: number) { return undefined; } /** - * Gets a data cell. + * Gets a cell. * @param columnIndex The column index. * @param rowIndex The row index. - * @returns The cell value. + * @returns The cell. */ cell(columnIndex: number, rowIndex: number): JSX.Element | undefined { // Column index must be 0. @@ -148,28 +202,40 @@ export class TableSummaryDataGridInstance extends DataGridInstance { return undefined; } - // If the table schema hasn't been loaded, return undefined. - if (!this._tableSchema) { - return undefined; - } - - // If the column schema hasn't been loaded, return undefined. - if (rowIndex >= this._tableSchema.columns.length) { + // Get the column schema. + const columnSchema = this._dataExplorerCache.getColumnSchema(rowIndex); + if (!columnSchema) { return undefined; } - // Get the column schema. - const columnSchema = this._tableSchema.columns[rowIndex]; - // Return the ColumnSummaryCell. return ( ); } - //#region Private Methods + //#region DataGridInstance Methods + + //#region Public Methods + + isColumnExpanded(columnIndex: number) { + return this._expandedColumns.has(columnIndex); + } + + toggleExpandedColumn(columnIndex: number) { + if (this._expandedColumns.has(columnIndex)) { + this._expandedColumns.delete(columnIndex); + } else { + this._expandedColumns.add(columnIndex); + this.scrollToRow(columnIndex); + } + + this._onDidUpdateEmitter.fire(); + } //#endregion Private Methods } diff --git a/src/vs/workbench/services/positronDataExplorer/common/dataExplorerCache.ts b/src/vs/workbench/services/positronDataExplorer/common/dataExplorerCache.ts new file mode 100644 index 00000000000..8396f7c1aed --- /dev/null +++ b/src/vs/workbench/services/positronDataExplorer/common/dataExplorerCache.ts @@ -0,0 +1,281 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (C) 2024 Posit Software, PBC. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +import { Emitter } from 'vs/base/common/event'; +import { Disposable } from 'vs/base/common/lifecycle'; +import { ColumnSchema, TableData } from 'vs/workbench/services/languageRuntime/common/positronDataExplorerComm'; +import { DataExplorerClientInstance } from 'vs/workbench/services/languageRuntime/common/languageRuntimeDataExplorerClient'; + +/** + * Constants. + */ +const OVERSCAN_FACTOR = 3; + +/** + * Creates an array from an index range. + * @param startIndex The start index. + * @param endIndex The end index. + * @returns An array with the specified index range. + */ +const arrayFromIndexRange = (startIndex: number, endIndex: number) => + Array.from({ length: endIndex - startIndex + 1 }, (_, i) => startIndex + i); + +/** + * DataExplorerCache class. + */ +export class DataExplorerCache extends Disposable { + //#region Private Properties + + /** + * Gets or sets the columns. + */ + private _columns = 0; + + /** + * Gets or sets the rows. + */ + private _rows = 0; + + /** + * Gets the data explorer client instance that this data explorer cache is caching data for. + */ + private readonly _dataExplorerClientInstance: DataExplorerClientInstance; + + /** + * Gets the column schema cache. + */ + private readonly _columnSchemaCache = new Map(); + + /** + * Gets the row label cache. + */ + private readonly _rowLabelCache = new Map(); + + /** + * Gets the data cell cache. + */ + private readonly _dataCellCache = new Map(); + + /** + * The onDidUpdateCache event emitter. + */ + protected readonly _onDidUpdateCacheEmitter = this._register(new Emitter); + + //#endregion Private Properties + + //#region Constructor & Dispose + + /** + * Constructor. + * @param dataExplorerClientInstance The DataExplorerClientInstance. + */ + constructor(dataExplorerClientInstance: DataExplorerClientInstance) { + // Call the base class's constructor. + super(); + + // Set the data explorer client instance. + this._dataExplorerClientInstance = dataExplorerClientInstance; + + // Add the onDidSchemaUpdate event handler. + this._dataExplorerClientInstance.onDidSchemaUpdate(async () => { + // Clear the column schema cache, row label cache, and data cell cache. + this._columnSchemaCache.clear(); + this._rowLabelCache.clear(); + this._dataCellCache.clear(); + }); + + // Add the onDidDataUpdate event handler. + this._dataExplorerClientInstance.onDidDataUpdate(async () => { + // Clear the row label cache and data cell cache. + this._rowLabelCache.clear(); + this._dataCellCache.clear(); + }); + } + + //#endregion Constructor & Dispose + + //#region Public Properties + + /** + * Gets the columns. + */ + get columns() { + return this._columns; + } + + /** + * Gets the rows. + */ + get rows() { + return this._rows; + } + + //#endregion Public Properties + + //#region Public Events + + /** + * onDidUpdateCache event. + */ + readonly onDidUpdateCache = this._onDidUpdateCacheEmitter.event; + + //#endregion Public Events + + //#region Public Methods + + /** + * Invalidates the data cache. + */ + invalidateDataCache() { + this._rowLabelCache.clear(); + this._dataCellCache.clear(); + } + + /** + * Updates the cache for the specified columns and rows. If data caching isn't needed, omit the + * firstRowIndex and visibleRows parameters. + * @param firstColumnIndex The first column index. + * @param visibleColumns The number of visible columns. + * @param firstRowIndex The first row index. + * @param visibleRows The number of visible rows. + */ + async updateCache( + firstColumnIndex: number, + visibleColumns: number, + firstRowIndex?: number, + visibleRows?: number + ): Promise { + // Get the size of the data. + const tableState = await this._dataExplorerClientInstance.getState(); + this._columns = tableState.table_shape.num_columns; + this._rows = tableState.table_shape.num_rows; + + // Set the start column index and the end column index of the columns to cache. + const startColumnIndex = Math.max( + firstColumnIndex - (visibleColumns * OVERSCAN_FACTOR), + 0 + ); + const endColumnIndex = Math.min( + startColumnIndex + visibleColumns + (visibleColumns * OVERSCAN_FACTOR * 2), + this._columns - 1 + ); + + // Build an array of the column indicies to cache. + const columnIndicies = arrayFromIndexRange(startColumnIndex, endColumnIndex); + + // Build an array of the column schema indices that need to be cached. + const columnSchemaIndices = columnIndicies.filter(columnIndex => + !this._columnSchemaCache.has(columnIndex) + ); + + // Set the cache updated flag. + let cacheUpdated = false; + + // If there are column schema indices that need to be cached, cache them. + if (columnSchemaIndices.length) { + // Get the schema. + const tableSchema = await this._dataExplorerClientInstance.getSchema( + columnSchemaIndices[0], + columnSchemaIndices[columnSchemaIndices.length - 1] - columnSchemaIndices[0] + 1 + ); + + // Update the column schema cache, overwriting any entries we already have cached. + for (let i = 0; i < tableSchema.columns.length; i++) { + this._columnSchemaCache.set(columnSchemaIndices[0] + i, tableSchema.columns[i]); + } + + // Update the cache updated flag. + cacheUpdated = true; + } + + // If data is also being cached, update the data cache. + if (firstRowIndex !== undefined && visibleRows !== undefined) { + // Set the start row index and the end row index of the rows to cache. + const startRowIndex = Math.max( + firstRowIndex - (visibleRows * OVERSCAN_FACTOR), + 0 + ); + const endRowIndex = Math.min( + startRowIndex + visibleRows + (visibleRows * OVERSCAN_FACTOR * 2), + this._rows - 1 + ); + + // Build an array of the row indices that need to be cached. + const rowIndices: number[] = []; + for (let rowIndex = startRowIndex; rowIndex <= endRowIndex; rowIndex++) { + for (let columnIndex = startColumnIndex; columnIndex <= endColumnIndex; columnIndex++) { + if (!this._dataCellCache.has(`${columnIndex},${rowIndex}`)) { + rowIndices.push(rowIndex); + break; + } + } + } + + if (rowIndices.length) { + const rows = rowIndices[rowIndices.length - 1] - rowIndices[0] + 1; + const tableData: TableData = await this._dataExplorerClientInstance.getDataValues( + rowIndices[0], + rows, + columnIndicies + ); + + for (let row = 0; row < rows; row++) { + const rowIndex = rowIndices[row]; + + if (tableData.row_labels) { + const rowLabel = tableData.row_labels[0][row]; + this._rowLabelCache.set(rowIndex, rowLabel); + } + + for (let column = 0; column < columnIndicies.length; column++) { + const value = tableData.columns[column][row]; + + const columnIndex = columnIndicies[column]; + const rowIndex = rowIndices[row]; + + this._dataCellCache.set(`${columnIndex},${rowIndex}`, value); + } + } + + // Update the cache updated flag. + cacheUpdated = true; + } + } + + // If the cache was updated, fire the onDidUpdateCache event. + if (cacheUpdated) { + this._onDidUpdateCacheEmitter.fire(); + } + } + + /** + * Gets the column schema for the specified column index. + * @param columnIndex The column index. + * @returns The column schema for the specified column index. + */ + getColumnSchema(columnIndex: number) { + return this._columnSchemaCache.get(columnIndex); + } + + /** + * Gets the row label for the specified row index. + * @param rowIndex The row index. + * @returns The row label for the specified column index. + */ + getRowLabel(rowIndex: number) { + return this._rowLabelCache.get(rowIndex) ?? `${rowIndex}`; + } + + /** + * Gets the cell value for the specified column index and row index. + * @param columnIndex The column index. + * @param rowIndex The row index. + * @returns The cell value for the specified column index and row index. + */ + getCellValue(columnIndex: number, rowIndex: number) { + return this._dataCellCache.get(`${columnIndex},${rowIndex}`); + } + + //#endregion Public Methods +}