Skip to content

Commit ced8dd2

Browse files
committed
feat: add configurable large-file open prompt via csv.maxFileSizeMB
1 parent f425650 commit ced8dd2

File tree

4 files changed

+123
-6
lines changed

4 files changed

+123
-6
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,7 @@ Global (Settings UI or `settings.json`):
9797
- `csv.fontFamily` (string, default empty): Override font family; falls back to `editor.fontFamily`.
9898
- `csv.cellPadding` (number, default `4`): Vertical cell padding in pixels.
9999
- `csv.clickableLinks` (boolean, default `true`): Make URLs in cells clickable. Ctrl/Cmd+click to open links.
100+
- `csv.maxFileSizeMB` (number, default `10`): Soft limit for opening files in CSV view. If exceeded, CSV prompts: `Cancel`, `Continue This Time`, or `Ignore Forever` (sets this setting to `0`).
100101
- Per-file encoding: use `CSV: Change File Encoding` to set a file's encoding (e.g., `utf8`, `utf16le`, `windows1250`, `gbk`). The extension will reopen the file using the chosen encoding.
101102

102103
Per-file (stored by the extension; set via commands):

package.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,12 @@
114114
"type": "boolean",
115115
"default": true,
116116
"description": "Make URLs in cells clickable. Ctrl/Cmd+click to open links."
117+
},
118+
"csv.maxFileSizeMB": {
119+
"type": "number",
120+
"default": 10,
121+
"minimum": 0,
122+
"description": "Soft max size in MB for opening files in CSV view. 0 disables the limit. When exceeded, CSV prompts to cancel, continue this time, or disable the limit."
117123
}
118124
}
119125
},

src/CsvEditorProvider.ts

Lines changed: 106 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,11 @@ import * as path from 'path';
66
class CsvEditorController {
77
// Note: Global registry lives on CsvEditorProvider (wrapper)
88

9+
private static readonly BYTES_PER_MB = 1024 * 1024;
10+
private static readonly DEFAULT_MAX_FILE_SIZE_MB = 10;
11+
private static readonly LARGE_FILE_CONTINUE_THIS_TIME = 'Continue This Time';
12+
private static readonly LARGE_FILE_IGNORE_FOREVER = 'Ignore Forever';
13+
914
private isUpdatingDocument = false;
1015
private isSaving = false;
1116
private currentWebviewPanel: vscode.WebviewPanel | undefined;
@@ -25,12 +30,12 @@ class CsvEditorController {
2530
const config = vscode.workspace.getConfiguration('csv', this.document.uri);
2631
if (!config.get<boolean>('enabled', true)) {
2732
// When disabled, immediately hand off to the default editor and close this tab
28-
try {
29-
const opts: any = { viewColumn: webviewPanel.viewColumn, preserveFocus: !webviewPanel.active, preview: webviewPanel.active ? webviewPanel.active : false };
30-
await vscode.commands.executeCommand('vscode.openWith', document.uri, 'default', opts);
31-
} finally {
32-
try { webviewPanel.dispose(); } catch {}
33-
}
33+
await this.openWithDefaultEditorAndClose(webviewPanel, document.uri);
34+
return;
35+
}
36+
37+
const proceed = await this.confirmLargeFileOpen(config, webviewPanel, _token);
38+
if (!proceed) {
3439
return;
3540
}
3641

@@ -125,6 +130,97 @@ class CsvEditorController {
125130
});
126131
}
127132

133+
private getMaxFileSizeLimitMb(config: vscode.WorkspaceConfiguration): number {
134+
const raw = Number(config.get<number>('maxFileSizeMB', CsvEditorController.DEFAULT_MAX_FILE_SIZE_MB));
135+
if (!Number.isFinite(raw) || raw <= 0) {
136+
return 0;
137+
}
138+
return raw;
139+
}
140+
141+
private shouldPromptForLargeFile(fileSizeBytes: number, maxFileSizeMB: number): boolean {
142+
if (!Number.isFinite(fileSizeBytes) || fileSizeBytes < 0) {
143+
return false;
144+
}
145+
if (!Number.isFinite(maxFileSizeMB) || maxFileSizeMB <= 0) {
146+
return false;
147+
}
148+
const thresholdBytes = Math.floor(maxFileSizeMB * CsvEditorController.BYTES_PER_MB);
149+
return fileSizeBytes > thresholdBytes;
150+
}
151+
152+
private formatSizeMb(fileSizeBytes: number): string {
153+
if (!Number.isFinite(fileSizeBytes) || fileSizeBytes <= 0) {
154+
return '0.0';
155+
}
156+
return (fileSizeBytes / CsvEditorController.BYTES_PER_MB).toFixed(1);
157+
}
158+
159+
private async openWithDefaultEditorAndClose(webviewPanel: vscode.WebviewPanel, uri: vscode.Uri): Promise<void> {
160+
try {
161+
const opts: any = {
162+
viewColumn: webviewPanel.viewColumn,
163+
preserveFocus: !webviewPanel.active,
164+
preview: webviewPanel.active ? webviewPanel.active : false
165+
};
166+
await vscode.commands.executeCommand('vscode.openWith', uri, 'default', opts);
167+
} finally {
168+
try { webviewPanel.dispose(); } catch {}
169+
}
170+
}
171+
172+
private async confirmLargeFileOpen(
173+
config: vscode.WorkspaceConfiguration,
174+
webviewPanel: vscode.WebviewPanel,
175+
token: vscode.CancellationToken
176+
): Promise<boolean> {
177+
const maxFileSizeMB = this.getMaxFileSizeLimitMb(config);
178+
if (maxFileSizeMB <= 0) {
179+
return true;
180+
}
181+
182+
let sizeBytes = 0;
183+
try {
184+
const stat = await vscode.workspace.fs.stat(this.document.uri);
185+
sizeBytes = Number(stat.size);
186+
} catch (err) {
187+
console.warn(`CSV: unable to stat file size for ${this.document.uri.toString()}`, err);
188+
return true;
189+
}
190+
191+
if (token.isCancellationRequested) {
192+
return false;
193+
}
194+
if (!this.shouldPromptForLargeFile(sizeBytes, maxFileSizeMB)) {
195+
return true;
196+
}
197+
198+
const fileLabel = path.basename(this.document.uri.fsPath || this.document.uri.path || this.document.uri.toString());
199+
const selected = await vscode.window.showWarningMessage(
200+
`CSV: "${fileLabel}" is ${this.formatSizeMb(sizeBytes)} MB and exceeds the csv.maxFileSizeMB limit (${maxFileSizeMB} MB).`,
201+
{
202+
modal: true,
203+
detail: 'Opening large files in CSV view can be slow and block the editor.'
204+
},
205+
CsvEditorController.LARGE_FILE_CONTINUE_THIS_TIME,
206+
CsvEditorController.LARGE_FILE_IGNORE_FOREVER
207+
);
208+
209+
if (selected === CsvEditorController.LARGE_FILE_CONTINUE_THIS_TIME) {
210+
return true;
211+
}
212+
if (selected === CsvEditorController.LARGE_FILE_IGNORE_FOREVER) {
213+
await vscode.workspace
214+
.getConfiguration('csv')
215+
.update('maxFileSizeMB', 0, vscode.ConfigurationTarget.Global);
216+
vscode.window.showInformationMessage('CSV: Large-file prompt disabled (csv.maxFileSizeMB = 0).');
217+
return true;
218+
}
219+
220+
try { webviewPanel.dispose(); } catch {}
221+
return false;
222+
}
223+
128224
public refresh() {
129225
const config = vscode.workspace.getConfiguration('csv', this.document.uri);
130226
if (!config.get<boolean>('enabled', true)) {
@@ -1384,6 +1480,10 @@ export class CsvEditorProvider implements vscode.CustomTextEditorProvider {
13841480
const c: any = new (CsvEditorController as any)({} as any);
13851481
return c.isAllowedExternalUrl(url);
13861482
},
1483+
shouldPromptForLargeFile(fileSizeBytes: number, maxFileSizeMB: number): boolean {
1484+
const c: any = new (CsvEditorController as any)({} as any);
1485+
return c.shouldPromptForLargeFile(fileSizeBytes, maxFileSizeMB);
1486+
},
13871487
// Expose header heuristic for tests. Allows specifying hiddenRows and
13881488
// optionally an override value through a mock workspaceState.
13891489
getEffectiveHeader(data: string[][], hiddenRows: number, override: undefined | boolean = undefined): boolean {

src/test/provider-utils.test.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,16 @@ describe('CsvEditorProvider utility methods', () => {
6969
assert.strictEqual(allowed(''), false);
7070
});
7171

72+
it('large file prompt helper honors threshold and disabled limit', () => {
73+
const shouldPrompt = CsvEditorProvider.__test.shouldPromptForLargeFile;
74+
const mb = 1024 * 1024;
75+
assert.strictEqual(shouldPrompt(10 * mb, 10), false);
76+
assert.strictEqual(shouldPrompt(10 * mb + 1, 10), true);
77+
assert.strictEqual(shouldPrompt(50 * mb, 0), false);
78+
assert.strictEqual(shouldPrompt(50 * mb, -1), false);
79+
assert.strictEqual(shouldPrompt(50 * mb, Number.NaN), false);
80+
});
81+
7282
it('handles very large row counts without stack overflow', () => {
7383
const rows: string[][] = Array.from({ length: 70000 }, (_, i) => [String(i)]);
7484

0 commit comments

Comments
 (0)