Tabular data converter sidekick
Everyday authoring chores, from the keyboard:
- Convert a selected table between CSV, Markdown and HTML — all six directions.
- Round-trip tables through Excel via the clipboard.
- Transform tables in place — sort, transpose, deduplicate, reorder or remove columns.
- Extract Markdown headings from a selection or a whole document.
- Generate a linked Markdown table of contents from those headings.
- Flip path separators in a selection between Windows
\ and POSIX /.
- Save regex find/replace recipes and recall them straight into VS Code's Find/Replace widget.
Remote repositories
PK Tools works with text files opened through VS Code Remote - SSH, WSL, Dev Containers,
Codespaces, and desktop virtual workspaces such as Remote Repositories. Install the extension
in the location offered by VS Code, then use its commands from the editor as usual.
Because the extension works through VS Code's text-editor and clipboard APIs, read-only virtual
repositories can still be converted or copied. Commands that paste or align text require an
editable document, while conversions that open a new result editor leave the source unchanged.
The browser-only editors at vscode.dev and github.dev are not currently supported; this
release targets desktop VS Code, including desktop-connected remote and virtual workspaces.
Commands & default keybindings
Every command sits under the PK Tools category, so typing pk in the Command Palette
(Ctrl+Shift+P) lists all fifteen at once.
| Command |
Windows/Linux |
macOS |
| Convert Table… (pick the target format) |
Ctrl+Alt+T |
Cmd+Alt+T |
| Transform Table… |
unbound |
unbound |
| Paste Excel Cells as Markdown Table |
Ctrl+Alt+V |
Cmd+Alt+V |
| Regex Find/Replace Snippet… |
Ctrl+Alt+R |
Cmd+Alt+R |
| Convert Path Separators… |
Ctrl+Alt+P |
Cmd+Alt+P |
| Paste Table as… (pick the target format) |
unbound |
unbound |
| Copy Selection as Excel Cells |
unbound |
unbound |
| Open Table in Excel |
unbound |
unbound |
| Open All Markdown Tables in Excel (one sheet each) |
unbound |
unbound |
| Extract Markdown Headings |
unbound |
unbound |
| Generate Markdown Table of Contents |
unbound |
unbound |
| Insert or Update Markdown Table of Contents |
unbound |
unbound |
| Right-align Numeric Columns in Markdown Table |
unbound |
unbound |
| Convert Table to Markdown |
unbound |
unbound |
| Convert Table to HTML |
unbound |
unbound |
| Convert Table to CSV |
unbound |
unbound |
The editor right-click menu carries a PK Tools submenu with twelve everyday entries —
Convert Table…, Transform Table…, Paste Table as…, Copy Selection as Excel Cells, Open Table in
Excel, Open All Markdown Tables in Excel (one sheet each) and Regex Find/Replace Snippet…, plus
Extract Markdown Headings, both table-of-contents commands, Right-align Numeric Columns in
Markdown Table, and Convert Path Separators….
The two "…" pickers cover every target format, so the three explicit Convert Table
to Markdown/HTML/CSV commands and Paste Excel Cells as Markdown Table are left off the menu
to keep it short; they are still in the Command Palette and still bindable. The submenu is always
there while an editor has focus, whether or not you have a selection.
VS Code keybindings are keyboard-only; a mouse chord such as Alt+right-click cannot be
bound to a command, which is why the submenu hangs off the plain right-click menu.
All commands are ordinary VS Code commands — remap them in File → Preferences → Keyboard Shortcuts, or in keybindings.json:
{ "key": "ctrl+alt+m", "command": "pkTools.convertTableToMarkdown", "when": "editorTextFocus" }
Run Extract Markdown Headings with text selected to extract headings only from that
selection. With an empty selection, the command scans the whole document. The headings open in
a new untitled editor beside the source with the language mode set to Markdown; the original
document is not changed.
ATX headings (# Heading through ###### Heading) and Setext headings (text followed by ===
or ---) are preserved in document order. Heading-like text inside fenced or indented code
blocks is ignored.
Markdown table of contents
Run Generate Markdown Table of Contents to turn the headings in the current selection—or the
whole document when nothing is selected—into nested Markdown links. The TOC opens in a new editor
beside the source, so the original document is not modified.
- [Project Guide](#project-guide)
- [Installation](#installation)
- [Windows](#windows)
ATX and Setext heading levels determine the nesting. Inline formatting is removed from link text,
Unicode heading text is preserved, and repeated headings receive the usual -1, -2, … anchor
suffixes. Headings inside fenced or indented code blocks are ignored.
To put the result directly in the source, place the cursor where the TOC should go and run
Insert or Update Markdown Table of Contents. The command inserts a generated block surrounded
by blank lines:
<!-- pk-tools-toc:start -->
- [Project Guide](#project-guide)
<!-- pk-tools-toc:end -->
On later runs, the marker pair is found anywhere in the document and its contents are refreshed,
regardless of the cursor position. The replacement is one undoable editor operation. An incomplete,
reversed, or duplicate marker set is reported instead of risking the wrong edit. If all headings
have been removed, updating clears stale entries but keeps the two markers; a new empty block is
not inserted into a document with no headings.
Table conversion
Select a table and run a convert command. The source format is detected automatically, which is why three "convert to" commands cover all six directions.
The result opens in a new editor beside the original with the matching language mode. Your original text is never modified.
With nothing selected, the whole document is converted.
a,b,c | a | b | c | <table>
1,2,3 → | --- | --- | --- | → <thead>
| 1 | 2 | 3 | <tr>
<th>a</th>
Automatic column alignment
When the target is Markdown, each column's alignment is inferred from its values:
| Column contents |
Alignment |
Numbers — 1200, 35.50, $1,240.00, 12.5%, (123), 1e10 |
right |
Dates — 2026-07-21, 21/07/2026, 21 Jul 2026, 2026-07-21T09:30 |
right |
Times — 09:30, 13:45:30, 1:45 PM |
right |
Logical values — TRUE/FALSE, Yes/No, T/F |
center |
| Anything else |
left |
| product | units | price | launched | at | active |
| :--------- | ----: | --------: | ---------: | ----: | :----: |
| Widget | 1200 | $35.50 | 2026-07-21 | 09:30 | TRUE |
| Gadget Pro | 85 | $1,240.00 | 2026-08-01 | 14:05 | FALSE |
A column takes a type only when every value agrees — one about 80 among the numbers makes the whole column text. Blanks and null-ish markers (N/A, null, -, NaN) are skipped, so a stray gap doesn't demote a numeric column.
The header is ignored when inferring, since it's a label rather than data — a quantity column of numbers still aligns right.
Alignment the source already specifies — an HTML text-align, a Markdown ---: — is explicit intent and always wins over inference. Turn the whole feature off with "pkTools.alignColumnsByType": false.
To update an existing Markdown table in place, place the cursor anywhere inside the table and run
Right-align Numeric Columns in Markdown Table from the Command Palette or the editor's
PK Tools submenu. The command automatically selects the table around the cursor; an explicit
selection still takes precedence. If that selection contains multiple tables, every table is
processed independently while headings, paragraphs, and whitespace between them are preserved.
It changes only numeric separator cells to ---: and preserves every other line. In addition to
plain numbers, it recognizes report-style values such as
777 (77.7%), 84.7K THB,
**2.57B THB**, ฿23.7, 41 sqm, 14.2×, +1.59, −0.27, and
1.72B THB (latent).
Numerical ranges such as 1–30,
1-30, 1 to 30, $10–$20, and 1.5M–2.0M THB are numeric too. Mixed narrative values
remain unchanged.
What it handles
- CSV — RFC 4180:
" quoting, "" escapes, delimiters and newlines inside quoted fields, CRLF or LF. The delimiter is sniffed (, ; tab |) unless you pin it with pkTools.csvDelimiter.
- Markdown — optional outer pipes, column alignment (
:---, :---:, ---:) is read and preserved, \| escapes survive round trips, and cell newlines become <br>.
- HTML —
<thead>/<tbody> or a bare <tr> list, entity decoding, inline tags stripped from cells, text-align mapped to Markdown alignment. If there is no <th>, the first row becomes the header.
- Ragged rows are padded to the header width rather than producing a broken table.
Limitation: the HTML reader targets well-formed table fragments, not arbitrary HTML. Nested tables and colspan/rowspan are not supported — this is the trade-off for shipping with zero dependencies.
Run Transform Table… to edit a CSV, Markdown, or HTML table in place. Select a table first,
or place the cursor inside a Markdown table to target it automatically. With no selection and no
Markdown table at the cursor, a CSV or HTML document is used as a whole.
The picker offers five operations:
- Sort rows ascending or descending by a chosen column. Numeric columns sort by value, text
uses natural case-insensitive order, and blank or null-like cells remain at the bottom.
- Transpose rows and columns, treating the existing header as the first row.
- Remove duplicate rows, retaining the first exact occurrence.
- Reorder or remove columns by choosing the output columns from left to right, then selecting
Done to omit any columns that remain.
The transformed table stays in its original format and is applied as one undoable edit. Existing
column alignment follows reordered columns; a transposed Markdown table receives freshly inferred
alignment when automatic column alignment is enabled.
Excel round trip
Excel → Markdown
Copy a range of cells in Excel, then press Ctrl+Alt+V in your document. The cells arrive as a formatted Markdown table at the cursor (replacing the selection, if any).
Excel puts tab-separated text on the clipboard, so cells containing commas stay whole — Oslo, NO lands in one column, not two. Cells with line breaks or quotes come through correctly too. The clipboard may equally hold HTML (copied from a web page) or an existing Markdown table; the source is detected either way.
Excel → CSV (or HTML)
Paste Table as… does the same at the cursor, but asks for the target format first — Markdown,
CSV, or HTML. Copy a range in Excel, run it, pick CSV, and the cells land as proper CSV:
the delimiter is the one from pkTools.csvDelimiter, and quoting follows the column rule below.
Ctrl+Alt+V stays on the Markdown-only paste, so the fast path is unchanged.
Automatic quoting, by column
CSV output quotes a value whenever it contains the delimiter, a ", or a line break — and then
quotes every other value in that column too, so a column reads consistently instead of
alternating between quoted and bare rows. Columns that never need quotes stay bare, and the header
row is quoted only if the header text itself needs it.
id,name,note
1,"Smith, John",ok
2,"Lee","has
comma, here"
id is untouched; name and note are quoted throughout. Values inside quotes are escaped
RFC 4180-style (" becomes ""), so the result parses straight back to the original cells.
The TSV written for Excel's clipboard is the exception — it keeps the minimum quoting, since Excel
reads clipboard quotes inconsistently and a tab inside a cell is rare.
Markdown → Excel
Copy Selection as Excel Cells puts tab-separated, CRLF-terminated text on the clipboard, ready to paste straight into a worksheet — Excel drops it into proper cells rather than one column of text.
It takes its source from:
- your selection, if you have one; or
- the clipboard, if nothing is selected — so a Markdown table you just copied from somewhere else can be made Excel-pasteable without dropping it into an editor first.
CSV, Markdown and HTML tables are all accepted as input.
When the source is a Markdown table, its inline formatting is markup rather than data, so it is
dropped: **1,250** arrives as the number 1,250, `42` as 42, and [Docs](https://…) as
Docs. Emphasis, strikethrough, code spans, links, images and styling tags such as <b> are all
removed; a backslash-escaped \* stays literal, and snake_case_id keeps its underscores.
Cell whitespace is tidied along with it — a run of spaces or tabs collapses to one, a non-breaking
space becomes an ordinary one, and the padding around a <br> line break goes — so the alignment
spaces an author used to line the Markdown source up never reach the worksheet. The same
applies to Convert Table to CSV/HTML and Paste Table as… CSV/HTML — Markdown output keeps its
formatting, and formatting characters in a CSV or HTML source are data and are left alone.
Excel always separates columns with tabs, so clipboard text with no tab at all is read as a single column. That is what keeps a lone column of Smith, John values intact. Text with a consistent , or ; on every line is still recognised as real CSV.
Opening a table in Excel
Open Table in Excel skips the clipboard: it writes the table to a CSV file and hands that file to
whichever application owns .csv, which on a machine with Office installed is Excel.
It chooses what to export in this order:
- your selection, if you have one;
- the only Markdown table in the document — the common case, and no prompt appears;
- the table the cursor sits in, when the document has several;
- otherwise a table you pick from a list showing each table's headers, starting line and row
count.
A document with no Markdown table at all — a CSV, TSV or HTML file — is exported whole.
Markdown markup is dropped exactly as it is for Copy Selection as Excel Cells, so **1,250**
lands in the cell as a number. The file is written as UTF-8 with a byte order mark, without
which Excel would read non-ASCII text (Thai included) in the system code page and show mojibake.
The CSV is named after its document — sales.md opens as sales.csv, and its second table as
sales-table-2.csv — so Excel's title bar says what you are looking at. Files go to a pk-tools-excel
folder in the system temp directory; because Excel keeps a lock on a file it has open, re-exporting
the same table while it is open writes sales-2.csv rather than failing.
Every table at once, one sheet each
A CSV file holds one table, so a report with a dozen tables would mean a dozen exports.
Open All Markdown Tables in Excel (one sheet each) instead writes a real .xlsx workbook with
one sheet per Markdown table and opens it. It takes every table in the document, or only those
inside your selection when you have one.
Each sheet is named after the heading it sits under, so the workbook's tabs read like the
document's outline. Characters Excel forbids in a sheet name (: \ / ? * [ ]) are replaced, names
are cut to Excel's 31-character limit, a table under no heading becomes Table 3, and a repeated
heading becomes Revenue (2) — Excel compares sheet names without regard to case.
Values arrive as values rather than text: 1,250 and $1,250 as numbers, (320) as −320, 45%
as a percentage, and an unambiguous 2026-07-21 as a date. Other date orderings such as 07/21/26
stay text, because guessing day-first from month-first would silently corrupt them. Each sheet gets
a bold frozen header row, an autofilter, and columns roughly fitted to their contents.
The workbook is named after its document (sales.md → sales-tables.xlsx) and lands in the same
pk-tools-excel temp folder, stepping aside to sales-tables-2.xlsx when the previous one is still
open in Excel. The command is Markdown-only; for a CSV, TSV or HTML document use Open Table in
Excel.
Path separator conversion
Run Convert Path Separators… (Ctrl+Alt+P) and pick a direction:
- Toggle — flip whichever separator the text already favours, so a Windows path becomes a
POSIX one and vice versa. If the selection has an equal number of each (or none), the command
asks you to choose a direction instead.
- To Windows — replace every
/ with \.
- To POSIX — replace every
\ with /.
With a selection, every selected span is converted; several selections are handled in one
undoable edit. With nothing selected, the line under each cursor is converted, so you can flip
a path without selecting it first.
The swap is a literal one-for-one character replacement, which makes it exactly reversible.
Drive letters (C:\), UNC prefixes (\\host\share) and URL schemes (https://) are converted
along with everything else in the selection — keep the selection to the path when the surrounding
text has slashes of its own.
src/test/run.ts → src\test\run.ts
C:\Users\me\notes.md → C:/Users/me/notes.md
Regex find/replace snippets
Press Ctrl+Alt+R, pick a recipe, and the native Find/Replace widget opens with the pattern, the replacement, and the regex toggle already set. You review the matches and press Replace All — nothing is changed behind your back.
Typing in the Quick Pick matches the pattern as well as the name, so searching cita or \[.*\] both find the citation stripper.
Built-in snippets
| Name |
Find |
Replace |
| Strip Markdown citations/links |
\[.*\]\(.*\) |
(empty) |
| Markdown link → link text |
\[([^\]]*)\]\([^)]*\) |
$1 |
| Collapse multiple blank lines |
\n{3,} |
\n\n |
| Trim trailing whitespace |
[ \t]+$ |
(empty) |
| Strip HTML tags |
<[^>]+> |
(empty) |
Turn these off with "pkTools.includeBuiltInSnippets": false.
Adding your own
"pkTools.regexSnippets": [
{
"name": "Strip footnote markers",
"find": "\\[\\^\\d+\\]",
"replace": "",
"description": "Remove [^1]-style footnote references"
},
{
"name": "Thai digits → Arabic",
"find": "[๐-๙]",
"replace": "",
"isRegex": true
}
]
Settings are JSON, so backslashes are doubled: the regex \[.*\] is written "\\[.*\\]".
Reusing a built-in's name overrides that built-in in place, so you can tweak one without losing the rest. A snippet whose pattern does not compile is skipped with a warning rather than breaking the list.
Set replace explicitly — even to "". VS Code only reveals the replace box when a replacement string is supplied, so an omitted replace opens find-only.
| Field |
Default |
Meaning |
name |
(required) |
Label in the Quick Pick |
find |
(required) |
Search pattern |
replace |
"" |
Replacement; $1, $2 for capture groups |
description |
— |
Detail line under the name |
isRegex |
true |
Set false for a literal search |
matchCase |
false |
Case-sensitive |
wholeWord |
false |
Whole-word match |
findInSelection |
false |
Limit to the current selection |
Settings
| Setting |
Default |
Description |
pkTools.regexSnippets |
[] |
Your saved find/replace recipes |
pkTools.includeBuiltInSnippets |
true |
Show the bundled snippets |
pkTools.csvDelimiter |
"auto" |
auto, ,, ;, \t, or \| |
pkTools.alignColumnsByType |
true |
Infer Markdown column alignment from the values |
pkTools.markdownPadColumns |
true |
Pad Markdown columns to equal width |
pkTools.htmlIndent |
2 |
Spaces per indent level in generated HTML |
Contributing
Building, testing and publishing this extension are covered in DEV.md.
License
MIT