Tabs to Spaces Converter – Normalise Indentation Without Breaking It
Mixed indentation is one of those problems that stays invisible right up to the moment something fails. Python raises TabError: inconsistent use of tabs and spaces, a YAML parser rejects a file outright because the spec forbids tabs in indentation, a Git diff reports every line as changed, and the same snippet lines up differently in three different editors. This tabs to spaces converter rewrites the whitespace in one consistent direction and shows you exactly what it changed.
Why one tab is not four spaces
A tab does not have a width. It is a movement: it pushes the cursor to the next tab stop, which is the next column that is a multiple of the tab size. At a tab size of four, the string ab\tcd renders as ab cd — the tab is worth two spaces, because ab already occupies columns 0 and 1 and the cursor only has to travel to column 4. A plain find-and-replace of \t with four spaces gets this wrong on every line where the tab is not already sitting on a stop, which is why converted files so often come out misaligned.
The default tab stops (elastic) mode reproduces the real behaviour using nextStop = floor(col / N) × N + N. If you actually want the naive substitution — and sometimes you do, for example when a downstream tool expects a fixed indent unit — switch to fixed count and every tab becomes exactly N spaces wherever it sits.
Choosing a direction and a scope
Tabs → Spaces is what you want for Python, YAML and any codebase that enforces spaces. Spaces → Tabs is the inverse: the leading whitespace of each line is measured in columns and rewritten as whole tabs plus any remainder, which is what Go, Makefiles and tab-separated data need. On the entire-line scope the reverse pass only collapses runs of two or more spaces that actually reach a tab stop, so word gaps and intra-line alignment survive — the same rule GNU unexpand -a follows.
The scope setting is the safety catch. With leading whitespace only, just the indentation is touched, so a tab used as a data separator halfway along a line is left exactly where it was. Widen the scope to entire line only when you really do want every tab converted.
make requires a real tab at the start of every recipe line and fails with a missing separator error if it finds spaces instead. The tool detects the Makefile shape — a target line followed by a tab-indented line — and warns you before you convert. YAML is the mirror image: tabs are illegal in its indentation, so converting them to spaces is mandatory rather than optional.Finding the lines that will bite
Analyse onlymode changes nothing and just reports. It counts tab-indented, space-indented and mixed lines, names the dominant style, lists the exact line numbers where tabs and spaces are mixed inside the same indent, and infers a likely tab size from the greatest common divisor of the space-indent widths. The per-line table shows each line’s indent before and after in visible glyphs, its width in columns, and whether the pass changed it.
Alignment, cleanup and export
Align columns mode reads tabs as separators instead of indentation and pads each column to the widest cell plus a gutter, turning tab-separated data into a fixed-width plain-text table. Alongside the conversion you can trim trailing whitespace, enforce a final newline, and normalise line endings between LF, CRLF and CR. Turn on show whitespace to render tabs as → and spaces as · in the preview panes — copying and downloading always use the raw text. Results export as TXT, the per-line report exports as CSV, and everything runs in your browser, so proprietary source never leaves your machine.