Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adds UI to open Data Explorer files as plaintext #6132

Merged
merged 6 commits into from
Jan 30, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/

import { localize } from '../../../../nls.js';
import { IEditorPane } from '../../../common/editor.js';
import { DEFAULT_EDITOR_ASSOCIATION, EditorResourceAccessor, IEditorPane } from '../../../common/editor.js';
import { KeyCode, KeyMod } from '../../../../base/common/keyCodes.js';
import { ILocalizedString } from '../../../../platform/action/common/action.js';
import { ContextKeyExpr } from '../../../../platform/contextkey/common/contextkey.js';
Expand All @@ -18,8 +18,11 @@ import { INotificationService, Severity } from '../../../../platform/notificatio
import { IPositronDataExplorerEditor } from './positronDataExplorerEditor.js';
import { IPositronDataExplorerService, PositronDataExplorerLayout } from '../../../services/positronDataExplorer/browser/interfaces/positronDataExplorerService.js';
import { PositronDataExplorerEditorInput } from './positronDataExplorerEditorInput.js';
import { POSITRON_DATA_EXPLORER_IS_ACTIVE_EDITOR, POSITRON_DATA_EXPLORER_IS_COLUMN_SORTING, POSITRON_DATA_EXPLORER_LAYOUT } from './positronDataExplorerContextKeys.js';
import { POSITRON_DATA_EXPLORER_IS_ACTIVE_EDITOR, POSITRON_DATA_EXPLORER_IS_COLUMN_SORTING, POSITRON_DATA_EXPLORER_IS_PLAINTEXT, POSITRON_DATA_EXPLORER_LAYOUT } from './positronDataExplorerContextKeys.js';
import { Codicon } from '../../../../base/common/codicons.js';
import { PositronDataExplorerUri } from '../../../services/positronDataExplorer/common/positronDataExplorerUri.js';
import { URI } from '../../../../base/common/uri.js';
import { EditorOpenSource } from '../../../../platform/editor/common/editor.js';

/**
* Positron data explorer action category.
Expand Down Expand Up @@ -47,7 +50,8 @@ export const enum PositronDataExplorerCommandId {
ExpandSummaryAction = 'workbench.action.positronDataExplorer.expandSummary',
SummaryOnLeftAction = 'workbench.action.positronDataExplorer.summaryOnLeft',
SummaryOnRightAction = 'workbench.action.positronDataExplorer.summaryOnRight',
ClearColumnSortingAction = 'workbench.action.positronDataExplorer.clearColumnSorting'
ClearColumnSortingAction = 'workbench.action.positronDataExplorer.clearColumnSorting',
OpenAsPlaintext = 'workbench.action.positronDataExplorer.openAsPlaintext'
}

/**
Expand Down Expand Up @@ -679,6 +683,61 @@ class PositronDataExplorerClearColumnSortingAction extends Action2 {
}
}

/**
* PositronDataExplorerOpenAsPlaintextAction action.
*/
class PositronDataExplorerOpenAsPlaintextAction extends Action2 {
/**
* Constructor.
*/
constructor() {
super({
id: PositronDataExplorerCommandId.OpenAsPlaintext,
title: {
value: localize('positronDataExplorer.openAsPlaintext', 'Open as Plain Text File'),
original: 'Open as Plain Text File'
},
displayTitleOnActionBar: true,
category,
f1: true,
precondition: ContextKeyExpr.and(
POSITRON_DATA_EXPLORER_IS_ACTIVE_EDITOR,
POSITRON_DATA_EXPLORER_IS_PLAINTEXT
),
icon: Codicon.fileText,
menu: [
{
id: MenuId.EditorActionsLeft,
when: ContextKeyExpr.and(
POSITRON_DATA_EXPLORER_IS_ACTIVE_EDITOR,
POSITRON_DATA_EXPLORER_IS_PLAINTEXT
)
},
{
id: MenuId.EditorTitle,
group: 'navigation',
when: ContextKeyExpr.and(
POSITRON_DATA_EXPLORER_IS_ACTIVE_EDITOR,
POSITRON_DATA_EXPLORER_IS_PLAINTEXT
)
}
]
});
}

/**
* Runs the action.
* @param accessor The services accessor.
*/
async run(accessor: ServicesAccessor): Promise<void> {
// Access the services we need.
// const textEditorService = accessor.get(ITextEditorService);
const editorService = accessor.get(IEditorService);
const dataExplorerUri = URI.parse(PositronDataExplorerUri.parse(EditorResourceAccessor.getOriginalUri(editorService.activeEditor)!)!);
await editorService.openEditor({ resource: URI.parse(dataExplorerUri.fsPath), options: { override: DEFAULT_EDITOR_ASSOCIATION.id, source: EditorOpenSource.USER } });
}
}

/**
* Registers Positron data explorer actions.
*/
Expand All @@ -690,4 +749,5 @@ export function registerPositronDataExplorerActions() {
registerAction2(PositronDataExplorerSummaryOnLeftAction);
registerAction2(PositronDataExplorerSummaryOnRightAction);
registerAction2(PositronDataExplorerClearColumnSortingAction);
registerAction2(PositronDataExplorerOpenAsPlaintextAction);
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,7 @@ export const POSITRON_DATA_EXPLORER_IS_COLUMN_SORTING = new RawContextKey<boolea
'positronDataExplorerIsColumnSorting',
false
);
export const POSITRON_DATA_EXPLORER_IS_PLAINTEXT = new RawContextKey<boolean>(
'positronDataExplorerIsPlaintext',
false
);
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,8 @@ import { PositronDataExplorerUri } from '../../../services/positronDataExplorer/
import { IPositronDataExplorerService, PositronDataExplorerLayout } from '../../../services/positronDataExplorer/browser/interfaces/positronDataExplorerService.js';
import { PositronDataExplorerEditorInput } from './positronDataExplorerEditorInput.js';
import { PositronDataExplorerClosed, PositronDataExplorerClosedStatus } from '../../../browser/positronDataExplorer/components/dataExplorerClosed/positronDataExplorerClosed.js';
import { POSITRON_DATA_EXPLORER_IS_COLUMN_SORTING, POSITRON_DATA_EXPLORER_LAYOUT } from './positronDataExplorerContextKeys.js';
import { POSITRON_DATA_EXPLORER_IS_COLUMN_SORTING, POSITRON_DATA_EXPLORER_IS_PLAINTEXT, POSITRON_DATA_EXPLORER_LAYOUT } from './positronDataExplorerContextKeys.js';
import { URI } from '../../../../base/common/uri.js';

/**
* IPositronDataExplorerEditorOptions interface.
Expand Down Expand Up @@ -96,6 +97,11 @@ export class PositronDataExplorerEditor extends EditorPane implements IPositronD
*/
private readonly _isColumnSortingContextKey: IContextKey<boolean>;

/**
* Gets the is plaintext editable context key.
*/
private readonly _isPlaintextContextKey: IContextKey<boolean>;

/**
* The onSizeChanged event emitter.
*/
Expand Down Expand Up @@ -251,6 +257,9 @@ export class PositronDataExplorerEditor extends EditorPane implements IPositronD
this._isColumnSortingContextKey = POSITRON_DATA_EXPLORER_IS_COLUMN_SORTING.bindTo(
this._group.scopedContextKeyService
);
this._isPlaintextContextKey = POSITRON_DATA_EXPLORER_IS_PLAINTEXT.bindTo(
this._group.scopedContextKeyService
);
}

/**
Expand Down Expand Up @@ -353,6 +362,14 @@ export class PositronDataExplorerEditor extends EditorPane implements IPositronD
positronDataExplorerInstance.tableDataDataGridInstance.isColumnSorting
);

const uri = URI.parse(this._identifier);
if (uri.scheme === 'duckdb') {
this._isPlaintextContextKey.set(PLAINTEXT_EXTS.some(ext => uri.path.endsWith(ext)));
} else {
this._isPlaintextContextKey.reset();
}


// Render the PositronDataExplorer.
this._positronReactRenderer.render(
<PositronDataExplorer
Expand Down Expand Up @@ -493,3 +510,8 @@ export class PositronDataExplorerEditor extends EditorPane implements IPositronD

//#endregion Private Methods
}

const PLAINTEXT_EXTS = [
".csv",
".tsv"
]
32 changes: 32 additions & 0 deletions test/e2e/tests/data-explorer/data-explorer-headless.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,20 @@ test.describe('Headless Data Explorer - Large Data Frame', {
test('Verifies headless data explorer functionality with large gzipped tsv file', async function ({ app, logger }) {
await testBody(app, logger, 'flights.tsv.gz');
});

test('Verifies headless data explorer can open csv file as plaintext', async function ({ app, logger }) {
const fileName = 'flights.csv';
const searchString = ',year,month,day,dep_time,sched_dep_time,dep_delay,arr_time,sched_arr_time,arr_delay,carrier,flight,tailnum,origin,dest,air_time,distance,hour,minute,time_hour';

await openAsPlaintext(app, fileName, searchString);
});

test('Verifies headless data explorer can open tsv file as plaintext', async function ({ app, logger }) {
const fileName = 'flights.tsv';
const searchString = /\s+year\s+month\s+day\s+dep_time\s+sched_dep_time\s+dep_delay\s+arr_time\s+sched_arr_time\s+arr_delay\s+carrier\s+flight\s+tailnum\s+origin\s+dest\s+air_time\s+distance\s+hour\s+minute\s+time_hour/;

await openAsPlaintext(app, fileName, searchString);
});
});

async function testBody(app: Application, logger: Logger, fileName: string) {
Expand All @@ -62,5 +76,23 @@ async function testBody(app: Application, logger: Logger, fileName: string) {
const lastRow = tableData.at(-1);
const lastHour = lastRow!['time_hour'];
expect(lastHour).toBe(LAST_CELL_CONTENTS);

// If file is plaintext (csv, tsv), check for the plaintext button in the actiobar
// Otherwise, ensure the button is not present
const shouldHavePlaintext = fileName.endsWith('.csv') || fileName.endsWith('.tsv');
const plaintextEl = app.code.driver.page.getByLabel('Open as Plain Text File');
expect(await plaintextEl.isVisible()).toBe(shouldHavePlaintext);
}).toPass();
}

async function openAsPlaintext(app: Application, fileName: string, searchString: string | RegExp) {
await app.workbench.quickaccess.openDataFile(join(app.workspacePathOrFolder, 'data-files', 'flights', fileName));
await app.workbench.quickaccess.runCommand('workbench.action.positronDataExplorer.openAsPlaintext');
await app.workbench.editor.waitForEditorContents(fileName, (contents) => {
if (searchString instanceof RegExp) {
return contents.search(searchString) !== -1;
} else {
return contents.includes(searchString);
}
});
}
4 changes: 4 additions & 0 deletions test/e2e/tests/data-explorer/helpers/100x100.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,10 @@ export const testDataExplorer = async (

// Return to Stacked layout
await app.workbench.layouts.enterLayout('stacked');

// Check that "open as plaintext" button is not available
const plaintextEl = app.code.driver.page.getByLabel('Open as Plain Text File');
expect(await plaintextEl.isVisible()).toBe(false);
};

export const parquetFilePath = (app: Application) => {
Expand Down