chrisgrieser/nvim-alt-substitute

github github
plugincommand-lineediting-support
stars 42
issues 0
subscribers 4
forks 0
CREATED

2023-04-06

UPDATED

10 months ago


nvim-alt-substitute

A substitute of vim's :substitute that uses lua patterns instead of vim regex. Since you really don't want to learn a whole new flavor of regex just to be able to make search-and-replace operations in your editor.

https://user-images.githubusercontent.com/73286100/231134276-e33b4ee8-611c-4b27-9c57-031ae13fc268.mp4

Colorscheme: dawnfox variant of nightfox.nvim

Motivation

Many people like me have only started using nvim after the introduction of lua as configuration language. While almost everything about neovim can be done with lua by now, search-and-replace via :substitute is one of few areas remaining where you still have to use vimscript. Regardless whether you like vimscript or not, learning vim's flavor of regex just for search-and-replace-operations feels somewhat unproductive.

Vim's :smagic does help a bit, but still requires additional backslashes where common regex syntax does not require them. Using verymagic gets you closest to common regex, but requires rather convoluted syntax that can make the command line hard to read in my view. And even if using magic or verymagic, vim's regex still differs from common regex syntax in various ways, like the the the way non-greedy quantifiers are written.

So for those of us who have never used neovim with anything other than lua, why not work with lua patterns for search-and-replace as well to drop the need to learn yet another regex flavor? For people already well-versed in vim regex, this plugin is indeed of little use, but for newcomers it may lower the barrier by removing the need to learn yet another regex flavor.

Features

  • Use :AltSubstitute (short form :S) to perform search-and-replace operations using lua patterns.
  • Incremental preview of the substitution.
  • Supports ranges, with % as default.
  • The g flag is supported and works like with :substitute.
  • New flags: i for case-insensitive search and f for fixed strings (literal strings).
:%s /\(\a\+\)\d\+/\1/g          -- :substitute
:S /(%a+)%d+/%1/g               -- :AltSubstitute
deviceModel2020 -> deviceModel  -- effect

Installation

-- lazy.nvim
{
    "chrisgrieser/nvim-alt-substitute",
      opts = true,
      -- lazy-loading with `cmd =` does not work well with incremental preview
      event = "CmdlineEnter",
},

-- packer
use {
    "chrisgrieser/nvim-alt-substitute",
    config = function() require("alt-substitute").setup({}) end,
}

Note
This plugin requires at least nvim 0.8, which introduced the incremental command preview feature.

Configuration

-- default values
opts = {
    showNotification = true, -- whether to show the "x replacements made" notification
}

The plugin uses ex-commands and comes without keymaps. You can set some of your own though. (Remember not to add <CR> at the end.)

-- prefill commandline with Substitution Syntax
vim.keymap.set({ "n", "x" }, "<leader>s", [[:S ///g<Left><Left><Left>]], { desc = "󱗘 :AltSubstitute" })

-- prefill commandline with Substitution Syntax and word under cursor
vim.keymap.set(
    { "n", "x" },
    "<leader>S",
    function() return ":S /" .. vim.fn.expand("<cword>") .. "//g<Left><Left>" end,
    { desc = "󱗘 :AltSubstitute (word under cursor)", expr = true }
)

Usage

The plugin registers the Ex-commands :AltSubstitue and :S as short form.

Flags

  • g: works the same as the g flag from :substitute: Without the g flag, only the first match in a line is replaced. With it, every occurrence in a line is replaced.
  • f: the search query and replace value are treated as fixed strings, meaning lua magic characters are treated as literal strings.
  • i: the search query is case insensitive. The i flag is ignored when the f flag is also used. (Also note that as opposed to :substitute, this plugin ignores the ignorecase and smartcase setting – case sensitivity is solely determined by whether this flag is present.)

Ranges

  • Ranges are line-based and work like all other vim command.
  • However, as opposed to :substitute, :AltSubstitute works on the whole buffer when no range is given. (In other words, % is the default range.)

Escaping

  • Like with :substitute, slashes (/) delimit search query, replace value, and flags. Therefore, to search for or replace a / you need to escape it with a backslash: \/.

Advanced Usage

Lua Pattern Tricks

Appearance

The incremental preview uses the same highlight group as :substitute, namely Substitition.

Command Line Completion

You can use cmp-cmdline-history to get suggestions of previous substitutions you made. If you find them not helpful, and do not want the suggestions to obfuscate your view of the buffer, then you can disable command suggestions for this plugin:

cmp.setup.cmdline(":", {
    sources = { --[[ your sources ]]
    },
    enabled = function()
        -- Set of commands where cmp will be disabled
        local disabled = {
            AltSubstitute = true,
            S = true,
        }
        -- Get first word of cmdline
        local cmd = vim.fn.getcmdline():match("%S+")
        -- Return true if cmd isn't disabled
        -- else call/return cmp.close(), which returns false
        return not disabled[cmd] or cmp.close()
    end,
})

Interactive Lua Pattern Evaluation

While unintended, I found this plugin's incremental preview to also be very useful for interactive testing of lua patterns. Without a replacement value, the plugin evaluates string.find() and with a replacement value, it evaluates string.gsub().

Current Limitations

  • :substitution flags other than g are not supported.
  • The ignorecase and the smartcase option are ignored. Instead, case sensitivity is termined by the presence or absense of the i flag.
  • inccommand=split is not supported, please use inccommand=unsplit instead.
  • Line breaks in the search or the replacement value are not supported.
  • Delimiters other than / are not supported yet. (You can make a PR to add them, the relevant functions are in the process-parameters module)

Add Support for more Regex Flavors

PRs adding support for more regex flavors, like for example javascript regex, are welcome. The plugin has been specifically built with extensibility in mind, so other regex flavors can be added by writing just one search function and one replace function. However, the bridging to other languages necessitates some tricky escaping. Also performance was an issue in my brief attempts, since the incremental preview basically runs the substitution on every keystroke.

Have a look this plugin's regex module to see want needs to be implemented, if you wanna give it a try.

Other Search-and-Replace Plugins

Credits

About Me
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.

Profiles

Buy Me a Coffee

[^1]: Frontier patterns are not mentioned in neovim's respective section for lua patterns. This is likely due to neovim using LuaJit, which itself is based on Lua 5.1, where frontier patterns were still an undocumented feature. But since frontier patterns are officially documented in the most recent lua version, it should be safe to assume that they are here to stay, so using them should not be an issue.