mini.nvim icon indicating copy to clipboard operation
mini.nvim copied to clipboard

Beta-testing 'mini.ai'

Open echasnovski opened this issue 1 year ago • 21 comments

Please leave your feedback about new 'mini.ai' module here. Feel free to either add new comment or positively upvote existing one.

Some things I am interested to find out (obviously, besides bugs):

  • Are configuration and function names intuitive enough?
  • Are default values of settings convenient for you?
  • Is documentation and examples clear enough?
  • What is your opinion about default textobjects (for punctuation, digits, and whitespace)? At the moment it mimics 'targets.vim' idea about separator textobject - the a variant includes only one of the edges (the maximum right ones, to be precise). Although useful in many cases (like _ in snake case variables allows da_ to delete word while preserving snake case), it might be not useful for some characters in most situations (like | seems to rarely serve as separator and more like surrounding). I see at least three ways to solve this:
    • Leave it as is. It means that users are encouraged to manually define custom textobjects to include both edges in a textobject. It is what currently is done with at least three different ways to achieve this. This is the way if there is little consensus about what character should be treated as surrounding by default.
    • Add new builtin surrounding to include both edges in a textobject. This is the way, if there is a strong consensus about certain character (like |) being more of a surrounding instead of separator and of which type (balanced or non-balanced).
    • Add new default builtin surrounding to act by default. Maybe some people like include both edges by default (I know I was at first, but then changed my mind because of how useful including only right edge is). Completely not opposed to this, but any options should be considered carefully. This is the way of adding new option.

Thanks!

echasnovski avatar Jul 28 '22 11:07 echasnovski

Thanks for your plugin! I have dreamed about lua textobject plugin for so long. I have come from reddit and some people were requesting 'next' and 'last' operator types. You were saying that they're rarely used but, as for me and, I suppose, for community too, it's a must have feature. I'm a long time user so I have mastered it and it's really cool to edit something far away with a few keystrokes without even jumping to location. Editing feels like magic! Hope you will overthink your decision about not adding these types of operators!

DeadlySquad13 avatar Jul 29 '22 06:07 DeadlySquad13

Thanks for your plugin! I have dreamed about lua textobject plugin for so long. I have come from reddit and some people were requesting 'next' and 'last' operator types. You were saying that they're rarely used but, as for me and, I suppose, for community too, it's a must have feature. I'm a long time user so I have mastered it and it's really cool to edit something far away with a few keystrokes without even jumping to location. Editing feels like magic! Hope you will overthink your decision about not adding this issue!

Thanks!

As I said on Reddit, I've never even once used that feature and didn't feel like missing much. I did got some use cases from comments. It feels that most of the time it is about enabling dot-repeated editing in one direction. This is supported with default v:count without needing to type extra n.

So with 'targets.vim' foo("bar", 2, []) after typing ^cina100<esc>.. will become foo(100, 100, 100). With 'mini.ai' it is possible with ^cia100<Esc> followed by c2ia100<Esc>. Is it something that you'll find acceptable?

I'll be really grateful if you could provide another examples of how you use il/al/in/an. It can help me understand the requirements better.

echasnovski avatar Jul 29 '22 06:07 echasnovski

Thanks for your plugin! I have dreamed about lua textobject plugin for so long. I have come from reddit and some people were requesting 'next' and 'last' operator types. You were saying that they're rarely used but, as for me and, I suppose, for community too, it's a must have feature. I'm a long time user so I have mastered it and it's really cool to edit something far away with a few keystrokes without even jumping to location. Editing feels like magic! Hope you will overthink your decision about not adding this issue!

Thanks!

As I said on Reddit, I've never even once used that feature and didn't feel like missing much. I did got some use cases from comments. It feels that most of the time it is about enabling dot-repeated editing in one direction. This is supported with default v:count without needing to type extra n.

So with 'targets.vim' foo("bar", 2, []) after typing ^cina100<esc>.. will become foo(100, 100, 100). With 'mini.ai' it is possible with ^cia100<Esc> followed by c2ia100<Esc>. Is it something that you'll find acceptable?

I'll be really grateful if you could provide another examples of how you use il/al/in/an. It can help me understand the requirements better.

Yes, you're right. It's mostly about convenient dot repeat. But I also use it to quickly edit, for example, object contents. Suppose you have a function:

vim.tbl_extend('error',
{
  a1 = b,
  a2 = c, 

  s1 = 3
}, {
  s2 = 4
})

And I usually edit it with a help of textobject: firstly I use ci{ to change contents of a first list while inside of it, then issue cin{ to edit second list without moving to it. Of course it works backwards (from second to first with cil{). Unfortunately, what you have shown is barely acceptable: well, it does work, but you:

  1. Waste a lot of keystrokes (after all this plugin is all about saving them).
  2. Have to count a number beforehand which distracts fluid experience.
  3. Works only if the contents of a both changes are the same, otherwise, you have to move. In my example objects can be completely different.

Of course, your work is splendid, you're free develop your plugin in whatever direction you think is better but for me advanced configuration features are not as important as these basic operators.

DeadlySquad13 avatar Jul 29 '22 07:07 DeadlySquad13

Thanks so much for feedback and example. That really helps!

I think I might have found a way to make work in a coherent way by introducing new search methods prev and next (maybe even add nearest for good measure): they won't give preference to covering region, only selecting closest previous or next region. With this, it will be easy to add those.

echasnovski avatar Jul 29 '22 07:07 echasnovski

Thanks so much for feedback and example. That really helps!

I think I might have found a way to make work in a coherent way by introducing new search methods prev and next (maybe even add nearest for good measure): they won't give preference to covering region, only selecting closest previous or next region. With this, it will be easy to add those.

I wish you good luck! You've been asking about criteria - I have also remembered that you can add count before operator, so in the previous example you would be able to jump from first to third object with a c2in{.

DeadlySquad13 avatar Jul 29 '22 07:07 DeadlySquad13

I already uses mini.ai now. Compared to targets.vim, it works pretty better in some text objects like aa, ia, etc and even a}, i}, sometimes targets doesn't recognize text objects across multiple lines well. I never think AI is useful. So did for nl once. And not until read the reddit thread did I realize that how can one utilize the potential of nl. But I do agree that nl may be useful now after reading those threads.

milanglacier avatar Jul 30 '22 04:07 milanglacier

I already uses mini.ai now. Compared to targets.vim, it works pretty better in some text objects like aa, ia, etc and even a}, i}, sometimes targets doesn't recognize text objects across long line well. I never think AI is useful. So did for nl once. And not until read the reddit thread did I realize that how can one utilize the potential of nl. But I do agree that nl may be useful now after reading those threads.

Thanks for the feedback, this is really helpful. And totally aligns with my experience about both AI and nl (and the usefulness of the latter). I am already working on nl.

(Offtopic: that is some seriously evolved Neovim config you've got there! Although, I am not sure about using cover_or_nearest: I added it for completeness sake, but don't really like its usability. After you've used it, could you please share your thoughts about it?)

echasnovski avatar Jul 30 '22 06:07 echasnovski

I

I already uses mini.ai now. Compared to targets.vim, it works pretty better in some text objects like aa, ia, etc and even a}, i}, sometimes targets doesn't recognize text objects across long line well. I never think AI is useful. So did for nl once. And not until read the reddit thread did I realize that how can one utilize the potential of nl. But I do agree that nl may be useful now after reading those threads.

Thanks for the feedback, this is really helpful. And totally aligns with my experience about both AI and nl (and the usefulness of the latter). I am already working on nl.

(Offtopic: that is some seriously evolved Neovim config you've got there! Although, I am not sure about using cover_or_nearest: I added it for completeness sake, but don't really like its usability. After you've used it, could you please share your thoughts about it?)

I do haha, just find this option is interesting and simply want to give it a try. Will try to find some interesting scenario.

milanglacier avatar Jul 30 '22 07:07 milanglacier

@DeadlySquad13, @milanglacier, I've added support for an/in/al/il mappings. They are basically a/i textobjects but with temporarily set search method to new next/prev.

Even during this limited time of testing I indeed found it useful to use cina to edit next argument (instead of not-so-intuitive c2ia which also doesn't quite work outside function call).

echasnovski avatar Jul 30 '22 12:07 echasnovski

@echasnovski the prev operator works great - it can be dot repeated no problem. But if we run: dina : https://user-images.githubusercontent.com/13521338/181919302-9921ff71-0242-4018-88dd-7ae1f201b2f4.mp4 mini ai skips next arg, and jumps 2x forward. Cool addition. btw. What would be fastest way to add additional mapping : A - which would work as aa (around arg, without removing aa) ?

JoseConseco avatar Jul 30 '22 14:07 JoseConseco

@echasnovski the prev operator works great - it can be dot repeated no problem. But if we run: dina : https://user-images.githubusercontent.com/13521338/181919302-9921ff71-0242-4018-88dd-7ae1f201b2f4.mp4 mini ai skips next arg, and jumps 2x forward. Cool addition.

Well, this happens because the second time cursor is already on the (previously) second argument and dina skips it. Imagine you didn't make dina beforehand and just put cursor there, what would your expectation be after hitting dina? I think dia in this case has the same effect (with default search method, of course).

echasnovski avatar Jul 30 '22 14:07 echasnovski

I would expect to be able to . repeat dina - without skipping args: https://user-images.githubusercontent.com/13521338/181919547-ef71ac20-22f6-4676-a671-3b8baccd738f.mp4 I think this is how it worked in targets.vim ? Ah, ok I can see now - if I nudge cursor to left, after first dina, then dot repeat works as expected

JoseConseco avatar Jul 30 '22 14:07 JoseConseco

btw. What would be fastest way to add additional mapping : A - which would work as aa (around arg, without removing aa) ?

Using mapping without noremap seems to be the way to go here:

vim.api.nvim_set_keymap('x', 'A', 'aa', { noremap = false })
vim.api.nvim_set_keymap('o', 'A', 'aa', { noremap = false })

However, you loose A in Virtual-Block mode.

echasnovski avatar Jul 30 '22 14:07 echasnovski

Hey, thanks for your work ! I would like to configure mini.ai to behave like target with a custom config :

  autocmd User targets#mappings#user call targets#mappings#extend({
	\ 'b': {'argument': [{'o': '(', 'c': ')', 's': ','}]},
	\ 'a': {'argument': [{'o': '\[', 'c': '\]', 's': ','}]},
	\ 'o': {'argument': [{'o': '{', 'c': '}', 's': ','}]},
	\ 'B': {'pair': [{'o':'(', 'c':')'}]},
	\ 'A': {'pair': [{'o':'[', 'c':']'}]},
	\ 'O': {'pair': [{'o':'{', 'c':'}'}]},
	\ 'Q': {'quote': [{'d': '`'}]}
\ })

Is it possible to achieve this with mini.ai ? Thanks

Runeword avatar Jul 30 '22 23:07 Runeword

targets.vim allows me to do ci/, which is incredibly useful for adjusting only/one/section/in/a/path. How can do this with ai?

sdemura avatar Jul 31 '22 01:07 sdemura

Hey, thanks for your work ! I would like to configure mini.ai to behave like target with a custom config :

  autocmd User targets#mappings#user call targets#mappings#extend({
	\ 'b': {'argument': [{'o': '(', 'c': ')', 's': ','}]},
	\ 'a': {'argument': [{'o': '\[', 'c': '\]', 's': ','}]},
	\ 'o': {'argument': [{'o': '{', 'c': '}', 's': ','}]},
	\ 'B': {'pair': [{'o':'(', 'c':')'}]},
	\ 'A': {'pair': [{'o':'[', 'c':']'}]},
	\ 'O': {'pair': [{'o':'{', 'c':'}'}]},
	\ 'Q': {'quote': [{'d': '`'}]}
\ })

Is it possible to achieve this with mini.ai ?

My pleasure!

Customization is possible with config.custom_textobjects. Here is everything apart from argument:

require('mini.ai').setup({
  custom_textobjects = {
    B = { '%b()', '^.().*().$' },
    A = { '%b[]', '^.().*().$' },
    O = { '%b{}', '^.().*().$' },
    Q = { '%b``', '^.().*().$' },
  },
})

Argument textobject a will work with all of those brackets, but I assume you want it to be more targeted, so to speak :) Not possible at the moment, unfortunately. But your are not the first one to request customization of argument textobject, so I am thinking about the best way to do that.

echasnovski avatar Jul 31 '22 06:07 echasnovski

targets.vim allows me to do ci/, which is incredibly useful for adjusting only/one/section/in/a/path. How can do this with ai?

Sure: ci/ will remove between / and start Insert mode; di/ will remove block with only right / which results in proper path. This works for any punctuation (ci,, di_, etc.), digit, or whitespace (di<Space>, di\t). These type of operations were one of the main goals before writing 'mini.ai'.

echasnovski avatar Jul 31 '22 06:07 echasnovski

I just pushed an implementation of MiniAi.gen_spec. It is a table with specification generators for common textobjects: pair, argument, function call.

@Runeword, now your 'targets.vim' config can be fully achieved with:

local gen_spec = require('mini.ai').gen_spec
require('mini.ai').setup({
  custom_textobjects = {
    b = gen_spec.argument({ brackets = { '%b()' } }),
    a = gen_spec.argument({ brackets = { '%b[]' } }),
    o = gen_spec.argument({ brackets = { '%b{}' } }),
    B = gen_spec.pair('(', ')', { type = 'balanced' }),
    A = gen_spec.pair('[', ']', { type = 'balanced' }),
    O = gen_spec.pair('{', '}', { type = 'balanced' }),
    Q = gen_spec.pair('`', '`', { type = 'balanced' }),
  },
})

echasnovski avatar Aug 01 '22 13:08 echasnovski

local gen_spec = require('mini.ai').gen_spec
require('mini.ai').setup({
  custom_textobjects = {
    b = gen_spec.argument({ brackets = { '%b()' } }),
    a = gen_spec.argument({ brackets = { '%b[]' } }),
    o = gen_spec.argument({ brackets = { '%b{}' } }),
    B = gen_spec.pair('(', ')', { type = 'balanced' }),
    A = gen_spec.pair('[', ']', { type = 'balanced' }),
    O = gen_spec.pair('{', '}', { type = 'balanced' }),
    Q = gen_spec.pair('`', '`', { type = 'balanced' }),
  },
})

Just updated my config, I have switched to mini.ai ! Thanks !

Runeword avatar Aug 01 '22 17:08 Runeword

I just pushed an implementation of MiniAi.gen_spec. It is a table with specification generators for common textobjects: pair, argument, function call.

@Runeword, now your 'targets.vim' config can be fully achieved with:

local gen_spec = require('mini.ai').gen_spec
require('mini.ai').setup({
  custom_textobjects = {
    b = gen_spec.argument({ brackets = { '%b()' } }),
    a = gen_spec.argument({ brackets = { '%b[]' } }),
    o = gen_spec.argument({ brackets = { '%b{}' } }),
    B = gen_spec.pair('(', ')', { type = 'balanced' }),
    A = gen_spec.pair('[', ']', { type = 'balanced' }),
    O = gen_spec.pair('{', '}', { type = 'balanced' }),
    Q = gen_spec.pair('`', '`', { type = 'balanced' }),
  },
})

To be honest, I don't quite understand what does brackets = {'%b()'} mean in the first example. But I don't bother by the custom textobjs, I used mini.ai with default settings and just find it is great, fewer false positives than targets.vim in many scenerio.

milanglacier avatar Aug 01 '22 20:08 milanglacier

To be honest, I don't quite understand what does brackets = {'%b()'} mean in the first example.

It makes argument textobject be recognized only inside paired () (like in (aa, bb) but not [aa, bb]).

echasnovski avatar Aug 02 '22 06:08 echasnovski

hello!

im testing case from doc:

 | b |  Alias for    | [( *a {bb} )]    |        |        |        |        |
 |   |  ), ], or }   |                  |        |        |        |        |

and using following example:

local str = "this (will {have} a [lot of]) <'surrounding'> `pairs`"

if i put cursor somewhere on 'have' word and do vab i would expect it to select {have}, but instead it selecting (will {have} a [lot of]) can you please explain this behaviour?

alex-popov-tech avatar Aug 07 '22 22:08 alex-popov-tech

Hi!

if i put cursor somewhere on 'have' word and do vab i would expect it to select {have}, but instead it selecting (will {have} a [lot of])

Your expectation is absolutely correct. When I execute same steps, I get {have} selected, so can not reproduce.

Possible reasons why you have (...) selected:

  • For some reason, require('mini.ai').setup() was not called. It should be called somewhere within your 'init.lua' (or other files called inside of it) in order to make appropriate mappings. In this case, ab will refer to Neovim's builtin textobject which coincidentally describes text within () block.
  • There is another mapping ab for Visual mode resulting in observed selection. If typed quickly (within 'timeoutlen' option), it will take precedence over a mapping which 'mini.ai' is using.

To eliminate both assumptions, type :xmap a. It should have line x a * v:lua.MiniAi.expr_textobject('x', 'a') and not have any line for ab mapping.

echasnovski avatar Aug 08 '22 07:08 echasnovski

I would absolutely love to see support for goto_next/prev-functionality, which is something I'm missing quite a bit after switching from treesitter-textobjects.

jonatan-branting avatar Aug 08 '22 14:08 jonatan-branting

I would absolutely love to see support for goto_next/prev-functionality, which is something I'm missing quite a bit after switching from treesitter-textobjects.

There is already g[ and g] which go to left and right end of "current" textobject respectively. So g[{ will go to left side of balanced {}, while g]{ - to the right. It only respects global search method though, so g[{ with default will jump to next {} if outside of one.

If you need a closer replication of next/prev textobjects, then you can create custom mappings with help of MiniAi.move_cursor() and passing appropriate textobject id and {search_method = 'next'}. So something like this:

for _, mode in ipairs({ 'n', 'x', 'o' }) do
  vim.api.nvim_set_keymap(
    mode, '[{', [[<Cmd>lua MiniAi.move_cursor('left', 'a', '{', { search_method = 'prev' })<CR>]], {}
  )
end

Seems to not be an exact replication, but might work.

At this point I am hesitant to add more mappings or options in favor of documenting possible solutions.

echasnovski avatar Aug 08 '22 14:08 echasnovski

Hi!

if i put cursor somewhere on 'have' word and do vab i would expect it to select {have}, but instead it selecting (will {have} a [lot of])

Your expectation is absolutely correct. When I execute same steps, I get {have} selected, so can not reproduce.

Possible reasons why you have (...) selected:

  • For some reason, require('mini.ai').setup() was not called. It should be called somewhere within your 'init.lua' (or other files called inside of it) in order to make appropriate mappings. In this case, ab will refer to Neovim's builtin textobject which coincidentally describes text within () block.
  • There is another mapping ab for Visual mode resulting in observed selection. If typed quickly (within 'timeoutlen' option), it will take precedence over a mapping which 'mini.ai' is using.

To eliminate both assumptions, type :xmap a. It should have line x a * v:lua.MiniAi.expr_textobject('x', 'a') and not have any line for ab mapping.

thank you for rapid and specific answer, there was a conflict in my config. it is working now <3

alex-popov-tech avatar Aug 08 '22 21:08 alex-popov-tech

Glad I could help. Would you mind sharing what it was? If it is not something very custom, it might be helpful for future references.

echasnovski avatar Aug 09 '22 06:08 echasnovski

im sorry but i won't be able to share exact details, because it was a chaos of re-compiling, debugging multiple plugins, and at some point it just started to work after i've reloaded zsh session 🤦🏻

alex-popov-tech avatar Aug 09 '22 20:08 alex-popov-tech

With release of version 0.5.0, 'mini.ai' is now out of beta-testing. Thank you all for your insight!

echasnovski avatar Aug 19 '22 15:08 echasnovski