Skip to content
| Marketplace
Sign in
Visual Studio Code>Formatters>Editor Text ToolkitNew to Visual Studio Code? Get it now.
Editor Text Toolkit

Editor Text Toolkit

HeavenSky

|
28 installs
| (0) | Free
Copy path with line numbers, change case, align by regex, and a plain text mode for large files.
Installation
Launch VS Code Quick Open (Ctrl+P), paste the following command, and press enter.
Copied to clipboard
More Info

Editor Text Toolkit

English · 简体中文

Five everyday editing tools in one VS Code extension — copy a path:12-14 reference, convert case, align by a regex, tame a huge log file, and diff two text fragments.

Zero runtime dependencies. English and 简体中文 UI, following the VS Code display language. No telemetry. No default keybindings, so nothing collides with what you already have.

Quick start

  1. Press Ctrl+Shift+P / Cmd+Shift+P and run Text Toolkit: Show All Commands.
  2. Pick a category. The picker shows the settings currently in effect and lists every action, so you can use the whole extension without memorising a single command name.
  3. Once you know what you reach for, bind a key to it.

Contents

  • Copy Path With Line Numbers
  • Change Case
  • Align by RegEx
  • Plain Text Mode
  • Compare (Diff)
  • Commands and keybindings
  • Settings
  • Localization
  • Development
  • License

Copy Path With Line Numbers

Copies a reference to where you are, in the form other people (and AI assistants, and issue trackers) can paste back:

src/features/diff/core.ts:112-118

Run it: Text Toolkit: Copy Path With Line Numbers, or right-click in the editor, or the picker.

The path part follows textToolkit.copyPath.pathStyle, and the picker also offers every style as a one-off that never writes your settings:

Style Result
absolute (default) /Users/you/project/src/app.ts
relative src/app.ts — falls back to the absolute path when no workspace folder contains the file
tilde ~/project/src/app.ts — only rewrites the prefix when the file is under your home directory
fileName app.ts

The line part collects line numbers from every cursor and selection, deduplicates them, sorts them, and merges consecutive ones into a fragment:

Form Meaning
path:a a single line
path:a-b a closed interval, lines a through b
path:a-b,c-d several fragments, comma separated
path:a+n n lines starting at a — opt-in, see below

textToolkit.copyPath.multiLineFormat picks the shape. For selected lines 3, 4, 8, 12, 13, 14:

Format Output
range (default) path:3-4,8,12-14
list path:3,4,8,12,13,14
perLine path:3, path:4, … one per line

The a+n form is off by default. Turn it on with { "textToolkit.advanced": { "copyPath.useLineCountSyntax": true } } and the same selection copies as path:3+2,8,12+3. Single lines stay a and fragments stay comma separated either way; list and perLine are unaffected, because they never emit fragments.

Use textToolkit.copyPath.separator to change the : between path and lines.

Note: unsaved documents and non-file documents (output channels, virtual documents) show a message instead of copying, because they have no path to copy.


Change Case

Converts the selection, several selections at once, or the word under the cursor between 16 styles.

Run it: Text Toolkit: Change Case... opens the style picker; with a single-line selection each entry previews the converted text, so you can see the result before committing. From a keybinding you can skip the picker entirely with { "style": "kebab" }.

All 16 styles, applied to the input fooBar baz-qux:

Style Result Notes
camel fooBarBazQux
pascal FooBarBazQux
snake foo_bar_baz_qux
constant FOO_BAR_BAZ_QUX
kebab foo-bar-baz-qux
param foo-bar-baz-qux alias of kebab
dot foo.bar.baz.qux
path foo/bar/baz/qux
no foo bar baz qux lower case, space separated
sentence Foo bar baz qux first character upper cased
title Foo Bar Baz Qux every word capitalised
lower foobar baz-qux whole string, separators untouched
upper FOOBAR BAZ-QUX whole string, separators untouched
lowerFirst fooBar baz-qux only the first character
upperFirst FooBar baz-qux only the first character
swap FOObAR BAZ-QUX every character's case reversed

What counts as "the word under the cursor": wider than the editor's own definition — _, -, / and $ are always treated as word characters, so foo_bar-baz converts as one unit. The . is not included by default; turn it on with { "textToolkit.advanced": { "changeCase.includeDotInCurrentWord": true } }, and then a cursor inside foo_bar.baz converts the whole thing instead of just baz.

Multi-line selections are split on the document's own end-of-line sequence, so an LF file on Windows behaves correctly.


Align by RegEx

Aligns the selected lines on a separator so columns line up.

Run it: select the lines, then Text Toolkit: Align by RegEx. Enter a regular expression, or the name of a saved template. The picker lists your templates with their patterns, so you never have to recall a name.

Save templates in settings:

{ "textToolkit.alignByRegex.templates": { "assign": "=|,|:", "arrow": "=>" } }

From a keybinding, pass either form directly: { "regex": "=" } or { "template": "assign" }.

It aligns whole lines covered by the primary selection; a selection that stops at column 0 of the following line does not drag that line in. Column widths use your editor.tabSize. The input box pre-fills with your last expression — turn that off with { "textToolkit.advanced": { "alignByRegex.rememberLastInput": false } }.

Three lines aligned on =:

# before                # after
const a = 1;            const a  = 1;
let bbbb = 22;          let bbbb = 22;
var cc = 333;           var cc   = 333;

The lines do not have to contain the same number of matches. Each column's width is computed only among the lines that actually have a match in that column, so a short line never widens an unrelated column, and a column matched by a single line is left untouched:

# before                # after
x = 111111111;          x  = 111111111;
yy = 2; zzz = 3;        yy = 2; zzz   = 3;
w = 4; vvvvv = 5;       w  = 4; vvvvv = 5;

Plain Text Mode

Makes a large text or log file responsive to read, search and edit, by removing the rendering and language-service work VS Code would otherwise do on it.

Five ways in:

  • Explorer right-click → Open in Plain Text Mode — opens the file and enters the mode in one step, so it is never rendered normally first. Accepts a multi-selection; folders are skipped.
  • Text Toolkit: Toggle Plain Text Mode for the file already in front of you.
  • Automatically, when you open a file of at least textToolkit.plainText.promptSizeMB MB (default 2) and accept the prompt. Don't ask again is remembered; undo it from the Plain Text Mode entry in the picker. Set the value to 0 to never be asked.
  • When VS Code will not display the file at all — above workbench.editorLargeFileConfirmation MB the editor is replaced by a "The file is not displayed in the text editor because it is very large" placeholder. The extension spots that tab and offers plain text mode for it; opening goes through the extension host, which is not bound by that limit, so you get the content without pressing Open Anyway first. It is a notification rather than a button on the placeholder itself, because VS Code hardcodes the buttons there and the editor/content menu that could add one is still proposed API. Same Don't ask again switch as above.
  • Silently, for extensions listed in textToolkit.plainText.autoApplyExtensions, for example [".log", ".csv"].

What it does:

  • Sets the document language to plaintext, which stops tokenization, semantic highlighting and every language-specific provider for that document.
  • Keeps language extensions from ever waking up when the file is opened by the explorer command. VS Code fires onLanguage:<id> the moment a text model is created, so opening a large .ts normally pulls in the TypeScript extension, which loads the whole project and fetches types while the editor sits on "Activating Extensions..."; switching the language afterwards is too late. The extension therefore writes one files.associations entry matching exactly that file into your user settings, opens it as plaintext, pins the language and removes the entry again — the model is never anything but plain text. Leaving the mode restores the language the file would otherwise have had. Turn it off with "plainText.suppressLanguageActivation": false.
  • Writes 29 [plaintext]-scoped editor overrides — word wrap on, and minimap, folding, sticky scroll, bracket colorization, guides, hovers, suggestions, code lens, links, the unicode scanners and more off. The full table is in the settings reference. Because they are language-scoped, your code files are unaffected while the mode is on.
  • Shows a Plain Text status bar item. Click it, or toggle again, to restore the original language and every setting value that was replaced.

Scope and limits — read before relying on it:

  • This removes rendering and language-service cost. It does not change how VS Code loads files: the whole document still lives in a TextModel, and in-file find/replace still runs on that model. Expect a solid improvement in the 5–50 MB range, and no order-of-magnitude change for hundreds of MB.
  • Settings are written to Workspace settings when a workspace is open, otherwise to User settings. Original values — including "was not set" — are recorded and restored on exit. The temporary files.associations entry described above is the one exception: it always goes to User settings, so nothing is ever written to a repository's .vscode/settings.json for it, and a leftover entry from a crashed session is cleaned up on the next activation.
  • The workbench.editorLargeFileConfirmation limit does not apply to this mode — see the fourth way in above. The one limit that still does is files.maxMemoryForLargeFilesMB, which this extension deliberately does not touch, because raising it trades stalls for out-of-memory crashes.

Set { "textToolkit.advanced": { "plainText.applyEditorSettings": false } } to only switch the language and skip the overrides entirely, or "plainText.disableLineNumbers": true to also hide line numbers while the mode is on.


Compare (Diff)

Diff two pieces of text without saving either of them to a file. Both sides are read-only, unless you ask for an editable copy.

Five ways to start a comparison:

Want to compare Do this
Two selections, in the same file or different files Select Text for Compare on the first, then Compare Text with Marked Selection on the second
A selection against the clipboard Compare Text with Clipboard
Two files split on screen Compare Text in Visible Editors
Any two open tabs, visible or not Compare Text in Two Open Tabs, then pick left and right
The sides the other way round Swap Diff Sides re-opens the most recent comparison exchanged

Selection rules: running a compare command with no selection uses the whole file. Multiple cursors work — the non-empty selections are sorted by document position and joined with newlines. The mark survives until you replace it or clear it, and can be set in one file and used in another.

Status bar: while a mark is set, an item on the right shows its source and line range. Click it to open the Compare picker (mark, compare, swap, toggle rules, clear).

Syntax highlighting: each side inherits the language of the file it came from, and the clipboard side inherits the language of the other side — so a clipboard diff against a .ts selection is highlighted as TypeScript.

Non-text clipboard content: the VS Code clipboard API only exposes text. Copying an image and text gives you the text, which is what gets compared. Copying only an image yields an empty string, indistinguishable from an empty clipboard, so the command tells you there is nothing to compare instead of opening a diff with one blank side.

Editable copies

A comparison is read-only by default. To edit one, click ✎ in the top-right corner of the diff editor — or pick Edit This Comparison as a Copy from the Compare picker. What you get depends on what is being compared:

  • Two whole files (no selection on either side): a native editable diff of the real files, so your edits save straight back to disk and Git integration works. Nothing is copied.
  • Anything else — a selection, the clipboard, a read-only virtual document such as git:: two real copy files under the extension's own storage directory, named after where they came from (service (ll.3-7).ts, Clipboard.ts). Both sides are editable, and edits are never written back to the selection they came from.
Dirty state A copy starts clean; it only becomes dirty once you change something, and closing it then asks whether to save
Ctrl+S Saves the copy file. Your source file is untouched — use File: Save As to put the result where you want it
Title Starts with ✎ , so a copy is never mistaken for a read-only comparison
Syntax highlighting Follows the copy's file name; a side without an extension (the clipboard) borrows the other side's, so a clipboard copy against a .ts selection is still highlighted
Normalization rules Applied once, when the copy is written. Toggling rules afterwards leaves open copies alone, so a rule switch can never overwrite your edits
Swap Diff Sides On a copy, it flips the same two files instead of writing a second copy, so unsaved edits survive
Back to the source The go-to-file button next to ✎, or Open the Source File in the picker, opens the file a side came from and selects the original lines
Cleaning up Copy files stay until you remove them — run Text Toolkit: Clean Up Comparison Copies. Nothing is deleted automatically. Close the copy tabs first, or they turn into editors for a deleted file

Normalization rules

textToolkit.diff.normalizationRules keeps predictable noise — timestamps, tabs, inconsistent spacing — out of a comparison. Rules rewrite only what the diff shows; your files are never touched.

{
  "textToolkit.diff.normalizationRules": [
    { "name": "Replace tabs with spaces", "match": "\t", "replaceWith": "  " },
    { "name": "One space after a comma", "match": ",\\s*([^,\n]+)", "replaceWith": ", $1" },
    { "name": "Ignore letter case", "match": ".*", "replaceWith": { "letterCase": "upper" }, "enableOnStart": false }
  ]
}
Field Required Meaning
match yes Regular expression; the global flag is applied automatically
replaceWith yes Replacement text, where $1, $2 … refer to capture groups — or { "letterCase": "upper" \| "lower" } to fold the case of whatever matched
name no Shown in the toggle picker
enableOnStart no false keeps the rule off until you enable it; defaults to true

Rules apply in array order. Toggle Normalization Rules turns them on and off at runtime without editing your settings, and refreshes every comparison that is already open — no need to close the diff and run it again. Editing the array in settings.json resets each rule to its own enableOnStart.

Two indicators, with deliberately different meanings:

  • The ~ in a diff title (instead of ↔) says that comparison was opened with at least one rule active. It is a snapshot and does not change afterwards.
  • The filter count in the status bar is the current number of active rules, and is always accurate.

An entry with an invalid match regex, or missing match / replaceWith, is skipped with a note in the extension log rather than breaking the feature.

What VS Code already does

The comparison opens in a stock VS Code diff editor, so everything the diff editor can do is already yours:

Want Use
Character-level highlighting inside a changed line On by default in the diff editor
Jump to the next / previous difference F7 / Shift+F7
Collapse unchanged regions diffEditor.hideUnchangedRegions.enabled
Inline instead of side-by-side diffEditor.renderSideBySide, or the ⋯ menu in the diff editor
Different diff colours Your colour theme, or workbench.colorCustomizations → diffEditor.*
Compare two files from the Explorer Built-in Select for Compare / Compare with Selected — editable and Git-aware
Compare against the version on disk Built-in File: Compare Active File with Saved
The diff at full width Drag the diff tab into its own editor group

Commands and keybindings

The two-level picker

Text Toolkit: Show All Commands is the front door. The first level lists five categories, each showing the settings currently in effect; picking one opens the second level:

Category Second level
Copy Path With Line Numbers Copy using current setting, plus each of the four path styles as a one-off (never writes your settings)
Change Case The 16 styles; with a single-line selection each entry previews the converted text
Align by RegEx Enter a regular expression..., plus every saved template listed with its pattern
Plain Text Mode Enable / Disable (whichever applies), and Re-enable large file prompt
Compare (Diff) Mark, compare with the mark, compare with the clipboard, compare visible editors, compare two open tabs, swap sides, turn the comparison into an editable copy, open the source file, toggle normalization rules, clean up copy files, and clear the mark when one is set

Three rules hold everywhere: the second level always starts with ← Back; Esc cancels the whole flow without changing anything; the first level never performs an action by itself.

Command reference

Nine commands appear in the Command Palette, all under the Text Toolkit category:

Command ID Title Arguments (for keybindings)
textToolkit.commands Show All Commands —
textToolkit.copyPathWithLines Copy Path With Line Numbers { "pathStyle": "absolute" \| "relative" \| "tilde" \| "fileName" }
textToolkit.changeCase Change Case... { "style": "camel" } (any of the 16 styles)
textToolkit.alignByRegex Align by RegEx { "regex": "=" } or { "template": "assign" }
textToolkit.plainText.toggle Toggle Plain Text Mode —
textToolkit.diff.markSelection Select Text for Compare —
textToolkit.diff.compareWithMarked Compare Text with Marked Selection —
textToolkit.diff.compareWithClipboard Compare Text with Clipboard —
textToolkit.diff.cleanUpCopies Clean Up Comparison Copies —

Eight more are hidden from the palette — they either need a file to act on, or are low-frequency enough that the picker is the better home. All of them still accept keybindings:

Command ID Title Where
textToolkit.plainText.open Open in Plain Text Mode Explorer right-click, on files (not folders)
textToolkit.diff.compareVisibleEditors Compare Text in Visible Editors Compare (Diff) picker
textToolkit.diff.compareTabs Compare Text in Two Open Tabs Compare (Diff) picker
textToolkit.diff.swapSides Swap Diff Sides Compare (Diff) picker
textToolkit.diff.editableCopy Edit This Comparison as a Copy ✎ in the diff editor title bar, Compare (Diff) picker
textToolkit.diff.openSource Open the Source File Go-to-file button in the diff editor title bar, Compare (Diff) picker
textToolkit.diff.toggleNormalizationRules Toggle Normalization Rules Compare (Diff) picker
textToolkit.diff.showMenu Show Compare Menu Compare status bar item

Context menus

  • Editor right-click: Copy Path With Line Numbers, Select Text for Compare, and Compare Text with Marked Selection once something is marked. Control the compare entries with textToolkit.diff.contextMenu (both / markOnly / none).
  • Explorer right-click: Open in Plain Text Mode, on files.

Keybindings

This extension contributes no default keybindings, so it cannot collide with your existing bindings or with another extension. Bind what you use, optionally with arguments:

[
  { "key": "shift+alt+c", "command": "textToolkit.copyPathWithLines", "when": "editorTextFocus" },
  { "key": "shift+alt+p", "command": "textToolkit.copyPathWithLines", "args": { "pathStyle": "absolute" }, "when": "editorTextFocus" },
  { "key": "ctrl+alt+k", "command": "textToolkit.changeCase", "args": { "style": "kebab" }, "when": "editorTextFocus" },
  { "key": "ctrl+alt+a", "command": "textToolkit.alignByRegex", "args": { "template": "assign" }, "when": "editorHasSelection" },
  { "key": "ctrl+alt+t", "command": "textToolkit.commands" },
  { "key": "ctrl+1", "command": "textToolkit.diff.markSelection", "when": "editorTextFocus" },
  { "key": "ctrl+2", "command": "textToolkit.diff.compareWithMarked", "when": "editorTextFocus" },
  { "key": "ctrl+3", "command": "textToolkit.diff.compareWithClipboard", "when": "editorTextFocus" }
]

Settings

Settings come in two layers, so the Settings UI stays short: ten entries you are likely to tune, and one object for everything else.

Exposed layer

{
  "textToolkit.copyPath.pathStyle": "absolute",
  "textToolkit.copyPath.separator": ":",
  "textToolkit.copyPath.multiLineFormat": "range",
  "textToolkit.alignByRegex.templates": { "assign": "=|,|:" },
  "textToolkit.plainText.promptSizeMB": 2,
  "textToolkit.plainText.autoApplyExtensions": [".log", ".csv"],
  "textToolkit.diff.normalizationRules": [],
  "textToolkit.diff.contextMenu": "both",
  "textToolkit.diff.clipboardSide": "left",
  "textToolkit.advanced": {}
}
Setting Type Default Values
textToolkit.copyPath.pathStyle string absolute absolute, relative, tilde, fileName
textToolkit.copyPath.separator string : any string
textToolkit.copyPath.multiLineFormat string range range, list, perLine
textToolkit.alignByRegex.templates object {} { "name": "regex" }
textToolkit.plainText.promptSizeMB number 2 0 disables the prompt
textToolkit.plainText.autoApplyExtensions string[] [] e.g. [".log", ".csv"]
textToolkit.diff.normalizationRules array [] see Normalization rules
textToolkit.diff.contextMenu string both both, markOnly, none
textToolkit.diff.clipboardSide string left left, right
textToolkit.advanced object {} see below

Built-in layer — textToolkit.advanced

Everything else has a built-in default and is not declared as its own setting. Override only what you need; every key you omit keeps its built-in value.

{
  "textToolkit.advanced": {
    "copyPath.useLineCountSyntax": true,
    "changeCase.includeDotInCurrentWord": true,
    "alignByRegex.rememberLastInput": false,
    "plainText.applyEditorSettings": true,
    "plainText.disableLineNumbers": true,
    "plainText.editorOverrides": {
      "editor.minimap.enabled": true,
      "editor.hover.enabled": null
    }
  }
}
Key Type Built-in default
copyPath.useLineCountSyntax boolean false (use a-b; true uses a+n)
changeCase.includeDotInCurrentWord boolean false
alignByRegex.rememberLastInput boolean true
plainText.applyEditorSettings boolean true
plainText.disableLineNumbers boolean false
plainText.suppressLanguageActivation boolean true (open files as plaintext through a temporary files.associations entry, so language extensions never activate)
plainText.editorOverrides object {}, merged on top of the 29-entry [plaintext] table below

You do not need this README to discover any of it: the Settings UI shows every key with its type, built-in default and accepted values, and settings.json completes each key as you type. Two snippets are offered on the value — All built-in defaults writes out the whole textToolkit.advanced object, and All 29 built-in [plaintext] overrides writes out the full override table, both ready to edit.

plainText.editorOverrides merges into the built-in table rather than replacing it:

  • setting id → value: change that entry ("editor.minimap.enabled": true keeps the minimap on),
  • setting id → null: drop that entry, so the setting keeps whatever you have configured globally,
  • any other editor.* id: added to the table, even if it is not listed below.

Unknown keys and values of the wrong type are ignored and the built-in default is kept, so a typo can never leave the extension in a broken state.

The 29 built-in [plaintext] overrides

All of them are written under the [plaintext] language scope, so they never affect your code files. "Other accepted values" lists what VS Code itself allows for that setting.

Setting id Built-in value Other accepted values
editor.minimap.enabled false true
editor.wordWrap "on" "off", "wordWrapColumn", "bounded"
editor.folding false true
editor.stickyScroll.enabled false true
editor.bracketPairColorization.enabled false true
editor.guides.indentation false true
editor.guides.bracketPairs false true, "active"
editor.matchBrackets "never" "always", "near"
editor.occurrencesHighlight "off" "singleFile", "multiFile"
editor.selectionHighlight false true
editor.renderWhitespace "none" "boundary", "selection", "trailing", "all"
editor.renderControlCharacters false true
editor.codeLens false true
editor.colorDecorators false true
editor.links false true
editor.hover.enabled false true
editor.parameterHints.enabled false true
editor.suggestOnTriggerCharacters false true
editor.wordBasedSuggestions "off" "currentDocument", "matchingDocuments", "allDocuments"
editor.quickSuggestions { "other": false, "comments": false, "strings": false } each context also takes "on" / "inline" / "off"
editor.formatOnType false true
editor.formatOnPaste false true
editor.autoClosingBrackets "never" "always", "languageDefined", "beforeWhitespace"
editor.autoClosingQuotes "never" same as autoClosingBrackets
editor.trimAutoWhitespace false true
editor.semanticHighlighting.enabled false true, "configuredByTheme"
editor.unicodeHighlight.nonBasicASCII false true, "inUntrustedWorkspace"
editor.unicodeHighlight.invisibleCharacters false true
editor.unicodeHighlight.ambiguousCharacters false true

Setting plainText.applyEditorSettings to false skips this table entirely and only switches the document language.


Localization

  • Command titles and setting descriptions: package.nls.json (English) and package.nls.zh-cn.json.
  • Runtime strings such as messages and picker labels: l10n/bundle.l10n.zh-cn.json; English lives in the source as the lookup key.

To add a language, copy either file to the matching locale suffix, for example package.nls.ja.json and l10n/bundle.l10n.ja.json.

Development

npm install
npm run typecheck   # tsc --noEmit
npm test            # vitest run, over the pure logic in test/*.test.ts
npm run check       # consistency gates: icon vs spec, package.nls parity, l10n bundle parity
npm run compile     # bundle to out/extension.js with a sourcemap
npm run build       # same but minified and without a sourcemap (what vscode:prepublish runs)
npm run watch       # rebuild on change
npm run gen:icon    # regenerate media/icon.svg and media/icon.png from scripts/icon-spec.mjs
npm run package     # vsce package into artifacts/, then assert the VSIX contents against an allowlist

npm run package does not run the checks for you — run typecheck, test and check first. npm run check is the gate that catches what a build cannot: a missing package.nls entry silently renders as %config.foo%, and a missing l10n entry silently falls back to English, so both directions are asserted — missing entries and unused ones.

Each feature lives in src/features/<name>/, with the pure logic in files that never import vscode so they can be unit-tested directly. change-case@5 is pure ESM, so the extension is bundled to CJS with esbuild; the packaged .vsix contains no node_modules.

License

MIT. Parts of this extension adapt earlier MIT-licensed work; the full third-party notices ship with the extension in NOTICE.md.

  • Contact us
  • Jobs
  • Privacy
  • Manage cookies
  • Terms of use
  • Trademarks
  • Your Privacy Choices
  • Consumer Health Privacy
© 2026 Microsoft