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 a snippet system #9801

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open

Add a snippet system #9801

wants to merge 6 commits into from

Conversation

pascalkuthe
Copy link
Member

This PR finally implements a real snippet system that can scale Fo fully support all features that you expect from snippets. It has been moved to helix core to reflect that these snippets can be sourced from other sources besides LSPs in the future.

I kept the snippet parsers and some of the logic around generating transactions but basically had to fully rewrite the snippet rendering since there were a ton of cases that weren't covered. Our old code around handling snippet indentation was also needlessly complicated, inefficient and incorrect which I fixed here too.

The actual tabstop handling (and its interaction with multi cursor) turned out to be quite tricky too. I created the notion of an active snippet which tracks the currently active tabstops on a document and maps them trough changes as needed. A snippet stays valid as long as all multi cursors are within the entire body of any snippet. I always hated how some vim snippet plugins made snippets way too sticky (so I would jump somewhere else, do something else hit tab and be back at the other end of a file at a renegade tabstop). This approach prevents that while still allowing you to enter normal mode and perform other edits on the snippet without immidietly discarding tabstops.

One you jump to a tabstop the placeholder will be selected, if you are in insertmode hitting any key not bound to a command will delete the placeholder text before inserting the corresponding character. This reuses (and expanded version) of our on_next_key mechanism.

Commits are best reviewed separately otherwise the diff may be hard to follow.

@pascalkuthe pascalkuthe added C-enhancement Category: Improvements A-language-server Area: Language server client E-hard Call for participation: Experience needed to fix: Hard / a lot A-core Area: Helix core improvements S-waiting-on-review Status: Awaiting review from a maintainer. S-needs-testing Status: Needs to be tested out in order to discover potential bugs. labels Mar 4, 2024
@pascalkuthe
Copy link
Member Author

I am not yet sure I am happy with the keybindings, there is no way to jump back to a previous tabstop, the interaction with smart tab is hardcoded and normal mode bindings are missing. All of this somewhat conflicts with the jump to parent node end stuff. What are your thoughts @dead10ck ?

@dead10ck
Copy link
Member

dead10ck commented Mar 4, 2024

Hard coding the interaction with smart tab doesn't seem wrong on the face of it. The desired behavior is for tab to do different things in different situations, and this seems like a new situation.

That said, that doesn't solve going back. This kind of seems like a feature that deserves its own special mode, like view mode. In this mode, we could have tab / shift-tab in addition to a set of duplicate bindings for non-kitty protocol terminals. And it would also help avoid having to write a smart-untab command.

@pascalkuthe
Copy link
Member Author

I thought we couldn't bins-tab at all? Or is that only because of the conflict with c-i? In insertmode c-i shouldn't be bound either so if we can create shift-tab bindings then that may work too..

One concern I had about hardworking tab is that I sometimes overshoot my target with tabstops and then endup doing a pretty for jump with the TS motion. So I guess I am not too much of a fan of having multiple things bound to the same key. Maybe that's just me but I guess having some configurability for smart tab could be nice.

@archseer
Copy link
Member

archseer commented Mar 4, 2024

When I started on this I figured snippets could be a separate sticky mode similar to how the debugger functions. An overlay on top of the regular keymap. Tabbing through all the tabstops would exit the mode too

@dead10ck
Copy link
Member

dead10ck commented Mar 4, 2024

Yeah, exactly my thought too. If it's a special mode / overlay, then we don't have to worry about conflicts or interacting with smart tab.

Also @pascalkuthe tab does conflict with Ctrl-i, so that couldn't be a default binding in normal mode. We'll have to figure out a different keymap for non-kitty terminals anyway though. Mod-tab only works in kitty terminals.

@dead10ck
Copy link
Member

dead10ck commented Mar 4, 2024

We could save the special mode for a follow-up PR though, and for now just come up with the bindings for non-kitty terminals. It will just probably not be the tab key.

@pascalkuthe
Copy link
Member Author

yeah I think having it be mode based would be nice. It's entirely possible to bind <S-tab> in insert mode it just bound to insert_tab by default which would be changed by the mode.

I think minor modes need some love in general. That would be a larger change tough so I think its best to just push that to follow-up work and only keep the very basic smart tab binding we have right now

Copy link
Contributor

@matoous matoous Mar 11, 2024

Choose a reason for hiding this comment

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

question: (unrelated to the PR) if we will already have these inside helix, would it eventually make sense to offer commands for transforming strings between different casing? Initially it seemed like a functionality better left for a plugin but with this only a plumbing in between would be what's missing.

Copy link
Member Author

Choose a reason for hiding this comment

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

I am not categorically opposed to it. We need the functionality for snippets since its part of the LSP snippet syntax. It's definitely not a priority tough

Choose a reason for hiding this comment

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

Maybe I'm misunderstanding but Helix already has a way to transform the selected text to uppercase. Maybe lowercase too, I'm not sure.

use std::ops::Index;
use std::sync::Arc;

use anyhow::{anyhow, Result};
Copy link
Member

Choose a reason for hiding this comment

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

What do you think about avoiding anyhow in helix-core? To me anyhow seems useful for stuff like helix-term and commands but for library-like stuff like helix-core it makes it easy to not introduce custom Error types. Since we only have one usage here it would be easy to drop and replace with something thin like a struct + thiserror

Copy link
Member Author

Choose a reason for hiding this comment

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

Sounds good, makes it easier to figure out what could go wrong and where

@pascalkuthe pascalkuthe force-pushed the snippet_placeholder branch 3 times, most recently from 3b55412 to de9f66e Compare April 1, 2024 23:21
@pascalkuthe
Copy link
Member Author

pascalkuthe commented Apr 1, 2024

rebased on master, fixed @the-mikedavis comments from above plus a bug he mentioned to me where accepting a (snippet) completion by continuing typing a word char would not correctly replace the placeholder

@noor-tg
Copy link

noor-tg commented Apr 6, 2024

Can I use this feature if I build helix from source ?

@zetashift
Copy link
Contributor

Can I use this feature if I build helix from source ?

If you build from the right branch yes!
This is the branch you want to checkout: https://github.com/helix-editor/helix/tree/snippet_placeholder

And if you're a nix user you can try it out in a single command:
nix profile install github:helix-editor/helix#snippet_placeholder

@noor-tg
Copy link

noor-tg commented Apr 7, 2024

Is there any docs about making a snippet ?
Or is it simple like vs code snippets ? In json format ?

@pascalkuthe
Copy link
Member Author

pascalkuthe commented Apr 7, 2024

it's vscode snippets but this doesn't handle loading user snippets, it only handles snippets send by the lsp

@TornaxO7 TornaxO7 mentioned this pull request Apr 7, 2024
@noor-tg
Copy link

noor-tg commented Apr 7, 2024

it's vscode snippets but this doesn't handle loading user snippets, it only handles snippets send by the lsp

For now I am using snippets lsp. But the stop points are not working. So if I build helix with this branch . The lsp will work with stop points ?

@pascalkuthe
Copy link
Member Author

Yes

@danillos
Copy link
Contributor

danillos commented Apr 7, 2024

@pascalkuthe I was testing and tabstop wasn't working. So I realized that it only works when smart-tab is enabled.

Shouldn't it work even when smart-tag disabled? Since they are different features.

@pascalkuthe
Copy link
Member Author

pascalkuthe commented Apr 7, 2024

More advanced keybindings are left to future work. You can bind the commands to jump to the previous/next tabstop commands manually to different keys

@noor-tg
Copy link

noor-tg commented Apr 8, 2024

Started to test it today. Generally, it is working very well with the mentioned lsp before.

Thank you very much, @pascalkuthe

@gabydd gabydd mentioned this pull request Apr 19, 2024
in the past DocumentDidChange and SelectionDidChange events were implemented in
a simplistic manner to get a simple prototype out. However, if you want to use
these events in more complex scenarios with interdependencies between the two
handlers the system fell short.

The `SelectionDidChange` event was dispatched before the DocumentDidChange (and
not at all if the selection wasn't manually set) so any handlers that wants to
track selection was not able to map their ranges yet.

The reason for this was actually the way that apply_impl was structured. The
function was slightly refactored to address these problems and enable moving
other range mappings to event handlers.
adds a variant of on_next_key callbacks that are only called when no other
mapping matches a key
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-core Area: Helix core improvements A-language-server Area: Language server client C-enhancement Category: Improvements E-hard Call for participation: Experience needed to fix: Hard / a lot S-needs-testing Status: Needs to be tested out in order to discover potential bugs. S-waiting-on-review Status: Awaiting review from a maintainer.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

9 participants