From c5b14184b2bbf4b73146b9618a57bffa84e7648d Mon Sep 17 00:00:00 2001 From: Dmitrii Shilov Date: Mon, 25 Nov 2024 09:37:53 +0100 Subject: [PATCH 1/2] feat: Deleted documents are highlighted - Deleted documents highlighted by line-through decoration - No actions with deleted rows --- src/docdb/session/DocumentSession.ts | 87 +++++++++++-------- src/docdb/session/QuerySession.ts | 46 ++++++++++ src/docdb/session/QuerySessionResult.ts | 2 + src/docdb/types/queryResult.ts | 2 + src/panels/QueryEditorTab.ts | 34 +++++--- src/utils/convertors.ts | 33 ++++--- src/utils/document.ts | 2 +- .../ResultPanel/ResultPanelToolbar.tsx | 12 ++- .../ResultPanel/ResultTabToolbar.tsx | 19 ++-- .../ResultPanel/ResultTabViewTable.tsx | 54 ++++++++---- .../state/QueryEditorContextProvider.tsx | 21 +++-- src/webviews/theme/slickgrid.scss | 4 + 12 files changed, 208 insertions(+), 108 deletions(-) diff --git a/src/docdb/session/DocumentSession.ts b/src/docdb/session/DocumentSession.ts index 0f2fc2510..cb9f7a264 100644 --- a/src/docdb/session/DocumentSession.ts +++ b/src/docdb/session/DocumentSession.ts @@ -19,11 +19,11 @@ import vscode from 'vscode'; import { ext } from '../../extensionVariables'; import { type Channel } from '../../panels/Communication/Channel/Channel'; import { getErrorMessage } from '../../panels/Communication/Channel/CommonChannel'; -import { extractPartitionKey } from '../../utils/document'; +import { extractPartitionKey, getDocumentId } from '../../utils/document'; import { localize } from '../../utils/localize'; import { type NoSqlQueryConnection } from '../NoSqlCodeLensProvider'; import { getCosmosClient, type CosmosDBCredential } from '../getCosmosClient'; -import { type CosmosDbRecord, type CosmosDbRecordIdentifier } from '../types/queryResult'; +import { type CosmosDbRecord, type CosmosDbRecordIdentifier, type QueryResultRecord } from '../types/queryResult'; export class DocumentSession { public readonly id: string; @@ -221,43 +221,60 @@ export class DocumentSession { }); } - public async delete(documentId: CosmosDbRecordIdentifier): Promise { - await callWithTelemetryAndErrorHandling('cosmosDB.nosql.document.session.delete', async (context) => { - this.setTelemetryProperties(context); - - if (this.isDisposed) { - throw new Error('Session is disposed'); - } + // Returns true if document was deleted, false if not, undefined if exception occurred + public async delete(documentId: CosmosDbRecordIdentifier): Promise { + return callWithTelemetryAndErrorHandling( + 'cosmosDB.nosql.document.session.delete', + async (context) => { + this.setTelemetryProperties(context); - if (documentId.id === undefined) { - throw new Error('Document id is required'); - } + if (this.isDisposed) { + throw new Error('Session is disposed'); + } - try { - const result = await this.client - .database(this.databaseId) - .container(this.containerId) - .item(documentId.id, documentId.partitionKey) - .delete({ - abortSignal: this.abortController.signal, - }); + if (documentId.id === undefined) { + throw new Error('Document id is required'); + } - if (result?.statusCode === 204) { - await this.channel.postMessage({ - type: 'event', - name: 'documentDeleted', - params: [this.id, documentId], - }); - } else { - await this.channel.postMessage({ - type: 'event', - name: 'documentError', - params: [this.id, 'Document deletion failed'], - }); + try { + const result = await this.client + .database(this.databaseId) + .container(this.containerId) + .item(documentId.id, documentId.partitionKey) + .delete({ + abortSignal: this.abortController.signal, + }); + + if (result?.statusCode === 204) { + await this.channel.postMessage({ + type: 'event', + name: 'documentDeleted', + params: [this.id, documentId], + }); + + return true; + } else { + await this.channel.postMessage({ + type: 'event', + name: 'documentError', + params: [this.id, 'Document deletion failed'], + }); + + return false; + } + } catch (error) { + await this.errorHandling(error, context); } - } catch (error) { - await this.errorHandling(error, context); - } + + return undefined; + }, + ); + } + + public async getDocumentId(document: QueryResultRecord): Promise { + return callWithTelemetryAndErrorHandling('cosmosDB.nosql.document.session.getDocumentId', async () => { + const partitionKey = await this.getPartitionKey(); + return getDocumentId(document, partitionKey); }); } diff --git a/src/docdb/session/QuerySession.ts b/src/docdb/session/QuerySession.ts index 53634b412..40d6f6a6a 100644 --- a/src/docdb/session/QuerySession.ts +++ b/src/docdb/session/QuerySession.ts @@ -20,6 +20,7 @@ import { type QueryResultRecord, type ResultViewMetadata, } from '../types/queryResult'; +import { DocumentSession } from './DocumentSession'; import { QuerySessionResult } from './QuerySessionResult'; export class QuerySession { @@ -234,6 +235,51 @@ export class QuerySession { }); } + public getDocumentId(row: number): Promise { + const result = this.sessionResult.getResult(this.currentIteration); + if (!result) { + throw new Error('No result found for current iteration'); + } + + const document = result.documents[row]; + if (!document) { + throw new Error(`No document found for row: ${row}`); + } + + const session = new DocumentSession(this.connection, this.channel); + return session.getDocumentId(document); + } + + public async deleteDocument(row: number): Promise { + const result = this.sessionResult.getResult(this.currentIteration); + if (!result) { + throw new Error('No result found for current iteration'); + } + + const document = result.documents[row]; + if (!document) { + throw new Error(`No document found for row: ${row}`); + } + + const session = new DocumentSession(this.connection, this.channel); + const documentId = await session.getDocumentId(document); + + if (!documentId) { + throw new Error('Document id not found'); + } + + const isDeleted = await session.delete(documentId); + if (isDeleted) { + result.deletedDocuments.push(row); + + await this.channel.postMessage({ + type: 'event', + name: 'queryResults', + params: [this.id, this.sessionResult.getSerializedResult(this.currentIteration), this.currentIteration], + }); + } + } + public dispose(): void { this.isDisposed = true; this.abortController?.abort(); diff --git a/src/docdb/session/QuerySessionResult.ts b/src/docdb/session/QuerySessionResult.ts index 983e8a704..5f5ee4f6c 100644 --- a/src/docdb/session/QuerySessionResult.ts +++ b/src/docdb/session/QuerySessionResult.ts @@ -63,6 +63,7 @@ export class QuerySessionResult { requestCharge: response.requestCharge, roundTrips: 1, // TODO: Is it required field? Query Pages Until Content Present hasMoreResults: response.hasMoreResults, + deletedDocuments: [], }); this.hasMoreResults = response.hasMoreResults; } @@ -84,6 +85,7 @@ export class QuerySessionResult { requestCharge: result.requestCharge, roundTrips: result.roundTrips, hasMoreResults: result.hasMoreResults, + deletedDocuments: result.deletedDocuments, query: this.query, }; diff --git a/src/docdb/types/queryResult.ts b/src/docdb/types/queryResult.ts index 97f6a7f0f..67e99cebb 100644 --- a/src/docdb/types/queryResult.ts +++ b/src/docdb/types/queryResult.ts @@ -51,6 +51,7 @@ export type QueryResult = { requestCharge: number; roundTrips: number; hasMoreResults: boolean; + deletedDocuments: number[]; }; export type SerializedQueryMetrics = { @@ -81,6 +82,7 @@ export type SerializedQueryResult = { requestCharge: number; roundTrips: number; hasMoreResults: boolean; + deletedDocuments: number[]; query: string; // The query that was executed }; diff --git a/src/panels/QueryEditorTab.ts b/src/panels/QueryEditorTab.ts index 88a7d56db..7ac1ab7fd 100644 --- a/src/panels/QueryEditorTab.ts +++ b/src/panels/QueryEditorTab.ts @@ -10,9 +10,8 @@ import * as vscode from 'vscode'; import { getNoSqlQueryConnection } from '../docdb/commands/connectNoSqlContainer'; import { getCosmosClientByConnection } from '../docdb/getCosmosClient'; import { type NoSqlQueryConnection } from '../docdb/NoSqlCodeLensProvider'; -import { DocumentSession } from '../docdb/session/DocumentSession'; import { QuerySession } from '../docdb/session/QuerySession'; -import { type CosmosDbRecordIdentifier, type ResultViewMetadata } from '../docdb/types/queryResult'; +import { type ResultViewMetadata } from '../docdb/types/queryResult'; import * as vscodeUtil from '../utils/vscodeUtils'; import { BaseTab, type CommandPayload } from './BaseTab'; import { DocumentTab } from './DocumentTab'; @@ -126,9 +125,13 @@ export class QueryEditorTab extends BaseTab { case 'firstPage': return this.firstPage(payload.params[0] as string); case 'openDocument': - return this.openDocument(payload.params[0] as string, payload.params[1] as CosmosDbRecordIdentifier); + return this.openDocument( + payload.params[0] as string, + payload.params[1] as string, + payload.params[2] as number, + ); case 'deleteDocument': - return this.deleteDocument(payload.params[0] as CosmosDbRecordIdentifier); + return this.deleteDocument(payload.params[0] as string, payload.params[1] as number); } return super.getCommand(payload); @@ -337,13 +340,13 @@ export class QueryEditorTab extends BaseTab { }); } - private async openDocument(mode: string, documentId?: CosmosDbRecordIdentifier): Promise { - await callWithTelemetryAndErrorHandling('cosmosDB.nosql.queryEditor.openDocument', () => { + private async openDocument(executionId: string, mode: string, row?: number): Promise { + await callWithTelemetryAndErrorHandling('cosmosDB.nosql.queryEditor.openDocument', async () => { if (!this.connection) { throw new Error('No connection'); } - if (!documentId && mode !== 'add') { + if (!row && mode !== 'add') { throw new Error('Impossible to open a document without an id'); } @@ -351,22 +354,29 @@ export class QueryEditorTab extends BaseTab { throw new Error(`Invalid mode: ${mode}`); } + const session = this.sessions.get(executionId); + if (!session) { + throw new Error(`No session found for executionId: ${executionId}`); + } + + const documentId = row ? await session.getDocumentId(row) : undefined; + DocumentTab.render(this.connection, mode, documentId, this.getNextViewColumn()); }); } - private async deleteDocument(documentId: CosmosDbRecordIdentifier): Promise { + private async deleteDocument(executionId: string, row: number): Promise { await callWithTelemetryAndErrorHandling('cosmosDB.nosql.queryEditor.deleteDocument', async () => { if (!this.connection) { throw new Error('No connection'); } - if (!documentId) { - throw new Error('Impossible to open a document without an id'); + const session = this.sessions.get(executionId); + if (!session) { + throw new Error(`No session found for executionId: ${executionId}`); } - const session = new DocumentSession(this.connection, this.channel); - await session.delete(documentId); + await session.deleteDocument(row); }); } diff --git a/src/utils/convertors.ts b/src/utils/convertors.ts index fa2d1ff3d..34b39f58a 100644 --- a/src/utils/convertors.ts +++ b/src/utils/convertors.ts @@ -20,6 +20,7 @@ export type TableRecord = Record & { __id: string }; export type TableData = { headers: string[]; dataset: TableRecord[]; + deletedRows: number[]; }; /** @@ -45,20 +46,13 @@ export const queryResultToJSON = (queryResult: SerializedQueryResult | null, sel return ''; } - if (selection) { - const selectedDocs = queryResult.documents - .map((doc, index) => { - if (!selection.includes(index)) { - return null; - } - return doc; - }) - .filter((doc) => doc !== null); - - return JSON.stringify(selectedDocs, null, 4); - } + const notDeletedRows = queryResult.documents + .map((_, index) => index) + .filter((index) => !queryResult.deletedDocuments.includes(index)); + const selectedRows = selection ? notDeletedRows.filter((index) => selection.includes(index)) : notDeletedRows; + const selectedDocs = queryResult.documents.filter((_, index) => selectedRows.includes(index)); - return JSON.stringify(queryResult.documents, null, 4); + return JSON.stringify(selectedDocs, null, 4); }; export const queryResultToTree = ( @@ -322,7 +316,7 @@ export const queryResultToTable = ( reorderColumns?: boolean, showServiceColumns?: boolean, ): TableData => { - let result: TableData = { headers: [], dataset: [] }; + let result: TableData = { headers: [], dataset: [], deletedRows: [] }; if (!queryResult) { return result; @@ -337,11 +331,13 @@ export const queryResultToTable = ( result = { headers: getTableHeadersWithRecordIdentifyColumns(queryResult.documents, partitionKey), dataset: getTableDatasetWithRecordIdentifyColumns(queryResult.documents, partitionKey), + deletedRows: queryResult.deletedDocuments, }; } else { result = { headers: getTableHeaders(queryResult.documents), dataset: getTableDataset(queryResult.documents), + deletedRows: queryResult.deletedDocuments, }; } @@ -517,9 +513,12 @@ export const queryResultToCsv = ( const tableView = queryResultToTable(queryResult, partitionKey); const headers = tableView.headers.join(','); - if (selection) { - tableView.dataset = tableView.dataset.filter((_, index) => selection.includes(index)); - } + const notDeletedRows = tableView.dataset + .map((_, index) => index) + .filter((index) => !tableView.deletedRows.includes(index)); + const selectedRows = selection ? notDeletedRows.filter((index) => selection.includes(index)) : notDeletedRows; + + tableView.dataset = tableView.dataset.filter((_, index) => selectedRows.includes(index)); const rows = tableView.dataset .map((row) => { diff --git a/src/utils/document.ts b/src/utils/document.ts index 374127354..90563db30 100644 --- a/src/utils/document.ts +++ b/src/utils/document.ts @@ -22,7 +22,7 @@ export const extractPartitionKey = (document: ItemDefinition, partitionKey: Part // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment interim = interim[prop]; } else { - return null; // It is not correct to return null, in other cases it should exception + return null; // It is not correct to return null, in other cases it should be exception } } if ( diff --git a/src/webviews/QueryEditor/ResultPanel/ResultPanelToolbar.tsx b/src/webviews/QueryEditor/ResultPanel/ResultPanelToolbar.tsx index d41bcbf8b..235eec5cb 100644 --- a/src/webviews/QueryEditor/ResultPanel/ResultPanelToolbar.tsx +++ b/src/webviews/QueryEditor/ResultPanel/ResultPanelToolbar.tsx @@ -97,7 +97,11 @@ const ToolbarGroupSave = ({ selectedTab }: ResultToolbarProps) => { const filename = `${state.dbName}_${state.collectionName}_${state.currentQueryResult?.activityId ?? 'query'}`; if (selectedTab === 'result__tab') { await dispatcher.saveToFile( - queryResultToCsv(state.currentQueryResult, state.partitionKey), + queryResultToCsv( + state.currentQueryResult, + state.partitionKey, + hasSelection ? state.selectedRows : undefined, + ), `${filename}_result`, 'csv', ); @@ -111,7 +115,11 @@ const ToolbarGroupSave = ({ selectedTab }: ResultToolbarProps) => { async function onSaveAsJSON() { const filename = `${state.dbName}_${state.collectionName}_${state.currentQueryResult?.activityId ?? 'query'}`; if (selectedTab === 'result__tab') { - await dispatcher.saveToFile(queryResultToJSON(state.currentQueryResult), `${filename}_result`, 'json'); + await dispatcher.saveToFile( + queryResultToJSON(state.currentQueryResult, hasSelection ? state.selectedRows : undefined), + `${filename}_result`, + 'json', + ); } if (selectedTab === 'stats__tab') { diff --git a/src/webviews/QueryEditor/ResultPanel/ResultTabToolbar.tsx b/src/webviews/QueryEditor/ResultPanel/ResultTabToolbar.tsx index 7b315d61f..79f3a92ea 100644 --- a/src/webviews/QueryEditor/ResultPanel/ResultTabToolbar.tsx +++ b/src/webviews/QueryEditor/ResultPanel/ResultTabToolbar.tsx @@ -7,8 +7,7 @@ import { type OptionOnSelectData } from '@fluentui/react-combobox'; import { Dropdown, Option, Toolbar, ToolbarButton, Tooltip, useRestoreFocusTarget } from '@fluentui/react-components'; import { AddFilled, DeleteRegular, EditRegular, EyeRegular } from '@fluentui/react-icons'; import { useMemo } from 'react'; -import { type CosmosDbRecordIdentifier } from 'src/docdb/types/queryResult'; -import { getDocumentId, isSelectStar } from '../../utils'; +import { isSelectStar } from '../../utils'; import { useQueryEditorDispatcher, useQueryEditorState } from '../state/QueryEditorContext'; import { type TableViewMode } from '../state/QueryEditorState'; @@ -29,14 +28,10 @@ export const ResultTabToolbar = ({ selectedTab }: ResultToolbarProps) => { ); const hasSelectedRows = state.selectedRows.length > 0; + const executionId = state.currentExecutionId; const getSelectedDocuments = () => { - return state.selectedRows - .map((rowIndex): CosmosDbRecordIdentifier | undefined => { - const document = state.currentQueryResult?.documents[rowIndex]; - return document ? getDocumentId(document, state.partitionKey) : undefined; - }) - .filter((document) => document !== undefined); + return state.selectedRows.filter((rowIndex) => !state.currentQueryResult?.deletedDocuments.includes(rowIndex)); }; const onOptionSelect = (data: OptionOnSelectData) => { @@ -55,14 +50,14 @@ export const ResultTabToolbar = ({ selectedTab }: ResultToolbarProps) => { } - onClick={() => void dispatcher.openDocument('add')} + onClick={() => void dispatcher.openDocument(executionId, 'add')} /> } - onClick={() => void dispatcher.openDocuments('view', getSelectedDocuments())} + onClick={() => void dispatcher.openDocuments(executionId, 'view', getSelectedDocuments())} disabled={!hasSelectedRows} /> @@ -70,7 +65,7 @@ export const ResultTabToolbar = ({ selectedTab }: ResultToolbarProps) => { } - onClick={() => void dispatcher.openDocuments('edit', getSelectedDocuments())} + onClick={() => void dispatcher.openDocuments(executionId, 'edit', getSelectedDocuments())} disabled={!hasSelectedRows} /> @@ -78,7 +73,7 @@ export const ResultTabToolbar = ({ selectedTab }: ResultToolbarProps) => { } - onClick={() => void dispatcher.deleteDocuments(getSelectedDocuments())} + onClick={() => void dispatcher.deleteDocuments(executionId, getSelectedDocuments())} disabled={!hasSelectedRows} /> diff --git a/src/webviews/QueryEditor/ResultPanel/ResultTabViewTable.tsx b/src/webviews/QueryEditor/ResultPanel/ResultTabViewTable.tsx index 216bb9614..448549a14 100644 --- a/src/webviews/QueryEditor/ResultPanel/ResultTabViewTable.tsx +++ b/src/webviews/QueryEditor/ResultPanel/ResultTabViewTable.tsx @@ -8,18 +8,19 @@ import * as React from 'react'; import { useEffect, useMemo, useRef, useState } from 'react'; import { SlickgridReact, + type Formatter, type GridOption, type OnDblClickEventArgs, type OnSelectedRowsChangedEventArgs, } from 'slickgrid-react'; -import { getDocumentId, isSelectStar, type TableData } from '../../utils'; +import { isSelectStar, type TableData } from '../../utils'; import { useQueryEditorDispatcher, useQueryEditorState } from '../state/QueryEditorContext'; type ResultTabViewTableProps = TableData & {}; -type GridColumn = { id: string; name: string; field: string; minWidth: number }; +type GridColumn = { id: string; name: string; field: string; minWidth: number; formatter: Formatter }; -export const ResultTabViewTable = ({ headers, dataset }: ResultTabViewTableProps) => { +export const ResultTabViewTable = ({ headers, dataset, deletedRows }: ResultTabViewTableProps) => { const state = useQueryEditorState(); const dispatcher = useQueryEditorDispatcher(); const gridRef = useRef(null); @@ -29,11 +30,8 @@ export const ResultTabViewTable = ({ headers, dataset }: ResultTabViewTableProps [state.currentQueryResult], ); - React.useEffect(() => { - gridRef.current?.gridService.renderGrid(); - }, [dataset, headers]); // Re-run when headers or data change - const [reservedHeaders, setReservedHeaders] = useState([]); + const [gridColumns, setGridColumns] = useState([]); useEffect(() => { setReservedHeaders(headers); @@ -45,14 +43,35 @@ export const ResultTabViewTable = ({ headers, dataset }: ResultTabViewTableProps headers = reservedHeaders; } - const gridColumns: GridColumn[] = headers.map((header) => { - return { - id: header + '_id', - name: header, - field: header.startsWith('/') ? header.slice(1) : header, - minWidth: 100, - }; - }); + useEffect(() => { + const gridColumns: GridColumn[] = headers.map((header) => { + return { + id: header + '_id', + name: header, + field: header.startsWith('/') ? header.slice(1) : header, + minWidth: 100, + formatter: (row: number, _cell: number, value: string) => { + if (deletedRows.includes(row)) { + return { + text: value, + addClasses: 'row-is-deleted', + toolTip: 'This document is deleted', + }; + } else { + return { + text: value, + }; + } + }, + }; + }); + + setGridColumns(gridColumns); + }, [headers, deletedRows]); + + React.useEffect(() => { + gridRef.current?.gridService.renderGrid(); + }, [dataset, headers]); // Re-run when headers or data change const onDblClick = (args: OnDblClickEventArgs) => { // If not in edit mode, do nothing @@ -60,9 +79,8 @@ export const ResultTabViewTable = ({ headers, dataset }: ResultTabViewTableProps // Open document in view mode const activeDocument = dataset[args.row]; - const documentId = activeDocument ? getDocumentId(activeDocument, state.partitionKey) : undefined; - if (documentId) { - void dispatcher.openDocument('view', documentId); + if (activeDocument && !deletedRows.includes(args.row)) { + void dispatcher.openDocument(state.currentExecutionId, 'view', args.row); } }; diff --git a/src/webviews/QueryEditor/state/QueryEditorContextProvider.tsx b/src/webviews/QueryEditor/state/QueryEditorContextProvider.tsx index 13a89e089..11bfe3c5a 100644 --- a/src/webviews/QueryEditor/state/QueryEditorContextProvider.tsx +++ b/src/webviews/QueryEditor/state/QueryEditorContextProvider.tsx @@ -5,7 +5,6 @@ import { type PartitionKeyDefinition } from '@azure/cosmos'; import { - type CosmosDbRecordIdentifier, DEFAULT_EXECUTION_TIMEOUT, DEFAULT_PAGE_SIZE, type ResultViewMetadata, @@ -87,20 +86,20 @@ export class QueryEditorContextProvider extends BaseContextProvider { this.dispatch({ type: 'setSelectedRows', selectedRows }); } - public async openDocument(mode: OpenDocumentMode, document?: CosmosDbRecordIdentifier): Promise { - await this.sendCommand('openDocument', mode, document); + public async openDocument(executionId: string, mode: OpenDocumentMode, row?: number): Promise { + await this.sendCommand('openDocument', executionId, mode, row); } - public async openDocuments(mode: OpenDocumentMode, documents: CosmosDbRecordIdentifier[]): Promise { - for (const document of documents) { - await this.openDocument(mode, document); + public async openDocuments(executionId: string, mode: OpenDocumentMode, rows: number[]): Promise { + for (const row of rows) { + await this.openDocument(executionId, mode, row); } } - public async deleteDocument(document: CosmosDbRecordIdentifier): Promise { - await this.sendCommand('deleteDocument', document); + public async deleteDocument(executionId: string, row: number): Promise { + await this.sendCommand('deleteDocument', executionId, row); } - public async deleteDocuments(documents: CosmosDbRecordIdentifier[]): Promise { - for (const document of documents) { - await this.deleteDocument(document); + public async deleteDocuments(executionId: string, rows: number[]): Promise { + for (const row of rows) { + await this.deleteDocument(executionId, row); } } diff --git a/src/webviews/theme/slickgrid.scss b/src/webviews/theme/slickgrid.scss index 6453feba7..bb7e3d65b 100644 --- a/src/webviews/theme/slickgrid.scss +++ b/src/webviews/theme/slickgrid.scss @@ -9,3 +9,7 @@ /* make sure to add the @import the SlickGrid Bootstrap Theme AFTER the variables changes */ @import '@slickgrid-universal/common/dist/styles/sass/slickgrid-theme-salesforce.scss'; + +.slick-cell.row-is-deleted { + text-decoration: line-through; +} From 5746eab69c045b85a00a77ae4d20a3db77988e4f Mon Sep 17 00:00:00 2001 From: Dmitrii Shilov Date: Mon, 25 Nov 2024 11:02:30 +0100 Subject: [PATCH 2/2] feat: Deleted documents are highlighted - Deleted documents highlighted by line-through decoration - No actions with deleted rows --- src/webviews/theme/slickgrid.scss | 1 + 1 file changed, 1 insertion(+) diff --git a/src/webviews/theme/slickgrid.scss b/src/webviews/theme/slickgrid.scss index bb7e3d65b..4fc99c1c6 100644 --- a/src/webviews/theme/slickgrid.scss +++ b/src/webviews/theme/slickgrid.scss @@ -12,4 +12,5 @@ .slick-cell.row-is-deleted { text-decoration: line-through; + color: var(--vscode-editor-findMatchBackground); }