kate-deft/docs/SAM.md

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.