helix/STEEL.md

257 lines
8.9 KiB
Markdown
Raw Normal View History

2023-08-21 00:20:14 +08:00
# Building
You will need:
2023-08-21 00:20:14 +08:00
2024-06-24 01:09:26 +08:00
* A clone of this fork, on the branch `steel-event-system`
2023-08-21 00:20:14 +08:00
## Installing helix
Just run
`cargo xtask steel`
2023-08-21 00:20:14 +08:00
To install the `hx` executable, with steel as a plugin language. This also includes:
2023-08-21 00:20:14 +08:00
2025-06-05 10:25:21 +08:00
The `steel` executable, the steel language server, the steel dylib installer, and the steel package manager `forge`.
2023-08-21 00:20:14 +08:00
## Setting up configurations for helix
2025-06-05 10:25:21 +08:00
There are 2 important files you'll want, which should be auto generated during the installation process if they don't already exist:
2023-08-21 00:20:14 +08:00
* `~/.config/helix/helix.scm`
* `~/.config/helix/init.scm`
Note - these both live inside the same directory that helix sets up for runtime configurations.
### `helix.scm`
The `helix.scm` module will be loaded first before anything else, the runtime will `require` this module, and any functions exported will now be available
to be used as typed commands. For example:
```scheme
# helix.scm
2024-07-06 22:51:15 +08:00
(require "helix/editor.scm")
2024-06-30 00:39:06 +08:00
(require (prefix-in helix. "helix/commands.scm"))
(require (prefix-in helix.static. "helix/static.scm"))
2023-08-21 00:20:14 +08:00
(provide shell git-add open-helix-scm open-init-scm)
2023-08-21 00:20:14 +08:00
;;@doc
;; Specialized shell implementation, where % is a wildcard for the current file
(define (shell cx . args)
;; Replace the % with the current file
(define expanded (map (lambda (x) (if (equal? x "%") (current-path cx) x)) args))
2024-07-11 05:11:36 +08:00
(apply helix.run-shell-command expanded))
2023-08-21 00:20:14 +08:00
;;@doc
;; Adds the current file to git
(define (git-add cx)
(shell cx "git" "add" "%"))
2023-08-21 00:34:07 +08:00
2024-07-02 04:12:49 +08:00
(define (current-path)
(let* ([focus (editor-focus)]
2024-10-31 07:43:38 +08:00
[focus-doc-id (editor->doc-id focus)])
(editor-document->path focus-doc-id)))
2023-08-21 00:34:07 +08:00
2023-08-22 09:30:34 +08:00
;;@doc
;; Open the helix.scm file
2024-07-02 04:12:49 +08:00
(define (open-helix-scm)
(helix.open (helix.static.get-helix-scm-path)))
2023-08-22 09:30:34 +08:00
;;@doc
;; Opens the init.scm file
2024-07-02 04:12:49 +08:00
(define (open-init-scm)
(helix.open (helix.static.get-init-scm-path)))
2023-08-21 00:34:07 +08:00
2023-08-21 00:20:14 +08:00
```
Now, if you'd like to add the current file you're editing to git, simply type `:git-add` - you'll see the doc pop up with it since we've annotated the function
with the `@doc` symbol. Hitting enter will execute the command.
You can also conveniently open the `helix.scm` file by using the typed command `:open-helix-scm`.
### `init.scm`
The `init.scm` file is run at the top level, immediately after the `helix.scm` module is `require`d. The helix context is available here, so you can interact with the editor.
The helix context is bound to the top level variable `*helix.cx*`.
For example, if we wanted to select a random theme at startup:
```scheme
# init.scm
(require-builtin steel/random as rand::)
2024-06-30 00:39:06 +08:00
(require (prefix-in helix. "helix/commands.scm"))
(require (prefix-in helix.static. "helix/static.scm"))
2023-08-21 00:20:14 +08:00
;; Picking one from the possible themes
(define possible-themes '("ayu_mirage" "tokyonight_storm" "catppuccin_macchiato"))
(define (select-random lst)
2025-06-13 08:47:26 +08:00
(let ([index (rand::rng->gen-range 0 (length lst))]) (list-ref lst index)))
2023-08-21 00:20:14 +08:00
(define (randomly-pick-theme options)
;; Randomly select the theme from the possible themes list
2024-07-07 00:24:26 +08:00
(helix.theme (select-random options)))
2023-08-21 00:20:14 +08:00
(randomly-pick-theme possible-themes)
```
### Libraries for helix
There are a handful of extra libraries in development for extending helix, and can be found here https://github.com/mattwparas/helix-config.
2024-06-30 00:39:06 +08:00
If you'd like to use them, create a directory called `cogs` in your `.config/helix` directory, and copy the files in there.
2023-08-21 00:20:14 +08:00
### options.scm
If you'd like to override configurations from your toml config:
```scheme
# init.scm
2024-06-30 00:39:06 +08:00
(require "helix/configuration.scm")
2023-08-21 00:20:14 +08:00
2024-06-30 00:39:06 +08:00
(file-picker (fp-hidden #f))
(cursorline #t)
(soft-wrap (sw-enable #t))
2023-08-21 00:20:14 +08:00
```
### keymaps.scm
Applying custom keybindings for certain file extensions:
```scheme
# init.scm
(require "cogs/keymaps.scm")
2024-06-30 00:39:06 +08:00
(require (only-in "cogs/file-tree.scm" FILE-TREE-KEYBINDINGS FILE-TREE))
(require (only-in "cogs/recentf.scm" recentf-open-files get-recent-files recentf-snapshot))
2023-08-21 00:20:14 +08:00
2024-06-30 00:39:06 +08:00
;; Set the global keybinding for now
(add-global-keybinding (hash "normal" (hash "C-r" (hash "f" ":recentf-open-files"))))
2023-08-21 00:20:14 +08:00
2024-06-30 00:39:06 +08:00
(define scm-keybindings (hash "insert" (hash "ret" ':scheme-indent "C-l" ':insert-lambda)))
2023-08-21 00:20:14 +08:00
;; Grab whatever the existing keybinding map is
2024-06-30 00:39:06 +08:00
(define standard-keybindings (deep-copy-global-keybindings))
(define file-tree-base (deep-copy-global-keybindings))
2023-08-21 00:20:14 +08:00
(merge-keybindings standard-keybindings scm-keybindings)
2024-06-30 00:39:06 +08:00
(merge-keybindings file-tree-base FILE-TREE-KEYBINDINGS)
2023-08-21 00:20:14 +08:00
2024-06-30 00:39:06 +08:00
(set-global-buffer-or-extension-keymap (hash "scm" standard-keybindings FILE-TREE file-tree-base))
2023-08-21 00:20:14 +08:00
```
In insert mode, this overrides the `ret` keybinding to instead use a custom scheme indent function. Functions _must_ be available as typed commands, and are referred to
as symbols. So in this case, the `scheme-indent` function was exported by my `helix.scm` module.
2025-06-05 10:25:21 +08:00
## Writing a plugin
### Getting setup
Before you start, you should make sure that your configuration for the steel lsp is wired up correctly. This will give you
access to the documentation that will help you as you write your plugin. To configure the LSP, you can add this to your
`init.scm`:
```scheme
(require "helix/configuration.scm")
(define-lsp "steel-language-server" (command "steel-language-server") (args '()))
(define-language "scheme"
(language-servers '("steel-language-server")))
```
This will give you an interactive setup that can help you run plugins as you go. I also like to evaluate commands
via the buffer, by either typing them in to the command prompt or by loading the current buffer. To load the current
buffer, you can type `:eval-buffer`, or to evaluate an individual command, you can run `:evalp` - note, in your init.scm, you
may need to add:
```scheme
(require (only-in "helix/ext" evalp eval-buffer))
```
This brings those functions to the top level scope so that you can interact with them. You may also be keen to peruse all of the steel
functions and modules available. Those can be found in `steel-docs.md`.
### Command API
There are two levels of the functionality exposed to plugins. The first is simply based around
chaining builtin commands, as if you're a lightning fast human typing commands very quickly. The other level
is a bit lower, and deals directly with the component API that helix uses to draw the text editor and various
popups, like the file picker or buffer selection.
To understand the first level, which is accessing typed commands and static commands, i.e. commands that you
typically type via `:`, or static commands, commands which are bound to keybindings, you can look at the modules:
* helix/commands.scm
* helix/static.scm
Every function here implicitly has access to a context, the helix context. This assumes that you're focused onto
some buffer, and any actions are assumed to be done within that context. For example, calling `vsplit` will
split the currently focused into a second, and move your focus to that window. Keeping track of that is important
to understand where your focus is.
In general, these functions do not return anything, given that they're purely for side effects. There are some functions
that do, and they should be documented as such. The API will need to be improved to return useful things where relevant.
### The UI
A good rule of thumb is to not block the UI. During the execution of a steel function, the helix context is exclusively
available to that executing function. As a result, you should not have long running functions there (note - if you end
up having an infinite loop of some kind, `ctrl-c` should break you out).
Luckily, there are a handful of ways we can accomplish more sophisticated plugins:
* Futures
* Threads
There are a handful of primitives that accept a future + a callback, where the callback will get executed once the future
is complete. The future will get scheduled on to the helix event loop, so the UI won't be blocked. (TODO: Document this more!)
Another way we can accomplish this is with native threads. Steel supports native threads, which means we can spawn a function
off on to another thread to run some code. Consider the following example which won't work:
```scheme
(spawn-native-thread (lambda () (sleep/ms 1000) (theme "focus_nova"))) ;; Note, this won't work!
```
This appears to spawn a thread, sleep for 1 second, and then change the theme. The issue here is that this thread does not
have control over the helix context. So what we'll have to do instead, is schedule a function to be run on the main thread:
```scheme
(require "helix/ext.scm")
(require-builtin steel/time)
(spawn-native-thread
(lambda ()
(hx.block-on-task
(lambda ()
(sleep/ms 1000)
(theme "focus_nova")))))
```
`hx.block-on-task` will check if we're running on the main thread. If we are already, it doesn't do anything - but otherwise,
it enqueues a callback that schedules itself onto the main thread, and waits till it can acquire the helix context. The function
is then run, and the value returned back to this thread of control.
There is also `hx.with-context` which does a similar thing, except it does _not_ block the current thread.
### Components
Coming soon!