Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add surround keybinds #320

Merged
merged 8 commits into from
Jun 22, 2021
Merged

Add surround keybinds #320

merged 8 commits into from
Jun 22, 2021

Conversation

sudormrfbin
Copy link
Member

@sudormrfbin sudormrfbin commented Jun 20, 2021

Adds vim-surround functionality, with keybinds inspired from vim-sandwich:

  • ma<char> ms<char> - Add char around selection
  • md<char> - Delete char pair
  • mr<from><to> - Replace from with to

helix-surround-2

Goto matching bracket (previously m) has been moved to mm.

TODO:

  • Add documentation
    • Usage section
    • Keymaps
    • mm change
  • Add tests
  • [bug] Correctly identify pairs when cursor is on a pair character

Future work:

  • vim-sandwich like visual feedback
  • b as a generic surround char finder (like b in vim-sandwich)
  • Show error when replace or delete cannot be done
  • Dot repeatable
  • Treesitter based implementation ?

Bugs

  • (Fixed in Skip enclosed pairs in surround #357) There are some subtle bugs right now with finding the correct surround pairs when they are spread across multiline lines, which can be solved by either using indent heuristics (works for most files) or directly querying treesitter, which is easier and accurate for languages with defined grammars (see match_bracket implementation).

@pickfire
Copy link
Contributor

pickfire commented Jun 20, 2021

Just wondering, how do you get the text of what you typed showing there? Looks cool.

Copy link
Contributor

@pickfire pickfire left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks interesting but the keys looks weird. At least the key proposed here seemed inconsistent or does not work well with the current select option. What if we do

  • select around tag (include the key searched)
  • select inner tag (exclude the key searched)
  • select only tag (only the 2 matched keys found)

Which only does select, and to change stuff we can do d or c or i after select. At least with these it is more consistent and can work with existing cursors or selection.

But one issue with this model that I mention is it won't work well on html end tags since they require an extra /.

@sudormrfbin
Copy link
Member Author

Just wondering, how do you get the text of what you typed showing there? Looks cool.

It's screenkey :D

Which only does select, and to change stuff we can do d or c or i after select. At least with these it is more consistent and can work with existing cursors or selection.

(I'm assuming when you say tags in the bullet points you mean surround characters like (, [, etc)

That's how I initially tried to implement it - placing selections on the surround chars seems the ideal way to do it, but I quickly ran into problems. Since the cursors placed on them are normal selections, deletion will work well, but adding and replacing will become hard.

If we have a buffer like this and the | are the cursor positions...

some |text|

... if the user presses i( we can reliably change text to (text).

But if we have something like this...

|
|

... and the user tries to type (text) on each line, this is what they'll get on typing the first (:

(|
)|

The problem is we can't magically tell if the intention is to simply add a single ( or surround with (). We'd need to implement some kind of mode for this, and then add hooks for insert, replace and change and add a way to select the surrounds.

I agree that selections you can make with respect to surround chars which you can then manipulate with normal operations like i, c, d, r, etc, are more intuitive, but right now they require too much work (unless there's a way to achieve it that I'm missing that doesn't involve too many hacks).

@sudormrfbin
Copy link
Member Author

sudormrfbin commented Jun 20, 2021

  • select around tag (include the key searched)
  • select inner tag (exclude the key searched)

Text objects for these two would be nice to have though.

But one issue with this model that I mention is it won't work well on html end tags since they require an extra /.

HTML tag support is currently not implemented but definitely worth looking into in the future (the current implementation assumes all surround chars have a length of 1)

Copy link
Member

@archseer archseer left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm trying to think through the mappings but I'd probably like to use ma and mi for text object selection. It feels like they'll be more frequently used too.

Maybe we could consider m+/m-? It's a bit harder to type though.. Or ms/mr for "match surround"/"match remove"

After thinking through usability and looking at https://github.com/h-youhei/kakoune-surround I do think you're right in surrounding the selection but not requiring a selection on remove, it seems easier to use because you don't need to make a precise selection.

helix-term/src/commands.rs Outdated Show resolved Hide resolved
@pickfire
Copy link
Contributor

pickfire commented Jun 21, 2021

Ah, I didn't know kakoune have something similar.

But one thing I am worried is what if someone wants to add/replace with multiple characters rather than one character? Do we not support surround cases like that? I see it being a good usecase like.

  <p>one</p>
  <p>two</p>
  <p>three</p>

For example, adding <strong> and </strong> between wrapping around one, two and three and within <p>. I find myself needing to do this quite frequently or something similar at least in html. Maybe we have an uppercase version to add multiple characters? We may also best support multiple selections to be able to modify the three directly.

Copy link
Contributor

@pickfire pickfire left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good to me. I tested and it works with usual case, and I try breaking it with utf-8 characters but it still work.

The only case where it didn't work is.

<p *ngIf="hello < 1">w(#)orld</p>
<p *ngIf="hello < 1">w(#)orld</p>
<p *ngIf="hello < 1">w(#)orld</p>

With cursor positioned at (#), when I do mr<a, it became

<p *ngIf="hello a 1">world</pa
<p *ngIf="hello a 1">world</pa
<p *ngIf="hello a 1">world</pa

Yeah, we don't support this now, but I think this may do weird cases where people have quotes within these special characters and it will break some stuff on people's code if we are not careful, especially with parts that have regex.

@pickfire
Copy link
Contributor

Just wondering, can we have mc<char> to change a single character with multiple characters like what we have in normal mode?

Copy link
Member

@archseer archseer left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Making sure this doesn't get merged accidentally, at the very least the gm conflict needs resolving

@sudormrfbin
Copy link
Member Author

Maybe we could consider m+/m-? It's a bit harder to type though.. Or ms/mr for "match surround"/"match remove"

@archseer the + and - variants are definitely hard to type - ms seems good for adding surrounds.

But one thing I am worried is what if someone wants to add/replace with multiple characters rather than one character? Do we not support surround cases like that? I see it being a good usecase like.

@pickfire It's definitely a good and valid usecase (and a godsend when you're editing html). I'll add it in another PR since I don't want to stall this one :P

We may also best support multiple selections to be able to modify the three directly.

That's already implemented in this PR, it's showcased towards the end of the gif :)

With cursor positioned at (#), when I do mr<a, it became

<p *ngIf="hello a 1">world</pa
<p *ngIf="hello a 1">world</pa
<p *ngIf="hello a 1">world</pa

Yeah that's definitely a bug. I'm thinking of a treesitter based implementation and then using the current implementation as a fallback. That would correctly identify the valid surround pairs.

Just wondering, can we have mc<char> to change a single character with multiple characters like what we have in normal mode?

Aha that seems the most ergonomic way to do it; I'll add it with the html tag editing support PR :)

@CBenoit
Copy link
Member

CBenoit commented Jun 21, 2021

(Passing by just to say it's really cool 👏 )

helix-term/src/keymap.rs Outdated Show resolved Hide resolved
helix-term/src/keymap.rs Outdated Show resolved Hide resolved
@sudormrfbin
Copy link
Member Author

Added documentation, changed ma to ms and rebased 🎉

Copy link
Member

@archseer archseer left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎉

@archseer archseer merged commit e0fd08d into helix-editor:master Jun 22, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants