CARVIEW |
Navigation Menu
-
-
Notifications
You must be signed in to change notification settings - Fork 2.9k
Add Steel as an optional plugin system #8675
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
base: master
Are you sure you want to change the base?
Conversation
Now steel can be built with this Nix devshell
flake: add Security apple framework
In steel lang repo discussion about LSP extension and inlay-hints (for dart lang). we had a problem, which is that the inlay-hints from the extension are removed when the main lsp provides some inlay-hints. It seems lsp remove old inlay-hints and then re-add new hints with specific elements. So, is there any update to handle this problem in the plugins layer. Or does this need changes to the core ? |
@nik-rev what discord is this? I can only see a matrix chat in the readme. |
the discord link was right beside the matrix chat badge. https://discord.gg/WwFRXdN6HU for anyone interested |
Thanks for the amazing work @mattwparas! So far, I've built switcheroo.hx, and I'm now building a plugin to be able to set helix's current directory to one of the directories listed in a file with a fuzzy search picker. Both of these plugins have been quite enjoyable to build so far, even as someone who has never used a scheme dialect before. Here are some thoughts from my first impressions:
Despite some these minor problems, my experience with plugin development has still been great, and I'm the most excited for helix that I have ever been. |
suggestion, could you try a zoxide integration? perhaps you could create a new picker with the directories available and if you selected it, cd to that directory? |
Sharing another plugin: scooter.hx, for interactive find-and-replace. You can search, toggle which results you want to replace, open the results up in a buffer and then resume, and replace the selections. Had a lot of fun building this, and a big thank you to @mattwparas both for building the plugin system and for giving me pointers! My primary thoughts on the plugin system (which I've mentioned on Discord) are that it would be really nice to have some generic components or utilities for plugins to use, such as a text component for entering text with the usual shortcuts e.g. ctrl+w to delete a word. However I could implement this manually so it wasn't a blocker, and hopefully over time some of these components could be built in to the plugin system, and perhaps others will be built as libraries that can be pulled in by plugin authors. |
* feat: add push-range & set-primary-index fns * feat: add remove-range fn --------- Co-authored-by: meepleek <ergocrab@gmail.com>
…h register->value etc. (#41)
Hey, I made a simple website helix-plugins.com to list the plugins made for helix until there's something more official. Don't hesitate to reach me out at contact@helix-plugins.com to add/remove/modify a listed plugin. I already put scooter.hx, switcheroo.hx and ghost-text.hx that I found here. Edit : I made it open source https://github.com/noahfraiture/helix-plugins |
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.
Finally found time to do another pass! This is looking really close, I'd just like to move as much Steel specific code as possible under it's own module namespace so it's easier to keep track of what changes are made specifically for Steel.
I think with some cleanup we can mark this experimental and merge under a default-off feature flag, that way users running nightly can go ahead and experiment :)
Thank you again for all the hard work! I know you've put a lot of effort into this from both Steel and Helix side.
xtask/src/main.rs
Outdated
@@ -87,6 +145,7 @@ pub mod tasks { | |||
Usage: Run with `cargo xtask <task>`, eg. `cargo xtask docgen`. | |||
Tasks: | |||
steel: Install steel |
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.
steel: Install steel | |
steel Install steel |
helix-event/Cargo.toml
Outdated
once_cell = "1.21" | ||
parking_lot = { workspace = true, features = ["hardware-lock-elision"] } |
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.
This seems to just reorder the dependencies? The comment should apply to parking_lot
@@ -31,10 +31,11 @@ assets = [ | |||
] | |||
|
|||
[features] | |||
default = ["git"] | |||
default = ["git", "steel"] # Remove steel if you don't want it |
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.
For the initial merge let's disable this from default
for now, and add instructions in STEEL.md
on cargo build
flag to enable
helix-term/Cargo.toml
Outdated
# plugin support | ||
steel-core = { workspace = true, optional = true } | ||
steel-doc = { git = "https://github.com/mattwparas/steel.git", version = "0.7.0" } | ||
# steel-doc = { path = "/home/matt/code/scratch/steel/crates/steel-doc", version = "0.6.0" } |
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.
stray comment
helix-term/src/commands.rs
Outdated
@@ -256,7 +265,11 @@ impl MappableCommand { | |||
cx.editor.set_error(format!("{}", e)); | |||
} | |||
} else { | |||
cx.editor.set_error(format!("no such command: '{name}'")); | |||
// TODO: Update this |
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's left to do here?
// A name separate from the file name | ||
pub name: Option<String>, |
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 think document.path
should be an URI. Then we can use helix://steel/myPlugin/...
etc. for internal documents. Since that's a pretty big refactor to get rid of PathBuf
, let's just rely on the uri
field you added for the time being (if self.uri.is_some()
it's an internal document)
Document that on the field and leave a TODO to consider merging uri
and path
helix-view/src/editor.rs
Outdated
@@ -1255,6 +1271,8 @@ impl Editor { | |||
handlers, | |||
mouse_down_range: None, | |||
cursor_cache: CursorCache::default(), | |||
editor_clipping: ClippingConfiguration::default(), | |||
user_defined_themes: Default::default(), |
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.
Can we just modify the main theme loader? I'd like to avoid adding even more fields to Editor
let mut area = area; | ||
|
||
// TODO: This may need to get looked at! | ||
if let Some(top) = cx.editor.editor_clipping.top { | ||
area = area.clip_top(top); | ||
} | ||
if let Some(bottom) = cx.editor.editor_clipping.bottom { | ||
area = area.clip_bottom(bottom); | ||
} | ||
if let Some(left) = cx.editor.editor_clipping.left { | ||
area = area.clip_left(left); | ||
} | ||
if let Some(right) = cx.editor.editor_clipping.right { | ||
area = area.clip_right(right); | ||
} |
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.
How is this being utilized?
helix-term/src/ui/overlay.rs
Outdated
// TODO: For this to be sound, all of the various functions | ||
// have to now be marked as send + sync + 'static. Annoying, | ||
// and something I'll look into with steel. | ||
unsafe impl<T> Send for Overlay<T> {} | ||
unsafe impl<T> Sync for Overlay<T> {} |
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.
Can we centralize all of these definitions in one file and add a config attr so they're only compiled with steel?
helix-term/src/commands.rs
Outdated
#[cfg(feature = "steel")] | ||
pub use engine::steel::{helix_module_file, steel_init_file}; |
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.
Is this something we need to export here rather than under engine/
?
Hi, I'm trying to write a plugin for rustowl.
However, it seems that the cursor-position refers to the number of characters from the beginning of the file, starting from 0. How can I split this into (define (rustowl-start)
(helix.misc.send-lsp-command
"rustowl"
"rustowl/cursor"
(hash
"position" (hash
"line" (helix.misc.cursor-line)
"character" (helix.misc.cursor-column))
"document" (hash "uri" (string-append "file://" (current-path))))
(lambda (response)
(displayln "RustOwl response:")
(displayln response)))) One more question: is it currently possible to visualize the ownership and lifetimes of variables based on the response from RustOwl? For example:
Thanks, |
I've created a PR to register (define (rustowl-start)
(helix.misc.send-lsp-command
"rustowl"
"rustowl/cursor"
(hash
"position" (hash
"line" (helix.misc.cursor-line)
"character" (helix.misc.cursor-column))
"document" (hash "uri" (string-append "file://" (current-path))))
(lambda (response)
(call-with-output-file "/tmp/helix.log"
(lambda (out)
(write response out)
(newline out)))))) After running #<hashmap {
'status: "finished",
'decorations: '(#<hashmap {
'type: "lifetime",
'overlapped: #true,
'local: #<hashmap {
'id: 4,
'fn_id: 3,
}>,
'hover_text: "lifetime of variable `s2`",
'range: #<hashmap {
'end: #<hashmap {
'character: 40,
'line: 6,
}>,
'start: #<hashmap {
'line: 6,
'character: 38,
}>,
}>,
}> #<hashmap {
'range: #<hashmap {
'start: #<hashmap {
'character: 17,
'line: 4,
}>,
'end: #<hashmap {
'character: 36,
'line: 4,
}>,
}>,
'type: "lifetime",
'overlapped: #true,
'hover_text: "lifetime of variable `s2`",
'local: #<hashmap {
'id: 4,
'fn_id: 3,
}>,
}> #<hashmap {
'local: #<hashmap {
'id: 4,
'fn_id: 3,
}>,
'hover_text: "lifetime of variable `s2`",
'range: #<hashmap {
'start: #<hashmap {
'character: 4,
'line: 3,
}>,
'end: #<hashmap {
'line: 4,
'character: 16,
}>,
}>,
'overlapped: #false,
'type: "lifetime",
}> #<hashmap {
'hover_text: "lifetime of variable `s2`",
'range: #<hashmap {
'start: #<hashmap {
'line: 4,
'character: 37,
}>,
'end: #<hashmap {
'character: 37,
'line: 6,
}>,
}>,
'overlapped: #false,
'local: #<hashmap {
'id: 4,
'fn_id: 3,
}>,
'type: "lifetime",
}> #<hashmap {
'range: #<hashmap {
'end: #<hashmap {
'character: 5,
'line: 7,
}>,
'start: #<hashmap {
'character: 41,
'line: 6,
}>,
}>,
'local: #<hashmap {
'fn_id: 3,
'id: 4,
}>,
'hover_text: "lifetime of variable `s2`",
'type: "lifetime",
'overlapped: #false,
}> #<hashmap {
'range: #<hashmap {
'end: #<hashmap {
'line: 6,
'character: 40,
}>,
'start: #<hashmap {
'line: 6,
'character: 38,
}>,
}>,
'type: "imm_borrow",
'hover_text: "immutable borrow",
'overlapped: #false,
'local: #<hashmap {
'id: 4,
'fn_id: 3,
}>,
}> #<hashmap {
'type: "call",
'hover_text: "function call",
'range: #<hashmap {
'end: #<hashmap {
'line: 4,
'character: 36,
}>,
'start: #<hashmap {
'character: 17,
'line: 4,
}>,
}>,
'local: #<hashmap {
'id: 4,
'fn_id: 3,
}>,
'overlapped: #false,
}> #<hashmap {
'overlapped: #false,
'local: #<hashmap {
'fn_id: 3,
'id: 4,
}>,
'range: #<hashmap {
'end: #<hashmap {
'character: 39,
'line: 8,
}>,
'start: #<hashmap {
'line: 8,
'character: 13,
}>,
}>,
'type: "outlive",
'hover_text: "variable `s2` is required to live here",
}> #<hashmap {
'overlapped: #false,
'local: #<hashmap {
'id: 4,
'fn_id: 3,
}>,
'type: "outlive",
'hover_text: "variable `s2` is required to live here",
'range: #<hashmap {
'end: #<hashmap {
'line: 8,
'character: 47,
}>,
'start: #<hashmap {
'character: 41,
'line: 8,
}>,
}>,
}>),
'path: "/Users/quantong/Code/personal/rust-book-exercises/10-Generic-Types-Traits-and-Lifetimes/lifetimes/src/main.rs",
'is_analyzed: #true,
}> I'm exploring ways to visualize variables ownership and lifetimes using decoration ranges. |
Notes:
Opening this just to track progress on the effort and gather some feedback. There is still work to be done but I would like to gather some opinions on the direction before I continue more.
You can see my currently functioning helix config here and there are instructions listed in the
STEEL.md
file. The main repo for steel lives here, however much documentation is in works and will be added soon.The bulk of the implementation lies in the
engine.rs
andscheme.rs
files.Design
Given prior conversation about developing a custom language implementation, I attempted to make the integration with Steel as agnostic of the engine as possible to keep that door open.
The interface I ended up with (which is subject to change and would love feedback on) is the following:
If you can implement this, the engine should be able to be embedded within Helix. On top of that, I believe what I have allows the coexistence of multiple scripting engines, with a built in priority for resolving commands / configurations / etc.
As a result, Steel here is entirely optional and also remains completely backwards compatible with the existing toml configuration. Steel is just another layer on the existing configuration chain, and as such will be applied last. This applies to both the
config.toml
and thelanguages.toml
. Keybindings can be defined via Steel as well, and these can be buffer specific, language specific, or global. Themes can also be defined from Steel code and enabled, although this is not as rigorously tested and is a relatively recent addition. Otherwise, I have been using this as my daily driver to develop for the last few months.I opted for a two tiered approach, centered around a handful of design ideas that I'd like feedback on:
The first, there is a
init.scm
and ahelix.scm
file - thehelix.scm
module is where you define any commands that you would like to use at all. Any function exposed via that module is eligible to be used as a typed command or via a keybinding. For example:This would then make the command
:shell
available, and it will just replace the%
with the current file. The documentation listed in the@doc
doc comment will also pop up explaining what the command does:Once the
helix.scm
module isrequire
'd - then theinit.scm
file is run. One thing to note is that thehelix.scm
module does not have direct access to a running helix context. It must act entirely stateless of anything related to the helix context object. Runninginit.scm
gives access to a helix object, currently defined as*helix.cx*
. This is something I'm not sure I particularly love, as it makes async function calls a bit odd - I think it might make more sense to make the helix context just a global inside of a module. This would also save the hassle that every function exposed has to accept acx
parameter - this ends up with a great deal of boilerplate that I don't love. Consider the following:Every function call to helix built ins requires passing in the
cx
object - I think just having them be able to reference the global behind the scenes would make this a bit ergonomic. The integration with the helix runtime would make sure whether that variable actually points to a legal context, since we pass this in via reference, so it is only alive for the duration of the call to the engine.Async functions
Steel has support for async functions, and has successfully been integrated with the tokio runtime used within helix, however it requires constructing manually the callback function yourself, rather than elegantly being able to use something like
await
. More to come on this, since the eventual design will depend on the decision to use a local context variable vs a global one.Built in functions
The basic built in functions are first all of the function that are typed and static - i.e. everything here:
However, these functions don't return values so aren't particularly useful for anything but their side effects to the editor state. As a result, I've taken the liberty of defining functions as I've needed/wanted them. Some care will need to be decided what those functions actually exposed are.
Examples
Here are some examples of plugins that I have developed using Steel:
File tree
Source can be found here
filetree.webm
Recent file picker
Source can be found here
recent-files.webm
This persists your recent files between sessions.
Scheme indent
Since steel is a scheme, there is a relatively okay scheme indent mode that only applied on
.scm
files, which can be found here. The implementation requires a little love, but worked enough for me to use helix to write scheme code 😄Terminal emulator
I did manage to whip up a terminal emulator, however paused the development of it while focusing on other things. When I get it back into working shape, I will post a video of it here. I am not sure what the status is with respect to a built in terminal emulator, but the one I got working did not attempt to do complete emulation, but rather just maintained a shell to interact with non-interactively (e.g. don't try to launch helix in it, you'll have a bad time 😄 )
Steel as a choice for a language
I understand that there is skepticism around something like Steel, however I have been working diligently on improving it. My current projects include shoring up the documentation, and working on an LSP for it to make development easier - but I will do that in parallel with maintaining this PR. If Steel is not chosen and a different language is picked, in theory the API I've exposed should do the trick at least with matching the implementation behavior that I've outlined here.
Pure rust plugins
As part of this, I spent some time trying to expose a C ABI from helix to do rust to rust plugins directly in helix without a scripting engine, with little success. Steel supports loading dylibs over a stable abi (will link to documentation once I've written it). I used this to develop the proof of concept terminal emulator. So, you might not be a huge fan of scheme code, but in theory you can write mostly Rust and use Steel as glue if you'd like - you would just be limited to the abi compatible types.
System compatibility
I develop off of Linux and Mac - but have not tested on windows. I have access to a windows system, and will get around to testing on that when the time comes.