kate-deft/docs/SAM.md

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's regexp(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.