Nano's Syntax Highlighting: Rules, Regex Limits, and a Working Go Example
Nano's syntax highlighting is a line-by-line POSIX ERE engine with first-match-wins loading order. Learn how to write custom .nanorc rules, avoid multi-line regex limits, enable true color, and keep large files responsive.
16 Jul 2025, 11:04 UTC

The problem: highlighting that works until it doesn't
GNU nano ships with syntax highlighting, but the mechanism is a line-by-line regex engine with no multi-line state. That means C block comments, Python triple-quoted strings, and heredocs often end up half-colored. If you've ever pasted a 200 KB generated file and watched nano stutter, you've hit the performance ceiling of testing every regex against every line. Understanding how rules load, what the regex flavor actually supports, and where the color palette lives lets you write definitions that are both correct and fast enough.
Load order decides which syntax wins
Nano reads syntax blocks in this sequence:
- System files in
/usr/share/nano/*.nanorc(alphabetical) - Your
~/.nanorcfrom top to bottom - Any
includedirectives encountered along the way
The first syntax block whose file-pattern regex matches the filename is used. Later matches are ignored. Because system files load before your ~/.nanorc, a user definition for "python" will replace the shipped one only if it appears after the system include. The typical pattern:
## ~/.nanorc
include "/usr/share/nano/*.nanorc"
# your custom syntaxes below
syntax "go" "\.go$"
color green "\<(package|import|func|var|const|type|struct|interface|map|chan|go|defer|select|case|default|range|fallthrough|break|continue|return|if|else|for|switch|goto)\>"
color cyan "//.*"
color yellow "\<[0-9]+\>"
Run nano --showrc (added in nano 5.8) to see the exact load order and confirm your block is last.
Regex flavor: POSIX ERE with nano extensions
Nano uses POSIX Extended Regular Expressions (ERE). That means:
- No look-ahead, look-behind, or non-greedy quantifiers (
.*?) - Word boundaries are written
\<and\>(not\b) - Capture groups work, but back-references in the same regex are the only cross-line memory — there is no multi-line state machine
Because each logical line is highlighted independently, a regex cannot span a newline. Workarounds like coloring """.*""" for Python docstrings only catch single-line cases; multi-line strings stay uncolored or bleed into following lines. Accept this limitation or switch to an editor with a proper parser.
Color palette: from 16 names to true color
Nano 4.0 (2019) added 256-color and true-color (RGB hex) support. The color and icolor (case-insensitive) directives accept:
- 16 named colors:
black,red,green,yellow,blue,magenta,cyan,whiteand theirbrightprefixes - Numeric indices 0–255 for 256-color mode
#rrggbbhex for true color
True color only works if your terminal advertises the RGB (or Tc) capability in terminfo. Check with:
infocmp -1 $TERM | grep -i rgb
If the output lacks RGB, nano falls back to the nearest 256-color approximation. Many older xterm-256color entries miss this capability; consider xterm-direct or a modern foot, kitty, wezterm terminfo.
Worked example: a minimal Go syntax
Place the following at the end of ~/.nanorc (after the system include). It demonstrates keywords, line comments, and numbers.
syntax "go" "\.go$"
color green "\<(package|import|func|var|const|type|struct|interface|map|chan|go|defer|select|case|default|range|fallthrough|break|continue|return|if|else|for|switch|goto)\>"
color cyan "//.*"
color yellow "\<[0-9]+\>"
Open a .go file. Keywords appear green, // comments cyan, decimal integers yellow. No multi-line comment support — /* ... */ stays uncolored because the regex cannot track state across lines.
Performance: the 500 KB cliff
Every line is tested against every regex in the active syntax block, in order. On files larger than ~500 KB with dozens of color rules, scrolling latency becomes noticeable. Nano 7.0 added --syntax=name to bypass auto-detection and force a specific syntax, which also skips the pattern-matching pass. For a 1 MB generated Go file:
time nano --syntax=go large.go # uses Go rules
# vs
time nano --syntax=default large.go # no highlighting
The difference can be seconds of startup time. Soft-wrapped lines (toggle with Alt+$) are highlighted as a single logical line; a 100 KB minified JSON line will repaint the whole thing on every scroll step.
Trade-offs and a practical check
- Collision risk: naming your syntax "c" or "sh" silently replaces the system definition. Use unique names unless you intend an override.
- Terminal detection: true-color hex values degrade to 256-color on mismatched terminfo; verify with the
infocmpcommand above. - No multi-line: accept that block comments and heredocs will be imperfect, or keep those files in an editor with a real parser.
To validate a new syntax quickly:
- Add the block to
~/.nanorcafter the system include. - Run
nano --showrcand confirm your file appears last. - Open a test file matching the pattern; verify colors.
- If colors are wrong, check
$TERMand terminfo for RGB capability.
Next step
Start with one language you edit daily. Copy the Go template, swap the file regex and keyword list, and drop it in ~/.nanorc. Measure startup on your largest file with time nano --syntax=yourname bigfile. If it's snappy, add more rules; if it lags, prune regexes or accept --syntax=default for that file. Nano's highlighter will never be a full parser, but within its line-by-line regex model it's predictable and hackable — exactly what a minimal editor should be.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.