-
-
Notifications
You must be signed in to change notification settings - Fork 2.5k
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
base: master
Are you sure you want to change the base?
Add a snippet system #9801
Conversation
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 ? |
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 |
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. |
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 |
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. |
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. |
yeah I think having it be mode based would be nice. It's entirely possible to bind 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 |
8138b01
to
87fd778
Compare
helix-core/src/case_conversion.rs
Outdated
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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}; |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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
3b55412
to
de9f66e
Compare
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 |
Can I use this feature if I build helix from source ? |
If you build from the right branch yes! And if you're a |
Is there any docs about making a snippet ? |
it's vscode snippets but this doesn't handle loading user snippets, it only handles snippets send by the lsp |
Yes |
@pascalkuthe I was testing and tabstop wasn't working. So I realized that it only works when Shouldn't it work even when smart-tag disabled? Since they are different features. |
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 |
Started to test it today. Generally, it is working very well with the mentioned lsp before. Thank you very much, @pascalkuthe |
de9f66e
to
98eb42b
Compare
260444d
to
d182aa1
Compare
Just wanted to mention that it's "#" selects a flake attribute in nix, not a git branch |
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
d182aa1
to
a6511ff
Compare
This might be out of scope for this PR (and if it is it might be good to open another issue or add a comment on the existing one), but I notice that this PR implements parsing for variables but does not resolve any of the special ones I see in the spec, e.g. TM_SELECTED_TEXT This would be a very nice addition that seems fairly easy to add on top of the amazing work in this PR. Otherwise, I've been using this branch for a few days and the snippet support it provides is an amazing upgrade with no obvious bugs. |
I have been using this feature for couple months and it works perfect. Thank you @pascalkuthe! Is anything blocking that to be in master? |
more messing around in nix; here's a
Now, for the life of me I can't figure out how to try this out myself. Is there a recommended language/lsp config for testing this out? Will docs about this change be incorporated? Willing to help with that if possible... the most i could gather is that c+x gives you autocomplete but it didn't really work for python at least. |
Personally I have been using this branch with this https://github.com/estin/simple-completion-language-server lsp that has a flake. Testing with that, the tabstops and everything else I try work great |
here's an updated flake that loads both the patched version of helix and the snippets LSP using home manager: (i'm new at nix so feedback welcome, esp. around naming conventions)
{
description = "Home Manager configuration of schlich";
inputs = {
# Specify the source of Home Manager and Nixpkgs.
nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";
home-manager = {
url = "github:nix-community/home-manager";
inputs.nixpkgs.follows = "nixpkgs";
};
helix.url = "github:helix-editor/helix?ref=snippet_placeholder";
scls.url = "github:estin/simple-completion-language-server";
};
outputs = { nixpkgs, home-manager, helix, scls, ... }:
let
# change system for your architecture
system = "aarch64-darwin";
pkgs = nixpkgs.legacyPackages.${system};
helix-dev = helix.packages.${system};
scls-dev = scls.defaultPackage.${system};
in {
homeConfigurations."schlich" = home-manager.lib.homeManagerConfiguration {
inherit pkgs;
# Specify your home configuration modules here, for example,
# the path to your home.nix.
modules = [ ./home.nix ];
# Optionally use extraSpecialArgs
# to pass through arguments to home.nix
extraSpecialArgs = { helixpkg = helix-dev; sclspkg = scls-dev; };
};
};
}
{ pkgs, helixpkg, sclspkg, ... }:
{
...
home.packages = [
...
helixpkg.helix
sclspkg
];
...
} relevant documentation for smart-tab are y'all enabling the |
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.