Ecosyste.ms: Awesome

An open API service indexing awesome lists of open source software.

Awesome Lists | Featured Topics | Projects

https://github.com/chrisgrieser/nvim-rip-substitute

Perform search and replace operations in the current buffer using a modern user interface and contemporary regex syntax.
https://github.com/chrisgrieser/nvim-rip-substitute

neovim-plugin nvim nvim-plugin replace-text search-and-replace substitution

Last synced: 2 days ago
JSON representation

Perform search and replace operations in the current buffer using a modern user interface and contemporary regex syntax.

Awesome Lists containing this project

README

        

# rip-substitute 🪦


badge

Search and replace in the current buffer with incremental preview, a convenient UI,
and modern regex syntax.

## Table of Contents

- [Features](#features)
- [Installation](#installation)
- [Configuration](#configuration)
- [Usage](#usage)
- [Advanced](#advanced)
- [Limitations](#limitations)
- [About the developer](#about-the-developer)

## Features
- Search and replace in the current buffer using
[ripgrep](https://github.com/BurntSushi/ripgrep).
- Uses **common regex syntax** — no more dealing with arcane vim regex.
- **Incremental preview** of matched strings and replacements, **live count** of
matches.
- Popup window instead of command line. This entails:
+ Syntax highlighting of the regex.
+ Editing with vim motions.
+ No more dealing with delimiters.
- **Sensible defaults**: entire buffer (`%`), all matches in a line
(`/g`), case-sensitive (`/I`).
- Substitute only in a **range**, with visual emphasis of the range
- **History** of previous substitutions.
- **Performant**: In a file with 5000 lines and thousands of matches, still
performs *blazingly fast*.â„¢
- **Regex101 integration**: Open the planned substitution in a pre-configured
[regex101](https://regex101.com/) browser-tab for debugging.
- **Quality-of-Life features**: automatic prefill of the escaped cursorword,
adaptive popup window width, toggle `--fixed-strings`, …
- Syntax comparison:
```txt
# all three are equivalent

# vim's :substitute
:% s/\(foo\)bar\(\.\)\@!/\1baz/gI

# vim's :substitute (very magic mode)
:% s/\v(foo)bar(\.)@!/\1baz/gI

# rip-substitute
(foo)bar(?!\.)
$1baz
```

## Installation
**Requirements**
- [ripgrep](https://github.com/BurntSushi/ripgrep) with `pcre2` support
+ `brew install ripgrep` (already includes `pcre2` by default)
+ `cargo install ripgrep --features pcre2`
- Alternatively, you can also use this plugin without `pcre2` by setting
`regexOptions.pcre2 = false` in the config. However, some features like
lookaheads are not supported then.
- Nvim >= 0.10
- optional: `:TSInstall regex` (adds syntax highlighting)

```lua
-- lazy.nvim
{
"chrisgrieser/nvim-rip-substitute",
cmd = "RipSubstitute",
keys = {
{
"fs",
function() require("rip-substitute").sub() end,
mode = { "n", "x" },
desc = " rip substitute",
},
},
},

-- packer
use {
"chrisgrieser/nvim-rip-substitute",
}
```

## Configuration

```lua
-- default settings
require("rip-substitute").setup {
popupWin = {
title = " rip-substitute",
border = "single",
matchCountHlGroup = "Keyword",
noMatchHlGroup = "ErrorMsg",
hideSearchReplaceLabels = false,
---@type "top"|"bottom"
position = "bottom",
},
prefill = {
---@type "cursorWord"| false
normal = "cursorWord",
---@type "selectionFirstLine"| false does not work with ex-command (see README).
visual = "selectionFirstLine",
startInReplaceLineIfPrefill = false,
alsoPrefillReplaceLine = false,
},
keymaps = { -- normal & visual mode, if not stated otherwise
abort = "q",
confirm = "",
insertModeConfirm = "",
prevSubst = "",
nextSubst = "",
toggleFixedStrings = "", -- ripgrep's `--fixed-strings`
toggleIgnoreCase = "", -- ripgrep's `--ignore-case`
openAtRegex101 = "R",
},
incrementalPreview = {
matchHlGroup = "IncSearch",
rangeBackdrop = {
enabled = true,
blend = 50, -- between 0 and 100
},
},
regexOptions = {
startWithFixedStringsOn = false,
startWithIgnoreCase = false,
-- pcre2 enables lookarounds and backreferences, but performs slower
pcre2 = true,
-- disable if you use named capture groups (see README for details)
autoBraceSimpleCaptureGroups = true,
},
editingBehavior = {
-- When typing `()` in the `search` line, automatically adds `$n` to the
-- `replace` line.
autoCaptureGroups = false,
},
notificationOnSuccess = true,
}
```

> [!NOTE]
> Any `ripgrep` config file set via `RIPGREP_CONFIG_PATH` is ignored by this
> plugin.

## Usage
**lua function**
```lua
vim.keymap.set(
{ "n", "x" },
"fs",
function() require("rip-substitute").sub() end,
{ desc = " rip substitute" }
)
```

- Normal mode: prefills the *escaped* word under the cursor
- Visual mode: prefills the *escaped* selection
- Visual *line* mode: replacements are only applied to the selected lines
(= the selection is used as range)

**Ex-command**
Alternatively, you can use the ex command `:RipSubstitute`, which also
accepts [a range
argument](https://neovim.io/doc/user/cmdline.html#cmdline-ranges). Note that
when using the ex-command, visual mode and visual line mode both pass a range.
To prefill the current selection, you therefore need to use the lua function.

```vim
" Substitute in entire file. Prefills the *escaped* word under the cursor.
:RipSubstitute

" Substitute in line range of the visual selection.
:'<,'>RipSubstitute

" Substitute in given range (in this case: current line to end of file).
:.,$ RipSubstitute
```

You can also pass a prefill for the search value, in which case the prefill
is *not* escaped.

```vim
:RipSubstitute prefilled_unescaped_string
```

## Advanced
**Remember prefill**
The function `require("rip-substitute").rememberCursorWord()` can be used to
save the word under the cursor for the next time `rip-substitute` is called.
(This overrides any other prefill for that run.)

One use case for this is to set a prefill for when you intend to run substitute
with a range, since calling `rip-substitute` in visual line is not able to pick
up a prefill.

**Filetype**
The popup window uses the filetype `rip-substitute`. This can be useful, for
instance, to disable auto-pairing plugins in the popup window.

**`autoBraceSimpleCaptureGroups`**
A gotcha of `ripgrep`'s regex syntax is that it treats `$1a` as the named
capture group "1a" and *not* as the first capture group followed by the
letter "a." (See `ripgrep`'s man page on `--replace` for details.)

If `regexOptions.autoBraceSimpleCaptureGroups = true` (the default),
`rip-substitute` automatically changes `$1a` to `${1}a`, to make writing the
regex more intuitive. However, if you regularly use named capture groups, you
may want to disable this setting.

## Limitations
- `--multiline` and various other flags are not supported yet.
- This plugin only searches the current buffer. To search and replace in
multiple files via `ripgrep`, use
[grug-far.nvim](https://github.com/MagicDuck/grug-far.nvim).

## About the developer
In my day job, I am a sociologist studying the social mechanisms underlying the
digital economy. For my PhD project, I investigate the governance of the app
economy and how software ecosystems manage the tension between innovation and
compatibility. If you are interested in this subject, feel free to get in touch.

I also occasionally blog about vim: [Nano Tips for Vim](https://nanotipsforvim.prose.sh)

- [Academic Website](https://chris-grieser.de/)
- [Mastodon](https://pkm.social/@pseudometa)
- [ResearchGate](https://www.researchgate.net/profile/Christopher-Grieser)
- [LinkedIn](https://www.linkedin.com/in/christopher-grieser-ba693b17a/)

Buy Me a Coffee at ko-fi.com