5.7 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$are per-line (match at every line boundary), the same as sam'sregexp(7)..does not cross newlines, also the same as sam.- PCRE also offers things sam lacks (
\d \w \b, lookahead, non-greedy, inline flags). They work, but prefer sam-style structural composition (x g v) — see "Extras" below.
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 .*? (a PCRE extra).
Anchors ^ / $ are per-line — same as sam
This is not a deviation. plan9 regexp(7) defines ^ as "the beginning of
a line" and $ as "the end of a line" (sam's regexp.c: BOL fires at offset
0 or after a \n; EOL fires before a \n). katesam compiles every pattern
with QRegularExpression::MultilineOption, so ^/$ match at every line
boundary, exactly as sam does:
| program | input | result |
|---|---|---|
,s/^/> /g |
a⏎b⏎c |
> a⏎> b⏎> c (every line) |
,s/$/;/g |
a⏎b⏎c |
a;⏎b;⏎c; (every line) |
The sam-idiomatic structural form works too, and is preferable because it composes with guards and other loops:
,x/.+/ s/^/> / prefix every (non-empty) line → > a⏎> b⏎> c
,x/.+/ a/;/ append ";" to every line → a;⏎b;⏎c;
. does not cross newlines (sam: a "character" is "any character but
newline"), which is also PCRE's default. Line-structured descent like
,x/.*\n/ … therefore behaves as expected.
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.
Extras — available, but not the sam way
PCRE accepts syntax plan9 regexp(7) never had. It all works, but treat it as
an escape hatch, not a headline: sam's power comes from composing simple
patterns with x/y/g/v/{}, not from clever single regexes. Prefer
structural composition; reach for these only when it is genuinely simpler.
| extra | example | note |
|---|---|---|
shorthand classes \d \w \s |
,s/\d+/N/g |
less typing than [0-9]; genuinely handy |
word boundary \b |
,s/\bfoo\b/X/g |
handy; sam would use x/foo/ with context |
ignore-case (?i) |
,s/(?i)todo/DONE/g |
fills a real gap — sam has no case-insensitive match |
non-greedy *? |
,s/a.*?c/X/ |
the structural x/…/ loop is the sam alternative |
| lookahead/behind | ,s/foo(?=bar)/X/ |
un-sam; express context with x/g/v instead |
| backreference in pattern | ,s/(\w)\1/D/g |
rarely the right tool |
A pattern using these is not portable back to sam. If portability or staying in the structural idiom matters, avoid them.
Practical guidance
- Order overlapping alternatives longest-first (
\.tar\.gz|\.gz, not the reverse). The only silent divergence from sam. ^/$are per-line — just like sam. For whole-line edits either anchor directly (,s/^/> /g) or, more sam-idiomatically, loop (,x/.+/ …).- Prefer structural composition (
x g v {}) over PCRE extras. The extras work but pull you out of the sam idiom and 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.