5.5 KiB
katesam — sam structural-regexp editing in Kate
The Sam tool view (left sidebar) runs the plan9
sam command language
against the active document, or project-wide via X/Y. Type a program, press
Ctrl+Return or click Run. Each Run is one undo step (Ctrl+Z reverts the
whole Run; multi-file X/Y undoes per file).
This document covers the regex deviation — katesam uses PCRE
(QRegularExpression), not plan9 regexp(7) — and what that means in practice.
Every example below is verified against the engine.
TL;DR
- Everyday patterns behave identically to sam.
- One real semantic difference: PCRE is leftmost-greedy, sam is
leftmost-longest. It only bites on overlapping alternation (
a|ab). ^and$anchor to the whole buffer, not each line, by default. This is the most common surprise. Use a structural loop or the(?m)flag.- You gain PCRE features sam never had:
\d \w \s \b, lookahead/lookbehind, non-greedy*?, backreferences, inline flags(?i)(?m)(?s).
The one semantic divergence: greedy vs longest
sam's regexp(7) matches the leftmost-longest substring. PCRE matches
leftmost, then resolves alternation left-to-right, first wins (greedy
within a branch, but alternation order decides between branches).
| program | input | sam (leftmost-longest) | katesam (PCRE) |
|---|---|---|---|
,s/a|ab/X/ |
ab |
X (matches ab) |
Xb (matches a) |
,s/ab|a/X/ |
ab |
X |
X |
Implication: when alternatives overlap, order them longest-first
(ab|a, not a|ab). This is the only case where a working sam command can
silently produce a different edit in katesam. Non-overlapping alternation
(cat|dog) and all non-alternation patterns are unaffected.
Quantifier greediness is the same in both (a.*c on axxcxxc → whole string in
both). katesam additionally offers non-greedy .*?:
| program | input | result |
|---|---|---|
,s/a.*c/X/ |
axxcxxc |
X (greedy, same as sam) |
,s/a.*?c/X/ |
axxcxxc |
Xxxc (non-greedy, PCRE bonus) |
Anchors ^ / $ are buffer-wide, not per-line
By default PCRE anchors ^/$ to the start/end of the whole buffer, so a
global substitution touches only the first/last position — not every line:
| program | input | result |
|---|---|---|
,s/^/> /g |
a⏎b⏎c |
> a⏎b⏎c (only line 1!) |
,s/$/;/g |
a⏎b⏎c |
a⏎b⏎c; (only the end!) |
This differs from how people expect line-oriented edits to work. Two correct ways to act per line:
1. Structural (sam-idiomatic, preferred). Loop over lines with x, then act
on each line's dot. .+ matches the non-newline run of each line:
,x/.+/ i/> / prefix every (non-empty) line with "> " → > a⏎> b⏎> c
,x/.+/ a/;/ append ";" to every line → a;⏎b;⏎c;
2. PCRE multiline flag (?m) (a bonus sam lacks). Makes ^/$ match at
every line boundary:
,s/(?m)^/> /g → > a⏎> b⏎> c
,s/(?m)$/;/g → a;⏎b;⏎c;
Prefer the structural form — it is the real sam way and composes with other
loop/guard steps. Reach for (?m) for quick one-liners.
What matches identically (no surprises)
| behaviour | katesam (PCRE) | sam | same? |
|---|---|---|---|
. crosses newline |
no | no | ✅ |
[^z]+ crosses newline |
yes | yes | ✅ |
empty-match advance s/x*/-/g on axbx → -a-b- |
✅ | ✅ | ✅ |
greedy quantifiers * + ? |
✅ | ✅ | ✅ |
character classes [a-z], groups ( ), ` |
` | ✅ | ✅ |
& whole-match, \1..\9 groups in replacement |
✅ | ✅ | ✅ |
.not crossing newlines matches sam, so line-structured descent (,x/.*\n/ ...) behaves as expected. Use(?s)if you want.to span newlines (PCRE bonus).
PCRE features sam never had (free upgrades)
All verified working in katesam:
| feature | example | effect |
|---|---|---|
| shorthand classes | ,s/\d+/N/g on a12b345 |
aNbN |
\w+ in a loop |
,x/\w+/ c/W/ on foo bar |
W W |
word boundary \b |
,s/\bfoo\b/X/g on foo foobar foo |
X foobar X |
| backreference | ,s/(\w)\1/D/g on aabbc |
DDc |
| lookahead | ,s/foo(?=bar)/X/g on foobar fooqux |
Xbar fooqux |
| inline flags | (?i) ignore-case, (?m) multiline, (?s) dotall |
per-pattern |
These make many edits easier than in real sam. The tradeoff is that a pattern written for katesam may not be portable back to sam.
Practical guidance
- Order overlapping alternatives longest-first (
\.tar\.gz|\.gz, not the reverse). The only silent divergence. - For per-line edits, prefer
,x/.+/ …over^/$. If you use anchors globally, remember they are buffer-wide, or add(?m). - Lean on PCRE extras (
\b,\d, lookahead) freely — they are reliable; just know they are not sam-portable. - Empty-match globals (
s/x*/…/g) advance one position per null match, as in sam — safe, but as always with*, double-check the result.
Why PCRE and not regexp(7)
QRegularExpression ships with Qt (already a dependency) and gives a richer,
familiar syntax for free. plan9 regexp(7) is a Rune-based NFA woven into sam's
own buffer types; using it would mean porting that engine. The tradeoff accepted
here: everyday fidelity plus extra power, at the cost of exact leftmost-longest
semantics on overlapping alternation. If that matters for your workflow, the
engine's regex calls are isolated in SamEngine and could be swapped for a
ported regexp.c later.