-
Notifications
You must be signed in to change notification settings - Fork 0
Substitute
jvim supports vim-style substitute commands for find-and-replace with regex, custom delimiters, range specification, and JSONPath-based structural substitution.
:[range]s/pattern/replacement/[flags]
| Command | Description |
|---|---|
:s/old/new/ |
Replace first match on current line |
:s/old/new/g |
Replace all matches on current line |
:%s/old/new/g |
Replace all matches in entire file |
:N,Ms/old/new/g |
Replace all matches in lines N to M (1-indexed) |
Any character immediately after s becomes the delimiter. This is useful when the pattern or replacement contains /:
:s#old#new#g → Uses # as delimiter
:s|old|new|g → Uses | as delimiter
:s@http://@https://@g → Uses @ to avoid escaping slashes
If you need the delimiter character inside the pattern or replacement, escape it with \:
:s/path\/to/path\/from/g → Replaces "path/to" with "path/from"
:s#a\#b#c\#d#g → Replaces "a#b" with "c#d"
The escape handling scans the text after s and builds parts: when \DELIM is encountered, the delimiter is treated as a literal character.
| Flag | Description |
|---|---|
g |
Global — replace all occurrences in each line (without this, only the first match per line is replaced) |
i |
Case-insensitive matching |
Flags can be combined: :s/old/new/gi
Full Python regular expression syntax is supported in the pattern:
:s/(\w+)/[\1]/g → Wrap each word in brackets (group capture)
:%s/^\s+//g → Remove leading whitespace
:s/(\d{4})-(\d{2})/\2-\1/g → Swap year-month format
Group references \1, \2, etc. are supported in the replacement string.
| Range | Description |
|---|---|
| (none) | Current line only |
% |
Entire file |
N,M |
Lines N through M (1-indexed, clamped to valid range) |
:%s/TODO/DONE/g → Entire file
:2,10s/from/to/g → Lines 2 through 10
:1,1s/old/new/ → First line only
When the search pattern starts with $. or $[, substitute operates on JSON structure instead of raw text. There are three modes, determined by the presence and position of =:
Pattern: JSONPath without = suffix
Renames JSON keys matching the path.
:s/$.name/username/g → Rename "name" to "username" at root
:s/$..name/label/g → Rename all "name" keys at any depth
:s/$.users[*].name/fullName/g → Rename "name" in each user object
Only the last segment of the path is renamed. The path must resolve to dict keys (not array indices).
Pattern: JSONPath ending with = (no filter value)
Replaces all values at the matching path.
:s/$.name=/Bob/g → Set root "name" to "Bob"
:s/$..status=/active/g → Set all "status" values to "active"
:s/$.users[*].age=/0/g → Reset all user ages to 0
:s/$..enabled=/true/g → Set all "enabled" to true
Pattern: JSONPath with =value filter
Replaces only values that match the filter condition.
:s/$..status="draft"/review/g → Replace only "draft" statuses with "review"
:s/$..count=0/1/g → Replace zero counts with 1
:s/$..enabled=false/true/g → Enable all disabled flags
Replacement values are automatically type-detected:
| Input | Detected Type | JSON Value |
|---|---|---|
null |
null | null |
true |
boolean | true |
false |
boolean | false |
42 |
number | 42 |
3.14 |
number | 3.14 |
hello |
string | "hello" |
"hello" |
string (already quoted) | "hello" |
The detection logic:
-
null,true,false→ passed through as-is - Parseable as float → passed through as-is
- Already wrapped in quotes → passed through as-is
- Everything else → JSON-encoded with
json.dumps()
When a JSONPath matches multiple locations, the substitute engine tracks used positions ((row, col) pairs) to prevent duplicate replacements. If a position has already been substituted, it searches forward from (row, col+1) for the next occurrence.
In JSONL mode, JSONPath substitution operates on each record independently:
- Content is split into blocks (groups of consecutive non-empty lines)
- Each block is parsed as a separate JSON document
- JSONPath resolution and substitution run per-record
- Results are mapped back to editor line positions using block start offsets
- The same pattern can match and substitute across all records
If the replacement string produces newlines (contains literal \n):
- The result is split by newlines
- Line indices are adjusted accordingly
- All folds and collapsed strings are cleared (since the structure has changed)
All substitutions can be undone with u. The entire substitution operation (across all affected lines) is recorded as a single undo point.
If no matches are found, the undo stack entry is automatically removed (no-op doesn't pollute history).
| Situation | Message |
|---|---|
| Success | "N substitution(s)" |
| No match | "Pattern not found: pattern" |
| Invalid JSON after substitution | "Invalid JSON: msg (line N)" |
| Invalid JSONPath | "Invalid JSONPath: error" |
| Path matches only containers | "JSONPath matches only objects/arrays (not substitutable)" |