Skip to content

Substitute

edgar edited this page Feb 23, 2026 · 1 revision

Substitute

jvim supports vim-style substitute commands for find-and-replace with regex, custom delimiters, range specification, and JSONPath-based structural substitution.

Basic Syntax

:[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)

Custom Delimiters

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

Escaped Delimiters

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.

Flags

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

Regex Support

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 Specification

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

JSONPath Substitute

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 =:

Mode 1: Key Rename

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).

Mode 2: Unconditional Value Replace

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

Mode 3: Filtered Value Replace

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

Auto-Type Detection

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:

  1. null, true, false → passed through as-is
  2. Parseable as float → passed through as-is
  3. Already wrapped in quotes → passed through as-is
  4. Everything else → JSON-encoded with json.dumps()

Position Deduplication

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.

JSONL Substitute

In JSONL mode, JSONPath substitution operates on each record independently:

  1. Content is split into blocks (groups of consecutive non-empty lines)
  2. Each block is parsed as a separate JSON document
  3. JSONPath resolution and substitution run per-record
  4. Results are mapped back to editor line positions using block start offsets
  5. The same pattern can match and substitute across all records

Multiline Handling

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)

Undo

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).

Status Messages

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)"

한국어 | Home

Clone this wiki locally