Skip to content

Commit 4ce446a

Browse files
committed
Add agents
1 parent a1540e0 commit 4ce446a

File tree

7 files changed

+154
-82
lines changed

7 files changed

+154
-82
lines changed

AGENTS.md

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
# Repository Guidelines
2+
3+
## Project Structure & Module Organization
4+
- `src/extension.ts`: Main VS Code extension (custom editor `csv.editor`, commands under `csv.*`).
5+
- `src/test/*.test.ts`: Unit tests (compiled to `out/test`).
6+
- `src/types/`: Type shims (e.g., `font-list.d.ts`).
7+
- `out/`: Transpiled JavaScript output.
8+
- `images/`: Marketplace icon and screenshots.
9+
- `package.json`: Activation events, commands, settings, and scripts.
10+
11+
## Build, Test, and Development Commands
12+
- `npm install`: Install dependencies.
13+
- `npm run compile`: TypeScript → `out/` via `tsc`.
14+
- `npm run lint`: ESLint over `**/*.ts` using `eslint.config.mjs`.
15+
- `npm test`: Compile, then run Node’s test runner on `out/test/**/*.test.js`.
16+
- `npm run package`: Create a `.vsix` using `vsce` (publish/build).
17+
18+
Example local loop:
19+
```
20+
npm install
21+
npm run lint && npm run compile
22+
npm test
23+
```
24+
25+
## Coding Style & Naming Conventions
26+
- Language: TypeScript with `strict` mode (see `tsconfig.json`).
27+
- Indentation: 2 spaces; include semicolons.
28+
- ESLint: prefer `===`, require curly braces, no throwing literals; import names camelCase/PascalCase.
29+
- Structure: keep functions small and pure where feasible; utilities that don’t touch VS Code APIs are easier to test.
30+
- Filenames: `kebab-case` for new files; tests end with `.test.ts`.
31+
32+
## Testing Guidelines
33+
- Framework: `node:test` with `assert`.
34+
- Location/pattern: `src/test/**/*.test.ts` → compiled to `out/test`.
35+
- Run: `npm test` (ensures a fresh compile).
36+
- Conventions: target pure utilities (parsing, type inference, color mapping, HTML escaping). Add tests when changing command behavior or settings logic. No formal coverage threshold; aim for meaningful cases.
37+
38+
## Commit & Pull Request Guidelines
39+
- Commits: imperative mood, concise. Optional prefixes like `fix:`/`chore:` to match history (e.g., “Add TSV support”, “chore: widen vscode engine compatibility”). Reference issues/PRs: `(#42)`.
40+
- PRs: clear description, linked issues, test plan, and screenshots/GIFs for UI changes. Note any new settings/commands and update `README.md` when user-facing.
41+
42+
## Security & Configuration Tips
43+
- Webview: escape all user data before injecting HTML; avoid `eval`/inline scripts. Be mindful of content security.
44+
- Settings: use `csv.*` keys declared in `package.json` and respect `csv.enabled`.
45+
- Compatibility: keep within engines in `package.json` (`vscode` and Node). Validate both `.csv` and `.tsv` flows.
46+

README.md

Lines changed: 34 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,9 @@ Working with CSV files shouldn’t be a chore. With CSV, you get:
3333
- **Enhanced Keyboard Navigation:** Navigate cells with Tab/Shift+Tab and use keyboard shortcuts for quick editing, saving, selection, and full-table `Ctrl/Cmd + A` select-all.
3434
- **Advanced Multi-Cell Selection:** Easily select and copy blocks of data, then paste them elsewhere as properly formatted CSV.
3535
- **Add/Delete Columns:** Right-click any cell to add a column left or right, or remove the selected column.
36+
- **Add/Delete Rows:** Insert above/below or remove the selected row via context menu.
3637
- **Edit Empty CSVs:** Create or open an empty CSV file and start typing immediately.
37-
- **Column Sorting:** Click column headers to sort ascending or descending.
38+
- **Column Sorting:** Right-click a header and choose A–Z or Z–A.
3839
- **Custom Font Selection:** Choose a font from a dropdown or inherit VS Code's default.
3940
- **Find & Highlight:** Built-in find widget helps you search for text within your CSV with real-time highlighting and navigation through matches.
4041
- **Preserved CSV Integrity:** All modifications respect CSV formatting—no unwanted extra characters or formatting issues.
@@ -71,41 +72,38 @@ Cursor (built on VS Code 1.99) and the latest VS Code releases (1.102).
7172

7273
---
7374

74-
## Planned Improvements
75+
## Commands
7576

76-
- **Row Insertion/Deletion:** Quickly add or remove rows without leaving the editor (coming soon).
77+
Open the Command Palette and search for:
78+
79+
- `CSV: Toggle Extension On/Off` (`csv.toggleExtension`)
80+
- `CSV: Toggle First Row as Header` (`csv.toggleHeader`)
81+
- `CSV: Toggle Serial Index Column` (`csv.toggleSerialIndex`)
82+
- `CSV: Change CSV Separator` (`csv.changeSeparator`)
83+
- `CSV: Change Font Family` (`csv.changeFontFamily`)
84+
85+
86+
## Settings
87+
88+
Configure in the Settings UI or `settings.json`:
89+
90+
- `csv.enabled` (boolean, default `true`): Enable/disable the custom editor.
91+
- `csv.treatFirstRowAsHeader` (boolean, default `true`): Treat the first row as a header.
92+
- `csv.addSerialIndex` (boolean, default `false`): Show a serial index column.
93+
- `csv.separator` (string, default `","`): Delimiter for parsing and saving.
94+
- `csv.fontFamily` (string, default empty): Override font family; falls back to `editor.fontFamily`.
95+
- `csv.cellPadding` (number, default `4`): Vertical cell padding in pixels.
96+
97+
Note: “Ignore First Rows” is a command-driven, per-file option stored by the extension.
7798

7899
---
79100

80101
## Release Notes
81102

82-
### v1.1.2
83-
- **Fixed:** fontFamily
84-
85-
### v1.1.0
86-
- **New:** Column sorting by clicking header labels.
87-
- **Added:** Font selection dropdown that honors VS Code font settings.
88-
- **Added:** Ability to create and edit empty CSV files.
89-
- **Improved:** Large CSV files load in 1000-row chunks for better performance.
90-
- **Enhanced:** `Ctrl/Cmd + A` now selects all cells in the grid.
91-
- **Fixed:** Correct row indexing when the header row is disabled.
92-
- **Improved:** Safer rendering for cells containing HTML-like text.
93-
94-
### v1.0.6
95-
- **New:** Multi-cell selection with intuitive `Shift + Click` support.
96-
- **Enhanced:** Clipboard integration for copying selected cells as clean, CSV-formatted text.
97-
- **Improved:** Navigation and editing, including better handling of special characters like quotes and commas.
98-
- **Added:** Advanced column type detection with dynamic color-coded highlighting.
99-
- **Refined:** Update mechanism for external document changes without interrupting your workflow.
100-
- **Configurable:** Added `csv.cellPadding` setting to adjust table cell padding.
101-
102-
### v1.0.2
103-
- **Improved:** Seamless activation of editing mode on double-click.
104-
- **Fixed:** `Tab` and `Shift + Tab` navigation issues, ensuring smooth cell-to-cell movement.
105-
- **Updated:** Sticky header styling now consistently matches the active theme.
106-
107-
### v1.0.0
108-
- **Initial Release:** Introduced a full-featured CSV with interactive cell editing, smart column sizing, and adaptive theme support.
103+
### v1.1.3
104+
- Added: TSV file support with automatic tab delimiter.
105+
106+
See full history in `CHANGELOG.md`.
109107

110108
---
111109

@@ -125,6 +123,12 @@ To create a VS Code extension package, run:
125123
npm run package
126124
```
127125

126+
To compile without running tests:
127+
128+
```bash
129+
npm run compile
130+
```
131+
128132
---
129133

130134
## Support

REVIEW.md

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
Repository Review and Prioritized Recommendations
2+
3+
Scope: VS Code custom editor for CSV/TSV (`csv.editor`). This document captures prioritized recommendations, their rationale, and current status.
4+
5+
Highest Priority (P0)
6+
- Preserve CSV correctness on edit: Always rebuild CSV with Papa when editing a single cell to keep quoting/escaping intact. Status: done.
7+
- Remove dead command wiring: `csv.changeIgnoreRows` was contributed but unimplemented. Removed from `package.json` and README. Status: done.
8+
- Provide referenced language configuration: `language-configuration.json` referenced but missing. Added minimal file to satisfy the contribution. Status: done.
9+
- Use current delimiter for copy: Copy-to-clipboard used comma regardless of settings/TSV. Now copies with the active delimiter. Status: done.
10+
- Harden virtual scrolling: Guard IntersectionObserver wiring when there is no initial row (empty/new docs). Status: done.
11+
- Tighten CSP: Replace `style-src 'unsafe-inline'` with a nonce-based policy matching the inline style tag. Status: done.
12+
13+
High Priority (P1)
14+
- README accuracy: Align sorting instructions with actual UX (context menu on header). Status: done.
15+
- Dependency/types cleanup: Remove unused `@types/mocha` and `mocha` from tsconfig types since tests use `node:test`. Status: done.
16+
- Remove built artifact from repo: Dropped tracked `.vsix`. Status: done.
17+
18+
Medium Priority (P2)
19+
- Sorting UX: Optional click-to-sort on header with visual indicator; currently available via context menu.
20+
- Keydown handler consolidation: Merge overlapping Escape handlers to reduce duplication.
21+
- Param clarity: Avoid parameter reassignment in `generateHtmlContent` (now using a local `headerFlag`). Status: done.
22+
23+
Notes and Rationale
24+
- Data integrity is paramount: CSV quoting/escaping can’t be reliably manipulated via line-slice editing; Papa ensures correctness.
25+
- UI consistency and discoverability: Updated README and ensured copy semantics align with user-chosen delimiter.
26+
- Security posture: CSP narrowed to nonce-based for styles and scripts; content remains escaped before injection.
27+
28+
Local Verification
29+
- Lint/tests were not executed in this environment due to sandbox limitations. Locally run:
30+
- `npm run lint`
31+
- `npm run compile`
32+
- `npm test`
33+
34+
Suggested Next Steps
35+
- Consider adding tests for: `getSeparator()` TSV default behavior and copy delimiter usage.
36+
- Optionally implement click-to-sort and add a test for numeric vs. locale string ordering.
37+

language-configuration.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{}

package.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,6 @@
105105
"papaparse": "^5.5.3"
106106
},
107107
"devDependencies": {
108-
"@types/mocha": "^10.0.10",
109108
"@types/node": "^20.11.19",
110109
"@types/papaparse": "^5.3.16",
111110
"@types/vscode": "^1.70.0",

src/extension.ts

Lines changed: 34 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -195,46 +195,22 @@ class CsvEditorProvider implements vscode.CustomTextEditorProvider {
195195

196196
/**
197197
* Updates a specific cell in the CSV document.
198-
* Tries a targeted edit first and falls back to rebuilding the CSV if necessary.
198+
* Always rebuilds via Papa to preserve quoting/escaping.
199199
*/
200200
private async updateDocument(row: number, col: number, value: string) {
201201
this.isUpdatingDocument = true;
202202
const separator = this.getSeparator();
203203
const oldText = this.document.getText();
204-
const lines = oldText.split(/\r?\n/);
205-
let editSucceeded = false;
206-
207-
if (row < lines.length) {
208-
const line = lines[row];
209-
const cells = line.split(separator);
210-
if (col < cells.length) {
211-
let startColOffset = 0;
212-
for (let i = 0; i < col; i++) {
213-
startColOffset += cells[i].length + separator.length;
214-
}
215-
const oldCellText = cells[col];
216-
const startPos = new vscode.Position(row, startColOffset);
217-
const endPos = new vscode.Position(row, startColOffset + oldCellText.length);
218-
const range = new vscode.Range(startPos, endPos);
219-
const edit = new vscode.WorkspaceEdit();
220-
edit.replace(this.document.uri, range, value);
221-
editSucceeded = await vscode.workspace.applyEdit(edit);
222-
}
223-
}
224-
225-
// If a direct cell edit fails, rebuild the entire CSV.
226-
if (!editSucceeded) {
227-
const result = Papa.parse(oldText, { dynamicTyping: false, delimiter: separator });
228-
const data = result.data as string[][];
229-
while (data.length <= row) data.push([]);
230-
while (data[row].length <= col) data[row].push('');
231-
data[row][col] = value;
232-
const newCsvText = Papa.unparse(data, { delimiter: separator });
233-
const fullRange = new vscode.Range(0, 0, this.document.lineCount, this.document.lineCount ? this.document.lineAt(this.document.lineCount - 1).text.length : 0);
234-
const edit = new vscode.WorkspaceEdit();
235-
edit.replace(this.document.uri, fullRange, newCsvText);
236-
await vscode.workspace.applyEdit(edit);
237-
}
204+
const result = Papa.parse(oldText, { dynamicTyping: false, delimiter: separator });
205+
const data = result.data as string[][];
206+
while (data.length <= row) data.push([]);
207+
while (data[row].length <= col) data[row].push('');
208+
data[row][col] = value;
209+
const newCsvText = Papa.unparse(data, { delimiter: separator });
210+
const fullRange = new vscode.Range(0, 0, this.document.lineCount, this.document.lineCount ? this.document.lineAt(this.document.lineCount - 1).text.length : 0);
211+
const edit = new vscode.WorkspaceEdit();
212+
edit.replace(this.document.uri, fullRange, newCsvText);
213+
await vscode.workspace.applyEdit(edit);
238214
this.isUpdatingDocument = false;
239215
console.log(`CSV: Updated row ${row + 1}, column ${col + 1} to "${value}"`);
240216
this.currentWebviewPanel?.webview.postMessage({ type: 'updateCell', row, col, value });
@@ -429,30 +405,31 @@ class CsvEditorProvider implements vscode.CustomTextEditorProvider {
429405
const data = result.data as string[][];
430406
const htmlContent = this.generateHtmlContent(data, treatHeader, addSerialIndex, fontFamily);
431407
const nonce = getNonce();
432-
this.currentWebviewPanel!.webview.html = this.wrapHtml(htmlContent, nonce, fontFamily, cellPadding);
408+
this.currentWebviewPanel!.webview.html = this.wrapHtml(htmlContent, nonce, fontFamily, cellPadding, separator);
433409
}
434410

435411
/**
436412
* Generates an HTML table from CSV data.
437413
*/
438414
private generateHtmlContent(data: string[][], treatHeader: boolean, addSerialIndex: boolean, fontFamily: string): string {
439415
/* ──────── NEW: ensure at least one editable cell ──────── */
416+
let headerFlag = treatHeader;
440417
if (data.length === 0) {
441418
data.push(['']); // single empty row + cell
442-
treatHeader = false; // no header in an empty sheet
419+
headerFlag = false; // no header in an empty sheet
443420
}
444421

445422
const isDark = vscode.window.activeColorTheme.kind === vscode.ColorThemeKind.Dark;
446-
const headerRow = treatHeader ? data[0] : [];
447-
const bodyData = treatHeader ? data.slice(1) : data;
423+
const headerRow = headerFlag ? data[0] : [];
424+
const bodyData = headerFlag ? data.slice(1) : data;
448425
const numColumns = Math.max(...data.map(row => row.length));
449426
const columnData = Array.from({ length: numColumns }, (_, i) => bodyData.map(row => row[i] || ''));
450427
const columnTypes = columnData.map(col => this.estimateColumnDataType(col));
451428
const columnColors = columnTypes.map((type, i) => this.getColumnColor(type, isDark, i));
452429
const columnWidths = this.computeColumnWidths(data);
453430
/* ──────────── VIRTUAL-SCROLL SUPPORT ──────────── */
454431
const CHUNK_SIZE = 1000; // rows per chunk
455-
const allRows = treatHeader ? bodyData : data;
432+
const allRows = headerFlag ? bodyData : data;
456433
const chunks: string[] = [];
457434

458435
if (allRows.length > CHUNK_SIZE) {
@@ -473,13 +450,18 @@ class CsvEditorProvider implements vscode.CustomTextEditorProvider {
473450
}
474451

475452
// keep **only** the first chunk in the initial render
476-
if (treatHeader) bodyData.length = CHUNK_SIZE;
477-
else data.length = CHUNK_SIZE;
453+
if (headerFlag) bodyData.length = CHUNK_SIZE;
454+
else data.length = CHUNK_SIZE;
478455
}
479456
/* ────────── END VIRTUAL-SCROLL SUPPORT ────────── */
480457

481-
let tableHtml = `<table>`;
482-
if (treatHeader) {
458+
// Generate CSS rules for per-column colors as a fallback to inline styles
459+
const colorCss = columnColors
460+
.map((hex, i) => `td[data-col="${i}"], th[data-col="${i}"] { color: ${hex}; }`)
461+
.join('');
462+
463+
let tableHtml = `<style>${colorCss}</style><table>`;
464+
if (headerFlag) {
483465
tableHtml += `<thead><tr>${
484466
addSerialIndex
485467
? `<th style="min-width: 4ch; max-width: 4ch; border: 1px solid ${isDark ? '#555' : '#ccc'}; background-color: ${isDark ? '#1e1e1e' : '#ffffff'}; color: #888;">#</th>`
@@ -530,7 +512,7 @@ class CsvEditorProvider implements vscode.CustomTextEditorProvider {
530512
/**
531513
* Wraps the provided HTML content in a complete HTML document with a strict Content Security Policy.
532514
*/
533-
private wrapHtml(content: string, nonce: string, fontFamily: string, cellPadding: number): string {
515+
private wrapHtml(content: string, nonce: string, fontFamily: string, cellPadding: number, separator: string): string {
534516
const isDark = vscode.window.activeColorTheme.kind === vscode.ColorThemeKind.Dark;
535517
return `<!DOCTYPE html>
536518
<html>
@@ -601,6 +583,7 @@ class CsvEditorProvider implements vscode.CustomTextEditorProvider {
601583
<script nonce="${nonce}">
602584
document.body.setAttribute('tabindex', '0'); document.body.focus();
603585
const vscode = acquireVsCodeApi();
586+
const CSV_SEPARATOR = ${JSON.stringify(separator)};
604587
let lastContextIsHeader = false; // remembers whether we right-clicked a <th>
605588
let isUpdating = false, isSelecting = false, anchorCell = null, rangeEndCell = null, currentSelection = [];
606589
let startCell = null, endCell = null, selectionMode = "cell";
@@ -616,7 +599,7 @@ class CsvEditorProvider implements vscode.CustomTextEditorProvider {
616599
const tbody = table.tBodies[0];
617600
618601
const loadNextChunk = () => {
619-
if (!csvChunks.length) return;
602+
if (!csvChunks.length || !tbody) return;
620603
tbody.insertAdjacentHTML('beforeend', csvChunks.shift());
621604
};
622605
@@ -625,12 +608,14 @@ class CsvEditorProvider implements vscode.CustomTextEditorProvider {
625608
if (entries[0].isIntersecting) {
626609
loadNextChunk();
627610
// observe the new last <tr>
628-
io.observe(tbody.querySelector('tr:last-child'));
611+
const last = tbody && tbody.querySelector('tr:last-child');
612+
if (last) io.observe(last);
629613
}
630614
}, { root: scrollContainer, rootMargin: '0px 0px 200px 0px' });
631615
632616
// initial observation
633-
io.observe(tbody.querySelector('tr:last-child'));
617+
const initialLast = tbody && tbody.querySelector('tr:last-child');
618+
if (initialLast) io.observe(initialLast);
634619
}
635620
/* ───────── END VIRTUAL-SCROLL LOADER ───────── */
636621
@@ -1048,7 +1033,7 @@ class CsvEditorProvider implements vscode.CustomTextEditorProvider {
10481033
const cell = table.querySelector(selector);
10491034
rowVals.push(cell ? cell.innerText : '');
10501035
}
1051-
csv += rowVals.join(',') + '\\n';
1036+
csv += rowVals.join(CSV_SEPARATOR) + '\\n';
10521037
}
10531038
vscode.postMessage({ type: 'copyToClipboard', text: csv.trimEnd() });
10541039
};

tsconfig.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,10 @@
66
"outDir": "./out",
77
"strict": true,
88
"esModuleInterop": true,
9-
"types": ["node", "vscode", "mocha"],
9+
"types": ["node", "vscode"],
1010
"skipLibCheck": true
1111
},
1212
"include": ["./**/*.ts"],
1313
"exclude": ["node_modules"]
1414
}
15-
15+

0 commit comments

Comments
 (0)