How to fix mixed tabs and spaces on Mac
You can do this with Note My — here is how to reveal the difference between a tab and four spaces, find every line that mixes them, and re-indent the file properly.
The file looks fine. Then a Python interpreter refuses a line that is visually flush with the one above it, or a diff shows forty changed lines when you edited one word, or you press the left arrow at the start of a line and the caret jumps four columns instead of one. All three have the same cause. The leading whitespace on those lines contains both tab characters and space characters, and a tab is not four spaces — it is a single character whose width changes from one app, and one setting, to the next.
This guide covers how to reveal that difference in Note My, how to get a list of every affected line, how to read that list in context, and how to re-indent the file to one consistent unit.
Revealing whitespace changes only what you see on screen. Nothing is written to the file until you re-indent it and save.
What mixed indentation actually means
In the review sheet, the Mixed tab/space indent finding means one thing: a single line whose own leading whitespace contains both a tab and a space. It is a precise definition, and the precision is what makes the findings worth reading: every row in the table is a line you genuinely need to look at. Two tabs followed by three spaces is mixed. A space followed by a tab is mixed. Four spaces on their own is not, and one tab on its own is not — however inconsistent they are with the line above.
That distinction matters, because it gives you two complementary tools rather than one. A file where half the lines are indented with tabs and half with spaces, each line internally consistent, is a whole-file inconsistency rather than a per-line one, and it will still break a build. Revealing the whitespace, below, is the way to see that case — every character on screen, the whole way down. The findings list is the way to pin down the individual lines that mix both characters within a single run. Between them you have the file covered from both directions, and the same re-indent fixes either one.
Seeing tabs and spaces side by side
Turn on the Show Invisibles toggle in the toolbar. Its tooltip reads "Show spaces, tabs and line endings", and that is exactly what it does: a space becomes a middle dot ·, a tab becomes an arrow →, and a non-breaking space gets a ring so it cannot be confused with an ordinary one. A tab-indented line and a space-indented line stop looking alike immediately.
Four independent switches sit behind that one button, and you can set each of them yourself in Settings ▸ Invisibles: show whitespace, show end of line, highlight trailing whitespace, and underline whitespace. They stay off until you ask for them, so the editor is quiet by default and as loud as you like the moment you need detail. The toolbar toggle and the View ▸ Show All Characters menu item each give you a ready-made combination — the toolbar sets whitespace, end of line and underline; the menu item sets whitespace, end of line and trailing highlight — so you can reach for whichever suits the file in front of you. Switch all four on together in Settings when you want the fullest view.
There are also three marker styles, chosen in the same pane.
| Style | What it draws |
|---|---|
| Symbols (default) | Quiet placeholders — · for a space, → for a tab, LF / CRLF / CR at the line end |
| Code Points | Escape-style labels, independent of the file's encoding. Use it when you need to know precisely which character you are looking at. |
| Bytes | Hex bytes in the document's own encoding — a space is 20 in UTF-8 and 20 00 in UTF-16 LE, a non-breaking space is C2 A0 in UTF-8. Use it when you care what the file will actually save. |
Indentation guides are a useful second signal. They are on by default, in ten styles from Dots to Spectrum, chosen in the same pane. On a tidy file the guides line up in even columns the whole way down. On a badly mixed file they wander — one run of lines steps in by a single column, the next by four — and that wandering tells you something is wrong before you have read a single marker. The guides give you a verdict from across the room; Show Invisibles tells you which character is actually there.
Getting a list of every affected line
Reading the whole document with markers on is ideal for a short file. For a long one you want the affected lines gathered into a list you can work down. Open the Clean review sheet and let our hidden-character scanner do the looking. It all happens on your Mac — nothing is uploaded, there is no account, and the file never leaves the machine.
-
Open the review sheet
Choose Clean ▸ Clean Invisible / Special Characters… from the toolbar's Clean menu. The sheet has a scope control set to Selection or Whole Document, a preset pop-up, the full list of character categories on the left, and a findings table on the right.
-
Enable the right category
In the left-hand list, tick Mixed tab/space indent. The presets each keep to their own remit — Code-safe, Safe and Prose are shaped for everyday passes, and Aggressive turns on every category at once — so tick this one by hand. Doing so switches the preset pop-up to Custom, which is exactly what you want here: a pass shaped to the question you are asking.
-
Scan
Press Scan. The findings table fills with one row per finding: Line:Col · Code Point · Category · Action · Risk. Double-click any row to reveal that spot in the editor, or select rows and use Show in Editor. Work down the table rather than hunting through the document. Our large-file engine keeps the sheet responsive on very long documents, so size is not a reason to skip this step.
-
Read the markers in the document
Findings are marked in the document as well as listed in the sheet: mixed indentation shows as a bar beside the offending run, so you can read the problem in context instead of by line number. The markers are temporary: closing the sheet clears them, and so does any edit to the text. Scan again to bring them back.
The indentation stays yours to decide
Mixed tab/space indent is an informational finding. It is shown to you and left exactly as you wrote it. Whether a run of leading whitespace should become tabs or spaces, and how many, is a decision about your project — not a character that is objectively junk — so the review sheet gives you every location, down to the line and column, and leaves the call to you. Apply on that sheet, and the hidden-character pill in the status bar, are reserved for the characters that are wrong however you look at them: zero-width characters, bidi controls and their relatives. When you want indentation checked specifically, tick the category and read the findings table; that is the tool built for the job.
That division of labour is what makes the review sheet safe to run on anything. Nothing changes until you ask for it, so you can open a file you have never seen before, scan it, read every finding, and close the sheet with the document exactly as you found it. Changing the indentation is a separate, deliberate step, and it is the Indentation sheet below.
Tick the one category you want and leave the preset on Custom. That keeps the findings table on indentation and keeps Apply just as narrow. The Aggressive preset also flattens smart quotes, dashes and ellipses — exactly right when you are normalising prose, and more than you want while you are chasing a tab through a source file. Choosing your own categories gives you precisely the pass you asked for and nothing else.
What changes in Python, Makefiles and Markdown
Our advanced language identifier tailors the review to the language in front of you, so the findings you get match the rules that language actually has:
- Python — mixed indentation is raised to danger severity. In Python this is not a style problem, it is a syntax error waiting to happen.
- Makefiles — the finding is dropped entirely. A recipe line must begin with a tab, so flagging it would be noise.
- Markdown — trailing-whitespace findings are dropped, because two trailing spaces are a hard line break.
Give the document a name and you get all of that tailoring straight away. Save the file with a .py name first, then scan, and the findings arrive ranked the way a Python file deserves. That ranking is worth having before you start fixing anything: the Risk column is the quickest way to sort a long findings table into what will break a build and what is merely untidy.
Fixing it: the Indentation sheet
Click Indent in the toolbar. It sits in the main row, one click away from wherever you happen to be in the document, and it opens the sheet where the whole file gets put right in a single pass.
The sheet opens with a language picker, a brace-style switch, a live preview card, the indent controls, and a comment-recovery toggle. Our advanced language identifier fills the picker in for you, and you can override it here. Set indent width — the default is 4, and the range is 1 to 12 — and choose tabs or spaces. Spaces is the default.
Save the file with ⌘S before you press anything. Then press Apply. Nothing changes before that. The Indent button does not act on your document; it opens this sheet, and Cancel leaves the file exactly as it was.
Saving first gives you a definite point to come back to, and coming back is one command. File ▸ Reload from Disk (⌘R) reads the file exactly as you last saved it and puts that version on screen. Reloading never changes the file on disk, so it costs you nothing to try: apply a width, look at the result in context, reload, try another. That loop is the quickest way to settle on a house style for a project you have just inherited.
The live preview card renders a compact if <condition> { … } sample that updates as you move the controls. Use it to settle the decision before you commit to it: a width of two against a width of four, K&R braces against Allman, all in one glance. Set the sample to look the way you want your code to look, then apply.
Apply does more than swap one whitespace character for another, and that is rather the point of it. Our indentation engine reformats the whole document so braces, blocks and statements sit at their proper depths, and it puts statements that share a line onto separate lines. A file that arrived through three different editors comes back reading as though one person wrote it, in one pass, with the mixed indentation gone as a side effect of the tidying. To keep the original alongside the new version, make a copy first with File ▸ Save a Copy As… (⌥⇧⌘S) and reformat the copy.
If the file is under version control, apply the re-indent as its own change, separate from the edit you meant to make.
Tabs or spaces, and the two language locks
Indent width and the tabs-or-spaces choice are stored per language, alongside brace style, so what you choose for one language does not follow you into another. Two languages carry a lock that overrides your global choice when you apply a re-indent:
| Language | Lock | Default |
|---|---|---|
| Go | Force tabs | On |
| Python | Force 4 spaces | Off |
The locks apply when you re-indent; auto-indent on Return follows your global indent unit. The tidy arrangement is to have the two agree, and it takes one setting. If you work in Go, set tabs as your global indent unit as well: what you type and what you apply then match from the first keystroke, and the Go lock keeps every file you touch idiomatic without another thought. The same holds in reverse if you switch the Python lock on — set four spaces globally too, and every line of that file comes out the same whether you typed it or applied it. Getting those two in step is the single best way to keep a file from drifting again once you have cleaned it up.
Checking the result
Leave Show Invisibles on and scroll the file. Every leading run should now be the same character throughout. Re-run the review sheet with Mixed tab/space indent still ticked; it should report nothing. Then save with ⌘S.
If the re-indent moved code further than you expected, the version you saved is one command away: File ▸ Reload from Disk (⌘R) brings it straight back, and you can change a setting and apply again. Our auto-recovery safety net runs alongside all of this — it holds on to unsaved work and brings it back the next time you open the app, so a power cut in the middle of a long tidy-up costs you nothing.
Troubleshooting
The findings list is empty but the file still looks inconsistent
That is the whole-file case rather than the per-line one: every individual line is internally consistent — some all tabs, some all spaces — and two other tools find it in seconds. Turn on Show Invisibles and scroll, or open Find & Replace from the toolbar, tick Regular Expression, search for \t, and read the live match count our find and replace shows you as you type. Search ▸ Count Matches reports the same thing as a message reading "N matches on M lines."
A very large file takes a moment to re-indent
Nothing has gone wrong. Re-indenting is a whole-document operation, so on a very long file it takes a visible moment; it is working, it will finish, and what you get back is the entire file put right in one pass rather than a few hundred lines at a time. Our large-file engine is built for documents of exactly this size — it is why a long file opens in the first place, and why the review sheet stays responsive while you work down its findings.
The result lands at a depth you did not expect
Check the braces first. Re-indenting follows the structure it is given, so a single unbalanced brace carries everything after it to a deeper level. Balance it, apply again, and the file falls into line. If you saved before applying, File ▸ Reload from Disk (⌘R) puts the earlier version back on screen while you go looking for the stray brace. Check the language picker at the top of the sheet as well: set it to the language you are actually working in, and the conventions applied are that language's own.
The whitespace markers disappeared
Two different things draw whitespace here, and they behave differently. Show Invisibles markers stay on until you turn them off. The review sheet's finding markers are temporary: they clear the moment you edit the text, and again when you close the sheet. Scan once more to bring them back.
What else you can do from here
- Put a whole document right in one pass. Width, unit and brace style are set once and applied to the entire file, so the result is consistent top to bottom rather than patched line by line.
- Keep a different convention for every language. Indent width, tabs-or-spaces and brace style are stored per language, so a Go file and a Python file each get what they should without you changing a setting between them.
- Survey a whole project before you touch it. Search ▸ Find in Folder… (⇧⌘F) lists every file that matches your search across a folder tree and leaves all of them exactly as they are — you read the results first and decide what to open.
- Sweep the files you have open. Find & Replace works across your open tabs with regular expressions and a live match count, which is the fast way through a handful of related files.
- Verify with your own eyes. Show Invisibles, three marker styles and ten indentation-guide styles let you confirm the result rather than take it on trust.
- Keep a copy of anything. File ▸ Save a Copy As… before you apply keeps the version you started with, and File ▸ Reload from Disk afterwards puts it back on screen whenever you want another look at it.
- Do all of it offline. Every scan and every reformat happens on your Mac, with no account and nothing uploaded.
Frequently asked questions
How do I see whether a line uses tabs or spaces?
Turn on Show Invisibles in the toolbar. A space draws as · and a tab as →. If you need certainty about the exact character, switch the marker style in Settings ▸ Invisibles to Code Points, or to Bytes to see the hex value in the file's own encoding.
Why does my Python file raise an indentation error when the lines look aligned?
Because a tab and a run of spaces can occupy the same width on screen and still be different characters. Python treats them differently. Note My raises mixed indentation to danger severity in Python files for exactly this reason. Save the file with a .py name before you scan, so it gets that treatment.
Can I convert a whole project from tabs to spaces at once?
You can work through a project quickly, file by file. Find every affected file with Search ▸ Find in Folder… (⇧⌘F), open the ones you want straight from the results, and apply the re-indent to each. Your width and unit are remembered per language, so from the second file onwards it is two clicks. Folder search reads your files and leaves them untouched, which means you can survey the whole tree before you change a single line.
Does the Spaces counter in the status bar tell me about indentation?
It gives you a running total of space characters for the whole document, which is a useful gauge at a glance. For locations, go to the review sheet's findings table: it gives you line and column for every affected line, and double-clicking a row takes you straight to that spot in the editor.
Will re-indenting change anything other than whitespace?
Yes, and that is usually what you want. Re-indenting also puts statements that share a line onto separate lines and applies your brace style — K&R by default, or Allman if you switch it — plus a set of per-language policies, so the file comes back consistent rather than merely re-spaced. To keep the version you started with, make a copy with File ▸ Save a Copy As… first and reformat that.
Is any of this sent anywhere for analysis?
Everything stays on your Mac. Note My contains no networking code of its own; the only network activity is performed by Apple’s own system services, to establish purchases and transactions state. There is no telemetry, no crash reporter and no update checker. Every scan and every reformat happens on your own machine, there is no account to create, and your files never leave it.