# katesam — sam structural-regexp editing in Kate The **Sam** tool view (left sidebar) runs the plan9 [sam](https://doc.cat-v.org/plan_9/4th_edition/papers/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.