echasnovski / mini.nvim

Library of 40+ independent Lua modules improving overall Neovim (version 0.8 and higher) experience with minimal effort
MIT License
5.14k stars 188 forks source link

converting vim-textobj-user to mini.ai #151

Closed barrett-ruth closed 1 year ago

barrett-ruth commented 1 year ago

Contributing guidelines

Module(s)

mini.ai

Description

Currently trying to convert my setup to the mini ecosystem. I have several vim-textobj-user based plugins, such as this one or this.

These plugins straightforwardly define their text objet regexes in one file, but I just don't know how to convert the call to textobj#user#plugin to mini.ai.

Any help would be appreciated.

echasnovski commented 1 year ago

Hi there!

All custom textobjects are defined inside config.custom_textobjects table. Here are some examples. So no need to call textobj#user#plugin, only define inside require('mini.ai').setup() call.

Here is a help section for full specification capabilities.

Here are some examples of user setups.

I once quickly wrote a version of textobject for whole buffer in this Reddit comment.

Hope this helps.

I've been planning to do a wiki page with various setups of custom textobjects for quite a while now. Maybe in the near future. I'll then take a look at how vim-textobj-xmlattr can be replaced.

barrett-ruth commented 1 year ago

Ok, most of that was what I expected, thanks for the clarification. By the way the documentation actually has an implementation of the "global" text object shorter than the one in the reddit comment here.

So, my last question is let's say I want to transcribe this plugin to mini.ai. It looks like I should essentially copy all of the logic into a custom textobject and return the from and two properties. However the above example has differing logic for the "around" and "inside" verbs on the text object. How can I communicate these things to mini.ai in order to maintain their functionality?

echasnovski commented 1 year ago

Your two paragraphs are interestingly interconnected :)

Yes, there is a whole buffer textobject implementation in documentation, but it is less flexible than the one from Reddit post. The difference being that the former doesn't differ between a and i textobjects, while the latter does. The way it is done is by utilizing its input arguments: all callable custom textobject specification are called with arguments same as MiniAi.find_textobject(). There is a note about it in specs docs but I realized it is not reflected in examples.

So the shorter answer is: use first ai_type argument of callable specification to condition the returned region(s).

barrett-ruth commented 1 year ago

Sorry for the prod @echasnovski ? At this point I have constructed two regexes to simulate one vim-textobj plugin I use: one for "a," and one for "i." Is there an easy way to translate that into mini/ai?

echasnovski commented 1 year ago

Sorry for the prod @echasnovski ? At this point I have constructed two regexes to simulate one vim-textobj plugin I use: one for "a," and one for "i." Is there an easy way to translate that into mini/ai?

Depends on regexes, unfortunately.

What are those textobjects?

barrett-ruth commented 1 year ago

The regexex here, which correlate to:

a: \s\+\(\([a-zA-Z0-9\-_:@.]\+\)\(=\(\(".\{-}"\)\|\(\w\+\)\)\)\=\) i: \(\([a-zA-Z0-9\-_:@.]\+\)\(=\(\(".\{-}"\)\|\(\w\+\)\)\)\=\)

echasnovski commented 1 year ago

The regexex here, which correlate to:

a: \s\+\(\([a-zA-Z0-9\-_:@.]\+\)\(=\(\(".\{-}"\)\|\(\w\+\)\)\)\=\) i: \(\([a-zA-Z0-9\-_:@.]\+\)\(=\(\(".\{-}"\)\|\(\w\+\)\)\)\=\)

That is a scary looking regex...

Do you actually want to implement xml attributes, i.e. match anything similar inside to these entries: <div class="box" id=footer data-updatable data-content="everything"></div>?

This can be implemented with composed Lua pattern (as described in textobject specification. Its complexity depends on how precise you want it to be.

I'd settle for simpler patterns with something like this:

require('mini.ai').setup({
  custom_textobjects = {
    x = { '%s+[^%s<>=]+=[^%s<>]+', '^%s+().*()$' },
  }
})

Little breakdown of what it does:

barrett-ruth commented 1 year ago

Thank you so much, my poor understanding of lua patterns is what is holding me back (although not an excuse!).

I'll look back at the documentation and try to make out a solution for this and variable-segment in the future. My two last pesky vim plugins!

echasnovski commented 1 year ago

Thank you so much, my poor understanding of lua patterns is what is holding me back (although not an excuse!).

I'll look back at the documentation and try to make out a solution for this and variable-segment in the future. My two last pesky vim plugins!

Lua pattern are initially design to be not as complicated as regular regexes. If you at least somewhat familiar with regexes, it shouldn't be much of a problem. I mostly learned them from here while experimenting using built-in string module. One important detail, though, is that LuaJit in Neovim also contains a so called "frontier pattern" - very useful to ensure that match is as wide as necessary.

Here is a MVP for 'variable-segment'. It doesn't really work in edge cases, so you can tweak it further. And speaking of frontier pattern, it also has is a slight update for xml attributes which includes maximum whitespace to the left of it:

require('mini.ai').setup({
  custom_textobjects = {
    x = { '%f[%s]%s+[^%s<>=]+=[^%s<>]+', '^%s+().*()$' },
    v = { { '%f[%w]()%w+()_', '%f[%u]()()%w*[%l%d]()()%u' } },
  }
})
barrett-ruth commented 1 year ago

Thanks so much!

astier commented 4 months ago

Hi. Just wanted to ask if there is some kind of documentation/collection of mini.ai textobjects as a replacement for vim-textobj-user and nvim-various-textobjs? I would like to migrate to mini.ai from targets.vim and this two textobj-libraries but having first to reimplement every textobject seems kind of daunting.

echasnovski commented 4 months ago

Hi. Just wanted to ask if there is some kind of documentation/collection of mini.ai textobjects as a replacement for vim-textobj-user and nvim-various-textobjs? I would like to migrate to mini.ai from targets.vim and this two textobj-libraries but having first to reimplement every textobject seems kind of daunting.

Hello :wave:

Since this issue was created, there is a 'mini.extra' module which provides (among others) some 'mini.ai' specifications.

Also 'mini.ai' has most of 'targets.vim' textobjects working out of the box.

Other then that, you'd have to write own specifications, I am afraid. What textobjects exactly do you miss not covered with what is described above?

astier commented 4 months ago

Thanks for letting me know about the textobjects in mini.extra. I didn't find any reference in the mini.ai-readme or docs. Maybe it should be mentioned somewhere for new users. indent-textobject is one of the most important for me. Some other objects are:

Of course there can be many more as can be seen in nvim-various-textobjs and vim-textobj-user. Maybe except of word-column the objects aren't that difficult to implement by oneself but I think it would be nice to have the most common textobjects-implementatons either be mentioned in a wiki or imo even better be shipped with a separate module like mini.textobjects. Together mini.ai and mini.textobjects could replace both targets.vim and other textobject-plugins like nvim-various-textobjs and vim-textobj-user and help new users to make the shift to exclusively using mini.ai.

What I find so appealing about mini.ai is that it unifies targets.vim and other textobject-plugins in a coherent way and makes it possible to use in/an, il/al, g] and g[ on all the textobjects defined by mini.ai which is not possible with 3rd-party-textobject-plugins. Moving from targets.vim to mini.ai is easy. From textobject-plugins not that much depending on how many textobjects the user is depending on.

PS: Thanks for the great plugins you are delivering. Although they are supposed to be 'mini' many of them seem to be equal or better than the old established 'big' plugins.

echasnovski commented 4 months ago

I didn't find any reference in the mini.ai-readme or docs. Maybe it should be mentioned somewhere for new users.

Indeed, thanks for noticing. There is a mention now.

  • word-column

Yeah, might be tricky to implement in full. As it seems to have an extra logic for what to consider a "column".

  • variable-segment

Should be possible, but frankly I don't really like the idea. Being more explicit during editing tends to be more robust (at least for me).

  • key-value
  • url

Indeed, both seem to be possible.


... or imo even better be shipped with a separate module like mini.textobjects.

I don't think this will happen. If some textobject is a combination of being non-trivial, popular, and having concise but not trivial implementation, it might be a candidate for MiniExtra.gen_ai_spec entry. This is a case by case decision, which I went through based on my editing experience to land on its current entries.

I am open to suggestions. Yet, none of those four seem to fit in those criteria. I don't see concise implementation for first three and "url" seems to be rather trivial in terms of textobjects (as in "it usually can be replicated with other textobject like iW, i), etc.).

astier commented 4 months ago

Yeah, might be tricky to implement in full. As it seems to have an extra logic for what to consider a "column".

Do you mean it wouldn't be possible to implement word-column even if one would try because of mini.ai limitations?

I don't see concise implementation for first three

Regarding variable-segment/subword why do you think it couldn't be concise if it would simply deal with camelCase. For underscores and other separators in most cases ci_ or da_ should be enough except for the outer subwords. Assuming we want to delete a subword with * being the cursor here are the cases:

I think just covering the camelCase scenario should be concise, popular and not to trivial.

For the non-camelcase scenarios an outer-operator combined with ci_ and da_ would make more sense. Like do_ would be:

What do you think about a last-paste-object? Mapped to g; by nvim-various-textobjs. My main-usecase is >g; to indent the last pasted code.

I found some weird behavior with mini.extra indent-object. In the following case vai and vii both only select from line 2 to 5. Shouldn't vai also include 1 and 6?

1 line
2    *line
3        line
4        line
5    line
6 line

In the next case vai and vii both also select lines 2 to 5. Shouldn't vii select 3 and 4?

1 line
2    line
3       *line
4        line
5    line
6 line
echasnovski commented 4 months ago

Yeah, might be tricky to implement in full. As it seems to have an extra logic for what to consider a "column".

Do you mean it wouldn't be possible to implement word-column even if one would try because of mini.ai limitations?

I mean that it would most certainly require a callable textobject specification with some ad hoc logic about what should be considered a "column". Definitely out of scope for 'mini.extra'.

Regarding variable-segment/subword why do you think it couldn't be concise if it would simply deal with camelCase.

There is already an example for camel case words. It looks scary, but it works for all four examples you listed.

For the non-camelcase scenarios an outer-operator combined with ci_ and da_ would make more sense. Like do_ would be:

The 'mini.ai' is about a (around) and i (inside) textobjects. Adding new one is out of scope. That said, tweaking _ textobject to also accept space as edge should be doable via nested patterns.

What do you think about a last-paste-object? Mapped to g; by nvim-various-textobjs. My main-usecase is >g; to indent the last pasted code.

Judging by its source code, it is straight up area between [ and ] marks. Making it work in most cases is fairly straightforward with the following:

local select_last_change = function()
  local mode = vim.fn.mode()
  local is_visual = mode == 'v' or mode == 'V' or mode == '\22'

  vim.api.nvim_win_set_cursor(0, vim.api.nvim_buf_get_mark(0, '['))
  vim.cmd('normal! ' .. (is_visual and 'o' or 'v'))
  vim.api.nvim_win_set_cursor(0, vim.api.nvim_buf_get_mark(0, ']'))
end
vim.keymap.set({ 'x', 'o' }, 'g;', select_last_change)

I found some weird behavior with mini.extra indent-object. In the following case vai and vii both only select from line 2 to 5. Shouldn't vai also include 1 and 6?

At first glance, yes it should be from 1 to 6. I'll take a look later.

echaya commented 1 month ago

I have also recently migrated from vim-textobj-user. Can you provide any example that could tackle the following please? @echasnovski

| The 'mini.ai' is about a (around) and i (inside) textobjects. Adding new one is out of scope. That said, tweaking _ textobject to also accept space as edge should be doable via nested patterns.

I'm struggling to achieve the following

Many thanks in advance!

echasnovski commented 1 month ago

I'm struggling to achieve the following

Here is an example:

-- A composed pattern which will select either inside `_` and/or ` `
local underscore_with_space = {
  -- Wrapping in curly brackets means that "unwrapping" composed pattern will
  -- result in those particular nested patterns.
  {
    -- Cases like ` a*aa_bbb_ccc` and `aaa_b*bb_ccc`
    { '[ _][^ _]+_', '^.()().*().()$' },
    -- Cases like `a*aa_bbb_ccc` at start of line
    { '^[^ _]+_',    '^()().*().()$' },
    -- Cases like `aaa_bbb_c*cc `
    { '_[^ _]+ ',    '^().().*()().$' },
    -- Cases like `aaa_bbb_c*cc` at the end of line
    { '_[^ _]+\n',   '^().().*()().$' },
  },
}
require('mini.ai').setup({
  custom_textobjects = { ['-'] = underscore_with_space },
})

This might look scary, but hopefully comments make it manageable. I'd suggest reading this with attention to examples (this is how I right now remembered how to do this :sweat_smile: ).

echaya commented 1 month ago

I'm struggling to achieve the following

Here is an example:

-- A composed pattern which will select either inside `_` and/or ` `
local underscore_with_space = {
  -- Wrapping in curly brackets means that "unwrapping" composed pattern will
  -- result in those particular nested patterns.
  {
    -- Cases like ` a*aa_bbb_ccc` and `aaa_b*bb_ccc`
    { '[ _][^ _]+_', '^.()().*().()$' },
    -- Cases like `a*aa_bbb_ccc` at start of line
    { '^[^ _]+_',    '^()().*().()$' },
    -- Cases like `aaa_bbb_c*cc `
    { '_[^ _]+ ',    '^().().*()().$' },
    -- Cases like `aaa_bbb_c*cc` at the end of line
    { '_[^ _]+\n',   '^().().*()().$' },
  },
}
require('mini.ai').setup({
  custom_textobjects = { ['-'] = underscore_with_space },
})

This might look scary, but hopefully comments make it manageable. I'd suggest reading this with attention to examples (this is how I right now remembered how to do this 😅 ).

Thanks so much on the above examples. Those are working perfectly 99% of the time!

What I realized is we also need to handle cases like ", ', ( among others on both sides (when the cursor is on one of those signs), any good way to do it in a more "dry" way other than chaining those signs together in the pattern?

. Really appreciate your help as always!

echasnovski commented 1 month ago

What I realized is we also need to handle cases like ", ', ( among others on both sides (when the cursor is on one of those signs), any good way to do it in a more "dry" way other than chaining those signs together in the pattern?

Maybe an automated approach which creates a necessary specification table in a loop. Or use [ %p] instead of (single space) as left and right delimiter.

I'd leave it you to actually iron out from 99% to 100%.

echaya commented 1 month ago

FWIW, below is what I settled with

    v = {
      {
        -- a*aa_bbb
        { "[^%w]()()[%w]+()_()" },
        -- a*aa_bb
        { "^()()[%w]+()_()" },
        -- aaa_bbb*_ccc
        { "_()()[%w]+()_()" },
        -- bbb_cc*cc
        { "()_()[%w]+()()%f[%W]" },
        -- bbb_cc*c at the end of the line
        { "()_()[%w]+()()$" },
      },
    },

I can't get my head's around when setting up () separately :)

echasnovski commented 1 month ago

I can't get my head's around when setting up () separately :)

Here is a description of what it does. It is basically a way to define how a and i textobjects will be extracted from the match.

echaya commented 1 month ago

I can't get my head's around when setting up () separately :)

Here is a description of what it does. It is basically a way to define how a and i textobjects will be extracted from the match.

yes I read it so I came up with { "_()()[%w]+()_()" } etc...but I haven't figured out how to transform it into { '_[^ _]+ ', '^().().*()().$' }, format.

Separately, as to the next step, I'm currently struggling with combining https://github.com/echasnovski/mini.nvim/issues/151#issuecomment-2336970229 with CamelCases. I was using this before starting using mini.ai and it is neat to handle both CamelCase and under_score_case in one TextObj.

Any help / suggestion will be well appreciated!

echasnovski commented 1 month ago

... but I haven't figured out how to transform it into { '_[^ _]+ ', '^().().*()().$' }, format.

If it works like that, then I suggest to use it like that. No need to follow a specific format. The nested pattern format was mostly a result of my previous attempts to combine several patterns at once, until it was apparent that handling them separately looked like the best choice.

Separately, as to the next step, I'm currently struggling with combining #151 (comment) with CamelCases. I was using this before starting using mini.ai and it is neat to handle both CamelCase and under_score_case in one TextObj.

Any help / suggestion will be well appreciated!

I'd start by adding new patterns to what you already got. If this doesn't work, textobject specification can be callable/function (either fully or instead of a single pattern) to provide vastly more flexibility (at the cost of more verbosity). In the end, it possible that 'mini.ai' nested pattern approach is not built for this kind of complex/flexible textobject specification.

Personally, it seems to be a better idea to have separate textobjects for snake and camel cases, though.