140 lines
5.5 KiB
Markdown
140 lines
5.5 KiB
Markdown
# 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 `$` 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.
|