Code Monkey home page Code Monkey logo

substitute.nvim's Introduction

🪓 substitute.nvim

Lua GitHub Workflow Status

substitute.nvim aim is to provide new operator motions to make it very easy to perform quick substitutions and exchange.

If you are familiar with svermeulen/vim-subversive and tommcdo/vim-exchange, this plugin does almost the same but rewritten in lua (and I hope this will be more maintainable, readable and efficient).

✨ Features

  • 🪓 Substitute operator
  • 🔁 Substitute over range motion
  • 🔀 Exchange operator

See this plugin in action

⚡️ Requirements

  • Neovim >= 0.8.0

(Neovim 0.6.0 compat)

📦 Installation

Install the plugin with your preferred package manager:

{
    "gbprod/substitute.nvim",
    opts = {
        -- your configuration comes here
        -- or leave it empty to use the default settings
        -- refer to the configuration section below
    }
}

⚙️ Configuration

Substitute comes with the following defaults:

{
  on_substitute = nil,
  yank_substituted_text = false,
  preserve_cursor_position = false,
  modifiers = nil,
  highlight_substituted_text = {
    enabled = true,
    timer = 500,
  },
  range = {
    prefix = "s",
    prompt_current_text = false,
    confirm = false,
    complete_word = false,
    subject = nil,
    range = nil,
    suffix = "",
    auto_apply = false,
    cursor_position = "end",
  },
  exchange = {
    motion = false,
    use_esc_to_cancel = true,
    preserve_cursor_position = false,
  },
}

More details on these options is available in the sections below corresponding to the different features.

🪓 Substitute operator

This plugin contains no default mappings and will have no effect until you add your own maps to it.

-- Lua
vim.keymap.set("n", "s", require('substitute').operator, { noremap = true })
vim.keymap.set("n", "ss", require('substitute').line, { noremap = true })
vim.keymap.set("n", "S", require('substitute').eol, { noremap = true })
vim.keymap.set("x", "s", require('substitute').visual, { noremap = true })

Then you can then execute s<motion> to substitute the text object provided by the motion with the contents of the default register (or an explicit register if provided). For example, you could execute siw to replace the current word under the cursor with the current yank, or sip to replace the paragraph, etc. (this action is dot-repeatable)

Note: in this case you will be shadowing the change character key s so you will have to use the longer form cl.

Each functions (operator, line, eol and visual) are configurable:

lua require('substitute').operator({
  count = 1,       -- number of substitutions
  register = "a",  -- register used for substitution
  motion = "iw",   -- only available for `operator`, this will automatically use
                   -- this motion for substitution instead of waiting for.
  modifiers = nil, -- this allows to modify substitued text, will override the default
                   -- configuration (see below)
})

⚙️ Configuration

on_substitute

Default : nil

Function that will be called each times a substitution is made. This function takes a param argument that contains the register used for substitution.

yank_substituted_text

Default : false

If true, when performing a substitution, substitued text is pushed into the default register.

highlight_substituted_text.enabled

Default : true

If true will temporary highlight substitued text.

highlight_substituted_text.timer

Default : 500

Define the duration of highlight.

preserve_cursor_position

Default : false

If true, the cursor position will be preserved when performing a substitution.

modifiers

Default : nil

Could be a function or a table of transformations that will be called to modify substitued text. See modifiers section below.

➰ Modifiers

Modifiers are used to modify the text before substitution is performed. You can chain those modifiers or even use a function to dynamicly choose modifier depending on the context.

Available modifiers are:

  • linewise : will create a new line for substitution ;
  • reindent : will reindent substitued text ;
  • trim : will trim substitued text ;
  • join : will join lines of substitued text.

Examples

If you want to create a new line for substitution and reindent, you can use:

require('substitute').operator({
  modifiers = { 'linewise', 'reindent' },
})

If you want to trim and join lines of substitued text, you can use:

require('substitute').operator({
  modifiers = { 'join', 'trim' },
})

If you want to trim text but only if you substitute text in a charwise motion, you can use:

require('substitute').operator({
  modifiers = function(state)
    if state.vmode == 'char' then
      return { 'trim' }
    end
  end,
})

If you always want to reindent text when making a linewise substitution, you can use:

require('substitute').operator({
  modifiers = function(state)
    if state.vmode == 'line' then
      return { 'reindent' }
    end
  end,
})

🤝 Integration

gbprod/yanky.nvim

To enable gbprod/yanky.nvim swap when performing a substitution, you can add this to your setup:

require("substitute").setup({
  on_substitute = require("yanky.integration").substitute(),
})
svermeulen/vim-yoink

To enable vim-yoink swap when performing a substitution, you can add this to your setup:

require("substitute").setup({
  on_substitute = function(_)
    vim.cmd("call yoink#startUndoRepeatSwap()")
  end,
})

vim-yoink does not support swapping when doing paste in visual mode. With this plugin, you can add thoss mappings to enable it :

vim.keymap.set("x", "p", require('substitute').visual, { noremap = true })
vim.keymap.set("x", "P", require('substitute').visual, { noremap = true })

🔁 Substitute over range motion

Another operator provided allows specifying both the text to replace and the line range over which to apply the change by using multiple consecutive motions.

vim.keymap.set("n", "<leader>s", require('substitute.range').operator, { noremap = true })
vim.keymap.set("x", "<leader>s", require('substitute.range').visual, { noremap = true })
vim.keymap.set("n", "<leader>ss", require('substitute.range').word, { noremap = true })

After adding this map, if you execute <leader>s<motion1><motion2> then the command line will be filled with a substitute command that allow to replace the text given by motion1 by the text will enter in the command line for each line provided by motion2.

Alternatively, we can also select motion1 in visual mode and then hit <leader>s<motion2> for the same effect.

For convenience, <leader>ss<motion2> can be used to select complete word under the cursor as motion1 (complete word means that complete_word options is override to true so is different from siwip which will not require that there be word boundaries on each match).

You can select the default replacement value by selecting a register. Eg: "a<leader>s<motion1><motion2> will use the content of a register as replacement value.

You can override any default configuration (described later) by passing this to the operator function. By example, this will use S as prefix of the substitution command (and use tpope/vim-abolish):

vim.keymap.set("n", "<leader>S", function ()
    require('substitute.range').operator({ prefix = 'S' })
end, { noremap = true })

⚙️ Configuration

range.prefix

Default : s

Substitution command that will be used (set it to S to use tpope/vim-abolish substitution by default).

range.suffix

Default : ""

Suffix added at the end of the substitute command. For example, it can be used to not save substitution history calls by adding | call histdel(':', -1).

range.prompt_current_text

Default : false

Substitution command replace part will be set to the current text. Eg. instead of s/pattern//g you will have s/pattern/pattern/g.

range.confirm

Default : false

Will ask for confirmation for each substitutions.

range.complete_word

Default : false

Will require that there be word boundaries on each match (eg: \<word\> instead of word).

range.group_substituted_text

Default : false

This will capture substituted text as you can use \1 to quickly reuse it.

range.subject

Default : nil

This allows you to control how the subject (to be replaced) is resolved. It accepts either a function, string, or a table with some special keys.

If it is a string that will be used directly. If it is a function it will be called when the operator is used, and should return the subject to be replaced. If it is a table you may provide one of the following keys with appropriate values:

  • register = "a" Use the contents of this register as the subject.
  • expand = "<cword>" Use the string given as the argument to vim.fn.expand() to get the subject.
  • last_search = true Shortcut for register = "/" to use the last / search.
  • motion = "iw" Use this motion at the current cursor to get the subject

eg. lua require('substitute.range').operator({ subject = {motion = 'iW'} }) will select inner WORD as subject of substitution.

range.range

Default : nil

This allows you to control the range of the substitution. This takes either a function, string, or a table with some special keys. If it is a string that will be used directly. If it is a function it will be called after the subject is resolved and should return a string. If it is a table you may provide one of the following keys with appropriate values:

  • motion = "ap" Use this motion from the current cursor to get the range.

eg. specifying range = '%' will make the substitution run over the whole file. See :h [range] for all the possible values here.

eg. lua require('substitute.range').operator({ range = { motion = 'ap' } }) will select around paragraph as range of substitution.

You can combine subject and range : lua require('substitute.range').operator({ subject = { motion='iw' }, range = { motion = 'ap' } }) will prepare substitution for inner word around paragraph.

range.register

Default : nil

This will use the content of this register as replacement value.

eg. lua require('substitute.range').operator({ register = 'a' }) will use "a register content as replacement.

range.auto_apply

Default : false

Will automatically apply the changes on the buffer. You will not have to validate the command line.

range.cursor_position

Default : end

This will set the cursor position in the command line at the end or at the beginning of the replacement text. Possible values are end and start.

eg. With range.cursor_position = 'start', the cursor will be set here: s/foo/|bar/g but with range.cursor_position = 'end' it will be set to s/foo/bar|/g.

🤝 Integration

tpope/vim-abolish

You can use tpope/vim-abolish substitution by default.

require("substitute").setup({
  range = {
    prefix = "S",
  }
})

🔀 Exchange operator

This operator allows to quickly exchange text inside a buffer.

Eg. To exchange two words, place your cursor on the first word and type sxiw. Then move to the second word and type sxiw again.

Note: the {motion} used in the first and second use of sx don't have to be the same. Note 2: this is dot-repeatable, so you can use . instead of sxiw for the second word.

You can select a whole line using the line function (sxx in the example below).

Because this operator has to be invoked twice to change the document, if you change your mind after invoking the operator once, you can cancel you selection using <Esc> key or the cancel function (mapped to sxc in the example below).

vim.keymap.set("n", "sx", require('substitute.exchange').operator, { noremap = true })
vim.keymap.set("n", "sxx", require('substitute.exchange').line, { noremap = true })
vim.keymap.set("x", "X", require('substitute.exchange').visual, { noremap = true })
vim.keymap.set("n", "sxc", require('substitute.exchange').cancel, { noremap = true })

⚙️ Configuration

exchange.motion

Default : nil

This will use this motion for exchange.

eg. lua require('substitute.exchange').operator({ motion = 'ap' }) will select around paragraph as range of exchange.

exchange.use_esc_to_cancel

Default : true

If true, you can use the <Esc> key to cancel exchange selection. If set to false, consider map the cancel function:

vim.keymap.set("n", "sxc", require('substitute.exchange').cancel, { noremap = true })

exchange.preserve_cursor_position

Default : false

If true, the cursor position will be preserved when performing an exchange.

🎨 Colors

Description Group Default
Selected range for range substitution SubstituteRange link to Search
Selected text for exchange SubstituteExchange link to Search

🎉 Credits

This plugin is a lua version of svermeulen/vim-subversive and tommcdo/vim-exchange awesome plugins.

substitute.nvim's People

Contributors

barklan avatar gbprod avatar gh-liu avatar indianboy42 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar

substitute.nvim's Issues

How is integration with yanky supposed to be set up?

I want the following feature: I want to be able to visually select a word, then press su, then <C-n><C-n> ... using the yank ring.
If I read the documentation correctly, this is supposed to do the trick:

        config = function()
            require("substitute").setup({
                on_substitute = function(event)
                    require("yanky").init_ring("p", event.register, event.count, event.vmode:match("[vV�]"))
                end
            })
            -- [...]
            vim.keymap.set("x", "su", "<cmd>lua require('substitute').visual()<cr>")

But that doesn't work. What happens is that when pressing viwsu the visual selection is correctly replaced, but on pressing <C-n>, the original word is restored. Pressing <C-n> again does nothing.
Previously, I also had the case that pressing <C-n> the second time would restore the original word AND then I could cycle through the yank ring entries directly after the word – but I can't reproduce that now anymore.

However, I do get the desired behaviour by just swapping out the mapping like so:
vim.keymap.set("x", "su", "<Plug>(YankyPutAfter)")

What's up with this? Am I reading the doc wrong and this behaviour is intended or is this a bug?

getting

Hi so the last week or so, when I use the operator it does substitute the word, but I also get this error:

E5108: Error executing lua ...local/share/nvim/lazy/substitute.nvim/lua/substitute.lua:45: attempt to index field 'highlight_substituted_text' (a nil value)
stack traceback:
        ...local/share/nvim/lazy/substitute.nvim/lua/substitute.lua:45: in function <...local/share/nvim/lazy/substitute.nvim/lua/substitute.lua:31>

Tried updating all plugins, reinstalling substitute.nvim, it still does it, with all these (default mapping) motions:


vim.keymap.set("n", "s", require('substitute').operator, { noremap = true })
vim.keymap.set("n", "ss", require('substitute').line, { noremap = true })
vim.keymap.set("n", "S", require('substitute').eol, { noremap = true })
vim.keymap.set("x", "s", require('substitute').visual, { noremap = true })

on Endavour Linux, fully updated and NVIM:

NVIM v0.9.4
Build type: Release
LuaJIT 2.1.1696795921

   system vimrc file: "$VIM/sysinit.vim"
     fall-back for $VIM: "/usr/share/nvim"

Best,

Broken install/Substitute over range

I'm getting an error when I try to execute substitute over range
Screen Shot 2023-03-20 at 8 48 31 AM

Relevant config. I have it imported with vim-plug towards the top of the file.
Screen Shot 2023-03-20 at 8 50 35 AM

I apologize if this is strictly local.

NVIM v0.8.3                                                                                                                                                            
Build type: Release
LuaJIT 2.1.0-beta3

[Feature Request] Group substituted word

Currently, substituting my_text leaves you with :'[,']s/my_text/|/g (| being the cursor):

My suggestion, is to instead use :'[,']s/\(my_text\)/|/g, automatically capturing my_text in a group.

This would allow users to more quickly complete with the current text with \1. For example:

:'[,']s/\(my_text\)/\1_is_amesome/g
:'[,']s/\(my_text\)/this_is_\1/g
:'[,']s/\(my_text\)/this_is_\1_and_now_way_longer/g

which result in

my_text_is_amesome
this_is_my_text
this_is_my_text_and_now_way_longer

This is an alternative to yank_substituted_text. However, I prefer it, because it doesn't clobber the yank register.

[substitute.range] Replace occurence with register in ... <some motion>

Hello! Great plugin. I'm a former Atom user of vim mode plus and I'm trying to set this up similarly to the occurrence modifier, and it's working mostly good.

With substitute.range, Is there a way to specify a subject.motion, the register, and have it execute the replace without needing to press enter to confirm the command?

What I'm actually doing is:

  • yank some text (e.g. a word),
  • move cursor to top of word in function to replace,
  • trigger the following require("substitute.range").operator({ subject = { motion = "iw" }, register = "0" })
  • do the motions to indicate range (e.g. function motion would be if)
  • press enter to confirm replacement (because I've already set it to the register, I'm hoping to eliminate this perhaps with another configuration option)

[Feature Request] Do not save substitutions in call history

When executing the substitution command, it stays in the : call history, together with all other commands. If you make a lot of substitutions, that's quite the problem. All of the (manual) : commands that you want to access in the history get drowned by a huge amount of substitution commands that you probably won't want to reuse.

It would be nice if the substitution command didn't save to the : call history.


As a suggestion, one possible implementation of this is with the use of histdel. The following command performs a substitution, but does not stay on the call history:

:'[,']s/old_text/new_text/g | call histdel(':', -1)

[substitute.range] Quickly prefix text

Hi there! I've managed to add the behaviour I want for adding text to a current selection.

-- Mark word visually, then edit all occurences based on range / motion.
vim.keymap.set("x", "mA", function()
	require("substitute.range").visual({ prompt_current_text = true })
end, { noremap = true })

But there doesn't appear to be an easy way to start editing from the start of the current text. I find myself press the arrow keys to go to the start of the word, and it's worse the longer the current_text is, ctrl+left arrow seems to jump to the start of the command which isn't ideal either, and alt+left arrow doesn't work

I understand it's a bit of a hack to want to prefix things this way. Hopefully it's a small change.

[Feature Request] Substitute with register

I currently have the following setup in my vim-subversive:

map('x', 'py', '"0<plug>(SubversiveSubstitute)', {noremap = false})
map('x', 'Py', '"0<plug>(SubversiveSubstituteRange)', {noremap = false})
map('x', 'pd', '"1<plug>(SubversiveSubstitute)', {noremap = false})
map('x', 'Pd', '"1<plug>(SubversiveSubstituteRange)', {noremap = false})
map('x', 'p+', '"+<plug>(SubversiveSubstitute)', {noremap = false})
map('x', 'P+', '"+<plug>(SubversiveSubstituteRange)', {noremap = false})

So, for example, after visually selecting some_text:

  • py will replace some_text with copied_text
  • pd will replace some_text with deleted_text
  • Pd5j will replace every instance of some_text up to 5 lines down with deleted_text

It'd be nice if substitute.nvim could also interact with registers in this way (e.g. an option to specify a register that would be used in the substitution).

Feature Suggestion: Duplication Operator

Maybe a bit far-fetched, but I feel like a duplication operator would kinda fit with the spirit of this plugin. Basically, the duplication operator should create a copy of the passed text object.

A default keybinding that could make sense would be yd, since duplication is a form of yanking (and since gd is already taken for goto-definition).

ydiw would then duplicate a word, ydd would duplicate a line, yD would duplicate from cursor position to end of line. If the text object covers at least a line, the duplication would copy lines down. If the text object covers less than a line, the duplication would be appended after the text object, but in the same line.

The same way sip is basically an equivalent for vipp, ydip would equivalent to yipp.

Highlighting the subject works only when cursor position is at the start of the subject

Hello! I noticed a visual bug / inconsistency and I'm not sure if it's just me.

When I run require("substitute.range").operator(), and do w (word motion), it shows the highlights for matching occurrences as expected.

image
(Example with a subword motion)

However when doing inner word (or in my case inner subword), no highlighting is displayed for the subject, not even the first, when I start from somewhere in the middle:

image

It does however shift the cursor to the start of the subject (which is fine) though it would be nice to get visual feedback before doing the motion to replace.

Proceeding with the motion shows the command mode text and the rest works fine, so it's functionally OK.

image

vim.highlight.link is deprecated

Latest Neovim on master (rev 782f726) shows the following warning:

vim.highlight.link is deprecated, use vim.api.nvim_set_hl instead. See :h deprecated
This function will be removed in Nvim version 0.9
stack traceback:
    usr/share/nvim/runtime/lua/vim/highlight.lua:32: in function 'link'
    ...ite/pack/packer/start/substitute.nvim/lua/substitute.lua:13: in function 'setup'

trim when substituting into not-linewise text object

Take this example:

local foo = {
	"apple",
	"orange",
	"banana"
}
buySome(fruits)

I yank "banana" with yy, go two lines down, and want to substitute "fruits" via si).

-- result includes the leading whitespace
buySome(	"banana")

-- preferred result
buySome("banana")

This issue happens mostly when substituting a linewise register onto a characterwise text obejct. While the plugin is smart enough not to add the trailing line break, it does still insert the leading whitespace, which is slightly annoying. And I cannot think of any scenario where that would be desirable

Issue with ` key remapped

Hi there, I noticed visual() functions not working properly after I set a custom mapping to ` (backtick key).

I fixed this by using this function instead:

function my_visual()
  substitute.state.register = vim.v.register
  substitute.state.count = (vim.v.count > 0 and vim.v.count or 1)
  vim.o.operatorfunc = "v:lua.require'substitute'.operator_callback"
  vim.api.nvim_feedkeys("g@`<", "in", false)
end

The only difference is that I appended "n" to the mode argument of nvim_feedkeys.

Do you think this is worth merging for all other users?

(This also makes me wonder, what's the previous mode "i" for? Reading the documentation didn't really help.)

[Bug] Ranged highlight only highlights the previously used range.

Here's an example. The first time the visual ranged substitution is used on map, all of the maps are correctly highlighted:
normal

Then I follow it up with 3j, but change my mind and cancel with <esc>. Now, if I repeat the substitution on map, only maps 3 lines down are highlighted, and the rest are left without highlight:
bugged

Great plugin btw :)
This is a really cool feature that isn't present in vim-subversive

`<Esc>` bug, and custom `<Esc>` mapping is removed

This issue is two-fold. First of all, substitute.nvim removes custom <Esc> mappings when calling exchange.operator(). Secondly, I've noticed another bug related to <Esc>. Here's a config to reproduce the bug:

Click to expand
-- Ignore default config and plugins
vim.opt.runtimepath:remove(vim.fn.expand('~/.config/nvim'))
vim.opt.packpath:remove(vim.fn.expand('~/.local/share/nvim/site'))

-- Append test directory
local test_dir = vim.fn.expand('~/code-other/nvim-test-config')
vim.opt.runtimepath:append(vim.fn.expand(test_dir))
vim.opt.packpath:append(vim.fn.expand(test_dir))

-- Install packer
local install_path = test_dir .. '/pack/packer/start/packer.nvim'
local install_plugins = false

if vim.fn.empty(vim.fn.glob(install_path)) > 0 then
  vim.cmd('!git clone https://github.com/wbthomason/packer.nvim ' .. install_path)
  vim.cmd('packadd packer.nvim')
  install_plugins = true
end

local packer = require('packer')

packer.init({
  package_root = test_dir .. '/pack',
  compile_path = test_dir .. '/plugin/packer_compiled.lua'
})

packer.startup(function()
  function Use(module)
    use(require(string.format('configs.%s', module)))
  end

  -- Packer can manage itself
  packer.use 'wbthomason/packer.nvim'

  use { 'gbprod/substitute.nvim',
    config = function()
      local substitute = require('substitute')
      local exchange = require('substitute.exchange')

      vim.keymap.set('n', '<Esc>', function() print('escape') end)
      vim.keymap.set('n', 'sx', exchange.operator)

      substitute.setup()
    end
  }

  if install_plugins then
    packer.sync()
  else
    -- load plugins at your earliest convenience
    vim.defer_fn(function()
      vim.cmd('doautocmd User LoadPlugins')
    end, 1)
  end
end)

To reproduce:

  1. > nvim -u *path_to_config_above* *path_to_any_file*
  2. Press sxiw on some word
  3. Press <Esc> twice
  4. After the second <Esc>, insert mode is entered and the text 6> (or similar) is inserted

I'm guessing that this is related to #43.

[Feature Request] substitute/exchange operator provides 2 motion calls.

For example, if the cursor is on props and press gs<motion1><motion2>, motion1 will replace motion2.

({ props }) -- press gsiwa{ on props

becomes:

(props)

This may seem useless on its own, but when combined with plugins like flash.nvim remote mode, operation can be performed even when the cursor is not on that text.

For example, exchange import and Homepage even cursor is not on them.

Screen.Recording.2024-02-02.at.09.45.42.mov

substitute<motion1><motion2> and exchange<motion1><motion2> will make it faster like this.

attempt to index field 'highlight_substituted_text' (a nil value)

Everytime I am trying to substitute I am getting the following error:

E5108: Error executing lua ...local/share/nvim/lazy/substitute.nvim/lua/substitute.lua:45: attempt to index field 'highlight_substituted_text' (a nil value)                                                                                                             
stack traceback:                                                                                                                                                                                                                                                         
        ...local/share/nvim/lazy/substitute.nvim/lua/substitute.lua:45: in function <...local/share/nvim/lazy/substitute.nvim/lua/substitute.lua:31>

Making escape work to cancel exchange action

Hi, great plugin, a nice replacement for the vim script alternatives. I was wondering if it could be made to respect esc when the exchange is initiated. I.e after the first cx[motion], instead of going off with cxc, to cancel to use . Something similar to how nvim surround works, which is also in a way a two step process (sometimes, depends on the action, but the idea is the same). Or I imagine something like a buffer local variable could be set to allow you to know if you are in the exchange state.

Error 'start' is higher than 'end'

Error info

E5108: Error executing lua ...ite/pack/packer/start/substitute.nvim/lua/substitute.lua:34: 'start' is higher than 'end'                                                                                      
stack traceback:                                                                                                                                                                                             
        [C]: in function 'text'                                                                                                                                                                              
        ...ite/pack/packer/start/substitute.nvim/lua/substitute.lua:34: in function <...ite/pack/packer/start/substitute.nvim/lua/substitute.lua:31>

Steps to reproduce

struct Monster {
  health: i32,  // With your cursor here copy the content of {} --- yiB or yi{
}

struct Wizard {} // Then here and try to put it inside the {} with your mapping ---- <leader>piB or <leader>pi{ 

Expected behavior

struct Monster {
  health: i32,
}

struct Wizard {
  health: i32,
}

unicode handling does not work correctly

try it on this file:

Aₜₛₓ
lol

Yank the lower word with yiw, then go to the second letter gg0l and then substitute over 2 letters su2l.
That should give me:

Alolₓ

but I get:

Alol<82><9b>ₓ

but actually these <82><9b> seem to be some special thingys. when I copy the upper line straight from nvim, I get
汁汯鮂苢ઓ

`highlight_substituted_text` doesn't seem to play nice with `vim.highlight.on_yank()`

I have the following configuration:

autocmd('TextYankPost', {
  group = group,
  pattern = '*',
  callback = function() vim.highlight.on_yank() end,
})

To highlight yanked text.

And the following substitute.nvim configuration;

return {
  'gbprod/substitute.nvim',
  version = '*',
  config = function()
    local substitute = require('substitute')
    local substitute_range = require('substitute.range')

    vim.keymap.set('n', 'S', substitute.eol, { noremap = true })
    vim.keymap.set('n', 's', substitute.operator, { noremap = true })
    vim.keymap.set('n', 'ss', substitute.line, { noremap = true })
    vim.keymap.set('x', 's', substitute.visual, { noremap = true })

    vim.keymap.set('n', '<Leader>s', substitute_range.operator, { noremap = true })
    vim.keymap.set('n', '<Leader>ss', substitute_range.word, { noremap = true })
    vim.keymap.set('x', '<Leader>s', substitute_range.visual, { noremap = true })
  end,
}

After upgrading to 1.1.2 I see the following error:

E5108: Error executing lua ...local/share/nvim/lazy/substitute.nvim/lua/substitute.lua:45: attempt to index field 'highlight_substituted_text' (a nil value)
stack traceback:
        ...local/share/nvim/lazy/substitute.nvim/lua/substitute.lua:45: in function <...local/share/nvim/lazy/substitute.nvim/lua/substitute.lua:31>

If I remove the auto command above, the issue is gone.

keep cursor at positon before operation

Hi. I switched to this plugin from vim-subversive. So far I am really enjoying it. I especially like the exchange-operator and that I can pass the desired motion to the operation as an option. The only thing I am missing from vim-subversive is the subversivePreserveCursorPosition option which keeps the cursor at the position before the operation. For example if I initialize the exchange-operation with sxiw the cursor jumps to the beginning of the word. This happens with all operations. Would it be possible to prevent the cursor from changing its position? Thank you.

Question about integration with `yanky.nvim`

Hi, I recently learned about your plugins and I'm taking them for a test drive

I had a question about the integration:

  • In yank.nvim, it's mentioned to use this to integrate with substitute.nvim:
require("substitute").setup({
  on_substitute = require("yanky.integration").substitute(),
})
  • And in substitute.nvim, it's mentioned to use this to integrate with yanky.nvim:
require("substitute").setup({
  on_substitute = function(event)
    require("yanky").init_ring("p", event.register, event.count, event.vmode:match("[vV�]"))
  end,
})

So, I was wondering which one is the correct one 🤔

(also the last character in ...vmode:match("[vV<this-char>]") appears as a question mark for me when I copy/paste, so I'd appreciate (if that's the one I can use) if you let me know which character it is

Thank you

`motion2` isn't working with `substitute.range`

I am unable to type the replacement text with the following mappings/commands.
I have tried the following mappings, trying both silent=true|false, and noremap=true|false:

-- Using `.operator()`
map(
    "n",
    "s;",
    "<Cmd>lua require('substitute.range').operator({motion1 = 'iw', motion2 = 'ie'})<CR>",
    {silent = false, noremap = true}
)

-- Or use `.word()`
map(
    "n",
    "s;",
    "<Cmd>lua require('substitute.range').word({motion2 = 'ie'})<CR>",
    {silent = false, noremap = true}
)

-- Or using a Lua function
map(
    "n",
    "s;",
    function()
        return require("substitute.range").word({motion2 = "ie"})
    end,
    {silent = false, noremap = true}
)

I have also tried to run these commands with command mode, and I am still unable to type in the replacement text. (i.e., :lua require("substitute.range").word({motion2 = "ie"})).

I am able to successfully provide the replacement text after I manually enter the second motion if I use the following mappings:

map(
    "n",
    "s;",
    "<Cmd>lua require('substitute.range').operator({motion1 = 'iw'})<CR>",
    {silent = false, noremap = true}
)

map(
    "n",
    "s;",
    "<Cmd>lua require('substitute.range').word()<CR>",
    {silent = false, noremap = true}
)

Configuration

require('substitute').setup({
    yank_substituted_text = false,
    range = {
        prefix = "s",
        suffix = "",
        prompt_current_text = false,
        group_substituted_text = true,
        complete_word = true,
        confirm = false,
        register = nil
    }
})

Custom action is not dot repeatable

I'm running with neovim:

➜  manifest git:(main) ✗ nvim --version
NVIM v0.10.0-dev-679+gb60a2ab4c
Build type: RelWithDebInfo
LuaJIT 2.1.0-beta3

on ubuntu 22

May be something wrong in my config,
I try to map the action with the following modifier (that a great feature)

require('substitute').setup{}

vim.keymap.set("n", "o", function ()
 require('substitute').operator({modifiers = {'reindent'}})
end, { noremap = true })

The action work with the mapping but it is not dot repeatable.

When I simply make the mapping

vim.keymap.set("n", "o", require('substitute').operator, { noremap = true })

the action is dot repeatable

[Typo] `yank_substitued_text`

The current Configuration section of the README mentions a yank_substitued_text, instead of yank_substituted_text. That may be a typo:

{
 on_substitute = nil,
 -- vvv   Here   vvv
 yank_substitued_text = false,
 range = {
   prefix = "s",
   prompt_current_text = false,
   confirm = false,
   complete_word = false,
   motion1 = false,
   motion2 = false,
 },
 exchange = {
   motion = false,
 },
}

Can we have a shortcut for the substitute-over-range that makes it apply over the whole file?

Currently it would be something like gg<leader>s<motion1>G or if you had a textobject for 'whole file' it would be <leader>s<motion1>ie. which isn't too bad but could be better.

Perhaps, for maximum flexibility add a configuration option that would allow to specify <motion2> (the range over which to substitute) from the call in the keymapping. Then we could put ie, ip, or whatever else might be most common for our workflow.

How to keep default settings and define keys table on lazy.nvim

When we set the keys table on lazy.nvim the plugin will be lazy loaded, what is a good thing. I would like to know how to keep most of default settings but being able to add the keys table to lazy load the plugin:

return { "gbprod/substitute.nvim", opts = { -- your configuration comes here -- or leave it empty to use the default settings -- refer to the configuration section below }, keys = { MY KEYS GO HERE to lazy load the plugin } }

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.