4.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).
The regex engine
katesam uses a faithful port of sam's own regular-expression engine
(src/sam/samregex.{h,cpp}, from plan9port src/cmd/sam/regexp.c), not PCRE.
This is a Thompson/Pike NFA, which gives the two properties sam relies on:
- Leftmost-longest (POSIX) matching, so results match real sam exactly, including overlapping alternation.
- Linear time, with no backtracking — immune to catastrophic blowup. A
pattern like
(a*)*bagainst thousands ofareturns instantly.
There is no PCRE deviation anymore: the dialect, the match semantics, and the anchors are sam's.
Dialect
The sam dialect is deliberately small (sam regexp(7)):
. any character except newline
* + ? zero-or-more / one-or-more / zero-or-one
| alternation
( ) grouping and capture (\1..\9 in replacements)
[ ] [^ ] character class / negated class (ranges a-z)
^ $ beginning / end of a LINE
\c literal c (escapes a metacharacter); \n is newline
There is intentionally no \d \w \s \b, no lookaround, no non-greedy, and
no inline flags. sam never had them. Use character classes ([0-9],
[a-zA-Z_]) and structural composition (x y g v) instead — that is the sam
way, and patterns stay portable to real sam.
Semantics (all verified against the port)
| behaviour | katesam | note |
|---|---|---|
overlapping alternation a|ab on ab |
matches ab |
leftmost-longest; order of branches does not matter |
. crosses newline |
no | matches sam |
^ / $ |
per-line | ^ = start of a line, $ = end of a line |
[^z]+ crosses newline |
no | negated classes also exclude \n, as in sam |
empty-match global s/x*/-/g on axbx |
-a-b- |
one advance per null match |
& whole match, \1..\9 groups in replacement |
yes | up to 9 capture groups |
pathological (a*)*b |
linear time | NFA, no backtracking |
Anchors are per-line
^ matches the beginning of any line and $ the end of any line — built into
the engine (sam regexp.c: BOL fires at offset 0 or after \n; EOL before
\n). So:
| program | input | result |
|---|---|---|
,s/^/> /g |
a⏎b⏎c |
> a⏎> b⏎> c |
,s/$/;/g |
a⏎b⏎c |
a;⏎b;⏎c; |
The structural idiom is equivalent and composes with guards/loops:
,x/.+/ s/^/> / prefix every (non-empty) line → > a⏎> b⏎> c
,x/.+/ a/;/ append ";" to every line → a;⏎b;⏎c;
Composing edits — the sam way
sam's power is structural composition, not clever single regexes. Descend into
matches with x/y, guard with g/v, group with {}:
,x/[a-zA-Z_]+/ g/^[A-Z]/ c/CONST/ every identifier starting uppercase → CONST
,x/"[^"]*"/ s/foo/bar/g foo→bar only inside double-quoted strings
0/start/,/end/ x/[a-z]+/ s/.*/[&]/ bracket every lowercase word in a region
Multi-file X/Y applies an inner program across the open buffers:
X/\.cpp$/ ,s/old_api/new_api/g rewrite every open .cpp buffer
Y/_test\./ ,x/TODO/ d drop TODO lines in non-test buffers
(X/Y iterate the set of documents Kate currently has open — acme's
open-window set — not the project on disk. The regex matches each buffer's file
path, or its display name for an unsaved scratch buffer. Nothing is opened or
read from disk; edits land in the live buffers, each its own undo step. Review
and save deliberately.)
Practical guidance
- Use character classes, not PCRE shorthands:
[0-9]not\d,[a-zA-Z_]not\w. - Prefer structural composition (
x g v {}) over one dense pattern. ^/$are per-line; anchor directly (,s/^…) or loop (,x/.+/ …).- Empty-match globals (
s/x*/…/g) advance one position per null match — safe, but as always with*, check the result.
Scope
Supported: the full address language (#n n 0 $ . ' /re/ ?re? compound
+ - , ;) and commands a c i d s p = m t k x y g v {} plus shell filters
< > | !. Out of scope (Kate manages files / its own undo): the multi-file menu
(b B n D), external file I/O (e r w f), "re" file-addressing, and sam's own
u (use Kate's Ctrl+Z).