L3MON4D3 / LuaSnip

Snippet Engine for Neovim written in Lua.
Apache License 2.0
3.29k stars 234 forks source link

Using Select and Cut to Populate $TM_SELECTED_TEXT Variable #1208

Closed Vanillma closed 1 week ago

Vanillma commented 1 month ago

Hi, I recently migrated from the vsnip snippet engine to the luasnip snippet engine.

First of all, thank you for creating this excellent snippet engine.

In vsnip, I had two options for using the $TM_SELECTED_TEXT variable:

One option would only select the text but not delete it. I had configured it to enter insert mode after selecting the text.

" Select & Cut in Visual Mode. "
xnoremap s <Plug>(vsnip-select-text)<esc>i
xnoremap c <Plug>(vsnip-cut-text)

I searched and only found one option in luasnip, which performs the cut operation.

require("luasnip").config.setup({store_selection_keys="c"});

Is there a way to simulate the select operation as well?

In HTML, there are times when I need to create a copy of an element but within a new container.

Currently, in luasnip, I have to cut the element first, then switch to normal mode, then undo, and then enter insert mode again to continue. This is a workaround to achieve this functionality.

The luasnip documentation states that it is inspired by vsnip.

The idea of selecting and cutting in vsnip was ingenious, and I think it would be beneficial to include it in luasnip as well.

Thanks

L3MON4D3 commented 1 month ago

Hi :) This sounds pretty reasonable, and is a really small change :D I've pushed a first version of this in #1209, could you check if it works as you expect? Just add copy_selection_keys in your config

Vanillma commented 1 month ago

Hi :) This sounds pretty reasonable, and is a really small change :D I've pushed a first version of this in #1209, could you check if it works as you expect? Just add copy_selection_keys in your config

Wow, that's fast! Thank you so much, you're amazing!

I tried it out, and it's working great now. But I ran some tests and I'd like to share them with you.

I apologize for the delayed response, as I'm still learning Lua. I tried to explore the luasnip code a bit today with the help of Gemini (Google AI) to understand how the selection part works.

Okay, first of all, I noticed that these two values are defined in the default_config file:

copy_selection_keys = nil,
cut_selection_keys = nil,

When the user sets them, they are used as {lhs} in the key mapping in config file:

if session.config.cut_selection_keys then
   vim.cmd(
    string.format(
     [[xnoremap <silent>  %s  %s]], session.config.cut_selection_keys, require("luasnip.util.select").select_keys
    )
  )
end

Then I found {rhs} in the select file and tried to play around with it a bit :))

M.select_keys =
 [[:lua require("luasnip.util.select").pre_cut()<Cr>gv"zs<cmd>lua require('luasnip.util.select').post_cut("z")<Cr>]]

M.copy_keys =
 [[:lua require("luasnip.util.select").pre_cut()<Cr>gv"zy<cmd>lua require('luasnip.util.select').post_cut("z")<Cr>]]

For example, I noticed that when I use copy, it doesn't enter insert mode by default (I know this is because of the "zy command, which only copies the value to the z register).

I initially changed it to this:

M.copy_keys =
 [[:lua require("luasnip.util.select").pre_cut()<Cr>gv"zyi<cmd>lua require('luasnip.util.select').post_cut("z")<Cr>]]

I added an "i" to it to enter insert mode after copying. Then I realized that this method always brings the cursor to the beginning of the line. To solve this, I played around with it again and came up with this :))

M.copy_keys =
 [[:lua require("luasnip.util.select").pre_cut()<Cr>gv"zygv<esc>i<cmd>lua require('luasnip.util.select').post_cut("z")<Cr>]]

With this method, the cursor stays where it is.

I know this behavior is probably only my preference and can't be forced on everyone who uses this popular plugin.

So I thought maybe a variable could be added for the user to customize!

local afterCopy = "<cmd>lua print('Copied')<cr>";
M.copy_keys =
 [[:lua require("luasnip.util.select").pre_cut()<Cr>gv"zy]] .. afterCopy .. [[<cmd>lua require('luasnip.util.select').post_cut("z")<Cr>]]

Now it copies and even prints a message for me :)

Finally, I want to thank you again. It's working great even now.

I understood how it works and I can customize it for myself.

I just wanted to share a few ideas with you that might help improve it!

L3MON4D3 commented 1 month ago

Wow, that's fast! Thank you so much, you're amazing!

TY, power of procrastination :P I'm happy it works for you :)

Then I found {rhs} in the select file and tried to play around with it a bit :))

Nice investigating :ok_hand:

That's some rather deep customization (or, just some I didn't anticipate :D), I'll have to consider how to best make an API for doing this (so your customization is not destroyed when I change something about the way select works :sweat_smile:)

I think it's fine if luasnip exposes the pre_yank and post_yank functions (so, rename them too, to be more accurate), and require that inbetween them, something is put into the register that post_yank gets as an argument. That has the flexibility you want to move the cursor around

Thanks for sharing that new usecase :)

L3MON4D3 commented 1 month ago

Ah if we do add the new api, we wouldn't even need the new setting, users could just set up their own keymapping :)

bew commented 1 month ago

Ah if we do add the new api, we wouldn't even need the new setting, users could just set up their own keymapping :)

Yes please, I much prefer defining keymaps myself 🙏

L3MON4D3 commented 1 week ago

Hi, finally got to wrapping this up :) I'll just paste the new doc-entry here, it has all the info on using the new pre-yank and post-yank callbacks:

Selection

Many snippets use the $TM_SELECTED_TEXT or (for LuaSnip, preferably LS_SELECT_RAW or LS_SELECT_DEDENT) variable, which has to be populated by selecting and then yanking (and usually also cutting) text from the buffer before expanding.

By default, this is disabled (as to not pollute keybindings which may be used for something else), so one has to

bew commented 1 week ago

Awesome! Will use that next time I update ✨

Nitpick on naming of rhs function as cut_keys feels a bit weird, have you considered cut_selection or cut_selection_action ?

L3MON4D3 commented 1 week ago

Oh, that's because it's not a function, but a sequence of keys :D Maybe cut_rhs would've been better, but I think not so much that I'd change it now

bew commented 1 week ago

I see, oh well ¯\_(ツ)_/¯