Compare commits

...

762 commits

Author SHA1 Message Date
d26f1b9d41 Merge remote-tracking branch 'SofusA/pull-diagnostics'
Some checks failed
Build / Check (msrv) (push) Has been cancelled
Build / Test Suite (push) Has been cancelled
Build / Lints (push) Has been cancelled
Build / Docs (push) Has been cancelled
Cachix / Publish Flake (push) Has been cancelled
GitHub Pages / deploy (push) Has been cancelled
2025-04-02 21:09:56 +02:00
1974fe9033 Merge remote-tracking branch 'upstream/master' 2025-04-02 21:05:51 +02:00
2c19d0934a Merge remote-tracking branch 'nik-rev/welcome-screen' 2025-04-02 21:05:40 +02:00
8229e746ae Merge remote-tracking branch 'nik-rev/gix-blame' 2025-04-02 21:04:54 +02:00
Nik Revenco
7c7bd20159 chore: incorrect comment fix 2025-04-02 16:10:10 +01:00
Nik Revenco
761a62df86 refactor: add comments explaining various variables + rename variables 2025-04-02 16:08:37 +01:00
Nik Revenco
3c0fcb0679 chore: justify welcome! macro with comment 2025-04-02 16:02:40 +01:00
Nik Revenco
b1c7bd91b9 refactor: do not pass argument unnecessarily 2025-04-02 16:00:49 +01:00
Sofus Addington
f32f5fddba
Pull diagnostics 2025-04-02 08:57:34 +02:00
Nik Revenco
c74fec4c6e fix?: do not block on the main thread when acquiring diff handle
not sure if this will work as I can't reproduce this
but let's see!
2025-04-02 00:12:38 +01:00
53ed151a9f Merge remote-tracking branch 'nik-rev/gix-blame' 2025-04-01 22:44:02 +02:00
7db2913019 Merge remote-tracking branch 'fabian1409/feat/border_type' 2025-04-01 22:41:11 +02:00
Nik Revenco
e8d7e76c73
fix: spelling error
Co-authored-by: Sebastian Klähn <39526136+Septias@users.noreply.github.com>
2025-04-01 12:17:48 +01:00
dependabot[bot]
7ebf650029
build(deps): bump once_cell in the rust-dependencies group (#13244)
Bumps the rust-dependencies group with 1 update: [once_cell](https://github.com/matklad/once_cell).


Updates `once_cell` from 1.21.1 to 1.21.3
- [Changelog](https://github.com/matklad/once_cell/blob/master/CHANGELOG.md)
- [Commits](https://github.com/matklad/once_cell/compare/v1.21.1...v1.21.3)

---
updated-dependencies:
- dependency-name: once_cell
  dependency-version: 1.21.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: rust-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-04-01 13:55:16 +09:00
Sri Senthil Balaji J
db187c4870
feat: add ConsoleOnly to desktop entry categories (#13236) 2025-03-31 09:28:50 -05:00
Michael Davis
e148d8b311
editor: Remove closed Document after updating Views
When closing a document we must wait until all views have been updated
first - either replacing their current document or closing the view -
before we remove the document from the `documents` map. The
`Editor::_refresh` helper is called by `Editor::close`. It accesses each
View's Document to sync changes and ensure that the cursor is in view.
When closing multiple Views at once, `Editor::_refresh` will attempt
to access the closing Document while refreshing a to-be-closed View.
2025-03-30 11:01:17 -04:00
Nik Revenco
1a0dad36b7 perf: only render inline blame for visible lines when all-lines is set
Previously, we rendereded the inline blame for lines in 3X the range of
the viewport

This is not necessary because when we scroll down, the rendering will
occur before we see the new content
2025-03-30 11:42:43 +01:00
Nik Revenco
95344a9585 perf: use string preallocations for string concatenation 2025-03-30 00:04:16 +00:00
Andrea Novellini
fb815e2c6f
Add peachpuff theme (#13225) 2025-03-29 14:44:55 -05:00
Gavin Morrow
e735485277
Remove bg from tokyonight text (#13216) 2025-03-29 14:43:33 -05:00
Keir Lawson
bb96a535fc
Add ui.text.directory to spacebones (#13213) 2025-03-29 14:41:43 -05:00
RoloEdits
01fce51c45
fix(keymap): point to proper MappableCommand instead of Command (#13214) 2025-03-28 08:51:36 -05:00
f79099a418 Fix bork merge 2025-03-27 21:49:15 +01:00
6ad3624f84 Merge remote-tracking branch 'RoloEdits/icons-v2' 2025-03-27 21:38:40 +01:00
2ea120d205 Merge remote-tracking branch 'nik-rev/merge-statusline-cmdline' 2025-03-27 21:37:36 +01:00
096201540b Merge remote-tracking branch 'SofusA/pull-diagnostics' 2025-03-27 21:37:28 +01:00
bf032afa79 Merge remote-tracking branch 'nik-rev/gix-blame' 2025-03-27 21:36:23 +01:00
b4ba2dc83d Merge remote-tracking branch 'nik-rev/welcome-screen' 2025-03-27 21:35:52 +01:00
Keir Lawson
7929c0719d
Track progress title an display in place of internal token (#13180)
Co-authored-by: Michael Davis <mcarsondavis@gmail.com>
2025-03-27 12:00:23 -05:00
Nik Revenco
a51334f00b feat: increase padding by 1 2025-03-27 16:42:01 +00:00
Nik Revenco
55ef555f87 feat: improve wording in start menu 2025-03-27 16:38:50 +00:00
Nik Revenco
603d95f5d3 feat: recommend <space>e over <space>f
_
2025-03-27 16:35:13 +00:00
Alexandre Legent
68d7308e25
Update golangci-lint command for v2 (#13204) 2025-03-27 11:27:25 -05:00
Daniel Fichtinger
a2c580c4ae
feat: add tmTheme as XML filetype (#13202) 2025-03-27 08:39:10 -05:00
Steven Vancoillie
cf7eb5517f
Add earl_grey theme (#13203) 2025-03-27 08:37:32 -05:00
Nik Revenco
593504bb27 feat: left padding 2 -> 1 2025-03-27 11:03:56 +00:00
Nik Revenco
3471d82f87 refactor: make padding more naive
no need for such a complex solution, this is already pretty good
2025-03-27 11:02:02 +00:00
Nik Revenco
1e2f1363bc feat: update welcome screen
1. remove `type ` prefix
2. add `<tab>` to themes point
2025-03-27 10:48:37 +00:00
Nik Revenco
b68aa35bf4 feat: change text of :theme in welcome message
_
2025-03-26 19:10:46 +00:00
Nik Revenco
871f12b751 fix: 2 panics
_

_
2025-03-26 15:04:15 +00:00
Nik Revenco
7e9bf8a3bb feat: shift the type to the left
_
2025-03-26 13:38:36 +00:00
Nik Revenco
810ac6023e feat: update dashboard 2025-03-26 13:34:20 +00:00
Nik Revenco
5640161e38 feat: Welcome Screen
_

_
2025-03-26 02:53:55 +00:00
Nik Revenco
af3b670de6 refactor: move expression
_
2025-03-25 21:52:54 +00:00
Nik Revenco
b3b1c88d27 refactor: pass the Style instead of Theme 2025-03-25 21:34:09 +00:00
Nik Revenco
c101f37298 style: fmt 2025-03-25 21:31:08 +00:00
Nik Revenco
082ba4d741 refactor: match over if 2025-03-25 21:27:14 +00:00
Nik Revenco
ab5663891c refactor: render inline blame in a separate Editor function 2025-03-25 21:24:33 +00:00
Nik Revenco
00d168a78d fix: funny boolean inversion 2025-03-25 21:14:02 +00:00
Nik Revenco
a8097f1cdc perf: use Vec<T> instead of HashMap<usize, T> 2025-03-25 20:02:05 +00:00
Nik Revenco
22f9571687 feat: split inline-blame.behaviour into two options
_

_
2025-03-25 19:11:46 +00:00
Michael Davis
388a3b78e3
Avoid removing modified documents in Editor::close_document
This fixes a regression from 6da1a79d80. `:buffer-close` on an
unmodified document would cause later panics since the document should
not have been removed. Instead of eagerly removing the document on the
first line we need to wait until we've checked that it's unmodified.
2025-03-25 09:03:32 -04:00
Nik Revenco
b9f8226208 refactor: remove new_config from EditorConfigDidChange event
There is no need for it because we have access to `Editor::config()`
2025-03-25 13:03:20 +00:00
Michael Davis
d43de14807
LSP: Avoid requesting document colors for ghost transactions
The point of ghost transactions is to avoid notifying language servers
about changes since the change is meant to be temporary. This is used
for completion while selecting items in the menu: updating the language
server would mess up incomplete completions.

When a document is changed by a ghost transaction the language server
will not be notified so its understanding of the document will not be
synchronized and any positions it sends may be out-of-date. So we should
avoid triggering a request for new document color information when a
document is changed by a ghost transaction.
2025-03-25 08:52:47 -04:00
Nik Revenco
d34074af1b perf: do not render inline blame on invisible lines 2025-03-25 12:51:47 +00:00
dependabot[bot]
04d1180a0c
build(deps): bump the rust-dependencies group with 4 updates (#13190)
Bumps the rust-dependencies group with 4 updates: [tempfile](https://github.com/Stebalien/tempfile), [log](https://github.com/rust-lang/log), [rustix](https://github.com/bytecodealliance/rustix) and [cc](https://github.com/rust-lang/cc-rs).


Updates `tempfile` from 3.19.0 to 3.19.1
- [Changelog](https://github.com/Stebalien/tempfile/blob/master/CHANGELOG.md)
- [Commits](https://github.com/Stebalien/tempfile/compare/v3.19.0...v3.19.1)

Updates `log` from 0.4.26 to 0.4.27
- [Release notes](https://github.com/rust-lang/log/releases)
- [Changelog](https://github.com/rust-lang/log/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/log/compare/0.4.26...0.4.27)

Updates `rustix` from 1.0.2 to 1.0.3
- [Release notes](https://github.com/bytecodealliance/rustix/releases)
- [Changelog](https://github.com/bytecodealliance/rustix/blob/main/CHANGES.md)
- [Commits](https://github.com/bytecodealliance/rustix/compare/v1.0.2...v1.0.3)

Updates `cc` from 1.2.16 to 1.2.17
- [Release notes](https://github.com/rust-lang/cc-rs/releases)
- [Changelog](https://github.com/rust-lang/cc-rs/blob/main/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/cc-rs/compare/cc-v1.2.16...cc-v1.2.17)

---
updated-dependencies:
- dependency-name: tempfile
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: rust-dependencies
- dependency-name: log
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: rust-dependencies
- dependency-name: rustix
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: rust-dependencies
- dependency-name: cc
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: rust-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-03-25 07:40:40 -05:00
Nik Revenco
ac0e677fae chore: appease clippy 2025-03-25 12:34:01 +00:00
Nik Revenco
76a92aff2f feat: all-lines option for inline blame 2025-03-25 12:25:12 +00:00
Nik Revenco
7478d9e688 refactor: extract as variable 2025-03-25 11:55:05 +00:00
Nik Revenco
f54fdef099 refactor: remove extra layer of sync 2025-03-25 11:46:36 +00:00
Nick
5adb4b7413
Allow :theme to show current theme (#13192)
Updates the signature for the command to take 0 arguments. This probably
regressed during 0efa8207d8.
2025-03-25 18:43:26 +09:00
Nik Revenco
8f0721f00a use format! instead of preallocating
this is more efficient apparently
2025-03-24 16:07:38 +00:00
Sebastian Dörner
13b2dc31f5
Book: Add a section with links to "Helix mode" in non-Helix software. (#12258) 2025-03-24 08:21:37 -05:00
Sofus Addington
8bce378bad
Pull diagnostics 2025-03-24 08:33:52 +01:00
Nik Revenco
07c69c1e74 fix: update blame when editing config 2025-03-24 04:00:02 +00:00
Nik Revenco
647615ddec perf: optimize obtaining blame for the same line
_

fix: blame_line_impl

_

_

_

_

_
2025-03-24 03:27:25 +00:00
Nik Revenco
29f442887a feat: Inline Git Blame
fix: use relative path when finding file

style: cargo fmt

_

chore: better error message

refactor: rename to `blame_line`

fix: use line of primary cursor for git blame

feat: basic implementation of blocking Blame handler

feat: implement basic virtual text (end of line blame)

feat: figure out how to draw stuff at the end of lines

feat: implement end of line virtual text for the current line

feat: implement inline git blame

chore: clean up

chore: remove unused import

_

chore: set `blame` to `false` by default

docs: document `[editor.vcs.blame]`

chore: add progress

perf: use background task for worker

_

chore: remove unnecessary panic!s

chore: remove commented code

refactor: remove some layers of abstraction

refactor: remove nesting

feat: [editor.vcs] -> [editor.version-control]

fix: account for inserted and deleted lines

_

refactor: extract into a `blame` module

feat: allow using custom commit format

feat: allow more customizability for inline blame

test: add tests for custom inline commit parsser

refactor: rename `blame` -> `blame_line`

_

_

test: create helper macros for tests

test: make test syntax more expressive. Allow specifying line numbers
that just got added

test: with interspersed lines

feat: add `line_blame` static command

_

test: add an extra test case

test: add ability to have `delete`d lines

test: fix on windows (?)

test: `delete` test case

test: add extra step to test case

test: add documentation for macro

refactor: use `hashmap!` macro

refactor: collapse match arm

fix: remove panic

perf: update inline git blame every 150 milliseconds instead of on each
command

test: add attributes on blocks

style: move function earlier in the file

perf: cache blame results in a hashma

chore: remove log statements

chore: clean up.

ALSO: removes checking for inline blame every N seconds.

_

perf: use mspc instead of busy-wait

docs: add information why we don't optimize the repo

_

test: add back the commented out tests

chore: comment out cfg(not(windows))

test: add extra history to blame test

docs: remove incorrect static command

_

test: disable test on windows

feat: send inline blame event update when reloading or saving the
document

feat: rename `version-control` -> `inline-blame`

feat: update theme key used for inline-blame

chore: remove unused #![allow]

chore:

style: remove accidental formatting

docs: remove incorrect key

perf: Use a single `ThreadSafeRepository` instead of re-constructing it
each time

feat: add `inline_blame` static command bound to `space + B`

style: revert formatting in keymap.md

chore: do not compute blame for document when changing config option

This isn't needed anymore because the inline-blame will be computed
regardless if `inline_blame.enable` is set or not

style: remove newline

refactor: use `fold` instead of loop

chore: clean up

feat: log error forl line blame when it happens

feat: improve message when we don't have the blame

We know that we don't have it because we're still calculating it.

feat: do not render inline blame for empty lines

_

feat: do not show blame output when we are on a hunk that was added

refactor: remove additional wrapper methods

fix

_

feat: more readable time for git blame

chr

feat:

feat: improved error handling

fix: path separator on Windows

test: disable on windows

refactor: move pretty date function formatter into `helix-stdx`

perf: do not use a syscall on each render

chore: add TODO comment to update gix version

chore: use `gix::path` conversion from Path -> BString

_

_

chore: do not update file blame on document save

This is not needed because when we write the file, we don't make a new
commit so the blame will not change.

refactor: use statics to get time elapsed instead of editor state

refactor: do not use custom event, use handler instead

fix: do not spawn a new handler

docs: correct examples for `editor.inline-blame.format`

docs: correct static command name

refactor: add comments, and improve variable names

I didn't really understand this function when I made it. Was just
copy-pasted from end of line diagnostics

I wanted to know what this is actually doing, so I investigated and
while doing this also added comments and improved names of variables
so others can understand too

fix: time in future is accounted for

perf: inline some functions that are called in only 1 place, during a
render loop

perf: add option to disable requesting inline blame in the background

fix: request blame again when document is reloaded

chore: inline blame is disabled with request on demand

feat: when requesting line blame with "blame on demand", show blame in
status

perf: use less allocations

perf: less allocations in `format_relative_time`

_

_

_

_

docs: correct name of command

_

feat: improve error message

_

feat: rename enum variants for inline blame behaviour

docs: improve description of behaviour field
2025-03-24 00:32:29 +00:00
Rolo
578a81f146 feat: add support for basic icons 2025-03-23 15:18:10 -07:00
Nik Revenco
0ee5850016
Color swatches ( 🟩 green 🟥 #ffaaaa ) (#12308) 2025-03-23 16:07:02 -05:00
Asta Halkjær From
8ff544757f
Make goto_word highlights visible (same fix as #12904) (#13174) 2025-03-23 10:27:58 -05:00
Branch Vincent
f07c1c1b29
add ui.text.directory to onedark (#13166) 2025-03-23 09:33:23 -05:00
RoloEdits
8ad6e53b1f
build(grammar): remove -fPIC flag from windows build (#13169)
Even though there is a check for `is_like_msvc`, when setting `CXX` to
`clang++` this will miss that check and try to use `-fPIC`, which is an
invlaid flag for the target.
2025-03-23 09:32:56 -05:00
Ahmir Postell
6bedca8064
Add focus_nova theme (#13144) 2025-03-22 16:24:16 -05:00
Michael Davis
7e7a98560e
LSP: Eagerly decode request results in the client
Previously the `call` helper (and its related functions) returned a
`serde_json::Value` which was then decoded either later in the client
(see signature help and hover) or by the client's caller. This led to
some unnecessary boilerplate in the client:

    let resp = self.call::<MyRequest>(params);
    Some(async move { Ok(serde_json::from_value(resp.await?)?) })

and in the caller. It also allowed for mistakes with the types. The
workspace symbol request's calling code for example mistakenly decoded a
`lsp::WorkspaceSymbolResponse` as `Vec<lsp::SymbolInformation>` - one of
the untagged enum members (so it parsed successfully) but not the
correct type.

With this change, the `call` helper eagerly decodes the response to a
request as the `lsp::request::Request::Result` trait item. This is
similar to the old helper `request` (which has become redundant and has
been eliminated) but all work is done within the same async block which
avoids some awkward lifetimes. The return types of functions like
`Client::text_document_range_inlay_hints` are now more verbose but it is
no longer possible to accidentally decode as an incorrect type.

Additionally `Client::resolve_code_action` now uses the `call_with_ref`
helper to avoid an unnecessary clone.
2025-03-22 14:40:29 -04:00
Michael Davis
6da1a79d80
Add document and LSP lifecycle events, move some callbacks into hooks
This adds events for:

* a document being opened
* a document being closed
* a language server sending the initialized notification
* a language server exiting

and also moves some handling done for these scenarios into hooks,
generally moving more into helix-view. A hook is also added on
`DocumentDidChange` which sends the `text_document_did_change`
notification - this resolves a TODO in `document`.
2025-03-22 11:41:50 -04:00
Michael Davis
2cc33b5c47
Add pull diagnostics identifier to LSP diagnostic provider
This includes a change to lsp-types to store the identifier as an Arc
since it will be cloned for each diagnostic.
2025-03-22 09:25:29 -04:00
Michael Davis
683fac65e7
Refactor DiagnosticProvider as an enum
This resolves a TODO in the core diagnostic module to refactor this
type. It was originally an alias of `LanguageServerId` for simplicity.
Refactoring as an enum is a necessary step towards introducing
"internal" diagnostics - diagnostics emitted by core features such as
a spell checker. Fully supporting this use-case will require further
larger changes to the diagnostic type, but the change to the provider
can be made first.

Note that `Copy` is not derived for `DiagnosticProvider` (as it was
previously because `LanguageServerId` is `Copy`). In the child commits
we will add the `identifier` used in LSP pull diagnostics which is a
string - not `Copy`.
2025-03-22 09:25:29 -04:00
Michael Davis
2d4c2a170c
commands: Allow any number of arguments in :bc, :bc!
Limiting to zero arguments was incorrect - a set of buffers can be
specified.
2025-03-22 09:17:30 -04:00
Michael Davis
14cab4ba62
LSP: Gracefully handle partial failures in multi-server LSP requests
This is the same change as 1c9a5bd366 but for:

* document symbols
* workspace symbols
* goto definition/declaration/.../references
* hover

Instead of bailing when one server fails, we log an error and continue
gathering items from the other responses.
2025-03-22 08:54:22 -04:00
Michael Davis
3a63e85b6a
Support EditorConfig (#13056) 2025-03-22 16:06:41 +09:00
RoloEdits
f6cb90593d
chore(worker): remove unused lifetime on EventAccumulator (#13158) 2025-03-22 16:00:39 +09:00
Ian Hobson
1c9a5bd366
Show successfully requested code actions after a failed request (#13156)
When requesting code actions from multiple LSP servers,
rather than bailing as soon as an error is encountered,
instead log the error and then keep going so that successful
requests can be presented to the user.
2025-03-21 09:10:24 -05:00
Michael Davis
1dee64f7ec
minor: Accept impl AsRef<Path> in loader's runtime_file helper
This is purely for ergonomics: we should be able to pass strings for
example

    crate::runtime_file(format!("namespace/{foo}/{bar}.txt"))

(Note that this works on Windows, see the `Path` documentation.)
2025-03-20 22:05:23 -04:00
Jan
b7d735ffe6
Switch from reddish-orange to orangeish-yellow for Solarized diff.delta (#13121) 2025-03-20 08:59:23 -05:00
Jan
7ca916ab73
Switch from reddish-orange to orangeish-yellow for Solarized diff.delta (#13121) 2025-03-20 08:55:06 -05:00
may
8e65077065
queries(scheme): consider the first argument of λ to be a variable (#13143) 2025-03-20 08:54:26 -05:00
Freddie Gilbraith
d6cacb2731
add werk language and highlights (#13136) 2025-03-20 08:04:52 -05:00
Rishikanth Chandrasekaran
ccf9564123
Adds Carbon theme for helix editor (#13067) 2025-03-19 08:54:10 -05:00
Max Milton
e7c82a34a5
language: Extend ini with more systemd file-types (#13139) 2025-03-19 08:44:37 -05:00
Michael Davis
33c17d48ff
minor: Move 'execute_lsp_command' helper into helix-view
This is a minor move that will make future refactors of code actions
simpler. We should be able to move nearly all code action functionality
into `helix-view`, save UI stuff like the `menu::Item` implementation
and dealings with the compositor.
2025-03-19 09:41:18 -04:00
Zeger Van de Vannet
6f463dbeb3
feat: add indents for starlark (#13126) 2025-03-18 09:18:45 -05:00
dependabot[bot]
70a60efcbe
build(deps): bump the rust-dependencies group with 5 updates (#13131) 2025-03-18 12:52:22 +09:00
trevershick
9d31e4df11
fix: adjust spelling of simlink->symlink (#13128) 2025-03-18 08:03:25 +09:00
Lens0021 / Leslie
27ca9d2c33
Add '///' to Amber comment-token configuration (#13122) 2025-03-16 11:03:03 -05:00
Ben Brown
e56d3abb0a
languages: Also include gitconfig as an extension (#13115)
This is useful for maintaining syntax highlighting when editing git
config files which have been included via `include` or `includeIf`.
2025-03-15 13:10:24 -05:00
Michael Davis
9574e551cf
commands: Allow zero or one arguments in :reflow
`:reflow` can optionally be passed a width.
2025-03-14 10:36:02 -04:00
Michael Davis
44bddf51b7
minor: Use parking_lot workspace dependency in helix-vcs
This is a follow-up from the parent commit - I accidentally didn't write
the buffer with this change before committing.
2025-03-13 12:43:33 -04:00
Michael Davis
b47c9da3a1
minor: Use a workspace dependency for parking_lot 2025-03-13 12:34:40 -04:00
VESSE Léo
fdaf12a35d
feat(tlaplus) : added tlaplus config + grammar (#13081) 2025-03-13 08:59:17 -05:00
SadMachinesP86
0d84bd563c
Fix Ruby highlights (#13055) 2025-03-13 08:48:13 -05:00
Michael Davis
1bd7a3901c
queries: Add JSON injection for Rust json!({..}) macros
Note that this injection doesn't work currently because precedence is
not handled by the current syntax highlighter. The switch to tree-house
will properly handle the precedence of this pattern.
2025-03-12 17:46:17 -04:00
may
694b61514f
queries: Inject into string content in Rust injections
This change also recognizes `RegexBuilder::new` calls for the regex
injection.
2025-03-12 17:31:50 -04:00
Michael Davis
7f416704b1
Fix precedence of JSON highlight queries for keys 2025-03-12 17:28:11 -04:00
Mikhail Katychev
430ce9c46b
chore: Point OpenSCAD grammar to official repo (#13033) 2025-03-12 16:10:38 -05:00
iximeow
d1e0891260
warn when configured theme is unusable for color reasons (#13058)
if `config.toml` either does not have `editor.true-color` or sets
it to false, many (most?) themes stop being usable. when loading such a
theme, Helix falls back to the default theme, but didn't mention this
anywhere - even in `~/.cache/helix/helix.log` when run with `-v`.

if this occurs when reloading a theme at runtime with `:theme`, there's
a fairly helpful error about

> `theme requires true color support`

seems worth logging about this if it happens during startup too.
2025-03-12 15:52:07 -05:00
Michael Davis
e74956fa4d
minor: Add a helper function for setting the configured theme
This block was duplicated in `Application::new` and in another helper
`Application::refresh_theme`. This change adds a helper to cover both
cases.
2025-03-12 16:32:52 -04:00
Chris44442
8d590e8aee
update vhdl tree-sitter (#13091) 2025-03-12 09:47:37 -05:00
Egor Afanasin
63ed85bc62
Sunset theme: version 2.0 (#13086) 2025-03-12 09:39:55 -05:00
Constantin Angheloiu
9bd3cecd49
Update base16_transparent.toml ui.linenr (#13080) 2025-03-12 09:18:40 -05:00
Daniel Fichtinger
8df58b2e17
feat(ini): bumped grammar version to include support for global parameters (#13088) 2025-03-11 15:28:53 -05:00
dependabot[bot]
f9360fb27e
build(deps): bump rustix from 0.38.44 to 1.0.2 (#13071)
* build(deps): bump rustix from 0.38.44 to 1.0.2

Bumps [rustix](https://github.com/bytecodealliance/rustix) from 0.38.44 to 1.0.2.
- [Release notes](https://github.com/bytecodealliance/rustix/releases)
- [Changelog](https://github.com/bytecodealliance/rustix/blob/main/CHANGES.md)
- [Commits](https://github.com/bytecodealliance/rustix/compare/v0.38.44...v1.0.2)

---
updated-dependencies:
- dependency-name: rustix
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>

* Drop unnecessary unsafe blocks for rustix Uid and Gid types

* Revert spurious downgrade of windows-sys

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Michael Davis <mcarsondavis@gmail.com>
2025-03-11 09:34:08 -05:00
dependabot[bot]
88a254d8bf
build(deps): bump cachix/install-nix-action from 30 to 31 (#13073)
Bumps [cachix/install-nix-action](https://github.com/cachix/install-nix-action) from 30 to 31.
- [Release notes](https://github.com/cachix/install-nix-action/releases)
- [Commits](https://github.com/cachix/install-nix-action/compare/v30...v31)

---
updated-dependencies:
- dependency-name: cachix/install-nix-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-03-11 08:49:18 -05:00
Jonathan Davies
c5c9e65cc4
Update install instructions (#13079) 2025-03-11 08:41:35 -05:00
dependabot[bot]
9db6c534a3
build(deps): bump cachix/cachix-action from 15 to 16 (#13074)
Bumps [cachix/cachix-action](https://github.com/cachix/cachix-action) from 15 to 16.
- [Release notes](https://github.com/cachix/cachix-action/releases)
- [Commits](https://github.com/cachix/cachix-action/compare/v15...v16)

---
updated-dependencies:
- dependency-name: cachix/cachix-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-03-11 08:30:17 -05:00
dependabot[bot]
ff558f9105
build(deps): bump the rust-dependencies group with 5 updates (#13070)
Bumps the rust-dependencies group with 5 updates:

| Package | From | To |
| --- | --- | --- |
| [tempfile](https://github.com/Stebalien/tempfile) | `3.17.1` | `3.18.0` |
| [once_cell](https://github.com/matklad/once_cell) | `1.20.3` | `1.21.0` |
| [serde](https://github.com/serde-rs/serde) | `1.0.218` | `1.0.219` |
| [tokio](https://github.com/tokio-rs/tokio) | `1.43.0` | `1.44.0` |
| [indexmap](https://github.com/indexmap-rs/indexmap) | `2.7.1` | `2.8.0` |


Updates `tempfile` from 3.17.1 to 3.18.0
- [Changelog](https://github.com/Stebalien/tempfile/blob/master/CHANGELOG.md)
- [Commits](https://github.com/Stebalien/tempfile/compare/v3.17.1...v3.18.0)

Updates `once_cell` from 1.20.3 to 1.21.0
- [Changelog](https://github.com/matklad/once_cell/blob/master/CHANGELOG.md)
- [Commits](https://github.com/matklad/once_cell/compare/v1.20.3...v1.21.0)

Updates `serde` from 1.0.218 to 1.0.219
- [Release notes](https://github.com/serde-rs/serde/releases)
- [Commits](https://github.com/serde-rs/serde/compare/v1.0.218...v1.0.219)

Updates `tokio` from 1.43.0 to 1.44.0
- [Release notes](https://github.com/tokio-rs/tokio/releases)
- [Commits](https://github.com/tokio-rs/tokio/compare/tokio-1.43.0...tokio-1.44.0)

Updates `indexmap` from 2.7.1 to 2.8.0
- [Changelog](https://github.com/indexmap-rs/indexmap/blob/main/RELEASES.md)
- [Commits](https://github.com/indexmap-rs/indexmap/compare/2.7.1...2.8.0)

---
updated-dependencies:
- dependency-name: tempfile
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: rust-dependencies
- dependency-name: once_cell
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: rust-dependencies
- dependency-name: serde
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: rust-dependencies
- dependency-name: tokio
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: rust-dependencies
- dependency-name: indexmap
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: rust-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-03-11 08:29:38 -05:00
Michael Davis
b38eae1f98
minor: Fix native line-ending handling in trimming integration tests 2025-03-10 11:09:46 -04:00
Michael Davis
67879a1e5b
Avoid inserting final newlines in empty files
This matches the behavior described by the EditorConfig spec for its
`insert_final_newline` option:

> Editors must not insert newlines in empty files when saving those
> files, even if `insert_final_newline = true`.

Co-authored-by: Axlefublr <101342105+Axlefublr@users.noreply.github.com>
2025-03-10 10:38:11 -04:00
Michael Davis
aa20eb8e7f
Add config for trimming trailing whitespace and newlines on write
These match the equivalent options in VSCode. `trim_trailing_whitespace`
is also the name used by EditorConfig.

* `trim-final-newlines` trims any extra line endings after the final one
* `trim-trailing-whitespace` trims any trailing whitespace (but not
  empty lines)
2025-03-10 10:18:55 -04:00
Michael Davis
ee9db440ce
minor: Trim trailing whitespace in languages.toml 2025-03-10 10:18:55 -04:00
Lauri Gustafsson
296eb9be83
languages.toml: Change wgsl_analyzer to wgsl-analyzer (#13063)
The binary name was changed in wgsl-analyzer commit
4c56b1435d30cd45d8aee52297bbf68ed5bb3beb and released in 0.9.7.
2025-03-10 08:22:49 -05:00
suza
dc4761ad3a
feat: Add SourcePawn language support (#13028) 2025-03-07 11:55:48 -06:00
Noel Cower
2d3b75a8c5
fix: render rulers before the cursor
Render rulers before the cursor to ensure that the cursor, when over
a ruler, is not hidden from view. Without this, you typically end up
with 1) foreground text that is the same as the background if the
ruler doesn't already have a foreground and 2) no visible cursor,
because the ruler's background color took precedence. By moving the
rulers before the cursor, this ensures that the theme is still rendered
more or less the way one would visually expect things to turn out.
2025-03-07 12:44:28 -05:00
Michael Davis
8da226f0b4
flake: Revert devShell linker to lld
`mold` does not appear to work on macOS as stated in the parent commit.
2025-03-07 12:06:33 -05:00
Michael Davis
fab08c0981
flake: Use mold for linking in devShell
Our `lld` was a bit out of date. Mold seems to be slightly faster
anyways and seems to work well on both Linux & macOS.
2025-03-07 09:41:48 -05:00
Michael Davis
b6e58c0fa4
flake: Split platform and common RUSTFLAGS in devShell
The `--no-rosegment` is not supported on macOS but the other flag
configurations can be used on both macOS and Linux.
2025-03-07 09:36:58 -05:00
Michael Davis
19558839b7
flake: Avoid setting HELIX_RUNTIME in devShell
The runtime directory should be correctly set without the need to set
HELIX_RUNTIME manually because we check for a runtime directory within
CARGO_MANIFEST_DIR.

This change also filters the runtime directory out of the source file
set passed to buildRustPackage since the runtime directory is not needed
at compilation time.
2025-03-06 18:40:35 -05:00
Michael Davis
c4d314d7ba
LSP: Fix offset encoding test case
Co-authored-by: Isaac Mills <rooster0055@protonmail.com>
2025-03-06 17:13:38 -05:00
Daniel Fichtinger
b423ed42f1
feat: add harper-ls LSP configuration (#13029) 2025-03-06 15:59:22 -06:00
Michael Davis
a3fa65880e
flake: Copy logo.svg in postInstall hook 2025-03-04 14:52:17 -05:00
Christopher Smyth
b1ee4ab5c6
Fix the git hash missing and add some more comments. (#13024) 2025-03-04 13:12:48 -06:00
Michael Davis
fbc0f956b3
minor: Move json deserialization into text_document_hover future
This follows a pattern used in the signature help request for example.
Moving the json deserialization into the return future of
`text_document_hover` makes the types easier for callers to work with.
2025-03-04 12:01:07 -05:00
Michael Davis
486f4297b7
Set cargoLock.allowBuiltinFetchGit in Nix package 2025-03-04 11:47:17 -05:00
Michael Davis
28e69f09fc
direnv: Watch changes to default.nix
Now that the package definition lives in default.nix we need direnv to
watch that file to get automatic reloads.
2025-03-04 11:31:54 -05:00
Michael Davis
ab56f9e26b
minor: Tweak some verbose LSP logs
The info log within `process_request_response` duplicated the body of
the JSON message printed earlier by the transport which was confusing.

The error log in the completion handler was easy to hit during normal
use and is not actually an error - dropping is the graceful way to
handle changes occurring while completion requests are in flight.
2025-03-04 11:25:11 -05:00
Christopher Smyth
1d453785e5
Clean up Nix Flake & make it easier to customize (#12831) 2025-03-04 10:23:28 -06:00
Erwin de Keijzer
671a6036b3
Add beans theme (#12963) 2025-03-04 10:03:46 -06:00
Alexander Brassel
82f8ac208f
Improve %% escaping error message (#13018) 2025-03-04 10:03:11 -06:00
dependabot[bot]
9440feae7c
build(deps): bump the rust-dependencies group with 11 updates (#13017)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-03-03 18:09:43 -06:00
luetage
1a28999002
Kanagawa: fix palette and attempt at a markdown compromise (#12895) 2025-03-02 11:06:59 -06:00
Michael Davis
0efa8207d8
Rewrite command line parsing, add flags and expansions (#12527)
Co-authored-by: Pascal Kuthe <pascalkuthe@pm.me>
2025-02-26 19:50:15 -06:00
may
e1c7a1ed77
remove unnecessary allocations in switch_case (#12786) 2025-02-26 19:03:29 -06:00
Michael Davis
7bebe0a70e
Highlight file picker directories with 'ui.text.directory'
This applies the same styling as the parent commit to the file pickers.
2025-02-26 19:19:37 -05:00
Nik Revenco
682967d328
feat: Improve look of Global Search Picker (#12855)
Co-authored-by: Poliorcetics <poliorcetics@users.noreply.github.com>
Co-authored-by: Nikita Revenco <154856872+NikitaRevenco@users.noreply.github.com>
Co-authored-by: Michael Davis <mcarsondavis@gmail.com>
2025-02-26 18:09:57 -06:00
Mykyta
1e8774a030
Added missing CSS highlight Tree Sitter Scopes (#12497) 2025-02-26 17:51:36 -06:00
Daniel Fichtinger
c36408457a
feat(kdl): add kdlfmt as formatter for kdl (#12967) 2025-02-26 17:50:36 -06:00
Branch Vincent
1dd8a19ad6
Add pkl-lsp (#12962) 2025-02-26 17:45:10 -06:00
tshaynik
43eab10a4c
languages.toml: add starpls as Starlark language server (#12958) 2025-02-26 17:43:16 -06:00
Daniel Fichtinger
83d4ca41cc
feat: added comment textobject to toml (#12952) 2025-02-26 17:40:34 -06:00
SofusA
534d0907d3
Update c-sharp queries (#12948) 2025-02-26 17:40:16 -06:00
Daniel Fichtinger
bb3af143f1
feat: language support for mail files (#12945) 2025-02-26 17:33:36 -06:00
wcampbell
26cb3c20e7
Accept more scons as python language (#12943) 2025-02-26 17:31:23 -06:00
Bang Lee
69f25a85da
Update languages.toml to add astro-ls (#12939) 2025-02-26 17:30:55 -06:00
Dmitriy Sokolov
8cb0d869e6
feat(lsp): add protobuf language servers (#12936) 2025-02-26 17:30:26 -06:00
David Vogt
c98302a543
feat(lsp): add container name as a column in the symbol pickers (#12930) 2025-02-26 17:28:34 -06:00
Roberto Vidal
0ba2e05a6f
fix: escape percent character when yanking to search register (#12886)
Co-authored-by: Michael Davis <mcarsondavis@gmail.com>
2025-02-26 17:22:41 -06:00
Michael Davis
e1060a2785
queries: Fix precedence in Erlang highlights 2025-02-26 10:55:00 -05:00
Michael Davis
fcddd50325
Set theme before opening documents
This is not consequential now but when we switch to the new highlighter
we will want the theme to be set (and the loader's `scopes` to be set
based on the theme) before parsing a document. Previously `set_theme`
came after the loading of documents, so documents would be missing
locals highlights after being loaded and before the first edit.
2025-02-26 10:49:36 -05:00
dependabot[bot]
35575b0b0f
build(deps): bump the rust-dependencies group with 6 updates (#12956)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-02-24 18:05:24 -06:00
Michael Davis
7e87a36e93
queries: Fix module highlights in koto 2025-02-24 12:49:43 -05:00
Michael Davis
6182bdc860
xtask: Inline query and theme checks into main module
This reverts cosmetic changes from <https://redirect.github.com/helix-editor/helix/pull/3234>:
that PR split the tasks into separate modules but the query and theme
check tasks are small enough that splitting them into separate files (or
modules) adds unnecessary friction.

This change also adds `theme-check` to the help message for the xtask
crate.
2025-02-24 10:51:28 -05:00
Michael Davis
e1c26ebfc7
queries: Reverse precedence for git-config highlights 2025-02-24 10:41:09 -05:00
Michael Davis
3683cd9ea3
queries: Remove unknown predicates 2025-02-22 14:26:41 -05:00
Sebastian Dörner
0deb8bbce6
Add indents and textobjects for Kotlin (#12925) 2025-02-20 11:58:27 -06:00
Michael Davis
3d7e2730e7
Read language servers from config in :lsp-restart
`:lsp-stop` should consider only the set of active language servers for
a document. `:lsp-restart` though may be used to start up a language
server that crashed or was manually stopped, so it needs to consider the
language servers in config instead.

This change inlines the `valid_lang_servers` function into `:lsp-stop`
and `:lsp-restart` and changes `:lsp-restart` to check the doc's config
rather than active language servers. `:lsp-restart` now also does not
need to clone the language server names as strings since it borrows from
the config and arguments rather than `Document`. The completer has also
been split into two - one matching active language servers, used by
`:lsp-stop`, and the other matching configured language servers, used by
`:lsp-restart`.

This also removes the part of `:lsp-restart` which bailed if a language
server failed to be restarted (for example because it is not installed).
There might be multiple language servers configured for a language and
only one installed. In that case the `:lsp-restart` should be considered
successful even if not all servers could be started. Bailing prevented
any language servers which could start from being attached to the
document. Instead errors are collected and emitted at the end.
2025-02-20 12:50:09 -05:00
J. Dekker
6304e7b2a7
languages/xml: add mpd & smil extensions (#12916) 2025-02-19 10:58:29 -06:00
Michael Davis
d031260180
Avoid cloning configured env vars when starting a language server
The clone of the hashmap can be avoided by passing a ref instead. This
commit also changes the `server_environment` type to match the bounds
expected by `Command::envs` - this will avoid future refactoring if the
underlying type changes (for example switching to `hashbrown::HashMap`).
2025-02-19 10:30:06 -05:00
Michael Davis
e0da129727
Use custom titles for register select info boxes
Previously all register selection info boxes had "Registers" as the
title. That was particularly confusing for `copy_between_registers`
which presents two info boxes back-to-back.
2025-02-19 10:29:15 -05:00
Michael Davis
b8912adbbf
Use a Cow<'static, str> for the Info component title
Some uses of the component (like for register) provide a static title.
We can trivially avoid the title allocation in those cases.
2025-02-19 10:10:55 -05:00
oxcrow
1c0b36b1b4
Improve jump label colors for github_light theme (#12907) 2025-02-18 08:40:42 -06:00
Michael Davis
e35d420199
application: Eliminate duplicate theme and syntax loader clones
The application held onto these since their introduction in ce97a2f0 but
the Arcs are duplicated between Application and Editor - we can store it
only on Editor without issue.
2025-02-17 19:01:54 -05:00
dependabot[bot]
48194825b9
build(deps): bump the rust-dependencies group with 3 updates (#12903)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-02-17 17:36:50 -06:00
Michael Davis
82f07fe6d1
Migrate helix-event to foldhash
This is following `hashbrown`'s switch in v0.15 from ahash to foldhash
for its `default-haster` feature, applied only to helix-event for now.

I don't have a strong preference between the two. Benchmarks in
Spellbook, which is particularly sensitive to hashers and hash table
performance, show no perceptible difference. Foldhash is dependency-free
though.

Once we migrate to the new tree-sitter bindings and highlighter we
should be able to eliminate the remaining dependencies on ahash.
2025-02-17 17:35:00 -05:00
Mike Boutin
1c47aec30c
Improve onedarker theme contrast cursorline/selection (#12833) 2025-02-17 13:04:15 -06:00
Nik Revenco
ef375d690e
feat: highlight rust repetition pattern (#12871)
Co-authored-by: Nikita Revenco <154856872+NikitaRevenco@users.noreply.github.com>
2025-02-17 12:54:49 -06:00
Axlefublr
0445062d27
fix :yank-joined disrespecting default-yank-register option (#12890) 2025-02-17 10:06:29 -06:00
Abderrahmane TAHRI JOUTI
46728046fd
Cyan Theme : fix popup not having any background (#12891) 2025-02-17 10:05:15 -06:00
dependabot[bot]
7275b7f850
build(deps): bump pulldown-cmark from 0.12.2 to 0.13.0 in the rust-dependencies group (#12865)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-02-13 08:31:38 -06:00
Michael Davis
ed3bc2b294
Remove unused dependencies
The dependabot file was matching on tree-sitter crates - that's a relic
from v0.6.0 and lower where grammars were regular dependencies.

The remaining changes are unused crates that were forgotten about during
shuffles like moving path canonicalization from helix-core to
helix-loader (and then again from helix-loader to helix-stdx).
2025-02-13 08:41:46 -05:00
Abderrahmane TAHRI JOUTI
3ccf8d58de
Cyan light UI grays and directory prompt (#12864) 2025-02-13 07:21:37 -06:00
RoloEdits
efb44e0b22
feat(sql): update tree-sitter files (#12837) 2025-02-13 07:16:27 -06:00
Michael Davis
144a4f402f
queries: Fix html highlight precedence ordering 2025-02-12 20:58:09 -05:00
Harishankar G
df752bbd45
Prevent auto-format in auto-save (#12817)
Co-authored-by: Michael Davis <mcarsondavis@gmail.com>
2025-02-12 12:11:01 -06:00
T. Li
d8c4c7c26f
fix: check and print remaining language servers (#12841) 2025-02-12 09:57:25 -06:00
Michael Davis
c3e9a0d607
Replace 'pkgbuild-language-server' with 'termux-language-server'
`pkgbuild-language-server` no longer exists and the PKGBUILD
functionality has moved to `termux-language-server`.
2025-02-12 10:51:07 -05:00
Jean-Louis Fuchs
258e3e1136
feat: Add support for the Ink programming language (#12773) 2025-02-12 09:46:50 -06:00
Michael Davis
5a66270c00
Remove typst-lsp config
typst-lsp has been deprecated in favor of tinymist.
2025-02-12 10:39:10 -05:00
Roberto Vidal
6aa82bb3f8
mark xsl files as XML (#12834) 2025-02-11 09:09:53 -06:00
dependabot[bot]
518d054fcb
build(deps): bump the rust-dependencies group with 4 updates (#12832)
Bumps the rust-dependencies group with 4 updates: [once_cell](https://github.com/matklad/once_cell), [toml](https://github.com/toml-rs/toml), [cc](https://github.com/rust-lang/cc-rs) and [which](https://github.com/harryfei/which-rs).


Updates `once_cell` from 1.20.2 to 1.20.3
- [Changelog](https://github.com/matklad/once_cell/blob/master/CHANGELOG.md)
- [Commits](https://github.com/matklad/once_cell/compare/v1.20.2...v1.20.3)

Updates `toml` from 0.8.19 to 0.8.20
- [Commits](https://github.com/toml-rs/toml/compare/toml-v0.8.19...toml-v0.8.20)

Updates `cc` from 1.2.11 to 1.2.13
- [Release notes](https://github.com/rust-lang/cc-rs/releases)
- [Changelog](https://github.com/rust-lang/cc-rs/blob/main/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/cc-rs/compare/cc-v1.2.11...cc-v1.2.13)

Updates `which` from 7.0.1 to 7.0.2
- [Release notes](https://github.com/harryfei/which-rs/releases)
- [Changelog](https://github.com/harryfei/which-rs/blob/master/CHANGELOG.md)
- [Commits](https://github.com/harryfei/which-rs/compare/7.0.1...7.0.2)

---
updated-dependencies:
- dependency-name: once_cell
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: rust-dependencies
- dependency-name: toml
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: rust-dependencies
- dependency-name: cc
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: rust-dependencies
- dependency-name: which
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: rust-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-02-11 14:53:29 +09:00
Matthew Mark Ibbetson
35faa73be1
Add Djot support (#12562) 2025-02-10 15:36:01 -06:00
Abhi
7a3470c48d
Add support for yara language (#12753) 2025-02-10 15:32:28 -06:00
Nikita Revenco
199dc05a04
fix: Align Markdown styles with tree sitter highlights (#12696)
Co-authored-by: Nikita Revenco <154856872+NikitaRevenco@users.noreply.github.com>
2025-02-10 14:58:39 -06:00
Michael Davis
5e2501da30
Reapply "Re-enable Hare by default (#11507)"
This reverts commit 151caeacc6.
2025-02-10 15:51:50 -05:00
Milo Moisson
a03becf021
nix: add indent TS query (#12829)
Co-authored-by: Michael Davis <mcarsondavis@gmail.com>
2025-02-10 14:47:31 -06:00
Kristoffer Plagborg Bak Sørensen
a6f94e04e6
feat: add mising pkgs.writers.write* nix tree-sitter injections (#12774) 2025-02-10 14:46:53 -06:00
Kristoffer Plagborg Bak Sørensen
2197b3cfa0
feat: add mising builtins.fromTOML nix tree-sitter injection (#12776) 2025-02-10 14:46:23 -06:00
Jaakko Paju
a19c95a0a7
Add CSV language and syntax highlighting (#11973) 2025-02-10 10:51:06 -06:00
Xubai Wang
ff012e844f
Fix Bash completion space regression (#12828) 2025-02-10 10:42:45 -06:00
Poliorcetics
fcfa70e66c
just: bump grammar support to handle more kind of shebang injections (#12818) 2025-02-10 09:55:27 -06:00
Nikita Revenco
da577cf1ac feat: always render the statusline 2025-02-07 21:04:56 +00:00
Nikita Revenco
1b89f998e8
fix: Rust highlights (regression from the reverse-query-precedence PR) (#12795)
Co-authored-by: Nikita Revenco <154856872+NikitaRevenco@users.noreply.github.com>
2025-02-07 08:51:22 -06:00
Nikita Revenco
df96f0c122 feat: merge-with-commandline false by default 2025-02-06 17:04:18 +00:00
Nikita Revenco
355402e210 fix: number indicators rendering on top of statusline 2025-02-06 16:57:49 +00:00
Jonas Köhnen
c36ed6ad92
feat(themes): add ui.text.directory to gruber-darker (#12797) 2025-02-06 09:31:36 -06:00
Michael Davis
18b9eb9e06
Update tree-sitter-erlang
This is for packaging reasons, previously the license identifier in
`package.json` accidentally listed MIT instead of Apache-2.0
2025-02-05 20:22:38 -05:00
Gabriel Dinner-David
b0e1eaf50d
reverse zig highlights (#12777) 2025-02-04 20:38:15 -06:00
Drew Zemke
a36730cb21
add support for the FGA language (#12763) 2025-02-04 11:06:22 -06:00
uncenter
75abc23428
Add Tera templating language support (#12756) 2025-02-04 10:56:36 -06:00
Michael Davis
313a6479b1
LSP: Properly discard out-of-date diagnostics
Previously the `filter` caused the diagnostics to not be attached to the
document - which is good - but the out-of-date diagnostics were still
inserted into the global (editor-wide) diagnostic set. Instead we should
completely discard out-of-date diagnostics.
2025-02-04 10:39:49 -05:00
Michael Davis
62625eda46
LSP: Move diagnostic handling from Application to Editor
There is no functional change to the move - it's just moving the code
into helix-view under a new method `Editor::handle_lsp_diagnostics` -
thought there is a typo fix, the removal of an unnecessary clone (for
the document's language config) and the removal of some nesting.

Co-authored-by: Sofus Addington <sofus@addington.dk>
2025-02-04 10:39:42 -05:00
Michael Davis
16ff06370f
queries: Remove (ERROR) from all highlights
We do not highlight `(ERROR)` nodes since the highlighting is quite
noisy while typing. Also see todo comments in `syntax.rs` - we could
introduce configuration in the future to prepend `(ERROR)` to a
language's highlights query.
2025-02-04 09:35:38 -05:00
Robin Heggelund Hansen
ee33a84489
Update highlights.scm for Gren language (#12769) 2025-02-04 08:34:59 -06:00
Niklas Wallgren
1258111394
Print full error chain when failing to load grammar (#12744) 2025-02-04 08:18:54 -06:00
Michael Davis
26db54155e
DAP: Drain pending requests on recv failure
This matches <https://redirect.github.com/helix-editor/helix/pull/4852>
for the DAP transport: when there is a failure to receive a message from
the debugger we should drain all pending requests and respond to them
with the StreamClosed error.

This improves the behavior when a debugger fails to initialize, for
example starting debugpy without debugpy installed. Previously the UI
would freeze until the request timed out. Now it instantly prints a
statusline error saying that the debugger failed to start up.
2025-02-04 09:09:54 -05:00
Michael Davis
d456377821
minor: Remove double BufReader wrapper in DAP client
`reader` is already a `BufReader` so there's no need to wrap it in
another `BufReader`. This is a typo/mistake made possible by the type
erasure (a `Box<BufReader<BufReader<T: Read>>>` is also a boxed reader).
2025-02-04 08:52:09 -05:00
Michael Davis
d0d16931e3
DAP: Configure child process stderr as piped
By default this is `Stdio::inherit` which sends stderr from the child
process to Helix. Instead we should use `Stdio::piped` which allows us
to read the piped output.

We can also expect that the stderr opens now (it should similarly to
stdout), so that we always start a reader for stderr like the LSP
client.
2025-02-04 08:51:46 -05:00
dependabot[bot]
8995ccaae2
build(deps): bump the rust-dependencies group with 4 updates (#12766)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-02-03 17:37:46 -06:00
Michael Davis
066e938ba0
Add copy_between_registers for interactive register copying 2025-02-02 20:49:25 -05:00
Michael Davis
e882a750ea
commands: Eagerly clear autoinfo in select_register, insert_register
This causes the infobox to disappear even when you type a non-character
key like escape. For example `"<esc>` now clears the infobox where
before it was left hanging.
2025-02-02 20:45:25 -05:00
Michael Davis
ebdab86ce6
minor: Prefer stable core::num::abs_diff to polyfill
This function was made stable in Rust 1.60.0 so we no longer need to
polyfill.
2025-02-02 20:42:55 -05:00
jack
ab6a92ed49
update(theme): add virtual-inlay hint highlight to snazzy theme (#11089) 2025-02-02 19:02:48 -06:00
Viktor Szépe
e22bbf5489
Fix typos (#12690) 2025-02-02 18:58:29 -06:00
Leo Unglaub
0ab403d428
Add block comment configuration for PHP 2025-02-02 19:37:13 -05:00
Michael Davis
b8bfc44e42
queries: Improve Rust const generic and '_' type highlighting
You may pass constants as type arguments the const generics feature.
This is used in spellbook for example as a poor man's enum, for example
`self.strip_suffix_only::<FULL_WORD>(word, hidden_homonym)`. With this
change that `FULL_WORD` part is highlighted as a constant instead of
a type.

This change also highlight the underscore in type placeholders - this
is similar to the highlighting done for bindings in Elixir or Erlang
for example. In `Vec<_>` the underscore is highlighted the same as a
comment.
2025-02-02 19:28:01 -05:00
Michael Davis
5952d564d1
Reverse highlight precedence ordering (#9458)
Co-authored-by: postsolar <120750161+postsolar@users.noreply.github.com>
Co-authored-by: Iorvethe <58810330+Iorvethe@users.noreply.github.com>
Co-authored-by: Blaž Hrastnik <blaz@mxxn.io>
Co-authored-by: gabydd <gabydinnerdavid@gmail.com>
2025-02-02 18:17:10 -06:00
Michael Davis
382401020c
queries: Add 'not-eq' and 'not-match' predicates to TSQ highlights 2025-02-02 18:39:07 -05:00
Michael Davis
93fa990e56
queries: Fix module/namespace highlight in Unison 2025-02-02 18:39:07 -05:00
Michael Davis
70d452db3e
core: Make completion item documentation optional
Path completion items always have documentation but future core (i.e.
non-LSP) completions may not always have documentation - for example
word completion from the current buffer.
2025-02-01 21:24:25 -05:00
Michael Davis
369f2bb93d
ui: Expose the 'prompt' module
The prompt Completion type alias is otherwise private. This will be
used in <https://redirect.github.com/helix-editor/helix/pull/12527>
to refactor some functions to return prompt completions.
2025-02-01 21:12:20 -05:00
Doug Kelkhoff
0f594c35f2
feat(lang:r): Add roxygen header comment token (#12748) 2025-02-01 19:44:07 -06:00
Remo Senekowitsch
de11273857
Document installation of rust-analyzer via rustup (#12618) 2025-02-01 19:43:20 -06:00
Pascal Kuthe
5c1f3f814f
implement incomplete completion requests 2025-02-01 19:36:10 -05:00
Pascal Kuthe
4e0fc0efc6
Add a completion handler type in helix-view for tracking responses
This will replace the `Sender<CompletionEvent>` in the child commits.
It tracks sender alongside extra metadata about the responses received
from providers - namely whether a request is incomplete or not - which
can be reused between subsequent requests to the provider.
2025-02-01 19:35:58 -05:00
Michael Davis
1ab35ade2d
minor: Move CompletionEvent to a new completion handler module
Completions are not specific to LSP anymore. In the child commits we
will expand on the types in this module so this refactor is done
eagerly to minimize changes later.
2025-02-01 19:32:37 -05:00
Pascal Kuthe
018081a5b1
core: Add a provider type to track the origin of a completion 2025-02-01 19:32:37 -05:00
Michael Davis
f0fa905622
LSP: Eagerly send requests in Client::request
This is a similar change to the parent commit but for `request`. The
requests should be sent eagerly so that the ordering stays consistent.

Co-authored-by: Pascal Kuthe <pascalkuthe@pm.me>
2025-02-01 19:32:37 -05:00
Michael Davis
5532ef35d9
LSP: Remove future wrapper from Client::notify, Client::reply
Previously LSP notifications were sent within a future and most callers
used a `tokio::spawn` to send the notification, silently discarding any
failures like problems serializing parameters or sending on the channel.
It's possible that tokio could schedule futures out of intended order
though which could cause notifications where order is important, like
document synchronization, to become partially shuffled. This change
removes the future wrapper and logs all internal failures.

Also included in this commit is the same change for `Client::reply`
which was also unnecessarily wrapped in a future.

Co-authored-by: Pascal Kuthe <pascalkuthe@pm.me>
2025-02-01 19:32:37 -05:00
Pascal Kuthe
0ea401d2d7
Use the slotmap workspace dependency in helix-view
This workspace dependency is already used in `helix-core` and
`helix-lsp`. This change uses it in `helix-view` as well for
consistency.
2025-02-01 19:32:23 -05:00
uncenter
e70f8833e2
Highlight $ template literals as shell commands (#12751) 2025-02-01 18:18:08 -06:00
Kristoffer Plagborg Bak Sørensen
30616344d7
Recognize .sublime-* files (#12750) 2025-02-01 17:44:06 -06:00
rhogenson
17ffa38a5a
Use the first char in a grapheme for classification (#12483)
Co-authored-by: Rose Hogenson <rosehogenson@posteo.net>
2025-02-01 17:09:45 -06:00
Michael Davis
c3620b7116
Join input and wait tasks in external formatter Tokio command
This matches the layout of `shell_impl_async` in `commands.rs` and
avoids a hang or maybe deadlock in `to_writer`'s calls to
`tokio::io::AsyncWriteExt::write_all`. I don't really understand the
underlying cause of the hang but it seems it's necessary to spawn a
new tokio task to provide input to stdin. This is shown in an example
in `tokio::process::Child::wait` but not documented explicitly.
2025-02-01 10:58:03 -05:00
Michael Davis
e9c16b7fc5
Use typable command doc when keybind provides no arguments
This improves the display of the keymap popup for example, so that if
you bind a key like `C-x = ":buffer-close"` under the `<space>` menu,
the infobox shows "Close the current buffer." rather than `:buffer-close
[]`.
2025-02-01 09:10:04 -05:00
Michael Davis
8439ce5683
Hover UI: Eagerly convert hover response to Markdown
This simplifies the hover component by eagerly converting all
`lsp::Hover` responses into `Markdown`s. Previously we cached the
current `Markdown` and created a new `Markdown` when switching the
active response. Instead we can consume the `lsp::Hover` and avoid some
clones of its inner types.
2025-01-31 17:34:56 -05:00
Nikita Revenco
6edff24c81
fix: add comment token for svelte files (#12743)
Co-authored-by: Nikita Revenco <154856872+NikitaRevenco@users.noreply.github.com>
2025-01-31 15:13:09 -06:00
Michael Davis
47f84d04ea
Set a statusline error for formatter errors in :format 2025-01-31 14:07:22 -05:00
Michael Davis
2367b20318
Remove popup_border calculations from LSP Hover UI component
The Hover component is used as the inner contents of a Popup. The Popup
should be doing calculations based on whether popup_borders is
configured and not Hover. This fixes an issue with hover rendering when
the popup border option is enabled for popups.

Fixes #12742
2025-01-31 12:01:33 -05:00
Michael Davis
28047fed7f
config: Deny unknown fields in [editor.smart-tab]
Previously a typo like "enabled" would silently be discarded. Instead
we should error when a field is configured which doesn't exist.

Fixes #12739
2025-01-31 08:34:30 -05:00
RoloEdits
025719c1d8
perf(ropey): enable simd feature for stdx (#12735) 2025-01-30 19:51:34 -05:00
renshyle
80dbe030a1
Do not record keys pressed by macros while recording a macro (#12733)
Co-authored-by: Michael Davis <mcarsondavis@gmail.com>
2025-01-30 18:14:04 -06:00
John Kelly
6906164177
Properly prevent crossterm features being used when feature = "term" not enabled in helix-view (#12734) 2025-01-30 17:38:07 -06:00
Michael Davis
d285a8a9e5
LSP: Fix option handling in goto definition/references commands
The language server may return `None` for a definition/reference
request. The parent commits introduced a regression for these commands
when a server did not provide locations. With this change a server may
respond with `null` and its locations will instead not be considered.

Fixes #12732
2025-01-30 11:59:02 -05:00
Michael Davis
1a821ac726
LSP: Support multiple langauge servers for goto references
This refactors goto_reference like the parent commit to query all
language servers supporting the feature.
2025-01-30 10:37:01 -05:00
Michael Davis
f7394d53fd
LSP: Support multiple language servers for goto definition
This covers all goto-definition-like commands: declaration, definition,
type definition and implementation.

Closes #11689

Co-authored-by: j <junglerobba@jngl.one>
2025-01-30 10:36:03 -05:00
Michael Davis
ba116b47a0
LSP commands: Move offset encoding onto the Location type
<https://github.com/helix-editor/helix/pull/11486> introduced a Location
type in the LSP commands module which unified helpers like
`jump_to_location`. This change moves `OffsetEncoding` onto that type.

`SymbolInformationItem` and `PickerDiagnostic` already had fields for
carrying the offset encoding. We would want a similar setup for goto
definition/references as well (for supporting multiple language servers
with that feature) but those use the `Location` type. By moving
`OffsetEncoding` onto `Location` we make future changes to allow
mulitple language servers possible for goto definition/references
features and also simplify some calls for symbols and diagnostics.
2025-01-29 17:25:12 -05:00
Michael Davis
c9dc940428
Fix byte/char indexing mix-up in path completion
The positions passed to `Transaction::change_by_selection` should be
character indexes. `edit_diff` is meant to track the number of
characters that should be deleted to erase the file name that has been
typed so far (if any). Mistakenly this was using `str::len` which is
the byte count. This fixes a bug that could cause more text to be
deleted than intended or a panic when completing a directory with
multi-byte characters like 'éclair'.

This change also moves the `edit_diff` binding out of the loop since
it's now performing some non-trivial work (counting characters, where
before it was just accessing the pre-computed number of bytes).
2025-01-29 10:05:21 -05:00
Gareth Widlansky
8328c422b7
Add ghostty configuration support (#12703) 2025-01-29 08:56:08 -06:00
0xLucqs
6049f2035b
chore(grammar): update cairo + queries (#12712) 2025-01-28 08:19:33 -06:00
Gabriel Dinner-David
8d6efaf350
fix zig highlight query use of #lua-match (#12708) 2025-01-28 00:19:43 -05:00
dependabot[bot]
98ddbf0086
build(deps): bump the rust-dependencies group with 2 updates (#12707)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-01-27 17:19:26 -06:00
Zi How Poh
0c8f0c0334
goto_diag: avoid wraparound by default (#12704) 2025-01-27 14:41:34 -06:00
Michael Davis
fec5101a41
DAP: Refactor handling of Event and Request protocol types
This change refactors the DAP `Event` type, the `helix_dap` module and
the `helix_dap::transport` module to be closer to the equivalent
implementations in `helix_lsp`. The DAP `Event` type is similar to LSP's
`Notification` so this change rewrites the enum as a trait which is
implemented by empty types (for example `enum Initialized {}`).

`Event` is then reintroduced as an enum in `helix_dap` with a helper
function to convert from the `Event` as the transport knows it. By
separating the definitions of `Event` between lib and transport, we can
handle incoming events which are not known to our `Event` enum. For
example debugpy sends `"debugpySockets"` which is unknown. With this
change, unknown events are discarded with an info-level log message.

The `Request` type was already a trait but did not have an enum with the
known values. This change also introduces a `helix_dap::Request` enum
similar to `helix_dap::Event`. This resolves a TODO comment about
avoiding `unwrap` when parsing the request's arguments.
2025-01-27 15:27:35 -05:00
Michael Davis
9bc63c1c59
DAP: Move module ID tests out of events module
These were mistakenly added to the events module, they should be part of
the types module.
2025-01-27 15:25:22 -05:00
Michael Davis
20151a5594
Move rope grapheme iterators from core to stdx 2025-01-27 09:24:40 -05:00
Michael Davis
51832b02c9
core: Remove unused byte index grapheme functions 2025-01-27 09:24:40 -05:00
Michael Davis
39b72329b4
stdx: Add floor/ceil/is grapheme boundary functions to RopeSliceExt
These functions are the equivalent of 23b424a46 for grapheme clusters.
In order to add the `is_grapheme_boundary` function we also need to
query whether a byte index lies on a character boundary, so this change
also adds `is_char_boundary`.
2025-01-27 09:24:40 -05:00
Michael Davis
0364521dca
goto_word: Skip keys with modifiers in both on-next-key blocks 2025-01-27 09:24:40 -05:00
Michael Davis
f5f9f499cf
goto_word: Reject jump label characters with modifiers
Previously you could use `<A-a><A-b>` to jump to a label "ab". We should
not treat characters with modifiers the same as characters without.
With this change the `<A-a>` input exits out of the jumping on-next-key.

Fixes #12695
2025-01-27 08:49:03 -05:00
Poliorcetics
b00b475dfe
just: bump grammar support to Just 1.39.0 (#12692) 2025-01-26 20:10:27 -06:00
kyfanc
9829ac0c02
Cycle through hover results from multiple language servers (#10122)
Co-authored-by: Vladyslav Karasov <36513243+cotneit@users.noreply.github.com>
Co-authored-by: Michael Davis <mcarsondavis@gmail.com>
2025-01-26 11:24:50 -06:00
Kristoffer Plagborg Bak Sørensen
7c907e66f4
feat: print helpful suggestion when using :{,r}sort incorrectly (#12585) 2025-01-26 10:39:19 -06:00
Kristoffer Plagborg Bak Sørensen
259be07f05
feat: add asm-lsp for assembly dialects (#12684) 2025-01-26 10:28:40 -06:00
Michael Davis
360c6bb061
stdx: Replace RopeSliceExt::byte_to_next_char with ceil_char_boundary
The new `RopeSliceExt::ceil_char_boundary` from the parent commits can
be used to implement `RopeSliceExt::byte_to_next_char` when used with
`RopeSlice::byte_to_char`. That function had only one caller and that
caller will eventually disappear when we switch to Ropey v2 and drop
character indexing, so we can drop `byte_to_next_char` now and replace
its caller with `byte_to_char` plus `ceil_char_boundary`.

This change keeps the unit tests for `byte_to_next_char` and checks them
against a polyfill of `byte_to_char` plus `ceil_char_boundary` to ensure
that `byte_to_next_char`'s intended behavior is not changed.
2025-01-26 11:11:53 -05:00
Michael Davis
4919058e90
Use RopeSliceExt floor/ceil functions for goto_file_impl search cap
This is a good example use-case of the `floor_char_boundary` and
`ceil_char_boundary` functions added in the parent commit. In the
single-width, single-selection case in `goto_file` we cap the search
to either the current line or 1000 bytes before or after the cursor
(whichever case comes earlier). That byte index might not lie on a
character boundary so it needs to be fixed to either the prior or
later boundary.
2025-01-26 11:10:50 -05:00
Michael Davis
23b424a46d
stdx: Add floor/ceil char boundary functions to RopeSliceExt
These functions mimic `str::floor_char_boundary` and
`str::floor_char_boundary` (currently unstable under
`round_char_boundary`). They're useful for correcting a byte index
which may not lie on a character boundary. For example you might limit
a search within a slice to some fixed number of bytes. The fixed number
might not lie on a boundary though so it needs to be corrected to
either the earlier (floor) or later (ceil) boundary.
2025-01-26 11:10:24 -05:00
Joel Dueck
aac0ce5fd1
Update install.md: fix link to lang server install instructions (#12675) 2025-01-26 14:21:13 +09:00
Michael Davis
899afad4a6
flake: Revert update of nixpkgs 2025-01-25 13:52:14 -05:00
magackame
3fdd98979c
fix: goto_file_impl incorrect use of slice instead of byte_slice (#12673) 2025-01-25 12:38:35 -06:00
Alexis Mousset
de738bac6a
Small refinements for modus themes (#12670) 2025-01-25 20:30:43 +09:00
Darshan Kumawat
81708b70e6
doc: Document mdm and mrm for popup help (#12650) 2025-01-24 11:46:19 -06:00
Michael Davis
8bf9adf7b6
Update tree-sitter-elixir 2025-01-24 12:37:48 -05:00
Kevin Danne
9088f8a599
fix: HELIX_RUNTIME environment path for windows on building-from-source book page (#12658)
Co-authored-by: Kevin Danne <kevin.danne@triluxds.com>
2025-01-24 09:03:49 -06:00
Fabian Gruber
f8e9a75633 feat: added rounded-corners option to draw rounded borders 2025-01-24 08:46:46 +01:00
Nikita Revenco
a63a2ad281
feat: specify custom lang server(s) for :lsp-stop and :lsp-restart (#12578)
Co-authored-by: Nikita Revenco <154856872+NikitaRevenco@users.noreply.github.com>
2025-01-23 18:14:35 -06:00
RoloEdits
4ded712dbd
perf(syntax): short-circuit if name matches language_id (#12407) 2025-01-23 17:49:14 -06:00
Michael Davis
151caeacc6
Revert "Re-enable Hare by default (#11507)"
This reverts commit 2c09a35ccf.

Temporarily reverting this to unblock any builds during SourceHut's
ongoing DDoS: <https://status.sr.ht/issues/2025-01-23-git.sr.ht-ddos/>
2025-01-23 18:43:13 -05:00
Michael Davis
d4ade40983
Rename "file browser" => "file explorer"
Connects #11285
2025-01-23 18:17:56 -05:00
Michael Davis
0b9701e899
tui buffer: Handle multi-width graphemes in set_string_anchored
We should skip zero-width graphemes and reset only long (more than
1-width) graphemes.

Connects #12036
2025-01-23 18:12:20 -05:00
Michael Davis
9d6ea773e9
prompt: Cap anchor to line length in cursor calculation
This prevents a panic when using `C-w` on a long single-word prompt line
for example.

Connects #12036
2025-01-23 17:33:28 -05:00
Denys Rybalka
6b044aeb29
Add file browser (#11285) 2025-01-23 16:28:18 -06:00
Yomain
8af33108f6
fix: better display of prompts on long inputs (#12036) 2025-01-23 15:56:34 -06:00
Michael Davis
1afa63d457
rust: Highlight / and ! within comments as comments 2025-01-23 16:17:44 -05:00
Khang Nguyen Duy
5f62c5c24c
Update to more up-to-date zig tree-sitter repo (#11980)
Co-authored-by: Khang Nguyen Duy <iceghost@users.noreply.github.com>
Co-authored-by: Khang Nguyen Duy <os@ndykhang.net>
2025-01-23 14:36:24 -06:00
TornaxO7
fa27ae16a7
Add path completion for multiple cursors (#12550) 2025-01-23 14:31:12 -06:00
Michael Davis
8986f8b953
Update nix flake inputs
$ nix flake update
    • Updated input 'crane':
        'github:ipetkov/crane/37e4f9f0976cb9281cd3f0c70081e5e0ecaee93f?narHash=sha256-WD0//20h%2B2/yPGkO88d2nYbb23WMWYvnRyDQ9Dx4UHg%3D' (2024-10-03)
      → 'github:ipetkov/crane/849376434956794ebc7a6b487d31aace395392ba?narHash=sha256-GLJvkOG29XCynQm8XWPyykMRqIhxKcBARVu7Ydrz02M%3D' (2025-01-22)
    • Updated input 'flake-utils':
        'github:numtide/flake-utils/c1dfcf08411b08f6b8615f7d8971a2bfa81d5e8a?narHash=sha256-X6rJYSESBVr3hBoH0WbKE5KvhPU5bloyZ2L4K60/fPQ%3D' (2024-09-17)
      → 'github:numtide/flake-utils/11707dc2f618dd54ca8739b309ec4fc024de578b?narHash=sha256-l0KFg5HjrsfsO/JpG%2Br7fRrqm12kzFHyUHqHCVpMMbI%3D' (2024-11-13)
    • Updated input 'nixpkgs':
        'github:nixos/nixpkgs/bc947f541ae55e999ffdb4013441347d83b00feb?narHash=sha256-NOiTvBbRLIOe5F6RbHaAh6%2B%2BBNjsb149fGZd1T4%2BKBg%3D' (2024-10-04)
      → 'github:nixos/nixpkgs/9e4d5190a9482a1fb9d18adf0bdb83c6e506eaab?narHash=sha256-nmKOgAU48S41dTPIXAq0AHZSehWUn6ZPrUKijHAMmIk%3D' (2025-01-21)
    • Updated input 'rust-overlay':
        'github:oxalica/rust-overlay/25685cc2c7054efc31351c172ae77b21814f2d42?narHash=sha256-lJMFnMO4maJuNO6PQ5fZesrTmglze3UFTTBuKGwR1Nw%3D' (2024-10-07)
      → 'github:oxalica/rust-overlay/38374302ae9edf819eac666d1f276d62c712dd06?narHash=sha256-S2rHCrQWCDVp63XxL/AQbGr1g5M8Zx14C7Jooa4oM8o%3D' (2025-01-23)
2025-01-23 15:18:37 -05:00
Rolo
650af50c13 fix: typos 2025-01-23 15:18:16 -05:00
Rolo
c1d382a532 fix(lints): clippy 1.84 2025-01-23 15:18:16 -05:00
Nikita Revenco
168b11e091
feat: passing multile of the same files in the arguments places a cursor at each position (#12192)
Co-authored-by: Nikita Revenco <154856872+NikitaRevenco@users.noreply.github.com>
Co-authored-by: Michael Davis <mcarsondavis@gmail.com>
2025-01-23 14:04:02 -06:00
uncenter
f70923c448
Restrict tagged template language injections for ecma (#12217) 2025-01-23 13:45:35 -06:00
Branch Vincent
8f1585a097 Add ghostty/config and hgrc file types 2025-01-23 14:23:48 -05:00
Branch Vincent
0d5f6f04c9 Add fish-lsp and bump tree-sitter-fish 2025-01-23 14:23:48 -05:00
Roman Frołow
122bbea7cf
tutor: flips selections -> flips direction of selection (#12638) 2025-01-23 13:22:28 -06:00
Roman Frołow
088ba58af5
docs: force creating symbolic link if it exists (#12637) 2025-01-23 13:17:06 -06:00
Remo Senekowitsch
ce348d84f6
book: Document language.rulers config option (#12627) 2025-01-23 13:16:37 -06:00
Remo Senekowitsch
def6139abd
docs: Fix broken link (#12623) 2025-01-23 13:15:54 -06:00
Fraser Li
cf7b36f0bf
Add beancount-language-server (#12610) 2025-01-23 13:14:10 -06:00
robb
c81e0136c5
Update languages.toml to fix cl-lsp (#12615) 2025-01-23 13:13:30 -06:00
Rock Boynton
dca235c5c8
Update tree-sitter-rust (#12607)
Co-authored-by: Rock Boynton <rboynton@anduril.com>
Co-authored-by: Michael Davis <mcarsondavis@gmail.com>
2025-01-23 13:11:07 -06:00
Michael Davis
fcf981bbd7
Recognize bun.lock as JSONC
Fixes #12651
2025-01-23 14:03:10 -05:00
Jose Alvarez
d123193902
Replace current file when loading in background from picker (#12605) 2025-01-23 12:49:20 -06:00
Pig Fang
430414979d
add language server for wat (#12581) 2025-01-23 12:46:15 -06:00
FITAHIANA Nomeniavo joe
6593969f8d
add ruby-lsp as candidate for the ruby lsp (#12511) 2025-01-23 12:09:46 -06:00
Michael Davis
cb0f201d0e
snippets: Discard placeholder text for the $0 tabstop 2025-01-23 09:50:19 -05:00
Michael Davis
032dadaf37
snippets: Add a test case for parsing ${0:placeholder}
This is an example snippet sent by older versions of clangd.
2025-01-23 09:50:19 -05:00
Michael Davis
7dea2b0ddd
CI: Cache tree-sitter grammars in all jobs
This change adds tree-sitter grammar caching to Check, Lints and Docs
jobs which all previously downloaded grammars in the `helix-term` build
script fresh per job. This should increase reliability and mitigate the
effects of an ongoing SourceHut outage
(<https://status.sr.ht/issues/2025-01-23-git.sr.ht-ddos/>).

This is also a nice speed boost for these jobs:

| Job name | Example time before | Example time after |
|---       |---                  |---                 |
| Check    | 2m20s               | 47s                |
| Lints    | 2m56s               | 1m10s              |
| Docs     | 4m56s               | 2m35s              |
2025-01-23 09:47:51 -05:00
Michael Davis
76a8682c4d syntax: Prefer RopeSlice for non-id language injection markers
The `Name` variant's inner type can be switched to `RopeSlice` since
the parent commit removed the usage of `&str`. In doing this we need to
switch from a regular `Regex` to a `rope::Regex`, which is mostly a
matter of renaming the type.

The `Filename` and `Shebang` variants can also switch to `RopeSlice`
which avoids allocations in cases where the text doesn't reside on
different chunks of the rope. Previously `Filename`'s `Cow` was always
the owned variant because of the conversion to a `PathBuf`.
2025-01-23 11:01:35 +09:00
Michael Davis
060255344c syntax: Lookup up (#set! injection.language "name") props by ID
This splits the `InjectionLanguageMarker::Name` into two: one that
preforms the previous behavior (using the language configurations'
`injection_regex` fields and performing a match) and a new variant that
looks up directly by `language_id` with equality.

The old variant is used when capturing the injection language like we
do in the markdown queries for codefences. That captured text is part of
the document being highlighted so we might need a regex to recognize a
language like JavaScript as either "js" or "javascript". But the text
passed in the `(#set! injection.language "name")` property can be
looked up directly. This property is in the query code so there's no
need to be flexible in what we accept: we can require that the
`(#set! injection.language ..)` properties refer to languages by their
configured ID. This should save a noticeable amount of work for the
common case of injections: `(#set! injection.language)` is used much
more often than `@injection.language`.
2025-01-23 11:01:35 +09:00
dependabot[bot]
09b2f6ab5f
build(deps): bump the rust-dependencies group with 5 updates (#12614)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Michael Davis <mcarsondavis@gmail.com>
2025-01-21 08:59:05 -06:00
Remo Senekowitsch
64aca8b350
Fix indent unit of git-rebase config (#12617) 2025-01-21 08:14:04 -06:00
Michael Davis
ccdb710431
minor: Rename '*' at eof integration test to be more specific 2025-01-21 09:08:36 -05:00
Nikita Revenco
ba4793fca0
fix: panic when pressing * after the end of the file (#12611)
* fix: panic when pressing `*` at the end of the file

chore: remove incorrect additions

* docs: add info comment

* test: add new syntax to add a selection at the final character

* test: `*` panics when after the last char

* test: move into a more appopriate module

* test: fix failing

* test: account for Windows test suite

* test: choose a different strategy for custom syntax

* test: do not modify the syntax

* style: remove newline

---------

Co-authored-by: Nikita Revenco <154856872+NikitaRevenco@users.noreply.github.com>
2025-01-21 16:05:15 +09:00
Michael Davis
e7ac2fcdec
Add changelog notes for 25.01.1 (#12560)
Some checks failed
Github Pages / deploy (push) Has been cancelled
Release / Fetch Grammars (push) Has been cancelled
Release / Dist (push) Has been cancelled
Release / Publish (push) Has been cancelled
2025-01-19 14:50:39 -06:00
Christoph Heiss
7cc93eb1c5
feat: add MERGE_MSG file glob for git-commit (#12589) 2025-01-18 12:50:03 -06:00
Michael Davis
2c09a35ccf
Re-enable Hare by default (#11507) 2025-01-18 09:48:51 -06:00
Frans Skarman
954c97f2b5
Bump Spade grammar (#12583) 2025-01-18 09:43:45 -06:00
Nikita Revenco
076d8bd173
fix: surprising behaviour when changing line above a comment (#12575)
Co-authored-by: Nikita Revenco <154856872+NikitaRevenco@users.noreply.github.com>
2025-01-17 15:29:39 -06:00
Michael Davis
343397391f
Remove unused variant from FormatterError
This seems to be a relic from the change which added external formatting
commands - initially it worked by writing the file in place and
reloading it. Now this error type is not possible and can be removed.
2025-01-17 11:00:37 -05:00
Michael Davis
69068770c8
Add extra logging for external formatters and formatting errors
This should help debug formatting failures when using external
formatters in the future. Previously we didn't log anything when an
external formatter failed despite having a custom error type for it.
2025-01-17 11:00:34 -05:00
Michael Davis
4c41c5250c
Eliminate an unnecessary clone in insert_newline 2025-01-17 10:23:36 -05:00
Michael Davis
ffdfb59033
Fix slicing panic in path completion variable expansion (#12556) 2025-01-16 18:10:30 -06:00
janos-r
d4ee22b483
Generate a .deb file (#12453) 2025-01-16 17:37:53 -06:00
Valentin B.
29dda1403f
chore(solidity): update to newest grammar and fix queries (#12457) 2025-01-16 09:11:18 -06:00
Michael Davis
19f7bc9ecb
book: Add missing TOC entries to editor config page 2025-01-16 08:14:09 -05:00
Michael Davis
3318953bf6
minor: Use more exact allocations in insert_newline
This is partially a style commit:

* Pull more bindings out the `change_by_selection` closure like the
  line-ending string and the comment tokens used for continuation.
* Prefer `Editor::config` to `Document`'s config.

The rest is changes to places where `insert_newline` may allocate.

The first is to move `new_text` out of the `change_by_selection`
closure, reusing it between iterations. This is not necessarily always
an improvement as we need to clone the text for the return type of the
closure. `SmartString`'s `From<String>` implementation reuses the
allocation when the string is too long to inline and drops it if it is
short enough to inline though which can be wasteful. `From<&String>`
clones the string's allocation only when it is too long to be inlined,
so we save on allocations for any `new_text` short enough to be inlined.

The rest is changes to `new_text.reserve_exact`. Previously calls to
this function in this block mixed up character and byte indexing by
treating the length of the line-ending as 1. `reserve_exact` takes a
number of bytes to reserve and that may be 2 when `line_ending` is a
CRLF. A call to `reserve_exact` is also added to the branch used when
continuing line comments.
2025-01-15 10:57:03 -05:00
Michael Davis
4bd17e542e
Fix offset tracking in insert_newline
#12177 changed `insert_newline`'s behavior to trim any trailing
whitespace on a line which came before a cursor. `insert_newline` would
previously never delete text. Even the whitespace stripping behavior in
#4854 worked by inserting text - a line ending at the beginning of the
line. `global_offs`, a variable that tracks the number of characters
inserted between iterations over the existing selection ranges, was not
updated to also account for text deleted by the trimming behavior,
causing cursors to be offset by the amount of trailing space deleted
and causing panics in some cases.

To fix this we need to subtract the number of trimmed whitespace
characters from `global_offs`. `global_offs` must become an `isize`
(was a `usize`) because it may become negative in cases where a lot of
trailing whitespace is trimmed. Integration tests have been added for
each of these cases.

Fixes #12461
Fixes #12495
Fixes #12539
2025-01-15 10:36:29 -05:00
Michael Davis
99d33c741a
Add '///' to Dart comment-token configuration
Fixes #12537
2025-01-15 08:33:33 -05:00
David Else
ca19496eed
Improve dark_plus theme: Change special, ui.text.directory and ui.virtual.wrap (#12530) 2025-01-14 12:55:01 -06:00
Robin Heggelund Hansen
f69659c5be
Add support for the Gren programming language (#12525) 2025-01-14 08:26:56 -06:00
Michael Davis
27bb2447db
Use a workspace dependency for bitflags 2025-01-13 18:26:31 -05:00
dependabot[bot]
3d772afc8b
build(deps): bump the rust-dependencies group with 6 updates
Bumps the rust-dependencies group with 6 updates:

| Package | From | To |
| --- | --- | --- |
| [thiserror](https://github.com/dtolnay/thiserror) | `2.0.9` | `2.0.11` |
| [bitflags](https://github.com/bitflags/bitflags) | `2.6.0` | `2.7.0` |
| [serde_json](https://github.com/serde-rs/json) | `1.0.134` | `1.0.135` |
| [tokio](https://github.com/tokio-rs/tokio) | `1.42.0` | `1.43.0` |
| [rustix](https://github.com/bytecodealliance/rustix) | `0.38.42` | `0.38.43` |
| [cc](https://github.com/rust-lang/cc-rs) | `1.2.7` | `1.2.9` |


Updates `thiserror` from 2.0.9 to 2.0.11
- [Release notes](https://github.com/dtolnay/thiserror/releases)
- [Commits](https://github.com/dtolnay/thiserror/compare/2.0.9...2.0.11)

Updates `bitflags` from 2.6.0 to 2.7.0
- [Release notes](https://github.com/bitflags/bitflags/releases)
- [Changelog](https://github.com/bitflags/bitflags/blob/main/CHANGELOG.md)
- [Commits](https://github.com/bitflags/bitflags/compare/2.6.0...2.7.0)

Updates `serde_json` from 1.0.134 to 1.0.135
- [Release notes](https://github.com/serde-rs/json/releases)
- [Commits](https://github.com/serde-rs/json/compare/v1.0.134...v1.0.135)

Updates `tokio` from 1.42.0 to 1.43.0
- [Release notes](https://github.com/tokio-rs/tokio/releases)
- [Commits](https://github.com/tokio-rs/tokio/compare/tokio-1.42.0...tokio-1.43.0)

Updates `rustix` from 0.38.42 to 0.38.43
- [Release notes](https://github.com/bytecodealliance/rustix/releases)
- [Changelog](https://github.com/bytecodealliance/rustix/blob/main/CHANGELOG.md)
- [Commits](https://github.com/bytecodealliance/rustix/compare/v0.38.42...v0.38.43)

Updates `cc` from 1.2.7 to 1.2.9
- [Release notes](https://github.com/rust-lang/cc-rs/releases)
- [Changelog](https://github.com/rust-lang/cc-rs/blob/main/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/cc-rs/compare/cc-v1.2.7...cc-v1.2.9)

---
updated-dependencies:
- dependency-name: thiserror
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: rust-dependencies
- dependency-name: bitflags
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: rust-dependencies
- dependency-name: serde_json
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: rust-dependencies
- dependency-name: tokio
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: rust-dependencies
- dependency-name: rustix
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: rust-dependencies
- dependency-name: cc
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: rust-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-01-13 23:09:54 +00:00
TornaxO7
60bff8feee
Fix open_{below, above} behaviour with multiple cursors (#12465) 2025-01-13 08:14:30 -06:00
Taylor C. Richberger
134aebf8cc
add rockspec to lua file types (#12516) 2025-01-13 07:45:38 -06:00
Álan Crístoffer
367ccc1c64
Fix a bug in matlab indentation and updates the grammar commit hash to latest (#12518) 2025-01-13 07:43:02 -06:00
Nikita Revenco
e01775a667
fix: unable to detect Color completion item hex code for some LSPs (#12501)
Co-authored-by: Nikita Revenco <154856872+NikitaRevenco@users.noreply.github.com>
2025-01-12 09:40:19 -06:00
Michael Davis
0f2ce303c5
Add directory name to :cd errors
For example `:cd README.md` would say "Not a directory" but would not
print the directory name. Now the error message includes some context
about the operation and requested directory.
2025-01-11 20:39:44 -05:00
meator
b05971f178
Add .clang-tidy highlighting (#12498) 2025-01-11 15:12:46 -06:00
Nikita Revenco
a539199666
feat(highlights): add more built-in functions for ecma, rust and haskell (#12488)
Co-authored-by: Nikita Revenco <154856872+NikitaRevenco@users.noreply.github.com>
2025-01-11 14:59:03 -06:00
Kirawi
e440e54e79
pin to ubuntu-22.04 for releases (#12464) 2025-01-11 10:52:13 -06:00
Nikita Revenco
8f5f818c88
fix(highlights): recognize ! as the never type (#12485)
Co-authored-by: Nikita Revenco <154856872+NikitaRevenco@users.noreply.github.com>
2025-01-11 10:49:39 -06:00
Michael Davis
b26903cd13
Add comment tokens for JSONC
Fixes #12491
2025-01-11 08:42:17 -05:00
Evan Richter
9721144e03
language support: CodeQL (#12470) 2025-01-10 09:03:04 -06:00
Rob Gonnella
a83c23bb03
Run formatter from Document directory (#12315)
Co-authored-by: Rob Gonnella <rob.gonnella@papayapay.com>
2025-01-08 12:36:40 -06:00
rhogenson
931dd9c1dc
Fix a typo in join_selections (#12452)
Co-authored-by: Rose Hogenson <rosehogenson@posteo.net>
2025-01-08 08:42:41 -06:00
Michael Davis
917174e546
Fix blank buffer picker preview on doc with no views
Reproduction:

* `hx`
* Open any file in a split (`<space>f` and choose anything with `<C-v>`)
* Close the split with `<C-w>q`
* Open up the buffer picker and look the file you opened previously

Previously the preview was empty in this case because the Document's
`selections` hashmap was empty and we returned early, giving `None`
instead of a FileLocation. Instead when the Document is not currently
open in any view we can show the document but with no range highlighted.
2025-01-07 15:11:15 -05:00
Michael Davis
a0bd39d40e
book: Document editor.lsp.display-progress-messages config option
Connects #5535
2025-01-07 09:17:59 -05:00
dependabot[bot]
e698b20245
build(deps): bump the rust-dependencies group with 3 updates (#12437) 2025-01-06 19:34:21 -05:00
Michael Davis
5616f1d66d
changelog: Add missing breaking change for display-messages config 2025-01-06 14:18:26 -05:00
Michael Davis
217818681e
Revert "refactor(shellwords)!: change arg handling strategy (#11149)"
This reverts commit 64b38d1a28.
2025-01-06 12:39:53 -05:00
Michael Davis
03f35af9c1
Format '--version' calendar version as 'YY.0M'
We use calendar versioning which isn't supported by Cargo, so we need
to add an extra leading zero to the month for releases between January
and September to match our usual 'YY.0M' formatting.

Closes #12414
2025-01-06 10:34:25 -05:00
uncenter
6c9619d094
Improve markdown heading highlights (#12417) 2025-01-05 16:35:09 -06:00
uncenter
e856dde738
Use @attribute scope for JSX attributes (#12416) 2025-01-05 16:33:08 -06:00
RoloEdits
f80ae997f2
perf: cache Documents relative path (#12385) 2025-01-05 16:29:16 -06:00
RoloEdits
64b38d1a28
refactor(shellwords)!: change arg handling strategy (#11149) 2025-01-05 12:18:30 -06:00
Seigo Mori
377e36908a
Add cursorline color to iceberg theme (#12404) 2025-01-05 10:58:31 -06:00
Nikita Revenco
fa4aa0fb42
docs: catppuccin themes should not be directly edited here (#12400)
Co-authored-by: uncenter <47499684+uncenter@users.noreply.github.com>
2025-01-05 10:55:28 -06:00
Nikita Revenco
2b8f8df1af
feat: correct Swift highlights (#12409)
- Adds injections for the `comment` language
- Correct highlight of the `nil` value. Same highlight as `null` in javascript, java and others
- Recognize `<` and `>` as punctuation, used in generics (same color as the syntax used in other languages)
- `protocol` function methods are recognized
- When accessing object properties, like `hello.world`, the `world` is properly recognized as being a member
- Recognize the `\` as an operator
2025-01-05 10:54:45 -06:00
Nikita Revenco
eed052e86b
feat: highlight : as a delimiter in Rust (#12408) 2025-01-05 10:51:33 -06:00
Erasin Wang
0654a1f058
Update onelight theme (#12399) 2025-01-05 10:27:38 -06:00
RoloEdits
353176082e
doc: generate lang-support.md for teal (#12402) 2025-01-05 01:11:10 +09:00
André L. Alvares
b47b946c47
Fix Teal LSP name. (#12395) 2025-01-04 11:49:44 +09:00
Will Faught
827deba74c
Change Cwd to Cmd (#12396) 2025-01-03 20:23:21 -05:00
Michael Davis
dabfb6ceea
Add changelog notes for 25.01 (#12347)
Some checks failed
Github Pages / deploy (push) Has been cancelled
Release / Fetch Grammars (push) Has been cancelled
Release / Dist (push) Has been cancelled
Release / Publish (push) Has been cancelled
2025-01-03 13:50:51 -06:00
Niza Toshpulatov
9dcc1f06b0
Add dim modifier to diagnostic.unnecessary in catppuccin_mocha.toml (#12391) 2025-01-03 08:13:44 -06:00
David Crespo
eaa7c8e9f6
docs: fix typo in usage.md (#12390) 2025-01-03 08:11:37 -06:00
Michael Davis
4817bfa003
minor: fix syntax tree pretty print test
This case needs to be adjusted for the escaping added in 38e8382b
2025-01-02 15:44:04 -05:00
Michael Davis
e0bccd2c58
tsq: Tune highlights to more closely match 24.07 release
The changes in #12148 were a bit radical - this restores some
customizations we had like using "label" for captures and not
highlighting the '#' or '?'/'!' parts differently. Also the highlighting
for predicates we do (not) support has been restored.
2025-01-02 15:37:18 -05:00
Michael Davis
38e8382b01
Escape double quotes for anonymous nodes in :tree-sitter-subtree
If the anonymous node contained a double quote it would throw off the
highlighting.
2025-01-02 15:33:48 -05:00
Darshan Kumawat
c9cc14728f
Update hyprland langugauge file type. (#12384) 2025-01-02 08:39:02 -06:00
Sebastian Dörner
b1759f998d
Migrate to an improved proto grammar (#12225) 2024-12-31 13:41:53 -06:00
Samuel Selleck
4a59f68a0d
LSP Client: Accept floats with trailing zeros as valid JSONRPC IDs (#12376) 2024-12-31 12:45:47 -06:00
dependabot[bot]
2b4a77b9bf
build(deps): bump the rust-dependencies group with 2 updates (#12371)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-12-31 09:12:48 -06:00
Egor Afanasin
41763b4851
Sunset theme: add ui.text.directory (#12372) 2024-12-31 09:11:33 -06:00
Matthew Taylor
bbeb99dc40
fix: Bump vento grammar revision (#12368) 2024-12-30 14:58:16 -06:00
Jean Abou Samra
9cc056e755
fix (hx --health): Don't print headers in white (#12355) 2024-12-29 10:38:28 -06:00
Karim Abou Zeid
6d07ae4f07
add uv shebang for python (#12360) 2024-12-28 20:44:26 -06:00
Michael Davis
073efe48f9
minor: Setup default keymap directly in xtask docgen
Instantiating EditorView is a lot of machinery which is unnecessary:
the default keymap is exposed through the `default` function in the
keymap module.
2024-12-28 21:42:09 -05:00
Michael Davis
127567df8e
docs: Fix dead links to tree-sitter docs
tree-sitter now uses mdbook for their docs and the switch adds a chapter
number to each section, so each link to the website needs an update.
2024-12-28 09:05:48 -05:00
{{david|odenwald}}
4b288e4de7
languages: add block comment tokens for jinja and nunjucks (#12348)
Co-authored-by: David Odenwald <daod@solute.de>
2024-12-28 07:44:41 -06:00
RoloEdits
19c91dfb80
fix: string literals with format args not in format! (#12354) 2024-12-28 07:44:16 -06:00
uncenter
a5a7cff311
Improve tsq/tree-sitter-query language support (#12148) 2024-12-27 08:57:53 -06:00
0rphee
7b9b9329b9
Make git revision available for nix flake builds (#12331) 2024-12-25 11:26:53 -06:00
Michael Davis
c262fe41ab
Consistently replace line-endings in paste/replace commands
Previously we replaced line-endings in pasted text to the document
line-ending for some values in paste commands. We missed the `repeat`
values in paste though and didn't do any replacement in the replace
command.

Along with this change I've refactored the replace command to avoid
intermediary collections. We previously eagerly collected the values
from the input register as a `Vec<String>` but we can avoid both of
those conversions and only allocate for the conversion to a `Tendril`.
We can also switch from `str::repeat` to a manual implementation to
avoid the intermediary conversion to a String - this avoids an extra
allocation in the common case (i.e. no count).

Fixes #12329
2024-12-25 11:38:44 -05:00
dependabot[bot]
a074129f9c
build(deps): bump the rust-dependencies group with 7 updates (#12327)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Michael Davis <mcarsondavis@gmail.com>
2024-12-25 10:05:50 -06:00
Bryce Berger
eda96cc285
Add diff injections for jjdescription tree-sitter (#12305) 2024-12-25 09:35:34 -06:00
DiegoDev
c63616b421
docs: Mention sudo in macports installation snippet (#12336) 2024-12-25 09:23:56 -06:00
Nikita Revenco
faf0eee3c9
feat: Highlight = and ; in .proto files (#12339) 2024-12-25 09:06:14 -06:00
Clay
a2b241eb08
chore: update tree-sitter-heex to version 0.7.0 (#12334) 2024-12-25 10:31:57 +09:00
Nikita Revenco
0fd4a4aae2
feat: Nginx support (#12309)
Co-authored-by: Denis Gruzdev <codingjerk@gmail.com>
2024-12-24 13:22:29 -06:00
Pedro Fedricci
9e77c44b38
feat(themes): add ui.text.directory to nightfox (#12328) 2024-12-23 20:26:16 -06:00
Michael Davis
ea17b9edb7
release docs: Fix git push command for the release tag
Git can be configured to push tags with `push.followTags` but this is
not the default. Pushing the tag explicitly with `git push <remote>
<tag>` is generally considered safer anyways since it only attempts to
push the desired tag.
2024-12-22 09:48:32 -05:00
Michael Davis
4f63a46e14
minor: Remove redundant condition in shell pipe trimming
`output.ends_with('\n')` implies `!output.is_empty()`

Connects #11183
2024-12-22 09:42:37 -05:00
Nikita Revenco
ac4c017165
feat: autohelp for delete, replace and add surrounds (#12262) 2024-12-22 08:17:44 -06:00
Maurice Hieronymus
b946b21b01
fix: Respect workspace-lsp-roots on doc opening (#12223)
When a new language server is started, find_lsp_workspace is called
with LanguageConfiguration::workspace_lsp_roots as the root_dirs.

This behavior is different when a new document is opened.
find_lsp_workspace is called with editor::Config::workspace_lsp_roots
which is never set.

This leads to a bug where workspace-lsp-roots is not respected when
opening a new document. This commit fixes this bug.
2024-12-22 08:08:41 -06:00
Nikita Revenco
ba6e6dc3dd
Colors for items in the completion menu (#12299) 2024-12-20 10:16:15 -06:00
cornishon
a91263d604
Odin textobjects (#12302)
Co-authored-by: Adam Zadrożny <zadroznyadam@protonmail.com>
2024-12-20 09:59:28 -06:00
Ian Hobson
06d0f33c94
Add Koto language support (#12307) 2024-12-20 09:56:13 -06:00
Eduardo Rittner Coelho
eaff0c3cd6
Document diagnostic severity levels (#12306) 2024-12-20 09:47:06 -06:00
uncenter
1e9412269a
Sync Catppuccin theme changes (#12304) 2024-12-20 09:43:45 -06:00
Nikita Revenco
355e381626
feat: use ui.text.directory for path completion item if its a folder (#12295) 2024-12-19 14:36:54 -06:00
Tobias Hunger
cbc06d1f15
chore: Update slint tree-sitter grammar to version 1.9 (#12297) 2024-12-19 10:16:12 -06:00
Eduardo Rittner Coelho
9e4da4b950
Show parser availability in --health [LANG] (#12228) 2024-12-18 11:21:58 -06:00
Christian Schneider
13e5a2ee5a
Outdent array literals for php [] (#12286)
Co-authored-by: Christian Schneider <schneider@search.ch>
2024-12-18 08:52:20 -06:00
David Else
0134bb7063
Update dark_plus theme for inactive text and improve jump label (#12289) 2024-12-18 08:32:41 -06:00
Peter Ingram
ec65cc4913
Adds colored directories to everforest themes (#12287)
Co-authored-by: Peter Ingram <p.ingram@mrx.technology>
2024-12-18 08:31:40 -06:00
Nikita Revenco
91a5d407da
Allow theming directory prompt completions (#12205) 2024-12-17 21:13:42 -06:00
Michael Davis
6eb186eb7b
helix-lsp-types: use bitflags::bitflags rather than extern crate
This seems to be a historical artifact in `lsp_types` - we can use a
regular `use` statement to pull in the `bitflags!` macro rather than
an external crate definition. This fixes rust-analyzer's ability to find
the macro at least on rust-analyzer 2024-02-26.
2024-12-17 15:42:36 -05:00
Michael Davis
1980bd5992
helix-lsp-types: Prefer crate::Url to url::Url
This is a cosmetic change to replace all direct `use`s of the `url::Url`
type in the `helix-lsp-types` crate with `use crate::Url;`. The types
are the same type currently: this refactor will make a future
replacement of the Url type less noisy.

Connects https://github.com/helix-editor/helix/pull/11889
2024-12-17 15:42:28 -05:00
Tim Sampson
cc3b77b584
dockerfile: bump tree-sitter grammar to gain support for heredocs (#12230) 2024-12-17 13:26:49 -06:00
Christian Schneider
fcded6ce1e
Trim trailing colons from paths to allow copy/pasting git grep -n output (#9963)
Co-authored-by: Christian Schneider <schneider@search.ch>
2024-12-17 13:02:06 -06:00
Pascal Kuthe
1badd9e434
implement snippet tabstop support 2024-12-17 13:34:40 -05:00
Pascal Kuthe
66fb1e67c0
add fallback onNextKey
adds a variant of on_next_key callbacks that are only called when no other
mapping matches a key
2024-12-17 13:34:40 -05:00
Pascal Kuthe
609c29bf7e
add DocumentFocusLost event 2024-12-17 13:34:40 -05:00
Pascal Kuthe
5537e68b5e
add changes and ghost_transaction to DocumentDidChange events 2024-12-17 13:34:40 -05:00
Pascal Kuthe
c8c0d04168
add snippet system to helix core 2024-12-17 13:34:39 -05:00
Pascal Kuthe
db959274d4
Add range type to helix stdx 2024-12-17 13:34:39 -05:00
dependabot[bot]
312c64f0c2
build(deps): bump the rust-dependencies group with 10 updates (#12277)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-12-16 18:48:13 -06:00
André Sá
67535804a5
Fix build from source with Spade tree-sitter grammar (#12276) 2024-12-16 14:44:28 -06:00
Michael Davis
bae6a58c3c
Add block-comment-tokens configuration for Java
Ref https://github.com/helix-editor/helix/pull/12266#issuecomment-2546370787
2024-12-16 14:02:35 -05:00
Integral
250d9fa8fe
Avoid allocating the --help message (#12243) 2024-12-16 11:16:48 -06:00
Aaalibaba
3b36cf1a15
Expand tildes in :read command (#12271) 2024-12-16 11:10:35 -06:00
Nikita Revenco
99fdbce566
docs: remove mention that - requires special handling (#12250) 2024-12-16 10:01:14 -06:00
David Else
9b14750e56
Add ltex-ls-plus language server (#12251) 2024-12-16 09:37:49 -06:00
TornaxO7
4e5b0644a2
language: add comment token for java files (#12266) 2024-12-16 09:24:04 -06:00
Takumi Matsuura
e14c346ee7
Fix panic in kill_to_end_of_line when handling multibyte characters (#12237) 2024-12-13 14:04:52 -06:00
RoloEdits
617f538d41
feat(highlights): add COMPLIANCE to error (#12244) 2024-12-13 13:26:08 -06:00
Yuki Kobayashi
ce133a2889
languages(v): use vlang/v-analyzer instead of v-analyzer/v-analyzer (#12236)
* use vlang/v-analyzer instead of v-analyzer/v-analyzer

* revert rev, because CI failed (couldn't repro working query-check locally, so not sure if this will work)
2024-12-13 12:09:24 +09:00
TornaxO7
89a7cde2f0
Fix continuing comment token for first line (#12215) 2024-12-10 13:24:34 -06:00
TornaxO7
51ac3e05e0
doc: fix default value in doc for continue-comments (#12235) 2024-12-10 13:19:31 -06:00
TornaxO7
5005c14e99
Add config option for continue commenting (#12213)
Co-authored-by: Michael Davis <mcarsondavis@gmail.com>
2024-12-09 17:31:41 -06:00
Michael Davis
2f74530328
helix-lsp-types: Remove Cargo.lock
This lockfile is unused since this crate was added to the workspace and
can be removed.

Closes #12227
2024-12-09 17:14:38 -05:00
Tshepang Mbambo
a1a5faebef
typo (#12224) 2024-12-09 12:23:30 -06:00
Nikita Revenco
db1d84256f
fix: report correct amount of files opened and improved error message when Helix can't parse directory as file (#12199)
* feat: improve information on the amount of files loaded

* refactor: naming consitency Doc and not Buf

* fix: correct name of method

* chore: appease clippy

* feat: more human error information when Helix cannot start

* refatcor: use if guard on match arm
2024-12-08 20:14:29 +09:00
Michael Davis
271c32f2e6
Support bindings with the Super (Cmd/Win/Meta) modifier (#6592)
Terminals which support the enhanced keyboard protocol send events for
keys pressed with the Super modifier (Windows/Linux key or the Command
key). The only changes that are needed to support this in Helix are:

* Mapping the modifier from crossterm's KeyModifiers to Helix's
  KeyModifiers.
* Representing and parsing the modifier from the KeyEvent text
  representation.
* Documenting the ability to remap it.

When writing keybindings, use 'Meta-', 'Cmd-' or 'Win-' which are all
synonymous. For example:

    [keys.normal]
    Cmd-s = ":write"

will trigger for the Windows or Linux keys and the Command key plus 's'.
2024-12-08 12:35:14 +09:00
Nikita Revenco
f44be74435 feat: merge-with-commandline config option 2024-12-06 15:14:25 +00:00
Nikita Revenco
13d2cd1293 feat: option to Merge statusline and Command line into 1 line 2024-12-06 14:51:13 +00:00
Tomas Zemanovic
fc9968bd4b
fix: allow to parse "-" as a key code (#12191)
Co-authored-by: Michael Davis <mcarsondavis@gmail.com>
2024-12-06 08:46:38 -06:00
Michael Davis
28953ef40f
Simplify change_current_directory and remove extra allocs 2024-12-05 18:50:31 -05:00
Nikita Revenco
93deb1f6ae
feat: :cd - changes to the previous working directory (#12194)
Co-authored-by: Michael Davis <mcarsondavis@gmail.com>
2024-12-05 17:40:37 -06:00
Allemand Instable
a6f80c5bd9
Fix mojo LSP configuration to conform to magic introduction (#12195)
Co-authored-by: Michael Davis <mcarsondavis@gmail.com>
2024-12-05 16:54:10 -06:00
David Crespo
cd1f6e8239
Add static commands to documentation (#11950)
Co-authored-by: Adam Perkowski <adas1per@protonmail.com>
2024-12-05 11:13:00 -06:00
Michael Davis
fd3e889927 Add integration tests for line comment continuation 2024-12-05 20:53:53 +09:00
Michael Davis
1e6fe00001 Trim all trailing whitespace on insert_newline 2024-12-05 20:53:53 +09:00
Ian Hobson
4c8175ca04 Draw each message line separately in draw_eol_diagnostic
`set_string_truncated` renders the entire string while ignoring
newlines, so if the diagnostic's message contains multiple lines it
produces messages like 'first linesecond line'.

To avoid these run-ons, this commit renders each line separately,
inserting double spaces for disambiguation.
2024-12-04 18:23:30 -06:00
Ian Hobson
715a13b2d3 Remove an incorrect comment
This was copied from the function above (set_style). I don't know enough
about the function to suggest an alternative.
2024-12-04 18:23:30 -06:00
Kieran Moy
e670970dd8
Change default comment token to # for unrecognized files (#12080)
* Change the default comment token

* update test

* keep the original
2024-12-05 01:11:39 +01:00
Nikita Revenco
565bfbba25
feat: :cd with no args changes to home directory (#12042) 2024-12-04 18:09:33 -06:00
rojebd
5bdf14110f
add Vintage theme (#9361)
Co-authored-by: Michael Davis <mcarsondavis@gmail.com>
2024-12-04 17:12:21 -06:00
rojebd
8a7006dd57
update voxed theme (#9328) 2024-12-04 17:03:45 -06:00
barsoosayque
cd972ae77d
Add support for Teal language (#12081)
Co-authored-by: Michael Davis <mcarsondavis@gmail.com>
2024-12-04 14:59:11 -06:00
Michael Davis
07e7e7534d
theme: Include key names in style parsing warnings
This should make it easier to figure out why the theme-check CI job
fails. Previously the message didn't include the failing key so you
were left searching or guessing where the error occurred.
2024-12-04 10:24:42 -05:00
Tomasz Zurkowski
7a2afdc080
Show an error when formatter is not available (#12183) 2024-12-04 08:27:54 -06:00
Michael Davis
085c4fe4c8
docs: Eliminate improper use of "LSP" term
Sometimes we used "LSP" to mean "language server". This change
eliminates the improper "LSP" usage.

Ref https://github.com/helix-editor/helix/pull/12183#discussion_r1868436105
2024-12-03 18:29:44 -05:00
Michael Davis
fa68bac391
contributing: Add steps for updating the MSRV 2024-12-03 10:07:59 -05:00
Michael Davis
403aaa8cf3
CI: Use an env var for MSRV
This just reduces duplication in the build workflow - no functional
change - to make updating the MSRV easier in the future.
2024-12-03 10:07:59 -05:00
dependabot[bot]
cf81e15315
build(deps): bump the rust-dependencies group with 3 updates (#12181)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-12-02 18:27:49 -06:00
Nikita Revenco
c0bfdd7bfe
fix: catppuccin theme colors for checked and unchecked lists (#12167) 2024-12-02 12:37:01 -06:00
Michael Davis
ec1628c07f
registers: Use saved values for clipboard providers which can't read
This fixes reading from the clipboard when using the termcode provider.
Reading isn't implemented for the termcode provider so `get_contents`
returns `ClipboardError::ReadingNotSupported`. `read_from_clipboard`
needs to recognize this case and use the saved values instead of
emitting an error log and returning nothing.

Follow-up of #10839
Also see #12142
2024-12-02 10:16:17 -05:00
uncenter
548f04fe26
Add support for the Vento template language (#12147)
Co-authored-by: Michael Davis <mcarsondavis@gmail.com>
2024-12-02 09:03:11 -06:00
xiabo
44c1d51d8c
add bufferline highlighting for flexoki themes (#12146) 2024-12-02 09:01:00 -06:00
Poliorcetics
83fe23ce75
just: bump grammar support to Just 1.37.0 (#12141) 2024-12-02 08:45:46 -06:00
Jaakko Paju
b1bdbc6789
Fix language configuration for .conf files (#12156) 2024-12-02 08:27:08 -06:00
Michael Davis
191b0f08a9
Remove unnecessary clippy allow for old false positive
The clippy version after the recent MSRV bump no longer emits
`redundant_clone` warnings for these lines. We allowed these previously
since they were emitted as false positives.
2024-12-02 09:23:42 -05:00
RoloEdits
5ba97ba41e
fix(clippy): clippy 1.83 lints (#12150) 2024-12-02 08:23:32 -06:00
Poliorcetics
e1d1a5c5a1
cleanup: remove pr.md introduced in #11448 (#12140) 2024-11-27 13:24:57 -06:00
Poliorcetics
0f4729289b
fix: Remove leftover debug println! (#12138)
Introduced in dc941d6d24
2024-11-27 20:05:18 +09:00
Milan Vaško
7676106960
Search selection with word boundary detection (#12126)
Co-authored-by: Michael Davis <mcarsondavis@gmail.com>
2024-11-26 13:30:53 -06:00
Ronan Desplanques
95e6c11ebc
Improve language support for Ada (#12131)
Co-authored-by: Michael Davis <mcarsondavis@gmail.com>
2024-11-26 12:43:34 -06:00
dependabot[bot]
80709cee61
build(deps): bump the rust-dependencies group with 4 updates (#12129)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-11-25 18:06:54 -06:00
Michael Davis
dbd3b251d8
CI: Pin mdbook to 0.4.43
This should prevent future surprising changes to the site from newly
published mdbook versions with breaking changes. For example mdbook
0.4.41 introduced some changes that needed the `index.hbs` file to be
updated. See the parent commit.
2024-11-25 17:56:17 -05:00
Michael Davis
436855ad6f
book: Copy over index.hbs from mdbook 0.4.43
0.4.41 introduced some breaking changes for those using custom themes
so we need to re-vendor the file. This file is taken from mdbook
0.4.43:

    mdbook init --theme

In a tmp directory, and then a reset of the line that adds Colibri as
a selectable theme.
2024-11-25 17:53:13 -05:00
RoloEdits
cbbeca6c52
fix(clippy): suppress unused lint on windows (#12107) 2024-11-22 08:10:11 -06:00
Ryan Roden-Corrent
46ffec3fd4
Add WORKSPACE.bzlmod to starlark file-types (#12103) 2024-11-22 07:57:20 -06:00
Michael Davis
fbe216e11c
CI: Match rust-toolchain action to MSRV version 2024-11-22 08:39:37 -05:00
Rolo
f07c1cc9f5 chore(msrv): bump MSRV from 1.70 to 1.76 2024-11-22 01:17:08 -08:00
Philipp Mildenberger
dc941d6d24
Add support for path completion (#2608)
Co-authored-by: Michael Davis <mcarsondavis@gmail.com>
Co-authored-by: Pascal Kuthe <pascalkuthe@pm.me>
2024-11-21 21:12:36 -06:00
Lens0021 / Leslie
f305c7299d
Add support for Amber-lang (#12021)
Co-authored-by: Phoenix Himself <pkaras.it@gmail.com>
Co-authored-by: Michael Davis <mcarsondavis@gmail.com>
2024-11-21 10:09:42 -06:00
Valentin B.
9e0d2d0a19
chore(solidity): add highlight queries (#12102)
Add highlights for `hex` and `unicode` string prefixes and YUL booleans
2024-11-21 07:58:14 -06:00
Niklas Gruhn
b8313da5a8
Add language support for Quint (#11898)
Co-authored-by: Michael Davis <mcarsondavis@gmail.com>
2024-11-20 17:56:24 -06:00
Egor Afanasin
32ff0fce4a
Add Sunset theme (#12093) 2024-11-20 17:26:44 -06:00
yehor
9e171e7d1d
Add default-yank-register option (#11430)
Co-authored-by: Michael Davis <mcarsondavis@gmail.com>
2024-11-20 17:24:55 -06:00
Teemu Säilynoja
b92e8abfb3
Update Snakemake language config (#11936) 2024-11-20 17:20:51 -06:00
PORTALSURFER
8807dbfc40
Update current hex themes, add a new hex theme (#10849) 2024-11-20 17:20:04 -06:00
Arthur
15b478d433
hyprlang: add hyprls language server (#11056)
Co-authored-by: Michael Davis <mcarsondavis@gmail.com>
2024-11-20 17:16:49 -06:00
Michael Davis
467fad51b1
clipboard: Sway builtin provider yank and paste commands
The configuration here is not super intuitive - in order to yank from
a clipboard provider we want it to paste the clipboard and in order to
paste to the clipboard we want it to yank the contents we pass.

This swaps the programs for yank and paste to align with that.

Ref #10839
2024-11-20 18:10:46 -05:00
Michael Davis
b855cd0cda
clipboard: Fix builtin_names for x-clip, x-sel, win-32-yank
Copy/paste error from #10839
2024-11-20 18:10:31 -05:00
spx01
6101b3a7a3
fix: simplify text reflowing strategy to improve language compatibility (#12048) 2024-11-20 16:40:43 -06:00
Yerlan
887bbbc375
Adding NestedText language support (#11987)
Co-authored-by: Yerlan Sergaziyev <yerlan.sergaziyev@rms-consult.de>
2024-11-20 16:39:34 -06:00
Ricardo Fernández Serrata
7ee66c0658
suggest *.desktop for AppImage (#10823)
Co-authored-by: Michael Davis <mcarsondavis@gmail.com>
2024-11-20 16:39:06 -06:00
Heath Stewart
843c058f0b
Use latest tree-sitter-bicep, support bicepparams (#11525)
Co-authored-by: Michael Davis <mcarsondavis@gmail.com>
2024-11-20 16:38:15 -06:00
Travis Harmon
ed7e5bd8dc
Use bold statusline for mode indicator in onedarker theme (#11958) 2024-11-20 16:36:59 -06:00
Krishan
b501a300e9
Update Zig's comment tokens (#12049) 2024-11-20 16:36:28 -06:00
Frans Skarman
310bc04f23
Add spade support (#11448)
Co-authored-by: Michael Davis <mcarsondavis@gmail.com>
2024-11-20 16:35:49 -06:00
Thomas Aarholt
2f6a113fbe
Docs Key-Remapping: Move Commands to top as introduction (#11953) 2024-11-20 16:35:06 -06:00
Alexis Mousset
8c6ca3c0fc
Update modus themes to 4.6.0 (#11949) 2024-11-20 16:30:35 -06:00
Michael Davis
b97c745631
docs: Improve display of inline-diagnostics config sample
Closes https://github.com/helix-editor/helix/issues/11591
2024-11-20 17:28:44 -05:00
Adam Chalmers
d8e2aab201
Document how to use modifier keys in macro remaps (#11930) 2024-11-20 16:26:46 -06:00
Michael Davis
188f701f50
docs: Remove invalid '--path helix-term' from build instructions
Fixes https://github.com/helix-editor/helix/issues/11557
2024-11-20 17:25:40 -05:00
Robert Edmonds
83f1b98e80
languages: Add ssh_config.d/*.conf as a glob for sshclientconfig (#11947) 2024-11-20 16:20:59 -06:00
Keir Lawson
4dc46f9472
Make Spacebones theme picker selection more legible (#12064) 2024-11-20 16:20:21 -06:00
Javier Goday
4d0b7e57b1
Set tags color in monokai theme (#11917) 2024-11-20 16:19:58 -06:00
Yuki Kobayashi
548fd57489
fix(languages): treat tsconfig.json as jsonc (#12031) 2024-11-20 16:19:23 -06:00
blt-r
8ed8d52e9d
Treat .clangd and .clang-format as YAML (#12032) 2024-11-20 16:19:06 -06:00
Elizabeth
eeb5b7bbdd fix: added .DS_Store to gitignore 2024-11-20 16:18:42 -06:00
Elizabeth
d95b21ddd3 fix(swift): disabled auto-format & added .swift-format highlighting 2024-11-20 16:18:42 -06:00
zetashift
56bb366f7e
Update Unison grammar and queries (#12039) 2024-11-20 16:18:18 -06:00
Aidan Gauland
06d5b88dee
feat(languages): add .livemd Markdown extension (#12034) 2024-11-20 16:17:40 -06:00
Eamon Caton
e2d79c1891
Add Carbonfox theme (#11558) 2024-11-20 16:16:05 -06:00
mesmere
5b3e0b64f0
Add new "Eiffel" theme (#11679) 2024-11-20 16:15:38 -06:00
Michael McClintock
07262f5170
Add yo themes (#11703) 2024-11-20 16:15:13 -06:00
pacien
6ec510d58f
queries/nix: add injections for nim writers (#11837) 2024-11-20 16:14:56 -06:00
Veesh Goldman
4d3612125b
chore: update perl + pod parsers (#11848) 2024-11-20 16:14:39 -06:00
Sebastian Neubauer
f9ac1f1ff1
Bump tree-sitter llvm grammars (#11851) 2024-11-20 16:14:15 -06:00
Grenier Célestin
2dbecd3c80
Bump tree-sitter-nasm (#11795) 2024-11-20 16:13:27 -06:00
eh
aa10b1fd11
Theme: Seoul256 dark & light (#11466) 2024-11-20 16:10:46 -06:00
Freddie Gilbraith
07968880e6
update to newest rescript treesitter library and queries (#11165)
Co-authored-by: Michael Davis <mcarsondavis@gmail.com>
Co-authored-by: Freddie Ridell <freddie@Gilbraith-MacBook-Pro.local>
2024-11-20 16:08:38 -06:00
Raghu Saxena
4e2faa0be9
Add :config-reload to configuration docs (#11086)
Co-authored-by: Michael Davis <mcarsondavis@gmail.com>
2024-11-20 16:08:06 -06:00
AbrA-K
0fca0d057e
Theme: add adwaita-light theme (#10869)
Co-authored-by: Michael Davis <mcarsondavis@gmail.com>
Co-authored-by: abra <abra@KadaZen.fritz.box>
2024-11-20 16:07:31 -06:00
Alfie Richards
68ee87695b
Add clipboard provider configuration (#10839) 2024-11-20 16:06:23 -06:00
RoloEdits
b6e555a2ed
feat(highlights): add INVARIANT to error tag (#12094) 2024-11-20 16:04:43 -06:00
Jaakko Paju
48e15f77f7
Add package.json and (and tsconfig.json) for TS/JS language config roots (#10652)
* Add package.json and tsconfig.json for TS/JS language config roots

* Add root to Javascript
2024-11-20 16:03:46 -06:00
Michael Davis
59b020ec91
Save an undo checkpoint before paste in insert mode (#8121) 2024-11-20 16:02:10 -06:00
Mark Stosberg
239262e094
docs: Document what directory file_picker opens from (#10816) 2024-11-20 15:58:50 -06:00
Eduardo Rittner Coelho
23600e3ecb
Add correct source file for TypableCommandList (#11839)
Co-authored-by: Michael Davis <mcarsondavis@gmail.com>
2024-11-20 15:58:24 -06:00
Sam Kagan
287e412780
docs(keymap): add select_all_children (#11972)
Co-authored-by: Sam Kagan <skagan@nrao.edu>
2024-11-20 15:58:00 -06:00
stefanvi
bc18dc2c0c
Pluralize 'parenthesis' in the tutorial (#12015) 2024-11-20 15:57:42 -06:00
Sebastian Dörner
3fd7ca334e
Add support for textproto language. (#11874) 2024-11-20 15:57:30 -06:00
Oren Mittman
6373027c9e
chore: add "ui.virtual.jump-label" to serika-dark theme (#11911) 2024-11-20 15:57:15 -06:00
dependabot[bot]
f06f481ad9
build(deps): bump the rust-dependencies group with 5 updates (#12088)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-11-20 15:56:56 -06:00
dependabot[bot]
a219d5aabb
build(deps): bump unicode-general-category from 0.6.0 to 1.0.0 (#12089)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-11-20 11:41:07 -06:00
Michael Davis
d489c03c4f helix-term: Use workspace thiserror dep 2024-11-20 11:40:45 -06:00
dependabot[bot]
f621423e7d build(deps): bump thiserror from 1.0.64 to 2.0.3
Bumps [thiserror](https://github.com/dtolnay/thiserror) from 1.0.64 to 2.0.3.
- [Release notes](https://github.com/dtolnay/thiserror/releases)
- [Commits](https://github.com/dtolnay/thiserror/compare/1.0.64...2.0.3)

---
updated-dependencies:
- dependency-name: thiserror
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-11-20 11:40:45 -06:00
dependabot[bot]
35802cb025
build(deps): bump which from 6.0.3 to 7.0.0 (#12090)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-11-20 11:40:00 -06:00
Michael Davis
6cca98264f Re-vendor encoding_rs test data
This fixes a test (`helix_view::document::gb18030_decode`) since
encoding_rs updated its gb18030 encoding to match GB18030-2022 (was
GB18030-2005).

The newly vendored files update the license line and I've included the
referenced license in the `encoding/` directory.
2024-11-13 15:30:17 +09:00
Michael Davis
9806ca08b1 Fix breaking change in gix Tree::loop_entry_by_path 2024-11-13 15:30:17 +09:00
dependabot[bot]
b5d56e57a6 build(deps): bump the rust-dependencies group across 1 directory with 11 updates
Bumps the rust-dependencies group with 10 updates in the / directory:

| Package | From | To |
| --- | --- | --- |
| [regex](https://github.com/rust-lang/regex) | `1.11.0` | `1.11.1` |
| [url](https://github.com/servo/rust-url) | `2.5.2` | `2.5.3` |
| [serde](https://github.com/serde-rs/serde) | `1.0.210` | `1.0.215` |
| [encoding_rs](https://github.com/hsivonen/encoding_rs) | `0.8.34` | `0.8.35` |
| [anyhow](https://github.com/dtolnay/anyhow) | `1.0.90` | `1.0.93` |
| [tempfile](https://github.com/Stebalien/tempfile) | `3.13.0` | `3.14.0` |
| [tokio](https://github.com/tokio-rs/tokio) | `1.40.0` | `1.41.1` |
| [libc](https://github.com/rust-lang/libc) | `0.2.161` | `0.2.162` |
| [cc](https://github.com/rust-lang/cc-rs) | `1.1.31` | `1.1.37` |
| [gix](https://github.com/GitoxideLabs/gitoxide) | `0.66.0` | `0.67.0` |



Updates `regex` from 1.11.0 to 1.11.1
- [Release notes](https://github.com/rust-lang/regex/releases)
- [Changelog](https://github.com/rust-lang/regex/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/regex/compare/1.11.0...1.11.1)

Updates `url` from 2.5.2 to 2.5.3
- [Release notes](https://github.com/servo/rust-url/releases)
- [Commits](https://github.com/servo/rust-url/compare/v2.5.2...v2.5.3)

Updates `serde` from 1.0.210 to 1.0.215
- [Release notes](https://github.com/serde-rs/serde/releases)
- [Commits](https://github.com/serde-rs/serde/compare/v1.0.210...v1.0.215)

Updates `encoding_rs` from 0.8.34 to 0.8.35
- [Commits](https://github.com/hsivonen/encoding_rs/compare/v0.8.34...v0.8.35)

Updates `anyhow` from 1.0.90 to 1.0.93
- [Release notes](https://github.com/dtolnay/anyhow/releases)
- [Commits](https://github.com/dtolnay/anyhow/compare/1.0.90...1.0.93)

Updates `tempfile` from 3.13.0 to 3.14.0
- [Changelog](https://github.com/Stebalien/tempfile/blob/master/CHANGELOG.md)
- [Commits](https://github.com/Stebalien/tempfile/compare/v3.13.0...v3.14.0)

Updates `tokio` from 1.40.0 to 1.41.1
- [Release notes](https://github.com/tokio-rs/tokio/releases)
- [Commits](https://github.com/tokio-rs/tokio/compare/tokio-1.40.0...tokio-1.41.1)

Updates `libc` from 0.2.161 to 0.2.162
- [Release notes](https://github.com/rust-lang/libc/releases)
- [Changelog](https://github.com/rust-lang/libc/blob/0.2.162/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/libc/compare/0.2.161...0.2.162)

Updates `rustix` from 0.38.37 to 0.38.40
- [Release notes](https://github.com/bytecodealliance/rustix/releases)
- [Changelog](https://github.com/bytecodealliance/rustix/blob/main/CHANGELOG.md)
- [Commits](https://github.com/bytecodealliance/rustix/compare/v0.38.37...v0.38.40)

Updates `cc` from 1.1.31 to 1.1.37
- [Release notes](https://github.com/rust-lang/cc-rs/releases)
- [Changelog](https://github.com/rust-lang/cc-rs/blob/main/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/cc-rs/compare/cc-v1.1.31...cc-v1.1.37)

Updates `gix` from 0.66.0 to 0.67.0
- [Release notes](https://github.com/GitoxideLabs/gitoxide/releases)
- [Changelog](https://github.com/GitoxideLabs/gitoxide/blob/main/CHANGELOG.md)
- [Commits](https://github.com/GitoxideLabs/gitoxide/compare/gix-v0.66.0...gix-v0.67.0)

---
updated-dependencies:
- dependency-name: regex
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: rust-dependencies
- dependency-name: url
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: rust-dependencies
- dependency-name: serde
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: rust-dependencies
- dependency-name: encoding_rs
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: rust-dependencies
- dependency-name: anyhow
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: rust-dependencies
- dependency-name: tempfile
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: rust-dependencies
- dependency-name: tokio
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: rust-dependencies
- dependency-name: libc
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: rust-dependencies
- dependency-name: rustix
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: rust-dependencies
- dependency-name: cc
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: rust-dependencies
- dependency-name: gix
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: rust-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-11-13 15:30:17 +09:00
Elizabeth
10c3502a89
fix: removed explict default config file from swift-format (#12052) 2024-11-11 12:30:55 +09:00
Yuki Kobayashi
b53dafe326
Treat .prettierrc as YAML (#11997) 2024-11-05 16:09:49 +09:00
Poliorcetics
c0920e779d
just: update tree-sitter-grammar to support 1.36.0 (#11606)
Release notes:

- https://github.com/casey/just/releases/tag/1.35.0
- https://github.com/casey/just/releases/tag/1.36.0

Notably, this adds `[private]` attributes on modules, which the current version of the grammar cannot parse,
as well as unicode codepoint escape sequences.
2024-11-05 16:09:20 +09:00
Elliot Fontaine
38faf74feb
feat: Add support for cylc configuration files (#11830)
Co-authored-by: Michael Davis <mcarsondavis@gmail.com>
2024-10-30 13:39:24 -05:00
dependabot[bot]
101a74bf6e
build(deps): bump the rust-dependencies group with 6 updates (#11924)
Bumps the rust-dependencies group with 6 updates:

| Package | From | To |
| --- | --- | --- |
| [serde_json](https://github.com/serde-rs/json) | `1.0.128` | `1.0.132` |
| [anyhow](https://github.com/dtolnay/anyhow) | `1.0.89` | `1.0.90` |
| [libc](https://github.com/rust-lang/libc) | `0.2.159` | `0.2.161` |
| [fern](https://github.com/daboross/fern) | `0.6.2` | `0.7.0` |
| [pulldown-cmark](https://github.com/raphlinus/pulldown-cmark) | `0.12.1` | `0.12.2` |
| [cc](https://github.com/rust-lang/cc-rs) | `1.1.30` | `1.1.31` |


Updates `serde_json` from 1.0.128 to 1.0.132
- [Release notes](https://github.com/serde-rs/json/releases)
- [Commits](https://github.com/serde-rs/json/compare/1.0.128...1.0.132)

Updates `anyhow` from 1.0.89 to 1.0.90
- [Release notes](https://github.com/dtolnay/anyhow/releases)
- [Commits](https://github.com/dtolnay/anyhow/compare/1.0.89...1.0.90)

Updates `libc` from 0.2.159 to 0.2.161
- [Release notes](https://github.com/rust-lang/libc/releases)
- [Changelog](https://github.com/rust-lang/libc/blob/0.2.161/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/libc/compare/0.2.159...0.2.161)

Updates `fern` from 0.6.2 to 0.7.0
- [Release notes](https://github.com/daboross/fern/releases)
- [Changelog](https://github.com/daboross/fern/blob/main/CHANGELOG.md)
- [Commits](https://github.com/daboross/fern/compare/fern-0.6.2...fern-0.7.0)

Updates `pulldown-cmark` from 0.12.1 to 0.12.2
- [Release notes](https://github.com/raphlinus/pulldown-cmark/releases)
- [Commits](https://github.com/raphlinus/pulldown-cmark/compare/v0.12.1...v0.12.2)

Updates `cc` from 1.1.30 to 1.1.31
- [Release notes](https://github.com/rust-lang/cc-rs/releases)
- [Changelog](https://github.com/rust-lang/cc-rs/blob/main/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/cc-rs/compare/cc-v1.1.30...cc-v1.1.31)

---
updated-dependencies:
- dependency-name: serde_json
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: rust-dependencies
- dependency-name: anyhow
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: rust-dependencies
- dependency-name: libc
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: rust-dependencies
- dependency-name: fern
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: rust-dependencies
- dependency-name: pulldown-cmark
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: rust-dependencies
- dependency-name: cc
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: rust-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-23 22:34:20 +09:00
Michael Davis
6d64e6288a Bump flake dependencies
`nix flake update`
2024-10-21 11:27:46 +09:00
Michael Davis
f371dcaa4e flake: Include --cfg tokio_unstable in Rust flags
The flake sets `RUSTFLAGS` and that overwrites the setting in
`.cargo/config.toml`. We need to add the `--cfg tokio_unstable` flag to
enable integration tests to run when called from the devShell.
2024-10-21 11:27:46 +09:00
TornaxO7
be2884d800
Continue line comments (#10996) 2024-10-19 05:48:07 -04:00
Sebastian Dall
a1453350df
Adding snakemake to language (#11858)
* feat: snakemake language

* feat: snakemake syntax highlighting

* doc: xtask docgen - snakemake

* Addressed feedback: removed redundant grammar

* fixed indentation

* removed has-ancestor predicate

---------

Co-authored-by: “SebastianDall” <“semoda@bio.auu.dk”>
2024-10-18 23:12:36 +02:00
karei
855a43a266
Bump jjdescription grammar revision (#11857) 2024-10-18 13:09:11 +09:00
Ivan B.
5ab1f1eb5a
docs(themes): place ui.highlight.frameline and ui.highlight together (#11896)
* docs(themes): place `ui.highlight.frameline` and `ui.highlight` together

* docs(themes): small fix
2024-10-18 13:08:54 +09:00
langurmonkey
1437ba1e5a
Add glsl_analyzer as default language server for GLSL (#11891)
* Add glsl_analyzer as default language server for GLSL

* Generate docs
2024-10-18 11:17:25 +09:00
dependabot[bot]
d1b8129491
build(deps): bump cc in the rust-dependencies group (#11890)
Bumps the rust-dependencies group with 1 update: [cc](https://github.com/rust-lang/cc-rs).


Updates `cc` from 1.1.28 to 1.1.30
- [Release notes](https://github.com/rust-lang/cc-rs/releases)
- [Changelog](https://github.com/rust-lang/cc-rs/blob/main/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/cc-rs/compare/cc-v1.1.28...cc-v1.1.30)

---
updated-dependencies:
- dependency-name: cc
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: rust-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-16 17:52:14 +09:00
David Else
f2d54db24f
Update repology URL after change from helix to helix-editor (#11877)
* Update repology URL after change from helix to helix-editor

* Update book/src/package-managers.md

Co-authored-by: Michael Davis <mcarsondavis@gmail.com>

---------

Co-authored-by: Michael Davis <mcarsondavis@gmail.com>
2024-10-16 01:53:59 +09:00
Dmitry Marakasov
e4f3483bd1
Fix repology badge (#11895) 2024-10-16 01:52:50 +09:00
dependabot[bot]
a7651f5bf0
build(deps): bump the rust-dependencies group with 4 updates (#11850)
Bumps the rust-dependencies group with 4 updates: [once_cell](https://github.com/matklad/once_cell), [futures-util](https://github.com/rust-lang/futures-rs), [futures-executor](https://github.com/rust-lang/futures-rs) and [cc](https://github.com/rust-lang/cc-rs).


Updates `once_cell` from 1.20.1 to 1.20.2
- [Changelog](https://github.com/matklad/once_cell/blob/master/CHANGELOG.md)
- [Commits](https://github.com/matklad/once_cell/compare/v1.20.1...v1.20.2)

Updates `futures-util` from 0.3.30 to 0.3.31
- [Release notes](https://github.com/rust-lang/futures-rs/releases)
- [Changelog](https://github.com/rust-lang/futures-rs/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/futures-rs/compare/0.3.30...0.3.31)

Updates `futures-executor` from 0.3.30 to 0.3.31
- [Release notes](https://github.com/rust-lang/futures-rs/releases)
- [Changelog](https://github.com/rust-lang/futures-rs/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/futures-rs/compare/0.3.30...0.3.31)

Updates `cc` from 1.1.23 to 1.1.28
- [Release notes](https://github.com/rust-lang/cc-rs/releases)
- [Changelog](https://github.com/rust-lang/cc-rs/blob/main/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/cc-rs/compare/cc-v1.1.23...cc-v1.1.28)

---
updated-dependencies:
- dependency-name: once_cell
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: rust-dependencies
- dependency-name: futures-util
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: rust-dependencies
- dependency-name: futures-executor
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: rust-dependencies
- dependency-name: cc
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: rust-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-11 16:38:21 +09:00
dependabot[bot]
761f70d611
build(deps): bump cachix/install-nix-action from 29 to 30 (#11852)
Bumps [cachix/install-nix-action](https://github.com/cachix/install-nix-action) from 29 to 30.
- [Release notes](https://github.com/cachix/install-nix-action/releases)
- [Commits](https://github.com/cachix/install-nix-action/compare/v29...v30)

---
updated-dependencies:
- dependency-name: cachix/install-nix-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-08 16:23:36 +09:00
rhogenson
f55f1f8b43
Remove auto-pair for single quote in SML. (#11838)
Similar to OCaml and other ML languages, single quote is a normal
character that can appear in identifiers and is also used in type
parameters. It is not used for strings or character literals, which both
use double quote.

Co-authored-by: Rose Hogenson <rosehogenson@posteo.net>
2024-10-07 11:56:54 +09:00
Christopher Kaster
048973fc55
Add support for dune project language (#11829) 2024-10-06 21:53:12 +02:00
RoloEdits
f6d39cbc1d
refactor(lsp): handle out-of-range active_signature (#11825) 2024-10-06 08:54:17 -05:00
Pascal Kuthe
162028d444
Merge pull request #11486 from helix-editor/lsp-location-refactor
Replace uses of `lsp::Location` with a custom Location type
2024-10-04 03:33:35 +02:00
Skyler Hawthorne
02b6f1488a
fix git repo detection on symlinks (#11732) 2024-10-03 12:08:06 -05:00
Ian Hobson
57ec3b7330
Add a highlight for the keyword.storage scope to the onedark theme (#11802)
Rust highlight queries make use of keyword.storage for keywords like
`struct`, `enum`, and also for modifiers like `mut` and `ref`.

Using a color that's different to the one used for
`"variable.parameter"` (red) improves differentiation for mutable
function arguments.
2024-10-01 10:05:45 +09:00
Akseli
083bb0118f
Fix some odin highlights (#11804)
Some of the odin highlights seemed wrong or lacking, like the import names were not being matched:

```odin

// color both "rl" here to same value
import rl "vendor:raylib"

...

rl.Vector3
```

Import color was also not being used correctly
2024-10-01 10:05:28 +09:00
dependabot[bot]
cb9307bb03
build(deps): bump the rust-dependencies group with 5 updates (#11805)
Bumps the rust-dependencies group with 5 updates:

| Package | From | To |
| --- | --- | --- |
| [once_cell](https://github.com/matklad/once_cell) | `1.19.0` | `1.20.1` |
| [regex](https://github.com/rust-lang/regex) | `1.10.6` | `1.11.0` |
| [tempfile](https://github.com/Stebalien/tempfile) | `3.12.0` | `3.13.0` |
| [libc](https://github.com/rust-lang/libc) | `0.2.158` | `0.2.159` |
| [cc](https://github.com/rust-lang/cc-rs) | `1.1.21` | `1.1.23` |


Updates `once_cell` from 1.19.0 to 1.20.1
- [Changelog](https://github.com/matklad/once_cell/blob/master/CHANGELOG.md)
- [Commits](https://github.com/matklad/once_cell/compare/v1.19.0...v1.20.1)

Updates `regex` from 1.10.6 to 1.11.0
- [Release notes](https://github.com/rust-lang/regex/releases)
- [Changelog](https://github.com/rust-lang/regex/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/regex/compare/1.10.6...1.11.0)

Updates `tempfile` from 3.12.0 to 3.13.0
- [Changelog](https://github.com/Stebalien/tempfile/blob/master/CHANGELOG.md)
- [Commits](https://github.com/Stebalien/tempfile/compare/v3.12.0...v3.13.0)

Updates `libc` from 0.2.158 to 0.2.159
- [Release notes](https://github.com/rust-lang/libc/releases)
- [Changelog](https://github.com/rust-lang/libc/blob/0.2.159/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/libc/compare/0.2.158...0.2.159)

Updates `cc` from 1.1.21 to 1.1.23
- [Release notes](https://github.com/rust-lang/cc-rs/releases)
- [Changelog](https://github.com/rust-lang/cc-rs/blob/main/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/cc-rs/compare/cc-v1.1.21...cc-v1.1.23)

---
updated-dependencies:
- dependency-name: once_cell
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: rust-dependencies
- dependency-name: regex
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: rust-dependencies
- dependency-name: tempfile
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: rust-dependencies
- dependency-name: libc
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: rust-dependencies
- dependency-name: cc
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: rust-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-01 10:05:07 +09:00
dependabot[bot]
e28b0da1fb
build(deps): bump cachix/install-nix-action from V28 to 29 (#11806)
Bumps [cachix/install-nix-action](https://github.com/cachix/install-nix-action) from V28 to 29. This release includes the previously tagged commit.
- [Release notes](https://github.com/cachix/install-nix-action/releases)
- [Commits](https://github.com/cachix/install-nix-action/compare/V28...v29)

---
updated-dependencies:
- dependency-name: cachix/install-nix-action
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-01 10:04:21 +09:00
offsetcyan
e5dd60f794
Add Erlang Language Platform (ELP) support to Erlang (#11499)
* Add ELP LSP to Erlang

* generate docs

---------

Co-authored-by: Blaž Hrastnik <blaz@mxxn.io>
2024-09-30 11:06:45 +09:00
Denis Krienbühl
d103248cb8
Small bogster theme improvements (#11353)
* Add a distinct jump-label to bogster theme

* Make the primary label visible in the bogster theme
2024-09-30 11:06:33 +09:00
David Crespo
42453786a0
add key concepts to usage.md (#11485) 2024-09-30 11:06:24 +09:00
Oren Mittman
e1cacd149c
chore: add "ui.virtual.jump-label" to gruber-darker theme (#11547) 2024-09-30 11:02:19 +09:00
David Else
5975e53600
Add vale language server (#11636)
Co-authored-by: Blaž Hrastnik <blaz@mxxn.io>
2024-09-30 11:02:02 +09:00
RoloEdits
24f24299f2
feat(languages): add superhtml as lsp for html (#11609) 2024-09-30 11:01:49 +09:00
chtenb
5ffd4ae529
Add undocumented keybindings to book (#11662) 2024-09-30 11:01:36 +09:00
chtenb
8cdce9212c
Improve tree-sitter-subtree (#11663)
* Make unnamed nodes visible in subtree view

* Refine command description

* Update generated docs

* Update unit test expected output
2024-09-30 10:59:31 +09:00
Axlefublr
48b89d4dcf
fix: fish builtin functions are highlighted as such (#11792)
* fix: fish builtin functions are highlighted as such

* fix: single-character commands recognized as builtins

???? how did that query even happen

* fix: update builtins to fish 3.7.1

* fix: add back `alias` and `isatty`

they are builtins, but aren't reported by builtin -n for some reason
2024-09-30 10:59:03 +09:00
Dmitriy Sokolov
dd45ae1289
languages.toml: recognize ldtk files (#11793) 2024-09-30 10:58:54 +09:00
Ian J Sikes
2c3a00e96a
Fix typo in tutor ch 13.5 (#11765)
It said "split" instead of "swap"
2024-09-30 10:58:19 +09:00
jneem
2ce4c6d5fa
Bump tree-sitter-nickel (#11771) 2024-09-29 14:30:50 +02:00
Tim
82dd963693
Add: validation of bundled themes in build workflow (#11627)
* Add: xtask to check themes for validation warnings

* Update: tidied up runtime paths

* Update: test build workflow

* Update: address clippy lints

* Revert: only trigger workflow on push to master branch

* Add: Theme::from_keys factory method to construct theme from Toml keys

* Update: returning validation failures in Loader.load method

* Update: commented out invalid keys from affected themes

* Update: correct invalid keys so that valid styles still applied

* Update: include default and base16_default themes in check

* Update: renamed validation_failures to load_errors

* Update: introduce load_with_warnings helper function and centralise logging of theme warnings

* Update: use consistent naming throughout
2024-09-28 13:52:09 +02:00
Konstantin Munteanu
70bbc9d526
Add .rbs files to ruby language (#11786) 2024-09-28 13:22:13 +09:00
Akseli
b18a471ed1
Remove "true" from odinfmt line (#11759)
The `-stdin` in `odinfmt` does not take any arguments, the `true` part here just confuses the formatter, and makes it ignore `odinfmt.json` file.

Removing it fixes the issue.
2024-09-25 15:24:11 +09:00
Tobias Hunger
f49b18d157
chore: Update slint tree-sitter grammar to version 1.8 (#11757)
Bump the commit to the tree-sitter corresponding to the
latest Slint release.
2024-09-25 15:23:52 +09:00
Lukas Knuth
50ba848b59
Update HCL grammar (#11749)
* Point HCL grammer to newest

This adds support for provider-defined function calls in Terraform.

* Update HCL grammar repo

The repository was moved from the original authors personal GitHub to the `tree-sitter-grammars` organization.

Co-authored-by: Michael Davis <mcarsondavis@gmail.com>

---------

Co-authored-by: Michael Davis <mcarsondavis@gmail.com>
2024-09-25 15:23:38 +09:00
dependabot[bot]
30aa375f2d
build(deps): bump the rust-dependencies group with 2 updates (#11761)
Bumps the rust-dependencies group with 2 updates: [thiserror](https://github.com/dtolnay/thiserror) and [cc](https://github.com/rust-lang/cc-rs).


Updates `thiserror` from 1.0.63 to 1.0.64
- [Release notes](https://github.com/dtolnay/thiserror/releases)
- [Commits](https://github.com/dtolnay/thiserror/compare/1.0.63...1.0.64)

Updates `cc` from 1.1.19 to 1.1.21
- [Release notes](https://github.com/rust-lang/cc-rs/releases)
- [Changelog](https://github.com/rust-lang/cc-rs/blob/main/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/cc-rs/compare/cc-v1.1.19...cc-v1.1.21)

---
updated-dependencies:
- dependency-name: thiserror
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: rust-dependencies
- dependency-name: cc
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: rust-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-25 02:39:41 +09:00
rhogenson
73deabaa40
Fix panic when drawing at the edge of the screen. (#11737)
When pressing tab at the edge of the screen, Helix panics in debug mode
subtracting position.col - self.offset.col.

To correctly account for graphemes that are partially visible,
column_in_bounds takes a width and returns whether the whole range is
in bounds.

Co-authored-by: Rose Hogenson <rosehogenson@posteo.net>
2024-09-23 02:17:02 +09:00
rhogenson
8b1764d164
Join single-line comments with J. (#11742)
Fixes #8565.

Co-authored-by: Rose Hogenson <rosehogenson@posteo.net>
2024-09-23 02:16:24 +09:00
James Munger
d6eb10d9f9
Update README.md (#11665)
Readability Clarification
2024-09-21 19:26:01 +02:00
Théo Daron
896bf47d8d
adding support for jujutsu VCS inside find_workspace resolution (#11685) 2024-09-21 19:22:50 +02:00
Thor 🪁
c850b90f67
add circom tree-sitter, syntax-highlighting, and lsp support (#11676)
* add circom tree-sitter and lsp support

* add circom syntax highlighting queries

* cargo xtask docgen

* updated highlights to reflect helix themes typing

* bugfix: ~= operator causing issues

* minor adjustment: add = and ; operator and delimiter
2024-09-21 19:13:02 +02:00
Mykyta
274c660a0e
small fix syntax highlighting in vue.js files (#11706)
* small fix syntax highlighting in vue.js files

* changes after review by mikedavis
2024-09-21 19:12:39 +02:00
rhogenson
5717aa8e35
Fix Rope.starts_with. (#11739)
Co-authored-by: Rose Hogenson <rosehogenson@posteo.net>
2024-09-21 23:05:17 +09:00
timd
9f93de5a4b
fix(themes): fix diagnostics in snazzy (#11731)
* fix(themes): fix diagnostics in snazzy

Before this change, the color scheme makes most diagnostics difficult
to read. This fix makes diagnostic much less obtrusive when using
snazzy.

* chore(fmt): nicely format snazzy theme file
2024-09-19 10:15:51 +09:00
Ayoub Benali
b85e824ba9
Handle window/showMessage and display it bellow status line (#5535)
* Handle window/showMessage and display it bellow status line

* Enable `editor.lsp.display_messages` by default

---------

Co-authored-by: Michael Davis <mcarsondavis@gmail.com>
2024-09-18 21:43:06 +02:00
Nicolas Karolak
a7b8b27abf
chore: add ruff and jedi lsp servers (#11630)
* chore: add ruff lsp server

Ruff provide a `server` command that starts a LSP server:
https://docs.astral.sh/ruff/editors/#language-server-protocol

* chore: add jedi lsp server

[jedi-language-server](https://github.com/pappasam/jedi-language-server) is a Python LSP server based the popular [jedi](https://jedi.readthedocs.io/en/latest/) library.

* docs: add ruff and jedi as python lsp servers
2024-09-18 12:12:31 +09:00
Jesús González
84fbadbdde
Update picker headers styling in Darcula themes (#11620)
* Apply styling to picker headers in Darcula themes

* Add background to active picker column in Darcula.
2024-09-17 11:34:10 +09:00
ves
f4df4bf5f2
Stylize horizon-dark picker v2 columns (#11649) 2024-09-17 11:33:21 +09:00
dependabot[bot]
c754949454
build(deps): bump the rust-dependencies group with 4 updates (#11712)
Bumps the rust-dependencies group with 4 updates: [unicode-segmentation](https://github.com/unicode-rs/unicode-segmentation), [anyhow](https://github.com/dtolnay/anyhow), [rustix](https://github.com/bytecodealliance/rustix) and [cc](https://github.com/rust-lang/cc-rs).


Updates `unicode-segmentation` from 1.11.0 to 1.12.0
- [Commits](https://github.com/unicode-rs/unicode-segmentation/compare/v1.11.0...v1.12.0)

Updates `anyhow` from 1.0.87 to 1.0.89
- [Release notes](https://github.com/dtolnay/anyhow/releases)
- [Commits](https://github.com/dtolnay/anyhow/compare/1.0.87...1.0.89)

Updates `rustix` from 0.38.36 to 0.38.37
- [Release notes](https://github.com/bytecodealliance/rustix/releases)
- [Changelog](https://github.com/bytecodealliance/rustix/blob/main/CHANGELOG.md)
- [Commits](https://github.com/bytecodealliance/rustix/compare/v0.38.36...v0.38.37)

Updates `cc` from 1.1.18 to 1.1.19
- [Release notes](https://github.com/rust-lang/cc-rs/releases)
- [Changelog](https://github.com/rust-lang/cc-rs/blob/main/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/cc-rs/compare/cc-v1.1.18...cc-v1.1.19)

---
updated-dependencies:
- dependency-name: unicode-segmentation
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: rust-dependencies
- dependency-name: anyhow
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: rust-dependencies
- dependency-name: rustix
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: rust-dependencies
- dependency-name: cc
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: rust-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-17 11:32:26 +09:00
dependabot[bot]
9851edf477
build(deps): bump cachix/install-nix-action from V27 to 28 (#11713)
Bumps [cachix/install-nix-action](https://github.com/cachix/install-nix-action) from V27 to 28. This release includes the previously tagged commit.
- [Release notes](https://github.com/cachix/install-nix-action/releases)
- [Commits](https://github.com/cachix/install-nix-action/compare/V27...V28)

---
updated-dependencies:
- dependency-name: cachix/install-nix-action
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-17 11:31:36 +09:00
Niklas Gruhn
5ce77de0dc
fix: Lean language server consuming excessive memory (#11683)
The Lean process, spawned by the language server, might use excessive
memory in certain situation, causing the entire system to freeze. See:

   https://github.com/leanprover/lean4/issues/5321

The language server accepts a CLI flag for limiting memory use. I set
it to 1024MB, which might be a bit arbitrary, but definitly prevents
the system from crashing.
2024-09-15 19:08:35 +09:00
dependabot[bot]
237cbe4bca
build(deps): bump the rust-dependencies group with 4 updates (#11669)
Bumps the rust-dependencies group with 4 updates: [globset](https://github.com/BurntSushi/ripgrep), [ignore](https://github.com/BurntSushi/ripgrep), [grep-regex](https://github.com/BurntSushi/ripgrep) and [grep-searcher](https://github.com/BurntSushi/ripgrep).


Updates `globset` from 0.4.14 to 0.4.15
- [Release notes](https://github.com/BurntSushi/ripgrep/releases)
- [Changelog](https://github.com/BurntSushi/ripgrep/blob/master/CHANGELOG.md)
- [Commits](https://github.com/BurntSushi/ripgrep/compare/globset-0.4.14...ignore-0.4.15)

Updates `ignore` from 0.4.22 to 0.4.23
- [Release notes](https://github.com/BurntSushi/ripgrep/releases)
- [Changelog](https://github.com/BurntSushi/ripgrep/blob/master/CHANGELOG.md)
- [Commits](https://github.com/BurntSushi/ripgrep/commits)

Updates `grep-regex` from 0.1.12 to 0.1.13
- [Release notes](https://github.com/BurntSushi/ripgrep/releases)
- [Changelog](https://github.com/BurntSushi/ripgrep/blob/master/CHANGELOG.md)
- [Commits](https://github.com/BurntSushi/ripgrep/compare/grep-regex-0.1.12...0.1.13)

Updates `grep-searcher` from 0.1.13 to 0.1.14
- [Release notes](https://github.com/BurntSushi/ripgrep/releases)
- [Changelog](https://github.com/BurntSushi/ripgrep/blob/master/CHANGELOG.md)
- [Commits](https://github.com/BurntSushi/ripgrep/compare/grep-searcher-0.1.13...0.1.14)

---
updated-dependencies:
- dependency-name: globset
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: rust-dependencies
- dependency-name: ignore
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: rust-dependencies
- dependency-name: grep-regex
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: rust-dependencies
- dependency-name: grep-searcher
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: rust-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-10 22:59:03 +09:00
Skyler Hawthorne
6309cc71cc
cargo update (#11651) 2024-09-08 13:08:49 +09:00
RoloEdits
c5083ef952
fix(clippy): doc_lazy_continuation (#11642) 2024-09-07 12:31:14 +09:00
dependabot[bot]
6d0a73f8e5
build(deps): bump gix-path from 0.10.10 to 0.10.11 (#11648)
Bumps [gix-path](https://github.com/Byron/gitoxide) from 0.10.10 to 0.10.11.
- [Release notes](https://github.com/Byron/gitoxide/releases)
- [Changelog](https://github.com/Byron/gitoxide/blob/main/CHANGELOG.md)
- [Commits](https://github.com/Byron/gitoxide/compare/gix-path-v0.10.10...gix-path-v0.10.11)

---
updated-dependencies:
- dependency-name: gix-path
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-07 12:31:04 +09:00
dependabot[bot]
41db5d735e
build(deps): bump the rust-dependencies group with 2 updates (#11622)
Bumps the rust-dependencies group with 2 updates: [tokio](https://github.com/tokio-rs/tokio) and [rustix](https://github.com/bytecodealliance/rustix).


Updates `tokio` from 1.39.3 to 1.40.0
- [Release notes](https://github.com/tokio-rs/tokio/releases)
- [Commits](https://github.com/tokio-rs/tokio/compare/tokio-1.39.3...tokio-1.40.0)

Updates `rustix` from 0.38.34 to 0.38.35
- [Release notes](https://github.com/bytecodealliance/rustix/releases)
- [Commits](https://github.com/bytecodealliance/rustix/compare/v0.38.34...v0.38.35)

---
updated-dependencies:
- dependency-name: tokio
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: rust-dependencies
- dependency-name: rustix
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: rust-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-04 21:22:45 +09:00
Michael Davis
da2b0a7484
Make helix_core::Uri cheap to clone
We clone this type very often in LSP pickers, for example diagnostics
and symbols. We can use a single Arc in many cases to avoid the
unnecessary clones.
2024-09-03 09:50:31 -04:00
Michael Davis
48e9357788
picker: Removed owned variant of PathOrId
The only caller of `from_path_buf` was removed in the parent commit
allowing us to drop owned variant of path's `Cow`. With this change we
never need to allocate in the picker preview callback.
2024-09-03 09:48:32 -04:00
Michael Davis
606b957172
Replace uses of lsp::Location with a custom Location type
The lsp location type has the lsp's URI type and a range. We can replace
that with a custom type private to the lsp commands module that uses the
core URI type instead.

We can't entirely replace the type with a new Location type in core.
That type might look like:

    pub struct Location {
        uri: crate::Uri,
        range: crate::Range,
    }

But we can't convert every `lsp::Location` to this type because for
definitions, references and diagnostics language servers send documents
which we haven't opened yet, so we don't have the information to convert
an `lsp::Range` (line+col) to a `helix_core::Range` (char indexing).

This cleans up the picker definitions in this file so that they can all
use helpers like `jump_to_location` and `location_to_file_location` for
the picker preview. It also removes the only use of the deprecated
`PathOrId::from_path_buf` function, allowing us to drop the owned
variant of that type in the child commit.
2024-09-03 09:48:32 -04:00
viyic
90f978d5af
Change primary selection cursor color for naysayer (#11617) 2024-09-03 14:37:17 +09:00
dependabot[bot]
1b5295a3f3
build(deps): bump the rust-dependencies group with 4 updates (#11585)
Bumps the rust-dependencies group with 4 updates: [serde](https://github.com/serde-rs/serde), [serde_json](https://github.com/serde-rs/json), [cc](https://github.com/rust-lang/cc-rs) and [gix](https://github.com/Byron/gitoxide).


Updates `serde` from 1.0.208 to 1.0.209
- [Release notes](https://github.com/serde-rs/serde/releases)
- [Commits](https://github.com/serde-rs/serde/compare/v1.0.208...v1.0.209)

Updates `serde_json` from 1.0.125 to 1.0.127
- [Release notes](https://github.com/serde-rs/json/releases)
- [Commits](https://github.com/serde-rs/json/compare/1.0.125...1.0.127)

Updates `cc` from 1.1.13 to 1.1.15
- [Release notes](https://github.com/rust-lang/cc-rs/releases)
- [Changelog](https://github.com/rust-lang/cc-rs/blob/main/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/cc-rs/compare/cc-v1.1.13...cc-v1.1.15)

Updates `gix` from 0.64.0 to 0.66.0
- [Release notes](https://github.com/Byron/gitoxide/releases)
- [Changelog](https://github.com/Byron/gitoxide/blob/main/CHANGELOG.md)
- [Commits](https://github.com/Byron/gitoxide/compare/gix-v0.64.0...gix-v0.66.0)

---
updated-dependencies:
- dependency-name: serde
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: rust-dependencies
- dependency-name: serde_json
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: rust-dependencies
- dependency-name: cc
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: rust-dependencies
- dependency-name: gix
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: rust-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-08-28 23:56:28 +09:00
Lennard Hofmann
af7a1fd20c
lsp: Gracefully ignore invalid diagnostic severity (#11569) 2024-08-25 14:27:10 -05:00
Yavorski
620dfceb84
Add cshtml to html file-types (#11540) 2024-08-23 17:28:36 +09:00
dependabot[bot]
38e6fcd5c5
build(deps): bump the rust-dependencies group with 7 updates (#11530)
Bumps the rust-dependencies group with 7 updates:

| Package | From | To |
| --- | --- | --- |
| [serde](https://github.com/serde-rs/serde) | `1.0.207` | `1.0.208` |
| [serde_json](https://github.com/serde-rs/json) | `1.0.124` | `1.0.125` |
| [tokio](https://github.com/tokio-rs/tokio) | `1.39.2` | `1.39.3` |
| [libc](https://github.com/rust-lang/libc) | `0.2.155` | `0.2.158` |
| [pulldown-cmark](https://github.com/raphlinus/pulldown-cmark) | `0.11.0` | `0.12.0` |
| [cc](https://github.com/rust-lang/cc-rs) | `1.1.10` | `1.1.13` |
| [which](https://github.com/harryfei/which-rs) | `6.0.2` | `6.0.3` |


Updates `serde` from 1.0.207 to 1.0.208
- [Release notes](https://github.com/serde-rs/serde/releases)
- [Commits](https://github.com/serde-rs/serde/compare/v1.0.207...v1.0.208)

Updates `serde_json` from 1.0.124 to 1.0.125
- [Release notes](https://github.com/serde-rs/json/releases)
- [Commits](https://github.com/serde-rs/json/compare/v1.0.124...1.0.125)

Updates `tokio` from 1.39.2 to 1.39.3
- [Release notes](https://github.com/tokio-rs/tokio/releases)
- [Commits](https://github.com/tokio-rs/tokio/compare/tokio-1.39.2...tokio-1.39.3)

Updates `libc` from 0.2.155 to 0.2.158
- [Release notes](https://github.com/rust-lang/libc/releases)
- [Changelog](https://github.com/rust-lang/libc/blob/0.2.158/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/libc/compare/0.2.155...0.2.158)

Updates `pulldown-cmark` from 0.11.0 to 0.12.0
- [Release notes](https://github.com/raphlinus/pulldown-cmark/releases)
- [Commits](https://github.com/raphlinus/pulldown-cmark/compare/v0.11.0...v0.12.0)

Updates `cc` from 1.1.10 to 1.1.13
- [Release notes](https://github.com/rust-lang/cc-rs/releases)
- [Changelog](https://github.com/rust-lang/cc-rs/blob/main/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/cc-rs/compare/cc-v1.1.10...cc-v1.1.13)

Updates `which` from 6.0.2 to 6.0.3
- [Release notes](https://github.com/harryfei/which-rs/releases)
- [Changelog](https://github.com/harryfei/which-rs/blob/master/CHANGELOG.md)
- [Commits](https://github.com/harryfei/which-rs/compare/6.0.2...6.0.3)

---
updated-dependencies:
- dependency-name: serde
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: rust-dependencies
- dependency-name: serde_json
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: rust-dependencies
- dependency-name: tokio
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: rust-dependencies
- dependency-name: libc
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: rust-dependencies
- dependency-name: pulldown-cmark
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: rust-dependencies
- dependency-name: cc
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: rust-dependencies
- dependency-name: which
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: rust-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-08-21 09:41:49 +09:00
0rphee
9e7c488ee3
Update gruvbox themes (#11477) 2024-08-19 18:38:46 -05:00
RoloEdits
4e729dea02
fix: ensure view is initiated for jump_* commands (#11529) 2024-08-20 00:28:59 +02:00
nryz
e290479474
copy shell completion to nix output (#11518) 2024-08-19 15:45:38 -05:00
Jaakko Paju
b90ec5c779
Add/improve textobject queries (#11513)
* Add textobject queries for YAML

* Add textobject queries for SQL

* Add textobject queries for HOCON

* Add textobject queries for git-config

* Add textobject queries for env

* Add textobject queries for Dockerfile

* Add textobject queries for docker-compose

* Add textobject queries for prisma

* Add entry textobject queries for hcl

* Add entry textobject queries for Nix

* Update docs
2024-08-17 19:27:50 +02:00
Per-Gunnar
ff33b07756
Clarify lesson 10.1 wording (#11478)
Co-authored-by: Per-gunnar Eriksson <per-gunnar.eriksson@fortnox.se>
2024-08-14 19:44:44 +02:00
Frans Skarman
f9aae99379
Highlight types and enum members in the rust prelude (#8535)
* Add some rust builtins

* rust queries: Add everything in the 2021 prelude

* Update runtime/queries/rust/highlights.scm

Co-authored-by: Michael Davis <mcarsondavis@gmail.com>

---------

Co-authored-by: Michael Davis <mcarsondavis@gmail.com>
2024-08-14 19:43:31 +02:00
0rphee
f65ec32a1c
Update everforest themes (#11459) 2024-08-13 13:53:03 +02:00
dependabot[bot]
112b2878ae
build(deps): bump the rust-dependencies group with 4 updates (#11476)
Bumps the rust-dependencies group with 4 updates: [serde](https://github.com/serde-rs/serde), [serde_json](https://github.com/serde-rs/json), [tempfile](https://github.com/Stebalien/tempfile) and [cc](https://github.com/rust-lang/cc-rs).


Updates `serde` from 1.0.204 to 1.0.207
- [Release notes](https://github.com/serde-rs/serde/releases)
- [Commits](https://github.com/serde-rs/serde/compare/v1.0.204...v1.0.207)

Updates `serde_json` from 1.0.122 to 1.0.124
- [Release notes](https://github.com/serde-rs/json/releases)
- [Commits](https://github.com/serde-rs/json/compare/v1.0.122...v1.0.124)

Updates `tempfile` from 3.11.0 to 3.12.0
- [Changelog](https://github.com/Stebalien/tempfile/blob/master/CHANGELOG.md)
- [Commits](https://github.com/Stebalien/tempfile/commits)

Updates `cc` from 1.1.7 to 1.1.10
- [Release notes](https://github.com/rust-lang/cc-rs/releases)
- [Changelog](https://github.com/rust-lang/cc-rs/blob/main/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/cc-rs/compare/cc-v1.1.7...cc-v1.1.10)

---
updated-dependencies:
- dependency-name: serde
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: rust-dependencies
- dependency-name: serde_json
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: rust-dependencies
- dependency-name: tempfile
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: rust-dependencies
- dependency-name: cc
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: rust-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-08-13 07:59:42 +02:00
RoloEdits
e46cedfc26
refactor(commands): trim_end of sh output (#11161) 2024-08-11 10:24:51 -05:00
Samy AB
91e642ce50
Add gherkin syntax highlighting (#11083)
Co-authored-by: Blaž Hrastnik <blaz@mxxn.io>
2024-08-10 08:25:31 +09:00
Pascal Kuthe
e604d9f8e0
keep (cursor) position when exactly replacing text (#5930)
Whenever a document is changed helix maps various positions like the
cursor or diagnostics through the `ChangeSet` applied to the document.

Currently, this mapping handles replacements as follows:

* Move position to the left for `Assoc::Before` (start of selection)
* Move position to the right for `Assoc::After` (end of selection)

However, when text is exactly replaced this can produce weird results
where the cursor is moved when it shouldn't. For example if `foo` is
selected and a separate cursor is placed on each character (`s.<ret>`)
and the text is replaced (for example `rx`) then the cursors are moved
to the side instead of remaining in place.

This change adds a special case to the mapping code of replacements:
If the deleted and inserted text have the same (char) length then
the position is returned as if the replacement doesn't exist.

only keep selections invariant under replacement

Keeping selections unchanged if they are inside an exact replacement
is intuitive. However, for diagnostics this is not desirable as
helix would otherwise fail to remove diagnostics if replacing parts
of the document.
2024-08-10 00:40:34 +09:00
Michael Davis
7e85fd5b77 Add documentation for static/typable/macro commands 2024-08-10 00:39:47 +09:00
Michael Davis
b7820ee668 Disallow macro keybindings within command sequences
This is a temporary limitation because of the way that command sequences
are executed. Each command is currently executed back-to-back
synchronously, but macros are by design queued up for the compositor.
So macros mixed into a command sequence will behave undesirably: they
will be executed after the rest of the static and/or typable commands
in the sequence.

This is pending a larger refactor of how we handle commands.
<https://redirect.github.com/helix-editor/helix/issues/5555> has
further details and <https://redirect.github.com/helix-editor/helix/issues/4508>
discusses a similar problem faced by the command palette.
2024-08-10 00:39:47 +09:00
Michael Davis
1098a348aa Parse and execute macro mappable commands 2024-08-10 00:39:47 +09:00
Timothy Hutz
bb43a90b86
removed duplicate in lang-support MD file with vector dedup. (#10563) 2024-08-10 00:36:47 +09:00
Chromo-residuum-opec
9daf5c6f8b
feat: add iceberg light/dark themes (#10674)
* feat: add iceberg light/dark themes

* set ui.virtual and ui.virtual.ruler

* quote ui.menu.selected key
2024-08-10 00:35:41 +09:00
Kiara Grouwstra
3b306fa022
Update languages.toml - add nixd, closes #10734 (#10767) 2024-08-10 00:35:29 +09:00
chtenb
e884daea41
Document completion menu bindings (#10994)
* Update keymap.md

* Update keymap.md

* Update keymap.md
2024-08-10 00:34:29 +09:00
Eduard Bardají Puig
779ce41a1f
Provide more details on runtime directory (#11026)
* Provide more details on runtime directory

* Improve pre-built binaries description
2024-08-10 00:34:18 +09:00
Sampo Siltanen
2e90868a37
Update fsharp tree sitter repo reference (#11061)
The repository reference used here was a fork from the actual
repository, which has now been moved under ionide organization, where it is
in active maintenance and development.

The commit SHA is the currently latest commit from main branch.

The injections.scm is copied as is from the fsharp treesitter repo
[queries](https://github.com/ionide/tree-sitter-fsharp/blame/main/queries).

The locals.scm is copied from the repo and the capture names are to follow
the standard names:
- Replace @local.definition.var @local.definition.function, and @local.definition.parameter with @local.definition
- Remove (#set! "definition.function.scope" "parent")

The highlights.scm is copied as well from the fsharp
treesitter repo, but modified here to match helix highlight scopes based
on my best guesstimates. The changes made:

- Remove @spell scopes
- Split @comment into @comment.line and @comment.block
- Replace @comment.documentation with @comment.block.documentation
- Replace @character.special with @special
- Replace @variable.member with @variable.other.member
- Replace @type.definition with @type
- Replace @function.member with @function.method
- Replace @module with @namespace
- Replace @constant.macro with @function.macro
- Replace @property with @variable.other.member
- Replace @variable.member with @variable.other.member
- Replace @variable.parameter.builtin with @variable.builtin
- Replace @function.call with @function
- Replace @number with @constant.numeric.integer and @constant.numeric.float
- Replace @boolean with @constant.builtin.boolean
- Replace @keyword.conditional with @keyword.control.conditional
- Replace @keyword.return with @keyword.control.return
- Replace @keyword.repeate with @keyword.control.repeat
- Replace @keyword.import with @keyword.control.import
- Replace @keyword.modifier with @keyword.storage.modifier
- Replace @keyword.type with @keyword.storage.type
- Replace @keyword.exception with @keyword.control.exception
- Replace @module.builtin with @namespace
2024-08-10 00:34:08 +09:00
kanielrkirby
f0282689da
Use try_lock in diff_handle for Diff gutter (#11092)
* Use `try_lock` in `diff_handle` for Diff gutter
- Add the method `try_load() -> Option<Diff>` to `DiffHandle`, using `try_lock` to avoid deadlocks.
- Use said method in `gutter.rs diff()`, which will use a blank `GutterFn` instead when met with a locked `Diff`.

* Revert changes

* Replace `Mutex` with `RwLock` in `Diff`

---------

Co-authored-by: Kaniel Kirby <pirate7007@runbox.com>
2024-08-10 00:32:46 +09:00
麦芽糖
aaaafb8f5f
feat: add thrift hightlight (#11367) 2024-08-10 00:26:58 +09:00
Poliorcetics
68f495b023
just: Use updated grammar with recent language changes and correct highlighting (#11380) 2024-08-10 00:26:48 +09:00
lefp
8851031449
add verilog comment textobjects (#11388) 2024-08-10 00:26:34 +09:00
Val Packett
2f60c21727
Add jq language support (#11393)
jq is a language for manipulating JSON data: https://jqlang.github.io/jq/
2024-08-10 00:26:28 +09:00
David Else
931ddbb077
Update HTML highlights (#11400)
* Update HTML highlights

* Update after review comments
2024-08-10 00:26:19 +09:00
RoloEdits
f52251960a
fix(picker): no longer trim search pattern (#11406) 2024-08-10 00:26:09 +09:00
Heath Stewart
d6431f41d9
Add TypeSpec support (#11412)
* Add TypeSpec support

Adds support for TypeSpec <https://typespec.io> in helix.

* Resolve PR comments

* Pull in LICENSE

Co-authored-by: Michael Davis <mcarsondavis@gmail.com>

---------

Co-authored-by: Michael Davis <mcarsondavis@gmail.com>
2024-08-10 00:25:06 +09:00
bilabila
c9c4452824
Support i3wm and sway config (#11424)
* Support i3wm and sway config

better syntax highlight and fix comment string

* typo
2024-08-10 00:24:19 +09:00
David Else
f8f056d82f
dark_plus: add picker highlights, update underlined modifier syntax, and tweak a few settings (#11415) 2024-08-10 00:23:58 +09:00
Raph
ca47b3c140
Added mesonlsp as the default LSP for Meson (#11416)
* defaulted meson to JCWasmx86/mesonlsp

* generated docs for mesonlsp
2024-08-10 00:23:42 +09:00
yehor
9678c3fe60
Add .svn as workspace root marker (#11429)
* add .svn as workspace-root marker

* cargo fmt
2024-08-10 00:23:27 +09:00
Jefta
518425e055
Add commands for movement by subwords (#8147)
* Allow moving by subword
* Add tests for subword movement
2024-08-08 13:57:59 -05:00
Blaž Hrastnik
0929704699 stdx: ...and this cast is now unnecessary 2024-08-07 07:29:35 +09:00
Blaž Hrastnik
7d017d8fd6 stdx: PSID now sits under Win32::Foundation 2024-08-07 07:21:50 +09:00
Blaž Hrastnik
8f6793a181 stdx: Include all required windows-sys APIs 2024-08-07 07:18:43 +09:00
Blaž Hrastnik
6ba46e5cbf Fix crossterm compilation on macOS 2024-08-07 06:46:59 +09:00
Blaž Hrastnik
70918570a9 tui: Port improvement from ratatui on crossterm 0.28 (https://github.com/ratatui-org/ratatui/pull/1072) 2024-08-07 04:30:38 +09:00
dependabot[bot]
92a87dbc31 build(deps): bump the rust-dependencies group with 9 updates
Bumps the rust-dependencies group with 9 updates:

| Package | From | To |
| --- | --- | --- |
| [regex](https://github.com/rust-lang/regex) | `1.10.5` | `1.10.6` |
| [dunce](https://gitlab.com/kornelski/dunce) | `1.0.4` | `1.0.5` |
| [serde_json](https://github.com/serde-rs/json) | `1.0.121` | `1.0.122` |
| [toml](https://github.com/toml-rs/toml) | `0.8.16` | `0.8.19` |
| [crossterm](https://github.com/crossterm-rs/crossterm) | `0.27.0` | `0.28.1` |
| [tempfile](https://github.com/Stebalien/tempfile) | `3.10.1` | `3.11.0` |
| [serde_repr](https://github.com/dtolnay/serde-repr) | `0.1.12` | `0.1.19` |
| [which](https://github.com/harryfei/which-rs) | `6.0.1` | `6.0.2` |
| [windows-sys](https://github.com/microsoft/windows-rs) | `0.52.0` | `0.59.0` |

Updates `regex` from 1.10.5 to 1.10.6
- [Release notes](https://github.com/rust-lang/regex/releases)
- [Changelog](https://github.com/rust-lang/regex/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/regex/compare/1.10.5...1.10.6)

Updates `dunce` from 1.0.4 to 1.0.5
- [Commits](https://gitlab.com/kornelski/dunce/compare/v1.0.4...v1.0.5)

Updates `serde_json` from 1.0.121 to 1.0.122
- [Release notes](https://github.com/serde-rs/json/releases)
- [Commits](https://github.com/serde-rs/json/compare/v1.0.121...v1.0.122)

Updates `toml` from 0.8.16 to 0.8.19
- [Commits](https://github.com/toml-rs/toml/compare/toml-v0.8.16...toml-v0.8.19)

Updates `crossterm` from 0.27.0 to 0.28.1
- [Release notes](https://github.com/crossterm-rs/crossterm/releases)
- [Changelog](https://github.com/crossterm-rs/crossterm/blob/master/CHANGELOG.md)
- [Commits](https://github.com/crossterm-rs/crossterm/commits)

Updates `tempfile` from 3.10.1 to 3.11.0
- [Changelog](https://github.com/Stebalien/tempfile/blob/master/CHANGELOG.md)
- [Commits](https://github.com/Stebalien/tempfile/compare/v3.10.1...v3.11.0)

Updates `serde_repr` from 0.1.12 to 0.1.19
- [Release notes](https://github.com/dtolnay/serde-repr/releases)
- [Commits](https://github.com/dtolnay/serde-repr/compare/0.1.12...0.1.19)

Updates `which` from 6.0.1 to 6.0.2
- [Release notes](https://github.com/harryfei/which-rs/releases)
- [Changelog](https://github.com/harryfei/which-rs/blob/master/CHANGELOG.md)
- [Commits](https://github.com/harryfei/which-rs/compare/6.0.1...6.0.2)

Updates `windows-sys` from 0.52.0 to 0.59.0
- [Release notes](https://github.com/microsoft/windows-rs/releases)
- [Commits](https://github.com/microsoft/windows-rs/compare/0.52.0...0.59.0)

---
updated-dependencies:
- dependency-name: regex
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: rust-dependencies
- dependency-name: dunce
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: rust-dependencies
- dependency-name: serde_json
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: rust-dependencies
- dependency-name: toml
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: rust-dependencies
- dependency-name: crossterm
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: rust-dependencies
- dependency-name: tempfile
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: rust-dependencies
- dependency-name: serde_repr
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: rust-dependencies
- dependency-name: which
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: rust-dependencies
- dependency-name: windows-sys
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: rust-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-08-07 04:27:05 +09:00
dependabot[bot]
6d123aa549 build(deps): bump bitflags from 1.3.2 to 2.6.0
Bumps [bitflags](https://github.com/bitflags/bitflags) from 1.3.2 to 2.6.0.
- [Release notes](https://github.com/bitflags/bitflags/releases)
- [Changelog](https://github.com/bitflags/bitflags/blob/main/CHANGELOG.md)
- [Commits](https://github.com/bitflags/bitflags/compare/1.3.2...2.6.0)

---
updated-dependencies:
- dependency-name: bitflags
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-08-07 04:20:20 +09:00
Philip Giuliani
b8c968fe47
Update Gleam tree sitter to support label shorthand syntax (#11427) 2024-08-07 04:16:44 +09:00
André Carneiro
0a4432b104
Add statusline errors when nothing is selected with s, K, A-K (#11370) 2024-08-02 21:04:19 +09:00
RoloEdits
cfe80acb6f
output stderr in :sh popup if shell commands fail (#11239)
* refactor(commands): output `stderr` in `:sh` popup

* refactor(commands): switch to `from_utf8_lossy`

This way something is always displayed.

* refactor: no longer log stderr output on failure
2024-08-01 01:29:14 +02:00
TheoCorn
63953e0b9e
fix :move panic when starting a new language server (#11387)
* fix move panic

* change location of is initialized check
2024-08-01 06:40:00 +09:00
RoloEdits
86aecc96a1
chore: clean up clippy lints (#11377)
Using clippy 1.80.0. Also cleans up some that were windows only.
2024-08-01 06:39:46 +09:00
Pascal Kuthe
3fcf168c33
Merge pull request #11355 from helix-editor/helix-lsp-types
Vendor `lsp-types`
2024-07-31 02:02:15 +02:00
Erasin Wang
b19551b11b
Updated Godot support (#11235)
- update gdscript highlights
- add godot-resource textobjects
2024-07-31 05:52:47 +09:00
Andrew Chou
a4cfcff284
update language configuration for Tcl (#11236)
The primary executable that comes with Tcl is `tclsh`. Not really sure what `tclish` is, as I initially thought it was a typo. However, there seems to be references to it based on a quick search (e.g. [here](https://wiki.tcl-lang.org/page/Tclish) and [here](https://tclish.sourceforge.net/)), so maybe it's a valid executable that I just haven't been aware of. I was hesitant to replace it and instead opted to just add `tclsh`.
2024-07-31 05:52:36 +09:00
Michael Davis
22c1a40725
Fix finding injection layer in tree cursor with nested layers (#11365)
The `take_while` should limit the layers to those that can match the
input range so we don't always scan the entire `injection_layers`. We
can limit `depth == 1` layers to those that start before the search
`end`. Deeper layers overlap with shallower layers though so we need
to allow those layers as well in the `take_while`.

For example

```vue
<script setup lang="ts">
const foo = 'bar'.match(/foo/);
const bar = foo;
</script>
```

L2 and L3 are a typescript layer and the `/foo/` part is a small regex
layer. If you used `A-o` before the regex layer you would select the
entire typescript layer. The search in `layer_id_containing_byte_range`
would not consider the typescript layer since the regex layer comes
earlier in `injection_ranges` and that layer's start is after `end`.
The regex layer has a depth of `2` though so the change in this commit
allows scanning through that layer.

Co-authored-by: Pascal Kuthe <pascalkuthe@pm.me>
2024-07-31 05:29:31 +09:00
dependabot[bot]
ce809fb9ef
build(deps): bump the rust-dependencies group with 6 updates (#11371) 2024-07-29 21:10:47 -05:00
麦芽糖
08ac37d295
Add theme keys for the picker header area (#11343)
* feat: pertty header

* 更新 themes.md

Co-authored-by: Michael Davis <mcarsondavis@gmail.com>

---------

Co-authored-by: Michael Davis <mcarsondavis@gmail.com>
2024-07-29 16:52:15 +02:00
Pascal Kuthe
8e041c99df
stable sort lsp edits (#11357) 2024-07-29 06:22:28 +09:00
Skyler Hawthorne
fa13b2bd0d
reduce log noise on file writes (#11361) 2024-07-28 15:59:21 -05:00
dnaq
2a2bc79335
Remove unnecessary .as_mut() call and fix log messages (#11358)
These are changes that fell out of commit:
d7a3cdea65ef321d53b8dc8417175781b5272049
2024-07-28 19:30:07 +02:00
lynn pepin
29439116b8
Documented ulimit fix for error during integration tests (#11356) 2024-07-28 18:40:50 +02:00
dnaq
f5950196d9
Fix panic when starting helix tutor (#11352)
Closes #11351

Also fixed some minor issues related to log
message contents, and removed unnecessary use
of `.as_mut()` as per code review comments on
the PR.
2024-07-28 18:29:26 +02:00
Michael Jones
fade4b218c
new theme named ao (#11063)
* new theme named ao

* Update runtime/themes/ao.toml

Co-authored-by: Michael Davis <mcarsondavis@gmail.com>

---------

Co-authored-by: Michael Davis <mcarsondavis@gmail.com>
2024-07-28 17:23:46 +02:00
Poliorcetics
9e55e8a416
contrib: nushell: also complete available languages with --health (#11346) 2024-07-28 16:52:20 +02:00
Michael Davis
af2ac551ba
Resolve unclosed HTML tag doc warning 2024-07-28 10:41:29 -04:00
Michael Davis
981e5cd737
helix-lsp-types: Resolve clippy lints in tests 2024-07-28 10:41:29 -04:00
Michael Davis
3963969b89
'cargo fmt' 2024-07-28 10:41:29 -04:00
Michael Davis
e21e4eb825
Replace lsp-types in helix-lsp with helix-lsp-types 2024-07-28 10:41:29 -04:00
Michael Davis
7793031aa6
Rename lsp-types crate to helix-lsp-types 2024-07-28 10:41:29 -04:00
Michael Davis
1ccdc55db9
Add helix-lsp-types to workspace 2024-07-28 10:41:29 -04:00
Michael Davis
2900bc03cf
Vendor the lsp-types crate 2024-07-28 10:41:28 -04:00
RoloEdits
6eae846197
feat(languages): update just grammar and queries (#11306)
* feat(languages): update `just` grammar and queries

Bump the

* refactor(syntax): inject shebang by id not name

---------

Co-authored-by: Trevor Gross <tmgross@umich.edu>
2024-07-28 15:54:10 +02:00
Michael Davis
ae72a1dc42 Tombstone LSP clients stopped with :lsp-stop
We use the empty vec in `inner_by_name` as a tombstone value. When the
vec is empty `get` should not automatically restart the server.
2024-07-28 22:16:39 +09:00
Michael Davis
59429e18d6 Lower log level for message about removing clients from the registry
Servers stopped with `:lsp-stop` will show this message when the server
exits. If the client isn't in the registry there isn't any work to do
to remove it so this branch is benign.
2024-07-28 22:16:39 +09:00
Skyler Hawthorne
face6a3268 Disable hard link integration test on Android
Non-rooted Android typically doesn't have permission to use hard links
at all, so this test fails on Android.
2024-07-27 22:34:41 -04:00
Pascal Kuthe
30fb63cc3d Update helix-core/Cargo.toml
Co-authored-by: Michael Davis <mcarsondavis@gmail.com>
2024-07-28 03:29:52 +01:00
Pascal Kuthe
2824e692a7 lock unicode width
Cargo automatically pumbs the patch version when installed with `cargo install`
without the locked flag which creates weird rendering artifacts
2024-07-28 03:29:52 +01:00
Kirawi
e5372b04a1
Fix writing hardlinks (#11340)
* don't use backup files with hardlinks

* check if the inodes remain the same in the test

* move funcs to faccess and use AsRawHandle

* use a copy as a backup for hardlinks

* delete backup after copy
2024-07-27 12:21:52 -05:00
Nikolay Minaev
0813147b97
Use fs' mtime to avoid saving problem on out-of-synced network fs (#11142)
In the case of network file systems, if the server time is ahead
of the local system time, then helix could annoy with messages
that the file has already been modified by another application.
2024-07-27 10:47:23 -05:00
Remo Senekowitsch
229784ccc7
Improve scrolloff behavior (#11323)
* Allow perfect centering of cursor

* Fix horizontal scrolloff

* Fix copypasta in comment
2024-07-26 17:20:33 +02:00
Michael Davis
a1e20a3426
Reorganize Document::apply_impl (#11304)
These changes are ported from
<https://redirect.github.com/helix-editor/helix/pull/9801>. It's a
cleanup of `Document::apply_impl` that uses some early returns to
reduce nesting and some reordering of the steps. The early returns
bail out of `apply_impl` early if the transaction fails to apply or
if the changes are empty (in which case we emit the SelectionDidChange
event). It's a somewhat cosmetic refactor that makes the function easier
to reason about but it also makes it harder to introduce bugs by mapping
positions through empty changesets for example.

Co-authored-by: Pascal Kuthe <pascalkuthe@pm.me>
2024-07-26 09:32:02 +02:00
Damir Vandic
9b65448f53
Fix example query in pickers.md (#11322)
Co-authored-by: Pascal Kuthe <pascalkuthe@pm.me>
2024-07-25 16:23:08 -05:00
Jimmy Zelinskie
5e945c327f
languages: add mdx to markdown filetypes (#11122) 2024-07-25 23:22:35 +02:00
dependabot[bot]
d48b6904f2
build(deps): bump gix-attributes from 0.22.2 to 0.22.3 (#11318)
Bumps [gix-attributes](https://github.com/Byron/gitoxide) from 0.22.2 to 0.22.3.
- [Release notes](https://github.com/Byron/gitoxide/releases)
- [Changelog](https://github.com/Byron/gitoxide/blob/main/CHANGELOG.md)
- [Commits](https://github.com/Byron/gitoxide/compare/gix-attributes-v0.22.2...gix-attributes-v0.22.3)

---
updated-dependencies:
- dependency-name: gix-attributes
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-07-25 21:41:53 +02:00
karei
f34dca797c
Add support for jjdescription files (#11271) 2024-07-25 23:12:55 +09:00
Luca Saccarola
ef4a4ff3c5
Make bash completion behave normally (#11246) 2024-07-24 18:25:25 +02:00
Ryan Roden-Corrent
5d3f05cbe1
Document use of filter columns in pickers (#11218)
* Document use of filter columns in pickers.

Filtering on columns was implemented in #9647.
The only documentation I could find on this feature
was the PR itself, and the video demo used a different syntax.

* Note that column filters are space-separated.

* Note that picker filters can be abbreviated.

* Specify correct picker in docs.

* Clarify picker filter prefix shortenting.

Co-authored-by: Michael Davis <mcarsondavis@gmail.com>

* Move picker docs to their own section.

* Update book/src/pickers.md

Co-authored-by: Michael Davis <mcarsondavis@gmail.com>

* Improve docs on picker registers, keybinds, and syntax.

* Clarify wording around picker queries.

Co-authored-by: Michael Davis <mcarsondavis@gmail.com>

---------

Co-authored-by: Ryan Roden-Corrent <ryan@rcorre.net>
Co-authored-by: Michael Davis <mcarsondavis@gmail.com>
2024-07-24 18:25:00 +02:00
RoloEdits
7c5e5f4e41
fix(lsp): find_completion_range off-by-one (#11266) 2024-07-24 10:34:20 -05:00
1adept
9d21b8fa85
just module extension (#11286)
Co-authored-by: adept <adept@noreply.codeberg.org>
2024-07-24 16:34:34 +02:00
Rich Robinson
182b26bebc
Fix typos in 2-character label jump Tutor entry (#11298) 2024-07-24 16:14:46 +02:00
JR
4c1835504b
Add tutor entry about 2-character label jump (#11273)
* Add tutor entry about 2-character label jump

* Move gw tutor to chapter 9

* Do not explicitely say which labels are shown following gw
2024-07-23 21:49:22 +02:00
Ingrid
1d0a3d49d3
Consistently maintain view position (#10559)
* replicate t-monaghan's changes

* remove View.offset in favour of Document.view_data.view_position

* improve access patterns for Document.view_data

* better borrow checker wrangling with doc_mut!()

* reintroduce ensure_cursor_in_view in handle_config_events

since we sorted out the borrow checker issues using partial borrows,
there's nothing stopping us from going back to the simpler implementation

* introduce helper functions on Document .view_offset, set_view_offset

* fix rebase breakage
2024-07-23 19:54:00 +02:00
dependabot[bot]
0d62656c98
build(deps): bump the rust-dependencies group with 5 updates (#11281)
Bumps the rust-dependencies group with 5 updates:

| Package | From | To |
| --- | --- | --- |
| [thiserror](https://github.com/dtolnay/thiserror) | `1.0.62` | `1.0.63` |
| [toml](https://github.com/toml-rs/toml) | `0.8.14` | `0.8.15` |
| [tokio](https://github.com/tokio-rs/tokio) | `1.38.0` | `1.38.1` |
| [cc](https://github.com/rust-lang/cc-rs) | `1.1.5` | `1.1.6` |
| [libloading](https://github.com/nagisa/rust_libloading) | `0.8.4` | `0.8.5` |


Updates `thiserror` from 1.0.62 to 1.0.63
- [Release notes](https://github.com/dtolnay/thiserror/releases)
- [Commits](https://github.com/dtolnay/thiserror/compare/1.0.62...1.0.63)

Updates `toml` from 0.8.14 to 0.8.15
- [Commits](https://github.com/toml-rs/toml/compare/toml-v0.8.14...toml-v0.8.15)

Updates `tokio` from 1.38.0 to 1.38.1
- [Release notes](https://github.com/tokio-rs/tokio/releases)
- [Commits](https://github.com/tokio-rs/tokio/compare/tokio-1.38.0...tokio-1.38.1)

Updates `cc` from 1.1.5 to 1.1.6
- [Release notes](https://github.com/rust-lang/cc-rs/releases)
- [Changelog](https://github.com/rust-lang/cc-rs/blob/main/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/cc-rs/compare/cc-v1.1.5...cc-v1.1.6)

Updates `libloading` from 0.8.4 to 0.8.5
- [Commits](https://github.com/nagisa/rust_libloading/compare/0.8.4...0.8.5)

---
updated-dependencies:
- dependency-name: thiserror
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: rust-dependencies
- dependency-name: toml
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: rust-dependencies
- dependency-name: tokio
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: rust-dependencies
- dependency-name: cc
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: rust-dependencies
- dependency-name: libloading
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: rust-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-07-23 17:05:34 +02:00
Michael Davis
86795a9dc7
Return document display name from the '%' special register (#11275) 2024-07-23 06:56:26 +09:00
karei
d47e085fe0
Revert kanagawa diff colour change from #11187 (#11270) 2024-07-23 06:54:52 +09:00
Hamir Mahal
f5231196bc
fix: usage of node12 which is deprecated (#11277)
* chore: changes from formatting on save

* fix: usage of `node12 which is deprecated`
2024-07-22 20:23:16 +02:00
Trevor Gross
70a9477ec8
Add :mv as an alias for :move (#11256)
`mv` is the familiar shell command to move or rename a file. Add this to
Helix as an alias for `:move`.
2024-07-22 16:55:12 +02:00
Poliorcetics
6c0a7f60eb
contrib: add nushell completions (#11262) 2024-07-22 09:47:27 -05:00
Michael Davis
dbaa636683
Picker: Skip dynamic query debounce for pastes (#11211)
Pastes are probably the last edit one means to make before the query
should run so it doesn't need to be debounced.
This makes global search much snappier for example when accepting the
history suggestion from the '/' register or pasting a pattern from the
clipboard or a register.
2024-07-19 17:44:55 +09:00
Tobias Hunger
748a9cf022
tree-sitter: Update SHA of parser fro the slint language (#11224)
There has been a new release with a few minor tweaks to the parser. The queries
are fine still.
2024-07-18 23:10:10 +09:00
Michael Davis
b927985cd0
global_search: Save only the primary query to the history register (#11216)
Two changes from the parent commit:

* Save only the `Picker::primary_query` - so you don't save other parts
  of the query, for example `%path foo.rs` while in `global_search`.
* Move the saving out of the `if let Some(option) = self.selection()`
  block. So when you hit enter you save to history whether you have a
  selection or not. If you want to close the picker without saving to
  the register you can use C-c or Esc instead.
2024-07-18 11:08:39 +09:00
Michael Davis
c9d829a26d
global_search: Save search when accepting an option (#11209)
The Prompt is set up to push the current line to history when hitting
Enter but the Picker doesn't pass the Enter event down to the Prompt
(for good reason: we don't want the Prompt's behavior of changing
completions when we hit a path separator). We should save the Prompt's
line to its configured history register when hitting Enter when there
is a selection in the Picker.

This currently only applies to `global_search`'s Picker since it's the
only Picker to use `Picker::with_history_register`.
2024-07-17 22:41:57 +09:00
karei
bd5e893149
Bring kanagawa colours better in line with neovim version (#11187)
- Fixes some colours not matching their counterpart in neovim.
- Adds `ui.debug` colours
- Fix for separators in inactive statuslines
2024-07-17 16:36:01 +09:00
irkill
72e0f6301b
Fix a typo in the tutor (#11201) 2024-07-17 16:34:41 +09:00
Michael Davis
22a051408a
Update release docs (#11182)
These haven't been updated in a little while. The original plan was to
update the version (in `Cargo.toml`) after a release to the next
planned release date but the way we release now is to update the version
as a part of the release process (just before tagging). Typically this
is all taken care of in the CHANGELOG-updating branch along with the
other documentation changes like the appdata file. The workflow now is
basically just to merge the changelog/release branch, pull, tag and push.
2024-07-16 12:31:29 +09:00
Leandro Braga
aac81424cd
Fix select_all_children command (#11195) 2024-07-16 12:30:45 +09:00
RoloEdits
535351067c
fix(commands): change pipe-like output trimming (#11183) 2024-07-16 12:29:44 +09:00
Adam Perkowski
850c9f691e
Fixed headings (# / ##) to match other docs (#11192) 2024-07-16 12:29:00 +09:00
dependabot[bot]
884b53c767
build(deps): bump the rust-dependencies group with 3 updates (#11194)
Bumps the rust-dependencies group with 3 updates: [thiserror](https://github.com/dtolnay/thiserror), [open](https://github.com/Byron/open-rs) and [cc](https://github.com/rust-lang/cc-rs).


Updates `thiserror` from 1.0.61 to 1.0.62
- [Release notes](https://github.com/dtolnay/thiserror/releases)
- [Commits](https://github.com/dtolnay/thiserror/compare/1.0.61...1.0.62)

Updates `open` from 5.2.0 to 5.3.0
- [Release notes](https://github.com/Byron/open-rs/releases)
- [Changelog](https://github.com/Byron/open-rs/blob/main/changelog.md)
- [Commits](https://github.com/Byron/open-rs/compare/v5.2.0...v5.3.0)

Updates `cc` from 1.0.106 to 1.1.5
- [Release notes](https://github.com/rust-lang/cc-rs/releases)
- [Changelog](https://github.com/rust-lang/cc-rs/blob/main/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/cc-rs/compare/cc-v1.0.106...cc-v1.1.5)

---
updated-dependencies:
- dependency-name: thiserror
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: rust-dependencies
- dependency-name: open
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: rust-dependencies
- dependency-name: cc
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: rust-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-07-16 12:28:41 +09:00
Masanori Ogino
6b947518c6
Regenerate documentation (#11196) 2024-07-16 12:27:45 +09:00
jyn
b0cf86d31b
add :edit and :e as aliases for :open (#11186)
Vim supports these, and i can't think of any reason helix would want to have a different meaning for `:edit` than `:open`.

docs: https://vimhelp.org/editing.txt.html#%3Aedit
2024-07-15 21:39:05 +02:00
Blaž Hrastnik
107cdf3e43
Merge pull request #6417 from pascalkuthe/inline-diagnostics 2024-07-16 00:28:34 +09:00
Pascal Kuthe
386fa371d7
gracefully handle lack of tokio runtime 2024-07-15 16:36:33 +02:00
Pascal Kuthe
7283ef881f
only show inline diagnostics after a delay 2024-07-15 16:36:29 +02:00
Pascal Kuthe
d8a115641d
fix scrolling/movement for multiline virtual text 2024-07-15 16:35:31 +02:00
Pascal Kuthe
a17b008b42
ignore empty virtual text layers 2024-07-15 16:35:31 +02:00
Pascal Kuthe
3abc07a79e
use correct position for cursor in doc popup 2024-07-15 16:35:31 +02:00
Pascal Kuthe
7e133167ca
remove redudant/incorrect view bound check 2024-07-15 16:35:30 +02:00
Pascal Kuthe
6d051d7084
render diagnostic inline 2024-07-15 16:35:30 +02:00
Pascal Kuthe
39b3d81abf
stable sort diagnostics to avoid flickering 2024-07-15 16:35:30 +02:00
Pascal Kuthe
839f4d758d
fix typo in doc_formatter.rs 2024-07-15 16:35:30 +02:00
Pascal Kuthe
2c0506aa96
streamline text decoration API
This commit brings the text decoration API inline with the
LineAnnotation API (so they are consistent) resulting in a single
streamlined API instead of multiple ADHOK callbacks.
2024-07-15 16:35:26 +02:00
Pascal Kuthe
9a93240d27
correctly wrap at text-width 2024-07-15 16:34:14 +02:00
Pascal Kuthe
4c7cdb8fea
Improve line annotation API
The line annotation as implemented in #5420 had two shortcomings:
* It required the height of virtual text lines to be known ahead time
* It checked for line anchors at every grapheme

The first problem made the API impractical to use in practice because
almost all virtual text needs to be softwrapped. For example inline
diagnostics should be softwrapped to avoid cutting off the diagnostic
message (as no scrolling is possible). While more complex virtual text
like side by side diffs must dynamically calculate the number of empty
lines two align two documents (which requires taking account both
softwrap and virtual text). To address this, the API has been
refactored to use a trait.

The second issue caused some performance overhead and unnecessarily
complicated the `DocumentFormatter`. It was addressed by only calling
the trait mentioned above at line breaks (instead of always). This
allows offers additional flexibility to annotations as it offers
the flexibility to align lines (needed for side by side diffs).
2024-07-15 16:34:14 +02:00
Pascal Kuthe
e15626a00a
track char_idx in DocFormatter 2024-07-15 16:34:13 +02:00
Pascal Kuthe
2023445a08
ensure highlight scopes are skipped properly 2024-07-15 16:34:13 +02:00
Pascal Kuthe
22dfad605a
implement Add/Sub for position
being able to add/subtract positions is very handy when writing rendering code
2024-07-15 16:34:10 +02:00
Blaž Hrastnik
08ee8b9443
Merge pull request #9647 from helix-editor/pickers-v2
`Picker`s "v2"
2024-07-15 23:30:58 +09:00
Michael Davis
9de5f5cefa
Picker: Highlight the currently active column
We can track the ranges in the input text that correspond to each column
and use this information during rendering to apply a new theme key that
makes the "active column" stand out. This makes it easier to tell at
a glance which column you're entering.
2024-07-15 10:03:35 -04:00
FlorianNAdam
6345b78409
Keep editor from switching to normal mode when loading a Document (#11176) 2024-07-15 22:48:37 +09:00
Michael Davis
a7777b3c11
Accept 'IntoIterator<Item = Column<T, D>>' for picker columns
This allows us to replace any `vec![..]`s of columns where all columns
are static with static slices `[..]`.
2024-07-15 09:31:33 -04:00
Michael Davis
009bbdaf8d
Picker: Reset the cursor on prompt change 2024-07-15 09:31:33 -04:00
Michael Davis
8555248b01
Accept 'IntoIterator<Item = T>' for Picker::new options
`Picker::new` loops through the input options to inject each of them, so
there's no need to collect into an intermediary Vec. This removes some
unnecessary collections. Also, pickers that start with no initial
options can now pass an empty slice instead of an empty Vec.

Co-authored-by: Luis Useche <useche@gmail.com>
2024-07-15 09:31:33 -04:00
Michael Davis
3906f6605f
Avoid allocations in Picker file preview callback
The `FileLocation` and `PathOrId` types can borrow paths rather than
requiring them to be owned. This takes a refactor of the preview
functions and preview internals within `Picker`. With this change we
avoid an unnecessary `PathBuf` clone per render for any picker with a
file preview function (i.e. most pickers).

This refactor is not fully complete. The `PathOrId` is _sometimes_ an
owned `PathBuf`. This is for pragmatic reasons rather than technical
ones. We need a further refactor to introduce more core types like
`Location` in order to eliminate the Cow and only use `&Path`s within
`PathOrId`. This is left for future work as it will be a larger refactor
almost entirely fitting into the LSP commands module and helix-core -
i.e. mostly unrelated to refactoring the `Picker` code itself.

Co-authored-by: Pascal Kuthe <pascalkuthe@pm.me>
2024-07-15 09:31:33 -04:00
Michael Davis
f4a433f855
Convert LSP URIs into custom URIs
This introduces a custom URI type in core meant to be extended later
if we want to support other schemes. For now it's just a wrapper over a
PathBuf. We use this new URI type to firewall `lsp::Url`. This was
previously done in 8141a4a but using a custom URI type is more flexible
and will improve the way Pickers handle paths for previews in the child
commit(s).

Co-authored-by: soqb <cb.setho@gmail.com>
2024-07-15 09:31:33 -04:00
Pascal Kuthe
408282097f
avoid collecting columns to a temporary vec 2024-07-15 09:31:33 -04:00
Michael Davis
6ccbfe9bdf
Request a UI redraw on Drop of an Injector
This fixes the changed files picker when used against a clean worktree
for example. Without it the running indicator does not disappear. It
also simplifies the dynamic query handler's implementation so that it
doesn't need to request a redraw explicitly.

Co-authored-by: Pascal Kuthe <pascalkuthe@pm.me>
2024-07-15 09:31:33 -04:00
Michael Davis
6492f17e7d
Add a hidden column for the global search line contents
We could expand on this in the future to have different preview modes
that you can toggle between with C-t. Currently that binding just hides
the preview but it could switch between different preview modes and in
one mode hide the path and just show the line contents.
2024-07-15 09:31:32 -04:00
Michael Davis
7b1131adf6
global_search: Suggest latest '/' register value 2024-07-15 09:31:32 -04:00
Michael Davis
1d023b05ac
Refactor global_search as a dynamic Picker 2024-07-15 09:31:32 -04:00
Michael Davis
5622db6798
Remove sym_picker helper fun
The parent commit split out the workspace symbol picker to an inline
definition so the `workspace` parameter is never passed as `true`. We
should consolidate this picker definition into the symbol_picker
function.
2024-07-15 09:31:32 -04:00
Michael Davis
9e31ba5475
Consolidate DynamicPicker into Picker
DynamicPicker is a thin wrapper over Picker that holds some additional
state, similar to the old FilePicker type. Like with FilePicker, we want
to fold the two types together, having Picker optionally hold that
extra state.

The DynamicPicker is a little more complicated than FilePicker was
though - it holds a query callback and current query string in state and
provides some debounce for queries using the IdleTimeout event.
We can move all of that state and debounce logic into an AsyncHook
implementation, introduced here as `DynamicQueryHandler`. The hook
receives updates to the primary query and debounces those events so
that once a query has been idle for a short time (275ms) we re-run
the query.

A standard Picker created through `new` for example can be promoted into
a Dynamic picker by chaining the new `with_dynamic_query` function, very
similar to FilePicker's replacement `with_preview`.

The workspace symbol picker has been migrated to the new way of writing
dynamic pickers as an example. The child commit will promote global
search into a dynamic Picker as well.
2024-07-15 09:31:32 -04:00
Michael Davis
11f809c177
Bump nucleo to v0.4.1
We will use this in the child commit to improve the picker's running
indicator. Nucleo 0.4.0 includes an `active_injectors` member that we
can use to detect if anything can push to the picker. When that count
drops to zero we can remove the running indicator.

Nucleo 0.4.1 contains a fix for crashes with interactive global search
on a large directory.
2024-07-15 09:31:32 -04:00
Michael Davis
2c9f5b3efb
Implement Error for InjectorShutdown 2024-07-15 09:31:32 -04:00
Michael Davis
53ac833efb
Replace picker shutdown bool with version number
This works nicely for dynamic pickers: we stop any running jobs like
global search that are pushing to the injector by incrementing the
version number when we start a new request. The boolean only allowed
us to shut the picker down once, but with a usize a picker can have
multiple "sessions" / "life-cycles" where it receives new options
from an injector.
2024-07-15 09:31:32 -04:00
Michael Davis
385b398808
Add column configurations for existing pickers
This removes the menu::Item implementations for picker item types and
adds `Vec<Column<T, D>>` configurations.
2024-07-15 09:31:32 -04:00
Michael Davis
c4c17c693d
Add a special query syntax for Pickers to select columns
Now that the picker is defined as a table, we need a way to provide
input for each field in the picker. We introduce a small query syntax
that supports multiple columns without being too verbose. Fields are
specified as `%field pattern`. The default column for a picker doesn't
need the `%field` prefix. The field name may be selected by a prefix
of the field, for example `%p foo.rs` rather than `%path foo.rs`.

Co-authored-by: ItsEthra <107059409+ItsEthra@users.noreply.github.com>
2024-07-15 09:31:31 -04:00
Michael Davis
f40fca88e0
Refactor Picker in terms of columns
`menu::Item` is replaced with column configurations for each picker
which control how a column is displayed and whether it is passed to
nucleo for filtering. (This is used for dynamic pickers so that we can
filter those items with the dynamic picker callback rather than nucleo.)

The picker has a new lucene-like syntax that can be used to filter the
picker only on certain criteria. If a filter is not specified, the text
in the prompt applies to the picker's configured "primary" column.

Adding column configurations for each picker is left for the child
commit.
2024-07-15 09:31:31 -04:00
Michael Davis
dae3841a75
Use an AsyncHook for picker preview highlighting
The picker previously used the IdleTimeout event as a trigger for
syntax-highlighting the currently selected document in the preview pane.
This is a bit ad-hoc now that the event system has landed and we can
refactor towards an AsyncHook (like those used for LSP completion and
signature-help). This should resolve some odd scenarios where the
preview did not highlight because of a race between the idle timeout
and items appearing in the picker.
2024-07-15 09:31:31 -04:00
Emran Ramezan
702a96d417
Update highlights.scm and injections.scm for blade.php files (#11138)
* Update highlights.scm for blade.php files

* Update injections.scm to add tree-sitter-comment injection

* Fixed the injection issues  regarding blade parameters
2024-07-15 22:29:14 +09:00
Masanori Ogino
7f77d95c79
Inject the comment grammar into Hare (#11173) 2024-07-15 22:28:23 +09:00
arcofx
1bad3b0dd4
Make format_selections respect document configuration (#11169) 2024-07-15 22:27:42 +09:00
629 changed files with 46400 additions and 12567 deletions

3
.envrc
View file

@ -1,7 +1,8 @@
watch_file shell.nix
watch_file default.nix
watch_file flake.lock
watch_file rust-toolchain.toml
# try to use flakes, if it fails use normal nix (ie. shell.nix)
use flake || use nix
eval "$shellHook"
eval "$shellHook"

View file

@ -8,9 +8,6 @@ updates:
schedule:
interval: "weekly"
groups:
tree-sitter:
patterns:
- "tree-sitter*"
rust-dependencies:
update-types:
- "minor"

View file

@ -6,7 +6,12 @@ on:
- master
merge_group:
schedule:
- cron: '00 01 * * *'
- cron: "00 01 * * *"
env:
MSRV: "1.76"
# This key can be changed to bust the cache of tree-sitter grammars.
GRAMMAR_CACHE_VERSION: ""
jobs:
check:
@ -16,16 +21,23 @@ jobs:
steps:
- name: Checkout sources
uses: actions/checkout@v4
- name: Install stable toolchain
uses: helix-editor/rust-toolchain@v1
- name: Install MSRV toolchain
uses: dtolnay/rust-toolchain@master
with:
profile: minimal
override: true
toolchain: ${{ env.MSRV }}
- uses: Swatinem/rust-cache@v2
with:
shared-key: "build"
- name: Cache tree-sitter grammars
uses: actions/cache@v4
with:
path: runtime/grammars
key: ${{ runner.os }}-stable-v${{ env.GRAMMAR_CACHE_VERSION }}-tree-sitter-grammars-${{ hashFiles('languages.toml') }}
restore-keys: ${{ runner.os }}-stable-v${{ env.GRAMMAR_CACHE_VERSION }}-tree-sitter-grammars-
- name: Run cargo check
run: cargo check
@ -40,19 +52,21 @@ jobs:
- name: Checkout sources
uses: actions/checkout@v4
- name: Install stable toolchain
uses: dtolnay/rust-toolchain@1.70
- name: Install MSRV toolchain
uses: dtolnay/rust-toolchain@master
with:
toolchain: ${{ env.MSRV }}
- uses: Swatinem/rust-cache@v2
with:
shared-key: "build"
- name: Cache test tree-sitter grammar
- name: Cache tree-sitter grammars
uses: actions/cache@v4
with:
path: runtime/grammars
key: ${{ runner.os }}-stable-v${{ env.CACHE_VERSION }}-tree-sitter-grammars-${{ hashFiles('languages.toml') }}
restore-keys: ${{ runner.os }}-stable-v${{ env.CACHE_VERSION }}-tree-sitter-grammars-
key: ${{ runner.os }}-stable-v${{ env.GRAMMAR_CACHE_VERSION }}-tree-sitter-grammars-${{ hashFiles('languages.toml') }}
restore-keys: ${{ runner.os }}-stable-v${{ env.GRAMMAR_CACHE_VERSION }}-tree-sitter-grammars-
- name: Run cargo test
run: cargo test --workspace
@ -72,15 +86,23 @@ jobs:
- name: Checkout sources
uses: actions/checkout@v4
- name: Install stable toolchain
uses: dtolnay/rust-toolchain@1.70
- name: Install MSRV toolchain
uses: dtolnay/rust-toolchain@master
with:
toolchain: ${{ env.MSRV }}
components: rustfmt, clippy
- uses: Swatinem/rust-cache@v2
with:
shared-key: "build"
- name: Cache tree-sitter grammars
uses: actions/cache@v4
with:
path: runtime/grammars
key: ${{ runner.os }}-stable-v${{ env.GRAMMAR_CACHE_VERSION }}-tree-sitter-grammars-${{ hashFiles('languages.toml') }}
restore-keys: ${{ runner.os }}-stable-v${{ env.GRAMMAR_CACHE_VERSION }}-tree-sitter-grammars-
- name: Run cargo fmt
run: cargo fmt --all --check
@ -100,16 +122,28 @@ jobs:
- name: Checkout sources
uses: actions/checkout@v4
- name: Install stable toolchain
uses: dtolnay/rust-toolchain@1.70
- name: Install MSRV toolchain
uses: dtolnay/rust-toolchain@master
with:
toolchain: ${{ env.MSRV }}
- uses: Swatinem/rust-cache@v2
with:
shared-key: "build"
- name: Cache tree-sitter grammars
uses: actions/cache@v4
with:
path: runtime/grammars
key: ${{ runner.os }}-stable-v${{ env.GRAMMAR_CACHE_VERSION }}-tree-sitter-grammars-${{ hashFiles('languages.toml') }}
restore-keys: ${{ runner.os }}-stable-v${{ env.GRAMMAR_CACHE_VERSION }}-tree-sitter-grammars-
- name: Validate queries
run: cargo xtask query-check
- name: Validate themes
run: cargo xtask theme-check
- name: Generate docs
run: cargo xtask docgen
@ -119,4 +153,3 @@ jobs:
git diff-files --quiet \
|| (echo "Run 'cargo xtask docgen', commit the changes and push again" \
&& exit 1)

View file

@ -14,10 +14,10 @@ jobs:
uses: actions/checkout@v4
- name: Install nix
uses: cachix/install-nix-action@V27
uses: cachix/install-nix-action@v31
- name: Authenticate with Cachix
uses: cachix/cachix-action@v15
uses: cachix/cachix-action@v16
with:
name: helix
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}

View file

@ -1,4 +1,4 @@
name: Github Pages
name: GitHub Pages
on:
push:
@ -16,8 +16,8 @@ jobs:
- name: Setup mdBook
uses: peaceiris/actions-mdbook@v2
with:
mdbook-version: 'latest'
# mdbook-version: '0.4.8'
# mdbook-version: 'latest'
mdbook-version: '0.4.43'
- run: mdbook build book

View file

@ -61,17 +61,17 @@ jobs:
build: [x86_64-linux, x86_64-macos, x86_64-windows] #, x86_64-win-gnu, win32-msvc
include:
- build: x86_64-linux
os: ubuntu-latest
os: ubuntu-22.04
rust: stable
target: x86_64-unknown-linux-gnu
cross: false
- build: aarch64-linux
os: ubuntu-latest
os: ubuntu-22.04
rust: stable
target: aarch64-unknown-linux-gnu
cross: true
# - build: riscv64-linux
# os: ubuntu-latest
# os: ubuntu-22.04
# rust: stable
# target: riscv64gc-unknown-linux-gnu
# cross: true
@ -147,16 +147,8 @@ jobs:
if: "!matrix.skip_tests"
run: ${{ env.CARGO }} test --release --locked --target ${{ matrix.target }} --workspace
- name: Set profile.release.strip = true
shell: bash
run: |
cat >> .cargo/config.toml <<EOF
[profile.release]
strip = true
EOF
- name: Build release binary
run: ${{ env.CARGO }} build --release --locked --target ${{ matrix.target }}
run: ${{ env.CARGO }} build --profile opt --locked --target ${{ matrix.target }}
- name: Build AppImage
shell: bash
@ -183,7 +175,7 @@ jobs:
mkdir -p "$APP.AppDir"/usr/{bin,lib/helix}
cp "target/${{ matrix.target }}/release/hx" "$APP.AppDir/usr/bin/hx"
cp "target/${{ matrix.target }}/opt/hx" "$APP.AppDir/usr/bin/hx"
rm -rf runtime/grammars/sources
cp -r runtime "$APP.AppDir/usr/lib/helix/runtime"
@ -206,14 +198,25 @@ jobs:
mv "$APP-$VERSION-$ARCH.AppImage" \
"$APP-$VERSION-$ARCH.AppImage.zsync" dist
- name: Build Deb
shell: bash
if: matrix.build == 'x86_64-linux'
run: |
cargo install cargo-deb
mkdir -p target/release
cp target/${{ matrix.target }}/opt/hx target/release/
cargo deb --no-build
mkdir -p dist
mv target/debian/*.deb dist/
- name: Build archive
shell: bash
run: |
mkdir -p dist
if [ "${{ matrix.os }}" = "windows-2019" ]; then
cp "target/${{ matrix.target }}/release/hx.exe" "dist/"
cp "target/${{ matrix.target }}/opt/hx.exe" "dist/"
else
cp "target/${{ matrix.target }}/release/hx" "dist/"
cp "target/${{ matrix.target }}/opt/hx" "dist/"
fi
if [ -d runtime/grammars/sources ]; then
rm -rf runtime/grammars/sources
@ -241,6 +244,7 @@ jobs:
set -ex
source="$(pwd)"
tag=${GITHUB_REF_NAME//\//}
mkdir -p runtime/grammars/sources
tar xJf grammars/grammars.tar.xz -C runtime/grammars/sources
rm -rf grammars
@ -254,7 +258,7 @@ jobs:
if [[ $platform =~ "windows" ]]; then
exe=".exe"
fi
pkgname=helix-$GITHUB_REF_NAME-$platform
pkgname=helix-$tag-$platform
mkdir -p $pkgname
cp $source/LICENSE $source/README.md $pkgname
mkdir $pkgname/contrib
@ -265,6 +269,7 @@ jobs:
if [[ "$platform" = "x86_64-linux" ]]; then
mv bins-$platform/helix-*.AppImage* dist/
mv bins-$platform/*.deb dist/
fi
if [ "$exe" = "" ]; then
@ -274,7 +279,7 @@ jobs:
fi
done
tar cJf dist/helix-$GITHUB_REF_NAME-source.tar.xz -C $source .
tar cJf dist/helix-$tag-source.tar.xz -C $source .
mv dist $source/
- name: Upload binaries to release

1
.gitignore vendored
View file

@ -3,3 +3,4 @@ target
helix-term/rustfmt.toml
result
runtime/grammars
.DS_Store

View file

@ -1,3 +1,302 @@
<!--
# YY.0M (YYYY-0M-0D)
Breaking changes:
Features:
Commands:
Usability improvements:
Fixes:
Themes:
New languages:
Updated languages and queries:
Packaging:
-->
# 25.01.1 (2025-01-19)
25.01.1 is a patch release focusing on fixing bugs and panics from changes in 25.01.
Usability improvements:
* Run external formatters from the document's directory ([#12315](https://github.com/helix-editor/helix/pull/12315))
Fixes:
* Fix blank buffer picker preview on doc with no views ([917174e](https://github.com/helix-editor/helix/commit/917174e))
* Fix `join_selections` behavior on tabs ([#12452](https://github.com/helix-editor/helix/pull/12452))
* Fix recognition for color LSP completion hex codes for some language servers ([#12501](https://github.com/helix-editor/helix/pull/12501))
* Fix offsets to selections updated by `open_below`/`open_above` (`o`/`O`) in multi-cursor scenarios ([#12465](https://github.com/helix-editor/helix/pull/12465))
* Fix offsets to selections updated by `insert_newline` when trimming whitespace in multi-cursor scenarios ([4bd17e5](https://github.com/helix-editor/helix/commit/4bd17e5))
* Fix panic in path completion from resolving variables like `${HOME:-$HOME}` ([#12556](https://github.com/helix-editor/helix/pull/12556))
* Prevent line comment continuation when using `change_selection` (`c`) on a line above a comment ([#12575](https://github.com/helix-editor/helix/pull/12575))
Themes:
* Update `onelight` ([#12399](https://github.com/helix-editor/helix/pull/12399))
* Add cursorline color to iceberg themes ([#12404](https://github.com/helix-editor/helix/pull/12404))
* Update `special`, `ui.text.directory` and `ui.virtual.wrap` in `dark_plus` ([#12530](https://github.com/helix-editor/helix/pull/12530))
New languages:
* CodeQL ([#12470](https://github.com/helix-editor/helix/pull/12470))
* Gren ([#12525](https://github.com/helix-editor/helix/pull/12525))
Updated languages and queries:
* Fix Teal LSP name ([#12395](https://github.com/helix-editor/helix/pull/12395))
* Highlight `:` in Rust as a delimiter ([#12408](https://github.com/helix-editor/helix/pull/12408))
* Update Swift highlights ([#12409](https://github.com/helix-editor/helix/pull/12409))
* Highlight JSX attributes as `@attribute` ([#12416](https://github.com/helix-editor/helix/pull/12416))
* Improve markdown heading highlights ([#12417](https://github.com/helix-editor/helix/pull/12417))
* Add comment tokens configuration for JSONC ([b26903c](https://github.com/helix-editor/helix/commit/b26903c))
* Highlight the never type `!` as a type in Rust ([#12485](https://github.com/helix-editor/helix/pull/12485))
* Expand builtin function highlights for ECMA languages, Rust and Haskell ([#12488](https://github.com/helix-editor/helix/pull/12488))
* Recognize `.clang-tidy` as YAML ([#12498](https://github.com/helix-editor/helix/pull/12498))
* Update MATLAB grammar and indent queries ([#12518](https://github.com/helix-editor/helix/pull/12518))
* Recognize `rockspec` as Lua ([#12516](https://github.com/helix-editor/helix/pull/12516))
* Add `///` to Dart comment tokens configuration ([99d33c7](https://github.com/helix-editor/helix/commit/99d33c7))
* Update Solidity grammar and queries ([#12457](https://github.com/helix-editor/helix/pull/12457))
* Update Spade grammar and queries ([#12583](https://github.com/helix-editor/helix/pull/12583))
* Re-enable Hare fetching and building by default ([#11507](https://github.com/helix-editor/helix/pull/11507))
Packaging:
* `--version` now prints a leading zero for single-digit months, for example `25.01` (03f35af)
* Pin the Ubuntu GitHub Actions runners used for releases to `ubuntu-22.04` ([#12464](https://github.com/helix-editor/helix/pull/12464))
* Produce a Debian package (`.deb` file) in the release GitHub Actions workflow ([#12453](https://github.com/helix-editor/helix/pull/12453))
# 25.01 (2025-01-03)
As always, a big thank you to all of the contributors! This release saw changes from 171 contributors.
Breaking changes:
* The `editor.lsp.display-messages` key now controls messages sent with the LSP `window/showMessage` notification rather than progress messages. If you want to enable progress messages you should now enable the `editor.lsp.display-progress-messages` key instead. ([#5535](https://github.com/helix-editor/helix/pull/5535))
Features:
* Big refactor for `Picker`s ([#9647](https://github.com/helix-editor/helix/pull/9647), [#11209](https://github.com/helix-editor/helix/pull/11209), [#11216](https://github.com/helix-editor/helix/pull/11216), [#11211](https://github.com/helix-editor/helix/pull/11211), [#11343](https://github.com/helix-editor/helix/pull/11343), [#11406](https://github.com/helix-editor/helix/pull/11406))
* Use a table layout and allow filtering by column
* Reimplement `global_search` to allow changing the query dynamically
* Add an alternative "inline" display for LSP diagnostics ([#6417](https://github.com/helix-editor/helix/pull/6417), [#11815](https://github.com/helix-editor/helix/pull/11815))
* Support defining keybindings as macros ([#4709](https://github.com/helix-editor/helix/pull/4709))
* Continue line comments in `o`/`O` and on `<ret>` in insert mode ([#10996](https://github.com/helix-editor/helix/pull/10996), [#12213](https://github.com/helix-editor/helix/pull/12213), [#12215](https://github.com/helix-editor/helix/pull/12215))
* Allow configuring and switching clipboard providers at runtime ([#10839](https://github.com/helix-editor/helix/pull/10839), [b855cd0](https://github.com/helix-editor/helix/commit/b855cd0), [467fad5](https://github.com/helix-editor/helix/commit/467fad5), [191b0f0](https://github.com/helix-editor/helix/commit/191b0f0))
* Add support for path completion ([#2608](https://github.com/helix-editor/helix/pull/2608))
* Support bindings with the Super (Cmd/Win/Meta) modifier ([#6592](https://github.com/helix-editor/helix/pull/6592))
* Support rendering and jumping between tabstops in snippet completions ([#9801](https://github.com/helix-editor/helix/pull/9801))
* Allow theming directory completions ([#12205](https://github.com/helix-editor/helix/pull/12205), [#12295](https://github.com/helix-editor/helix/pull/12295))
Commands:
* Add commands to move within snake_case or camelCase words ([#8147](https://github.com/helix-editor/helix/pull/8147))
* Add `search_selection_detect_word_boundaries` ([#12126](https://github.com/helix-editor/helix/pull/12126))
* This command takes the `*` key in normal and select mode, replacing `search_selection` which was moved to `A-*`.
Usability improvements:
* Add `:edit` and `:e` aliases for `:open` ([#11186](https://github.com/helix-editor/helix/pull/11186), [#11196](https://github.com/helix-editor/helix/pull/11196))
* Trim trailing newline from pipe command outputs when the input doesn't have a trailing newline ([#11183](https://github.com/helix-editor/helix/pull/11183), [4f63a46](https://github.com/helix-editor/helix/commit/4f63a46))
* Add `:mv` alias for `:move` ([#11256](https://github.com/helix-editor/helix/pull/11256))
* Return document display name instead of absolute path from the `%` special register ([#11275](https://github.com/helix-editor/helix/pull/11275))
* Track view position on a per-view instead of per-document basis ([#10559](https://github.com/helix-editor/helix/pull/10559))
* Improve scrolloff calculation to leave a gap in the middle ([#11323](https://github.com/helix-editor/helix/pull/11323))
* Show a popup for stderr printed by failed `:sh` commands ([#11239](https://github.com/helix-editor/helix/pull/11239))
* Add statusline errors when nothing is selected with `s`, `K`, `A-K` ([#11370](https://github.com/helix-editor/helix/pull/11370))
* Add `.svn` as a workspace root marker ([#11429](https://github.com/helix-editor/helix/pull/11429))
* Trim the end of `:sh` outputs ([#11161](https://github.com/helix-editor/helix/pull/11161))
* Show LSP `window/showMessage` messages in the statusline ([#5535](https://github.com/helix-editor/helix/pull/5535))
* Support finding workspace directories via `.jj` directories ([#11685](https://github.com/helix-editor/helix/pull/11685))
* Join single-line comments with `join_selections` (`J`) ([#11742](https://github.com/helix-editor/helix/pull/11742))
* Show anonymous syntax tree nodes in `:tree-sitter-subtree` ([#11663](https://github.com/helix-editor/helix/pull/11663), [38e8382](https://github.com/helix-editor/helix/commit/38e8382))
* Save an undo checkpoint before paste in insert mode ([#8121](https://github.com/helix-editor/helix/pull/8121))
* Only break on ASCII spaces in `:reflow` ([#12048](https://github.com/helix-editor/helix/pull/12048))
* Add a `default-yank-register` config option ([#11430](https://github.com/helix-editor/helix/pull/11430))
* Show a statusline error for `:format` when a formatter is not available ([#12183](https://github.com/helix-editor/helix/pull/12183))
* Change to the home directory with `:cd` with no arguments ([#12042](https://github.com/helix-editor/helix/pull/12042))
* Change default comment token to `#` for unrecognized files ([#12080](https://github.com/helix-editor/helix/pull/12080), [#12266](https://github.com/helix-editor/helix/pull/12266), [bae6a58](https://github.com/helix-editor/helix/commit/bae6a58))
* Trim all trailing whitespace on `insert_newline` ([#12177](https://github.com/helix-editor/helix/pull/12177))
* Change to the prior directory with `:cd -` ([#12194](https://github.com/helix-editor/helix/pull/12194))
* Allow parsing `-` (with no modifiers) as a keybinding ([#12191](https://github.com/helix-editor/helix/pull/12191))
* Improve opening statusline and error messages when opening duplicate files or directories ([#12199](https://github.com/helix-editor/helix/pull/12199))
* Trim trailing colons in paths passed on the argv ([#9963](https://github.com/helix-editor/helix/pull/9963))
* Show tree-sitter parser availability in `hx --health <lang>` ([#12228](https://github.com/helix-editor/helix/pull/12228))
* Show a preview block for colors in the LSP completion menu ([#12299](https://github.com/helix-editor/helix/pull/12299))
* Add infobox help for `surround_add`, `surround_replace` and `surround_delete` ([#12262](https://github.com/helix-editor/helix/pull/12262))
Fixes:
* Respect document indentation settings in `format_selections` (`=`) ([#11169](https://github.com/helix-editor/helix/pull/11169))
* Avoid switching the current document to normal mode during an LSP `workspace/applyEdit` operation ([#11176](https://github.com/helix-editor/helix/pull/11176))
* Fix off-by-one in LSP `find_completion_range` ([#11266](https://github.com/helix-editor/helix/pull/11266))
* Prefer file-system mtime to local system time for detecting external modifications ([#11142](https://github.com/helix-editor/helix/pull/11142), [#11352](https://github.com/helix-editor/helix/pull/11352), [#11358](https://github.com/helix-editor/helix/pull/11358), [#11361](https://github.com/helix-editor/helix/pull/11361))
* Fix writing of hardlinks ([#11340](https://github.com/helix-editor/helix/pull/11340))
* Prevent language servers from being automatically restarted when stopped with `:lsp-stop` ([#11321](https://github.com/helix-editor/helix/pull/11321))
* Stable-sort LSP text edits ([#11357](https://github.com/helix-editor/helix/pull/11357))
* Fix determination of current language layer in documents with nested language injections ([#11365](https://github.com/helix-editor/helix/pull/11365))
* Fix a panic from `:move`ing a file to a new extension which starts a language server ([#11387](https://github.com/helix-editor/helix/pull/11387))
* Fix a panic from duplicating the diff gutter ([#11092](https://github.com/helix-editor/helix/pull/11092))
* Keep cursor position when exactly replacing text ([#5930](https://github.com/helix-editor/helix/pull/5930))
* Fix a panic from `jump_backward` on a newly opened split ([#11508](https://github.com/helix-editor/helix/pull/11508))
* Fix a panic from language servers sending an unknown diagnostic severity ([#11569](https://github.com/helix-editor/helix/pull/11569))
* Fix a panic when drawing at the edge of the screen ([#11737](https://github.com/helix-editor/helix/pull/11737))
* Fix git repo detection on symlinks ([#11732](https://github.com/helix-editor/helix/pull/11732))
* Fix a panic from a language server sending an out-of-range active signature index in `textDocument/signatureHelp` ([#11825](https://github.com/helix-editor/helix/pull/11825))
* Fix a panic from using `C-k` in a prompt ending in a multi-byte character ([#12237](https://github.com/helix-editor/helix/pull/12237))
* Expand tildes in paths passed to `:read` ([#12271](https://github.com/helix-editor/helix/pull/12271))
* Respect per-language `workspace-lsp-roots` configuration when opening new documents ([#12223](https://github.com/helix-editor/helix/pull/12223))
* Consistently replace line-endings in paste/replace commands ([c262fe4](https://github.com/helix-editor/helix/commit/c262fe4))
* Fix formatting in error statusline messages when inspecting variables in DAP ([#12354](https://github.com/helix-editor/helix/pull/12354))
* Fix invisible printing of headers in `--health` output on light terminals ([#12355](https://github.com/helix-editor/helix/pull/12355))
* Accept integers serialized as floats in the JSONRPC `id` field ([#12376](https://github.com/helix-editor/helix/pull/12376))
Themes:
* Bring `kanagawa` colors better in line with neovim version ([#11187](https://github.com/helix-editor/helix/pull/11187), [#11270](https://github.com/helix-editor/helix/pull/11270))
* Add `ao` ([#11063](https://github.com/helix-editor/helix/pull/11063))
* Update `dark_plus` ([#11415](https://github.com/helix-editor/helix/pull/11415))
* Add `iceberg-light` and `iceberg-dark` ([#10674](https://github.com/helix-editor/helix/pull/10674))
* Update everforest themes ([#11459](https://github.com/helix-editor/helix/pull/11459))
* Update gruvbox themes ([#11477](https://github.com/helix-editor/helix/pull/11477))
* Change primary selection cursor color for `naysayer` ([#11617](https://github.com/helix-editor/helix/pull/11617))
* Style picker column names in `horizon-dark` ([#11649](https://github.com/helix-editor/helix/pull/11649))
* Style picker column names in Darcula themes ([#11649](https://github.com/helix-editor/helix/pull/11649))
* Update diagnostics colors in `snazzy` ([#11731](https://github.com/helix-editor/helix/pull/11731))
* Update bogster themes ([#11353](https://github.com/helix-editor/helix/pull/11353))
* Highlight `keyword.storage` in `onedark` ([#11802](https://github.com/helix-editor/helix/pull/11802))
* Add `ui.virtual.jump-label` to `serika-dark` ([#11911](https://github.com/helix-editor/helix/pull/11911))
* Add `adwaita-light` ([#10869](https://github.com/helix-editor/helix/pull/10869))
* Add seoul256 themes ([#11466](https://github.com/helix-editor/helix/pull/11466))
* Add yo themes ([#11703](https://github.com/helix-editor/helix/pull/11703))
* Add `eiffel` ([#11679](https://github.com/helix-editor/helix/pull/11679))
* Add `carbonfox` ([#11558](https://github.com/helix-editor/helix/pull/11558))
* Set tags color in monokai themes ([#11917](https://github.com/helix-editor/helix/pull/11917))
* Improve readability of spacebones picker selection ([#12064](https://github.com/helix-editor/helix/pull/12064))
* Update modus themes ([#11949](https://github.com/helix-editor/helix/pull/11949))
* Use bold for statusline mode indicator in `onedarker` ([#11958](https://github.com/helix-editor/helix/pull/11958))
* Update hex themes, add a new hex theme ([#10849](https://github.com/helix-editor/helix/pull/10849))
* Add `sunset` ([#12093](https://github.com/helix-editor/helix/pull/12093))
* Add bufferline highlighting for flexoki themes ([#12146](https://github.com/helix-editor/helix/pull/12146))
* Add colors for (un)checked list items to catppuccin themes ([#12167](https://github.com/helix-editor/helix/pull/12167))
* Update `voxed` ([#9328](https://github.com/helix-editor/helix/pull/9328))
* Add `vintage` ([#9361](https://github.com/helix-editor/helix/pull/9361))
* Add directory style to everforest themes ([#12287](https://github.com/helix-editor/helix/pull/12287))
* Add inactive text and update jump label highlights in `dark_plus` ([#12289](https://github.com/helix-editor/helix/pull/12289))
* Sync changes with catppuccin themes ([#12304](https://github.com/helix-editor/helix/pull/12304))
* Add `ui.text.directory` to `nightfox` ([#12328](https://github.com/helix-editor/helix/pull/12328))
* Add `ui.text.directory` to `sunset` ([#12328](https://github.com/helix-editor/helix/pull/12328))
* Add `diagnostic.unnecessary` to Catppuccin themes ([#12391](https://github.com/helix-editor/helix/pull/12391))
New languages:
* `jjdescription` ([#11271](https://github.com/helix-editor/helix/pull/11271), [#11857](https://github.com/helix-editor/helix/pull/11857), [#12305](https://github.com/helix-editor/helix/pull/12305))
* i3wm and Sway configs ([#11424](https://github.com/helix-editor/helix/pull/11424))
* TypeSpec ([#11412](https://github.com/helix-editor/helix/pull/11412))
* jq ([#11393](https://github.com/helix-editor/helix/pull/11393))
* Thrift ([#11367](https://github.com/helix-editor/helix/pull/11367))
* Gherkin ([#11083](https://github.com/helix-editor/helix/pull/11083))
* Circom ([#11676](https://github.com/helix-editor/helix/pull/11676))
* Dune ([#11829](https://github.com/helix-editor/helix/pull/11829))
* Snakemake ([#11858](https://github.com/helix-editor/helix/pull/11858), [#11936](https://github.com/helix-editor/helix/pull/11936))
* Cylc ([#11830](https://github.com/helix-editor/helix/pull/11830))
* textproto ([#11874](https://github.com/helix-editor/helix/pull/11874))
* Spade ([#11448](https://github.com/helix-editor/helix/pull/11448), [#12276](https://github.com/helix-editor/helix/pull/12276))
* NestedText ([#11987](https://github.com/helix-editor/helix/pull/11987))
* Quint ([#11898](https://github.com/helix-editor/helix/pull/11898))
* Amber-lang ([#12021](https://github.com/helix-editor/helix/pull/12021))
* Vento ([#12147](https://github.com/helix-editor/helix/pull/12147))
* Teal ([#12081](https://github.com/helix-editor/helix/pull/12081))
* Koto ([#12307](https://github.com/helix-editor/helix/pull/12307))
* NGINX ([#12309](https://github.com/helix-editor/helix/pull/12309))
Updated languages and queries:
* Add comment injections for Hare ([#11173](https://github.com/helix-editor/helix/pull/11173))
* Improve highlights for `blade.php` files ([#11138](https://github.com/helix-editor/helix/pull/11138))
* Update tree-sitter-slint ([#11224](https://github.com/helix-editor/helix/pull/11224), [#11757](https://github.com/helix-editor/helix/pull/11757), [#12297](https://github.com/helix-editor/helix/pull/12297))
* Recognize `just` files as Just ([#11286](https://github.com/helix-editor/helix/pull/11286))
* Recognize `mdx` as Markdown ([#11122](https://github.com/helix-editor/helix/pull/11122))
* Update Just grammar and queries ([#11306](https://github.com/helix-editor/helix/pull/11306))
* Recognize `tclsh` as TCL ([#11236](https://github.com/helix-editor/helix/pull/11236))
* Update Godot grammar and queries ([#11235](https://github.com/helix-editor/helix/pull/11235))
* Update Gleam grammar and queries ([#11427](https://github.com/helix-editor/helix/pull/11427))
* Add `mesonlsp` for Meson ([#11416](https://github.com/helix-editor/helix/pull/11416))
* Update HTML highlights ([#11400](https://github.com/helix-editor/helix/pull/11400))
* Add comment textobjects for Verilog ([#11388](https://github.com/helix-editor/helix/pull/11388))
* Switch tree-sitter-just grammar ([#11380](https://github.com/helix-editor/helix/pull/11380), [#11606](https://github.com/helix-editor/helix/pull/11606), [#12141](https://github.com/helix-editor/helix/pull/12141))
* Update tree-sitter-fsharp ([#11061](https://github.com/helix-editor/helix/pull/11061))
* Add `nixd` for Nix ([#10767](https://github.com/helix-editor/helix/pull/10767))
* Highlight types and enum members from the Rust prelude ([#8535](https://github.com/helix-editor/helix/pull/8535))
* Improve textobjects for HCL, Nix ([#11513](https://github.com/helix-editor/helix/pull/11513))
* Add textobjects queries for docker-compose, dockerfile, env, git-config, hcl, hocon, prisma, SQL and YAML ([#11513](https://github.com/helix-editor/helix/pull/11513))
* Recognize cshtml files as HTML ([#11540](https://github.com/helix-editor/helix/pull/11540))
* Set a memory limit for the Lean language server ([#11683](https://github.com/helix-editor/helix/pull/11683))
* Add configurations for jedi and ruff language servers ([#11630](https://github.com/helix-editor/helix/pull/11630))
* Update Vue highlights ([#11706](https://github.com/helix-editor/helix/pull/11706))
* Switch tree-sitter-hcl grammar ([#11749](https://github.com/helix-editor/helix/pull/11749))
* Fix `odinfmt` formatter configuration ([#11759](https://github.com/helix-editor/helix/pull/11759))
* Recognize `rbs` files as Ruby ([#11786](https://github.com/helix-editor/helix/pull/11786))
* Update tree-sitter-nickel ([#11771](https://github.com/helix-editor/helix/pull/11771))
* Recognize `ldtk` and `ldtkl` files as JSON ([#11793](https://github.com/helix-editor/helix/pull/11793))
* Fix highlights for builtin functions in Fish ([#11792](https://github.com/helix-editor/helix/pull/11792))
* Add `superhtml` for HTML ([#11609](https://github.com/helix-editor/helix/pull/11609))
* Add a configuration for the Vale language server ([#11636](https://github.com/helix-editor/helix/pull/11636))
* Add Erlang Language Platform (`elp`) for Erlang ([#11499](https://github.com/helix-editor/helix/pull/11499))
* Update Odin highlights ([#11804](https://github.com/helix-editor/helix/pull/11804))
* Remove auto-pairs for single quotes in SML ([#11838](https://github.com/helix-editor/helix/pull/11838))
* Add `glsl_analyzer` for GLSL ([#11891](https://github.com/helix-editor/helix/pull/11891))
* Recognize `.prettierrc` as YAML ([#11997](https://github.com/helix-editor/helix/pull/11997))
* Fix `swift-format` formatter configuration ([#12052](https://github.com/helix-editor/helix/pull/12052))
* Add `package.json` and `tsconfig.json` as JS/TS workspace roots ([#10652](https://github.com/helix-editor/helix/pull/10652))
* Add "INVARIANT" to comment error highlights ([#12094](https://github.com/helix-editor/helix/pull/12094))
* Update Rescript grammar and queries ([#11165](https://github.com/helix-editor/helix/pull/11165))
* Update tree-sitter-nasm ([#11795](https://github.com/helix-editor/helix/pull/11795))
* Update LLVM grammars ([#11851](https://github.com/helix-editor/helix/pull/11851))
* Update Perl and Pod grammars ([#11848](https://github.com/helix-editor/helix/pull/11848))
* Add Nim injections in Nix ([#11837](https://github.com/helix-editor/helix/pull/11837))
* Recognize `livemd` as Markdown ([#12034](https://github.com/helix-editor/helix/pull/12034))
* Update Unison grammar and queries ([#12039](https://github.com/helix-editor/helix/pull/12039))
* Turn off Swift auto-format by default ([#12071](https://github.com/helix-editor/helix/pull/12071))
* Recognize `.swift-format` as JSON ([#12071](https://github.com/helix-editor/helix/pull/12071))
* Recognize `.clangd` and `.clang-format` as YAML ([#12032](https://github.com/helix-editor/helix/pull/12032))
* Recognize `ssh_config.d/*.conf` as sshclientconfig ([#11947](https://github.com/helix-editor/helix/pull/11947))
* Update comment token configs for Zig ([#12049](https://github.com/helix-editor/helix/pull/12049))
* Update tree-sitter-bicep ([#11525](https://github.com/helix-editor/helix/pull/11525))
* Add `hyperls` for Hyperlang ([#11056](https://github.com/helix-editor/helix/pull/11056))
* Add highlight queries for Solidity ([#12102](https://github.com/helix-editor/helix/pull/12102))
* Recognize `WORKSPACE.bzlmod` as Starlark ([#12103](https://github.com/helix-editor/helix/pull/12103))
* Update Ada grammar and queries ([#12131](https://github.com/helix-editor/helix/pull/12131))
* Restrict Hocon file-types glob patterns ([#12156](https://github.com/helix-editor/helix/pull/12156))
* Update Mojo language server to Magic ([#12195](https://github.com/helix-editor/helix/pull/12195))
* Switch tree-sitter-v grammar ([#12236](https://github.com/helix-editor/helix/pull/12236))
* Add "COMPLIANCE" to comment error highlights ([#12094](https://github.com/helix-editor/helix/pull/12094))
* Add a language server configuration for `ltex-ls-plus` ([#12251](https://github.com/helix-editor/helix/pull/12251))
* Update tree-sitter-dockerfile ([#12230](https://github.com/helix-editor/helix/pull/12230))
* Add `]` to PHP outdents ([#12286](https://github.com/helix-editor/helix/pull/12286))
* Add textobjects for Odin ([#12302](https://github.com/helix-editor/helix/pull/12302))
* Update tree-sitter-heex and queries ([#12334](https://github.com/helix-editor/helix/pull/12334))
* Update protobuf highlights ([#12339](https://github.com/helix-editor/helix/pull/12339))
* Switch tree-sitter-query (TSQ) grammar ([#12148](https://github.com/helix-editor/helix/pull/12148), [e0bccd2](https://github.com/helix-editor/helix/commit/e0bccd2))
* Add block comment configurations for jinja and nunjucks ([#12348](https://github.com/helix-editor/helix/pull/12348))
* Add `uv` shebang for python ([#12360](https://github.com/helix-editor/helix/pull/12360))
* Update tree-sitter-vento ([#12368](https://github.com/helix-editor/helix/pull/12368))
* Switch Protobuf tree-sitter grammar ([#12225](https://github.com/helix-editor/helix/pull/12225))
* Recognize `hypr/*.conf` as Hyprland ([#12384](https://github.com/helix-editor/helix/pull/12384))
Packaging:
* Add completions for Nushell ([#11262](https://github.com/helix-editor/helix/pull/11262), [#11346](https://github.com/helix-editor/helix/pull/11346))
* Fix completion of flags in Bash completions ([#11246](https://github.com/helix-editor/helix/pull/11246))
* Include shell completions in Nix outputs ([#11518](https://github.com/helix-editor/helix/pull/11518))
# 24.07 (2024-07-14)
Thanks to all of the contributors! This release has changes from 160 contributors.
@ -16,7 +315,7 @@ Features:
Commands:
- `select_all_siblings` (`A-a`) - select all siblings of each selection ([87c4161](https://github.com/helix-editor/helix/commit/87c4161))
- `select_all_children` (`A-i`) - select all children of each selection ([fa67c5c](https://github.com/helix-editor/helix/commit/fa67c5c))
- `select_all_children` (`A-I`) - select all children of each selection ([fa67c5c](https://github.com/helix-editor/helix/commit/fa67c5c))
- `:read` - insert the contents of the given file at each selection ([#10447](https://github.com/helix-editor/helix/pull/10447))
Usability improvements:
@ -436,7 +735,7 @@ Updated languages and queries:
- Recognize common Dockerfile file types ([#9772](https://github.com/helix-editor/helix/pull/9772))
- Recognize NUON files as Nu ([#9839](https://github.com/helix-editor/helix/pull/9839))
- Add textobjects for Java native functions and constructors ([#9806](https://github.com/helix-editor/helix/pull/9806))
- Fix "braket" typeo in JSX highlights ([#9910](https://github.com/helix-editor/helix/pull/9910))
- Fix "braket" typo in JSX highlights ([#9910](https://github.com/helix-editor/helix/pull/9910))
- Update tree-sitter-hurl ([#9775](https://github.com/helix-editor/helix/pull/9775))
- Add textobjects queries for Vala ([#8541](https://github.com/helix-editor/helix/pull/8541))
- Update tree-sitter-git-config ([9610254](https://github.com/helix-editor/helix/commit/9610254))
@ -643,7 +942,7 @@ Updated languages and queries:
- Add Fortran comment injections ([#7305](https://github.com/helix-editor/helix/pull/7305))
- Switch Vue language server to `vue-language-server` ([#7312](https://github.com/helix-editor/helix/pull/7312))
- Update tree-sitter-sql ([#7387](https://github.com/helix-editor/helix/pull/7387), [#8464](https://github.com/helix-editor/helix/pull/8464))
- Replace the MATLAB tre-sitter grammar ([#7388](https://github.com/helix-editor/helix/pull/7388), [#7442](https://github.com/helix-editor/helix/pull/7442), [#7491](https://github.com/helix-editor/helix/pull/7491), [#7493](https://github.com/helix-editor/helix/pull/7493), [#7511](https://github.com/helix-editor/helix/pull/7511), [#7532](https://github.com/helix-editor/helix/pull/7532), [#8040](https://github.com/helix-editor/helix/pull/8040))
- Replace the MATLAB tree-sitter grammar ([#7388](https://github.com/helix-editor/helix/pull/7388), [#7442](https://github.com/helix-editor/helix/pull/7442), [#7491](https://github.com/helix-editor/helix/pull/7491), [#7493](https://github.com/helix-editor/helix/pull/7493), [#7511](https://github.com/helix-editor/helix/pull/7511), [#7532](https://github.com/helix-editor/helix/pull/7532), [#8040](https://github.com/helix-editor/helix/pull/8040))
- Highlight TOML table headers ([#7441](https://github.com/helix-editor/helix/pull/7441))
- Recognize `cppm` file-type as C++ ([#7492](https://github.com/helix-editor/helix/pull/7492))
- Refactor ecma language queries into private and public queries ([#7207](https://github.com/helix-editor/helix/pull/7207))
@ -1130,7 +1429,7 @@ Features:
- Support underline styles and colors ([#4061](https://github.com/helix-editor/helix/pull/4061), [98c121c](https://github.com/helix-editor/helix/commit/98c121c))
- Inheritance for themes ([#3067](https://github.com/helix-editor/helix/pull/3067), [#4096](https://github.com/helix-editor/helix/pull/4096))
- Cursorcolumn ([#4084](https://github.com/helix-editor/helix/pull/4084))
- Overhauled system for writing files and quiting ([#2267](https://github.com/helix-editor/helix/pull/2267), [#4397](https://github.com/helix-editor/helix/pull/4397))
- Overhauled system for writing files and quitting ([#2267](https://github.com/helix-editor/helix/pull/2267), [#4397](https://github.com/helix-editor/helix/pull/4397))
- Autosave when terminal loses focus ([#3178](https://github.com/helix-editor/helix/pull/3178))
- Use OSC52 as a fallback for the system clipboard ([#3220](https://github.com/helix-editor/helix/pull/3220))
- Show git diffs in the gutter ([#3890](https://github.com/helix-editor/helix/pull/3890), [#5012](https://github.com/helix-editor/helix/pull/5012), [#4995](https://github.com/helix-editor/helix/pull/4995))
@ -1285,7 +1584,7 @@ Themes:
- Update `pop-dark` ([#4323](https://github.com/helix-editor/helix/pull/4323))
- Update `rose_pine` ([#4221](https://github.com/helix-editor/helix/pull/4221))
- Add `kanagawa` ([#4300](https://github.com/helix-editor/helix/pull/4300))
- Add `hex_steel`, `hex_toxic` and `hex_lavendar` ([#4367](https://github.com/helix-editor/helix/pull/4367), [#4990](https://github.com/helix-editor/helix/pull/4990))
- Add `hex_steel`, `hex_toxic` and `hex_lavender` ([#4367](https://github.com/helix-editor/helix/pull/4367), [#4990](https://github.com/helix-editor/helix/pull/4990))
- Update `tokyonight` and `tokyonight_storm` ([#4415](https://github.com/helix-editor/helix/pull/4415))
- Update `gruvbox` ([#4626](https://github.com/helix-editor/helix/pull/4626))
- Update `dark_plus` ([#4661](https://github.com/helix-editor/helix/pull/4661), [#4678](https://github.com/helix-editor/helix/pull/4678))
@ -1452,7 +1751,7 @@ Usability improvements and fixes:
- Introduce `keyword.storage` highlight scope ([#2731](https://github.com/helix-editor/helix/pull/2731))
- Handle symlinks more consistently ([#2718](https://github.com/helix-editor/helix/pull/2718))
- Improve markdown list rendering ([#2687](https://github.com/helix-editor/helix/pull/2687))
- Update auto-pairs and idle-timout settings when the config is reloaded ([#2736](https://github.com/helix-editor/helix/pull/2736))
- Update auto-pairs and idle-timeout settings when the config is reloaded ([#2736](https://github.com/helix-editor/helix/pull/2736))
- Fix panic on closing last buffer ([#2658](https://github.com/helix-editor/helix/pull/2658))
- Prevent modifying jumplist until jumping to a reference ([#2670](https://github.com/helix-editor/helix/pull/2670))
- Ensure `:quit` and `:quit!` take no arguments ([#2654](https://github.com/helix-editor/helix/pull/2654))

1905
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -5,6 +5,7 @@ members = [
"helix-view",
"helix-term",
"helix-tui",
"helix-lsp-types",
"helix-lsp",
"helix-event",
"helix-dap",
@ -21,13 +22,12 @@ default-members = [
[profile.release]
lto = "thin"
# debug = true
[profile.opt]
inherits = "release"
lto = "fat"
codegen-units = 1
# strip = "debuginfo" # TODO: or strip = true
strip = true
opt-level = 3
[profile.integration]
@ -38,16 +38,22 @@ package.helix-term.opt-level = 2
[workspace.dependencies]
tree-sitter = { version = "0.22" }
nucleo = "0.2.0"
nucleo = "0.5.0"
slotmap = "1.0.7"
thiserror = "1.0"
thiserror = "2.0"
tempfile = "3.19.1"
bitflags = "2.9"
unicode-segmentation = "1.2"
ropey = { version = "1.6.1", default-features = false, features = ["simd"] }
foldhash = "0.1"
parking_lot = "0.12"
[workspace.package]
version = "24.7.0"
version = "25.1.1"
edition = "2021"
authors = ["Blaž Hrastnik <blaz@mxxn.io>"]
categories = ["editor"]
repository = "https://github.com/helix-editor/helix"
homepage = "https://helix-editor.com"
license = "MPL-2.0"
rust-version = "1.70"
rust-version = "1.76"

View file

@ -37,8 +37,8 @@ All shortcuts/keymaps can be found [in the documentation on the website](https:/
- Built-in language server support
- Smart, incremental syntax highlighting and code editing via tree-sitter
It's a terminal-based editor first, but I'd like to explore a custom renderer
(similar to Emacs) in wgpu or skulpin.
Although it's primarily a terminal-based editor, I am interested in exploring
a custom renderer (similar to Emacs) using wgpu or skulpin.
Note: Only certain languages have indentation definitions at the moment. Check
`runtime/queries/<lang>/` for `indents.scm`.
@ -47,7 +47,7 @@ Note: Only certain languages have indentation definitions at the moment. Check
[Installation documentation](https://docs.helix-editor.com/install.html).
[![Packaging status](https://repology.org/badge/vertical-allrepos/helix.svg?exclude_unsupported=1)](https://repology.org/project/helix/versions)
[![Packaging status](https://repology.org/badge/vertical-allrepos/helix-editor.svg?exclude_unsupported=1)](https://repology.org/project/helix-editor/versions)
# Contributing

View file

@ -10,10 +10,14 @@
- [Surround](./surround.md)
- [Textobjects](./textobjects.md)
- [Syntax aware motions](./syntax-aware-motions.md)
- [Pickers](./pickers.md)
- [Keymap](./keymap.md)
- [Command line](./command-line.md)
- [Commands](./commands.md)
- [Language support](./lang-support.md)
- [Migrating from Vim](./from-vim.md)
- [Ecosystem](./ecosystem.md)
- [Migrating from Vim](./from-vim.md)
- [Helix mode in other software](./other-software.md)
- [Configuration](./configuration.md)
- [Editor](./editor.md)
- [Themes](./themes.md)

View file

@ -7,6 +7,7 @@
- [Note to packagers](#note-to-packagers)
- [Validating the installation](#validating-the-installation)
- [Configure the desktop shortcut](#configure-the-desktop-shortcut)
- [Building the Debian package](#building-the-debian-package)
Requirements:
@ -63,11 +64,9 @@ export HELIX_RUNTIME=~/src/helix/runtime
Or, create a symbolic link:
```sh
ln -Ts $PWD/runtime ~/.config/helix/runtime
ln -Tsf $PWD/runtime ~/.config/helix/runtime
```
If the above command fails to create a symbolic link because the file exists either move `~/.config/helix/runtime` to a new location or delete it, then run the symlink command above again.
#### Windows
Either set the `HELIX_RUNTIME` environment variable to point to the runtime files using the Windows setting (search for
@ -75,7 +74,7 @@ Either set the `HELIX_RUNTIME` environment variable to point to the runtime file
Cmd:
```sh
setx HELIX_RUNTIME "%userprofile%\source\repos\helix\runtime"
setx HELIX_RUNTIME "%userprofile%\src\helix\runtime"
```
> 💡 `%userprofile%` resolves to your user directory like
@ -117,7 +116,7 @@ to package the runtime into `/usr/lib/helix/runtime`. The rough steps a build
script could follow are:
1. `export HELIX_DEFAULT_RUNTIME=/usr/lib/helix/runtime`
1. `cargo build --profile opt --locked --path helix-term`
1. `cargo build --profile opt --locked`
1. `cp -r runtime $BUILD_DIR/usr/lib/helix/`
1. `cp target/opt/hx $BUILD_DIR/usr/bin/hx`
@ -162,3 +161,39 @@ file. For example, to use `kitty`:
sed -i "s|Exec=hx %F|Exec=kitty hx %F|g" ~/.local/share/applications/Helix.desktop
sed -i "s|Terminal=true|Terminal=false|g" ~/.local/share/applications/Helix.desktop
```
### Building the Debian package
If the `.deb` file provided on the release page uses a `libc` version higher
than that used by your Debian, Ubuntu, or Mint system, you can build the package
from source to match your system's dependencies.
Install `cargo-deb`, the tool used for building the `.deb` file:
```sh
cargo install cargo-deb
```
After cloning and entering the Helix repository as previously described,
use the following command to build the release binary and package it into a `.deb` file in a single step.
```sh
cargo deb -- --locked
```
> 💡 This locks you into the `--release` profile. But you can also build helix in any way you like.
> As long as you leave a `target/release/hx` file, it will get packaged with `cargo deb --no-build`
> 💡 Don't worry about the repeated
> ```
> warning: Failed to find dependency specification
> ```
> warnings. Cargo deb just reports which packaged files it didn't derive dependencies for. But
> so far the dependency deriving seams very good, even if some of the grammar files are skipped.
You can find the resulted `.deb` in `target/debian/`. It should contain everything it needs, including the
- completions for bash, fish, zsh
- .desktop file
- icon (though desktop environments might use their own since the name of the package is correctly `helix`)
- launcher to the binary with the runtime

82
book/src/command-line.md Normal file
View file

@ -0,0 +1,82 @@
# Command line
- [Quoting](#quoting)
- [Flags](#flags)
- [Expansions](#expansions)
- [Exceptions](#exceptions)
The command line is used for executing [typable commands](./commands.md#typable-commands) like `:write` or `:quit`. Press `:` to activate the command line.
Typable commands optionally accept arguments. `:write` for example accepts an optional path to write the file contents. The command line also supports a quoting syntax for arguments, flags to modify command behaviors, and _expansions_ - a way to insert values from the editor. Most commands support these features but some have custom parsing rules (see the [exceptions](#exceptions) below).
## Quoting
By default, command arguments are split on tabs and space characters. `:open README.md CHANGELOG.md` for example should open two files, `README.md` and `CHANGELOG.md`. Arguments that contain spaces can be surrounded in single quotes (`'`) or backticks (`` ` ``) to prevent the space from separating the argument, like `:open 'a b.txt'`.
Double quotes may be used the same way, but double quotes _expand_ their inner content. `:echo "%{cursor_line}"` for example may print `1` because of the expansion for the `cursor_line` variable. `:echo '%{cursor_line}'` though prints `%{cursor_line}` literally: content within single quotes or backticks is interpreted as-is.
On Unix systems the backslash character may be used to escape certain characters depending on where it is used. Within an argument which isn't surround in quotes, the backslash can be used to escape the space or tab characters: `:open a\ b.txt` is equivalent to `:open 'a b.txt'`. The backslash may also be used to escape quote characters (`'`, `` ` ``, `"`) or the percent token (`%`) when used at the beginning of an argument. `:echo \%%sh{foo}` for example prints `%sh{foo}` instead of invoking a `foo` shell command and `:echo \"quote` prints `"quote`. The backslash character is treated literally in any other situation on Unix systems and always on Windows: `:echo \n` always prints `\n`.
## Flags
Command flags are optional switches that can be used to alter the behavior of a command. For example the `:sort` command accepts an optional `--reverse` (or `-r` for short) flag which causes the sort command to reverse the sorting direction. Typing the `-` character shows completions for the current command's flags, if any.
The `--` flag specifies the end of flags. All arguments after `--` are treated as positional arguments: `:open -- -a.txt` opens a file called `-a.txt`.
## Expansions
Expansions are patterns that Helix recognizes and replaces within the command line. Helix recognizes anything starting with a percent token (`%`) as an expansion, for example `%sh{echo hi!}`. Expansions are particularly useful when used in commands like `:echo` or `:noop` for executing simple scripts. For example:
```toml
[keys.normal]
# Print the current line's git blame information to the statusline.
space.B = ":echo %sh{git blame -L %{cursor_line},+1 %{buffer_name}}"
```
Expansions take the form `%[<kind>]<open><contents><close>`. In `%sh{echo hi!}`, for example, the kind is `sh` - the shell expansion - and the contents are "echo hi!", with `{` and `}` acting as opening and closing delimiters. The following open/close characters are recognized as expansion delimiter pairs: `(`/`)`, `[`/`]`, `{`/`}` and `<`/`>`. Plus the single characters `'`, `"` or `|` may be used instead: `%{cursor_line}` is equivalent to `%<cursor_line>`, `%[cursor_line]` or `%|cursor_line|`.
To escape a percent character instead of treating it as an expansion, use two percent characters consecutively. To execute a shell command like `date -u +'%Y-%m-%d'`, double the percent characters: `:echo %sh{date -u +'%%Y-%%m-%%d'}`.
When no `<kind>` is provided, Helix will expand a **variable**. For example `%{cursor_line}` can be used as in argument to insert the line number. `:echo %{cursor_line}` for instance may print `1` to the statusline.
The following variables are supported:
| Name | Description |
|--- |--- |
| `cursor_line` | The line number of the primary cursor in the currently focused document, starting at 1. |
| `cursor_column` | The column number of the primary cursor in the currently focused document, starting at 1. This is counted as the number of grapheme clusters from the start of the line rather than bytes or codepoints. |
| `buffer_name` | The relative path of the currently focused document. `[scratch]` is expanded instead for scratch buffers. |
| `line_ending` | A string containing the line ending of the currently focused document. For example on Unix systems this is usually a line-feed character (`\n`) but on Windows systems this may be a carriage-return plus a line-feed (`\r\n`). The line ending kind of the currently focused document can be inspected with the `:line-ending` command. |
Aside from editor variables, the following expansions may be used:
* Unicode `%u{..}`. The contents may contain up to six hexadecimal numbers corresponding to a Unicode codepoint value. For example `:echo %u{25CF}` prints `●` to the statusline.
* Shell `%sh{..}`. The contents are passed to the configured shell command. For example `:echo %sh{echo "20 * 5" | bc}` may print `100` on the statusline on when using a shell with `echo` and the `bc` calculator installed. Shell expansions are evaluated recursively. `%sh{echo '%{buffer_name}:%{cursor_line}'}` for example executes a command like `echo 'README.md:1'`: the variables within the `%sh{..}` expansion are evaluated before executing the shell command.
As mentioned above, double quotes can be used to surround arguments containing spaces but also support expansions within the quoted content unlike singe quotes or backticks. For example `:echo "circle: %u{25CF}"` prints `circle: ●` to the statusline while `:echo 'circle: %u{25CF}'` prints `circle: %u{25CF}`.
Note that expansions are only evaluated once the Enter key is pressed in command mode.
## Exceptions
The following commands support expansions but otherwise pass the given argument directly to the shell program without interpreting quotes:
* `:insert-output`
* `:append-output`
* `:pipe`
* `:pipe-to`
* `:run-shell-command`
For example executing `:sh echo "%{buffer_name}:%{cursor_column}"` would pass text like `echo "README.md:1"` as an argument to the shell program: the expansions are evaluated but not the quotes. As mentioned above, percent characters can be used in shell commands by doubling the percent character. To insert the output of a command like `date -u +'%Y-%m-%d'` use `:insert-output date -u +'%%Y-%%m-%%d'`.
The `:set-option` and `:toggle-option` commands use regular parsing for the first argument - the config option name - and parse the rest depending on the config option's type. `:set-option` interprets the second argument as a string for string config options and parses everything else as JSON.
`:toggle-option`'s behavior depends on the JSON type of the config option supplied as the first argument:
* Booleans: only the config option name should be provided. For example `:toggle-option auto-format` will flip the `auto-format` option.
* Strings: the rest of the command line is parsed with regular quoting rules. For example `:toggle-option indent-heuristic hybrid tree-sitter simple` cycles through "hybrid", "tree-sitter" and "simple" values on each invocation of the command.
* Numbers, arrays and objects: the rest of the command line is parsed as a stream of JSON values. For example `:toggle-option rulers [81] [51, 73]` cycles through `[81]` and `[51, 73]`.
When providing multiple values to `:toggle-option` there should be no duplicates. `:toggle-option indent-heuristic hybrid simple tree-sitter simple` for example would only toggle between "hybrid" and "tree-sitter" values.
`:lsp-workspace-command` works similarly to `:toggle-option`. The first argument (if present) is parsed according to normal rules. The rest of the line is parsed as JSON values. Unlike `:toggle-option`, string arguments for a command must be quoted. For example `:lsp-workspace-command lsp.Command "foo" "bar"`.

View file

@ -1,5 +1,16 @@
# Commands
Command mode can be activated by pressing `:`. The built-in commands are:
- [Typable commands](#typable-commands)
- [Static commands](#static-commands)
## Typable commands
Typable commands are used from command mode and may take arguments. Command mode can be activated by pressing `:`. The built-in typable commands are:
{{#include ./generated/typable-cmd.md}}
## Static Commands
Static commands take no arguments and can be bound to keys. Static commands can also be executed from the command picker (`<space>?`). The built-in static commands are:
{{#include ./generated/static-cmd.md}}

View file

@ -27,8 +27,8 @@ hidden = false
You can use a custom configuration file by specifying it with the `-c` or
`--config` command line argument, for example `hx -c path/to/custom-config.toml`.
Additionally, you can reload the configuration file by sending the USR1
signal to the Helix process on Unix operating systems, such as by using the command `pkill -USR1 hx`.
You can reload the config file by issuing the `:config-reload` command. Alternatively, on Unix operating systems, you can reload it by sending the USR1
signal to the Helix process, such as by using the command `pkill -USR1 hx`.
Finally, you can have a `config.toml` local to a project by putting it under a `.helix` directory in your repository.
Its settings will be merged with the configuration directory `config.toml` and the built-in configuration.

3
book/src/ecosystem.md Normal file
View file

@ -0,0 +1,3 @@
# Ecosystem
This section has information related to the wider Helix ecosystem.

View file

@ -1,11 +1,14 @@
## Editor
- [`[editor]` Section](#editor-section)
- [`[editor.clipboard-provider]` Section](#editorclipboard-provider-section)
- [`[editor.statusline]` Section](#editorstatusline-section)
- [`[editor.lsp]` Section](#editorlsp-section)
- [`[editor.inline-blame]` Section](#editorinlineblame-section)
- [`[editor.cursor-shape]` Section](#editorcursor-shape-section)
- [`[editor.file-picker]` Section](#editorfile-picker-section)
- [`[editor.auto-pairs]` Section](#editorauto-pairs-section)
- [`[editor.auto-save]` Section](#editorauto-save-section)
- [`[editor.search]` Section](#editorsearch-section)
- [`[editor.whitespace]` Section](#editorwhitespace-section)
- [`[editor.indent-guides]` Section](#editorindent-guides-section)
@ -16,6 +19,7 @@
- [`[editor.gutters.spacer]` Section](#editorguttersspacer-section)
- [`[editor.soft-wrap]` Section](#editorsoft-wrap-section)
- [`[editor.smart-tab]` Section](#editorsmart-tab-section)
- [`[editor.inline-diagnostics]` Section](#editorinline-diagnostics-section)
### `[editor]` Section
@ -23,14 +27,17 @@
|--|--|---------|
| `scrolloff` | Number of lines of padding around the edge of the screen when scrolling | `5` |
| `mouse` | Enable mouse mode | `true` |
| `default-yank-register` | Default register used for yank/paste | `"` |
| `middle-click-paste` | Middle click paste support | `true` |
| `scroll-lines` | Number of lines to scroll per scroll wheel step | `3` |
| `shell` | Shell to use when running external commands | Unix: `["sh", "-c"]`<br/>Windows: `["cmd", "/C"]` |
| `line-number` | Line number display: `absolute` simply shows each line's number, while `relative` shows the distance from the current line. When unfocused or in insert mode, `relative` will still show absolute line numbers | `absolute` |
| `cursorline` | Highlight all lines with a cursor | `false` |
| `cursorcolumn` | Highlight all columns with a cursor | `false` |
| `continue-comments` | if helix should automatically add a line comment token if you create a new line inside a comment. | `true` |
| `gutters` | Gutters to display: Available are `diagnostics` and `diff` and `line-numbers` and `spacer`, note that `diagnostics` also includes other features like breakpoints, 1-width padding will be inserted if gutters is non-empty | `["diagnostics", "spacer", "line-numbers", "spacer", "diff"]` |
| `auto-completion` | Enable automatic pop up of auto-completion | `true` |
| `path-completion` | Enable filepath completion. Show files and directories if an existing path at the cursor was recognized, either absolute or relative to the current opened document or current working directory (if the buffer is not yet saved). Defaults to true. | `true` |
| `auto-format` | Enable automatic formatting on save | `true` |
| `idle-timeout` | Time in milliseconds since last keypress before idle timers trigger. | `250` |
| `completion-timeout` | Time in milliseconds after typing a word character before completions are shown, set to 5 for instant. | `250` |
@ -47,9 +54,39 @@
| `workspace-lsp-roots` | Directories relative to the workspace root that are treated as LSP roots. Should only be set in `.helix/config.toml` | `[]` |
| `default-line-ending` | The line ending to use for new documents. Can be `native`, `lf`, `crlf`, `ff`, `cr` or `nel`. `native` uses the platform's native line ending (`crlf` on Windows, otherwise `lf`). | `native` |
| `insert-final-newline` | Whether to automatically insert a trailing line-ending on write if missing | `true` |
| `trim-final-newlines` | Whether to automatically remove line-endings after the final one on write | `false` |
| `trim-trailing-whitespace` | Whether to automatically remove whitespace preceding line endings on write | `false` |
| `popup-border` | Draw border around `popup`, `menu`, `all`, or `none` | `none` |
| `rounded-corners` | Set to `true` to draw rounded border corners | `false` |
| `indent-heuristic` | How the indentation for a newly inserted line is computed: `simple` just copies the indentation level from the previous line, `tree-sitter` computes the indentation based on the syntax tree and `hybrid` combines both approaches. If the chosen heuristic is not available, a different one will be used as a fallback (the fallback order being `hybrid` -> `tree-sitter` -> `simple`). | `hybrid`
| `jump-label-alphabet` | The characters that are used to generate two character jump labels. Characters at the start of the alphabet are used first. | `"abcdefghijklmnopqrstuvwxyz"`
| `end-of-line-diagnostics` | Minimum severity of diagnostics to render at the end of the line. Set to `disable` to disable entirely. Refer to the setting about `inline-diagnostics` for more details | "disable"
| `clipboard-provider` | Which API to use for clipboard interaction. One of `pasteboard` (MacOS), `wayland`, `x-clip`, `x-sel`, `win-32-yank`, `termux`, `tmux`, `windows`, `termcode`, `none`, or a custom command set. | Platform and environment specific. |
| `editor-config` | Whether to read settings from [EditorConfig](https://editorconfig.org) files | `true` |
| `welcome-screen` | Whether to enable the welcome screen | `true` |
### `[editor.clipboard-provider]` Section
Helix can be configured either to use a builtin clipboard configuration or to use
a provided command.
For instance, setting it to use OSC 52 termcodes, the configuration would be:
```toml
[editor]
clipboard-provider = "termcode"
```
Alternatively, Helix can be configured to use arbitrary commands for clipboard integration:
```toml
[editor.clipboard-provider.custom]
yank = { command = "cat", args = ["test.txt"] }
paste = { command = "tee", args = ["test.txt"] }
primary-yank = { command = "cat", args = ["test-primary.txt"] } # optional
primary-paste = { command = "tee", args = ["test-primary.txt"] } # optional
```
For custom commands the contents of the yank/paste is communicated over stdin/stdout.
### `[editor.statusline]` Section
@ -82,6 +119,7 @@ The `[editor.statusline]` key takes the following sub-keys:
| `mode.normal` | The text shown in the `mode` element for normal mode | `"NOR"` |
| `mode.insert` | The text shown in the `mode` element for insert mode | `"INS"` |
| `mode.select` | The text shown in the `mode` element for select mode | `"SEL"` |
| `merge-with-commandline` | If set, the command line and statusline will merge into a single line. Status text will replace the statusline briefly | `false` |
The following statusline elements can be configured:
@ -114,16 +152,62 @@ The following statusline elements can be configured:
| Key | Description | Default |
| --- | ----------- | ------- |
| `enable` | Enables LSP integration. Setting to false will completely disable language servers regardless of language settings.| `true` |
| `display-messages` | Display LSP progress messages below statusline[^1] | `false` |
| `display-messages` | Display LSP `window/showMessage` messages below statusline[^1] | `true` |
| `display-progress-messages` | Display LSP progress messages below statusline[^1] | `false` |
| `auto-signature-help` | Enable automatic popup of signature help (parameter hints) | `true` |
| `display-inlay-hints` | Display inlay hints[^2] | `false` |
| `display-color-swatches` | Show color swatches next to colors | `true` |
| `display-signature-help-docs` | Display docs under signature help popup | `true` |
| `snippets` | Enables snippet completions. Requires a server restart (`:lsp-restart`) to take effect after `:config-reload`/`:set`. | `true` |
| `goto-reference-include-declaration` | Include declaration in the goto references popup. | `true` |
[^1]: By default, a progress spinner is shown in the statusline beside the file path.
[^2]: You may also have to activate them in the LSP config for them to appear, not just in Helix. Inlay hints in Helix are still being improved on and may be a little bit laggy/janky under some circumstances. Please report any bugs you see so we can fix them!
[^2]: You may also have to activate them in the language server config for them to appear, not just in Helix. Inlay hints in Helix are still being improved on and may be a little bit laggy/janky under some circumstances. Please report any bugs you see so we can fix them!
### `[editor.inline-blame]` Section
Inline blame is virtual text that appears at the end of a line, displaying information about the most recent commit that affected this line.
| Key | Description | Default |
| ------- | ------------------------------------------ | ------- |
| `behaviour` | Choose when to show inline blame | `"hidden"` |
| `compute` | Choose when inline blame should be computed | `"on-demand"` |
| `format` | The format in which to show the inline blame | `"{author}, {time-ago} • {message} • {commit}"` |
The `behaviour` can be one of the following:
- `"all-lines"`: Inline blame is on every line.
- `"cursor-line"`: Inline blame is only on the line of the primary cursor.
- `"hidden"`: Inline blame is not shown.
Inline blame will only show if the blame for the file has already been computed.
The `compute` key determines under which circumstances the blame is computed, and can be one of the following:
- `"on-demand"`: Blame for the file is computed only when explicitly requested, such as when using `space + B` to blame the line of the cursor. There may be a little delay when loading the blame. When opening new files, even with `behaviour` not set to `"hidden"`, the inline blame won't show. It needs to be computed first in order to become available. This computation can be manually triggered by requesting it with `space + B`.
- `"background"`: Blame for the file is loaded in the background. This will have zero effect on performance of the Editor, but will use a little bit extra resources. Directly requesting the blame with `space + B` will be instant. Inline blame will show as soon as the blame is available when loading new files.
`inline-blame-format` allows customization of the blame message, and can be set to any string. Variables can be used like so: `{variable}`. These are the available variables:
- `author`: The author of the commit
- `date`: When the commit was made
- `time-ago`: How long ago the commit was made
- `message`: The message of the commit, excluding the body
- `body`: The body of the commit
- `commit`: The short hex SHA1 hash of the commit
- `email`: The email of the author of the commit
Any of the variables can potentially be empty.
In this case, the content before the variable will not be included in the string.
If the variable is at the beginning of the string, the content after the variable will not be included.
Some examples, using the default value `format` value:
- If `author` is empty: `"{time-ago} • {message} • {commit}"`
- If `time-ago` is empty: `"{author} • {message} • {commit}"`
- If `message` is empty: `"{author}, {time-ago} • {commit}"`
- If `commit` is empty: `"{author}, {time-ago} • {message}"`
- If `time-ago` and `message` is empty: `"{author} • {commit}"`
- If `author` and `message` is empty: `"{time-ago} • {commit}"`
### `[editor.cursor-shape]` Section
@ -393,3 +477,44 @@ S-tab = "move_parent_node_start"
tab = "extend_parent_node_end"
S-tab = "extend_parent_node_start"
```
### `[editor.inline-diagnostics]` Section
Options for rendering diagnostics inside the text like shown below
```
fn main() {
let foo = bar;
└─ no such value in this scope
}
````
| Key | Description | Default |
|------------|-------------|---------|
| `cursor-line` | The minimum severity that a diagnostic must have to be shown inline on the line that contains the primary cursor. Set to `disable` to not show any diagnostics inline. This option does not have any effect when in insert-mode and will only take effect 350ms after moving the cursor to a different line. | `"disable"` |
| `other-lines` | The minimum severity that a diagnostic must have to be shown inline on a line that does not contain the cursor-line. Set to `disable` to not show any diagnostics inline. | `"disable"` |
| `prefix-len` | How many horizontal bars `─` are rendered before the diagnostic text. | `1` |
| `max-wrap` | Equivalent of the `editor.soft-wrap.max-wrap` option for diagnostics. | `20` |
| `max-diagnostics` | Maximum number of diagnostics to render inline for a given line | `10` |
The allowed values for `cursor-line` and `other-lines` are: `error`, `warning`, `info`, `hint`.
The (first) diagnostic with the highest severity that is not shown inline is rendered at the end of the line (as long as its severity is higher than the `end-of-line-diagnostics` config option):
```
fn main() {
let baz = 1;
let foo = bar; a local variable with a similar name exists: baz
└─ no such value in this scope
}
```
The new diagnostic rendering is not yet enabled by default. As soon as end of line or inline diagnostics are enabled the old diagnostics rendering is automatically disabled. The recommended default setting are:
```toml
[editor]
end-of-line-diagnostics = "hint"
[editor.inline-diagnostics]
cursor-line = "warning" # show warnings and errors on the cursorline inline
```

View file

@ -1,13 +1,14 @@
| Language | Syntax Highlighting | Treesitter Textobjects | Auto Indent | Default LSP |
| Language | Syntax Highlighting | Treesitter Textobjects | Auto Indent | Default language servers |
| --- | --- | --- | --- | --- |
| ada | ✓ | ✓ | | `ada_language_server`, `ada_language_server` |
| ada | ✓ | ✓ | | `ada_language_server` |
| adl | ✓ | ✓ | ✓ | |
| agda | ✓ | | | |
| astro | ✓ | | | |
| amber | ✓ | | | |
| astro | ✓ | | | `astro-ls` |
| awk | ✓ | ✓ | | `awk-language-server` |
| bash | ✓ | ✓ | ✓ | `bash-language-server` |
| bass | ✓ | | | `bass` |
| beancount | ✓ | | | |
| beancount | ✓ | | | `beancount-language-server` |
| bibtex | ✓ | | | `texlab` |
| bicep | ✓ | | | `bicep-langserver` |
| bitbake | ✓ | | | `bitbake-language-server` |
@ -19,25 +20,31 @@
| cairo | ✓ | ✓ | ✓ | `cairo-language-server` |
| capnp | ✓ | | ✓ | |
| cel | ✓ | | | |
| circom | ✓ | | | `circom-lsp` |
| clojure | ✓ | | | `clojure-lsp` |
| cmake | ✓ | ✓ | ✓ | `cmake-language-server` |
| codeql | ✓ | ✓ | | `codeql` |
| comment | ✓ | | | |
| common-lisp | ✓ | | ✓ | `cl-lsp` |
| cpon | ✓ | | ✓ | |
| cpp | ✓ | ✓ | ✓ | `clangd` |
| crystal | ✓ | ✓ | | `crystalline` |
| css | ✓ | | ✓ | `vscode-css-language-server` |
| csv | ✓ | | | |
| cue | ✓ | | | `cuelsp` |
| cylc | ✓ | ✓ | ✓ | |
| d | ✓ | ✓ | ✓ | `serve-d` |
| dart | ✓ | ✓ | ✓ | `dart` |
| dbml | ✓ | | | |
| devicetree | ✓ | | | |
| dhall | ✓ | ✓ | | `dhall-lsp-server` |
| diff | ✓ | | | |
| docker-compose | ✓ | | ✓ | `docker-compose-langserver`, `yaml-language-server` |
| dockerfile | ✓ | | | `docker-langserver` |
| djot | ✓ | | | |
| docker-compose | ✓ | ✓ | ✓ | `docker-compose-langserver`, `yaml-language-server` |
| dockerfile | ✓ | ✓ | | `docker-langserver` |
| dot | ✓ | | | `dot-language-server` |
| dtd | ✓ | | | |
| dune | ✓ | | | |
| earthfile | ✓ | ✓ | ✓ | `earthlyls` |
| edoc | ✓ | | | |
| eex | ✓ | | | |
@ -46,34 +53,39 @@
| elixir | ✓ | ✓ | ✓ | `elixir-ls` |
| elm | ✓ | ✓ | | `elm-language-server` |
| elvish | ✓ | | | `elvish` |
| env | ✓ | | | |
| env | ✓ | | | |
| erb | ✓ | | | |
| erlang | ✓ | ✓ | | `erlang_ls` |
| erlang | ✓ | ✓ | | `erlang_ls`, `elp` |
| esdl | ✓ | | | |
| fga | ✓ | ✓ | ✓ | |
| fidl | ✓ | | | |
| fish | ✓ | ✓ | ✓ | |
| fish | ✓ | ✓ | ✓ | `fish-lsp` |
| forth | ✓ | | | `forth-lsp` |
| fortran | ✓ | | ✓ | `fortls` |
| fsharp | ✓ | | | `fsautocomplete` |
| gas | ✓ | ✓ | | |
| gas | ✓ | ✓ | | `asm-lsp` |
| gdscript | ✓ | ✓ | ✓ | |
| gemini | ✓ | | | |
| gherkin | ✓ | | | |
| ghostty | ✓ | | | |
| git-attributes | ✓ | | | |
| git-commit | ✓ | ✓ | | |
| git-config | ✓ | | | |
| git-config | ✓ | | | |
| git-ignore | ✓ | | | |
| git-rebase | ✓ | | | |
| gjs | ✓ | ✓ | ✓ | `typescript-language-server`, `vscode-eslint-language-server`, `ember-language-server` |
| gleam | ✓ | ✓ | | `gleam` |
| glimmer | ✓ | | | `ember-language-server` |
| glsl | ✓ | ✓ | ✓ | |
| glsl | ✓ | ✓ | ✓ | `glsl_analyzer` |
| gn | ✓ | | | |
| go | ✓ | ✓ | ✓ | `gopls`, `golangci-lint-langserver` |
| godot-resource | ✓ | | | |
| godot-resource | ✓ | | | |
| gomod | ✓ | | | `gopls` |
| gotmpl | ✓ | | | `gopls` |
| gowork | ✓ | | | `gopls` |
| gpr | ✓ | | | `ada_language_server` |
| graphql | ✓ | ✓ | | `graphql-lsp` |
| gren | ✓ | ✓ | | |
| groovy | ✓ | | | |
| gts | ✓ | ✓ | ✓ | `typescript-language-server`, `vscode-eslint-language-server`, `ember-language-server` |
| hare | ✓ | | | |
@ -82,20 +94,23 @@
| hcl | ✓ | ✓ | ✓ | `terraform-ls` |
| heex | ✓ | ✓ | | `elixir-ls` |
| helm | ✓ | | | `helm_ls` |
| hocon | ✓ | | ✓ | |
| hocon | ✓ | | ✓ | |
| hoon | ✓ | | | |
| hosts | ✓ | | | |
| html | ✓ | | | `vscode-html-language-server` |
| html | ✓ | | | `vscode-html-language-server`, `superhtml` |
| hurl | ✓ | ✓ | ✓ | |
| hyprlang | ✓ | | ✓ | |
| hyprlang | ✓ | | ✓ | `hyprls` |
| idris | | | | `idris2-lsp` |
| iex | ✓ | | | |
| ini | ✓ | | | |
| ink | ✓ | | | |
| inko | ✓ | ✓ | ✓ | |
| janet | ✓ | | | |
| java | ✓ | ✓ | ✓ | `jdtls` |
| javascript | ✓ | ✓ | ✓ | `typescript-language-server` |
| jinja | ✓ | | | |
| jjdescription | ✓ | | | |
| jq | ✓ | ✓ | | `jq-lsp` |
| jsdoc | ✓ | | | |
| json | ✓ | ✓ | ✓ | `vscode-json-language-server` |
| json5 | ✓ | | | |
@ -106,7 +121,8 @@
| just | ✓ | ✓ | ✓ | |
| kdl | ✓ | ✓ | ✓ | |
| koka | ✓ | | ✓ | `koka` |
| kotlin | ✓ | | | `kotlin-language-server` |
| kotlin | ✓ | ✓ | ✓ | `kotlin-language-server` |
| koto | ✓ | ✓ | ✓ | `koto-ls` |
| latex | ✓ | ✓ | | `texlab` |
| ld | ✓ | | ✓ | |
| ldif | ✓ | | | |
@ -118,26 +134,29 @@
| log | ✓ | | | |
| lpf | ✓ | | | |
| lua | ✓ | ✓ | ✓ | `lua-language-server` |
| mail | ✓ | ✓ | | |
| make | ✓ | | ✓ | |
| markdoc | ✓ | | | `markdoc-ls` |
| markdown | ✓ | | | `marksman`, `markdown-oxide` |
| markdown.inline | ✓ | | | |
| matlab | ✓ | ✓ | ✓ | |
| mermaid | ✓ | | | |
| meson | ✓ | | ✓ | |
| meson | ✓ | | ✓ | `mesonlsp` |
| mint | | | | `mint` |
| mojo | ✓ | ✓ | ✓ | `mojo-lsp-server` |
| mojo | ✓ | ✓ | ✓ | `magic` |
| move | ✓ | | | |
| msbuild | ✓ | | ✓ | |
| nasm | ✓ | ✓ | | |
| nasm | ✓ | ✓ | | `asm-lsp` |
| nestedtext | ✓ | ✓ | ✓ | |
| nginx | ✓ | | | |
| nickel | ✓ | | ✓ | `nls` |
| nim | ✓ | ✓ | ✓ | `nimlangserver` |
| nix | ✓ | ✓ | | `nil` |
| nix | ✓ | ✓ | | `nil`, `nixd` |
| nu | ✓ | | | `nu` |
| nunjucks | ✓ | | | |
| ocaml | ✓ | | ✓ | `ocamllsp` |
| ocaml-interface | ✓ | | | `ocamllsp` |
| odin | ✓ | | ✓ | `ols` |
| odin | ✓ | | ✓ | `ols` |
| ohm | ✓ | ✓ | ✓ | |
| opencl | ✓ | ✓ | ✓ | `clangd` |
| openscad | ✓ | | | `openscad-lsp` |
@ -149,19 +168,20 @@
| pest | ✓ | ✓ | ✓ | `pest-language-server` |
| php | ✓ | ✓ | ✓ | `intelephense` |
| php-only | ✓ | | | |
| pkgbuild | ✓ | ✓ | ✓ | `pkgbuild-language-server`, `bash-language-server` |
| pkl | ✓ | | ✓ | |
| pkgbuild | ✓ | ✓ | ✓ | `termux-language-server`, `bash-language-server` |
| pkl | ✓ | | ✓ | `pkl-lsp` |
| po | ✓ | ✓ | | |
| pod | ✓ | | | |
| ponylang | ✓ | ✓ | ✓ | |
| powershell | ✓ | | | |
| prisma | ✓ | | | `prisma-language-server` |
| prisma | ✓ | | | `prisma-language-server` |
| prolog | | | | `swipl` |
| protobuf | ✓ | ✓ | ✓ | `bufls`, `pb` |
| protobuf | ✓ | ✓ | ✓ | `buf`, `pb`, `protols` |
| prql | ✓ | | | |
| purescript | ✓ | ✓ | | `purescript-language-server` |
| python | ✓ | ✓ | ✓ | `pylsp` |
| python | ✓ | ✓ | ✓ | `ruff`, `jedi-language-server`, `pylsp` |
| qml | ✓ | | ✓ | `qmlls` |
| quint | ✓ | | | `quint-language-server` |
| r | ✓ | | | `R` |
| racket | ✓ | | ✓ | `racket` |
| regex | ✓ | | | |
@ -171,7 +191,7 @@
| robot | ✓ | | | `robotframework_ls` |
| ron | ✓ | | ✓ | |
| rst | ✓ | | | |
| ruby | ✓ | ✓ | ✓ | `solargraph` |
| ruby | ✓ | ✓ | ✓ | `ruby-lsp`, `solargraph` |
| rust | ✓ | ✓ | ✓ | `rust-analyzer` |
| sage | ✓ | ✓ | | |
| scala | ✓ | ✓ | ✓ | `metals` |
@ -181,11 +201,14 @@
| smali | ✓ | | ✓ | |
| smithy | ✓ | | | `cs` |
| sml | ✓ | | | |
| snakemake | ✓ | | ✓ | `pylsp` |
| solidity | ✓ | ✓ | | `solc` |
| sourcepawn | ✓ | ✓ | | `sourcepawn-studio` |
| spade | ✓ | | ✓ | `spade-language-server` |
| spicedb | ✓ | | | |
| sql | ✓ | | | |
| sql | ✓ | | | |
| sshclientconfig | ✓ | | | |
| starlark | ✓ | ✓ | | |
| starlark | ✓ | ✓ | | `starpls` |
| strace | ✓ | | | |
| supercollider | ✓ | | | |
| svelte | ✓ | | ✓ | `svelteserver` |
@ -196,33 +219,42 @@
| tact | ✓ | ✓ | ✓ | |
| task | ✓ | | | |
| tcl | ✓ | | ✓ | |
| teal | ✓ | | | `teal-language-server` |
| templ | ✓ | | | `templ` |
| tera | ✓ | | | |
| textproto | ✓ | ✓ | ✓ | |
| tfvars | ✓ | | ✓ | `terraform-ls` |
| thrift | ✓ | | | |
| tlaplus | ✓ | | | |
| todotxt | ✓ | | | |
| toml | ✓ | ✓ | | `taplo` |
| tsq | ✓ | | | |
| tsq | ✓ | | | `ts_query_ls` |
| tsx | ✓ | ✓ | ✓ | `typescript-language-server` |
| twig | ✓ | | | |
| typescript | ✓ | ✓ | ✓ | `typescript-language-server` |
| typst | ✓ | | | `tinymist`, `typst-lsp` |
| typespec | ✓ | ✓ | ✓ | `tsp-server` |
| typst | ✓ | | | `tinymist` |
| ungrammar | ✓ | | | |
| unison | ✓ | | ✓ | |
| unison | ✓ | | ✓ | |
| uxntal | ✓ | | | |
| v | ✓ | ✓ | ✓ | `v-analyzer` |
| vala | ✓ | ✓ | | `vala-language-server` |
| vento | ✓ | | | |
| verilog | ✓ | ✓ | | `svlangserver` |
| vhdl | ✓ | | | `vhdl_ls` |
| vhs | ✓ | | | |
| vue | ✓ | | | `vue-language-server` |
| wast | ✓ | | | |
| wat | ✓ | | | |
| wat | ✓ | | | `wat_server` |
| webc | ✓ | | | |
| wgsl | ✓ | | | `wgsl_analyzer` |
| werk | ✓ | | | |
| wgsl | ✓ | | | `wgsl-analyzer` |
| wit | ✓ | | ✓ | |
| wren | ✓ | ✓ | ✓ | |
| xit | ✓ | | | |
| xml | ✓ | | ✓ | |
| xtc | ✓ | | | |
| yaml | ✓ | | ✓ | `yaml-language-server`, `ansible-language-server` |
| yaml | ✓ | ✓ | ✓ | `yaml-language-server`, `ansible-language-server` |
| yara | ✓ | | | `yls` |
| yuck | ✓ | | | |
| zig | ✓ | ✓ | ✓ | `zls` |

View file

@ -0,0 +1,301 @@
| Name | Description | Default keybinds |
| --- | --- | --- |
| `no_op` | Do nothing | |
| `move_char_left` | Move left | normal: `` h ``, `` <left> ``, insert: `` <left> `` |
| `move_char_right` | Move right | normal: `` l ``, `` <right> ``, insert: `` <right> `` |
| `move_line_up` | Move up | normal: `` gk `` |
| `move_line_down` | Move down | normal: `` gj `` |
| `move_visual_line_up` | Move up | normal: `` k ``, `` <up> ``, insert: `` <up> `` |
| `move_visual_line_down` | Move down | normal: `` j ``, `` <down> ``, insert: `` <down> `` |
| `extend_char_left` | Extend left | select: `` h ``, `` <left> `` |
| `extend_char_right` | Extend right | select: `` l ``, `` <right> `` |
| `extend_line_up` | Extend up | select: `` gk `` |
| `extend_line_down` | Extend down | select: `` gj `` |
| `extend_visual_line_up` | Extend up | select: `` k ``, `` <up> `` |
| `extend_visual_line_down` | Extend down | select: `` j ``, `` <down> `` |
| `copy_selection_on_next_line` | Copy selection on next line | normal: `` C ``, select: `` C `` |
| `copy_selection_on_prev_line` | Copy selection on previous line | normal: `` <A-C> ``, select: `` <A-C> `` |
| `move_next_word_start` | Move to start of next word | normal: `` w `` |
| `move_prev_word_start` | Move to start of previous word | normal: `` b `` |
| `move_next_word_end` | Move to end of next word | normal: `` e `` |
| `move_prev_word_end` | Move to end of previous word | |
| `move_next_long_word_start` | Move to start of next long word | normal: `` W `` |
| `move_prev_long_word_start` | Move to start of previous long word | normal: `` B `` |
| `move_next_long_word_end` | Move to end of next long word | normal: `` E `` |
| `move_prev_long_word_end` | Move to end of previous long word | |
| `move_next_sub_word_start` | Move to start of next sub word | |
| `move_prev_sub_word_start` | Move to start of previous sub word | |
| `move_next_sub_word_end` | Move to end of next sub word | |
| `move_prev_sub_word_end` | Move to end of previous sub word | |
| `move_parent_node_end` | Move to end of the parent node | normal: `` <A-e> `` |
| `move_parent_node_start` | Move to beginning of the parent node | normal: `` <A-b> `` |
| `extend_next_word_start` | Extend to start of next word | select: `` w `` |
| `extend_prev_word_start` | Extend to start of previous word | select: `` b `` |
| `extend_next_word_end` | Extend to end of next word | select: `` e `` |
| `extend_prev_word_end` | Extend to end of previous word | |
| `extend_next_long_word_start` | Extend to start of next long word | select: `` W `` |
| `extend_prev_long_word_start` | Extend to start of previous long word | select: `` B `` |
| `extend_next_long_word_end` | Extend to end of next long word | select: `` E `` |
| `extend_prev_long_word_end` | Extend to end of prev long word | |
| `extend_next_sub_word_start` | Extend to start of next sub word | |
| `extend_prev_sub_word_start` | Extend to start of previous sub word | |
| `extend_next_sub_word_end` | Extend to end of next sub word | |
| `extend_prev_sub_word_end` | Extend to end of prev sub word | |
| `extend_parent_node_end` | Extend to end of the parent node | select: `` <A-e> `` |
| `extend_parent_node_start` | Extend to beginning of the parent node | select: `` <A-b> `` |
| `find_till_char` | Move till next occurrence of char | normal: `` t `` |
| `find_next_char` | Move to next occurrence of char | normal: `` f `` |
| `extend_till_char` | Extend till next occurrence of char | select: `` t `` |
| `extend_next_char` | Extend to next occurrence of char | select: `` f `` |
| `till_prev_char` | Move till previous occurrence of char | normal: `` T `` |
| `find_prev_char` | Move to previous occurrence of char | normal: `` F `` |
| `extend_till_prev_char` | Extend till previous occurrence of char | select: `` T `` |
| `extend_prev_char` | Extend to previous occurrence of char | select: `` F `` |
| `repeat_last_motion` | Repeat last motion | normal: `` <A-.> ``, select: `` <A-.> `` |
| `replace` | Replace with new char | normal: `` r ``, select: `` r `` |
| `switch_case` | Switch (toggle) case | normal: `` ~ ``, select: `` ~ `` |
| `switch_to_uppercase` | Switch to uppercase | normal: `` <A-`> ``, select: `` <A-`> `` |
| `switch_to_lowercase` | Switch to lowercase | normal: `` ` ``, select: `` ` `` |
| `page_up` | Move page up | normal: `` <C-b> ``, `` Z<C-b> ``, `` z<C-b> ``, `` <pageup> ``, `` Z<pageup> ``, `` z<pageup> ``, select: `` <C-b> ``, `` Z<C-b> ``, `` z<C-b> ``, `` <pageup> ``, `` Z<pageup> ``, `` z<pageup> ``, insert: `` <pageup> `` |
| `page_down` | Move page down | normal: `` <C-f> ``, `` Z<C-f> ``, `` z<C-f> ``, `` <pagedown> ``, `` Z<pagedown> ``, `` z<pagedown> ``, select: `` <C-f> ``, `` Z<C-f> ``, `` z<C-f> ``, `` <pagedown> ``, `` Z<pagedown> ``, `` z<pagedown> ``, insert: `` <pagedown> `` |
| `half_page_up` | Move half page up | |
| `half_page_down` | Move half page down | |
| `page_cursor_up` | Move page and cursor up | |
| `page_cursor_down` | Move page and cursor down | |
| `page_cursor_half_up` | Move page and cursor half up | normal: `` <C-u> ``, `` Z<C-u> ``, `` z<C-u> ``, `` Z<backspace> ``, `` z<backspace> ``, select: `` <C-u> ``, `` Z<C-u> ``, `` z<C-u> ``, `` Z<backspace> ``, `` z<backspace> `` |
| `page_cursor_half_down` | Move page and cursor half down | normal: `` <C-d> ``, `` Z<C-d> ``, `` z<C-d> ``, `` Z<space> ``, `` z<space> ``, select: `` <C-d> ``, `` Z<C-d> ``, `` z<C-d> ``, `` Z<space> ``, `` z<space> `` |
| `select_all` | Select whole document | normal: `` % ``, select: `` % `` |
| `select_regex` | Select all regex matches inside selections | normal: `` s ``, select: `` s `` |
| `split_selection` | Split selections on regex matches | normal: `` S ``, select: `` S `` |
| `split_selection_on_newline` | Split selection on newlines | normal: `` <A-s> ``, select: `` <A-s> `` |
| `merge_selections` | Merge selections | normal: `` <A-minus> ``, select: `` <A-minus> `` |
| `merge_consecutive_selections` | Merge consecutive selections | normal: `` <A-_> ``, select: `` <A-_> `` |
| `search` | Search for regex pattern | normal: `` / ``, `` Z/ ``, `` z/ ``, select: `` / ``, `` Z/ ``, `` z/ `` |
| `rsearch` | Reverse search for regex pattern | normal: `` ? ``, `` Z? ``, `` z? ``, select: `` ? ``, `` Z? ``, `` z? `` |
| `search_next` | Select next search match | normal: `` n ``, `` Zn ``, `` zn ``, select: `` Zn ``, `` zn `` |
| `search_prev` | Select previous search match | normal: `` N ``, `` ZN ``, `` zN ``, select: `` ZN ``, `` zN `` |
| `extend_search_next` | Add next search match to selection | select: `` n `` |
| `extend_search_prev` | Add previous search match to selection | select: `` N `` |
| `search_selection` | Use current selection as search pattern | normal: `` <A-*> ``, select: `` <A-*> `` |
| `search_selection_detect_word_boundaries` | Use current selection as the search pattern, automatically wrapping with `\b` on word boundaries | normal: `` * ``, select: `` * `` |
| `make_search_word_bounded` | Modify current search to make it word bounded | |
| `global_search` | Global search in workspace folder | normal: `` <space>/ ``, select: `` <space>/ `` |
| `extend_line` | Select current line, if already selected, extend to another line based on the anchor | |
| `extend_line_below` | Select current line, if already selected, extend to next line | normal: `` x ``, select: `` x `` |
| `extend_line_above` | Select current line, if already selected, extend to previous line | |
| `select_line_above` | Select current line, if already selected, extend or shrink line above based on the anchor | |
| `select_line_below` | Select current line, if already selected, extend or shrink line below based on the anchor | |
| `extend_to_line_bounds` | Extend selection to line bounds | normal: `` X ``, select: `` X `` |
| `shrink_to_line_bounds` | Shrink selection to line bounds | normal: `` <A-x> ``, select: `` <A-x> `` |
| `delete_selection` | Delete selection | normal: `` d ``, select: `` d `` |
| `delete_selection_noyank` | Delete selection without yanking | normal: `` <A-d> ``, select: `` <A-d> `` |
| `change_selection` | Change selection | normal: `` c ``, select: `` c `` |
| `change_selection_noyank` | Change selection without yanking | normal: `` <A-c> ``, select: `` <A-c> `` |
| `collapse_selection` | Collapse selection into single cursor | normal: `` ; ``, select: `` ; `` |
| `flip_selections` | Flip selection cursor and anchor | normal: `` <A-;> ``, select: `` <A-;> `` |
| `ensure_selections_forward` | Ensure all selections face forward | normal: `` <A-:> ``, select: `` <A-:> `` |
| `insert_mode` | Insert before selection | normal: `` i ``, select: `` i `` |
| `append_mode` | Append after selection | normal: `` a ``, select: `` a `` |
| `command_mode` | Enter command mode | normal: `` : ``, select: `` : `` |
| `file_picker` | Open file picker | normal: `` <space>f ``, select: `` <space>f `` |
| `file_picker_in_current_buffer_directory` | Open file picker at current buffer's directory | |
| `file_picker_in_current_directory` | Open file picker at current working directory | normal: `` <space>F ``, select: `` <space>F `` |
| `file_explorer` | Open file explorer in workspace root | normal: `` <space>e ``, select: `` <space>e `` |
| `file_explorer_in_current_buffer_directory` | Open file explorer at current buffer's directory | normal: `` <space>E ``, select: `` <space>E `` |
| `file_explorer_in_current_directory` | Open file explorer at current working directory | |
| `code_action` | Perform code action | normal: `` <space>a ``, select: `` <space>a `` |
| `buffer_picker` | Open buffer picker | normal: `` <space>b ``, select: `` <space>b `` |
| `jumplist_picker` | Open jumplist picker | normal: `` <space>j ``, select: `` <space>j `` |
| `symbol_picker` | Open symbol picker | normal: `` <space>s ``, select: `` <space>s `` |
| `changed_file_picker` | Open changed file picker | normal: `` <space>g ``, select: `` <space>g `` |
| `select_references_to_symbol_under_cursor` | Select symbol references | normal: `` <space>h ``, select: `` <space>h `` |
| `workspace_symbol_picker` | Open workspace symbol picker | normal: `` <space>S ``, select: `` <space>S `` |
| `diagnostics_picker` | Open diagnostic picker | normal: `` <space>d ``, select: `` <space>d `` |
| `workspace_diagnostics_picker` | Open workspace diagnostic picker | normal: `` <space>D ``, select: `` <space>D `` |
| `last_picker` | Open last picker | normal: `` <space>' ``, select: `` <space>' `` |
| `insert_at_line_start` | Insert at start of line | normal: `` I ``, select: `` I `` |
| `insert_at_line_end` | Insert at end of line | normal: `` A ``, select: `` A `` |
| `open_below` | Open new line below selection | normal: `` o ``, select: `` o `` |
| `open_above` | Open new line above selection | normal: `` O ``, select: `` O `` |
| `normal_mode` | Enter normal mode | normal: `` <esc> ``, select: `` v ``, insert: `` <esc> `` |
| `select_mode` | Enter selection extend mode | normal: `` v `` |
| `exit_select_mode` | Exit selection mode | select: `` <esc> `` |
| `goto_definition` | Goto definition | normal: `` gd ``, select: `` gd `` |
| `goto_declaration` | Goto declaration | normal: `` gD ``, select: `` gD `` |
| `add_newline_above` | Add newline above | normal: `` [<space> ``, select: `` [<space> `` |
| `add_newline_below` | Add newline below | normal: `` ]<space> ``, select: `` ]<space> `` |
| `goto_type_definition` | Goto type definition | normal: `` gy ``, select: `` gy `` |
| `goto_implementation` | Goto implementation | normal: `` gi ``, select: `` gi `` |
| `goto_file_start` | Goto line number <n> else file start | normal: `` gg ``, select: `` gg `` |
| `goto_file_end` | Goto file end | |
| `goto_file` | Goto files/URLs in selections | normal: `` gf ``, select: `` gf `` |
| `goto_file_hsplit` | Goto files in selections (hsplit) | normal: `` <C-w>f ``, `` <space>wf ``, select: `` <C-w>f ``, `` <space>wf `` |
| `goto_file_vsplit` | Goto files in selections (vsplit) | normal: `` <C-w>F ``, `` <space>wF ``, select: `` <C-w>F ``, `` <space>wF `` |
| `goto_reference` | Goto references | normal: `` gr ``, select: `` gr `` |
| `goto_window_top` | Goto window top | normal: `` gt ``, select: `` gt `` |
| `goto_window_center` | Goto window center | normal: `` gc ``, select: `` gc `` |
| `goto_window_bottom` | Goto window bottom | normal: `` gb ``, select: `` gb `` |
| `goto_last_accessed_file` | Goto last accessed file | normal: `` ga ``, select: `` ga `` |
| `goto_last_modified_file` | Goto last modified file | normal: `` gm ``, select: `` gm `` |
| `goto_last_modification` | Goto last modification | normal: `` g. ``, select: `` g. `` |
| `goto_line` | Goto line | normal: `` G ``, select: `` G `` |
| `goto_last_line` | Goto last line | normal: `` ge ``, select: `` ge `` |
| `goto_first_diag` | Goto first diagnostic | normal: `` [D ``, select: `` [D `` |
| `goto_last_diag` | Goto last diagnostic | normal: `` ]D ``, select: `` ]D `` |
| `goto_next_diag` | Goto next diagnostic | normal: `` ]d ``, select: `` ]d `` |
| `goto_prev_diag` | Goto previous diagnostic | normal: `` [d ``, select: `` [d `` |
| `goto_next_change` | Goto next change | normal: `` ]g ``, select: `` ]g `` |
| `goto_prev_change` | Goto previous change | normal: `` [g ``, select: `` [g `` |
| `goto_first_change` | Goto first change | normal: `` [G ``, select: `` [G `` |
| `goto_last_change` | Goto last change | normal: `` ]G ``, select: `` ]G `` |
| `goto_line_start` | Goto line start | normal: `` gh ``, `` <home> ``, select: `` gh ``, insert: `` <home> `` |
| `goto_line_end` | Goto line end | normal: `` gl ``, `` <end> ``, select: `` gl `` |
| `goto_next_buffer` | Goto next buffer | normal: `` gn ``, select: `` gn `` |
| `goto_previous_buffer` | Goto previous buffer | normal: `` gp ``, select: `` gp `` |
| `goto_line_end_newline` | Goto newline at line end | insert: `` <end> `` |
| `goto_first_nonwhitespace` | Goto first non-blank in line | normal: `` gs ``, select: `` gs `` |
| `trim_selections` | Trim whitespace from selections | normal: `` _ ``, select: `` _ `` |
| `extend_to_line_start` | Extend to line start | select: `` <home> `` |
| `extend_to_first_nonwhitespace` | Extend to first non-blank in line | |
| `extend_to_line_end` | Extend to line end | select: `` <end> `` |
| `extend_to_line_end_newline` | Extend to line end | |
| `signature_help` | Show signature help | |
| `smart_tab` | Insert tab if all cursors have all whitespace to their left; otherwise, run a separate command. | insert: `` <tab> `` |
| `insert_tab` | Insert tab char | insert: `` <S-tab> `` |
| `insert_newline` | Insert newline char | insert: `` <C-j> ``, `` <ret> `` |
| `delete_char_backward` | Delete previous char | insert: `` <C-h> ``, `` <backspace> ``, `` <S-backspace> `` |
| `delete_char_forward` | Delete next char | insert: `` <C-d> ``, `` <del> `` |
| `delete_word_backward` | Delete previous word | insert: `` <C-w> ``, `` <A-backspace> `` |
| `delete_word_forward` | Delete next word | insert: `` <A-d> ``, `` <A-del> `` |
| `kill_to_line_start` | Delete till start of line | insert: `` <C-u> `` |
| `kill_to_line_end` | Delete till end of line | insert: `` <C-k> `` |
| `undo` | Undo change | normal: `` u ``, select: `` u `` |
| `redo` | Redo change | normal: `` U ``, select: `` U `` |
| `earlier` | Move backward in history | normal: `` <A-u> ``, select: `` <A-u> `` |
| `later` | Move forward in history | normal: `` <A-U> ``, select: `` <A-U> `` |
| `commit_undo_checkpoint` | Commit changes to new checkpoint | insert: `` <C-s> `` |
| `yank` | Yank selection | normal: `` y ``, select: `` y `` |
| `yank_to_clipboard` | Yank selections to clipboard | normal: `` <space>y ``, select: `` <space>y `` |
| `yank_to_primary_clipboard` | Yank selections to primary clipboard | |
| `yank_joined` | Join and yank selections | |
| `yank_joined_to_clipboard` | Join and yank selections to clipboard | |
| `yank_main_selection_to_clipboard` | Yank main selection to clipboard | normal: `` <space>Y ``, select: `` <space>Y `` |
| `yank_joined_to_primary_clipboard` | Join and yank selections to primary clipboard | |
| `yank_main_selection_to_primary_clipboard` | Yank main selection to primary clipboard | |
| `replace_with_yanked` | Replace with yanked text | normal: `` R ``, select: `` R `` |
| `replace_selections_with_clipboard` | Replace selections by clipboard content | normal: `` <space>R ``, select: `` <space>R `` |
| `replace_selections_with_primary_clipboard` | Replace selections by primary clipboard | |
| `paste_after` | Paste after selection | normal: `` p ``, select: `` p `` |
| `paste_before` | Paste before selection | normal: `` P ``, select: `` P `` |
| `paste_clipboard_after` | Paste clipboard after selections | normal: `` <space>p ``, select: `` <space>p `` |
| `paste_clipboard_before` | Paste clipboard before selections | normal: `` <space>P ``, select: `` <space>P `` |
| `paste_primary_clipboard_after` | Paste primary clipboard after selections | |
| `paste_primary_clipboard_before` | Paste primary clipboard before selections | |
| `indent` | Indent selection | normal: `` <gt> ``, select: `` <gt> `` |
| `unindent` | Unindent selection | normal: `` <lt> ``, select: `` <lt> `` |
| `format_selections` | Format selection | normal: `` = ``, select: `` = `` |
| `join_selections` | Join lines inside selection | normal: `` J ``, select: `` J `` |
| `join_selections_space` | Join lines inside selection and select spaces | normal: `` <A-J> ``, select: `` <A-J> `` |
| `keep_selections` | Keep selections matching regex | normal: `` K ``, select: `` K `` |
| `remove_selections` | Remove selections matching regex | normal: `` <A-K> ``, select: `` <A-K> `` |
| `align_selections` | Align selections in column | normal: `` & ``, select: `` & `` |
| `keep_primary_selection` | Keep primary selection | normal: `` , ``, select: `` , `` |
| `remove_primary_selection` | Remove primary selection | normal: `` <A-,> ``, select: `` <A-,> `` |
| `completion` | Invoke completion popup | insert: `` <C-x> `` |
| `hover` | Show docs for item under cursor | normal: `` <space>k ``, select: `` <space>k `` |
| `toggle_comments` | Comment/uncomment selections | normal: `` <C-c> ``, `` <space>c ``, select: `` <C-c> ``, `` <space>c `` |
| `toggle_line_comments` | Line comment/uncomment selections | normal: `` <space><A-c> ``, select: `` <space><A-c> `` |
| `toggle_block_comments` | Block comment/uncomment selections | normal: `` <space>C ``, select: `` <space>C `` |
| `rotate_selections_forward` | Rotate selections forward | normal: `` ) ``, select: `` ) `` |
| `rotate_selections_backward` | Rotate selections backward | normal: `` ( ``, select: `` ( `` |
| `rotate_selection_contents_forward` | Rotate selection contents forward | normal: `` <A-)> ``, select: `` <A-)> `` |
| `rotate_selection_contents_backward` | Rotate selections contents backward | normal: `` <A-(> ``, select: `` <A-(> `` |
| `reverse_selection_contents` | Reverse selections contents | |
| `expand_selection` | Expand selection to parent syntax node | normal: `` <A-o> ``, `` <A-up> ``, select: `` <A-o> ``, `` <A-up> `` |
| `shrink_selection` | Shrink selection to previously expanded syntax node | normal: `` <A-i> ``, `` <A-down> ``, select: `` <A-i> ``, `` <A-down> `` |
| `select_next_sibling` | Select next sibling in the syntax tree | normal: `` <A-n> ``, `` <A-right> ``, select: `` <A-n> ``, `` <A-right> `` |
| `select_prev_sibling` | Select previous sibling the in syntax tree | normal: `` <A-p> ``, `` <A-left> ``, select: `` <A-p> ``, `` <A-left> `` |
| `select_all_siblings` | Select all siblings of the current node | normal: `` <A-a> ``, select: `` <A-a> `` |
| `select_all_children` | Select all children of the current node | normal: `` <A-I> ``, `` <S-A-down> ``, select: `` <A-I> ``, `` <S-A-down> `` |
| `jump_forward` | Jump forward on jumplist | normal: `` <C-i> ``, `` <tab> ``, select: `` <C-i> ``, `` <tab> `` |
| `jump_backward` | Jump backward on jumplist | normal: `` <C-o> ``, select: `` <C-o> `` |
| `save_selection` | Save current selection to jumplist | normal: `` <C-s> ``, select: `` <C-s> `` |
| `jump_view_right` | Jump to right split | normal: `` <C-w>l ``, `` <space>wl ``, `` <C-w><C-l> ``, `` <C-w><right> ``, `` <space>w<C-l> ``, `` <space>w<right> ``, select: `` <C-w>l ``, `` <space>wl ``, `` <C-w><C-l> ``, `` <C-w><right> ``, `` <space>w<C-l> ``, `` <space>w<right> `` |
| `jump_view_left` | Jump to left split | normal: `` <C-w>h ``, `` <space>wh ``, `` <C-w><C-h> ``, `` <C-w><left> ``, `` <space>w<C-h> ``, `` <space>w<left> ``, select: `` <C-w>h ``, `` <space>wh ``, `` <C-w><C-h> ``, `` <C-w><left> ``, `` <space>w<C-h> ``, `` <space>w<left> `` |
| `jump_view_up` | Jump to split above | normal: `` <C-w>k ``, `` <C-w><up> ``, `` <space>wk ``, `` <C-w><C-k> ``, `` <space>w<up> ``, `` <space>w<C-k> ``, select: `` <C-w>k ``, `` <C-w><up> ``, `` <space>wk ``, `` <C-w><C-k> ``, `` <space>w<up> ``, `` <space>w<C-k> `` |
| `jump_view_down` | Jump to split below | normal: `` <C-w>j ``, `` <space>wj ``, `` <C-w><C-j> ``, `` <C-w><down> ``, `` <space>w<C-j> ``, `` <space>w<down> ``, select: `` <C-w>j ``, `` <space>wj ``, `` <C-w><C-j> ``, `` <C-w><down> ``, `` <space>w<C-j> ``, `` <space>w<down> `` |
| `swap_view_right` | Swap with right split | normal: `` <C-w>L ``, `` <space>wL ``, select: `` <C-w>L ``, `` <space>wL `` |
| `swap_view_left` | Swap with left split | normal: `` <C-w>H ``, `` <space>wH ``, select: `` <C-w>H ``, `` <space>wH `` |
| `swap_view_up` | Swap with split above | normal: `` <C-w>K ``, `` <space>wK ``, select: `` <C-w>K ``, `` <space>wK `` |
| `swap_view_down` | Swap with split below | normal: `` <C-w>J ``, `` <space>wJ ``, select: `` <C-w>J ``, `` <space>wJ `` |
| `transpose_view` | Transpose splits | normal: `` <C-w>t ``, `` <space>wt ``, `` <C-w><C-t> ``, `` <space>w<C-t> ``, select: `` <C-w>t ``, `` <space>wt ``, `` <C-w><C-t> ``, `` <space>w<C-t> `` |
| `rotate_view` | Goto next window | normal: `` <C-w>w ``, `` <space>ww ``, `` <C-w><C-w> ``, `` <space>w<C-w> ``, select: `` <C-w>w ``, `` <space>ww ``, `` <C-w><C-w> ``, `` <space>w<C-w> `` |
| `rotate_view_reverse` | Goto previous window | |
| `hsplit` | Horizontal bottom split | normal: `` <C-w>s ``, `` <space>ws ``, `` <C-w><C-s> ``, `` <space>w<C-s> ``, select: `` <C-w>s ``, `` <space>ws ``, `` <C-w><C-s> ``, `` <space>w<C-s> `` |
| `hsplit_new` | Horizontal bottom split scratch buffer | normal: `` <C-w>ns ``, `` <space>wns ``, `` <C-w>n<C-s> ``, `` <space>wn<C-s> ``, select: `` <C-w>ns ``, `` <space>wns ``, `` <C-w>n<C-s> ``, `` <space>wn<C-s> `` |
| `vsplit` | Vertical right split | normal: `` <C-w>v ``, `` <space>wv ``, `` <C-w><C-v> ``, `` <space>w<C-v> ``, select: `` <C-w>v ``, `` <space>wv ``, `` <C-w><C-v> ``, `` <space>w<C-v> `` |
| `vsplit_new` | Vertical right split scratch buffer | normal: `` <C-w>nv ``, `` <space>wnv ``, `` <C-w>n<C-v> ``, `` <space>wn<C-v> ``, select: `` <C-w>nv ``, `` <space>wnv ``, `` <C-w>n<C-v> ``, `` <space>wn<C-v> `` |
| `wclose` | Close window | normal: `` <C-w>q ``, `` <space>wq ``, `` <C-w><C-q> ``, `` <space>w<C-q> ``, select: `` <C-w>q ``, `` <space>wq ``, `` <C-w><C-q> ``, `` <space>w<C-q> `` |
| `wonly` | Close windows except current | normal: `` <C-w>o ``, `` <space>wo ``, `` <C-w><C-o> ``, `` <space>w<C-o> ``, select: `` <C-w>o ``, `` <space>wo ``, `` <C-w><C-o> ``, `` <space>w<C-o> `` |
| `select_register` | Select register | normal: `` " ``, select: `` " `` |
| `insert_register` | Insert register | insert: `` <C-r> `` |
| `copy_between_registers` | Copy between two registers | |
| `align_view_middle` | Align view middle | normal: `` Zm ``, `` zm ``, select: `` Zm ``, `` zm `` |
| `align_view_top` | Align view top | normal: `` Zt ``, `` zt ``, select: `` Zt ``, `` zt `` |
| `align_view_center` | Align view center | normal: `` Zc ``, `` Zz ``, `` zc ``, `` zz ``, select: `` Zc ``, `` Zz ``, `` zc ``, `` zz `` |
| `align_view_bottom` | Align view bottom | normal: `` Zb ``, `` zb ``, select: `` Zb ``, `` zb `` |
| `scroll_up` | Scroll view up | normal: `` Zk ``, `` zk ``, `` Z<up> ``, `` z<up> ``, select: `` Zk ``, `` zk ``, `` Z<up> ``, `` z<up> `` |
| `scroll_down` | Scroll view down | normal: `` Zj ``, `` zj ``, `` Z<down> ``, `` z<down> ``, select: `` Zj ``, `` zj ``, `` Z<down> ``, `` z<down> `` |
| `match_brackets` | Goto matching bracket | normal: `` mm ``, select: `` mm `` |
| `surround_add` | Surround add | normal: `` ms ``, select: `` ms `` |
| `surround_replace` | Surround replace | normal: `` mr ``, select: `` mr `` |
| `surround_delete` | Surround delete | normal: `` md ``, select: `` md `` |
| `select_textobject_around` | Select around object | normal: `` ma ``, select: `` ma `` |
| `select_textobject_inner` | Select inside object | normal: `` mi ``, select: `` mi `` |
| `goto_next_function` | Goto next function | normal: `` ]f ``, select: `` ]f `` |
| `goto_prev_function` | Goto previous function | normal: `` [f ``, select: `` [f `` |
| `goto_next_class` | Goto next type definition | normal: `` ]t ``, select: `` ]t `` |
| `goto_prev_class` | Goto previous type definition | normal: `` [t ``, select: `` [t `` |
| `goto_next_parameter` | Goto next parameter | normal: `` ]a ``, select: `` ]a `` |
| `goto_prev_parameter` | Goto previous parameter | normal: `` [a ``, select: `` [a `` |
| `goto_next_comment` | Goto next comment | normal: `` ]c ``, select: `` ]c `` |
| `goto_prev_comment` | Goto previous comment | normal: `` [c ``, select: `` [c `` |
| `goto_next_test` | Goto next test | normal: `` ]T ``, select: `` ]T `` |
| `goto_prev_test` | Goto previous test | normal: `` [T ``, select: `` [T `` |
| `goto_next_entry` | Goto next pairing | normal: `` ]e ``, select: `` ]e `` |
| `goto_prev_entry` | Goto previous pairing | normal: `` [e ``, select: `` [e `` |
| `goto_next_paragraph` | Goto next paragraph | normal: `` ]p ``, select: `` ]p `` |
| `goto_prev_paragraph` | Goto previous paragraph | normal: `` [p ``, select: `` [p `` |
| `dap_launch` | Launch debug target | normal: `` <space>Gl ``, select: `` <space>Gl `` |
| `dap_restart` | Restart debugging session | normal: `` <space>Gr ``, select: `` <space>Gr `` |
| `dap_toggle_breakpoint` | Toggle breakpoint | normal: `` <space>Gb ``, select: `` <space>Gb `` |
| `dap_continue` | Continue program execution | normal: `` <space>Gc ``, select: `` <space>Gc `` |
| `dap_pause` | Pause program execution | normal: `` <space>Gh ``, select: `` <space>Gh `` |
| `dap_step_in` | Step in | normal: `` <space>Gi ``, select: `` <space>Gi `` |
| `dap_step_out` | Step out | normal: `` <space>Go ``, select: `` <space>Go `` |
| `dap_next` | Step to next | normal: `` <space>Gn ``, select: `` <space>Gn `` |
| `dap_variables` | List variables | normal: `` <space>Gv ``, select: `` <space>Gv `` |
| `dap_terminate` | End debug session | normal: `` <space>Gt ``, select: `` <space>Gt `` |
| `dap_edit_condition` | Edit breakpoint condition on current line | normal: `` <space>G<C-c> ``, select: `` <space>G<C-c> `` |
| `dap_edit_log` | Edit breakpoint log message on current line | normal: `` <space>G<C-l> ``, select: `` <space>G<C-l> `` |
| `dap_switch_thread` | Switch current thread | normal: `` <space>Gst ``, select: `` <space>Gst `` |
| `dap_switch_stack_frame` | Switch stack frame | normal: `` <space>Gsf ``, select: `` <space>Gsf `` |
| `dap_enable_exceptions` | Enable exception breakpoints | normal: `` <space>Ge ``, select: `` <space>Ge `` |
| `dap_disable_exceptions` | Disable exception breakpoints | normal: `` <space>GE ``, select: `` <space>GE `` |
| `shell_pipe` | Pipe selections through shell command | normal: `` \| ``, select: `` \| `` |
| `shell_pipe_to` | Pipe selections into shell command ignoring output | normal: `` <A-\|> ``, select: `` <A-\|> `` |
| `shell_insert_output` | Insert shell command output before selections | normal: `` ! ``, select: `` ! `` |
| `shell_append_output` | Append shell command output after selections | normal: `` <A-!> ``, select: `` <A-!> `` |
| `shell_keep_pipe` | Filter selections with shell predicate | normal: `` $ ``, select: `` $ `` |
| `suspend` | Suspend and return to shell | normal: `` <C-z> ``, select: `` <C-z> `` |
| `rename_symbol` | Rename symbol | normal: `` <space>r ``, select: `` <space>r `` |
| `increment` | Increment item under cursor | normal: `` <C-a> ``, select: `` <C-a> `` |
| `decrement` | Decrement item under cursor | normal: `` <C-x> ``, select: `` <C-x> `` |
| `record_macro` | Record macro | normal: `` Q ``, select: `` Q `` |
| `replay_macro` | Replay macro | normal: `` q ``, select: `` q `` |
| `command_palette` | Open command palette | normal: `` <space>? ``, select: `` <space>? `` |
| `goto_word` | Jump to a two-character label | normal: `` gw `` |
| `extend_to_word` | Extend to a two-character label | select: `` gw `` |
| `goto_next_tabstop` | goto next snippet placeholder | |
| `goto_prev_tabstop` | goto next snippet placeholder | |
| `blame_line` | Show blame for the current line | normal: `` <space>B ``, select: `` <space>B `` |

View file

@ -2,7 +2,7 @@
| --- | --- |
| `:quit`, `:q` | Close the current view. |
| `:quit!`, `:q!` | Force close the current view, ignoring unsaved changes. |
| `:open`, `:o` | Open a file from disk into the current view. |
| `:open`, `:o`, `:edit`, `:e` | Open a file from disk into the current view. |
| `:buffer-close`, `:bc`, `:bclose` | Close the current buffer. |
| `:buffer-close!`, `:bc!`, `:bclose!` | Close the current buffer forcefully, ignoring unsaved changes. |
| `:buffer-close-others`, `:bco`, `:bcloseother` | Close all buffers but the currently focused one. |
@ -16,7 +16,7 @@
| `:write-buffer-close`, `:wbc` | Write changes to disk and closes the buffer. Accepts an optional path (:write-buffer-close some/path.txt) |
| `:write-buffer-close!`, `:wbc!` | Force write changes to disk creating necessary subdirectories and closes the buffer. Accepts an optional path (:write-buffer-close! some/path.txt) |
| `:new`, `:n` | Create a new scratch buffer. |
| `:format`, `:fmt` | Format the file using the LSP formatter. |
| `:format`, `:fmt` | Format the file using an external formatter or language server. |
| `:indent-style` | Set the indentation style for editing. ('t' for tabs or 1-16 for number of spaces.) |
| `:line-ending` | Set the document's default line ending. Options: crlf, lf. |
| `:earlier`, `:ear` | Jump back to an earlier point in edit history. Accepts a number of steps or a time span. |
@ -52,8 +52,8 @@
| `:reload-all`, `:rla` | Discard changes and reload all documents from the source files. |
| `:update`, `:u` | Write changes only if the file has been modified. |
| `:lsp-workspace-command` | Open workspace command picker |
| `:lsp-restart` | Restarts the language servers used by the current doc |
| `:lsp-stop` | Stops the language servers that are used by the current doc |
| `:lsp-restart` | Restarts the given language servers, or all language servers that are used by the current file if no arguments are supplied |
| `:lsp-stop` | Stops the given language servers, or all language servers that are used by the current file if no arguments are supplied |
| `:tree-sitter-scopes` | Display tree sitter scopes, primarily for theming and development. |
| `:tree-sitter-highlight-name` | Display name of tree-sitter highlight scope under the cursor. |
| `:debug-start`, `:dbg` | Start a debug session from a given template with given parameters. |
@ -67,12 +67,11 @@
| `:goto`, `:g` | Goto line number. |
| `:set-language`, `:lang` | Set the language of current buffer (show current language if no value specified). |
| `:set-option`, `:set` | Set a config option at runtime.<br>For example to disable smart case search, use `:set search.smart-case false`. |
| `:toggle-option`, `:toggle` | Toggle a boolean config option at runtime.<br>For example to toggle smart case search, use `:toggle search.smart-case`. |
| `:toggle-option`, `:toggle` | Toggle a config option at runtime.<br>For example to toggle smart case search, use `:toggle search.smart-case`. |
| `:get-option`, `:get` | Get the current value of a config option. |
| `:sort` | Sort ranges in selection. |
| `:rsort` | Sort ranges in selection in reverse order. |
| `:reflow` | Hard-wrap the current selection of lines to a given width. |
| `:tree-sitter-subtree`, `:ts-subtree` | Display tree sitter subtree under cursor, primarily for debugging queries. |
| `:tree-sitter-subtree`, `:ts-subtree` | Display the smallest tree-sitter subtree that spans the primary selection, primarily for debugging queries. |
| `:config-reload` | Refresh user config. |
| `:config-open` | Open the user config.toml file. |
| `:config-open-workspace` | Open the workspace config.toml file. |
@ -85,6 +84,8 @@
| `:reset-diff-change`, `:diffget`, `:diffg` | Reset the diff change at the cursor position. |
| `:clear-register` | Clear given register. If no argument is provided, clear all registers. |
| `:redraw` | Clear and re-render the whole UI |
| `:move` | Move the current buffer and its corresponding file to a different path |
| `:move`, `:mv` | Move the current buffer and its corresponding file to a different path |
| `:yank-diagnostic` | Yank diagnostic(s) under primary cursor to register, or clipboard by default |
| `:read`, `:r` | Load a file into buffer |
| `:echo` | Prints the given arguments to the statusline. |
| `:noop` | Does nothing. |

View file

@ -1,4 +1,4 @@
# Adding new languages to Helix
## Adding new languages to Helix
In order to add a new language to Helix, you will need to follow the steps
below.
@ -34,16 +34,10 @@ below.
2. Create a new directory for the language with the path
`runtime/queries/<name>/`.
3. Refer to the
[tree-sitter website](https://tree-sitter.github.io/tree-sitter/syntax-highlighting#queries)
[tree-sitter website](https://tree-sitter.github.io/tree-sitter/3-syntax-highlighting.html#highlights)
for more information on writing queries.
4. A list of highlight captures can be found [on the themes page](https://docs.helix-editor.com/themes.html#scopes).
> 💡 In Helix, the first matching query takes precedence when evaluating
> queries, which is different from other editors such as Neovim where the last
> matching query supersedes the ones before it. See
> [this issue](https://github.com/helix-editor/helix/pull/1170#issuecomment-997294090)
> for an example.
## Common issues
- If you encounter errors when running Helix after switching branches, you may

View file

@ -1,4 +1,4 @@
# Adding indent queries
## Adding indent queries
Helix uses tree-sitter to correctly indent new lines. This requires a tree-
sitter grammar and an `indent.scm` query file placed in `runtime/queries/

View file

@ -1,4 +1,4 @@
# Adding Injection Queries
## Adding Injection Queries
Writing language injection queries allows one to highlight a specific node as a different language.
In addition to the [standard][upstream-docs] language injection options used by tree-sitter, there
@ -57,4 +57,4 @@ second argument (a string).
- `#any-of?` (standard):
The first argument (a capture) must be one of the other arguments (strings).
[upstream-docs]: http://tree-sitter.github.io/tree-sitter/syntax-highlighting#language-injection
[upstream-docs]: https://tree-sitter.github.io/tree-sitter/3-syntax-highlighting.html#language-injection

View file

@ -1,4 +1,4 @@
# Adding textobject queries
## Adding textobject queries
Helix supports textobjects that are language specific, such as functions, classes, etc.
These textobjects require an accompanying tree-sitter grammar and a `textobjects.scm` query file
@ -44,6 +44,6 @@ in its `textobjects.scm` file, function navigation should also work automaticall
`function.movement` should be defined only if the node captured by `function.around`
doesn't make sense in a navigation context.
[tree-sitter-queries]: https://tree-sitter.github.io/tree-sitter/using-parsers#query-syntax
[tree-sitter-captures]: https://tree-sitter.github.io/tree-sitter/using-parsers#capturing-nodes
[tree-sitter-queries]: https://tree-sitter.github.io/tree-sitter/using-parsers/queries/1-syntax.html
[tree-sitter-captures]: https://tree-sitter.github.io/tree-sitter/using-parsers/queries/2-operators.html#capturing-nodes
[textobject-examples]: https://github.com/search?q=repo%3Ahelix-editor%2Fhelix+path%3A%2A%2A/textobjects.scm&type=Code&ref=advsearch&l=&l=

View file

@ -1,6 +1,7 @@
# Installing Helix
To install Helix, follow the instructions specific to your operating system.
The typical way to install Helix is via [your operating system's package manager](./package-managers.md).
Note that:
- To get the latest nightly version of Helix, you need to
@ -8,12 +9,16 @@ Note that:
- To take full advantage of Helix, install the language servers for your
preferred programming languages. See the
[wiki](https://github.com/helix-editor/helix/wiki/How-to-install-the-default-language-servers)
[wiki](https://github.com/helix-editor/helix/wiki/Language-Server-Configurations)
for instructions.
## Pre-built binaries
Download pre-built binaries from the [GitHub Releases page](https://github.com/helix-editor/helix/releases).
Add the `hx` binary to your system's `$PATH` to use it from the command line, and copy the `runtime` directory into the config directory (for example `~/.config/helix/runtime` on Linux/macOS).
The runtime location can be overriden via the HELIX_RUNTIME environment variable.
The tarball contents include an `hx` binary and a `runtime` directory.
To set up Helix:
1. Add the `hx` binary to your system's `$PATH` to allow it to be used from the command line.
2. Copy the `runtime` directory to a location that `hx` searches for runtime files. A typical location on Linux/macOS is `~/.config/helix/runtime`.
To see the runtime directories that `hx` searches, run `hx --health`. If necessary, you can override the default runtime location by setting the `HELIX_RUNTIME` environment variable.

View file

@ -112,39 +112,43 @@ Normal mode is the default mode when you launch helix. You can return to it from
### Selection manipulation
| Key | Description | Command |
| ----- | ----------- | ------- |
| `s` | Select all regex matches inside selections | `select_regex` |
| `S` | Split selection into sub selections on regex matches | `split_selection` |
| `Alt-s` | Split selection on newlines | `split_selection_on_newline` |
| `Alt-minus` | Merge selections | `merge_selections` |
| `Alt-_` | Merge consecutive selections | `merge_consecutive_selections` |
| `&` | Align selection in columns | `align_selections` |
| `_` | Trim whitespace from the selection | `trim_selections` |
| `;` | Collapse selection onto a single cursor | `collapse_selection` |
| `Alt-;` | Flip selection cursor and anchor | `flip_selections` |
| `Alt-:` | Ensures the selection is in forward direction | `ensure_selections_forward` |
| `,` | Keep only the primary selection | `keep_primary_selection` |
| `Alt-,` | Remove the primary selection | `remove_primary_selection` |
| `C` | Copy selection onto the next line (Add cursor below) | `copy_selection_on_next_line` |
| `Alt-C` | Copy selection onto the previous line (Add cursor above) | `copy_selection_on_prev_line` |
| `(` | Rotate main selection backward | `rotate_selections_backward` |
| `)` | Rotate main selection forward | `rotate_selections_forward` |
| `Alt-(` | Rotate selection contents backward | `rotate_selection_contents_backward` |
| `Alt-)` | Rotate selection contents forward | `rotate_selection_contents_forward` |
| `%` | Select entire file | `select_all` |
| `x` | Select current line, if already selected, extend to next line | `extend_line_below` |
| `X` | Extend selection to line bounds (line-wise selection) | `extend_to_line_bounds` |
| `Alt-x` | Shrink selection to line bounds (line-wise selection) | `shrink_to_line_bounds` |
| `J` | Join lines inside selection | `join_selections` |
| `Alt-J` | Join lines inside selection and select the inserted space | `join_selections_space` |
| `K` | Keep selections matching the regex | `keep_selections` |
| `Alt-K` | Remove selections matching the regex | `remove_selections` |
| `Ctrl-c` | Comment/uncomment the selections | `toggle_comments` |
| `Alt-o`, `Alt-up` | Expand selection to parent syntax node (**TS**) | `expand_selection` |
| `Alt-i`, `Alt-down` | Shrink syntax tree object selection (**TS**) | `shrink_selection` |
| `Alt-p`, `Alt-left` | Select previous sibling node in syntax tree (**TS**) | `select_prev_sibling` |
| `Alt-n`, `Alt-right` | Select next sibling node in syntax tree (**TS**) | `select_next_sibling` |
| Key | Description | Command |
| ----- | ----------- | ------- |
| `s` | Select all regex matches inside selections | `select_regex` |
| `S` | Split selection into sub selections on regex matches | `split_selection` |
| `Alt-s` | Split selection on newlines | `split_selection_on_newline` |
| `Alt-minus` | Merge selections | `merge_selections` |
| `Alt-_` | Merge consecutive selections | `merge_consecutive_selections` |
| `&` | Align selection in columns | `align_selections` |
| `_` | Trim whitespace from the selection | `trim_selections` |
| `;` | Collapse selection onto a single cursor | `collapse_selection` |
| `Alt-;` | Flip selection cursor and anchor | `flip_selections` |
| `Alt-:` | Ensures the selection is in forward direction | `ensure_selections_forward` |
| `,` | Keep only the primary selection | `keep_primary_selection` |
| `Alt-,` | Remove the primary selection | `remove_primary_selection` |
| `C` | Copy selection onto the next line (Add cursor below) | `copy_selection_on_next_line` |
| `Alt-C` | Copy selection onto the previous line (Add cursor above) | `copy_selection_on_prev_line` |
| `(` | Rotate main selection backward | `rotate_selections_backward` |
| `)` | Rotate main selection forward | `rotate_selections_forward` |
| `Alt-(` | Rotate selection contents backward | `rotate_selection_contents_backward` |
| `Alt-)` | Rotate selection contents forward | `rotate_selection_contents_forward` |
| `%` | Select entire file | `select_all` |
| `x` | Select current line, if already selected, extend to next line | `extend_line_below` |
| `X` | Extend selection to line bounds (line-wise selection) | `extend_to_line_bounds` |
| `Alt-x` | Shrink selection to line bounds (line-wise selection) | `shrink_to_line_bounds` |
| `J` | Join lines inside selection | `join_selections` |
| `Alt-J` | Join lines inside selection and select the inserted space | `join_selections_space` |
| `K` | Keep selections matching the regex | `keep_selections` |
| `Alt-K` | Remove selections matching the regex | `remove_selections` |
| `Ctrl-c` | Comment/uncomment the selections | `toggle_comments` |
| `Alt-o`, `Alt-up` | Expand selection to parent syntax node (**TS**) | `expand_selection` |
| `Alt-i`, `Alt-down` | Shrink syntax tree object selection (**TS**) | `shrink_selection` |
| `Alt-p`, `Alt-left` | Select previous sibling node in syntax tree (**TS**) | `select_prev_sibling` |
| `Alt-n`, `Alt-right` | Select next sibling node in syntax tree (**TS**) | `select_next_sibling` |
| `Alt-a` | Select all sibling nodes in syntax tree (**TS**) | `select_all_siblings` |
| `Alt-I`, `Alt-Shift-down`| Select all children nodes in syntax tree (**TS**) | `select_all_children` |
| `Alt-e` | Move to end of parent node in syntax tree (**TS**) | `move_parent_node_end` |
| `Alt-b` | Move to start of parent node in syntax tree (**TS**) | `move_parent_node_start` |
### Search
@ -156,7 +160,8 @@ Search commands all operate on the `/` register by default. To use a different r
| `?` | Search for previous pattern | `rsearch` |
| `n` | Select next search match | `search_next` |
| `N` | Select previous search match | `search_prev` |
| `*` | Use current selection as the search pattern | `search_selection` |
| `*` | Use current selection as the search pattern, automatically wrapping with `\b` on word boundaries | `search_selection_detect_word_boundaries` |
| `Alt-*` | Use current selection as the search pattern | `search_selection` |
### Minor modes
@ -278,7 +283,7 @@ This layer is a kludge of mappings, mostly pickers.
| Key | Description | Command |
| ----- | ----------- | ------- |
| `f` | Open file picker | `file_picker` |
| `f` | Open file picker at LSP workspace root | `file_picker` |
| `F` | Open file picker at current working directory | `file_picker_in_current_directory` |
| `b` | Open buffer picker | `buffer_picker` |
| `j` | Open jumplist picker | `jumplist_picker` |
@ -304,6 +309,7 @@ This layer is a kludge of mappings, mostly pickers.
| `R` | Replace selections by clipboard contents | `replace_selections_with_clipboard` |
| `/` | Global search in workspace folder | `global_search` |
| `?` | Open command palette | `command_palette` |
| `B` | Show blame for the current line | `blame_line` |
> 💡 Global search displays results in a fuzzy picker, use `Space + '` to bring it back up after opening a file.
@ -320,10 +326,14 @@ Displays documentation for item under cursor. Remapping currently not supported.
Displays documentation for the selected completion item. Remapping currently not supported.
| Key | Description |
| ---- | ----------- |
| `Shift-Tab`, `Ctrl-p`, `Up` | Previous entry |
| `Tab`, `Ctrl-n`, `Down` | Next entry |
| Key | Description |
| ---- | ----------- |
| `Shift-Tab`, `Ctrl-p`, `Up` | Previous entry |
| `Tab`, `Ctrl-n`, `Down` | Next entry |
| `Enter` | Close menu and accept completion |
| `Ctrl-c` | Close menu and reject completion |
Any other keypresses result in the completion being accepted.
##### Signature-help Popup
@ -436,6 +446,8 @@ you to selectively add search terms to your selections.
## Picker
Keys to use within picker. Remapping currently not supported.
See the documentation page on [pickers](./pickers.md) for more info.
[Prompt](#prompt) keybinds also work in pickers, except where they conflict with picker keybinds.
| Key | Description |
| ----- | ------------- |

View file

@ -1,4 +1,4 @@
# Language Support
## Language Support
The following languages and Language Servers are supported. To use
Language Server features, you must first [configure][lsp-config-wiki] the

View file

@ -1,4 +1,4 @@
# Languages
## Languages
Language-specific settings and settings for language servers are configured
in `languages.toml` files.
@ -13,7 +13,7 @@ There are three possible locations for a `languages.toml` file:
2. In your [configuration directory](./configuration.md). This overrides values
from the built-in language configuration. For example, to disable
auto-LSP-formatting in Rust:
auto-formatting for Rust:
```toml
# in <config_dir>/helix/languages.toml
@ -60,15 +60,17 @@ These configuration keys are available:
| `shebangs` | The interpreters from the shebang line, for example `["sh", "bash"]` |
| `roots` | A set of marker files to look for when trying to find the workspace root. For example `Cargo.lock`, `yarn.lock` |
| `auto-format` | Whether to autoformat this language when saving |
| `diagnostic-severity` | Minimal severity of diagnostic for it to be displayed. (Allowed values: `Error`, `Warning`, `Info`, `Hint`) |
| `diagnostic-severity` | Minimal severity of diagnostic for it to be displayed. (Allowed values: `error`, `warning`, `info`, `hint`) |
| `comment-tokens` | The tokens to use as a comment token, either a single token `"//"` or an array `["//", "///", "//!"]` (the first token will be used for commenting). Also configurable as `comment-token` for backwards compatibility|
| `block-comment-tokens`| The start and end tokens for a multiline comment either an array or single table of `{ start = "/*", end = "*/"}`. The first set of tokens will be used for commenting, any pairs in the array can be uncommented |
| `indent` | The indent to use. Has sub keys `unit` (the text inserted into the document when indenting; usually set to N spaces or `"\t"` for tabs) and `tab-width` (the number of spaces rendered for a tab) |
| `language-servers` | The Language Servers used for this language. See below for more information in the section [Configuring Language Servers for a language](#configuring-language-servers-for-a-language) |
| `grammar` | The tree-sitter grammar to use (defaults to the value of `name`) |
| `formatter` | The formatter for the language, it will take precedence over the lsp when defined. The formatter must be able to take the original file as input from stdin and write the formatted file to stdout |
| `soft-wrap` | [editor.softwrap](./configuration.md#editorsoft-wrap-section)
| `soft-wrap` | [editor.softwrap](./editor.md#editorsoft-wrap-section)
| `text-width` | Maximum line length. Used for the `:reflow` command and soft-wrapping if `soft-wrap.wrap-at-text-width` is set, defaults to `editor.text-width` |
| `rulers` | Overrides the `editor.rulers` config key for the language. |
| `path-completion` | Overrides the `editor.path-completion` config key for the language. |
| `workspace-lsp-roots` | Directories relative to the workspace root that are treated as LSP roots. Should only be set in `.helix/config.toml`. Overwrites the setting of the same name in `config.toml` if set. |
| `persistent-diagnostic-sources` | An array of LSP diagnostic sources assumed unchanged when the language server resends the same set of diagnostics. Helix can track the position for these diagnostics internally instead. Useful for diagnostics that are recomputed on save.
@ -127,7 +129,7 @@ These are the available options for a language server.
| ---- | ----------- |
| `command` | The name or path of the language server binary to execute. Binaries must be in `$PATH` |
| `args` | A list of arguments to pass to the language server binary |
| `config` | LSP initialization options |
| `config` | Language server initialization options |
| `timeout` | The maximum time a request to the language server may take, in seconds. Defaults to `20` |
| `environment` | Any environment variables that will be used when starting the language server `{ "KEY1" = "Value1", "KEY2" = "Value2" }` |
| `required-root-patterns` | A list of `glob` patterns to look for in the working directory. The language server is started if at least one of them is found. |
@ -240,4 +242,4 @@ use-grammars = { except = [ "yaml", "json" ] }
When omitted, all grammars are fetched and built.
[treesitter-language-injection]: https://tree-sitter.github.io/tree-sitter/syntax-highlighting#language-injection
[treesitter-language-injection]: https://tree-sitter.github.io/tree-sitter/3-syntax-highlighting.html#language-injection

View file

@ -0,0 +1,32 @@
# Helix mode in other software
Helix' keymap and interaction model ([Using Helix](#usage.md)) is easier to adopt if it can be used consistently in many editing contexts. Yet, certain use cases cannot easily be addressed directly in Helix. Similar to vim, this leads to the creation of "Helix mode" in various other software products, allowing Helix-style editing for a greater variety of use cases.
"Helix mode" is frequently still in early stages or missing entirely. For such cases, we also link to relevant bugs or discussions.
## Other editors
| Editor | Plugin or feature providing Helix editing | Comments
| --- | --- | --- |
| [Vim](https://www.vim.org/) | [helix.vim](https://github.com/chtenb/helix.vim) config |
| [IntelliJ IDEA](https://www.jetbrains.com/idea/) / [Android Studio](https://developer.android.com/studio)| [IdeaVim](https://plugins.jetbrains.com/plugin/164-ideavim) plugin + [helix.idea.vim](https://github.com/chtenb/helix.vim) config | Minimum recommended version is IdeaVim 2.19.0.
| [Visual Studio Code](https://code.visualstudio.com/) | [Dance](https://marketplace.visualstudio.com/items?itemName=gregoire.dance) extension, or its [Helix fork](https://marketplace.visualstudio.com/items?itemName=kend.dancehelixkey) | The Helix fork has diverged. You can also use the original Dance and tweak its keybindings directly (try [this config](https://github.com/71/dance/issues/299#issuecomment-1655509531)).
| [Visual Studio Code](https://code.visualstudio.com/) | [Helix for VS Code](https://marketplace.visualstudio.com/items?itemName=jasew.vscode-helix-emulation) extension|
| [Zed](https://zed.dev/) | native via keybindings ([Bug](https://github.com/zed-industries/zed/issues/4642)) |
| [CodeMirror](https://codemirror.net/) | [codemirror-helix](https://gitlab.com/_rvidal/codemirror-helix) |
## Shells
| Shell | Plugin or feature providing Helix editing
| --- | ---
| Fish | [Feature Request](https://github.com/fish-shell/fish-shell/issues/7748)
| Fish | [fish-helix](https://github.com/sshilovsky/fish-helix/tree/main)
| Zsh | [helix-zsh](https://github.com/john-h-k/helix-zsh)
| Nushell | [Feature Request](https://github.com/nushell/reedline/issues/639)
## Other software
| Software | Plugin or feature providing Helix editing. | Comments
| --- | --- | --- |
| [Obsidian](https://obsidian.md/) | [Obsidian-Helix](https://github.com/Sinono3/obsidian-helix) | Uses `codemirror-helix` listed above.

View file

@ -1,7 +1,8 @@
## Package managers
- [Linux](#linux)
- [Ubuntu](#ubuntu)
- [Ubuntu/Debian](#ubuntudebian)
- [Ubuntu (PPA)](#ubuntu-ppa)
- [Fedora/RHEL](#fedorarhel)
- [Arch Linux extra](#arch-linux-extra)
- [NixOS](#nixos)
@ -17,13 +18,20 @@
- [Chocolatey](#chocolatey)
- [MSYS2](#msys2)
[![Packaging status](https://repology.org/badge/vertical-allrepos/helix.svg)](https://repology.org/project/helix/versions)
[![Packaging status](https://repology.org/badge/vertical-allrepos/helix-editor.svg)](https://repology.org/project/helix-editor/versions)
## Linux
The following third party repositories are available:
### Ubuntu
### Ubuntu/Debian
Install the Debian package from the release page.
If you are running a system older than Ubuntu 22.04, Mint 21, or Debian 12, you can build the `.deb` file locally
[from source](./building-from-source.md#building-the-debian-package).
### Ubuntu (PPA)
Add the `PPA` for Helix:
@ -101,7 +109,15 @@ Download the official Helix AppImage from the [latest releases](https://github.c
chmod +x helix-*.AppImage # change permission for executable mode
./helix-*.AppImage # run helix
```
You can optionally [add the `.desktop` file](./building-from-source.md#configure-the-desktop-shortcut). Helix must be installed in `PATH` with the name `hx`. For example:
```sh
mkdir -p "$HOME/.local/bin"
mv helix-*.AppImage "$HOME/.local/bin/hx"
```
and make sure `~/.local/bin` is in your `PATH`.
## macOS
### Homebrew Core
@ -113,7 +129,7 @@ brew install helix
### MacPorts
```sh
port install helix
sudo port install helix
```
## Windows

11
book/src/pickers.md Normal file
View file

@ -0,0 +1,11 @@
## Using pickers
Helix has a variety of pickers, which are interactive windows used to select various kinds of items. These include a file picker, global search picker, and more. Most pickers are accessed via keybindings in [space mode](./keymap.md#space-mode). Pickers have their own [keymap](./keymap.md#picker) for navigation.
### Filtering Picker Results
Most pickers perform fuzzy matching using [fzf syntax](https://github.com/junegunn/fzf?tab=readme-ov-file#search-syntax). Two exceptions are the global search picker, which uses regex, and the workspace symbol picker, which passes search terms to the language server. Note that OR operations (`|`) are not currently supported.
If a picker shows multiple columns, you may apply the filter to a specific column by prefixing the column name with `%`. Column names can be shortened to any prefix, so `%p`, `%pa` or `%pat` all mean the same as `%path`. For example, a query of `helix %p .toml !lang` in the global search picker searches for the term "helix" within files with paths ending in ".toml" but not including "lang".
You can insert the contents of a [register](./registers.md) using `Ctrl-r` followed by a register name. For example, one could insert the currently selected text using `Ctrl-r`-`.`, or the directory of the current file using `Ctrl-r`-`%` followed by `Ctrl-w` to remove the last path section. The global search picker will use the contents of the [search register](./registers.md#default-registers) if you press `Enter` without typing a filter. For example, pressing `*`-`Space-/`-`Enter` will start a global search for the currently selected text.

View file

@ -1,13 +1,34 @@
# Key remapping
## Key remapping
Helix currently supports one-way key remapping through a simple TOML configuration
file. (More powerful solutions such as rebinding via commands will be
available in the future).
There are three kinds of commands that can be used in keymaps:
* Static commands: commands like `move_char_right` which are usually bound to
keys and used for movement and editing. A list of static commands is
available in the [Keymap](./keymap.html) documentation and in the source code
in [`helix-term/src/commands.rs`](https://github.com/helix-editor/helix/blob/master/helix-term/src/commands.rs)
at the invocation of `static_commands!` macro.
* Typable commands: commands that can be executed from command mode (`:`), for
example `:write!`. See the [Commands](./commands.html) documentation for a
list of available typeable commands or the `TypableCommandList` declaration in
the source code at [`helix-term/src/commands/typed.rs`](https://github.com/helix-editor/helix/blob/master/helix-term/src/commands/typed.rs).
* Macros: sequences of keys that are executed in order. These keybindings
start with `@` and then list any number of keys to be executed. For example
`@miw` can be used to select the surrounding word. For now, macro keybindings
are not allowed in keybinding sequences due to limitations in the way that
command sequences are executed. Modifier keys (e.g. Alt+o) can be used
like `"<A-o>"`, e.g. `"@miw<A-o>"`
To remap keys, create a `config.toml` file in your `helix` configuration
directory (default `~/.config/helix` on Linux systems) with a structure like
this:
> 💡 To set a modifier + key as a keymap, type `A-X = ...` or `C-X = ...` for Alt + X or Ctrl + X. Combine with Shift using a dash, e.g. `C-S-esc`.
> Within macros, wrap them in `<>`, e.g. `<A-X>` and `<C-X>` to distinguish from the `A` or `C` keys.
```toml
# At most one section each of 'keys.normal', 'keys.insert' and 'keys.select'
[keys.normal]
@ -18,6 +39,7 @@ w = "move_line_up" # Maps the 'w' key move_line_up
"C-S-esc" = "extend_line" # Maps Ctrl-Shift-Escape to extend_line
g = { a = "code_action" } # Maps `ga` to show possible code actions
"ret" = ["open_below", "normal_mode"] # Maps the enter key to open_below then re-enter normal mode
"A-x" = "@x<A-d>" # Maps Alt-x to a macro selecting the whole line and deleting it without yanking it
[keys.insert]
"A-x" = "normal_mode" # Maps Alt-X to enter normal mode
@ -50,15 +72,28 @@ t = ":run-shell-command cargo test"
## Special keys and modifiers
Ctrl, Shift and Alt modifiers are encoded respectively with the prefixes
`C-`, `S-` and `A-`. Special keys are encoded as follows:
Ctrl, Shift and Alt modifiers are encoded respectively with the prefixes `C-`, `S-` and `A-`.
The [Super key](https://en.wikipedia.org/wiki/Super_key_(keyboard_button)) - the Windows/Linux
key or the Command key on Mac keyboards - is also supported when using a terminal emulator that
supports the [enhanced keyboard protocol](https://github.com/helix-editor/helix/wiki/Terminal-Support#enhanced-keyboard-protocol).
The super key is encoded with prefixes `Meta-`, `Cmd-` or `Win-`. These are all synonyms for the
super modifier - binding a key with a `Win-` modifier will mean it can be used with the
Windows/Linux key or the Command key.
```toml
[keys.normal]
C-s = ":write" # Ctrl and 's' to write
Cmd-s = ":write" # Cmd or Win or Meta and 's' to write
```
Special keys are encoded as follows:
| Key name | Representation |
| --- | --- |
| Backspace | `"backspace"` |
| Space | `"space"` |
| Return/Enter | `"ret"` |
| \- | `"minus"` |
| Left | `"left"` |
| Right | `"right"` |
| Up | `"up"` |
@ -75,5 +110,13 @@ Ctrl, Shift and Alt modifiers are encoded respectively with the prefixes
Keys can be disabled by binding them to the `no_op` command.
A list of commands is available in the [Keymap](https://docs.helix-editor.com/keymap.html) documentation
and in the source code at [`helix-term/src/commands.rs`](https://github.com/helix-editor/helix/blob/master/helix-term/src/commands.rs) at the invocation of `static_commands!` macro and the `TypableCommandList`.
All other keys such as `?`, `!`, `-` etc. can be used literally:
```toml
[keys.normal]
"?" = ":write"
"!" = ":write"
"-" = ":write"
```
Note: `-` can't be used when combined with a modifier, for example `Alt` + `-` should be written as `A-minus`. `A--` is not accepted.

View file

@ -1,4 +1,4 @@
# Themes
## Themes
To use a theme add `theme = "<name>"` to the top of your [`config.toml`](./configuration.md) file, or select it during runtime using `:theme <name>`.
@ -136,7 +136,7 @@ The following is a list of scopes available to use for styling:
#### Syntax highlighting
These keys match [tree-sitter scopes](https://tree-sitter.github.io/tree-sitter/syntax-highlighting#theme).
These keys match [tree-sitter scopes](https://tree-sitter.github.io/tree-sitter/3-syntax-highlighting.html#highlights).
When determining styling for a highlight, the longest matching theme key will be used. For example, if the highlight is `function.builtin.static`, the key `function.builtin` will be used instead of `function`.
@ -283,7 +283,6 @@ These scopes are used for theming the editor interface:
| `ui.debug.active` | Indicator for the line at which debugging execution is paused at, found in the gutter |
| `ui.gutter` | Gutter |
| `ui.gutter.selected` | Gutter for the line the cursor is on |
| `ui.highlight.frameline` | Line at which debugging execution is paused at |
| `ui.linenr` | Line numbers |
| `ui.linenr.selected` | Line number for the line the cursor is on |
| `ui.statusline` | Statusline |
@ -293,30 +292,36 @@ These scopes are used for theming the editor interface:
| `ui.statusline.select` | Statusline mode during select mode ([only if `editor.color-modes` is enabled][editor-section]) |
| `ui.statusline.separator` | Separator character in statusline |
| `ui.bufferline` | Style for the buffer line |
| `ui.bufferline.active` | Style for the active buffer in buffer line |
| `ui.bufferline.active` | Style for the active buffer in buffer line |
| `ui.bufferline.background` | Style for bufferline background |
| `ui.popup` | Documentation popups (e.g. Space + k) |
| `ui.popup.info` | Prompt for multiple key options |
| `ui.picker.header` | Header row area in pickers with multiple columns |
| `ui.picker.header.column` | Column names in pickers with multiple columns |
| `ui.picker.header.column.active` | The column name in pickers with multiple columns where the cursor is entering into. |
| `ui.window` | Borderlines separating splits |
| `ui.help` | Description box for commands |
| `ui.text` | Default text style, command prompts, popup text, etc. |
| `ui.text.focus` | The currently selected line in the picker |
| `ui.text.inactive` | Same as `ui.text` but when the text is inactive (e.g. suggestions) |
| `ui.text.info` | The key: command text in `ui.popup.info` boxes |
| `ui.text.directory` | Directory names in prompt completion |
| `ui.virtual.ruler` | Ruler columns (see the [`editor.rulers` config][editor-section]) |
| `ui.virtual.whitespace` | Visible whitespace characters |
| `ui.virtual.indent-guide` | Vertical indent width guides |
| `ui.virtual.inlay-hint` | Default style for inlay hints of all kinds |
| `ui.virtual.inlay-hint.parameter` | Style for inlay hints of kind `parameter` (LSPs are not required to set a kind) |
| `ui.virtual.inlay-hint.type` | Style for inlay hints of kind `type` (LSPs are not required to set a kind) |
| `ui.virtual.inlay-hint.parameter` | Style for inlay hints of kind `parameter` (language servers are not required to set a kind) |
| `ui.virtual.inlay-hint.type` | Style for inlay hints of kind `type` (language servers are not required to set a kind) |
| `ui.virtual.wrap` | Soft-wrap indicator (see the [`editor.soft-wrap` config][editor-section]) |
| `ui.virtual.jump-label` | Style for virtual jump labels |
| `ui.virtual.inline-blame` | Inline blame indicator (see the [`editor.inline-blame` config][editor-section]) |
| `ui.menu` | Code and command completion menus |
| `ui.menu.selected` | Selected autocomplete item |
| `ui.menu.scroll` | `fg` sets thumb color, `bg` sets track color of scrollbar |
| `ui.selection` | For selections in the editing area |
| `ui.selection.primary` | |
| `ui.highlight` | Highlighted lines in the picker preview |
| `ui.highlight.frameline` | Line at which debugging execution is paused at |
| `ui.cursorline.primary` | The line of the primary cursor ([if cursorline is enabled][editor-section]) |
| `ui.cursorline.secondary` | The lines of any other cursors ([if cursorline is enabled][editor-section]) |
| `ui.cursorcolumn.primary` | The column of the primary cursor ([if cursorcolumn is enabled][editor-section]) |
@ -332,5 +337,6 @@ These scopes are used for theming the editor interface:
| `diagnostic.error` | Diagnostics error (editing area) |
| `diagnostic.unnecessary` | Diagnostics with unnecessary tag (editing area) |
| `diagnostic.deprecated` | Diagnostics with deprecated tag (editing area) |
| `tabstop` | Snippet placeholder |
[editor-section]: ./configuration.md#editor-section

View file

@ -7,3 +7,27 @@ can be accessed via the command `hx --tutor` or `:tutor`.
> 💡 Currently, not all functionality is fully documented, please refer to the
> [key mappings](./keymap.md) list.
## Modes
Helix is a modal editor, meaning it has different modes for different tasks. The main modes are:
* [Normal mode](./keymap.md#normal-mode): For navigation and editing commands. This is the default mode.
* [Insert mode](./keymap.md#insert-mode): For typing text directly into the document. Access by typing `i` in normal mode.
* [Select/extend mode](./keymap.md#select--extend-mode): For making selections and performing operations on them. Access by typing `v` in normal mode.
## Buffers
Buffers are in-memory representations of files. You can have multiple buffers open at once. Use [pickers](./pickers.md) or commands like `:buffer-next` and `:buffer-previous` to open buffers or switch between them.
## Selection-first editing
Inspired by [Kakoune](http://kakoune.org/), Helix follows the `selection → action` model. This means that whatever you are going to act on (a word, a paragraph, a line, etc.) is selected first and the action itself (delete, change, yank, etc.) comes second. A cursor is simply a single width selection.
## Multiple selections
Also inspired by Kakoune, multiple selections are a core mode of interaction in Helix. For example, the standard way of replacing multiple instances of a word is to first select all instances (so there is one selection per instance) and then use the change action (`c`) to edit them all at the same time.
## Motions
Motions are commands that move the cursor or modify selections. They're used for navigation and text manipulation. Examples include `w` to move to the next word, or `f` to find a character. See the [Movement](./keymap.md#movement) section of the keymap for more motions.

View file

@ -1,5 +1,5 @@
<!DOCTYPE HTML>
<html lang="{{ language }}" class="{{ default_theme }}" dir="{{ text_direction }}">
<html lang="{{ language }}" class="{{ default_theme }} sidebar-visible" dir="{{ text_direction }}">
<head>
<!-- Book generated using mdBook -->
<meta charset="UTF-8">
@ -52,15 +52,17 @@
<!-- MathJax -->
<script async src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.1/MathJax.js?config=TeX-AMS-MML_HTMLorMML"></script>
{{/if}}
</head>
<body class="sidebar-visible no-js">
<div id="body-container">
<!-- Provide site root to javascript -->
<script>
var path_to_root = "{{ path_to_root }}";
var default_theme = window.matchMedia("(prefers-color-scheme: dark)").matches ? "{{ preferred_dark_theme }}" : "{{ default_theme }}";
</script>
<!-- Start loading toc.js asap -->
<script src="{{ path_to_root }}toc.js"></script>
</head>
<body>
<div id="body-container">
<!-- Work around some values being stored in localStorage wrapped in quotes -->
<script>
try {
@ -82,19 +84,16 @@
var theme;
try { theme = localStorage.getItem('mdbook-theme'); } catch(e) { }
if (theme === null || theme === undefined) { theme = default_theme; }
var html = document.querySelector('html');
const html = document.documentElement;
html.classList.remove('{{ default_theme }}')
html.classList.add(theme);
var body = document.querySelector('body');
body.classList.remove('no-js')
body.classList.add('js');
html.classList.add("js");
</script>
<input type="checkbox" id="sidebar-toggle-anchor" class="hidden">
<!-- Hide / unhide sidebar before it is displayed -->
<script>
var body = document.querySelector('body');
var sidebar = null;
var sidebar_toggle = document.getElementById("sidebar-toggle-anchor");
if (document.body.clientWidth >= 1080) {
@ -104,39 +103,21 @@
sidebar = 'hidden';
}
sidebar_toggle.checked = sidebar === 'visible';
body.classList.remove('sidebar-visible');
body.classList.add("sidebar-" + sidebar);
html.classList.remove('sidebar-visible');
html.classList.add("sidebar-" + sidebar);
</script>
<nav id="sidebar" class="sidebar" aria-label="Table of contents">
<div class="sidebar-scrollbox">
{{#toc}}{{/toc}}
<!-- populated by js -->
<mdbook-sidebar-scrollbox class="sidebar-scrollbox"></mdbook-sidebar-scrollbox>
<noscript>
<iframe class="sidebar-iframe-outer" src="{{ path_to_root }}toc.html"></iframe>
</noscript>
<div id="sidebar-resize-handle" class="sidebar-resize-handle">
<div class="sidebar-resize-indicator"></div>
</div>
<div id="sidebar-resize-handle" class="sidebar-resize-handle"></div>
</nav>
<!-- Track and set sidebar scroll position -->
<script>
var sidebarScrollbox = document.querySelector('#sidebar .sidebar-scrollbox');
sidebarScrollbox.addEventListener('click', function(e) {
if (e.target.tagName === 'A') {
sessionStorage.setItem('sidebar-scroll', sidebarScrollbox.scrollTop);
}
}, { passive: true });
var sidebarScrollTop = sessionStorage.getItem('sidebar-scroll');
sessionStorage.removeItem('sidebar-scroll');
if (sidebarScrollTop) {
// preserve sidebar scroll position when navigating via links within sidebar
sidebarScrollbox.scrollTop = sidebarScrollTop;
} else {
// scroll sidebar to current active section when navigating via "next/previous chapter" buttons
var activeSection = document.querySelector('#sidebar .active');
if (activeSection) {
activeSection.scrollIntoView({ block: 'center' });
}
}
</script>
<div id="page-wrapper" class="page-wrapper">
<div class="page">

View file

@ -47,6 +47,12 @@
<content_rating type="oars-1.1" />
<releases>
<release version="25.01.1" date="2025-01-19">
<url>https://github.com/helix-editor/helix/releases/tag/25.01.1</url>
</release>
<release version="25.01" date="2025-01-03">
<url>https://helix-editor.com/news/release-25-01-highlights/</url>
</release>
<release version="24.07" date="2024-07-14">
<url>https://github.com/helix-editor/helix/releases/tag/24.07</url>
</release>

View file

@ -86,6 +86,6 @@ Keywords[ru]=текст;текстовый редактор;
Keywords[sr]=Текст;едитор;
Keywords[tr]=Metin;düzenleyici;
Icon=helix
Categories=Utility;TextEditor;
Categories=Utility;TextEditor;ConsoleOnly
StartupNotify=false
MimeType=text/english;text/plain;text/x-makefile;text/x-c++hdr;text/x-c++src;text/x-chdr;text/x-csrc;text/x-java;text/x-moc;text/x-pascal;text/x-tcl;text/x-tex;application/x-shellscript;text/x-c;text/x-c++;

View file

@ -2,23 +2,31 @@
# Bash completion script for Helix editor
_hx() {
# $1 command name
# $2 word being completed
# $3 word preceding
local cur prev languages
COMPREPLY=()
cur="${COMP_WORDS[COMP_CWORD]}"
prev="${COMP_WORDS[COMP_CWORD - 1]}"
case "$3" in
-g | --grammar)
COMPREPLY="$(compgen -W 'fetch build' -- $2)"
;;
--health)
local languages=$(hx --health |tail -n '+7' |awk '{print $1}' |sed 's/\x1b\[[0-9;]*m//g')
COMPREPLY="$(compgen -W """$languages""" -- $2)"
;;
*)
COMPREPLY="$(compgen -fd -W "-h --help --tutor -V --version -v -vv -vvv --health -g --grammar --vsplit --hsplit -c --config --log" -- """$2""")"
;;
esac
case "$prev" in
-g | --grammar)
mapfile -t COMPREPLY < <(compgen -W 'fetch build' -- "$cur")
return 0
;;
--health)
languages=$(hx --health | tail -n '+7' | awk '{print $1}' | sed 's/\x1b\[[0-9;]*m//g')
mapfile -t COMPREPLY < <(compgen -W """$languages""" -- "$cur")
return 0
;;
esac
local IFS=$'\n'
COMPREPLY=($COMPREPLY)
case "$2" in
-*)
mapfile -t COMPREPLY < <(compgen -W "-h --help --tutor -V --version -v -vv -vvv --health -g --grammar --vsplit --hsplit -c --config --log" -- """$2""")
return 0
;;
*)
mapfile -t COMPREPLY < <(compgen -fd -- """$2""")
return 0
;;
esac
} && complete -o filenames -F _hx hx

View file

@ -20,7 +20,7 @@ var config = [ "--config" "-c" ]
set edit:completion:arg-completer[hx] = {|@args|
var n = (count $args)
if (>= $n 3) {
# Stop completions if passed arg will take presedence
# Stop completions if passed arg will take precedence
# and invalidate further input
if (has-value $skips $args[-2]) {
return

29
contrib/completion/hx.nu Normal file
View file

@ -0,0 +1,29 @@
# Completions for Helix: <https://github.com/helix-editor/helix>
#
# NOTE: the `+N` syntax is not supported in Nushell (https://github.com/nushell/nushell/issues/13418)
# so it has not been specified here and will not be proposed in the autocompletion of Nushell.
# The help message won't be overridden though, so it will still be present here
def health_categories [] {
let languages = ^hx --health languages | detect columns | get Language | filter { $in != null }
let completions = [ "all", "clipboard", "languages" ] | append $languages
return $completions
}
def grammar_categories [] { ["fetch", "build"] }
# A post-modern text editor.
export extern hx [
--help(-h), # Prints help information
--tutor, # Loads the tutorial
--health: string@health_categories, # Checks for potential errors in editor setup
--grammar(-g): string@grammar_categories, # Fetches or builds tree-sitter grammars listed in `languages.toml`
--config(-c): glob, # Specifies a file to use for configuration
-v, # Increases logging verbosity each use for up to 3 times
--log: glob, # Specifies a file to use for logging
--version(-V), # Prints version information
--vsplit, # Splits all given files vertically into different windows
--hsplit, # Splits all given files horizontally into different windows
--working-dir(-w): glob, # Specify an initial working directory
...files: glob, # Sets the input file to use, position can also be specified via file[:row[:col]]
]

3
contrib/hx_launcher.sh Executable file
View file

@ -0,0 +1,3 @@
#!/usr/bin/env sh
HELIX_RUNTIME=/usr/lib/helix/runtime exec /usr/lib/helix/hx "$@"

View file

@ -1,8 +1,84 @@
# Flake's default package for non-flake-enabled nix instances
let
compat = builtins.fetchTarball {
url = "https://github.com/edolstra/flake-compat/archive/b4a34015c698c7793d592d66adbab377907a2be8.tar.gz";
sha256 = "sha256:1qc703yg0babixi6wshn5wm2kgl5y1drcswgszh4xxzbrwkk9sv7";
};
{
lib,
rustPlatform,
callPackage,
runCommand,
installShellFiles,
git,
gitRev ? null,
...
}: let
fs = lib.fileset;
src = fs.difference (fs.gitTracked ./.) (fs.unions [
./.envrc
./rustfmt.toml
./screenshot.png
./book
./docs
./runtime
./flake.lock
(fs.fileFilter (file: lib.strings.hasInfix ".git" file.name) ./.)
(fs.fileFilter (file: file.hasExt "svg") ./.)
(fs.fileFilter (file: file.hasExt "md") ./.)
(fs.fileFilter (file: file.hasExt "nix") ./.)
]);
# Next we actually need to build the grammars and the runtime directory
# that they reside in. It is built by calling the derivation in the
# grammars.nix file, then taking the runtime directory in the git repo
# and hooking symlinks up to it.
grammars = callPackage ./grammars.nix {};
runtimeDir = runCommand "helix-runtime" {} ''
mkdir -p $out
ln -s ${./runtime}/* $out
rm -r $out/grammars
ln -s ${grammars} $out/grammars
'';
in
(import compat {src = ./.;}).defaultNix
rustPlatform.buildRustPackage (self: {
cargoLock = {
lockFile = ./Cargo.lock;
# This is not allowed in nixpkgs but is very convenient here: it allows us to
# avoid specifying `outputHashes` here for any git dependencies we might take
# on temporarily.
allowBuiltinFetchGit = true;
};
nativeBuildInputs = [
installShellFiles
git
];
buildType = "release";
name = with builtins; (fromTOML (readFile ./helix-term/Cargo.toml)).package.name;
src = fs.toSource {
root = ./.;
fileset = src;
};
# Helix attempts to reach out to the network and get the grammars. Nix doesn't allow this.
HELIX_DISABLE_AUTO_GRAMMAR_BUILD = "1";
# So Helix knows what rev it is.
HELIX_NIX_BUILD_REV = gitRev;
doCheck = false;
strictDeps = true;
# Sets the Helix runtime dir to the grammars
env.HELIX_DEFAULT_RUNTIME = "${runtimeDir}";
# Get all the application stuff in the output directory.
postInstall = ''
mkdir -p $out/lib
installShellCompletion ${./contrib/completion}/hx.{bash,fish,zsh}
mkdir -p $out/share/{applications,icons/hicolor/{256x256,scalable}/apps}
cp ${./contrib/Helix.desktop} $out/share/applications
cp ${./logo.svg} $out/share/icons/hicolor/scalable/apps/helix.svg
cp ${./contrib/helix.png} $out/share/icons/hicolor/256x256/apps
'';
meta.mainProgram = "hx";
})

View file

@ -15,6 +15,8 @@ Some suggestions to get started:
- If your preferred language is missing, integrating a tree-sitter grammar for
it and defining syntax highlight queries for it is straight forward and
doesn't require much knowledge of the internals.
- If you don't use the Nix development shell and are getting your rust-analyzer binary from rustup, you may need to run `rustup component add rust-analyzer`.
This is because `rust-toolchain.toml` selects our MSRV for the development toolchain but doesn't download the matching rust-analyzer automatically.
We provide an [architecture.md][architecture.md] that should give you
a good overview of the internals.
@ -53,12 +55,22 @@ Existing tests can be used as examples. Helpers can be found in
[helpers.rs][helpers.rs]. The log level can be set with the `HELIX_LOG_LEVEL`
environment variable, e.g. `HELIX_LOG_LEVEL=debug cargo integration-test`.
Contributors using MacOS might encounter `Too many open files (os error 24)`
failures while running integration tests. This can be resolved by increasing
the default value (e.g. to `10240` from `256`) by running `ulimit -n 10240`.
## Minimum Stable Rust Version (MSRV) Policy
Helix follows the MSRV of Firefox.
The current MSRV and future changes to the MSRV are listed in the [Firefox documentation].
Helix keeps an intentionally low MSRV for the sake of easy building and packaging
downstream. We follow [Firefox's MSRV policy]. Once Firefox's MSRV increases we
may bump ours as well, but be sure to check that popular distributions like Ubuntu
package the new MSRV version. When increasing the MSRV, update these three places:
[Firefox documentation]: https://firefox-source-docs.mozilla.org/writing-rust-code/update-policy.html
* the `workspace.package.rust-version` key in `Cargo.toml` in the repository root
* the `env.MSRV` key at the top of `.github/workflows/build.yml`
* the `toolchain.channel` key in `rust-toolchain.toml`
[Firefox's MSRV policy]: https://firefox-source-docs.mozilla.org/writing-rust-code/update-policy.html
[good-first-issue]: https://github.com/helix-editor/helix/labels/E-easy
[log-file]: https://github.com/helix-editor/helix/wiki/FAQ#access-the-log-file
[architecture.md]: ./architecture.md

View file

@ -1,13 +1,14 @@
| Crate | Description |
| ----------- | ----------- |
| helix-core | Core editing primitives, functional. |
| helix-lsp | Language server client |
| helix-dap | Debug Adapter Protocol (DAP) client |
| helix-loader | Functions for building, fetching, and loading external resources |
| helix-view | UI abstractions for use in backends, imperative shell. |
| helix-term | Terminal UI |
| helix-tui | TUI primitives, forked from tui-rs, inspired by Cursive |
| Crate | Description |
| ----------- | ----------- |
| helix-core | Core editing primitives, functional. |
| helix-lsp | Language server client |
| helix-lsp-types | Language Server Protocol type definitions |
| helix-dap | Debug Adapter Protocol (DAP) client |
| helix-loader | Functions for building, fetching, and loading external resources |
| helix-view | UI abstractions for use in backends, imperative shell. |
| helix-term | Terminal UI |
| helix-tui | TUI primitives, forked from tui-rs, inspired by Cursive |
This document contains a high-level overview of Helix internals.

View file

@ -5,19 +5,18 @@ Helix releases are versioned in the Calendar Versioning scheme:
`22.05.1`. In these instructions we'll use `<tag>` as a placeholder for the tag
being published.
* Merge the changelog PR
* Add new `<release>` entry in `contrib/Helix.appdata.xml` with release information according to the [AppStream spec](https://www.freedesktop.org/software/appstream/docs/sect-Metadata-Releases.html)
* Merge the PR with the release updates. That branch should:
* Update the version:
* Update the `workspace.package.version` key in `Cargo.toml`. Cargo only accepts
SemVer versions so a CalVer version of `22.07` for example must be formatted
as `22.7.0`. Patch/bugfix releases should increment the SemVer patch number. A
patch release for 22.07 would be `22.7.1`.
* Run `cargo check` and commit the resulting change to `Cargo.lock`
* Add changelog notes to `CHANGELOG.md`
* Add new `<release>` entry in `contrib/Helix.appdata.xml` with release information according to the [AppStream spec](https://www.freedesktop.org/software/appstream/docs/sect-Metadata-Releases.html)
* Tag and push
* `git tag -s -m "<tag>" -a <tag> && git push`
* Make sure to switch to master and pull first
* Edit the `Cargo.toml` file and change the date in the `version` field to the next planned release
* Due to Cargo having a strict requirement on SemVer with 3 or more version
numbers, a `0` is required in the micro version; however, unless we are
publishing a patch release after a major release, the `.0` is dropped in
the user facing version.
* Releases are planned to happen every two months, so `22.05.0` would change to `22.07.0`
* If we are pushing a patch/bugfix release in the same month as the previous
release, bump the micro version, e.g. `22.07.0` to `22.07.1`
* Switch to master and pull
* `git tag -s -m "<tag>" -a <tag> && git push origin <tag>` (note the `-s` which signs the tag)
* Wait for the Release CI to finish
* It will automatically turn the git tag into a GitHub release when it uploads artifacts
* Edit the new release

42
flake.lock generated
View file

@ -1,35 +1,15 @@
{
"nodes": {
"crane": {
"inputs": {
"nixpkgs": [
"nixpkgs"
]
},
"locked": {
"lastModified": 1709610799,
"narHash": "sha256-5jfLQx0U9hXbi2skYMGodDJkIgffrjIOgMRjZqms2QE=",
"owner": "ipetkov",
"repo": "crane",
"rev": "81c393c776d5379c030607866afef6406ca1be57",
"type": "github"
},
"original": {
"owner": "ipetkov",
"repo": "crane",
"type": "github"
}
},
"flake-utils": {
"inputs": {
"systems": "systems"
},
"locked": {
"lastModified": 1709126324,
"narHash": "sha256-q6EQdSeUZOG26WelxqkmR7kArjgWCdw5sfJVHPH/7j8=",
"lastModified": 1731533236,
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "d465f4819400de7c8d874d50b982301f28a84605",
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
"type": "github"
},
"original": {
@ -40,11 +20,11 @@
},
"nixpkgs": {
"locked": {
"lastModified": 1709479366,
"narHash": "sha256-n6F0n8UV6lnTZbYPl1A9q1BS0p4hduAv1mGAP17CVd0=",
"lastModified": 1740560979,
"narHash": "sha256-Vr3Qi346M+8CjedtbyUevIGDZW8LcA1fTG0ugPY/Hic=",
"owner": "nixos",
"repo": "nixpkgs",
"rev": "b8697e57f10292a6165a20f03d2f42920dfaf973",
"rev": "5135c59491985879812717f4c9fea69604e7f26f",
"type": "github"
},
"original": {
@ -56,7 +36,6 @@
},
"root": {
"inputs": {
"crane": "crane",
"flake-utils": "flake-utils",
"nixpkgs": "nixpkgs",
"rust-overlay": "rust-overlay"
@ -64,19 +43,16 @@
},
"rust-overlay": {
"inputs": {
"flake-utils": [
"flake-utils"
],
"nixpkgs": [
"nixpkgs"
]
},
"locked": {
"lastModified": 1709604635,
"narHash": "sha256-le4fwmWmjGRYWwkho0Gr7mnnZndOOe4XGbLw68OvF40=",
"lastModified": 1740623427,
"narHash": "sha256-3SdPQrZoa4odlScFDUHd4CUPQ/R1gtH4Mq9u8CBiK8M=",
"owner": "oxalica",
"repo": "rust-overlay",
"rev": "e86c0fb5d3a22a5f30d7f64ecad88643fe26449d",
"rev": "d342e8b5fd88421ff982f383c853f0fc78a847ab",
"type": "github"
},
"original": {

208
flake.nix
View file

@ -6,13 +6,6 @@
flake-utils.url = "github:numtide/flake-utils";
rust-overlay = {
url = "github:oxalica/rust-overlay";
inputs = {
nixpkgs.follows = "nixpkgs";
flake-utils.follows = "flake-utils";
};
};
crane = {
url = "github:ipetkov/crane";
inputs.nixpkgs.follows = "nixpkgs";
};
};
@ -20,173 +13,74 @@
outputs = {
self,
nixpkgs,
crane,
flake-utils,
rust-overlay,
...
}:
}: let
gitRev = self.rev or self.dirtyRev or null;
in
flake-utils.lib.eachDefaultSystem (system: let
pkgs = import nixpkgs {
inherit system;
overlays = [(import rust-overlay)];
};
mkRootPath = rel:
builtins.path {
path = "${toString ./.}/${rel}";
name = rel;
};
filteredSource = let
pathsToIgnore = [
".envrc"
".ignore"
".github"
".gitignore"
"logo_dark.svg"
"logo_light.svg"
"rust-toolchain.toml"
"rustfmt.toml"
"runtime"
"screenshot.png"
"book"
"docs"
"README.md"
"CHANGELOG.md"
"shell.nix"
"default.nix"
"grammars.nix"
"flake.nix"
"flake.lock"
];
ignorePaths = path: type: let
inherit (nixpkgs) lib;
# split the nix store path into its components
components = lib.splitString "/" path;
# drop off the `/nix/hash-source` section from the path
relPathComponents = lib.drop 4 components;
# reassemble the path components
relPath = lib.concatStringsSep "/" relPathComponents;
in
lib.all (p: ! (lib.hasPrefix p relPath)) pathsToIgnore;
in
builtins.path {
name = "helix-source";
path = toString ./.;
# filter out unnecessary paths
filter = ignorePaths;
};
makeOverridableHelix = old: config: let
grammars = pkgs.callPackage ./grammars.nix config;
runtimeDir = pkgs.runCommand "helix-runtime" {} ''
mkdir -p $out
ln -s ${mkRootPath "runtime"}/* $out
rm -r $out/grammars
ln -s ${grammars} $out/grammars
'';
helix-wrapped =
pkgs.runCommand
old.name
{
inherit (old) pname version;
meta = old.meta or {};
passthru =
(old.passthru or {})
// {
unwrapped = old;
};
nativeBuildInputs = [pkgs.makeWrapper];
makeWrapperArgs = config.makeWrapperArgs or [];
}
''
cp -rs --no-preserve=mode,ownership ${old} $out
wrapProgram "$out/bin/hx" ''${makeWrapperArgs[@]} --set HELIX_RUNTIME "${runtimeDir}"
'';
in
helix-wrapped
// {
override = makeOverridableHelix old;
passthru =
helix-wrapped.passthru
// {
wrapper = old: makeOverridableHelix old config;
};
};
stdenv =
if pkgs.stdenv.isLinux
then pkgs.stdenv
else pkgs.clangStdenv;
rustFlagsEnv = pkgs.lib.optionalString stdenv.isLinux "-C link-arg=-fuse-ld=lld -C target-cpu=native -Clink-arg=-Wl,--no-rosegment";
rustToolchain = pkgs.pkgsBuildHost.rust-bin.fromRustupToolchainFile ./rust-toolchain.toml;
craneLibMSRV = (crane.mkLib pkgs).overrideToolchain rustToolchain;
craneLibStable = (crane.mkLib pkgs).overrideToolchain pkgs.pkgsBuildHost.rust-bin.stable.latest.default;
commonArgs = {
inherit stdenv;
inherit (craneLibMSRV.crateNameFromCargoToml {cargoToml = ./helix-term/Cargo.toml;}) pname;
inherit (craneLibMSRV.crateNameFromCargoToml {cargoToml = ./Cargo.toml;}) version;
src = filteredSource;
# disable fetching and building of tree-sitter grammars in the helix-term build.rs
HELIX_DISABLE_AUTO_GRAMMAR_BUILD = "1";
buildInputs = [stdenv.cc.cc.lib];
# disable tests
doCheck = false;
meta.mainProgram = "hx";
# Get Helix's MSRV toolchain to build with by default.
msrvToolchain = pkgs.pkgsBuildHost.rust-bin.fromRustupToolchainFile ./rust-toolchain.toml;
msrvPlatform = pkgs.makeRustPlatform {
cargo = msrvToolchain;
rustc = msrvToolchain;
};
cargoArtifacts = craneLibMSRV.buildDepsOnly commonArgs;
in {
packages = {
helix-unwrapped = craneLibStable.buildPackage (commonArgs
// {
cargoArtifacts = craneLibStable.buildDepsOnly commonArgs;
postInstall = ''
mkdir -p $out/share/applications $out/share/icons/hicolor/scalable/apps $out/share/icons/hicolor/256x256/apps
cp contrib/Helix.desktop $out/share/applications
cp logo.svg $out/share/icons/hicolor/scalable/apps/helix.svg
cp contrib/helix.png $out/share/icons/hicolor/256x256/apps
'';
});
helix = makeOverridableHelix self.packages.${system}.helix-unwrapped {};
default = self.packages.${system}.helix;
packages = rec {
helix = pkgs.callPackage ./default.nix {inherit gitRev;};
/**
The default Helix build. Uses the latest stable Rust toolchain, and unstable
nixpkgs.
The build inputs can be overriden with the following:
packages.${system}.default.override { rustPlatform = newPlatform; };
Overriding a derivation attribute can be done as well:
packages.${system}.default.overrideAttrs { buildType = "debug"; };
*/
default = helix;
};
checks = {
# Build the crate itself
inherit (self.packages.${system}) helix;
clippy = craneLibMSRV.cargoClippy (commonArgs
// {
inherit cargoArtifacts;
cargoClippyExtraArgs = "--all-targets -- --deny warnings";
});
fmt = craneLibMSRV.cargoFmt commonArgs;
doc = craneLibMSRV.cargoDoc (commonArgs
// {
inherit cargoArtifacts;
});
test = craneLibMSRV.cargoTest (commonArgs
// {
inherit cargoArtifacts;
});
checks.helix = self.outputs.packages.${system}.helix.override {
buildType = "debug";
rustPlatform = msrvPlatform;
};
devShells.default = pkgs.mkShell {
inputsFrom = builtins.attrValues self.checks.${system};
nativeBuildInputs = with pkgs;
[lld_13 cargo-flamegraph rust-analyzer]
++ (lib.optional (stdenv.isx86_64 && stdenv.isLinux) pkgs.cargo-tarpaulin)
++ (lib.optional stdenv.isLinux pkgs.lldb)
++ (lib.optional stdenv.isDarwin pkgs.darwin.apple_sdk.frameworks.CoreFoundation);
shellHook = ''
export HELIX_RUNTIME="$PWD/runtime"
export RUST_BACKTRACE="1"
export RUSTFLAGS="''${RUSTFLAGS:-""} ${rustFlagsEnv}"
'';
};
# Devshell behavior is preserved.
devShells.default = let
commonRustFlagsEnv = "-C link-arg=-fuse-ld=lld -C target-cpu=native --cfg tokio_unstable";
platformRustFlagsEnv = pkgs.lib.optionalString pkgs.stdenv.isLinux "-Clink-arg=-Wl,--no-rosegment";
in
pkgs.mkShell
{
inputsFrom = [self.checks.${system}.helix];
nativeBuildInputs = with pkgs;
[
lld
cargo-flamegraph
rust-bin.nightly.latest.rust-analyzer
]
++ (lib.optional (stdenv.isx86_64 && stdenv.isLinux) cargo-tarpaulin)
++ (lib.optional stdenv.isLinux lldb)
++ (lib.optional stdenv.isDarwin darwin.apple_sdk.frameworks.CoreFoundation);
shellHook = ''
export RUST_BACKTRACE="1"
export RUSTFLAGS="''${RUSTFLAGS:-""} ${commonRustFlagsEnv} ${platformRustFlagsEnv}"
'';
};
})
// {
overlays.default = final: prev: {
inherit (self.packages.${final.system}) helix;
helix = final.callPackage ./default.nix {inherit gitRev;};
};
};

View file

@ -32,10 +32,10 @@
# If `use-grammars.except` is set, use all other grammars.
# Otherwise use all grammars.
useGrammar = grammar:
if languagesConfig?use-grammars.only then
builtins.elem grammar.name languagesConfig.use-grammars.only
else if languagesConfig?use-grammars.except then
!(builtins.elem grammar.name languagesConfig.use-grammars.except)
if languagesConfig ? use-grammars.only
then builtins.elem grammar.name languagesConfig.use-grammars.only
else if languagesConfig ? use-grammars.except
then !(builtins.elem grammar.name languagesConfig.use-grammars.except)
else true;
grammarsToUse = builtins.filter useGrammar languagesConfig.grammar;
gitGrammars = builtins.filter isGitGrammar grammarsToUse;
@ -66,10 +66,10 @@
version = grammar.source.rev;
src = source;
sourceRoot = if builtins.hasAttr "subpath" grammar.source then
"source/${grammar.source.subpath}"
else
"source";
sourceRoot =
if builtins.hasAttr "subpath" grammar.source
then "source/${grammar.source.subpath}"
else "source";
dontConfigure = true;
@ -116,17 +116,21 @@
'';
};
grammarsToBuild = builtins.filter includeGrammarIf gitGrammars;
builtGrammars = builtins.map (grammar: {
inherit (grammar) name;
value = buildGrammar grammar;
}) grammarsToBuild;
builtGrammars =
builtins.map (grammar: {
inherit (grammar) name;
value = buildGrammar grammar;
})
grammarsToBuild;
extensibleGrammars =
lib.makeExtensible (self: builtins.listToAttrs builtGrammars);
overlayedGrammars = lib.pipe extensibleGrammars
overlaidGrammars =
lib.pipe extensibleGrammars
(builtins.map (overlay: grammar: grammar.extend overlay) grammarOverlays);
grammarLinks = lib.mapAttrsToList
grammarLinks =
lib.mapAttrsToList
(name: artifact: "ln -s ${artifact}/${name}.so $out/${name}.so")
(lib.filterAttrs (n: v: lib.isDerivation v) overlayedGrammars);
(lib.filterAttrs (n: v: lib.isDerivation v) overlaidGrammars);
in
runCommand "consolidated-helix-grammars" {} ''
mkdir -p $out

View file

@ -18,41 +18,48 @@ integration = []
[dependencies]
helix-stdx = { path = "../helix-stdx" }
helix-loader = { path = "../helix-loader" }
helix-parsec = { path = "../helix-parsec" }
ropey = { version = "1.6.1", default-features = false, features = ["simd"] }
smallvec = "1.13"
ropey.workspace = true
smallvec = "1.14"
smartstring = "1.0.1"
unicode-segmentation = "1.11"
unicode-width = "0.1"
unicode-general-category = "0.6"
unicode-segmentation.workspace = true
# unicode-width is changing width definitions
# that both break our logic and disagree with common
# width definitions in terminals, we need to replace it.
# For now lets lock the version to avoid rendering glitches
# when installing without `--locked`
unicode-width = "=0.1.12"
unicode-general-category = "1.0"
slotmap.workspace = true
tree-sitter.workspace = true
once_cell = "1.19"
once_cell = "1.21"
arc-swap = "1"
regex = "1"
bitflags = "2.6"
bitflags.workspace = true
ahash = "0.8.11"
hashbrown = { version = "0.14.5", features = ["raw"] }
dunce = "1.0"
url = "2.5.4"
log = "0.4"
anyhow = "1.0"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
toml = "0.8"
imara-diff = "0.1.6"
imara-diff = "0.1.8"
encoding_rs = "0.8"
chrono = { version = "0.4", default-features = false, features = ["alloc", "std"] }
etcetera = "0.8"
textwrap = "0.16.1"
textwrap = "0.16.2"
nucleo.workspace = true
parking_lot = "0.12"
globset = "0.4.14"
parking_lot.workspace = true
globset = "0.4.16"
regex-cursor = "0.1.5"
[dev-dependencies]
quickcheck = { version = "1", default-features = false }
indoc = "2.0.5"
indoc = "2.0.6"

View file

@ -75,9 +75,9 @@ impl From<(&char, &char)> for Pair {
impl AutoPairs {
/// Make a new AutoPairs set with the given pairs and default conditions.
pub fn new<'a, V: 'a, A>(pairs: V) -> Self
pub fn new<'a, V, A>(pairs: V) -> Self
where
V: IntoIterator<Item = A>,
V: IntoIterator<Item = A> + 'a,
A: Into<Pair>,
{
let mut auto_pairs = HashMap::new();

View file

@ -0,0 +1,69 @@
use crate::Tendril;
// todo: should this be grapheme aware?
pub fn to_pascal_case(text: impl Iterator<Item = char>) -> Tendril {
let mut res = Tendril::new();
to_pascal_case_with(text, &mut res);
res
}
pub fn to_pascal_case_with(text: impl Iterator<Item = char>, buf: &mut Tendril) {
let mut at_word_start = true;
for c in text {
// we don't count _ as a word char here so case conversions work well
if !c.is_alphanumeric() {
at_word_start = true;
continue;
}
if at_word_start {
at_word_start = false;
buf.extend(c.to_uppercase());
} else {
buf.push(c)
}
}
}
pub fn to_upper_case_with(text: impl Iterator<Item = char>, buf: &mut Tendril) {
for c in text {
for c in c.to_uppercase() {
buf.push(c)
}
}
}
pub fn to_lower_case_with(text: impl Iterator<Item = char>, buf: &mut Tendril) {
for c in text {
for c in c.to_lowercase() {
buf.push(c)
}
}
}
pub fn to_camel_case(text: impl Iterator<Item = char>) -> Tendril {
let mut res = Tendril::new();
to_camel_case_with(text, &mut res);
res
}
pub fn to_camel_case_with(mut text: impl Iterator<Item = char>, buf: &mut Tendril) {
for c in &mut text {
if c.is_alphanumeric() {
buf.extend(c.to_lowercase())
}
}
let mut at_word_start = false;
for c in text {
// we don't count _ as a word char here so case conversions work well
if !c.is_alphanumeric() {
at_word_start = true;
continue;
}
if at_word_start {
at_word_start = false;
buf.extend(c.to_uppercase());
} else {
buf.push(c)
}
}
}

File diff suppressed because it is too large Load diff

View file

@ -9,6 +9,24 @@ use crate::{
use helix_stdx::rope::RopeSliceExt;
use std::borrow::Cow;
pub const DEFAULT_COMMENT_TOKEN: &str = "#";
/// Returns the longest matching comment token of the given line (if it exists).
pub fn get_comment_token<'a, S: AsRef<str>>(
text: RopeSlice,
tokens: &'a [S],
line_num: usize,
) -> Option<&'a str> {
let line = text.line(line_num);
let start = line.first_non_whitespace_char()?;
tokens
.iter()
.map(AsRef::as_ref)
.filter(|token| line.slice(start..).starts_with(token))
.max_by_key(|token| token.len())
}
/// Given text, a comment token, and a set of line indices, returns the following:
/// - Whether the given lines should be considered commented
/// - If any of the lines are uncommented, all lines are considered as such.
@ -28,21 +46,20 @@ fn find_line_comment(
let mut min = usize::MAX; // minimum col for first_non_whitespace_char
let mut margin = 1;
let token_len = token.chars().count();
for line in lines {
let line_slice = text.line(line);
if let Some(pos) = line_slice.first_non_whitespace_char() {
let len = line_slice.len_chars();
if pos < min {
min = pos;
}
min = std::cmp::min(min, pos);
// line can be shorter than pos + token len
let fragment = Cow::from(line_slice.slice(pos..std::cmp::min(pos + token.len(), len)));
// as soon as one of the non-blank lines doesn't have a comment, the whole block is
// considered uncommented.
if fragment != token {
// as soon as one of the non-blank lines doesn't have a comment, the whole block is
// considered uncommented.
commented = false;
}
@ -56,6 +73,7 @@ fn find_line_comment(
to_change.push(line);
}
}
(commented, to_change, min, margin)
}
@ -63,7 +81,7 @@ fn find_line_comment(
pub fn toggle_line_comments(doc: &Rope, selection: &Selection, token: Option<&str>) -> Transaction {
let text = doc.slice(..);
let token = token.unwrap_or("//");
let token = token.unwrap_or(DEFAULT_COMMENT_TOKEN);
let comment = Tendril::from(format!("{} ", token));
let mut lines: Vec<usize> = Vec::with_capacity(selection.len());
@ -129,10 +147,7 @@ pub fn find_block_comments(
let mut only_whitespace = true;
let mut comment_changes = Vec::with_capacity(selection.len());
let default_tokens = tokens.first().cloned().unwrap_or_default();
// TODO: check if this can be removed on MSRV bump
#[allow(clippy::redundant_clone)]
let mut start_token = default_tokens.start.clone();
#[allow(clippy::redundant_clone)]
let mut end_token = default_tokens.end.clone();
let mut tokens = tokens.to_vec();
@ -189,13 +204,9 @@ pub fn find_block_comments(
range: *range,
start_pos,
end_pos,
start_margin: selection_slice
.get_char(after_start)
.map_or(false, |c| c == ' '),
start_margin: selection_slice.get_char(after_start) == Some(' '),
end_margin: after_start != before_end
&& selection_slice
.get_char(before_end)
.map_or(false, |c| c == ' '),
&& (selection_slice.get_char(before_end) == Some(' ')),
start_token: start_token.to_string(),
end_token: end_token.to_string(),
});
@ -317,56 +328,87 @@ pub fn split_lines_of_selection(text: RopeSlice, selection: &Selection) -> Selec
mod test {
use super::*;
#[test]
fn test_find_line_comment() {
// four lines, two space indented, except for line 1 which is blank.
let mut doc = Rope::from(" 1\n\n 2\n 3");
// select whole document
let mut selection = Selection::single(0, doc.len_chars() - 1);
mod find_line_comment {
use super::*;
let text = doc.slice(..);
#[test]
fn not_commented() {
// four lines, two space indented, except for line 1 which is blank.
let doc = Rope::from(" 1\n\n 2\n 3");
let res = find_line_comment("//", text, 0..3);
// (commented = true, to_change = [line 0, line 2], min = col 2, margin = 0)
assert_eq!(res, (false, vec![0, 2], 2, 0));
let text = doc.slice(..);
// comment
let transaction = toggle_line_comments(&doc, &selection, None);
transaction.apply(&mut doc);
selection = selection.map(transaction.changes());
let res = find_line_comment("//", text, 0..3);
// (commented = false, to_change = [line 0, line 2], min = col 2, margin = 0)
assert_eq!(res, (false, vec![0, 2], 2, 0));
}
assert_eq!(doc, " // 1\n\n // 2\n // 3");
#[test]
fn is_commented() {
// three lines where the second line is empty.
let doc = Rope::from("// hello\n\n// there");
// uncomment
let transaction = toggle_line_comments(&doc, &selection, None);
transaction.apply(&mut doc);
selection = selection.map(transaction.changes());
assert_eq!(doc, " 1\n\n 2\n 3");
assert!(selection.len() == 1); // to ignore the selection unused warning
let res = find_line_comment("//", doc.slice(..), 0..3);
// 0 margin comments
doc = Rope::from(" //1\n\n //2\n //3");
// reset the selection.
selection = Selection::single(0, doc.len_chars() - 1);
// (commented = true, to_change = [line 0, line 2], min = col 0, margin = 1)
assert_eq!(res, (true, vec![0, 2], 0, 1));
}
}
let transaction = toggle_line_comments(&doc, &selection, None);
transaction.apply(&mut doc);
selection = selection.map(transaction.changes());
assert_eq!(doc, " 1\n\n 2\n 3");
assert!(selection.len() == 1); // to ignore the selection unused warning
// TODO: account for uncommenting with uneven comment indentation
mod toggle_line_comment {
use super::*;
// 0 margin comments, with no space
doc = Rope::from("//");
// reset the selection.
selection = Selection::single(0, doc.len_chars() - 1);
#[test]
fn comment() {
// four lines, two space indented, except for line 1 which is blank.
let mut doc = Rope::from(" 1\n\n 2\n 3");
// select whole document
let selection = Selection::single(0, doc.len_chars() - 1);
let transaction = toggle_line_comments(&doc, &selection, None);
transaction.apply(&mut doc);
selection = selection.map(transaction.changes());
assert_eq!(doc, "");
assert!(selection.len() == 1); // to ignore the selection unused warning
let transaction = toggle_line_comments(&doc, &selection, None);
transaction.apply(&mut doc);
// TODO: account for uncommenting with uneven comment indentation
assert_eq!(doc, " # 1\n\n # 2\n # 3");
}
#[test]
fn uncomment() {
let mut doc = Rope::from(" # 1\n\n # 2\n # 3");
let mut selection = Selection::single(0, doc.len_chars() - 1);
let transaction = toggle_line_comments(&doc, &selection, None);
transaction.apply(&mut doc);
selection = selection.map(transaction.changes());
assert_eq!(doc, " 1\n\n 2\n 3");
assert!(selection.len() == 1); // to ignore the selection unused warning
}
#[test]
fn uncomment_0_margin_comments() {
let mut doc = Rope::from(" #1\n\n #2\n #3");
let mut selection = Selection::single(0, doc.len_chars() - 1);
let transaction = toggle_line_comments(&doc, &selection, None);
transaction.apply(&mut doc);
selection = selection.map(transaction.changes());
assert_eq!(doc, " 1\n\n 2\n 3");
assert!(selection.len() == 1); // to ignore the selection unused warning
}
#[test]
fn uncomment_0_margin_comments_with_no_space() {
let mut doc = Rope::from("#");
let mut selection = Selection::single(0, doc.len_chars() - 1);
let transaction = toggle_line_comments(&doc, &selection, None);
transaction.apply(&mut doc);
selection = selection.map(transaction.changes());
assert_eq!(doc, "");
assert!(selection.len() == 1); // to ignore the selection unused warning
}
}
#[test]
@ -413,4 +455,32 @@ mod test {
transaction.apply(&mut doc);
assert_eq!(doc, "");
}
/// Test, if `get_comment_tokens` works, even if the content of the file includes chars, whose
/// byte size unequal the amount of chars
#[test]
fn test_get_comment_with_char_boundaries() {
let rope = Rope::from("··");
let tokens = ["//", "///"];
assert_eq!(
super::get_comment_token(rope.slice(..), tokens.as_slice(), 0),
None
);
}
/// Test for `get_comment_token`.
///
/// Assuming the comment tokens are stored as `["///", "//"]`, `get_comment_token` should still
/// return `///` instead of `//` if the user is in a doc-comment section.
#[test]
fn test_use_longest_comment() {
let text = Rope::from(" /// amogus");
let tokens = ["///", "//"];
assert_eq!(
super::get_comment_token(text.slice(..), tokens.as_slice(), 0),
Some("///")
);
}
}

View file

@ -0,0 +1,25 @@
use std::borrow::Cow;
use crate::{diagnostic::LanguageServerId, Transaction};
#[derive(Debug, PartialEq, Clone)]
pub struct CompletionItem {
pub transaction: Transaction,
pub label: Cow<'static, str>,
pub kind: Cow<'static, str>,
/// Containing Markdown
pub documentation: Option<String>,
pub provider: CompletionProvider,
}
#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)]
pub enum CompletionProvider {
Lsp(LanguageServerId),
Path,
}
impl From<LanguageServerId> for CompletionProvider {
fn from(id: LanguageServerId) -> Self {
CompletionProvider::Lsp(id)
}
}

View file

@ -1,10 +1,12 @@
//! LSP diagnostic utility types.
use std::fmt;
use std::{fmt, sync::Arc};
pub use helix_stdx::range::Range;
use serde::{Deserialize, Serialize};
/// Describes the severity level of a [`Diagnostic`].
#[derive(Debug, Clone, Copy, Eq, PartialEq, PartialOrd, Ord, Deserialize, Serialize)]
#[derive(Debug, Clone, Copy, Eq, PartialEq, PartialOrd, Ord, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum Severity {
Hint,
Info,
@ -18,13 +20,6 @@ impl Default for Severity {
}
}
/// A range of `char`s within the text.
#[derive(Debug, Clone, Copy, PartialOrd, Ord, PartialEq, Eq)]
pub struct Range {
pub start: usize,
pub end: usize,
}
#[derive(Debug, Eq, Hash, PartialEq, Clone, Deserialize, Serialize)]
pub enum NumberOrString {
Number(i32),
@ -55,8 +50,35 @@ pub struct Diagnostic {
pub data: Option<serde_json::Value>,
}
// TODO turn this into an enum + feature flag when lsp becomes optional
pub type DiagnosticProvider = LanguageServerId;
/// The source of a diagnostic.
///
/// This type is cheap to clone: all data is either `Copy` or wrapped in an `Arc`.
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub enum DiagnosticProvider {
Lsp {
/// The ID of the language server which sent the diagnostic.
server_id: LanguageServerId,
/// An optional identifier under which diagnostics are managed by the client.
///
/// `identifier` is a field from the LSP "Pull Diagnostics" feature meant to provide an
/// optional "namespace" for diagnostics: a language server can respond to a diagnostics
/// pull request with an identifier and these diagnostics should be treated as separate
/// from push diagnostics. Rust-analyzer uses this feature for example to provide Cargo
/// diagnostics with push and internal diagnostics with pull. The push diagnostics should
/// not clear the pull diagnostics and vice-versa.
identifier: Option<Arc<str>>,
},
// Future internal features can go here...
}
impl DiagnosticProvider {
pub fn language_server_id(&self) -> Option<LanguageServerId> {
match self {
Self::Lsp { server_id, .. } => Some(*server_id),
// _ => None,
}
}
}
// while I would prefer having this in helix-lsp that necessitates a bunch of
// conversions I would rather not add. I think its fine since this just a very
@ -71,3 +93,10 @@ impl fmt::Display for LanguageServerId {
write!(f, "{:?}", self.0)
}
}
impl Diagnostic {
#[inline]
pub fn severity(&self) -> Severity {
self.severity.unwrap_or(Severity::Warning)
}
}

View file

@ -10,18 +10,21 @@
//! called a "block" and the caller must advance it as needed.
use std::borrow::Cow;
use std::cmp::Ordering;
use std::fmt::Debug;
use std::mem::{replace, take};
use std::mem::replace;
#[cfg(test)]
mod test;
use unicode_segmentation::{Graphemes, UnicodeSegmentation};
use helix_stdx::rope::{RopeGraphemes, RopeSliceExt};
use crate::graphemes::{Grapheme, GraphemeStr};
use crate::syntax::Highlight;
use crate::text_annotations::TextAnnotations;
use crate::{Position, RopeGraphemes, RopeSlice};
use crate::{Position, RopeSlice};
/// TODO make Highlight a u32 to reduce the size of this enum to a single word.
#[derive(Debug, Clone, Copy)]
@ -37,52 +40,104 @@ pub enum GraphemeSource {
},
}
#[derive(Debug, Clone)]
pub struct FormattedGrapheme<'a> {
pub grapheme: Grapheme<'a>,
pub source: GraphemeSource,
impl GraphemeSource {
/// Returns whether this grapheme is virtual inline text
pub fn is_virtual(self) -> bool {
matches!(self, GraphemeSource::VirtualText { .. })
}
pub fn is_eof(self) -> bool {
// all doc chars except the EOF char have non-zero codepoints
matches!(self, GraphemeSource::Document { codepoints: 0 })
}
pub fn doc_chars(self) -> usize {
match self {
GraphemeSource::Document { codepoints } => codepoints as usize,
GraphemeSource::VirtualText { .. } => 0,
}
}
}
impl<'a> FormattedGrapheme<'a> {
pub fn new(
#[derive(Debug, Clone)]
pub struct FormattedGrapheme<'a> {
pub raw: Grapheme<'a>,
pub source: GraphemeSource,
pub visual_pos: Position,
/// Document line at the start of the grapheme
pub line_idx: usize,
/// Document char position at the start of the grapheme
pub char_idx: usize,
}
impl FormattedGrapheme<'_> {
pub fn is_virtual(&self) -> bool {
self.source.is_virtual()
}
pub fn doc_chars(&self) -> usize {
self.source.doc_chars()
}
pub fn is_whitespace(&self) -> bool {
self.raw.is_whitespace()
}
pub fn width(&self) -> usize {
self.raw.width()
}
pub fn is_word_boundary(&self) -> bool {
self.raw.is_word_boundary()
}
}
#[derive(Debug, Clone)]
struct GraphemeWithSource<'a> {
grapheme: Grapheme<'a>,
source: GraphemeSource,
}
impl<'a> GraphemeWithSource<'a> {
fn new(
g: GraphemeStr<'a>,
visual_x: usize,
tab_width: u16,
source: GraphemeSource,
) -> FormattedGrapheme<'a> {
FormattedGrapheme {
) -> GraphemeWithSource<'a> {
GraphemeWithSource {
grapheme: Grapheme::new(g, visual_x, tab_width),
source,
}
}
/// Returns whether this grapheme is virtual inline text
pub fn is_virtual(&self) -> bool {
matches!(self.source, GraphemeSource::VirtualText { .. })
}
pub fn placeholder() -> Self {
FormattedGrapheme {
fn placeholder() -> Self {
GraphemeWithSource {
grapheme: Grapheme::Other { g: " ".into() },
source: GraphemeSource::Document { codepoints: 0 },
}
}
pub fn doc_chars(&self) -> usize {
match self.source {
GraphemeSource::Document { codepoints } => codepoints as usize,
GraphemeSource::VirtualText { .. } => 0,
}
fn doc_chars(&self) -> usize {
self.source.doc_chars()
}
pub fn is_whitespace(&self) -> bool {
fn is_whitespace(&self) -> bool {
self.grapheme.is_whitespace()
}
pub fn width(&self) -> usize {
fn is_newline(&self) -> bool {
matches!(self.grapheme, Grapheme::Newline)
}
fn is_eof(&self) -> bool {
self.source.is_eof()
}
fn width(&self) -> usize {
self.grapheme.width()
}
pub fn is_word_boundary(&self) -> bool {
fn is_word_boundary(&self) -> bool {
self.grapheme.is_word_boundary()
}
}
@ -96,6 +151,7 @@ pub struct TextFormat {
pub wrap_indicator: Box<str>,
pub wrap_indicator_highlight: Option<Highlight>,
pub viewport_width: u16,
pub soft_wrap_at_text_width: bool,
}
// test implementation is basically only used for testing or when softwrap is always disabled
@ -109,6 +165,7 @@ impl Default for TextFormat {
wrap_indicator: Box::from(" "),
viewport_width: 17,
wrap_indicator_highlight: None,
soft_wrap_at_text_width: false,
}
}
}
@ -127,10 +184,7 @@ pub struct DocumentFormatter<'t> {
line_pos: usize,
exhausted: bool,
/// Line breaks to be reserved for virtual text
/// at the next line break
virtual_lines: usize,
inline_anntoation_graphemes: Option<(Graphemes<'t>, Option<Highlight>)>,
inline_annotation_graphemes: Option<(Graphemes<'t>, Option<Highlight>)>,
// softwrap specific
/// The indentation of the current line
@ -139,9 +193,9 @@ pub struct DocumentFormatter<'t> {
indent_level: Option<usize>,
/// In case a long word needs to be split a single grapheme might need to be wrapped
/// while the rest of the word stays on the same line
peeked_grapheme: Option<(FormattedGrapheme<'t>, usize)>,
peeked_grapheme: Option<GraphemeWithSource<'t>>,
/// A first-in first-out (fifo) buffer for the Graphemes of any given word
word_buf: Vec<FormattedGrapheme<'t>>,
word_buf: Vec<GraphemeWithSource<'t>>,
/// The index of the next grapheme that will be yielded from the `word_buf`
word_i: usize,
}
@ -157,35 +211,35 @@ impl<'t> DocumentFormatter<'t> {
text_fmt: &'t TextFormat,
annotations: &'t TextAnnotations,
char_idx: usize,
) -> (Self, usize) {
) -> Self {
// TODO divide long lines into blocks to avoid bad performance for long lines
let block_line_idx = text.char_to_line(char_idx.min(text.len_chars()));
let block_char_idx = text.line_to_char(block_line_idx);
annotations.reset_pos(block_char_idx);
(
DocumentFormatter {
text_fmt,
annotations,
visual_pos: Position { row: 0, col: 0 },
graphemes: RopeGraphemes::new(text.slice(block_char_idx..)),
char_pos: block_char_idx,
exhausted: false,
virtual_lines: 0,
indent_level: None,
peeked_grapheme: None,
word_buf: Vec::with_capacity(64),
word_i: 0,
line_pos: block_line_idx,
inline_anntoation_graphemes: None,
},
block_char_idx,
)
DocumentFormatter {
text_fmt,
annotations,
visual_pos: Position { row: 0, col: 0 },
graphemes: text.slice(block_char_idx..).graphemes(),
char_pos: block_char_idx,
exhausted: false,
indent_level: None,
peeked_grapheme: None,
word_buf: Vec::with_capacity(64),
word_i: 0,
line_pos: block_line_idx,
inline_annotation_graphemes: None,
}
}
fn next_inline_annotation_grapheme(&mut self) -> Option<(&'t str, Option<Highlight>)> {
fn next_inline_annotation_grapheme(
&mut self,
char_pos: usize,
) -> Option<(&'t str, Option<Highlight>)> {
loop {
if let Some(&mut (ref mut annotation, highlight)) =
self.inline_anntoation_graphemes.as_mut()
self.inline_annotation_graphemes.as_mut()
{
if let Some(grapheme) = annotation.next() {
return Some((grapheme, highlight));
@ -193,9 +247,9 @@ impl<'t> DocumentFormatter<'t> {
}
if let Some((annotation, highlight)) =
self.annotations.next_inline_annotation_at(self.char_pos)
self.annotations.next_inline_annotation_at(char_pos)
{
self.inline_anntoation_graphemes = Some((
self.inline_annotation_graphemes = Some((
UnicodeSegmentation::graphemes(&*annotation.text, true),
highlight,
))
@ -205,21 +259,19 @@ impl<'t> DocumentFormatter<'t> {
}
}
fn advance_grapheme(&mut self, col: usize) -> Option<FormattedGrapheme<'t>> {
fn advance_grapheme(&mut self, col: usize, char_pos: usize) -> Option<GraphemeWithSource<'t>> {
let (grapheme, source) =
if let Some((grapheme, highlight)) = self.next_inline_annotation_grapheme() {
if let Some((grapheme, highlight)) = self.next_inline_annotation_grapheme(char_pos) {
(grapheme.into(), GraphemeSource::VirtualText { highlight })
} else if let Some(grapheme) = self.graphemes.next() {
self.virtual_lines += self.annotations.annotation_lines_at(self.char_pos);
let codepoints = grapheme.len_chars() as u32;
let overlay = self.annotations.overlay_at(self.char_pos);
let overlay = self.annotations.overlay_at(char_pos);
let grapheme = match overlay {
Some((overlay, _)) => overlay.grapheme.as_str().into(),
None => Cow::from(grapheme).into(),
};
self.char_pos += codepoints as usize;
(grapheme, GraphemeSource::Document { codepoints })
} else {
if self.exhausted {
@ -228,19 +280,19 @@ impl<'t> DocumentFormatter<'t> {
self.exhausted = true;
// EOF grapheme is required for rendering
// and correct position computations
return Some(FormattedGrapheme {
return Some(GraphemeWithSource {
grapheme: Grapheme::Other { g: " ".into() },
source: GraphemeSource::Document { codepoints: 0 },
});
};
let grapheme = FormattedGrapheme::new(grapheme, col, self.text_fmt.tab_width, source);
let grapheme = GraphemeWithSource::new(grapheme, col, self.text_fmt.tab_width, source);
Some(grapheme)
}
/// Move a word to the next visual line
fn wrap_word(&mut self, virtual_lines_before_word: usize) -> usize {
fn wrap_word(&mut self) -> usize {
// softwrap this word to the next line
let indent_carry_over = if let Some(indent) = self.indent_level {
if indent as u16 <= self.text_fmt.max_indent_retain {
@ -254,15 +306,17 @@ impl<'t> DocumentFormatter<'t> {
0
};
let virtual_lines =
self.annotations
.virtual_lines_at(self.char_pos, self.visual_pos, self.line_pos);
self.visual_pos.col = indent_carry_over as usize;
self.virtual_lines -= virtual_lines_before_word;
self.visual_pos.row += 1 + virtual_lines_before_word;
self.visual_pos.row += 1 + virtual_lines;
let mut i = 0;
let mut word_width = 0;
let wrap_indicator = UnicodeSegmentation::graphemes(&*self.text_fmt.wrap_indicator, true)
.map(|g| {
i += 1;
let grapheme = FormattedGrapheme::new(
let grapheme = GraphemeWithSource::new(
g.into(),
self.visual_pos.col + word_width,
self.text_fmt.tab_width,
@ -282,46 +336,72 @@ impl<'t> DocumentFormatter<'t> {
.change_position(visual_x, self.text_fmt.tab_width);
word_width += grapheme.width();
}
if let Some(grapheme) = &mut self.peeked_grapheme {
let visual_x = self.visual_pos.col + word_width;
grapheme
.grapheme
.change_position(visual_x, self.text_fmt.tab_width);
}
word_width
}
fn peek_grapheme(&mut self, col: usize, char_pos: usize) -> Option<&GraphemeWithSource<'t>> {
if self.peeked_grapheme.is_none() {
self.peeked_grapheme = self.advance_grapheme(col, char_pos);
}
self.peeked_grapheme.as_ref()
}
fn next_grapheme(&mut self, col: usize, char_pos: usize) -> Option<GraphemeWithSource<'t>> {
self.peek_grapheme(col, char_pos);
self.peeked_grapheme.take()
}
fn advance_to_next_word(&mut self) {
self.word_buf.clear();
let mut word_width = 0;
let virtual_lines_before_word = self.virtual_lines;
let mut virtual_lines_before_grapheme = self.virtual_lines;
let mut word_chars = 0;
if self.exhausted {
return;
}
loop {
// softwrap word if necessary
if word_width + self.visual_pos.col >= self.text_fmt.viewport_width as usize {
// wrapping this word would move too much text to the next line
// split the word at the line end instead
if word_width > self.text_fmt.max_wrap as usize {
// Usually we stop accomulating graphemes as soon as softwrapping becomes necessary.
// However if the last grapheme is multiple columns wide it might extend beyond the EOL.
// The condition below ensures that this grapheme is not cutoff and instead wrapped to the next line
if word_width + self.visual_pos.col > self.text_fmt.viewport_width as usize {
self.peeked_grapheme = self.word_buf.pop().map(|grapheme| {
(grapheme, self.virtual_lines - virtual_lines_before_grapheme)
});
self.virtual_lines = virtual_lines_before_grapheme;
}
let mut col = self.visual_pos.col + word_width;
let char_pos = self.char_pos + word_chars;
match col.cmp(&(self.text_fmt.viewport_width as usize)) {
// The EOF char and newline chars are always selectable in helix. That means
// that wrapping happens "too-early" if a word fits a line perfectly. This
// is intentional so that all selectable graphemes are always visible (and
// therefore the cursor never disappears). However if the user manually set a
// lower softwrap width then this is undesirable. Just increasing the viewport-
// width by one doesn't work because if a line is wrapped multiple times then
// some words may extend past the specified width.
//
// So we special case a word that ends exactly at line bounds and is followed
// by a newline/eof character here.
Ordering::Equal
if self.text_fmt.soft_wrap_at_text_width
&& self
.peek_grapheme(col, char_pos)
.is_some_and(|grapheme| grapheme.is_newline() || grapheme.is_eof()) => {
}
Ordering::Equal if word_width > self.text_fmt.max_wrap as usize => return,
Ordering::Greater if word_width > self.text_fmt.max_wrap as usize => {
self.peeked_grapheme = self.word_buf.pop();
return;
}
word_width = self.wrap_word(virtual_lines_before_word);
Ordering::Equal | Ordering::Greater => {
word_width = self.wrap_word();
col = self.visual_pos.col + word_width;
}
Ordering::Less => (),
}
virtual_lines_before_grapheme = self.virtual_lines;
let grapheme = if let Some((grapheme, virtual_lines)) = self.peeked_grapheme.take() {
self.virtual_lines += virtual_lines;
grapheme
} else if let Some(grapheme) = self.advance_grapheme(self.visual_pos.col + word_width) {
grapheme
} else {
let Some(grapheme) = self.next_grapheme(col, char_pos) else {
return;
};
word_chars += grapheme.doc_chars();
// Track indentation
if !grapheme.is_whitespace() && self.indent_level.is_none() {
@ -340,19 +420,18 @@ impl<'t> DocumentFormatter<'t> {
}
}
/// returns the document line pos of the **next** grapheme that will be yielded
pub fn line_pos(&self) -> usize {
self.line_pos
/// returns the char index at the end of the last yielded grapheme
pub fn next_char_pos(&self) -> usize {
self.char_pos
}
/// returns the visual pos of the **next** grapheme that will be yielded
pub fn visual_pos(&self) -> Position {
/// returns the visual position at the end of the last yielded grapheme
pub fn next_visual_pos(&self) -> Position {
self.visual_pos
}
}
impl<'t> Iterator for DocumentFormatter<'t> {
type Item = (FormattedGrapheme<'t>, Position);
type Item = FormattedGrapheme<'t>;
fn next(&mut self) -> Option<Self::Item> {
let grapheme = if self.text_fmt.soft_wrap {
@ -362,23 +441,40 @@ impl<'t> Iterator for DocumentFormatter<'t> {
}
let grapheme = replace(
self.word_buf.get_mut(self.word_i)?,
FormattedGrapheme::placeholder(),
GraphemeWithSource::placeholder(),
);
self.word_i += 1;
grapheme
} else {
self.advance_grapheme(self.visual_pos.col)?
self.advance_grapheme(self.visual_pos.col, self.char_pos)?
};
let pos = self.visual_pos;
if grapheme.grapheme == Grapheme::Newline {
self.visual_pos.row += 1;
self.visual_pos.row += take(&mut self.virtual_lines);
let grapheme = FormattedGrapheme {
raw: grapheme.grapheme,
source: grapheme.source,
visual_pos: self.visual_pos,
line_idx: self.line_pos,
char_idx: self.char_pos,
};
self.char_pos += grapheme.doc_chars();
if !grapheme.is_virtual() {
self.annotations.process_virtual_text_anchors(&grapheme);
}
if grapheme.raw == Grapheme::Newline {
// move to end of newline char
self.visual_pos.col += 1;
let virtual_lines =
self.annotations
.virtual_lines_at(self.char_pos, self.visual_pos, self.line_pos);
self.visual_pos.row += 1 + virtual_lines;
self.visual_pos.col = 0;
self.line_pos += 1;
if !grapheme.is_virtual() {
self.line_pos += 1;
}
} else {
self.visual_pos.col += grapheme.width();
}
Some((grapheme, pos))
Some(grapheme)
}
}

View file

@ -12,6 +12,7 @@ impl TextFormat {
wrap_indicator_highlight: None,
// use a prime number to allow lining up too often with repeat
viewport_width: 17,
soft_wrap_at_text_width: false,
}
}
}
@ -21,20 +22,23 @@ impl<'t> DocumentFormatter<'t> {
use std::fmt::Write;
let mut res = String::new();
let viewport_width = self.text_fmt.viewport_width;
let soft_wrap_at_text_width = self.text_fmt.soft_wrap_at_text_width;
let mut line = 0;
for (grapheme, pos) in self {
if pos.row != line {
for grapheme in self {
if grapheme.visual_pos.row != line {
line += 1;
assert_eq!(pos.row, line);
write!(res, "\n{}", ".".repeat(pos.col)).unwrap();
assert_eq!(grapheme.visual_pos.row, line);
write!(res, "\n{}", ".".repeat(grapheme.visual_pos.col)).unwrap();
}
if !soft_wrap_at_text_width {
assert!(
pos.col <= viewport_width as usize,
grapheme.visual_pos.col <= viewport_width as usize,
"softwrapped failed {}<={viewport_width}",
pos.col
grapheme.visual_pos.col
);
}
write!(res, "{}", grapheme.grapheme).unwrap();
write!(res, "{}", grapheme.raw).unwrap();
}
res
@ -48,7 +52,6 @@ fn softwrap_text(text: &str) -> String {
&TextAnnotations::default(),
0,
)
.0
.collect_to_str()
}
@ -99,6 +102,30 @@ fn long_word_softwrap() {
);
}
#[test]
fn softwrap_multichar_grapheme() {
assert_eq!(
softwrap_text("xxxx xxxx xxx a\u{0301}bc\n"),
"xxxx xxxx xxx \n.ábc \n "
)
}
fn softwrap_text_at_text_width(text: &str) -> String {
let mut text_fmt = TextFormat::new_test(true);
text_fmt.soft_wrap_at_text_width = true;
let annotations = TextAnnotations::default();
let mut formatter =
DocumentFormatter::new_at_prev_checkpoint(text.into(), &text_fmt, &annotations, 0);
formatter.collect_to_str()
}
#[test]
fn long_word_softwrap_text_width() {
assert_eq!(
softwrap_text_at_text_width("xxxxxxxx1xxxx2xxx\nxxxxxxxx1xxxx2xxx"),
"xxxxxxxx1xxxx2xxx \nxxxxxxxx1xxxx2xxx "
);
}
fn overlay_text(text: &str, char_pos: usize, softwrap: bool, overlays: &[Overlay]) -> String {
DocumentFormatter::new_at_prev_checkpoint(
text.into(),
@ -106,7 +133,6 @@ fn overlay_text(text: &str, char_pos: usize, softwrap: bool, overlays: &[Overlay
TextAnnotations::default().add_overlay(overlays, None),
char_pos,
)
.0
.collect_to_str()
}
@ -143,7 +169,6 @@ fn annotate_text(text: &str, softwrap: bool, annotations: &[InlineAnnotation]) -
TextAnnotations::default().add_inline_annotations(annotations, None),
0,
)
.0
.collect_to_str()
}
@ -182,7 +207,6 @@ fn annotation_and_overlay() {
.add_overlay(overlay.as_slice(), None),
0,
)
.0
.collect_to_str(),
"fooo bar "
);

View file

@ -0,0 +1,333 @@
//! Support for [EditorConfig](https://EditorConfig.org) configuration loading.
//!
//! EditorConfig is an editor-agnostic format for specifying configuration in an INI-like, human
//! friendly syntax in `.editorconfig` files (which are intended to be checked into VCS). This
//! module provides functions to search for all `.editorconfig` files that apply to a given path
//! and returns an `EditorConfig` type containing any specified configuration options.
//!
//! At time of writing, this module follows the [spec](https://spec.editorconfig.org/) at
//! version 0.17.2.
use std::{
collections::HashMap,
fs,
num::{NonZeroU16, NonZeroU8},
path::Path,
str::FromStr,
};
use encoding_rs::Encoding;
use globset::{GlobBuilder, GlobMatcher};
use crate::{
indent::{IndentStyle, MAX_INDENT},
LineEnding,
};
/// Configuration declared for a path in `.editorconfig` files.
#[derive(Debug, Default, PartialEq, Eq)]
pub struct EditorConfig {
pub indent_style: Option<IndentStyle>,
pub tab_width: Option<NonZeroU8>,
pub line_ending: Option<LineEnding>,
pub encoding: Option<&'static Encoding>,
// pub spelling_language: Option<SpellingLanguage>,
pub trim_trailing_whitespace: Option<bool>,
pub insert_final_newline: Option<bool>,
pub max_line_length: Option<NonZeroU16>,
}
impl EditorConfig {
/// Finds any configuration in `.editorconfig` files which applies to the given path.
///
/// If no configuration applies then `EditorConfig::default()` is returned.
pub fn find(path: &Path) -> Self {
let mut configs = Vec::new();
// <https://spec.editorconfig.org/#file-processing>
for ancestor in path.ancestors() {
let editor_config_file = ancestor.join(".editorconfig");
let Ok(contents) = fs::read_to_string(&editor_config_file) else {
continue;
};
let ini = match contents.parse::<Ini>() {
Ok(ini) => ini,
Err(err) => {
log::warn!("Ignoring EditorConfig file at '{editor_config_file:?}' because a glob failed to compile: {err}");
continue;
}
};
let is_root = ini.pairs.get("root").map(AsRef::as_ref) == Some("true");
configs.push((ini, ancestor));
// > The search shall stop if an EditorConfig file is found with the `root` key set to
// > `true` in the preamble or when reaching the root filesystem directory.
if is_root {
break;
}
}
let mut pairs = Pairs::new();
// Reverse the configuration stack so that the `.editorconfig` files closest to `path`
// are applied last and overwrite settings in files closer to the search ceiling.
//
// > If multiple EditorConfig files have matching sections, the pairs from the closer
// > EditorConfig file are read last, so pairs in closer files take precedence.
for (config, dir) in configs.into_iter().rev() {
let relative_path = path.strip_prefix(dir).expect("dir is an ancestor of path");
for section in config.sections {
if section.glob.is_match(relative_path) {
log::info!(
"applying EditorConfig from section '{}' in file {:?}",
section.glob.glob(),
dir.join(".editorconfig")
);
pairs.extend(section.pairs);
}
}
}
Self::from_pairs(pairs)
}
fn from_pairs(pairs: Pairs) -> Self {
enum IndentSize {
Tab,
Spaces(NonZeroU8),
}
// <https://spec.editorconfig.org/#supported-pairs>
let indent_size = pairs.get("indent_size").and_then(|value| {
if value.as_ref() == "tab" {
Some(IndentSize::Tab)
} else if let Ok(spaces) = value.parse::<NonZeroU8>() {
Some(IndentSize::Spaces(spaces))
} else {
None
}
});
let tab_width = pairs
.get("tab_width")
.and_then(|value| value.parse::<NonZeroU8>().ok())
.or(match indent_size {
Some(IndentSize::Spaces(spaces)) => Some(spaces),
_ => None,
});
let indent_style = pairs
.get("indent_style")
.and_then(|value| match value.as_ref() {
"tab" => Some(IndentStyle::Tabs),
"space" => {
let spaces = match indent_size {
Some(IndentSize::Spaces(spaces)) => spaces.get(),
Some(IndentSize::Tab) => tab_width.map(|n| n.get()).unwrap_or(4),
None => 4,
};
Some(IndentStyle::Spaces(spaces.clamp(1, MAX_INDENT)))
}
_ => None,
});
let line_ending = pairs
.get("end_of_line")
.and_then(|value| match value.as_ref() {
"lf" => Some(LineEnding::LF),
"crlf" => Some(LineEnding::Crlf),
#[cfg(feature = "unicode-lines")]
"cr" => Some(LineEnding::CR),
_ => None,
});
let encoding = pairs.get("charset").and_then(|value| match value.as_ref() {
"latin1" => Some(encoding_rs::WINDOWS_1252),
"utf-8" => Some(encoding_rs::UTF_8),
// `utf-8-bom` is intentionally ignored.
// > `utf-8-bom` is discouraged.
"utf-16le" => Some(encoding_rs::UTF_16LE),
"utf-16be" => Some(encoding_rs::UTF_16BE),
_ => None,
});
let trim_trailing_whitespace =
pairs
.get("trim_trailing_whitespace")
.and_then(|value| match value.as_ref() {
"true" => Some(true),
"false" => Some(false),
_ => None,
});
let insert_final_newline = pairs
.get("insert_final_newline")
.and_then(|value| match value.as_ref() {
"true" => Some(true),
"false" => Some(false),
_ => None,
});
// This option is not in the spec but is supported by some editors.
// <https://github.com/editorconfig/editorconfig/wiki/EditorConfig-Properties#max_line_length>
let max_line_length = pairs
.get("max_line_length")
.and_then(|value| value.parse::<NonZeroU16>().ok());
Self {
indent_style,
tab_width,
line_ending,
encoding,
trim_trailing_whitespace,
insert_final_newline,
max_line_length,
}
}
}
type Pairs = HashMap<Box<str>, Box<str>>;
#[derive(Debug)]
struct Section {
glob: GlobMatcher,
pairs: Pairs,
}
#[derive(Debug, Default)]
struct Ini {
pairs: Pairs,
sections: Vec<Section>,
}
impl FromStr for Ini {
type Err = globset::Error;
fn from_str(source: &str) -> Result<Self, Self::Err> {
// <https://spec.editorconfig.org/#file-format>
let mut ini = Ini::default();
// > EditorConfig files are in an INI-like file format. To read an EditorConfig file, take
// > one line at a time, from beginning to end. For each line:
for full_line in source.lines() {
// > 1. Remove all leading and trailing whitespace.
let line = full_line.trim();
// > 2. Process the remaining text as specified for its type below.
// > The types of lines are:
// > * Blank: contains nothing. Blank lines are ignored.
if line.is_empty() {
continue;
}
// > * Comment: starts with a ';' or '#'. Comment lines are ignored.
if line.starts_with([';', '#']) {
continue;
}
if let Some(section) = line.strip_prefix('[').and_then(|s| s.strip_suffix(']')) {
// > * Section Header: starts with a `[` and ends with a `]`. These lines define
// > globs...
// <https://spec.editorconfig.org/#glob-expressions>
// We need to modify the glob string slightly since EditorConfig's glob flavor
// doesn't match `globset`'s exactly. `globset` only allows '**' at the beginning
// or end of a glob or between two '/'s. (This replacement is not very fancy but
// should cover most practical cases.)
let mut glob_str = section.replace("**.", "**/*.");
if !is_glob_relative(section) {
glob_str.insert_str(0, "**/");
}
let glob = GlobBuilder::new(&glob_str)
.literal_separator(true)
.backslash_escape(true)
.build()?;
ini.sections.push(Section {
glob: glob.compile_matcher(),
pairs: Pairs::new(),
});
} else if let Some((key, value)) = line.split_once('=') {
// > * Key-Value Pair (or Pair): contains a key and a value, separated by an `=`.
// > * Key: The part before the first `=` on the line.
// > * Value: The part, if any, after the first `=` on the line.
// > * Keys and values are trimmed of leading and trailing whitespace, but
// > include any whitespace that is between non-whitespace characters.
// > * If a value is not provided, then the value is an empty string.
let key = key.trim().to_lowercase().into_boxed_str();
let value = value.trim().to_lowercase().into_boxed_str();
if let Some(section) = ini.sections.last_mut() {
section.pairs.insert(key, value);
} else {
ini.pairs.insert(key, value);
}
}
}
Ok(ini)
}
}
/// Determines whether a glob is relative to the directory of the config file.
fn is_glob_relative(source: &str) -> bool {
// > If the glob contains a path separator (a `/` not inside square brackets), then the
// > glob is relative to the directory level of the particular `.editorconfig` file itself.
let mut idx = 0;
while let Some(open) = source[idx..].find('[').map(|open| idx + open) {
if source[..open].contains('/') {
return true;
}
idx = source[open..]
.find(']')
.map_or(source.len(), |close| idx + close);
}
source[idx..].contains('/')
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn is_glob_relative_test() {
assert!(is_glob_relative("subdir/*.c"));
assert!(!is_glob_relative("*.txt"));
assert!(!is_glob_relative("[a/b].c"));
}
fn editor_config(path: impl AsRef<Path>, source: &str) -> EditorConfig {
let path = path.as_ref();
let ini = source.parse::<Ini>().unwrap();
let pairs = ini
.sections
.into_iter()
.filter(|section| section.glob.is_match(path))
.fold(Pairs::new(), |mut acc, section| {
acc.extend(section.pairs);
acc
});
EditorConfig::from_pairs(pairs)
}
#[test]
fn parse_test() {
let source = r#"
[*]
indent_style = space
[Makefile]
indent_style = tab
[docs/**.txt]
insert_final_newline = true
"#;
assert_eq!(
editor_config("a.txt", source),
EditorConfig {
indent_style: Some(IndentStyle::Spaces(4)),
..Default::default()
}
);
assert_eq!(
editor_config("pkg/Makefile", source),
EditorConfig {
indent_style: Some(IndentStyle::Tabs),
..Default::default()
}
);
assert_eq!(
editor_config("docs/config/editor.txt", source),
EditorConfig {
indent_style: Some(IndentStyle::Spaces(4)),
insert_final_newline: Some(true),
..Default::default()
}
);
}
}

View file

@ -1,6 +1,6 @@
use std::ops::DerefMut;
use nucleo::pattern::{Atom, AtomKind, CaseMatching};
use nucleo::pattern::{Atom, AtomKind, CaseMatching, Normalization};
use nucleo::Config;
use parking_lot::Mutex;
@ -38,6 +38,12 @@ pub fn fuzzy_match<T: AsRef<str>>(
if path {
matcher.config.set_match_paths();
}
let pattern = Atom::new(pattern, CaseMatching::Smart, AtomKind::Fuzzy, false);
let pattern = Atom::new(
pattern,
CaseMatching::Smart,
Normalization::Smart,
AtomKind::Fuzzy,
false,
);
pattern.match_list(items, &mut matcher)
}

View file

@ -1,7 +1,7 @@
//! Utility functions to traverse the unicode graphemes of a `Rope`'s text contents.
//!
//! Based on <https://github.com/cessen/led/blob/c4fa72405f510b7fd16052f90a598c429b3104a6/src/graphemes.rs>
use ropey::{iter::Chunks, str_utils::byte_to_char_idx, RopeSlice};
use ropey::{str_utils::byte_to_char_idx, RopeSlice};
use unicode_segmentation::{GraphemeCursor, GraphemeIncomplete};
use unicode_width::UnicodeWidthStr;
@ -28,6 +28,11 @@ pub enum Grapheme<'a> {
}
impl<'a> Grapheme<'a> {
pub fn new_decoration(g: &'static str) -> Grapheme<'a> {
assert_ne!(g, "\t");
Grapheme::new(g.into(), 0, 0)
}
pub fn new(g: GraphemeStr<'a>, visual_x: usize, tab_width: u16) -> Grapheme<'a> {
match g {
g if g == "\t" => Grapheme::Tab {
@ -59,7 +64,7 @@ impl<'a> Grapheme<'a> {
}
pub fn is_whitespace(&self) -> bool {
!matches!(&self, Grapheme::Other { g } if !g.chars().all(char_is_whitespace))
!matches!(&self, Grapheme::Other { g } if !g.chars().next().is_some_and(char_is_whitespace))
}
// TODO currently word boundaries are used for softwrapping.
@ -67,7 +72,7 @@ impl<'a> Grapheme<'a> {
// This could however be improved in the future by considering unicode
// character classes but
pub fn is_word_boundary(&self) -> bool {
!matches!(&self, Grapheme::Other { g,.. } if g.chars().all(char_is_word))
!matches!(&self, Grapheme::Other { g,.. } if g.chars().next().is_some_and(char_is_word))
}
}
@ -114,6 +119,9 @@ pub fn grapheme_width(g: &str) -> usize {
}
}
// NOTE: for byte indexing versions of these functions see `RopeSliceExt`'s
// `floor_grapheme_boundary` and `ceil_grapheme_boundary` and the rope grapheme iterators.
#[must_use]
pub fn nth_prev_grapheme_boundary(slice: RopeSlice, char_idx: usize, n: usize) -> usize {
// Bounds check
@ -203,43 +211,6 @@ pub fn nth_next_grapheme_boundary(slice: RopeSlice, char_idx: usize, n: usize) -
chunk_char_idx + tmp
}
#[must_use]
pub fn nth_next_grapheme_boundary_byte(slice: RopeSlice, mut byte_idx: usize, n: usize) -> usize {
// Bounds check
debug_assert!(byte_idx <= slice.len_bytes());
// Get the chunk with our byte index in it.
let (mut chunk, mut chunk_byte_idx, mut _chunk_char_idx, _) = slice.chunk_at_byte(byte_idx);
// Set up the grapheme cursor.
let mut gc = GraphemeCursor::new(byte_idx, slice.len_bytes(), true);
// Find the nth next grapheme cluster boundary.
for _ in 0..n {
loop {
match gc.next_boundary(chunk, chunk_byte_idx) {
Ok(None) => return slice.len_bytes(),
Ok(Some(n)) => {
byte_idx = n;
break;
}
Err(GraphemeIncomplete::NextChunk) => {
chunk_byte_idx += chunk.len();
let (a, _, _c, _) = slice.chunk_at_byte(chunk_byte_idx);
chunk = a;
// chunk_char_idx = c;
}
Err(GraphemeIncomplete::PreContext(n)) => {
let ctx_chunk = slice.chunk_at_byte(n - 1).0;
gc.provide_context(ctx_chunk, n - ctx_chunk.len());
}
_ => unreachable!(),
}
}
}
byte_idx
}
/// Finds the next grapheme boundary after the given char position.
#[must_use]
#[inline(always)]
@ -247,13 +218,6 @@ pub fn next_grapheme_boundary(slice: RopeSlice, char_idx: usize) -> usize {
nth_next_grapheme_boundary(slice, char_idx, 1)
}
/// Finds the next grapheme boundary after the given byte position.
#[must_use]
#[inline(always)]
pub fn next_grapheme_boundary_byte(slice: RopeSlice, byte_idx: usize) -> usize {
nth_next_grapheme_boundary_byte(slice, byte_idx, 1)
}
/// Returns the passed char index if it's already a grapheme boundary,
/// or the next grapheme boundary char index if not.
#[must_use]
@ -306,187 +270,6 @@ pub fn is_grapheme_boundary(slice: RopeSlice, char_idx: usize) -> bool {
}
}
/// Returns whether the given byte position is a grapheme boundary.
#[must_use]
pub fn is_grapheme_boundary_byte(slice: RopeSlice, byte_idx: usize) -> bool {
// Bounds check
debug_assert!(byte_idx <= slice.len_bytes());
// Get the chunk with our byte index in it.
let (chunk, chunk_byte_idx, _, _) = slice.chunk_at_byte(byte_idx);
// Set up the grapheme cursor.
let mut gc = GraphemeCursor::new(byte_idx, slice.len_bytes(), true);
// Determine if the given position is a grapheme cluster boundary.
loop {
match gc.is_boundary(chunk, chunk_byte_idx) {
Ok(n) => return n,
Err(GraphemeIncomplete::PreContext(n)) => {
let (ctx_chunk, ctx_byte_start, _, _) = slice.chunk_at_byte(n - 1);
gc.provide_context(ctx_chunk, ctx_byte_start);
}
Err(_) => unreachable!(),
}
}
}
/// An iterator over the graphemes of a `RopeSlice`.
#[derive(Clone)]
pub struct RopeGraphemes<'a> {
text: RopeSlice<'a>,
chunks: Chunks<'a>,
cur_chunk: &'a str,
cur_chunk_start: usize,
cursor: GraphemeCursor,
}
impl<'a> fmt::Debug for RopeGraphemes<'a> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("RopeGraphemes")
.field("text", &self.text)
.field("chunks", &self.chunks)
.field("cur_chunk", &self.cur_chunk)
.field("cur_chunk_start", &self.cur_chunk_start)
// .field("cursor", &self.cursor)
.finish()
}
}
impl<'a> RopeGraphemes<'a> {
#[must_use]
pub fn new(slice: RopeSlice) -> RopeGraphemes {
let mut chunks = slice.chunks();
let first_chunk = chunks.next().unwrap_or("");
RopeGraphemes {
text: slice,
chunks,
cur_chunk: first_chunk,
cur_chunk_start: 0,
cursor: GraphemeCursor::new(0, slice.len_bytes(), true),
}
}
}
impl<'a> Iterator for RopeGraphemes<'a> {
type Item = RopeSlice<'a>;
fn next(&mut self) -> Option<RopeSlice<'a>> {
let a = self.cursor.cur_cursor();
let b;
loop {
match self
.cursor
.next_boundary(self.cur_chunk, self.cur_chunk_start)
{
Ok(None) => {
return None;
}
Ok(Some(n)) => {
b = n;
break;
}
Err(GraphemeIncomplete::NextChunk) => {
self.cur_chunk_start += self.cur_chunk.len();
self.cur_chunk = self.chunks.next().unwrap_or("");
}
Err(GraphemeIncomplete::PreContext(idx)) => {
let (chunk, byte_idx, _, _) = self.text.chunk_at_byte(idx.saturating_sub(1));
self.cursor.provide_context(chunk, byte_idx);
}
_ => unreachable!(),
}
}
if a < self.cur_chunk_start {
Some(self.text.byte_slice(a..b))
} else {
let a2 = a - self.cur_chunk_start;
let b2 = b - self.cur_chunk_start;
Some((&self.cur_chunk[a2..b2]).into())
}
}
}
/// An iterator over the graphemes of a `RopeSlice` in reverse.
#[derive(Clone)]
pub struct RevRopeGraphemes<'a> {
text: RopeSlice<'a>,
chunks: Chunks<'a>,
cur_chunk: &'a str,
cur_chunk_start: usize,
cursor: GraphemeCursor,
}
impl<'a> fmt::Debug for RevRopeGraphemes<'a> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("RevRopeGraphemes")
.field("text", &self.text)
.field("chunks", &self.chunks)
.field("cur_chunk", &self.cur_chunk)
.field("cur_chunk_start", &self.cur_chunk_start)
// .field("cursor", &self.cursor)
.finish()
}
}
impl<'a> RevRopeGraphemes<'a> {
#[must_use]
pub fn new(slice: RopeSlice) -> RevRopeGraphemes {
let (mut chunks, mut cur_chunk_start, _, _) = slice.chunks_at_byte(slice.len_bytes());
chunks.reverse();
let first_chunk = chunks.next().unwrap_or("");
cur_chunk_start -= first_chunk.len();
RevRopeGraphemes {
text: slice,
chunks,
cur_chunk: first_chunk,
cur_chunk_start,
cursor: GraphemeCursor::new(slice.len_bytes(), slice.len_bytes(), true),
}
}
}
impl<'a> Iterator for RevRopeGraphemes<'a> {
type Item = RopeSlice<'a>;
fn next(&mut self) -> Option<RopeSlice<'a>> {
let a = self.cursor.cur_cursor();
let b;
loop {
match self
.cursor
.prev_boundary(self.cur_chunk, self.cur_chunk_start)
{
Ok(None) => {
return None;
}
Ok(Some(n)) => {
b = n;
break;
}
Err(GraphemeIncomplete::PrevChunk) => {
self.cur_chunk = self.chunks.next().unwrap_or("");
self.cur_chunk_start -= self.cur_chunk.len();
}
Err(GraphemeIncomplete::PreContext(idx)) => {
let (chunk, byte_idx, _, _) = self.text.chunk_at_byte(idx.saturating_sub(1));
self.cursor.provide_context(chunk, byte_idx);
}
_ => unreachable!(),
}
}
if a >= self.cur_chunk_start + self.cur_chunk.len() {
Some(self.text.byte_slice(b..a))
} else {
let a2 = a - self.cur_chunk_start;
let b2 = b - self.cur_chunk_start;
Some((&self.cur_chunk[b2..a2]).into())
}
}
}
/// A highly compressed Cow<'a, str> that holds
/// atmost u31::MAX bytes and is readonly
pub struct GraphemeStr<'a> {
@ -537,7 +320,7 @@ impl<'a> From<&'a str> for GraphemeStr<'a> {
}
}
impl<'a> From<String> for GraphemeStr<'a> {
impl From<String> for GraphemeStr<'_> {
fn from(g: String) -> Self {
let len = g.len();
let ptr = Box::into_raw(g.into_bytes().into_boxed_slice()) as *mut u8;

View file

@ -1,4 +1,4 @@
use std::{borrow::Cow, collections::HashMap};
use std::{borrow::Cow, collections::HashMap, iter};
use helix_stdx::rope::RopeSliceExt;
use tree_sitter::{Query, QueryCursor, QueryPredicateArg};
@ -8,7 +8,7 @@ use crate::{
graphemes::{grapheme_width, tab_width_at},
syntax::{IndentationHeuristic, LanguageConfiguration, RopeProvider, Syntax},
tree_sitter::Node,
Position, Rope, RopeGraphemes, RopeSlice,
Position, Rope, RopeSlice, Tendril,
};
/// Enum representing indentation style.
@ -200,7 +200,7 @@ pub fn indent_level_for_line(line: RopeSlice, tab_width: usize, indent_width: us
/// Create a string of tabs & spaces that has the same visual width as the given RopeSlice (independent of the tab width).
fn whitespace_with_same_width(text: RopeSlice) -> String {
let mut s = String::new();
for grapheme in RopeGraphemes::new(text) {
for grapheme in text.graphemes() {
if grapheme == "\t" {
s.push('\t');
} else {
@ -210,6 +210,36 @@ fn whitespace_with_same_width(text: RopeSlice) -> String {
s
}
/// normalizes indentation to tabs/spaces based on user configuration
/// This function does not change the actual indentation width, just the character
/// composition.
pub fn normalize_indentation(
prefix: RopeSlice<'_>,
line: RopeSlice<'_>,
dst: &mut Tendril,
indent_style: IndentStyle,
tab_width: usize,
) -> usize {
#[allow(deprecated)]
let off = crate::visual_coords_at_pos(prefix, prefix.len_chars(), tab_width).col;
let mut len = 0;
let mut original_len = 0;
for ch in line.chars() {
match ch {
'\t' => len += tab_width_at(len + off, tab_width as u16),
' ' => len += 1,
_ => break,
}
original_len += 1;
}
if indent_style == IndentStyle::Tabs {
dst.extend(iter::repeat('\t').take(len / tab_width));
len %= tab_width;
}
dst.extend(iter::repeat(' ').take(len));
original_len
}
fn add_indent_level(
mut base_indent: String,
added_indent_level: isize,
@ -265,7 +295,7 @@ fn is_first_in_line(node: Node, text: RopeSlice, new_line_byte_pos: Option<usize
/// This is usually constructed in one of 2 ways:
/// - Successively add indent captures to get the (added) indent from a single line
/// - Successively add the indent results for each line
/// The string that this indentation defines starts with the string contained in the align field (unless it is None), followed by:
/// The string that this indentation defines starts with the string contained in the align field (unless it is None), followed by:
/// - max(0, indent - outdent) tabs, if tabs are used for indentation
/// - max(0, indent - outdent)*indent_width spaces, if spaces are used for indentation
#[derive(Default, Debug, PartialEq, Eq, Clone)]
@ -386,7 +416,7 @@ enum IndentCaptureType<'a> {
Align(RopeSlice<'a>),
}
impl<'a> IndentCaptureType<'a> {
impl IndentCaptureType<'_> {
fn default_scope(&self) -> IndentScope {
match self {
IndentCaptureType::Indent | IndentCaptureType::IndentAlways => IndentScope::Tail,
@ -426,7 +456,7 @@ struct IndentQueryResult<'a> {
fn get_node_start_line(node: Node, new_line_byte_pos: Option<usize>) -> usize {
let mut node_line = node.start_position().row;
// Adjust for the new line that will be inserted
if new_line_byte_pos.map_or(false, |pos| node.start_byte() >= pos) {
if new_line_byte_pos.is_some_and(|pos| node.start_byte() >= pos) {
node_line += 1;
}
node_line
@ -434,7 +464,7 @@ fn get_node_start_line(node: Node, new_line_byte_pos: Option<usize>) -> usize {
fn get_node_end_line(node: Node, new_line_byte_pos: Option<usize>) -> usize {
let mut node_line = node.end_position().row;
// Adjust for the new line that will be inserted (with a strict inequality since end_byte is exclusive)
if new_line_byte_pos.map_or(false, |pos| node.end_byte() > pos) {
if new_line_byte_pos.is_some_and(|pos| node.end_byte() > pos) {
node_line += 1;
}
node_line
@ -457,7 +487,7 @@ fn query_indents<'a>(
// Skip matches where not all custom predicates are fulfilled
if !query.general_predicates(m.pattern_index).iter().all(|pred| {
match pred.operator.as_ref() {
"not-kind-eq?" => match (pred.args.get(0), pred.args.get(1)) {
"not-kind-eq?" => match (pred.args.first(), pred.args.get(1)) {
(
Some(QueryPredicateArg::Capture(capture_idx)),
Some(QueryPredicateArg::String(kind)),
@ -473,7 +503,7 @@ fn query_indents<'a>(
}
},
"same-line?" | "not-same-line?" => {
match (pred.args.get(0), pred.args.get(1)) {
match (pred.args.first(), pred.args.get(1)) {
(
Some(QueryPredicateArg::Capture(capt1)),
Some(QueryPredicateArg::Capture(capt2))
@ -495,7 +525,7 @@ fn query_indents<'a>(
}
}
}
"one-line?" | "not-one-line?" => match pred.args.get(0) {
"one-line?" | "not-one-line?" => match pred.args.first() {
Some(QueryPredicateArg::Capture(capture_idx)) => {
let node = m.nodes_for_capture_index(*capture_idx).next();
@ -786,6 +816,7 @@ fn init_indent_query<'a, 'b>(
/// - The line after the node. This is defined by:
/// - The scope `tail`.
/// - The scope `all` if this node is not the first node on its line.
///
/// Intuitively, `all` applies to everything contained in this node while `tail` applies to everything except for the first line of the node.
/// The indents from different nodes for the same line are then combined.
/// The result [Indentation] is simply the sum of the [Indentation] for all lines.

View file

@ -1,12 +1,16 @@
pub use encoding_rs as encoding;
pub mod auto_pairs;
pub mod case_conversion;
pub mod chars;
pub mod command_line;
pub mod comment;
pub mod completion;
pub mod config;
pub mod diagnostic;
pub mod diff;
pub mod doc_formatter;
pub mod editor_config;
pub mod fuzzy;
pub mod graphemes;
pub mod history;
@ -20,13 +24,14 @@ pub mod object;
mod position;
pub mod search;
pub mod selection;
pub mod shellwords;
pub mod snippets;
pub mod surround;
pub mod syntax;
pub mod test;
pub mod text_annotations;
pub mod textobject;
mod transaction;
pub mod uri;
pub mod wrap;
pub mod unicode {
@ -50,10 +55,9 @@ pub type Tendril = SmartString<smartstring::LazyCompact>;
#[doc(inline)]
pub use {regex, tree_sitter};
pub use graphemes::RopeGraphemes;
pub use position::{
char_idx_at_visual_offset, coords_at_pos, pos_at_coords, visual_offset_from_anchor,
visual_offset_from_block, Position, VisualOffsetError,
char_idx_at_visual_offset, coords_at_pos, pos_at_coords, softwrapped_dimensions,
visual_offset_from_anchor, visual_offset_from_block, Position, VisualOffsetError,
};
#[allow(deprecated)]
pub use position::{pos_at_visual_coords, visual_coords_at_pos};
@ -62,7 +66,10 @@ pub use selection::{Range, Selection};
pub use smallvec::{smallvec, SmallVec};
pub use syntax::Syntax;
pub use completion::CompletionItem;
pub use diagnostic::Diagnostic;
pub use line_ending::{LineEnding, NATIVE_LINE_ENDING};
pub use transaction::{Assoc, Change, ChangeSet, Deletion, Operation, Transaction};
pub use uri::Uri;

View file

@ -79,19 +79,19 @@ pub fn move_vertically_visual(
Direction::Backward => -(count as isize),
};
// TODO how to handle inline annotations that span an entire visual line (very unlikely).
// Compute visual offset relative to block start to avoid trasversing the block twice
row_off += visual_pos.row as isize;
let new_pos = char_idx_at_visual_offset(
let (mut new_pos, virtual_rows) = char_idx_at_visual_offset(
slice,
block_off,
row_off,
new_col as usize,
text_fmt,
annotations,
)
.0;
);
if dir == Direction::Forward {
new_pos += (virtual_rows != 0) as usize;
}
// Special-case to avoid moving to the end of the last non-empty line.
if behaviour == Movement::Extend && slice.line(slice.char_to_line(new_pos)).len_chars() == 0 {
@ -197,13 +197,31 @@ pub fn move_prev_long_word_end(slice: RopeSlice, range: Range, count: usize) ->
word_move(slice, range, count, WordMotionTarget::PrevLongWordEnd)
}
pub fn move_next_sub_word_start(slice: RopeSlice, range: Range, count: usize) -> Range {
word_move(slice, range, count, WordMotionTarget::NextSubWordStart)
}
pub fn move_next_sub_word_end(slice: RopeSlice, range: Range, count: usize) -> Range {
word_move(slice, range, count, WordMotionTarget::NextSubWordEnd)
}
pub fn move_prev_sub_word_start(slice: RopeSlice, range: Range, count: usize) -> Range {
word_move(slice, range, count, WordMotionTarget::PrevSubWordStart)
}
pub fn move_prev_sub_word_end(slice: RopeSlice, range: Range, count: usize) -> Range {
word_move(slice, range, count, WordMotionTarget::PrevSubWordEnd)
}
fn word_move(slice: RopeSlice, range: Range, count: usize, target: WordMotionTarget) -> Range {
let is_prev = matches!(
target,
WordMotionTarget::PrevWordStart
| WordMotionTarget::PrevLongWordStart
| WordMotionTarget::PrevSubWordStart
| WordMotionTarget::PrevWordEnd
| WordMotionTarget::PrevLongWordEnd
| WordMotionTarget::PrevSubWordEnd
);
// Special-case early-out.
@ -383,6 +401,12 @@ pub enum WordMotionTarget {
NextLongWordEnd,
PrevLongWordStart,
PrevLongWordEnd,
// A sub word is similar to a regular word, except it is also delimited by
// underscores and transitions from lowercase to uppercase.
NextSubWordStart,
NextSubWordEnd,
PrevSubWordStart,
PrevSubWordEnd,
}
pub trait CharHelpers {
@ -398,8 +422,10 @@ impl CharHelpers for Chars<'_> {
target,
WordMotionTarget::PrevWordStart
| WordMotionTarget::PrevLongWordStart
| WordMotionTarget::PrevSubWordStart
| WordMotionTarget::PrevWordEnd
| WordMotionTarget::PrevLongWordEnd
| WordMotionTarget::PrevSubWordEnd
);
// Reverse the iterator if needed for the motion direction.
@ -476,6 +502,25 @@ fn is_long_word_boundary(a: char, b: char) -> bool {
}
}
fn is_sub_word_boundary(a: char, b: char, dir: Direction) -> bool {
match (categorize_char(a), categorize_char(b)) {
(CharCategory::Word, CharCategory::Word) => {
if (a == '_') != (b == '_') {
return true;
}
// Subword boundaries are directional: in 'fooBar', there is a
// boundary between 'o' and 'B', but not between 'B' and 'a'.
match dir {
Direction::Forward => a.is_lowercase() && b.is_uppercase(),
Direction::Backward => a.is_uppercase() && b.is_lowercase(),
}
}
(a, b) if a != b => true,
_ => false,
}
}
fn reached_target(target: WordMotionTarget, prev_ch: char, next_ch: char) -> bool {
match target {
WordMotionTarget::NextWordStart | WordMotionTarget::PrevWordEnd => {
@ -494,6 +539,22 @@ fn reached_target(target: WordMotionTarget, prev_ch: char, next_ch: char) -> boo
is_long_word_boundary(prev_ch, next_ch)
&& (!prev_ch.is_whitespace() || char_is_line_ending(next_ch))
}
WordMotionTarget::NextSubWordStart => {
is_sub_word_boundary(prev_ch, next_ch, Direction::Forward)
&& (char_is_line_ending(next_ch) || !(next_ch.is_whitespace() || next_ch == '_'))
}
WordMotionTarget::PrevSubWordEnd => {
is_sub_word_boundary(prev_ch, next_ch, Direction::Backward)
&& (char_is_line_ending(next_ch) || !(next_ch.is_whitespace() || next_ch == '_'))
}
WordMotionTarget::NextSubWordEnd => {
is_sub_word_boundary(prev_ch, next_ch, Direction::Forward)
&& (!(prev_ch.is_whitespace() || prev_ch == '_') || char_is_line_ending(next_ch))
}
WordMotionTarget::PrevSubWordStart => {
is_sub_word_boundary(prev_ch, next_ch, Direction::Backward)
&& (!(prev_ch.is_whitespace() || prev_ch == '_') || char_is_line_ending(next_ch))
}
}
}
@ -1012,6 +1073,178 @@ mod test {
}
}
#[test]
fn test_behaviour_when_moving_to_start_of_next_sub_words() {
let tests = [
(
"NextSubwordStart",
vec![
(1, Range::new(0, 0), Range::new(0, 4)),
(1, Range::new(4, 4), Range::new(4, 11)),
],
),
(
"next_subword_start",
vec![
(1, Range::new(0, 0), Range::new(0, 5)),
(1, Range::new(4, 4), Range::new(5, 13)),
],
),
(
"Next_Subword_Start",
vec![
(1, Range::new(0, 0), Range::new(0, 5)),
(1, Range::new(4, 4), Range::new(5, 13)),
],
),
(
"NEXT_SUBWORD_START",
vec![
(1, Range::new(0, 0), Range::new(0, 5)),
(1, Range::new(4, 4), Range::new(5, 13)),
],
),
(
"next subword start",
vec![
(1, Range::new(0, 0), Range::new(0, 5)),
(1, Range::new(4, 4), Range::new(5, 13)),
],
),
(
"Next Subword Start",
vec![
(1, Range::new(0, 0), Range::new(0, 5)),
(1, Range::new(4, 4), Range::new(5, 13)),
],
),
(
"NEXT SUBWORD START",
vec![
(1, Range::new(0, 0), Range::new(0, 5)),
(1, Range::new(4, 4), Range::new(5, 13)),
],
),
(
"next__subword__start",
vec![
(1, Range::new(0, 0), Range::new(0, 6)),
(1, Range::new(4, 4), Range::new(4, 6)),
(1, Range::new(5, 5), Range::new(6, 15)),
],
),
(
"Next__Subword__Start",
vec![
(1, Range::new(0, 0), Range::new(0, 6)),
(1, Range::new(4, 4), Range::new(4, 6)),
(1, Range::new(5, 5), Range::new(6, 15)),
],
),
(
"NEXT__SUBWORD__START",
vec![
(1, Range::new(0, 0), Range::new(0, 6)),
(1, Range::new(4, 4), Range::new(4, 6)),
(1, Range::new(5, 5), Range::new(6, 15)),
],
),
];
for (sample, scenario) in tests {
for (count, begin, expected_end) in scenario.into_iter() {
let range = move_next_sub_word_start(Rope::from(sample).slice(..), begin, count);
assert_eq!(range, expected_end, "Case failed: [{}]", sample);
}
}
}
#[test]
fn test_behaviour_when_moving_to_end_of_next_sub_words() {
let tests = [
(
"NextSubwordEnd",
vec![
(1, Range::new(0, 0), Range::new(0, 4)),
(1, Range::new(4, 4), Range::new(4, 11)),
],
),
(
"next subword end",
vec![
(1, Range::new(0, 0), Range::new(0, 4)),
(1, Range::new(4, 4), Range::new(4, 12)),
],
),
(
"Next Subword End",
vec![
(1, Range::new(0, 0), Range::new(0, 4)),
(1, Range::new(4, 4), Range::new(4, 12)),
],
),
(
"NEXT SUBWORD END",
vec![
(1, Range::new(0, 0), Range::new(0, 4)),
(1, Range::new(4, 4), Range::new(4, 12)),
],
),
(
"next_subword_end",
vec![
(1, Range::new(0, 0), Range::new(0, 4)),
(1, Range::new(4, 4), Range::new(4, 12)),
],
),
(
"Next_Subword_End",
vec![
(1, Range::new(0, 0), Range::new(0, 4)),
(1, Range::new(4, 4), Range::new(4, 12)),
],
),
(
"NEXT_SUBWORD_END",
vec![
(1, Range::new(0, 0), Range::new(0, 4)),
(1, Range::new(4, 4), Range::new(4, 12)),
],
),
(
"next__subword__end",
vec![
(1, Range::new(0, 0), Range::new(0, 4)),
(1, Range::new(4, 4), Range::new(4, 13)),
(1, Range::new(5, 5), Range::new(5, 13)),
],
),
(
"Next__Subword__End",
vec![
(1, Range::new(0, 0), Range::new(0, 4)),
(1, Range::new(4, 4), Range::new(4, 13)),
(1, Range::new(5, 5), Range::new(5, 13)),
],
),
(
"NEXT__SUBWORD__END",
vec![
(1, Range::new(0, 0), Range::new(0, 4)),
(1, Range::new(4, 4), Range::new(4, 13)),
(1, Range::new(5, 5), Range::new(5, 13)),
],
),
];
for (sample, scenario) in tests {
for (count, begin, expected_end) in scenario.into_iter() {
let range = move_next_sub_word_end(Rope::from(sample).slice(..), begin, count);
assert_eq!(range, expected_end, "Case failed: [{}]", sample);
}
}
}
#[test]
fn test_behaviour_when_moving_to_start_of_next_long_words() {
let tests = [
@ -1181,6 +1414,92 @@ mod test {
}
}
#[test]
fn test_behaviour_when_moving_to_start_of_previous_sub_words() {
let tests = [
(
"PrevSubwordEnd",
vec![
(1, Range::new(13, 13), Range::new(14, 11)),
(1, Range::new(11, 11), Range::new(11, 4)),
],
),
(
"prev subword end",
vec![
(1, Range::new(15, 15), Range::new(16, 13)),
(1, Range::new(12, 12), Range::new(13, 5)),
],
),
(
"Prev Subword End",
vec![
(1, Range::new(15, 15), Range::new(16, 13)),
(1, Range::new(12, 12), Range::new(13, 5)),
],
),
(
"PREV SUBWORD END",
vec![
(1, Range::new(15, 15), Range::new(16, 13)),
(1, Range::new(12, 12), Range::new(13, 5)),
],
),
(
"prev_subword_end",
vec![
(1, Range::new(15, 15), Range::new(16, 13)),
(1, Range::new(12, 12), Range::new(13, 5)),
],
),
(
"Prev_Subword_End",
vec![
(1, Range::new(15, 15), Range::new(16, 13)),
(1, Range::new(12, 12), Range::new(13, 5)),
],
),
(
"PREV_SUBWORD_END",
vec![
(1, Range::new(15, 15), Range::new(16, 13)),
(1, Range::new(12, 12), Range::new(13, 5)),
],
),
(
"prev__subword__end",
vec![
(1, Range::new(17, 17), Range::new(18, 15)),
(1, Range::new(13, 13), Range::new(14, 6)),
(1, Range::new(14, 14), Range::new(15, 6)),
],
),
(
"Prev__Subword__End",
vec![
(1, Range::new(17, 17), Range::new(18, 15)),
(1, Range::new(13, 13), Range::new(14, 6)),
(1, Range::new(14, 14), Range::new(15, 6)),
],
),
(
"PREV__SUBWORD__END",
vec![
(1, Range::new(17, 17), Range::new(18, 15)),
(1, Range::new(13, 13), Range::new(14, 6)),
(1, Range::new(14, 14), Range::new(15, 6)),
],
),
];
for (sample, scenario) in tests {
for (count, begin, expected_end) in scenario.into_iter() {
let range = move_prev_sub_word_start(Rope::from(sample).slice(..), begin, count);
assert_eq!(range, expected_end, "Case failed: [{}]", sample);
}
}
}
#[test]
fn test_behaviour_when_moving_to_start_of_previous_long_words() {
let tests = [
@ -1444,6 +1763,92 @@ mod test {
}
}
#[test]
fn test_behaviour_when_moving_to_end_of_previous_sub_words() {
let tests = [
(
"PrevSubwordEnd",
vec![
(1, Range::new(13, 13), Range::new(14, 11)),
(1, Range::new(11, 11), Range::new(11, 4)),
],
),
(
"prev subword end",
vec![
(1, Range::new(15, 15), Range::new(16, 12)),
(1, Range::new(12, 12), Range::new(12, 4)),
],
),
(
"Prev Subword End",
vec![
(1, Range::new(15, 15), Range::new(16, 12)),
(1, Range::new(12, 12), Range::new(12, 4)),
],
),
(
"PREV SUBWORD END",
vec![
(1, Range::new(15, 15), Range::new(16, 12)),
(1, Range::new(12, 12), Range::new(12, 4)),
],
),
(
"prev_subword_end",
vec![
(1, Range::new(15, 15), Range::new(16, 12)),
(1, Range::new(12, 12), Range::new(12, 4)),
],
),
(
"Prev_Subword_End",
vec![
(1, Range::new(15, 15), Range::new(16, 12)),
(1, Range::new(12, 12), Range::new(12, 4)),
],
),
(
"PREV_SUBWORD_END",
vec![
(1, Range::new(15, 15), Range::new(16, 12)),
(1, Range::new(12, 12), Range::new(12, 4)),
],
),
(
"prev__subword__end",
vec![
(1, Range::new(17, 17), Range::new(18, 13)),
(1, Range::new(13, 13), Range::new(13, 4)),
(1, Range::new(14, 14), Range::new(15, 13)),
],
),
(
"Prev__Subword__End",
vec![
(1, Range::new(17, 17), Range::new(18, 13)),
(1, Range::new(13, 13), Range::new(13, 4)),
(1, Range::new(14, 14), Range::new(15, 13)),
],
),
(
"PREV__SUBWORD__END",
vec![
(1, Range::new(17, 17), Range::new(18, 13)),
(1, Range::new(13, 13), Range::new(13, 4)),
(1, Range::new(14, 14), Range::new(15, 13)),
],
),
];
for (sample, scenario) in tests {
for (count, begin, expected_end) in scenario.into_iter() {
let range = move_prev_sub_word_end(Rope::from(sample).slice(..), begin, count);
assert_eq!(range, expected_end, "Case failed: [{}]", sample);
}
}
}
#[test]
fn test_behaviour_when_moving_to_end_of_next_long_words() {
let tests = [

View file

@ -1,9 +1,15 @@
use std::{borrow::Cow, cmp::Ordering};
use std::{
borrow::Cow,
cmp::Ordering,
ops::{Add, AddAssign, Sub, SubAssign},
};
use helix_stdx::rope::RopeSliceExt;
use crate::{
chars::char_is_line_ending,
doc_formatter::{DocumentFormatter, TextFormat},
graphemes::{ensure_grapheme_boundary_prev, grapheme_width, RopeGraphemes},
graphemes::{ensure_grapheme_boundary_prev, grapheme_width},
line_ending::line_end_char_index,
text_annotations::TextAnnotations,
RopeSlice,
@ -16,6 +22,38 @@ pub struct Position {
pub col: usize,
}
impl AddAssign for Position {
fn add_assign(&mut self, rhs: Self) {
self.row += rhs.row;
self.col += rhs.col;
}
}
impl SubAssign for Position {
fn sub_assign(&mut self, rhs: Self) {
self.row -= rhs.row;
self.col -= rhs.col;
}
}
impl Sub for Position {
type Output = Position;
fn sub(mut self, rhs: Self) -> Self::Output {
self -= rhs;
self
}
}
impl Add for Position {
type Output = Position;
fn add(mut self, rhs: Self) -> Self::Output {
self += rhs;
self
}
}
impl Position {
pub const fn new(row: usize, col: usize) -> Self {
Self { row, col }
@ -65,7 +103,7 @@ pub fn coords_at_pos(text: RopeSlice, pos: usize) -> Position {
let line_start = text.line_to_char(line);
let pos = ensure_grapheme_boundary_prev(text, pos);
let col = RopeGraphemes::new(text.slice(line_start..pos)).count();
let col = text.slice(line_start..pos).graphemes().count();
Position::new(line, col)
}
@ -90,7 +128,7 @@ pub fn visual_coords_at_pos(text: RopeSlice, pos: usize, tab_width: usize) -> Po
let mut col = 0;
for grapheme in RopeGraphemes::new(text.slice(line_start..pos)) {
for grapheme in text.slice(line_start..pos).graphemes() {
if grapheme == "\t" {
col += tab_width - (col % tab_width);
} else {
@ -121,22 +159,31 @@ pub fn visual_offset_from_block(
annotations: &TextAnnotations,
) -> (Position, usize) {
let mut last_pos = Position::default();
let (formatter, block_start) =
let mut formatter =
DocumentFormatter::new_at_prev_checkpoint(text, text_fmt, annotations, anchor);
let mut char_pos = block_start;
let block_start = formatter.next_char_pos();
for (grapheme, vpos) in formatter {
last_pos = vpos;
char_pos += grapheme.doc_chars();
if char_pos > pos {
return (last_pos, block_start);
while let Some(grapheme) = formatter.next() {
last_pos = grapheme.visual_pos;
if formatter.next_char_pos() > pos {
return (grapheme.visual_pos, block_start);
}
}
(last_pos, block_start)
}
/// Returns the height of the given text when softwrapping
pub fn softwrapped_dimensions(text: RopeSlice, text_fmt: &TextFormat) -> (usize, u16) {
let last_pos =
visual_offset_from_block(text, 0, usize::MAX, text_fmt, &TextAnnotations::default()).0;
if last_pos.row == 0 {
(1, last_pos.col as u16)
} else {
(last_pos.row + 1, text_fmt.viewport_width)
}
}
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub enum VisualOffsetError {
PosBeforeAnchorRow,
@ -153,22 +200,21 @@ pub fn visual_offset_from_anchor(
annotations: &TextAnnotations,
max_rows: usize,
) -> Result<(Position, usize), VisualOffsetError> {
let (formatter, block_start) =
let mut formatter =
DocumentFormatter::new_at_prev_checkpoint(text, text_fmt, annotations, anchor);
let mut char_pos = block_start;
let mut anchor_line = None;
let mut found_pos = None;
let mut last_pos = Position::default();
let block_start = formatter.next_char_pos();
if pos < block_start {
return Err(VisualOffsetError::PosBeforeAnchorRow);
}
for (grapheme, vpos) in formatter {
last_pos = vpos;
char_pos += grapheme.doc_chars();
while let Some(grapheme) = formatter.next() {
last_pos = grapheme.visual_pos;
if char_pos > pos {
if formatter.next_char_pos() > pos {
if let Some(anchor_line) = anchor_line {
last_pos.row -= anchor_line;
return Ok((last_pos, block_start));
@ -176,7 +222,7 @@ pub fn visual_offset_from_anchor(
found_pos = Some(last_pos);
}
}
if char_pos > anchor && anchor_line.is_none() {
if formatter.next_char_pos() > anchor && anchor_line.is_none() {
if let Some(mut found_pos) = found_pos {
return if found_pos.row == last_pos.row {
found_pos.row = 0;
@ -190,7 +236,7 @@ pub fn visual_offset_from_anchor(
}
if let Some(anchor_line) = anchor_line {
if vpos.row >= anchor_line + max_rows {
if grapheme.visual_pos.row >= anchor_line + max_rows {
return Err(VisualOffsetError::PosAfterMaxRow);
}
}
@ -231,7 +277,7 @@ pub fn pos_at_coords(text: RopeSlice, coords: Position, limit_before_line_ending
};
let mut col_char_offset = 0;
for (i, g) in RopeGraphemes::new(text.slice(line_start..line_end)).enumerate() {
for (i, g) in text.slice(line_start..line_end).graphemes().enumerate() {
if i == col {
break;
}
@ -262,7 +308,7 @@ pub fn pos_at_visual_coords(text: RopeSlice, coords: Position, tab_width: usize)
let mut col_char_offset = 0;
let mut cols_remaining = col;
for grapheme in RopeGraphemes::new(text.slice(line_start..line_end)) {
for grapheme in text.slice(line_start..line_end).graphemes() {
let grapheme_width = if grapheme == "\t" {
tab_width - ((col - cols_remaining) % tab_width)
} else {
@ -368,39 +414,43 @@ pub fn char_idx_at_visual_block_offset(
text_fmt: &TextFormat,
annotations: &TextAnnotations,
) -> (usize, usize) {
let (formatter, mut char_idx) =
let mut formatter =
DocumentFormatter::new_at_prev_checkpoint(text, text_fmt, annotations, anchor);
let mut last_char_idx = char_idx;
let mut last_char_idx_on_line = None;
let mut last_char_idx = formatter.next_char_pos();
let mut found_non_virtual_on_row = false;
let mut last_row = 0;
for (grapheme, grapheme_pos) in formatter {
match grapheme_pos.row.cmp(&row) {
for grapheme in &mut formatter {
match grapheme.visual_pos.row.cmp(&row) {
Ordering::Equal => {
if grapheme_pos.col + grapheme.width() > column {
if grapheme.visual_pos.col + grapheme.width() > column {
if !grapheme.is_virtual() {
return (char_idx, 0);
} else if let Some(char_idx) = last_char_idx_on_line {
return (char_idx, 0);
return (grapheme.char_idx, 0);
} else if found_non_virtual_on_row {
return (last_char_idx, 0);
}
} else if !grapheme.is_virtual() {
last_char_idx_on_line = Some(char_idx)
found_non_virtual_on_row = true;
last_char_idx = grapheme.char_idx;
}
}
Ordering::Greater if found_non_virtual_on_row => return (last_char_idx, 0),
Ordering::Greater => return (last_char_idx, row - last_row),
_ => (),
Ordering::Less => {
if !grapheme.is_virtual() {
last_row = grapheme.visual_pos.row;
last_char_idx = grapheme.char_idx;
}
}
}
last_char_idx = char_idx;
last_row = grapheme_pos.row;
char_idx += grapheme.doc_chars();
}
(char_idx, 0)
(formatter.next_char_pos(), 0)
}
#[cfg(test)]
mod test {
use super::*;
use crate::text_annotations::InlineAnnotation;
use crate::Rope;
#[test]
@ -761,6 +811,30 @@ mod test {
assert_eq!(pos_at_visual_coords(slice, (10, 10).into(), 4), 0);
}
#[test]
fn test_char_idx_at_visual_row_offset_inline_annotation() {
let text = Rope::from("foo\nbar");
let slice = text.slice(..);
let mut text_fmt = TextFormat::default();
let annotations = [InlineAnnotation {
text: "x".repeat(100).into(),
char_idx: 3,
}];
text_fmt.soft_wrap = true;
assert_eq!(
char_idx_at_visual_offset(
slice,
0,
1,
0,
&text_fmt,
TextAnnotations::default().add_inline_annotations(&annotations, None)
),
(2, 1)
);
}
#[test]
fn test_char_idx_at_visual_row_offset() {
let text = Rope::from("ḧëḷḷö\nẅöṛḷḋ\nfoo");

View file

@ -9,8 +9,9 @@ use crate::{
},
line_ending::get_line_ending,
movement::Direction,
Assoc, ChangeSet, RopeGraphemes, RopeSlice,
Assoc, ChangeSet, RopeSlice,
};
use helix_stdx::range::is_subset;
use helix_stdx::rope::{self, RopeSliceExt};
use smallvec::{smallvec, SmallVec};
use std::{borrow::Cow, iter, slice};
@ -184,16 +185,16 @@ impl Range {
let positions_to_map = match self.anchor.cmp(&self.head) {
Ordering::Equal => [
(&mut self.anchor, Assoc::After),
(&mut self.head, Assoc::After),
(&mut self.anchor, Assoc::AfterSticky),
(&mut self.head, Assoc::AfterSticky),
],
Ordering::Less => [
(&mut self.anchor, Assoc::After),
(&mut self.head, Assoc::Before),
(&mut self.anchor, Assoc::AfterSticky),
(&mut self.head, Assoc::BeforeSticky),
],
Ordering::Greater => [
(&mut self.head, Assoc::After),
(&mut self.anchor, Assoc::Before),
(&mut self.head, Assoc::AfterSticky),
(&mut self.anchor, Assoc::BeforeSticky),
],
};
changes.update_positions(positions_to_map.into_iter());
@ -378,7 +379,7 @@ impl Range {
/// Returns true if this Range covers a single grapheme in the given text
pub fn is_single_grapheme(&self, doc: RopeSlice) -> bool {
let mut graphemes = RopeGraphemes::new(doc.slice(self.from()..self.to()));
let mut graphemes = doc.slice(self.from()..self.to()).graphemes();
let first = graphemes.next();
let second = graphemes.next();
first.is_some() && second.is_none()
@ -401,6 +402,15 @@ impl From<(usize, usize)> for Range {
}
}
impl From<Range> for helix_stdx::Range {
fn from(range: Range) -> Self {
Self {
start: range.from(),
end: range.to(),
}
}
}
/// A selection consists of one or more selection ranges.
/// invariant: A selection can never be empty (always contains at least primary range).
#[derive(Debug, Clone, PartialEq, Eq)]
@ -482,16 +492,16 @@ impl Selection {
range.old_visual_position = None;
match range.anchor.cmp(&range.head) {
Ordering::Equal => [
(&mut range.anchor, Assoc::After),
(&mut range.head, Assoc::After),
(&mut range.anchor, Assoc::AfterSticky),
(&mut range.head, Assoc::AfterSticky),
],
Ordering::Less => [
(&mut range.anchor, Assoc::After),
(&mut range.head, Assoc::Before),
(&mut range.anchor, Assoc::AfterSticky),
(&mut range.head, Assoc::BeforeSticky),
],
Ordering::Greater => [
(&mut range.head, Assoc::After),
(&mut range.anchor, Assoc::Before),
(&mut range.head, Assoc::AfterSticky),
(&mut range.anchor, Assoc::BeforeSticky),
],
}
});
@ -513,6 +523,10 @@ impl Selection {
}
}
pub fn range_bounds(&self) -> impl Iterator<Item = helix_stdx::Range> + '_ {
self.ranges.iter().map(|&range| range.into())
}
pub fn primary_index(&self) -> usize {
self.primary_index
}
@ -605,7 +619,6 @@ impl Selection {
self
}
// TODO: consume an iterator or a vec to reduce allocations?
#[must_use]
pub fn new(ranges: SmallVec<[Range; 1]>, primary_index: usize) -> Self {
assert!(!ranges.is_empty());
@ -660,7 +673,7 @@ impl Selection {
pub fn fragments<'a>(
&'a self,
text: RopeSlice<'a>,
) -> impl DoubleEndedIterator<Item = Cow<'a, str>> + ExactSizeIterator<Item = Cow<str>> + 'a
) -> impl DoubleEndedIterator<Item = Cow<'a, str>> + ExactSizeIterator<Item = Cow<'a, str>>
{
self.ranges.iter().map(move |range| range.fragment(text))
}
@ -683,32 +696,9 @@ impl Selection {
self.ranges.len()
}
// returns true if self ⊇ other
/// returns true if self ⊇ other
pub fn contains(&self, other: &Selection) -> bool {
let (mut iter_self, mut iter_other) = (self.iter(), other.iter());
let (mut ele_self, mut ele_other) = (iter_self.next(), iter_other.next());
loop {
match (ele_self, ele_other) {
(Some(ra), Some(rb)) => {
if !ra.contains_range(rb) {
// `self` doesn't contain next element from `other`, advance `self`, we need to match all from `other`
ele_self = iter_self.next();
} else {
// matched element from `other`, advance `other`
ele_other = iter_other.next();
};
}
(None, Some(_)) => {
// exhausted `self`, we can't match the reminder of `other`
return false;
}
(_, None) => {
// no elements from `other` left to match, `self` contains `other`
return true;
}
}
}
is_subset::<true>(self.range_bounds(), other.range_bounds())
}
}
@ -730,6 +720,12 @@ impl IntoIterator for Selection {
}
}
impl FromIterator<Range> for Selection {
fn from_iter<T: IntoIterator<Item = Range>>(ranges: T) -> Self {
Self::new(ranges.into_iter().collect(), 0)
}
}
impl From<Range> for Selection {
fn from(range: Range) -> Self {
Self {
@ -744,7 +740,7 @@ pub struct LineRangeIter<'a> {
text: RopeSlice<'a>,
}
impl<'a> Iterator for LineRangeIter<'a> {
impl Iterator for LineRangeIter<'_> {
type Item = (usize, usize);
fn next(&mut self) -> Option<Self::Item> {

View file

@ -1,350 +0,0 @@
use std::borrow::Cow;
/// Auto escape for shellwords usage.
pub fn escape(input: Cow<str>) -> Cow<str> {
if !input.chars().any(|x| x.is_ascii_whitespace()) {
input
} else if cfg!(unix) {
Cow::Owned(input.chars().fold(String::new(), |mut buf, c| {
if c.is_ascii_whitespace() {
buf.push('\\');
}
buf.push(c);
buf
}))
} else {
Cow::Owned(format!("\"{}\"", input))
}
}
enum State {
OnWhitespace,
Unquoted,
UnquotedEscaped,
Quoted,
QuoteEscaped,
Dquoted,
DquoteEscaped,
}
pub struct Shellwords<'a> {
state: State,
/// Shellwords where whitespace and escapes has been resolved.
words: Vec<Cow<'a, str>>,
/// The parts of the input that are divided into shellwords. This can be
/// used to retrieve the original text for a given word by looking up the
/// same index in the Vec as the word in `words`.
parts: Vec<&'a str>,
}
impl<'a> From<&'a str> for Shellwords<'a> {
fn from(input: &'a str) -> Self {
use State::*;
let mut state = Unquoted;
let mut words = Vec::new();
let mut parts = Vec::new();
let mut escaped = String::with_capacity(input.len());
let mut part_start = 0;
let mut unescaped_start = 0;
let mut end = 0;
for (i, c) in input.char_indices() {
state = match state {
OnWhitespace => match c {
'"' => {
end = i;
Dquoted
}
'\'' => {
end = i;
Quoted
}
'\\' => {
if cfg!(unix) {
escaped.push_str(&input[unescaped_start..i]);
unescaped_start = i + 1;
UnquotedEscaped
} else {
OnWhitespace
}
}
c if c.is_ascii_whitespace() => {
end = i;
OnWhitespace
}
_ => Unquoted,
},
Unquoted => match c {
'\\' => {
if cfg!(unix) {
escaped.push_str(&input[unescaped_start..i]);
unescaped_start = i + 1;
UnquotedEscaped
} else {
Unquoted
}
}
c if c.is_ascii_whitespace() => {
end = i;
OnWhitespace
}
_ => Unquoted,
},
UnquotedEscaped => Unquoted,
Quoted => match c {
'\\' => {
if cfg!(unix) {
escaped.push_str(&input[unescaped_start..i]);
unescaped_start = i + 1;
QuoteEscaped
} else {
Quoted
}
}
'\'' => {
end = i;
OnWhitespace
}
_ => Quoted,
},
QuoteEscaped => Quoted,
Dquoted => match c {
'\\' => {
if cfg!(unix) {
escaped.push_str(&input[unescaped_start..i]);
unescaped_start = i + 1;
DquoteEscaped
} else {
Dquoted
}
}
'"' => {
end = i;
OnWhitespace
}
_ => Dquoted,
},
DquoteEscaped => Dquoted,
};
let c_len = c.len_utf8();
if i == input.len() - c_len && end == 0 {
end = i + c_len;
}
if end > 0 {
let esc_trim = escaped.trim();
let inp = &input[unescaped_start..end];
if !(esc_trim.is_empty() && inp.trim().is_empty()) {
if esc_trim.is_empty() {
words.push(inp.into());
parts.push(inp);
} else {
words.push([escaped, inp.into()].concat().into());
parts.push(&input[part_start..end]);
escaped = "".to_string();
}
}
unescaped_start = i + 1;
part_start = i + 1;
end = 0;
}
}
debug_assert!(words.len() == parts.len());
Self {
state,
words,
parts,
}
}
}
impl<'a> Shellwords<'a> {
/// Checks that the input ends with a whitespace character which is not escaped.
///
/// # Examples
///
/// ```rust
/// use helix_core::shellwords::Shellwords;
/// assert_eq!(Shellwords::from(" ").ends_with_whitespace(), true);
/// assert_eq!(Shellwords::from(":open ").ends_with_whitespace(), true);
/// assert_eq!(Shellwords::from(":open foo.txt ").ends_with_whitespace(), true);
/// assert_eq!(Shellwords::from(":open").ends_with_whitespace(), false);
/// #[cfg(unix)]
/// assert_eq!(Shellwords::from(":open a\\ ").ends_with_whitespace(), false);
/// #[cfg(unix)]
/// assert_eq!(Shellwords::from(":open a\\ b.txt").ends_with_whitespace(), false);
/// ```
pub fn ends_with_whitespace(&self) -> bool {
matches!(self.state, State::OnWhitespace)
}
/// Returns the list of shellwords calculated from the input string.
pub fn words(&self) -> &[Cow<'a, str>] {
&self.words
}
/// Returns a list of strings which correspond to [`Self::words`] but represent the original
/// text in the input string - including escape characters - without separating whitespace.
pub fn parts(&self) -> &[&'a str] {
&self.parts
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
#[cfg(windows)]
fn test_normal() {
let input = r#":o single_word twó wörds \three\ \"with\ escaping\\"#;
let shellwords = Shellwords::from(input);
let result = shellwords.words().to_vec();
let expected = vec![
Cow::from(":o"),
Cow::from("single_word"),
Cow::from("twó"),
Cow::from("wörds"),
Cow::from("\\three\\"),
Cow::from("\\"),
Cow::from("with\\ escaping\\\\"),
];
// TODO test is_owned and is_borrowed, once they get stabilized.
assert_eq!(expected, result);
}
#[test]
#[cfg(unix)]
fn test_normal() {
let input = r#":o single_word twó wörds \three\ \"with\ escaping\\"#;
let shellwords = Shellwords::from(input);
let result = shellwords.words().to_vec();
let expected = vec![
Cow::from(":o"),
Cow::from("single_word"),
Cow::from("twó"),
Cow::from("wörds"),
Cow::from(r#"three "with escaping\"#),
];
// TODO test is_owned and is_borrowed, once they get stabilized.
assert_eq!(expected, result);
}
#[test]
#[cfg(unix)]
fn test_quoted() {
let quoted =
r#":o 'single_word' 'twó wörds' '' ' ''\three\' \"with\ escaping\\' 'quote incomplete"#;
let shellwords = Shellwords::from(quoted);
let result = shellwords.words().to_vec();
let expected = vec![
Cow::from(":o"),
Cow::from("single_word"),
Cow::from("twó wörds"),
Cow::from(r#"three' "with escaping\"#),
Cow::from("quote incomplete"),
];
assert_eq!(expected, result);
}
#[test]
#[cfg(unix)]
fn test_dquoted() {
let dquoted = r#":o "single_word" "twó wörds" "" " ""\three\' \"with\ escaping\\" "dquote incomplete"#;
let shellwords = Shellwords::from(dquoted);
let result = shellwords.words().to_vec();
let expected = vec![
Cow::from(":o"),
Cow::from("single_word"),
Cow::from("twó wörds"),
Cow::from(r#"three' "with escaping\"#),
Cow::from("dquote incomplete"),
];
assert_eq!(expected, result);
}
#[test]
#[cfg(unix)]
fn test_mixed() {
let dquoted = r#":o single_word 'twó wörds' "\three\' \"with\ escaping\\""no space before"'and after' $#%^@ "%^&(%^" ')(*&^%''a\\\\\b' '"#;
let shellwords = Shellwords::from(dquoted);
let result = shellwords.words().to_vec();
let expected = vec![
Cow::from(":o"),
Cow::from("single_word"),
Cow::from("twó wörds"),
Cow::from("three' \"with escaping\\"),
Cow::from("no space before"),
Cow::from("and after"),
Cow::from("$#%^@"),
Cow::from("%^&(%^"),
Cow::from(")(*&^%"),
Cow::from(r#"a\\b"#),
//last ' just changes to quoted but since we dont have anything after it, it should be ignored
];
assert_eq!(expected, result);
}
#[test]
fn test_lists() {
let input =
r#":set statusline.center ["file-type","file-encoding"] '["list", "in", "quotes"]'"#;
let shellwords = Shellwords::from(input);
let result = shellwords.words().to_vec();
let expected = vec![
Cow::from(":set"),
Cow::from("statusline.center"),
Cow::from(r#"["file-type","file-encoding"]"#),
Cow::from(r#"["list", "in", "quotes"]"#),
];
assert_eq!(expected, result);
}
#[test]
#[cfg(unix)]
fn test_escaping_unix() {
assert_eq!(escape("foobar".into()), Cow::Borrowed("foobar"));
assert_eq!(escape("foo bar".into()), Cow::Borrowed("foo\\ bar"));
assert_eq!(escape("foo\tbar".into()), Cow::Borrowed("foo\\\tbar"));
}
#[test]
#[cfg(windows)]
fn test_escaping_windows() {
assert_eq!(escape("foobar".into()), Cow::Borrowed("foobar"));
assert_eq!(escape("foo bar".into()), Cow::Borrowed("\"foo bar\""));
}
#[test]
#[cfg(unix)]
fn test_parts() {
assert_eq!(Shellwords::from(":o a").parts(), &[":o", "a"]);
assert_eq!(Shellwords::from(":o a\\ ").parts(), &[":o", "a\\ "]);
}
#[test]
#[cfg(windows)]
fn test_parts() {
assert_eq!(Shellwords::from(":o a").parts(), &[":o", "a"]);
assert_eq!(Shellwords::from(":o a\\ ").parts(), &[":o", "a\\"]);
}
#[test]
fn test_multibyte_at_end() {
assert_eq!(Shellwords::from("𒀀").parts(), &["𒀀"]);
assert_eq!(
Shellwords::from(":sh echo 𒀀").parts(),
&[":sh", "echo", "𒀀"]
);
assert_eq!(
Shellwords::from(":sh echo 𒀀 hello world𒀀").parts(),
&[":sh", "echo", "𒀀", "hello", "world𒀀"]
);
}
}

View file

@ -0,0 +1,13 @@
mod active;
mod elaborate;
mod parser;
mod render;
#[derive(PartialEq, Eq, Hash, Debug, PartialOrd, Ord, Clone, Copy)]
pub struct TabstopIdx(usize);
pub const LAST_TABSTOP_IDX: TabstopIdx = TabstopIdx(usize::MAX);
pub use active::ActiveSnippet;
pub use elaborate::{Snippet, SnippetElement, Transform};
pub use render::RenderedSnippet;
pub use render::SnippetRenderCtx;

View file

@ -0,0 +1,272 @@
use std::ops::{Index, IndexMut};
use hashbrown::HashSet;
use helix_stdx::range::{is_exact_subset, is_subset};
use helix_stdx::Range;
use ropey::Rope;
use crate::movement::Direction;
use crate::snippets::render::{RenderedSnippet, Tabstop};
use crate::snippets::TabstopIdx;
use crate::{Assoc, ChangeSet, Selection, Transaction};
pub struct ActiveSnippet {
ranges: Vec<Range>,
active_tabstops: HashSet<TabstopIdx>,
current_tabstop: TabstopIdx,
tabstops: Vec<Tabstop>,
}
impl Index<TabstopIdx> for ActiveSnippet {
type Output = Tabstop;
fn index(&self, index: TabstopIdx) -> &Tabstop {
&self.tabstops[index.0]
}
}
impl IndexMut<TabstopIdx> for ActiveSnippet {
fn index_mut(&mut self, index: TabstopIdx) -> &mut Tabstop {
&mut self.tabstops[index.0]
}
}
impl ActiveSnippet {
pub fn new(snippet: RenderedSnippet) -> Option<Self> {
let snippet = Self {
ranges: snippet.ranges,
tabstops: snippet.tabstops,
active_tabstops: HashSet::new(),
current_tabstop: TabstopIdx(0),
};
(snippet.tabstops.len() != 1).then_some(snippet)
}
pub fn is_valid(&self, new_selection: &Selection) -> bool {
is_subset::<false>(self.ranges.iter().copied(), new_selection.range_bounds())
}
pub fn tabstops(&self) -> impl Iterator<Item = &Tabstop> {
self.tabstops.iter()
}
pub fn delete_placeholder(&self, doc: &Rope) -> Transaction {
Transaction::delete(
doc,
self[self.current_tabstop]
.ranges
.iter()
.map(|range| (range.start, range.end)),
)
}
/// maps the active snippets through a `ChangeSet` updating all tabstop ranges
pub fn map(&mut self, changes: &ChangeSet) -> bool {
let positions_to_map = self.ranges.iter_mut().flat_map(|range| {
[
(&mut range.start, Assoc::After),
(&mut range.end, Assoc::Before),
]
});
changes.update_positions(positions_to_map);
for (i, tabstop) in self.tabstops.iter_mut().enumerate() {
if self.active_tabstops.contains(&TabstopIdx(i)) {
let positions_to_map = tabstop.ranges.iter_mut().flat_map(|range| {
let end_assoc = if range.start == range.end {
Assoc::Before
} else {
Assoc::After
};
[
(&mut range.start, Assoc::Before),
(&mut range.end, end_assoc),
]
});
changes.update_positions(positions_to_map);
} else {
let positions_to_map = tabstop.ranges.iter_mut().flat_map(|range| {
let end_assoc = if range.start == range.end {
Assoc::After
} else {
Assoc::Before
};
[
(&mut range.start, Assoc::After),
(&mut range.end, end_assoc),
]
});
changes.update_positions(positions_to_map);
}
let mut snippet_ranges = self.ranges.iter();
let mut snippet_range = snippet_ranges.next().unwrap();
let mut tabstop_i = 0;
let mut prev = Range { start: 0, end: 0 };
let num_ranges = tabstop.ranges.len() / self.ranges.len();
tabstop.ranges.retain_mut(|range| {
if tabstop_i == num_ranges {
snippet_range = snippet_ranges.next().unwrap();
tabstop_i = 0;
}
tabstop_i += 1;
let retain = snippet_range.start <= snippet_range.end;
if retain {
range.start = range.start.max(snippet_range.start);
range.end = range.end.max(range.start).min(snippet_range.end);
// guaranteed by assoc
debug_assert!(prev.start <= range.start);
debug_assert!(range.start <= range.end);
if prev.end > range.start {
// not really sure what to do in this case. It shouldn't
// really occur in practice, the below just ensures
// our invariants hold
range.start = prev.end;
range.end = range.end.max(range.start)
}
prev = *range;
}
retain
});
}
self.ranges.iter().all(|range| range.end <= range.start)
}
pub fn next_tabstop(&mut self, current_selection: &Selection) -> (Selection, bool) {
let primary_idx = self.primary_idx(current_selection);
while self.current_tabstop.0 + 1 < self.tabstops.len() {
self.current_tabstop.0 += 1;
if self.activate_tabstop() {
let selection = self.tabstop_selection(primary_idx, Direction::Forward);
return (selection, self.current_tabstop.0 + 1 == self.tabstops.len());
}
}
(
self.tabstop_selection(primary_idx, Direction::Forward),
true,
)
}
pub fn prev_tabstop(&mut self, current_selection: &Selection) -> Option<Selection> {
let primary_idx = self.primary_idx(current_selection);
while self.current_tabstop.0 != 0 {
self.current_tabstop.0 -= 1;
if self.activate_tabstop() {
return Some(self.tabstop_selection(primary_idx, Direction::Forward));
}
}
None
}
// computes the primary idx adjusted for the number of cursors in the current tabstop
fn primary_idx(&self, current_selection: &Selection) -> usize {
let primary: Range = current_selection.primary().into();
let res = self
.ranges
.iter()
.position(|&range| range.contains(primary));
res.unwrap_or_else(|| {
unreachable!(
"active snippet must be valid {current_selection:?} {:?}",
self.ranges
)
})
}
fn activate_tabstop(&mut self) -> bool {
let tabstop = &self[self.current_tabstop];
if tabstop.has_placeholder() && tabstop.ranges.iter().all(|range| range.is_empty()) {
return false;
}
self.active_tabstops.clear();
self.active_tabstops.insert(self.current_tabstop);
let mut parent = self[self.current_tabstop].parent;
while let Some(tabstop) = parent {
self.active_tabstops.insert(tabstop);
parent = self[tabstop].parent;
}
true
// TODO: if the user removes the selection(s) in one snippet (but
// there are still other cursors in other snippets) and jumps to the
// next tabstop the selection in that tabstop is restored (at the
// next tabstop). This could be annoying since its not possible to
// remove a snippet cursor until the snippet is complete. On the other
// hand it may be useful since the user may just have meant to edit
// a subselection (like with s) of the tabstops and so the selection
// removal was just temporary. Potentially this could have some sort of
// separate keymap
}
pub fn tabstop_selection(&self, primary_idx: usize, direction: Direction) -> Selection {
let tabstop = &self[self.current_tabstop];
tabstop.selection(direction, primary_idx, self.ranges.len())
}
pub fn insert_subsnippet(mut self, snippet: RenderedSnippet) -> Option<Self> {
if snippet.ranges.len() % self.ranges.len() != 0
|| !is_exact_subset(self.ranges.iter().copied(), snippet.ranges.iter().copied())
{
log::warn!("number of subsnippets did not match, discarding outer snippet");
return ActiveSnippet::new(snippet);
}
let mut cnt = 0;
let parent = self[self.current_tabstop].parent;
let tabstops = snippet.tabstops.into_iter().map(|mut tabstop| {
cnt += 1;
if let Some(parent) = &mut tabstop.parent {
parent.0 += self.current_tabstop.0;
} else {
tabstop.parent = parent;
}
tabstop
});
self.tabstops
.splice(self.current_tabstop.0..=self.current_tabstop.0, tabstops);
self.activate_tabstop();
Some(self)
}
}
#[cfg(test)]
mod tests {
use std::iter::{self};
use ropey::Rope;
use crate::snippets::{ActiveSnippet, Snippet, SnippetRenderCtx};
use crate::{Selection, Transaction};
#[test]
fn fully_remove() {
let snippet = Snippet::parse("foo(${1:bar})$0").unwrap();
let mut doc = Rope::from("bar.\n");
let (transaction, _, snippet) = snippet.render(
&doc,
&Selection::point(4),
|_| (4, 4),
&mut SnippetRenderCtx::test_ctx(),
);
assert!(transaction.apply(&mut doc));
assert_eq!(doc, "bar.foo(bar)\n");
let mut snippet = ActiveSnippet::new(snippet).unwrap();
let edit = Transaction::change(&doc, iter::once((4, 12, None)));
assert!(edit.apply(&mut doc));
snippet.map(edit.changes());
assert!(!snippet.is_valid(&Selection::point(4)))
}
#[test]
fn tabstop_zero_with_placeholder() {
// The `$0` tabstop should not have placeholder text. When we receive a snippet like this
// (from older versions of clangd for example) we should discard the placeholder text.
let snippet = Snippet::parse("sizeof(${0:expression-or-type})").unwrap();
let mut doc = Rope::from("\n");
let (transaction, _, snippet) = snippet.render(
&doc,
&Selection::point(0),
|_| (0, 0),
&mut SnippetRenderCtx::test_ctx(),
);
assert!(transaction.apply(&mut doc));
assert_eq!(doc, "sizeof()\n");
assert!(ActiveSnippet::new(snippet).is_none());
}
}

View file

@ -0,0 +1,385 @@
use std::mem::swap;
use std::ops::Index;
use std::sync::Arc;
use anyhow::{anyhow, Result};
use helix_stdx::rope::RopeSliceExt;
use helix_stdx::Range;
use regex_cursor::engines::meta::Builder as RegexBuilder;
use regex_cursor::engines::meta::Regex;
use regex_cursor::regex_automata::util::syntax::Config as RegexConfig;
use ropey::RopeSlice;
use crate::case_conversion::to_lower_case_with;
use crate::case_conversion::to_upper_case_with;
use crate::case_conversion::{to_camel_case_with, to_pascal_case_with};
use crate::snippets::parser::{self, CaseChange, FormatItem};
use crate::snippets::{TabstopIdx, LAST_TABSTOP_IDX};
use crate::Tendril;
#[derive(Debug)]
pub struct Snippet {
elements: Vec<SnippetElement>,
tabstops: Vec<Tabstop>,
}
impl Snippet {
pub fn parse(snippet: &str) -> Result<Self> {
let parsed_snippet = parser::parse(snippet)
.map_err(|rest| anyhow!("Failed to parse snippet. Remaining input: {}", rest))?;
Ok(Snippet::new(parsed_snippet))
}
pub fn new(elements: Vec<parser::SnippetElement>) -> Snippet {
let mut res = Snippet {
elements: Vec::new(),
tabstops: Vec::new(),
};
res.elements = res.elaborate(elements, None).into();
res.fixup_tabstops();
res.ensure_last_tabstop();
res.renumber_tabstops();
res
}
pub fn elements(&self) -> &[SnippetElement] {
&self.elements
}
pub fn tabstops(&self) -> impl Iterator<Item = &Tabstop> {
self.tabstops.iter()
}
fn renumber_tabstops(&mut self) {
Self::renumber_tabstops_in(&self.tabstops, &mut self.elements);
for i in 0..self.tabstops.len() {
if let Some(parent) = self.tabstops[i].parent {
let parent = self
.tabstops
.binary_search_by_key(&parent, |tabstop| tabstop.idx)
.expect("all tabstops have been resolved");
self.tabstops[i].parent = Some(TabstopIdx(parent));
}
let tabstop = &mut self.tabstops[i];
if let TabstopKind::Placeholder { default } = &tabstop.kind {
let mut default = default.clone();
tabstop.kind = TabstopKind::Empty;
Self::renumber_tabstops_in(&self.tabstops, Arc::get_mut(&mut default).unwrap());
self.tabstops[i].kind = TabstopKind::Placeholder { default };
}
}
}
fn renumber_tabstops_in(tabstops: &[Tabstop], elements: &mut [SnippetElement]) {
for elem in elements {
match elem {
SnippetElement::Tabstop { idx } => {
idx.0 = tabstops
.binary_search_by_key(&*idx, |tabstop| tabstop.idx)
.expect("all tabstops have been resolved")
}
SnippetElement::Variable { default, .. } => {
if let Some(default) = default {
Self::renumber_tabstops_in(tabstops, default);
}
}
SnippetElement::Text(_) => (),
}
}
}
fn fixup_tabstops(&mut self) {
self.tabstops.sort_by_key(|tabstop| tabstop.idx);
self.tabstops.dedup_by(|tabstop1, tabstop2| {
if tabstop1.idx != tabstop2.idx {
return false;
}
// use the first non empty tabstop for all multicursor tabstops
if tabstop2.kind.is_empty() {
swap(tabstop2, tabstop1)
}
true
})
}
fn ensure_last_tabstop(&mut self) {
if matches!(self.tabstops.last(), Some(tabstop) if tabstop.idx == LAST_TABSTOP_IDX) {
return;
}
self.tabstops.push(Tabstop {
idx: LAST_TABSTOP_IDX,
parent: None,
kind: TabstopKind::Empty,
});
self.elements.push(SnippetElement::Tabstop {
idx: LAST_TABSTOP_IDX,
})
}
fn elaborate(
&mut self,
default: Vec<parser::SnippetElement>,
parent: Option<TabstopIdx>,
) -> Box<[SnippetElement]> {
default
.into_iter()
.map(|val| match val {
parser::SnippetElement::Tabstop {
tabstop,
transform: None,
} => SnippetElement::Tabstop {
idx: self.elaborate_placeholder(tabstop, parent, Vec::new()),
},
parser::SnippetElement::Tabstop {
tabstop,
transform: Some(transform),
} => SnippetElement::Tabstop {
idx: self.elaborate_transform(tabstop, parent, transform),
},
parser::SnippetElement::Placeholder { tabstop, value } => SnippetElement::Tabstop {
idx: self.elaborate_placeholder(tabstop, parent, value),
},
parser::SnippetElement::Choice { tabstop, choices } => SnippetElement::Tabstop {
idx: self.elaborate_choice(tabstop, parent, choices),
},
parser::SnippetElement::Variable {
name,
default,
transform,
} => SnippetElement::Variable {
name,
default: default.map(|default| self.elaborate(default, parent)),
// TODO: error for invalid transforms
transform: transform.and_then(Transform::new).map(Box::new),
},
parser::SnippetElement::Text(text) => SnippetElement::Text(text),
})
.collect()
}
fn elaborate_choice(
&mut self,
idx: usize,
parent: Option<TabstopIdx>,
choices: Vec<Tendril>,
) -> TabstopIdx {
let idx = TabstopIdx::elaborate(idx);
self.tabstops.push(Tabstop {
idx,
parent,
kind: TabstopKind::Choice {
choices: choices.into(),
},
});
idx
}
fn elaborate_placeholder(
&mut self,
idx: usize,
parent: Option<TabstopIdx>,
mut default: Vec<parser::SnippetElement>,
) -> TabstopIdx {
let idx = TabstopIdx::elaborate(idx);
if idx == LAST_TABSTOP_IDX && !default.is_empty() {
// Older versions of clangd for example may send a snippet like `${0:placeholder}`
// which is considered by VSCode to be a misuse of the `$0` tabstop.
log::warn!("Discarding placeholder text for the `$0` tabstop ({default:?}). \
The `$0` tabstop signifies the final cursor position and should not include placeholder text.");
default.clear();
}
let default = self.elaborate(default, Some(idx));
self.tabstops.push(Tabstop {
idx,
parent,
kind: TabstopKind::Placeholder {
default: default.into(),
},
});
idx
}
fn elaborate_transform(
&mut self,
idx: usize,
parent: Option<TabstopIdx>,
transform: parser::Transform,
) -> TabstopIdx {
let idx = TabstopIdx::elaborate(idx);
if let Some(transform) = Transform::new(transform) {
self.tabstops.push(Tabstop {
idx,
parent,
kind: TabstopKind::Transform(Arc::new(transform)),
})
} else {
// TODO: proper error
self.tabstops.push(Tabstop {
idx,
parent,
kind: TabstopKind::Empty,
})
}
idx
}
}
impl Index<TabstopIdx> for Snippet {
type Output = Tabstop;
fn index(&self, index: TabstopIdx) -> &Tabstop {
&self.tabstops[index.0]
}
}
#[derive(Debug)]
pub enum SnippetElement {
Tabstop {
idx: TabstopIdx,
},
Variable {
name: Tendril,
default: Option<Box<[SnippetElement]>>,
transform: Option<Box<Transform>>,
},
Text(Tendril),
}
#[derive(Debug)]
pub struct Tabstop {
idx: TabstopIdx,
pub parent: Option<TabstopIdx>,
pub kind: TabstopKind,
}
#[derive(Debug)]
pub enum TabstopKind {
Choice { choices: Arc<[Tendril]> },
Placeholder { default: Arc<[SnippetElement]> },
Empty,
Transform(Arc<Transform>),
}
impl TabstopKind {
pub fn is_empty(&self) -> bool {
matches!(self, TabstopKind::Empty)
}
}
#[derive(Debug)]
pub struct Transform {
regex: Regex,
regex_str: Box<str>,
global: bool,
replacement: Box<[FormatItem]>,
}
impl PartialEq for Transform {
fn eq(&self, other: &Self) -> bool {
self.replacement == other.replacement
&& self.global == other.global
// doens't compare m and i setting but close enough
&& self.regex_str == other.regex_str
}
}
impl Transform {
fn new(transform: parser::Transform) -> Option<Transform> {
let mut config = RegexConfig::new();
let mut global = false;
let mut invalid_config = false;
for c in transform.options.chars() {
match c {
'i' => {
config = config.case_insensitive(true);
}
'm' => {
config = config.multi_line(true);
}
'g' => {
global = true;
}
// we ignore 'u' since we always want to
// do unicode aware matching
_ => invalid_config = true,
}
}
if invalid_config {
log::error!("invalid transform configuration characters {transform:?}");
}
let regex = match RegexBuilder::new().syntax(config).build(&transform.regex) {
Ok(regex) => regex,
Err(err) => {
log::error!("invalid transform {err} {transform:?}");
return None;
}
};
Some(Transform {
regex,
regex_str: transform.regex.as_str().into(),
global,
replacement: transform.replacement.into(),
})
}
pub fn apply(&self, mut doc: RopeSlice<'_>, range: Range) -> Tendril {
let mut buf = Tendril::new();
let it = self
.regex
.captures_iter(doc.regex_input_at(range))
.enumerate();
doc = doc.slice(range);
let mut last_match = 0;
for (_, cap) in it {
// unwrap on 0 is OK because captures only reports matches
let m = cap.get_group(0).unwrap();
buf.extend(doc.byte_slice(last_match..m.start).chunks());
last_match = m.end;
for fmt in &*self.replacement {
match *fmt {
FormatItem::Text(ref text) => {
buf.push_str(text);
}
FormatItem::Capture(i) => {
if let Some(cap) = cap.get_group(i) {
buf.extend(doc.byte_slice(cap.range()).chunks());
}
}
FormatItem::CaseChange(i, change) => {
if let Some(cap) = cap.get_group(i).filter(|i| !i.is_empty()) {
let mut chars = doc.byte_slice(cap.range()).chars();
match change {
CaseChange::Upcase => to_upper_case_with(chars, &mut buf),
CaseChange::Downcase => to_lower_case_with(chars, &mut buf),
CaseChange::Capitalize => {
let first_char = chars.next().unwrap();
buf.extend(first_char.to_uppercase());
buf.extend(chars);
}
CaseChange::PascalCase => to_pascal_case_with(chars, &mut buf),
CaseChange::CamelCase => to_camel_case_with(chars, &mut buf),
}
}
}
FormatItem::Conditional(i, ref if_, ref else_) => {
if cap.get_group(i).map_or(true, |mat| mat.is_empty()) {
buf.push_str(else_)
} else {
buf.push_str(if_)
}
}
}
}
if !self.global {
break;
}
}
buf.extend(doc.byte_slice(last_match..).chunks());
buf
}
}
impl TabstopIdx {
fn elaborate(idx: usize) -> Self {
TabstopIdx(idx.wrapping_sub(1))
}
}

View file

@ -0,0 +1,935 @@
/*!
A parser for LSP/VSCode style snippet syntax
See <https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#snippet_syntax>.
``` text
any ::= tabstop | placeholder | choice | variable | text
tabstop ::= '$' int | '${' int '}'
placeholder ::= '${' int ':' any '}'
choice ::= '${' int '|' text (',' text)* '|}'
variable ::= '$' var | '${' var }'
| '${' var ':' any '}'
| '${' var '/' regex '/' (format | text)+ '/' options '}'
format ::= '$' int | '${' int '}'
| '${' int ':' '/upcase' | '/downcase' | '/capitalize' '}'
| '${' int ':+' if '}'
| '${' int ':?' if ':' else '}'
| '${' int ':-' else '}' | '${' int ':' else '}'
regex ::= Regular Expression value (ctor-string)
options ::= Regular Expression option (ctor-options)
var ::= [_a-zA-Z] [_a-zA-Z0-9]*
int ::= [0-9]+
text ::= .*
if ::= text
else ::= text
```
*/
use crate::Tendril;
use helix_parsec::*;
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub enum CaseChange {
Upcase,
Downcase,
Capitalize,
PascalCase,
CamelCase,
}
#[derive(Debug, PartialEq, Eq)]
pub enum FormatItem {
Text(Tendril),
Capture(usize),
CaseChange(usize, CaseChange),
Conditional(usize, Tendril, Tendril),
}
#[derive(Debug, PartialEq, Eq)]
pub struct Transform {
pub regex: Tendril,
pub replacement: Vec<FormatItem>,
pub options: Tendril,
}
#[derive(Debug, PartialEq, Eq)]
pub enum SnippetElement {
Tabstop {
tabstop: usize,
transform: Option<Transform>,
},
Placeholder {
tabstop: usize,
value: Vec<SnippetElement>,
},
Choice {
tabstop: usize,
choices: Vec<Tendril>,
},
Variable {
name: Tendril,
default: Option<Vec<SnippetElement>>,
transform: Option<Transform>,
},
Text(Tendril),
}
pub fn parse(s: &str) -> Result<Vec<SnippetElement>, &str> {
snippet().parse(s).and_then(|(remainder, snippet)| {
if remainder.is_empty() {
Ok(snippet)
} else {
Err(remainder)
}
})
}
fn var<'a>() -> impl Parser<'a, Output = &'a str> {
// var = [_a-zA-Z][_a-zA-Z0-9]*
move |input: &'a str| {
input
.char_indices()
.take_while(|(p, c)| {
*c == '_'
|| if *p == 0 {
c.is_ascii_alphabetic()
} else {
c.is_ascii_alphanumeric()
}
})
.last()
.map(|(index, c)| {
let index = index + c.len_utf8();
(&input[index..], &input[0..index])
})
.ok_or(input)
}
}
const TEXT_ESCAPE_CHARS: &[char] = &['\\', '}', '$'];
const CHOICE_TEXT_ESCAPE_CHARS: &[char] = &['\\', '|', ','];
fn text<'a>(
escape_chars: &'static [char],
term_chars: &'static [char],
) -> impl Parser<'a, Output = Tendril> {
move |input: &'a str| {
let mut chars = input.char_indices().peekable();
let mut res = Tendril::new();
while let Some((i, c)) = chars.next() {
match c {
'\\' => {
if let Some(&(_, c)) = chars.peek() {
if escape_chars.contains(&c) {
chars.next();
res.push(c);
continue;
}
}
res.push('\\');
}
c if term_chars.contains(&c) => return Ok((&input[i..], res)),
c => res.push(c),
}
}
Ok(("", res))
}
}
fn digit<'a>() -> impl Parser<'a, Output = usize> {
filter_map(take_while(|c| c.is_ascii_digit()), |s| s.parse().ok())
}
fn case_change<'a>() -> impl Parser<'a, Output = CaseChange> {
use CaseChange::*;
choice!(
map("upcase", |_| Upcase),
map("downcase", |_| Downcase),
map("capitalize", |_| Capitalize),
map("pascalcase", |_| PascalCase),
map("camelcase", |_| CamelCase),
)
}
fn format<'a>() -> impl Parser<'a, Output = FormatItem> {
use FormatItem::*;
choice!(
// '$' int
map(right("$", digit()), Capture),
// '${' int '}'
map(seq!("${", digit(), "}"), |seq| Capture(seq.1)),
// '${' int ':' '/upcase' | '/downcase' | '/capitalize' '}'
map(seq!("${", digit(), ":/", case_change(), "}"), |seq| {
CaseChange(seq.1, seq.3)
}),
// '${' int ':+' if '}'
map(
seq!("${", digit(), ":+", text(TEXT_ESCAPE_CHARS, &['}']), "}"),
|seq| { Conditional(seq.1, seq.3, Tendril::new()) }
),
// '${' int ':?' if ':' else '}'
map(
seq!(
"${",
digit(),
":?",
text(TEXT_ESCAPE_CHARS, &[':']),
":",
text(TEXT_ESCAPE_CHARS, &['}']),
"}"
),
|seq| { Conditional(seq.1, seq.3, seq.5) }
),
// '${' int ':-' else '}' | '${' int ':' else '}'
map(
seq!(
"${",
digit(),
":",
optional("-"),
text(TEXT_ESCAPE_CHARS, &['}']),
"}"
),
|seq| { Conditional(seq.1, Tendril::new(), seq.4) }
),
)
}
fn regex<'a>() -> impl Parser<'a, Output = Transform> {
map(
seq!(
"/",
// TODO parse as ECMAScript and convert to rust regex
text(&['/'], &['/']),
"/",
zero_or_more(choice!(
format(),
// text doesn't parse $, if format fails we just accept the $ as text
map("$", |_| FormatItem::Text("$".into())),
map(text(&['\\', '/'], &['/', '$']), FormatItem::Text),
)),
"/",
// vscode really doesn't allow escaping } here
// so it's impossible to write a regex escape containing a }
// we can consider deviating here and allowing the escape
text(&[], &['}']),
),
|(_, value, _, replacement, _, options)| Transform {
regex: value,
replacement,
options,
},
)
}
fn tabstop<'a>() -> impl Parser<'a, Output = SnippetElement> {
map(
or(
map(right("$", digit()), |i| (i, None)),
map(
seq!("${", digit(), optional(regex()), "}"),
|(_, i, transform, _)| (i, transform),
),
),
|(tabstop, transform)| SnippetElement::Tabstop { tabstop, transform },
)
}
fn placeholder<'a>() -> impl Parser<'a, Output = SnippetElement> {
map(
seq!(
"${",
digit(),
":",
// according to the grammar there is just a single anything here.
// However in the prose it is explained that placeholders can be nested.
// The example there contains both a placeholder text and a nested placeholder
// which indicates a list. Looking at the VSCode sourcecode, the placeholder
// is indeed parsed as zero_or_more so the grammar is simply incorrect here
zero_or_more(anything(TEXT_ESCAPE_CHARS, true)),
"}"
),
|seq| SnippetElement::Placeholder {
tabstop: seq.1,
value: seq.3,
},
)
}
fn choice<'a>() -> impl Parser<'a, Output = SnippetElement> {
map(
seq!(
"${",
digit(),
"|",
sep(text(CHOICE_TEXT_ESCAPE_CHARS, &['|', ',']), ","),
"|}",
),
|seq| SnippetElement::Choice {
tabstop: seq.1,
choices: seq.3,
},
)
}
fn variable<'a>() -> impl Parser<'a, Output = SnippetElement> {
choice!(
// $var
map(right("$", var()), |name| SnippetElement::Variable {
name: name.into(),
default: None,
transform: None,
}),
// ${var}
map(seq!("${", var(), "}",), |values| SnippetElement::Variable {
name: values.1.into(),
default: None,
transform: None,
}),
// ${var:default}
map(
seq!(
"${",
var(),
":",
zero_or_more(anything(TEXT_ESCAPE_CHARS, true)),
"}",
),
|values| SnippetElement::Variable {
name: values.1.into(),
default: Some(values.3),
transform: None,
}
),
// ${var/value/format/options}
map(seq!("${", var(), regex(), "}"), |values| {
SnippetElement::Variable {
name: values.1.into(),
default: None,
transform: Some(values.2),
}
}),
)
}
fn anything<'a>(
escape_chars: &'static [char],
end_at_brace: bool,
) -> impl Parser<'a, Output = SnippetElement> {
let term_chars: &[_] = if end_at_brace { &['$', '}'] } else { &['$'] };
move |input: &'a str| {
let parser = choice!(
tabstop(),
placeholder(),
choice(),
variable(),
map("$", |_| SnippetElement::Text("$".into())),
map(text(escape_chars, term_chars), SnippetElement::Text),
);
parser.parse(input)
}
}
fn snippet<'a>() -> impl Parser<'a, Output = Vec<SnippetElement>> {
one_or_more(anything(TEXT_ESCAPE_CHARS, false))
}
#[cfg(test)]
mod test {
use crate::snippets::{Snippet, SnippetRenderCtx};
use super::SnippetElement::*;
use super::*;
#[test]
fn empty_string_is_error() {
assert_eq!(Err(""), parse(""));
}
#[test]
fn parse_placeholders_in_function_call() {
assert_eq!(
Ok(vec![
Text("match(".into()),
Placeholder {
tabstop: 1,
value: vec![Text("Arg1".into())],
},
Text(")".into()),
]),
parse("match(${1:Arg1})")
);
// The `$0` tabstop should not have placeholder text. The parser should handle this case
// normally and then the placeholder text should be discarded during elaboration.
assert_eq!(
Ok(vec![
Text("sizeof(".into()),
Placeholder {
tabstop: 0,
value: vec![Text("expression-or-type".into())],
},
Text(")".into()),
]),
parse("sizeof(${0:expression-or-type})")
);
}
#[test]
fn unterminated_placeholder() {
assert_eq!(
Ok(vec![
Text("match(".into()),
Text("$".into()),
Text("{1:)".into())
]),
parse("match(${1:)")
)
}
#[test]
fn parse_empty_placeholder() {
assert_eq!(
Ok(vec![
Text("match(".into()),
Placeholder {
tabstop: 1,
value: vec![],
},
Text(")".into()),
]),
parse("match(${1:})")
)
}
#[test]
fn parse_placeholders_in_statement() {
assert_eq!(
Ok(vec![
Text("local ".into()),
Placeholder {
tabstop: 1,
value: vec![Text("var".into())],
},
Text(" = ".into()),
Placeholder {
tabstop: 1,
value: vec![Text("value".into())],
},
]),
parse("local ${1:var} = ${1:value}")
)
}
#[test]
fn parse_tabstop_nested_in_placeholder() {
assert_eq!(
Ok(vec![Placeholder {
tabstop: 1,
value: vec![
Text("var, ".into()),
Tabstop {
tabstop: 2,
transform: None
}
],
}]),
parse("${1:var, $2}")
)
}
#[test]
fn parse_placeholder_nested_in_placeholder() {
assert_eq!(
Ok({
vec![Placeholder {
tabstop: 1,
value: vec![
Text("foo ".into()),
Placeholder {
tabstop: 2,
value: vec![Text("bar".into())],
},
],
}]
}),
parse("${1:foo ${2:bar}}")
)
}
#[test]
fn parse_all() {
assert_eq!(
Ok(vec![
Text("hello ".into()),
Tabstop {
tabstop: 1,
transform: None
},
Tabstop {
tabstop: 2,
transform: None
},
Text(" ".into()),
Choice {
tabstop: 1,
choices: vec!["one".into(), "two".into(), "three".into()],
},
Text(" ".into()),
Variable {
name: "name".into(),
default: Some(vec![Text("foo".into())]),
transform: None,
},
Text(" ".into()),
Variable {
name: "var".into(),
default: None,
transform: None,
},
Text(" ".into()),
Variable {
name: "TM".into(),
default: None,
transform: None,
},
]),
parse("hello $1${2} ${1|one,two,three|} ${name:foo} $var $TM")
);
}
#[test]
fn regex_capture_replace() {
assert_eq!(
Ok({
vec![Variable {
name: "TM_FILENAME".into(),
default: None,
transform: Some(Transform {
regex: "(.*).+$".into(),
replacement: vec![FormatItem::Capture(1), FormatItem::Text("$".into())],
options: Tendril::new(),
}),
}]
}),
parse("${TM_FILENAME/(.*).+$/$1$/}")
);
}
#[test]
fn rust_macro() {
assert_eq!(
Ok({
vec![
Text("macro_rules! ".into()),
Tabstop {
tabstop: 1,
transform: None,
},
Text(" {\n (".into()),
Tabstop {
tabstop: 2,
transform: None,
},
Text(") => {\n ".into()),
Tabstop {
tabstop: 0,
transform: None,
},
Text("\n };\n}".into()),
]
}),
parse("macro_rules! $1 {\n ($2) => {\n $0\n };\n}")
);
}
fn assert_text(snippet: &str, parsed_text: &str) {
let snippet = Snippet::parse(snippet).unwrap();
let mut rendered_snippet = snippet.prepare_render();
let rendered_text = snippet
.render_at(
&mut rendered_snippet,
"".into(),
false,
&mut SnippetRenderCtx::test_ctx(),
0,
)
.0;
assert_eq!(rendered_text, parsed_text)
}
#[test]
fn robust_parsing() {
assert_text("$", "$");
assert_text("\\\\$", "\\$");
assert_text("{", "{");
assert_text("\\}", "}");
assert_text("\\abc", "\\abc");
assert_text("foo${f:\\}}bar", "foo}bar");
assert_text("\\{", "\\{");
assert_text("I need \\\\\\$", "I need \\$");
assert_text("\\", "\\");
assert_text("\\{{", "\\{{");
assert_text("{{", "{{");
assert_text("{{dd", "{{dd");
assert_text("}}", "}}");
assert_text("ff}}", "ff}}");
assert_text("farboo", "farboo");
assert_text("far{{}}boo", "far{{}}boo");
assert_text("far{{123}}boo", "far{{123}}boo");
assert_text("far\\{{123}}boo", "far\\{{123}}boo");
assert_text("far{{id:bern}}boo", "far{{id:bern}}boo");
assert_text("far{{id:bern {{basel}}}}boo", "far{{id:bern {{basel}}}}boo");
assert_text(
"far{{id:bern {{id:basel}}}}boo",
"far{{id:bern {{id:basel}}}}boo",
);
assert_text(
"far{{id:bern {{id2:basel}}}}boo",
"far{{id:bern {{id2:basel}}}}boo",
);
assert_text("${}$\\a\\$\\}\\\\", "${}$\\a$}\\");
assert_text("farboo", "farboo");
assert_text("far{{}}boo", "far{{}}boo");
assert_text("far{{123}}boo", "far{{123}}boo");
assert_text("far\\{{123}}boo", "far\\{{123}}boo");
assert_text("far`123`boo", "far`123`boo");
assert_text("far\\`123\\`boo", "far\\`123\\`boo");
assert_text("\\$far-boo", "$far-boo");
}
fn assert_snippet(snippet: &str, expect: &[SnippetElement]) {
let elements = parse(snippet).unwrap();
assert_eq!(elements, expect.to_owned())
}
#[test]
fn parse_variable() {
use SnippetElement::*;
assert_snippet(
"$far-boo",
&[
Variable {
name: "far".into(),
default: None,
transform: None,
},
Text("-boo".into()),
],
);
assert_snippet(
"far$farboo",
&[
Text("far".into()),
Variable {
name: "farboo".into(),
transform: None,
default: None,
},
],
);
assert_snippet(
"far${farboo}",
&[
Text("far".into()),
Variable {
name: "farboo".into(),
transform: None,
default: None,
},
],
);
assert_snippet(
"$123",
&[Tabstop {
tabstop: 123,
transform: None,
}],
);
assert_snippet(
"$farboo",
&[Variable {
name: "farboo".into(),
transform: None,
default: None,
}],
);
assert_snippet(
"$far12boo",
&[Variable {
name: "far12boo".into(),
transform: None,
default: None,
}],
);
assert_snippet(
"000_${far}_000",
&[
Text("000_".into()),
Variable {
name: "far".into(),
transform: None,
default: None,
},
Text("_000".into()),
],
);
}
#[test]
fn parse_variable_transform() {
assert_snippet(
"${foo///}",
&[Variable {
name: "foo".into(),
transform: Some(Transform {
regex: Tendril::new(),
replacement: Vec::new(),
options: Tendril::new(),
}),
default: None,
}],
);
assert_snippet(
"${foo/regex/format/gmi}",
&[Variable {
name: "foo".into(),
transform: Some(Transform {
regex: "regex".into(),
replacement: vec![FormatItem::Text("format".into())],
options: "gmi".into(),
}),
default: None,
}],
);
assert_snippet(
"${foo/([A-Z][a-z])/format/}",
&[Variable {
name: "foo".into(),
transform: Some(Transform {
regex: "([A-Z][a-z])".into(),
replacement: vec![FormatItem::Text("format".into())],
options: Tendril::new(),
}),
default: None,
}],
);
// invalid regex TODO: reneable tests once we actually parse this regex flavor
// assert_text(
// "${foo/([A-Z][a-z])/format/GMI}",
// "${foo/([A-Z][a-z])/format/GMI}",
// );
// assert_text(
// "${foo/([A-Z][a-z])/format/funky}",
// "${foo/([A-Z][a-z])/format/funky}",
// );
// assert_text("${foo/([A-Z][a-z]/format/}", "${foo/([A-Z][a-z]/format/}");
assert_text(
"${foo/regex\\/format/options}",
"${foo/regex\\/format/options}",
);
// tricky regex
assert_snippet(
"${foo/m\\/atch/$1/i}",
&[Variable {
name: "foo".into(),
transform: Some(Transform {
regex: "m/atch".into(),
replacement: vec![FormatItem::Capture(1)],
options: "i".into(),
}),
default: None,
}],
);
// incomplete
assert_text("${foo///", "${foo///");
assert_text("${foo/regex/format/options", "${foo/regex/format/options");
// format string
assert_snippet(
"${foo/.*/${0:fooo}/i}",
&[Variable {
name: "foo".into(),
transform: Some(Transform {
regex: ".*".into(),
replacement: vec![FormatItem::Conditional(0, Tendril::new(), "fooo".into())],
options: "i".into(),
}),
default: None,
}],
);
assert_snippet(
"${foo/.*/${1}/i}",
&[Variable {
name: "foo".into(),
transform: Some(Transform {
regex: ".*".into(),
replacement: vec![FormatItem::Capture(1)],
options: "i".into(),
}),
default: None,
}],
);
assert_snippet(
"${foo/.*/$1/i}",
&[Variable {
name: "foo".into(),
transform: Some(Transform {
regex: ".*".into(),
replacement: vec![FormatItem::Capture(1)],
options: "i".into(),
}),
default: None,
}],
);
assert_snippet(
"${foo/.*/This-$1-encloses/i}",
&[Variable {
name: "foo".into(),
transform: Some(Transform {
regex: ".*".into(),
replacement: vec![
FormatItem::Text("This-".into()),
FormatItem::Capture(1),
FormatItem::Text("-encloses".into()),
],
options: "i".into(),
}),
default: None,
}],
);
assert_snippet(
"${foo/.*/complex${1:else}/i}",
&[Variable {
name: "foo".into(),
transform: Some(Transform {
regex: ".*".into(),
replacement: vec![
FormatItem::Text("complex".into()),
FormatItem::Conditional(1, Tendril::new(), "else".into()),
],
options: "i".into(),
}),
default: None,
}],
);
assert_snippet(
"${foo/.*/complex${1:-else}/i}",
&[Variable {
name: "foo".into(),
transform: Some(Transform {
regex: ".*".into(),
replacement: vec![
FormatItem::Text("complex".into()),
FormatItem::Conditional(1, Tendril::new(), "else".into()),
],
options: "i".into(),
}),
default: None,
}],
);
assert_snippet(
"${foo/.*/complex${1:+if}/i}",
&[Variable {
name: "foo".into(),
transform: Some(Transform {
regex: ".*".into(),
replacement: vec![
FormatItem::Text("complex".into()),
FormatItem::Conditional(1, "if".into(), Tendril::new()),
],
options: "i".into(),
}),
default: None,
}],
);
assert_snippet(
"${foo/.*/complex${1:?if:else}/i}",
&[Variable {
name: "foo".into(),
transform: Some(Transform {
regex: ".*".into(),
replacement: vec![
FormatItem::Text("complex".into()),
FormatItem::Conditional(1, "if".into(), "else".into()),
],
options: "i".into(),
}),
default: None,
}],
);
assert_snippet(
"${foo/.*/complex${1:/upcase}/i}",
&[Variable {
name: "foo".into(),
transform: Some(Transform {
regex: ".*".into(),
replacement: vec![
FormatItem::Text("complex".into()),
FormatItem::CaseChange(1, CaseChange::Upcase),
],
options: "i".into(),
}),
default: None,
}],
);
assert_snippet(
"${TM_DIRECTORY/src\\//$1/}",
&[Variable {
name: "TM_DIRECTORY".into(),
transform: Some(Transform {
regex: "src/".into(),
replacement: vec![FormatItem::Capture(1)],
options: Tendril::new(),
}),
default: None,
}],
);
assert_snippet(
"${TM_SELECTED_TEXT/a/\\/$1/g}",
&[Variable {
name: "TM_SELECTED_TEXT".into(),
transform: Some(Transform {
regex: "a".into(),
replacement: vec![FormatItem::Text("/".into()), FormatItem::Capture(1)],
options: "g".into(),
}),
default: None,
}],
);
assert_snippet(
"${TM_SELECTED_TEXT/a/in\\/$1ner/g}",
&[Variable {
name: "TM_SELECTED_TEXT".into(),
transform: Some(Transform {
regex: "a".into(),
replacement: vec![
FormatItem::Text("in/".into()),
FormatItem::Capture(1),
FormatItem::Text("ner".into()),
],
options: "g".into(),
}),
default: None,
}],
);
assert_snippet(
"${TM_SELECTED_TEXT/a/end\\//g}",
&[Variable {
name: "TM_SELECTED_TEXT".into(),
transform: Some(Transform {
regex: "a".into(),
replacement: vec![FormatItem::Text("end/".into())],
options: "g".into(),
}),
default: None,
}],
);
}
// TODO port more tests from https://github.com/microsoft/vscode/blob/dce493cb6e36346ef2714e82c42ce14fc461b15c/src/vs/editor/contrib/snippet/test/browser/snippetParser.test.ts
}

View file

@ -0,0 +1,355 @@
use std::borrow::Cow;
use std::ops::{Index, IndexMut};
use std::sync::Arc;
use helix_stdx::Range;
use ropey::{Rope, RopeSlice};
use smallvec::SmallVec;
use crate::indent::{normalize_indentation, IndentStyle};
use crate::movement::Direction;
use crate::snippets::elaborate;
use crate::snippets::TabstopIdx;
use crate::snippets::{Snippet, SnippetElement, Transform};
use crate::{selection, Selection, Tendril, Transaction};
#[derive(Debug, Clone, PartialEq)]
pub enum TabstopKind {
Choice { choices: Arc<[Tendril]> },
Placeholder,
Empty,
Transform(Arc<Transform>),
}
#[derive(Debug, PartialEq)]
pub struct Tabstop {
pub ranges: SmallVec<[Range; 1]>,
pub parent: Option<TabstopIdx>,
pub kind: TabstopKind,
}
impl Tabstop {
pub fn has_placeholder(&self) -> bool {
matches!(
self.kind,
TabstopKind::Choice { .. } | TabstopKind::Placeholder
)
}
pub fn selection(
&self,
direction: Direction,
primary_idx: usize,
snippet_ranges: usize,
) -> Selection {
Selection::new(
self.ranges
.iter()
.map(|&range| {
let mut range = selection::Range::new(range.start, range.end);
if direction == Direction::Backward {
range = range.flip()
}
range
})
.collect(),
primary_idx * (self.ranges.len() / snippet_ranges),
)
}
}
#[derive(Debug, Default, PartialEq)]
pub struct RenderedSnippet {
pub tabstops: Vec<Tabstop>,
pub ranges: Vec<Range>,
}
impl RenderedSnippet {
pub fn first_selection(&self, direction: Direction, primary_idx: usize) -> Selection {
self.tabstops[0].selection(direction, primary_idx, self.ranges.len())
}
}
impl Index<TabstopIdx> for RenderedSnippet {
type Output = Tabstop;
fn index(&self, index: TabstopIdx) -> &Tabstop {
&self.tabstops[index.0]
}
}
impl IndexMut<TabstopIdx> for RenderedSnippet {
fn index_mut(&mut self, index: TabstopIdx) -> &mut Tabstop {
&mut self.tabstops[index.0]
}
}
impl Snippet {
pub fn prepare_render(&self) -> RenderedSnippet {
let tabstops =
self.tabstops()
.map(|tabstop| Tabstop {
ranges: SmallVec::new(),
parent: tabstop.parent,
kind: match &tabstop.kind {
elaborate::TabstopKind::Choice { choices } => TabstopKind::Choice {
choices: choices.clone(),
},
// start out as empty: the first non-empty placeholder will change this to
// a placeholder automatically
elaborate::TabstopKind::Empty
| elaborate::TabstopKind::Placeholder { .. } => TabstopKind::Empty,
elaborate::TabstopKind::Transform(transform) => {
TabstopKind::Transform(transform.clone())
}
},
})
.collect();
RenderedSnippet {
tabstops,
ranges: Vec::new(),
}
}
pub fn render_at(
&self,
snippet: &mut RenderedSnippet,
indent: RopeSlice<'_>,
at_newline: bool,
ctx: &mut SnippetRenderCtx,
pos: usize,
) -> (Tendril, usize) {
let mut ctx = SnippetRender {
dst: snippet,
src: self,
indent,
text: Tendril::new(),
off: pos,
ctx,
at_newline,
};
ctx.render_elements(self.elements());
let end = ctx.off;
let text = ctx.text;
snippet.ranges.push(Range { start: pos, end });
(text, end - pos)
}
pub fn render(
&self,
doc: &Rope,
selection: &Selection,
change_range: impl FnMut(&selection::Range) -> (usize, usize),
ctx: &mut SnippetRenderCtx,
) -> (Transaction, Selection, RenderedSnippet) {
let mut snippet = self.prepare_render();
let mut off = 0;
let (transaction, selection) = Transaction::change_by_selection_ignore_overlapping(
doc,
selection,
change_range,
|replacement_start, replacement_end| {
let line_idx = doc.char_to_line(replacement_start);
let line_start = doc.line_to_char(line_idx);
let prefix = doc.slice(line_start..replacement_start);
let indent_len = prefix.chars().take_while(|c| c.is_whitespace()).count();
let indent = prefix.slice(..indent_len);
let at_newline = indent_len == replacement_start - line_start;
let (replacement, replacement_len) = self.render_at(
&mut snippet,
indent,
at_newline,
ctx,
(replacement_start as i128 + off) as usize,
);
off +=
replacement_start as i128 - replacement_end as i128 + replacement_len as i128;
Some(replacement)
},
);
(transaction, selection, snippet)
}
}
pub type VariableResolver = dyn FnMut(&str) -> Option<Cow<str>>;
pub struct SnippetRenderCtx {
pub resolve_var: Box<VariableResolver>,
pub tab_width: usize,
pub indent_style: IndentStyle,
pub line_ending: &'static str,
}
impl SnippetRenderCtx {
#[cfg(test)]
pub(super) fn test_ctx() -> SnippetRenderCtx {
SnippetRenderCtx {
resolve_var: Box::new(|_| None),
tab_width: 4,
indent_style: IndentStyle::Spaces(4),
line_ending: "\n",
}
}
}
struct SnippetRender<'a> {
ctx: &'a mut SnippetRenderCtx,
dst: &'a mut RenderedSnippet,
src: &'a Snippet,
indent: RopeSlice<'a>,
text: Tendril,
off: usize,
at_newline: bool,
}
impl SnippetRender<'_> {
fn render_elements(&mut self, elements: &[SnippetElement]) {
for element in elements {
self.render_element(element)
}
}
fn render_element(&mut self, element: &SnippetElement) {
match *element {
SnippetElement::Tabstop { idx } => self.render_tabstop(idx),
SnippetElement::Variable {
ref name,
ref default,
ref transform,
} => {
// TODO: allow resolve_var access to the doc and make it return rope slice
// so we can access selections and other document content without allocating
if let Some(val) = (self.ctx.resolve_var)(name) {
if let Some(transform) = transform {
self.push_multiline_str(&transform.apply(
(&*val).into(),
Range {
start: 0,
end: val.chars().count(),
},
));
} else {
self.push_multiline_str(&val)
}
} else if let Some(default) = default {
self.render_elements(default)
}
}
SnippetElement::Text(ref text) => self.push_multiline_str(text),
}
}
fn push_multiline_str(&mut self, text: &str) {
let mut lines = text
.split('\n')
.map(|line| line.strip_suffix('\r').unwrap_or(line));
let first_line = lines.next().unwrap();
self.push_str(first_line, self.at_newline);
for line in lines {
self.push_newline();
self.push_str(line, true);
}
}
fn push_str(&mut self, mut text: &str, at_newline: bool) {
if at_newline {
let old_len = self.text.len();
let old_indent_len = normalize_indentation(
self.indent,
text.into(),
&mut self.text,
self.ctx.indent_style,
self.ctx.tab_width,
);
// this is ok because indentation can only be ascii chars (' ' and '\t')
self.off += self.text.len() - old_len;
text = &text[old_indent_len..];
if text.is_empty() {
self.at_newline = true;
return;
}
}
self.text.push_str(text);
self.off += text.chars().count();
}
fn push_newline(&mut self) {
self.off += self.ctx.line_ending.chars().count() + self.indent.len_chars();
self.text.push_str(self.ctx.line_ending);
self.text.extend(self.indent.chunks());
}
fn render_tabstop(&mut self, tabstop: TabstopIdx) {
let start = self.off;
let end = match &self.src[tabstop].kind {
elaborate::TabstopKind::Placeholder { default } if !default.is_empty() => {
self.render_elements(default);
self.dst[tabstop].kind = TabstopKind::Placeholder;
self.off
}
_ => start,
};
self.dst[tabstop].ranges.push(Range { start, end });
}
}
#[cfg(test)]
mod tests {
use helix_stdx::Range;
use crate::snippets::render::Tabstop;
use crate::snippets::{Snippet, SnippetRenderCtx};
use super::TabstopKind;
fn assert_snippet(snippet: &str, expect: &str, tabstops: &[Tabstop]) {
let snippet = Snippet::parse(snippet).unwrap();
let mut rendered_snippet = snippet.prepare_render();
let rendered_text = snippet
.render_at(
&mut rendered_snippet,
"\t".into(),
false,
&mut SnippetRenderCtx::test_ctx(),
0,
)
.0;
assert_eq!(rendered_text, expect);
assert_eq!(&rendered_snippet.tabstops, tabstops);
assert_eq!(
rendered_snippet.ranges.last().unwrap().end,
rendered_text.chars().count()
);
assert_eq!(rendered_snippet.ranges.last().unwrap().start, 0)
}
#[test]
fn rust_macro() {
assert_snippet(
"macro_rules! ${1:name} {\n\t($3) => {\n\t\t$2\n\t};\n}",
"macro_rules! name {\n\t () => {\n\t \n\t };\n\t}",
&[
Tabstop {
ranges: vec![Range { start: 13, end: 17 }].into(),
parent: None,
kind: TabstopKind::Placeholder,
},
Tabstop {
ranges: vec![Range { start: 42, end: 42 }].into(),
parent: None,
kind: TabstopKind::Empty,
},
Tabstop {
ranges: vec![Range { start: 26, end: 26 }].into(),
parent: None,
kind: TabstopKind::Empty,
},
Tabstop {
ranges: vec![Range { start: 53, end: 53 }].into(),
parent: None,
kind: TabstopKind::Empty,
},
],
);
}
}

View file

@ -36,12 +36,12 @@ use helix_loader::grammar::{get_language, load_runtime_file};
pub use tree_cursor::TreeCursor;
fn deserialize_regex<'de, D>(deserializer: D) -> Result<Option<Regex>, D::Error>
fn deserialize_regex<'de, D>(deserializer: D) -> Result<Option<rope::Regex>, D::Error>
where
D: serde::Deserializer<'de>,
{
Option::<String>::deserialize(deserializer)?
.map(|buf| Regex::new(&buf).map_err(serde::de::Error::custom))
.map(|buf| rope::Regex::new(&buf).map_err(serde::de::Error::custom))
.transpose()
}
@ -125,6 +125,9 @@ pub struct LanguageConfiguration {
#[serde(skip_serializing_if = "Option::is_none")]
pub formatter: Option<FormatterConfiguration>,
/// If set, overrides `editor.path-completion`.
pub path_completion: Option<bool>,
#[serde(default)]
pub diagnostic_severity: Severity,
@ -132,7 +135,7 @@ pub struct LanguageConfiguration {
// content_regex
#[serde(default, skip_serializing, deserialize_with = "deserialize_regex")]
pub injection_regex: Option<Regex>,
pub injection_regex: Option<rope::Regex>,
// first_line_regex
//
#[serde(skip)]
@ -329,8 +332,10 @@ pub enum LanguageServerFeature {
WorkspaceSymbols,
// Symbols, use bitflags, see above?
Diagnostics,
PullDiagnostics,
RenameSymbol,
InlayHints,
DocumentColors,
}
impl Display for LanguageServerFeature {
@ -352,8 +357,10 @@ impl Display for LanguageServerFeature {
DocumentSymbols => "document-symbols",
WorkspaceSymbols => "workspace-symbols",
Diagnostics => "diagnostics",
PullDiagnostics => "pull-diagnostics",
RenameSymbol => "rename-symbol",
InlayHints => "inlay-hints",
DocumentColors => "document-colors",
};
write!(f, "{feature}",)
}
@ -616,7 +623,7 @@ pub enum CapturedNode<'a> {
Grouped(Vec<Node<'a>>),
}
impl<'a> CapturedNode<'a> {
impl CapturedNode<'_> {
pub fn start_byte(&self) -> usize {
match self {
Self::Single(n) => n.start_byte(),
@ -753,7 +760,7 @@ impl LanguageConfiguration {
let language = get_language(self.grammar.as_deref().unwrap_or(&self.language_id))
.map_err(|err| {
log::error!(
"Failed to load tree-sitter parser for language {:?}: {}",
"Failed to load tree-sitter parser for language {:?}: {:#}",
self.language_id,
err
)
@ -991,21 +998,32 @@ impl Loader {
.cloned()
}
pub fn language_config_for_language_id(&self, id: &str) -> Option<Arc<LanguageConfiguration>> {
pub fn language_config_for_language_id(
&self,
id: impl PartialEq<String>,
) -> Option<Arc<LanguageConfiguration>> {
self.language_configs
.iter()
.find(|config| config.language_id == id)
.find(|config| id.eq(&config.language_id))
.cloned()
}
/// Unlike language_config_for_language_id, which only returns Some for an exact id, this
/// Unlike `language_config_for_language_id`, which only returns Some for an exact id, this
/// function will perform a regex match on the given string to find the closest language match.
pub fn language_config_for_name(&self, name: &str) -> Option<Arc<LanguageConfiguration>> {
pub fn language_config_for_name(&self, slice: RopeSlice) -> Option<Arc<LanguageConfiguration>> {
// PERF: If the name matches up with the id, then this saves the need to do expensive regex.
let shortcircuit = self.language_config_for_language_id(slice);
if shortcircuit.is_some() {
return shortcircuit;
}
// If the name did not match up with a known id, then match on injection regex.
let mut best_match_length = 0;
let mut best_match_position = None;
for (i, configuration) in self.language_configs.iter().enumerate() {
if let Some(injection_regex) = &configuration.injection_regex {
if let Some(mat) = injection_regex.find(name) {
if let Some(mat) = injection_regex.find(slice.regex_input()) {
let length = mat.end() - mat.start();
if length > best_match_length {
best_match_position = Some(i);
@ -1023,10 +1041,17 @@ impl Loader {
capture: &InjectionLanguageMarker,
) -> Option<Arc<LanguageConfiguration>> {
match capture {
InjectionLanguageMarker::Name(string) => self.language_config_for_name(string),
InjectionLanguageMarker::Filename(file) => self.language_config_for_file_name(file),
InjectionLanguageMarker::LanguageId(id) => self.language_config_for_language_id(*id),
InjectionLanguageMarker::Name(name) => self.language_config_for_name(*name),
InjectionLanguageMarker::Filename(file) => {
let path_str: Cow<str> = (*file).into();
self.language_config_for_file_name(Path::new(path_str.as_ref()))
}
InjectionLanguageMarker::Shebang(shebang) => {
self.language_config_for_language_id(shebang)
let shebang_str: Cow<str> = (*shebang).into();
self.language_config_ids_by_shebang
.get(shebang_str.as_ref())
.and_then(|&id| self.language_configs.get(id).cloned())
}
}
}
@ -1430,8 +1455,11 @@ impl Syntax {
// The `captures` iterator borrows the `Tree` and the `QueryCursor`, which
// prevents them from being moved. But both of these values are really just
// pointers, so it's actually ok to move them.
let cursor_ref =
unsafe { mem::transmute::<_, &'static mut QueryCursor>(&mut cursor) };
let cursor_ref = unsafe {
mem::transmute::<&mut tree_sitter::QueryCursor, &mut tree_sitter::QueryCursor>(
&mut cursor,
)
};
// if reusing cursors & no range this resets to whole range
cursor_ref.set_byte_range(range.clone().unwrap_or(0..usize::MAX));
@ -1736,7 +1764,7 @@ pub(crate) fn generate_edits(
}
use std::sync::atomic::{AtomicUsize, Ordering};
use std::{iter, mem, ops, str, usize};
use std::{iter, mem, ops, str};
use tree_sitter::{
Language as Grammar, Node, Parser, Point, Query, QueryCaptures, QueryCursor, QueryError,
QueryMatch, Range, TextProvider, Tree,
@ -1845,7 +1873,7 @@ struct HighlightIterLayer<'a> {
depth: u32,
}
impl<'a> fmt::Debug for HighlightIterLayer<'a> {
impl fmt::Debug for HighlightIterLayer<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("HighlightIterLayer").finish()
}
@ -2023,12 +2051,13 @@ impl HighlightConfiguration {
for capture in query_match.captures {
let index = Some(capture.index);
if index == self.injection_language_capture_index {
let name = byte_range_to_str(capture.node.byte_range(), source);
injection_capture = Some(InjectionLanguageMarker::Name(name));
injection_capture = Some(InjectionLanguageMarker::Name(
source.byte_slice(capture.node.byte_range()),
));
} else if index == self.injection_filename_capture_index {
let name = byte_range_to_str(capture.node.byte_range(), source);
let path = Path::new(name.as_ref()).to_path_buf();
injection_capture = Some(InjectionLanguageMarker::Filename(path.into()));
injection_capture = Some(InjectionLanguageMarker::Filename(
source.byte_slice(capture.node.byte_range()),
));
} else if index == self.injection_shebang_capture_index {
let node_slice = source.byte_slice(capture.node.byte_range());
@ -2047,7 +2076,7 @@ impl HighlightConfiguration {
.captures_iter(lines.regex_input())
.map(|cap| {
let cap = lines.byte_slice(cap.get_group(1).unwrap().range());
InjectionLanguageMarker::Shebang(cap.into())
InjectionLanguageMarker::Shebang(cap)
})
.next()
} else if index == self.injection_content_capture_index {
@ -2078,8 +2107,8 @@ impl HighlightConfiguration {
"injection.language" if injection_capture.is_none() => {
injection_capture = prop
.value
.as_ref()
.map(|s| InjectionLanguageMarker::Name(s.as_ref().into()));
.as_deref()
.map(InjectionLanguageMarker::LanguageId);
}
// By default, injections do not include the *children* of an
@ -2102,7 +2131,7 @@ impl HighlightConfiguration {
}
}
impl<'a> HighlightIterLayer<'a> {
impl HighlightIterLayer<'_> {
// First, sort scope boundaries by their byte offset in the document. At a
// given position, emit scope endings before scope beginnings. Finally, emit
// scope boundaries from deeper layers first.
@ -2240,7 +2269,7 @@ fn intersect_ranges(
result
}
impl<'a> HighlightIter<'a> {
impl HighlightIter<'_> {
fn emit_event(
&mut self,
offset: usize,
@ -2295,7 +2324,7 @@ impl<'a> HighlightIter<'a> {
}
}
impl<'a> Iterator for HighlightIter<'a> {
impl Iterator for HighlightIter<'_> {
type Item = Result<HighlightEvent, Error>;
fn next(&mut self) -> Option<Self::Item> {
@ -2477,15 +2506,17 @@ impl<'a> Iterator for HighlightIter<'a> {
}
}
// Once a highlighting pattern is found for the current node, skip over
// any later highlighting patterns that also match this node. Captures
// Use the last capture found for the current node, skipping over any
// highlight patterns that also match this node. Captures
// for a given node are ordered by pattern index, so these subsequent
// captures are guaranteed to be for highlighting, not injections or
// local variables.
while let Some((next_match, next_capture_index)) = captures.peek() {
let next_capture = next_match.captures[*next_capture_index];
if next_capture.node == capture.node {
captures.next();
match_.remove();
capture = next_capture;
match_ = captures.next().unwrap().0;
} else {
break;
}
@ -2514,9 +2545,20 @@ impl<'a> Iterator for HighlightIter<'a> {
#[derive(Debug, Clone)]
pub enum InjectionLanguageMarker<'a> {
Name(Cow<'a, str>),
Filename(Cow<'a, Path>),
Shebang(String),
/// The language is specified by `LanguageConfiguration`'s `language_id` field.
///
/// This marker is used when a pattern sets the `injection.language` property, for example
/// `(#set! injection.language "rust")`.
LanguageId(&'a str),
/// The language is specified in the document and captured by `@injection.language`.
///
/// This is used for markdown code fences for example. While the `LanguageId` variant can be
/// looked up by finding the language config that sets an `language_id`, this variant contains
/// text from the document being highlighted, so the text is checked against each language's
/// `injection_regex`.
Name(RopeSlice<'a>),
Filename(RopeSlice<'a>),
Shebang(RopeSlice<'a>),
}
const SHEBANG: &str = r"#!\s*(?:\S*[/\\](?:env\s+(?:\-\S+\s+)*)?)?([^\s\.\d]+)";
@ -2659,12 +2701,20 @@ fn node_is_visible(node: &Node) -> bool {
node.is_missing() || (node.is_named() && node.language().node_kind_is_visible(node.kind_id()))
}
fn format_anonymous_node_kind(kind: &str) -> Cow<str> {
if kind.contains('"') {
Cow::Owned(kind.replace('"', "\\\""))
} else {
Cow::Borrowed(kind)
}
}
pub fn pretty_print_tree<W: fmt::Write>(fmt: &mut W, node: Node) -> fmt::Result {
if node.child_count() == 0 {
if node_is_visible(&node) {
write!(fmt, "({})", node.kind())
} else {
write!(fmt, "\"{}\"", node.kind())
write!(fmt, "\"{}\"", format_anonymous_node_kind(node.kind()))
}
} else {
pretty_print_tree_impl(fmt, &mut node.walk(), 0)
@ -2688,6 +2738,8 @@ fn pretty_print_tree_impl<W: fmt::Write>(
}
write!(fmt, "({}", node.kind())?;
} else {
write!(fmt, " \"{}\"", format_anonymous_node_kind(node.kind()))?;
}
// Handle children.
@ -2946,7 +2998,7 @@ mod test {
#[test]
fn test_pretty_print() {
let source = r#"// Hello"#;
assert_pretty_print("rust", source, "(line_comment)", 0, source.len());
assert_pretty_print("rust", source, "(line_comment \"//\")", 0, source.len());
// A large tree should be indented with fields:
let source = r#"fn main() {
@ -2956,16 +3008,16 @@ mod test {
"rust",
source,
concat!(
"(function_item\n",
"(function_item \"fn\"\n",
" name: (identifier)\n",
" parameters: (parameters)\n",
" body: (block\n",
" parameters: (parameters \"(\" \")\")\n",
" body: (block \"{\"\n",
" (expression_statement\n",
" (macro_invocation\n",
" macro: (identifier)\n",
" (token_tree\n",
" (string_literal\n",
" (string_content)))))))",
" macro: (identifier) \"!\"\n",
" (token_tree \"(\"\n",
" (string_literal \"\\\"\"\n",
" (string_content) \"\\\"\") \")\")) \";\") \"}\"))",
),
0,
source.len(),
@ -2977,7 +3029,7 @@ mod test {
// Error nodes are printed as errors:
let source = r#"}{"#;
assert_pretty_print("rust", source, "(ERROR)", 0, source.len());
assert_pretty_print("rust", source, "(ERROR \"}\" \"{\")", 0, source.len());
// Fields broken under unnamed nodes are determined correctly.
// In the following source, `object` belongs to the `singleton_method`
@ -2992,11 +3044,11 @@ mod test {
"ruby",
source,
concat!(
"(singleton_method\n",
" object: (self)\n",
"(singleton_method \"def\"\n",
" object: (self) \".\"\n",
" name: (identifier)\n",
" body: (body_statement\n",
" (true)))"
" (true)) \"end\")"
),
0,
source.len(),

View file

@ -204,7 +204,7 @@ impl<'a> TreeCursor<'a> {
self.injection_ranges[start_idx..]
.iter()
.take_while(|range| range.start < end)
.take_while(|range| range.start < end || range.depth > 1)
.find_map(|range| (range.start <= start).then_some(range.layer_id))
.unwrap_or(self.root)
}
@ -217,7 +217,7 @@ impl<'a> TreeCursor<'a> {
/// Returns an iterator over the children of the node the TreeCursor is on
/// at the time this is called.
pub fn children(&'a mut self) -> ChildIter {
pub fn children(&'a mut self) -> ChildIter<'a> {
let parent = self.node();
ChildIter {
@ -229,7 +229,7 @@ impl<'a> TreeCursor<'a> {
/// Returns an iterator over the named children of the node the TreeCursor is on
/// at the time this is called.
pub fn named_children(&'a mut self) -> ChildIter {
pub fn named_children(&'a mut self) -> ChildIter<'a> {
let parent = self.node();
ChildIter {

View file

@ -1,8 +1,12 @@
use std::cell::Cell;
use std::cmp::Ordering;
use std::fmt::Debug;
use std::ops::Range;
use std::ptr::NonNull;
use crate::doc_formatter::FormattedGrapheme;
use crate::syntax::Highlight;
use crate::Tendril;
use crate::{Position, Tendril};
/// An inline annotation is continuous text shown
/// on the screen before the grapheme that starts at
@ -75,19 +79,98 @@ impl Overlay {
}
}
/// Line annotations allow for virtual text between normal
/// text lines. They cause `height` empty lines to be inserted
/// below the document line that contains `anchor_char_idx`.
/// Line annotations allow inserting virtual text lines between normal text
/// lines. These lines can be filled with text in the rendering code as their
/// contents have no effect beyond visual appearance.
///
/// These lines can be filled with text in the rendering code
/// as their contents have no effect beyond visual appearance.
/// The height of virtual text is usually not known ahead of time as virtual
/// text often requires softwrapping. Furthermore the height of some virtual
/// text like side-by-side diffs depends on the height of the text (again
/// influenced by softwrap) and other virtual text. Therefore line annotations
/// are computed on the fly instead of ahead of time like other annotations.
///
/// To insert a line after a document line simply set
/// `anchor_char_idx` to `doc.line_to_char(line_idx)`
#[derive(Debug, Clone)]
pub struct LineAnnotation {
pub anchor_char_idx: usize,
pub height: usize,
/// The core of this trait `insert_virtual_lines` function. It is called at the
/// end of every visual line and allows the `LineAnnotation` to insert empty
/// virtual lines. Apart from that the `LineAnnotation` trait has multiple
/// methods that allow it to track anchors in the document.
///
/// When a new traversal of a document starts `reset_pos` is called. Afterwards
/// the other functions are called with indices that are larger then the
/// one passed to `reset_pos`. This allows performing a binary search (use
/// `partition_point`) in `reset_pos` once and then to only look at the next
/// anchor during each method call.
///
/// The `reset_pos`, `skip_conceal` and `process_anchor` functions all return a
/// `char_idx` anchor. This anchor is stored when transversing the document and
/// when the grapheme at the anchor is traversed the `process_anchor` function
/// is called.
///
/// # Note
///
/// All functions only receive immutable references to `self`.
/// `LineAnnotation`s that want to store an internal position or
/// state of some kind should use `Cell`. Using interior mutability for
/// caches is preferable as otherwise a lot of lifetimes become invariant
/// which complicates APIs a lot.
pub trait LineAnnotation {
/// Resets the internal position to `char_idx`. This function is called
/// when a new traversal of a document starts.
///
/// All `char_idx` passed to `insert_virtual_lines` are strictly monotonically increasing
/// with the first `char_idx` greater or equal to the `char_idx`
/// passed to this function.
///
/// # Returns
///
/// The `char_idx` of the next anchor this `LineAnnotation` is interested in,
/// replaces the currently registered anchor. Return `usize::MAX` to ignore
fn reset_pos(&mut self, _char_idx: usize) -> usize {
usize::MAX
}
/// Called when a text is concealed that contains an anchor registered by this `LineAnnotation`.
/// In this case the line decorations **must** ensure that virtual text anchored within that
/// char range is skipped.
///
/// # Returns
///
/// The `char_idx` of the next anchor this `LineAnnotation` is interested in,
/// **after the end of conceal_end_char_idx**
/// replaces the currently registered anchor. Return `usize::MAX` to ignore
fn skip_concealed_anchors(&mut self, conceal_end_char_idx: usize) -> usize {
self.reset_pos(conceal_end_char_idx)
}
/// Process an anchor (horizontal position is provided) and returns the next anchor.
///
/// # Returns
///
/// The `char_idx` of the next anchor this `LineAnnotation` is interested in,
/// replaces the currently registered anchor. Return `usize::MAX` to ignore
fn process_anchor(&mut self, _grapheme: &FormattedGrapheme) -> usize {
usize::MAX
}
/// This function is called at the end of a visual line to insert virtual text
///
/// # Returns
///
/// The number of additional virtual lines to reserve
///
/// # Note
///
/// The `line_end_visual_pos` parameter indicates the visual vertical distance
/// from the start of block where the traversal starts. This includes the offset
/// from other `LineAnnotations`. This allows inline annotations to consider
/// the height of the text and "align" two different documents (like for side
/// by side diffs). These annotations that want to "align" two documents should
/// therefore be added last so that other virtual text is also considered while aligning
fn insert_virtual_lines(
&mut self,
line_end_char_idx: usize,
line_end_visual_pos: Position,
doc_line: usize,
) -> Position;
}
#[derive(Debug)]
@ -128,7 +211,7 @@ impl<A, M> Layer<'_, A, M> {
}
impl<'a, A, M> From<(&'a [A], M)> for Layer<'a, A, M> {
fn from((annotations, metadata): (&'a [A], M)) -> Layer<A, M> {
fn from((annotations, metadata): (&'a [A], M)) -> Layer<'a, A, M> {
Layer {
annotations,
current_index: Cell::new(0),
@ -143,13 +226,68 @@ fn reset_pos<A, M>(layers: &[Layer<A, M>], pos: usize, get_pos: impl Fn(&A) -> u
}
}
/// Safety: We store LineAnnotation in a NonNull pointer. This is necessary to work
/// around an unfortunate inconsistency in rusts variance system that unnnecesarily
/// makes the lifetime invariant if implemented with safe code. This makes the
/// DocFormatter API very cumbersome/basically impossible to work with.
///
/// Normally object types `dyn Foo + 'a` are covariant so if we used `Box<dyn LineAnnotation + 'a>` below
/// everything would be alright. However we want to use `Cell<Box<dyn LineAnnotation + 'a>>`
/// to be able to call the mutable function on `LineAnnotation`. The problem is that
/// some types like `Cell` make all their arguments invariant. This is important for soundness
/// normally for the same reasons that `&'a mut T` is invariant over `T`
/// (see <https://doc.rust-lang.org/nomicon/subtyping.html>). However for `&'a mut` (`dyn Foo + 'b`)
/// there is a specical rule in the language to make `'b` covariant (otherwise trait objects would be
/// super annoying to use). See <https://users.rust-lang.org/t/solved-variance-of-dyn-trait-a> for
/// why this is sound. Sadly that rule doesn't apply to `Cell<Box<(dyn Foo + 'a)>`
/// (or other invariant types like `UnsafeCell` or `*mut (dyn Foo + 'a)`).
///
/// We sidestep the problem by using `NonNull` which is covariant. In the
/// special case of trait objects this is sound (easily checked by adding a
/// `PhantomData<&'a mut Foo + 'a)>` field). We don't need an explicit `Cell`
/// type here because we never hand out any refereces to the trait objects. That
/// means any reference to the pointer can create a valid multable reference
/// that is covariant over `'a` (or in other words it's a raw pointer, as long as
/// we don't hand out references we are free to do whatever we want).
struct RawBox<T: ?Sized>(NonNull<T>);
impl<T: ?Sized> RawBox<T> {
/// Safety: Only a single mutable reference
/// created by this function may exist at a given time.
#[allow(clippy::mut_from_ref)]
unsafe fn get(&self) -> &mut T {
&mut *self.0.as_ptr()
}
}
impl<T: ?Sized> From<Box<T>> for RawBox<T> {
fn from(box_: Box<T>) -> Self {
// obviously safe because Box::into_raw never returns null
unsafe { Self(NonNull::new_unchecked(Box::into_raw(box_))) }
}
}
impl<T: ?Sized> Drop for RawBox<T> {
fn drop(&mut self) {
unsafe { drop(Box::from_raw(self.0.as_ptr())) }
}
}
/// Annotations that change that is displayed when the document is render.
/// Also commonly called virtual text.
#[derive(Default, Debug, Clone)]
#[derive(Default)]
pub struct TextAnnotations<'a> {
inline_annotations: Vec<Layer<'a, InlineAnnotation, Option<Highlight>>>,
overlays: Vec<Layer<'a, Overlay, Option<Highlight>>>,
line_annotations: Vec<Layer<'a, LineAnnotation, ()>>,
line_annotations: Vec<(Cell<usize>, RawBox<dyn LineAnnotation + 'a>)>,
}
impl Debug for TextAnnotations<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("TextAnnotations")
.field("inline_annotations", &self.inline_annotations)
.field("overlays", &self.overlays)
.finish_non_exhaustive()
}
}
impl<'a> TextAnnotations<'a> {
@ -157,9 +295,9 @@ impl<'a> TextAnnotations<'a> {
pub fn reset_pos(&self, char_idx: usize) {
reset_pos(&self.inline_annotations, char_idx, |annot| annot.char_idx);
reset_pos(&self.overlays, char_idx, |annot| annot.char_idx);
reset_pos(&self.line_annotations, char_idx, |annot| {
annot.anchor_char_idx
});
for (next_anchor, layer) in &self.line_annotations {
next_anchor.set(unsafe { layer.get().reset_pos(char_idx) });
}
}
pub fn collect_overlay_highlights(
@ -196,7 +334,9 @@ impl<'a> TextAnnotations<'a> {
layer: &'a [InlineAnnotation],
highlight: Option<Highlight>,
) -> &mut Self {
self.inline_annotations.push((layer, highlight).into());
if !layer.is_empty() {
self.inline_annotations.push((layer, highlight).into());
}
self
}
@ -211,7 +351,9 @@ impl<'a> TextAnnotations<'a> {
/// If multiple layers contain overlay at the same position
/// the overlay from the layer added last will be show.
pub fn add_overlay(&mut self, layer: &'a [Overlay], highlight: Option<Highlight>) -> &mut Self {
self.overlays.push((layer, highlight).into());
if !layer.is_empty() {
self.overlays.push((layer, highlight).into());
}
self
}
@ -219,8 +361,9 @@ impl<'a> TextAnnotations<'a> {
///
/// The line annotations **must be sorted** by their `char_idx`.
/// Multiple line annotations with the same `char_idx` **are not allowed**.
pub fn add_line_annotation(&mut self, layer: &'a [LineAnnotation]) -> &mut Self {
self.line_annotations.push((layer, ()).into());
pub fn add_line_annotation(&mut self, layer: Box<dyn LineAnnotation + 'a>) -> &mut Self {
self.line_annotations
.push((Cell::new(usize::MAX), layer.into()));
self
}
@ -250,21 +393,35 @@ impl<'a> TextAnnotations<'a> {
overlay
}
pub(crate) fn annotation_lines_at(&self, char_idx: usize) -> usize {
self.line_annotations
.iter()
.map(|layer| {
let mut lines = 0;
while let Some(annot) = layer.annotations.get(layer.current_index.get()) {
if annot.anchor_char_idx == char_idx {
layer.current_index.set(layer.current_index.get() + 1);
lines += annot.height
} else {
break;
pub(crate) fn process_virtual_text_anchors(&self, grapheme: &FormattedGrapheme) {
for (next_anchor, layer) in &self.line_annotations {
loop {
match next_anchor.get().cmp(&grapheme.char_idx) {
Ordering::Less => next_anchor
.set(unsafe { layer.get().skip_concealed_anchors(grapheme.char_idx) }),
Ordering::Equal => {
next_anchor.set(unsafe { layer.get().process_anchor(grapheme) })
}
}
lines
})
.sum()
Ordering::Greater => break,
};
}
}
}
pub(crate) fn virtual_lines_at(
&self,
char_idx: usize,
line_end_visual_pos: Position,
doc_line: usize,
) -> usize {
let mut virt_off = Position::new(0, 0);
for (_, layer) in &self.line_annotations {
virt_off += unsafe {
layer
.get()
.insert_virtual_lines(char_idx, line_end_visual_pos + virt_off, doc_line)
};
}
virt_off.row
}
}

View file

@ -29,6 +29,12 @@ pub enum Assoc {
/// Acts like `Before` if a word character is inserted
/// before the position, otherwise acts like `After`
BeforeWord,
/// Acts like `Before` but if the position is within an exact replacement
/// (exact size) the offset to the start of the replacement is kept
BeforeSticky,
/// Acts like `After` but if the position is within an exact replacement
/// (exact size) the offset to the start of the replacement is kept
AfterSticky,
}
impl Assoc {
@ -40,13 +46,17 @@ impl Assoc {
fn insert_offset(self, s: &str) -> usize {
let chars = s.chars().count();
match self {
Assoc::After => chars,
Assoc::After | Assoc::AfterSticky => chars,
Assoc::AfterWord => s.chars().take_while(|&c| char_is_word(c)).count(),
// return position before inserted text
Assoc::Before => 0,
Assoc::Before | Assoc::BeforeSticky => 0,
Assoc::BeforeWord => chars - s.chars().rev().take_while(|&c| char_is_word(c)).count(),
}
}
pub fn sticky(self) -> bool {
matches!(self, Assoc::BeforeSticky | Assoc::AfterSticky)
}
}
#[derive(Debug, Default, Clone, PartialEq, Eq)]
@ -456,8 +466,14 @@ impl ChangeSet {
if pos == old_pos && assoc.stay_at_gaps() {
new_pos
} else {
// place to end of insert
new_pos + assoc.insert_offset(s)
let ins = assoc.insert_offset(s);
// if the deleted and inserted text have the exact same size
// keep the relative offset into the new text
if *len == ins && assoc.sticky() {
new_pos + (pos - old_pos)
} else {
new_pos + assoc.insert_offset(s)
}
}
}),
i
@ -753,7 +769,7 @@ impl<'a> ChangeIterator<'a> {
}
}
impl<'a> Iterator for ChangeIterator<'a> {
impl Iterator for ChangeIterator<'_> {
type Item = Change;
fn next(&mut self) -> Option<Self::Item> {

125
helix-core/src/uri.rs Normal file
View file

@ -0,0 +1,125 @@
use std::{
fmt,
path::{Path, PathBuf},
sync::Arc,
};
/// A generic pointer to a file location.
///
/// Currently this type only supports paths to local files.
///
/// Cloning this type is cheap: the internal representation uses an Arc.
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
#[non_exhaustive]
pub enum Uri {
File(Arc<Path>),
}
impl Uri {
// This clippy allow mirrors url::Url::from_file_path
#[allow(clippy::result_unit_err)]
pub fn to_url(&self) -> Result<url::Url, ()> {
match self {
Uri::File(path) => url::Url::from_file_path(path),
}
}
pub fn as_path(&self) -> Option<&Path> {
match self {
Self::File(path) => Some(path),
}
}
}
impl From<PathBuf> for Uri {
fn from(path: PathBuf) -> Self {
Self::File(path.into())
}
}
impl fmt::Display for Uri {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::File(path) => write!(f, "{}", path.display()),
}
}
}
#[derive(Debug)]
pub struct UrlConversionError {
source: url::Url,
kind: UrlConversionErrorKind,
}
#[derive(Debug)]
pub enum UrlConversionErrorKind {
UnsupportedScheme,
UnableToConvert,
}
impl fmt::Display for UrlConversionError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self.kind {
UrlConversionErrorKind::UnsupportedScheme => {
write!(
f,
"unsupported scheme '{}' in URL {}",
self.source.scheme(),
self.source
)
}
UrlConversionErrorKind::UnableToConvert => {
write!(f, "unable to convert URL to file path: {}", self.source)
}
}
}
}
impl std::error::Error for UrlConversionError {}
fn convert_url_to_uri(url: &url::Url) -> Result<Uri, UrlConversionErrorKind> {
if url.scheme() == "file" {
url.to_file_path()
.map(|path| Uri::File(helix_stdx::path::normalize(path).into()))
.map_err(|_| UrlConversionErrorKind::UnableToConvert)
} else {
Err(UrlConversionErrorKind::UnsupportedScheme)
}
}
impl TryFrom<url::Url> for Uri {
type Error = UrlConversionError;
fn try_from(url: url::Url) -> Result<Self, Self::Error> {
convert_url_to_uri(&url).map_err(|kind| Self::Error { source: url, kind })
}
}
impl TryFrom<&url::Url> for Uri {
type Error = UrlConversionError;
fn try_from(url: &url::Url) -> Result<Self, Self::Error> {
convert_url_to_uri(url).map_err(|kind| Self::Error {
source: url.clone(),
kind,
})
}
}
#[cfg(test)]
mod test {
use super::*;
use url::Url;
#[test]
fn unknown_scheme() {
let url = Url::parse("csharp:/metadata/foo/bar/Baz.cs").unwrap();
assert!(matches!(
Uri::try_from(url),
Err(UrlConversionError {
kind: UrlConversionErrorKind::UnsupportedScheme,
..
})
));
}
}

View file

@ -4,6 +4,8 @@ use textwrap::{Options, WordSplitter::NoHyphenation};
/// Given a slice of text, return the text re-wrapped to fit it
/// within the given width.
pub fn reflow_hard_wrap(text: &str, text_width: usize) -> SmartString<LazyCompact> {
let options = Options::new(text_width).word_splitter(NoHyphenation);
let options = Options::new(text_width)
.word_splitter(NoHyphenation)
.word_separator(textwrap::WordSeparator::AsciiSpace);
textwrap::refill(text, options).into()
}

View file

@ -24,4 +24,4 @@ tokio = { version = "1", features = ["rt", "rt-multi-thread", "io-util", "io-std
thiserror.workspace = true
[dev-dependencies]
fern = "0.6"
fern = "0.7"

View file

@ -119,6 +119,7 @@ impl Client {
.args(args)
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.stderr(Stdio::piped())
// make sure the process is reaped on drop
.kill_on_drop(true)
.spawn();
@ -128,16 +129,12 @@ impl Client {
// TODO: do we need bufreader/writer here? or do we use async wrappers on unblock?
let writer = BufWriter::new(process.stdin.take().expect("Failed to open stdin"));
let reader = BufReader::new(process.stdout.take().expect("Failed to open stdout"));
let errors = process.stderr.take().map(BufReader::new);
let stderr = BufReader::new(process.stderr.take().expect("Failed to open stderr"));
Self::streams(
Box::new(BufReader::new(reader)),
Box::new(reader),
Box::new(writer),
// errors.map(|errors| Box::new(BufReader::new(errors))),
match errors {
Some(errors) => Some(Box::new(BufReader::new(errors))),
None => None,
},
Some(Box::new(stderr)),
id,
Some(process),
)

View file

@ -3,10 +3,11 @@ mod transport;
mod types;
pub use client::{Client, ConnectionType};
pub use events::Event;
pub use transport::{Payload, Response, Transport};
pub use types::*;
use serde::de::DeserializeOwned;
use thiserror::Error;
#[derive(Error, Debug)]
pub enum Error {
@ -18,9 +19,84 @@ pub enum Error {
Timeout(u64),
#[error("server closed the stream")]
StreamClosed,
#[error("Unhandled")]
Unhandled,
#[error(transparent)]
ExecutableNotFound(#[from] helix_stdx::env::ExecutableNotFoundError),
#[error(transparent)]
Other(#[from] anyhow::Error),
}
pub type Result<T> = core::result::Result<T, Error>;
#[derive(Debug)]
pub enum Request {
RunInTerminal(<requests::RunInTerminal as types::Request>::Arguments),
}
impl Request {
pub fn parse(command: &str, arguments: Option<serde_json::Value>) -> Result<Self> {
use crate::types::Request as _;
let arguments = arguments.unwrap_or_default();
let request = match command {
requests::RunInTerminal::COMMAND => Self::RunInTerminal(parse_value(arguments)?),
_ => return Err(Error::Unhandled),
};
Ok(request)
}
}
#[derive(Debug)]
pub enum Event {
Initialized(<events::Initialized as events::Event>::Body),
Stopped(<events::Stopped as events::Event>::Body),
Continued(<events::Continued as events::Event>::Body),
Exited(<events::Exited as events::Event>::Body),
Terminated(<events::Terminated as events::Event>::Body),
Thread(<events::Thread as events::Event>::Body),
Output(<events::Output as events::Event>::Body),
Breakpoint(<events::Breakpoint as events::Event>::Body),
Module(<events::Module as events::Event>::Body),
LoadedSource(<events::LoadedSource as events::Event>::Body),
Process(<events::Process as events::Event>::Body),
Capabilities(<events::Capabilities as events::Event>::Body),
// ProgressStart(),
// ProgressUpdate(),
// ProgressEnd(),
// Invalidated(),
Memory(<events::Memory as events::Event>::Body),
}
impl Event {
pub fn parse(event: &str, body: Option<serde_json::Value>) -> Result<Self> {
use crate::events::Event as _;
let body = body.unwrap_or_default();
let event = match event {
events::Initialized::EVENT => Self::Initialized(parse_value(body)?),
events::Stopped::EVENT => Self::Stopped(parse_value(body)?),
events::Continued::EVENT => Self::Continued(parse_value(body)?),
events::Exited::EVENT => Self::Exited(parse_value(body)?),
events::Terminated::EVENT => Self::Terminated(parse_value(body)?),
events::Thread::EVENT => Self::Thread(parse_value(body)?),
events::Output::EVENT => Self::Output(parse_value(body)?),
events::Breakpoint::EVENT => Self::Breakpoint(parse_value(body)?),
events::Module::EVENT => Self::Module(parse_value(body)?),
events::LoadedSource::EVENT => Self::LoadedSource(parse_value(body)?),
events::Process::EVENT => Self::Process(parse_value(body)?),
events::Capabilities::EVENT => Self::Capabilities(parse_value(body)?),
events::Memory::EVENT => Self::Memory(parse_value(body)?),
_ => return Err(Error::Unhandled),
};
Ok(event)
}
}
fn parse_value<T>(value: serde_json::Value) -> Result<T>
where
T: DeserializeOwned,
{
serde_json::from_value(value).map_err(|err| err.into())
}

View file

@ -1,4 +1,4 @@
use crate::{Error, Event, Result};
use crate::{Error, Result};
use anyhow::Context;
use log::{error, info, warn};
use serde::{Deserialize, Serialize};
@ -32,11 +32,17 @@ pub struct Response {
pub body: Option<Value>,
}
#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
pub struct Event {
pub event: String,
pub body: Option<Value>,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
#[serde(tag = "type", rename_all = "camelCase")]
pub enum Payload {
// type = "event"
Event(Box<Event>),
Event(Event),
// type = "response"
Response(Response),
// type = "request"
@ -230,25 +236,37 @@ impl Transport {
}
}
async fn recv_inner(
async fn recv(
transport: Arc<Self>,
mut server_stdout: Box<dyn AsyncBufRead + Unpin + Send>,
client_tx: UnboundedSender<Payload>,
) -> Result<()> {
) {
let mut recv_buffer = String::new();
loop {
let msg = Self::recv_server_message(&mut server_stdout, &mut recv_buffer).await?;
transport.process_server_message(&client_tx, msg).await?;
}
}
match Self::recv_server_message(&mut server_stdout, &mut recv_buffer).await {
Ok(msg) => match transport.process_server_message(&client_tx, msg).await {
Ok(_) => (),
Err(err) => {
error!("err: <- {err:?}");
break;
}
},
Err(err) => {
if !matches!(err, Error::StreamClosed) {
error!("Exiting after unexpected error: {err:?}");
}
async fn recv(
transport: Arc<Self>,
server_stdout: Box<dyn AsyncBufRead + Unpin + Send>,
client_tx: UnboundedSender<Payload>,
) {
if let Err(err) = Self::recv_inner(transport, server_stdout, client_tx).await {
error!("err: <- {:?}", err);
// Close any outstanding requests.
for (id, tx) in transport.pending_requests.lock().await.drain() {
match tx.send(Err(Error::StreamClosed)).await {
Ok(_) => (),
Err(_) => {
error!("Could not close request on a closed channel (id={id})");
}
}
}
}
}
}
}

View file

@ -759,33 +759,30 @@ pub mod requests {
pub mod events {
use super::*;
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
#[serde(tag = "event", content = "body")]
// seq is omitted as unused and is not sent by some implementations
pub enum Event {
Initialized(Option<DebuggerCapabilities>),
Stopped(Stopped),
Continued(Continued),
Exited(Exited),
Terminated(Option<Terminated>),
Thread(Thread),
Output(Output),
Breakpoint(Breakpoint),
Module(Module),
LoadedSource(LoadedSource),
Process(Process),
Capabilities(Capabilities),
// ProgressStart(),
// ProgressUpdate(),
// ProgressEnd(),
// Invalidated(),
Memory(Memory),
pub trait Event {
type Body: serde::de::DeserializeOwned + serde::Serialize;
const EVENT: &'static str;
}
#[derive(Debug)]
pub enum Initialized {}
impl Event for Initialized {
type Body = Option<DebuggerCapabilities>;
const EVENT: &'static str = "initialized";
}
#[derive(Debug)]
pub enum Stopped {}
impl Event for Stopped {
type Body = StoppedBody;
const EVENT: &'static str = "stopped";
}
#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct Stopped {
pub struct StoppedBody {
pub reason: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
@ -801,37 +798,77 @@ pub mod events {
pub hit_breakpoint_ids: Option<Vec<usize>>,
}
#[derive(Debug)]
pub enum Continued {}
impl Event for Continued {
type Body = ContinuedBody;
const EVENT: &'static str = "continued";
}
#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct Continued {
pub struct ContinuedBody {
pub thread_id: ThreadId,
#[serde(skip_serializing_if = "Option::is_none")]
pub all_threads_continued: Option<bool>,
}
#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct Exited {
pub exit_code: usize,
#[derive(Debug)]
pub enum Exited {}
impl Event for Exited {
type Body = ExitedBody;
const EVENT: &'static str = "exited";
}
#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct Terminated {
pub struct ExitedBody {
pub exit_code: usize,
}
#[derive(Debug)]
pub enum Terminated {}
impl Event for Terminated {
type Body = Option<TerminatedBody>;
const EVENT: &'static str = "terminated";
}
#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct TerminatedBody {
#[serde(skip_serializing_if = "Option::is_none")]
pub restart: Option<Value>,
}
#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct Thread {
pub reason: String,
pub thread_id: ThreadId,
#[derive(Debug)]
pub enum Thread {}
impl Event for Thread {
type Body = ThreadBody;
const EVENT: &'static str = "thread";
}
#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct Output {
pub struct ThreadBody {
pub reason: String,
pub thread_id: ThreadId,
}
#[derive(Debug)]
pub enum Output {}
impl Event for Output {
type Body = OutputBody;
const EVENT: &'static str = "output";
}
#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct OutputBody {
pub output: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub category: Option<String>,
@ -849,30 +886,62 @@ pub mod events {
pub data: Option<Value>,
}
#[derive(Debug)]
pub enum Breakpoint {}
impl Event for Breakpoint {
type Body = BreakpointBody;
const EVENT: &'static str = "breakpoint";
}
#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct Breakpoint {
pub struct BreakpointBody {
pub reason: String,
pub breakpoint: super::Breakpoint,
}
#[derive(Debug)]
pub enum Module {}
impl Event for Module {
type Body = ModuleBody;
const EVENT: &'static str = "module";
}
#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct Module {
pub struct ModuleBody {
pub reason: String,
pub module: super::Module,
}
#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct LoadedSource {
pub reason: String,
pub source: super::Source,
#[derive(Debug)]
pub enum LoadedSource {}
impl Event for LoadedSource {
type Body = LoadedSourceBody;
const EVENT: &'static str = "loadedSource";
}
#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct Process {
pub struct LoadedSourceBody {
pub reason: String,
pub source: super::Source,
}
#[derive(Debug)]
pub enum Process {}
impl Event for Process {
type Body = ProcessBody;
const EVENT: &'static str = "process";
}
#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct ProcessBody {
pub name: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub system_process_id: Option<usize>,
@ -884,39 +953,55 @@ pub mod events {
pub pointer_size: Option<usize>,
}
#[derive(Debug)]
pub enum Capabilities {}
impl Event for Capabilities {
type Body = CapabilitiesBody;
const EVENT: &'static str = "capabilities";
}
#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct Capabilities {
pub struct CapabilitiesBody {
pub capabilities: super::DebuggerCapabilities,
}
// #[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
// #[serde(rename_all = "camelCase")]
// pub struct Invalidated {
// pub struct InvalidatedBody {
// pub areas: Vec<InvalidatedArea>,
// pub thread_id: Option<ThreadId>,
// pub stack_frame_id: Option<usize>,
// }
#[derive(Debug)]
pub enum Memory {}
impl Event for Memory {
type Body = MemoryBody;
const EVENT: &'static str = "memory";
}
#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct Memory {
pub struct MemoryBody {
pub memory_reference: String,
pub offset: usize,
pub count: usize,
}
#[test]
fn test_deserialize_module_id_from_number() {
let raw = r#"{"id": 0, "name": "Name"}"#;
let module: super::Module = serde_json::from_str(raw).expect("Error!");
assert_eq!(module.id, "0");
}
#[test]
fn test_deserialize_module_id_from_string() {
let raw = r#"{"id": "0", "name": "Name"}"#;
let module: super::Module = serde_json::from_str(raw).expect("Error!");
assert_eq!(module.id, "0");
}
}
#[test]
fn test_deserialize_module_id_from_number() {
let raw = r#"{"id": 0, "name": "Name"}"#;
let module: Module = serde_json::from_str(raw).expect("Error!");
assert_eq!(module.id, "0");
}
#[test]
fn test_deserialize_module_id_from_string() {
let raw = r#"{"id": "0", "name": "Name"}"#;
let module: Module = serde_json::from_str(raw).expect("Error!");
assert_eq!(module.id, "0");
}

View file

@ -12,18 +12,18 @@ homepage.workspace = true
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
ahash = "0.8.11"
hashbrown = "0.14.5"
foldhash.workspace = true
hashbrown = "0.15"
tokio = { version = "1", features = ["rt", "rt-multi-thread", "time", "sync", "parking_lot", "macros"] }
# the event registry is essentially read only but must be an rwlock so we can
# setup new events on initialization, hardware-lock-elision hugely benefits this case
# as it essentially makes the lock entirely free as long as there is no writes
parking_lot = { version = "0.12", features = ["hardware-lock-elision"] }
once_cell = "1.18"
parking_lot = { workspace = true, features = ["hardware-lock-elision"] }
once_cell = "1.21"
anyhow = "1"
log = "0.4"
futures-executor = "0.3.28"
futures-executor = "0.3.31"
[features]
integration_test = []

View file

@ -1,15 +1,18 @@
use std::borrow::Borrow;
use std::future::Future;
use std::sync::atomic::AtomicU64;
use std::sync::atomic::Ordering::Relaxed;
use std::sync::Arc;
pub use oneshot::channel as cancelation;
use tokio::sync::oneshot;
use tokio::sync::Notify;
pub type CancelTx = oneshot::Sender<()>;
pub type CancelRx = oneshot::Receiver<()>;
pub async fn cancelable_future<T>(future: impl Future<Output = T>, cancel: CancelRx) -> Option<T> {
pub async fn cancelable_future<T>(
future: impl Future<Output = T>,
cancel: impl Borrow<TaskHandle>,
) -> Option<T> {
tokio::select! {
biased;
_ = cancel => {
_ = cancel.borrow().canceled() => {
None
}
res = future => {
@ -17,3 +20,268 @@ pub async fn cancelable_future<T>(future: impl Future<Output = T>, cancel: Cance
}
}
}
#[derive(Default, Debug)]
struct Shared {
state: AtomicU64,
// `Notify` has some features that we don't really need here because it
// supports waking single tasks (`notify_one`) and does its own (more
// complicated) state tracking, we could reimplement the waiter linked list
// with modest effort and reduce memory consumption by one word/8 bytes and
// reduce code complexity/number of atomic operations.
//
// I don't think that's worth the complexity (unsafe code).
//
// if we only cared about async code then we could also only use a notify
// (without the generation count), this would be equivalent (or maybe more
// correct if we want to allow cloning the TX) but it would be extremly slow
// to frequently check for cancelation from sync code
notify: Notify,
}
impl Shared {
fn generation(&self) -> u32 {
self.state.load(Relaxed) as u32
}
fn num_running(&self) -> u32 {
(self.state.load(Relaxed) >> 32) as u32
}
/// Increments the generation count and sets `num_running`
/// to the provided value, this operation is not with
/// regard to the generation counter (doesn't use `fetch_add`)
/// so the calling code must ensure it cannot execute concurrently
/// to maintain correctness (but not safety)
fn inc_generation(&self, num_running: u32) -> (u32, u32) {
let state = self.state.load(Relaxed);
let generation = state as u32;
let prev_running = (state >> 32) as u32;
// no need to create a new generation if the refcount is zero (fastpath)
if prev_running == 0 && num_running == 0 {
return (generation, 0);
}
let new_generation = generation.saturating_add(1);
self.state.store(
new_generation as u64 | ((num_running as u64) << 32),
Relaxed,
);
self.notify.notify_waiters();
(new_generation, prev_running)
}
fn inc_running(&self, generation: u32) {
let mut state = self.state.load(Relaxed);
loop {
let current_generation = state as u32;
if current_generation != generation {
break;
}
let off = 1 << 32;
let res = self.state.compare_exchange_weak(
state,
state.saturating_add(off),
Relaxed,
Relaxed,
);
match res {
Ok(_) => break,
Err(new_state) => state = new_state,
}
}
}
fn dec_running(&self, generation: u32) {
let mut state = self.state.load(Relaxed);
loop {
let current_generation = state as u32;
if current_generation != generation {
break;
}
let num_running = (state >> 32) as u32;
// running can't be zero here, that would mean we miscounted somewhere
assert_ne!(num_running, 0);
let off = 1 << 32;
let res = self
.state
.compare_exchange_weak(state, state - off, Relaxed, Relaxed);
match res {
Ok(_) => break,
Err(new_state) => state = new_state,
}
}
}
}
// This intentionally doesn't implement `Clone` and requires a mutable reference
// for cancelation to avoid races (in inc_generation).
/// A task controller allows managing a single subtask enabling the controller
/// to cancel the subtask and to check whether it is still running.
///
/// For efficiency reasons the controller can be reused/restarted,
/// in that case the previous task is automatically canceled.
///
/// If the controller is dropped, the subtasks are automatically canceled.
#[derive(Default, Debug)]
pub struct TaskController {
shared: Arc<Shared>,
}
impl TaskController {
pub fn new() -> Self {
TaskController::default()
}
/// Cancels the active task (handle).
///
/// Returns whether any tasks were still running before the cancelation.
pub fn cancel(&mut self) -> bool {
self.shared.inc_generation(0).1 != 0
}
/// Checks whether there are any task handles
/// that haven't been dropped (or canceled) yet.
pub fn is_running(&self) -> bool {
self.shared.num_running() != 0
}
/// Starts a new task and cancels the previous task (handles).
pub fn restart(&mut self) -> TaskHandle {
TaskHandle {
generation: self.shared.inc_generation(1).0,
shared: self.shared.clone(),
}
}
}
impl Drop for TaskController {
fn drop(&mut self) {
self.cancel();
}
}
/// A handle that is used to link a task with a task controller.
///
/// It can be used to cancel async futures very efficiently but can also be checked for
/// cancelation very quickly (single atomic read) in blocking code.
/// The handle can be cheaply cloned (reference counted).
///
/// The TaskController can check whether a task is "running" by inspecting the
/// refcount of the (current) tasks handles. Therefore, if that information
/// is important, ensure that the handle is not dropped until the task fully
/// completes.
pub struct TaskHandle {
shared: Arc<Shared>,
generation: u32,
}
impl Clone for TaskHandle {
fn clone(&self) -> Self {
self.shared.inc_running(self.generation);
TaskHandle {
shared: self.shared.clone(),
generation: self.generation,
}
}
}
impl Drop for TaskHandle {
fn drop(&mut self) {
self.shared.dec_running(self.generation);
}
}
impl TaskHandle {
/// Waits until [`TaskController::cancel`] is called for the corresponding
/// [`TaskController`]. Immediately returns if `cancel` was already called since
pub async fn canceled(&self) {
let notified = self.shared.notify.notified();
if !self.is_canceled() {
notified.await
}
}
pub fn is_canceled(&self) -> bool {
self.generation != self.shared.generation()
}
}
#[cfg(test)]
mod tests {
use std::future::poll_fn;
use futures_executor::block_on;
use tokio::task::yield_now;
use crate::{cancelable_future, TaskController};
#[test]
fn immediate_cancel() {
let mut controller = TaskController::new();
let handle = controller.restart();
controller.cancel();
assert!(handle.is_canceled());
controller.restart();
assert!(handle.is_canceled());
let res = block_on(cancelable_future(
poll_fn(|_cx| std::task::Poll::Ready(())),
handle,
));
assert!(res.is_none());
}
#[test]
fn running_count() {
let mut controller = TaskController::new();
let handle = controller.restart();
assert!(controller.is_running());
assert!(!handle.is_canceled());
drop(handle);
assert!(!controller.is_running());
assert!(!controller.cancel());
let handle = controller.restart();
assert!(!handle.is_canceled());
assert!(controller.is_running());
let handle2 = handle.clone();
assert!(!handle.is_canceled());
assert!(controller.is_running());
drop(handle2);
assert!(!handle.is_canceled());
assert!(controller.is_running());
assert!(controller.cancel());
assert!(handle.is_canceled());
assert!(!controller.is_running());
}
#[test]
fn no_cancel() {
let mut controller = TaskController::new();
let handle = controller.restart();
assert!(!handle.is_canceled());
let res = block_on(cancelable_future(
poll_fn(|_cx| std::task::Poll::Ready(())),
handle,
));
assert!(res.is_some());
}
#[test]
fn delayed_cancel() {
let mut controller = TaskController::new();
let handle = controller.restart();
let mut hit = false;
let res = block_on(cancelable_future(
async {
controller.cancel();
hit = true;
yield_now().await;
},
handle,
));
assert!(res.is_none());
assert!(hit);
}
}

View file

@ -28,7 +28,10 @@ pub trait AsyncHook: Sync + Send + 'static + Sized {
// so it should only be reached in case of total CPU overload.
// However, a bounded channel is much more efficient so it's nice to use here
let (tx, rx) = mpsc::channel(128);
tokio::spawn(run(self, rx));
// only spawn worker if we are inside runtime to avoid having to spawn a runtime for unrelated unit tests
if tokio::runtime::Handle::try_current().is_ok() {
tokio::spawn(run(self, rx));
}
tx
}
}

View file

@ -32,9 +32,11 @@
//! to helix-view in the future if we manage to detach the compositor from its rendering backend.
use anyhow::Result;
pub use cancel::{cancelable_future, cancelation, CancelRx, CancelTx};
pub use cancel::{cancelable_future, TaskController, TaskHandle};
pub use debounce::{send_blocking, AsyncHook};
pub use redraw::{lock_frame, redraw_requested, request_redraw, start_frame, RenderLockGuard};
pub use redraw::{
lock_frame, redraw_requested, request_redraw, start_frame, RenderLockGuard, RequestRedrawOnDrop,
};
pub use registry::Event;
mod cancel;

View file

@ -51,3 +51,12 @@ pub fn start_frame() {
pub fn lock_frame() -> RenderLockGuard {
RENDER_LOCK.read()
}
/// A zero sized type that requests a redraw via [request_redraw] when the type [Drop]s.
pub struct RequestRedrawOnDrop;
impl Drop for RequestRedrawOnDrop {
fn drop(&mut self) {
request_redraw();
}
}

View file

@ -14,8 +14,8 @@ use crate::hook::ErasedHook;
use crate::runtime_local;
pub struct Registry {
events: HashMap<&'static str, TypeId, ahash::RandomState>,
handlers: HashMap<&'static str, Vec<ErasedHook>, ahash::RandomState>,
events: HashMap<&'static str, TypeId, foldhash::fast::FixedState>,
handlers: HashMap<&'static str, Vec<ErasedHook>, foldhash::fast::FixedState>,
}
impl Registry {
@ -105,8 +105,8 @@ runtime_local! {
static REGISTRY: RwLock<Registry> = RwLock::new(Registry {
// hardcoded random number is good enough here we don't care about DOS resistance
// and avoids the additional complexity of `Option<Registry>`
events: HashMap::with_hasher(ahash::RandomState::with_seeds(423, 9978, 38322, 3280080)),
handlers: HashMap::with_hasher(ahash::RandomState::with_seeds(423, 99078, 382322, 3282938)),
events: HashMap::with_hasher(foldhash::fast::FixedState::with_seed(72536814787)),
handlers: HashMap::with_hasher(foldhash::fast::FixedState::with_seed(72536814787)),
});
}

View file

@ -41,8 +41,9 @@ macro_rules! runtime_local {
#[cfg(feature = "integration_test")]
pub struct RuntimeLocal<T: 'static> {
data:
parking_lot::RwLock<hashbrown::HashMap<tokio::runtime::Id, &'static T, ahash::RandomState>>,
data: parking_lot::RwLock<
hashbrown::HashMap<tokio::runtime::Id, &'static T, foldhash::fast::FixedState>,
>,
init: fn() -> T,
}
@ -53,7 +54,7 @@ impl<T> RuntimeLocal<T> {
pub const fn __new(init: fn() -> T) -> Self {
Self {
data: parking_lot::RwLock::new(hashbrown::HashMap::with_hasher(
ahash::RandomState::with_seeds(423, 9978, 38322, 3280080),
foldhash::fast::FixedState::with_seed(12345678910),
)),
init,
}

View file

@ -20,9 +20,9 @@ helix-stdx = { path = "../helix-stdx" }
anyhow = "1"
serde = { version = "1.0", features = ["derive"] }
toml = "0.8"
etcetera = "0.8"
etcetera = "0.10"
tree-sitter.workspace = true
once_cell = "1.19"
once_cell = "1.21"
log = "0.4"
# TODO: these two should be on !wasm32 only
@ -30,8 +30,7 @@ log = "0.4"
# cloning/compiling tree-sitter grammars
cc = { version = "1" }
threadpool = { version = "1.0" }
tempfile = "3.10.1"
dunce = "1.0.4"
tempfile.workspace = true
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
libloading = "0.8"

View file

@ -6,23 +6,26 @@ const MAJOR: &str = env!("CARGO_PKG_VERSION_MAJOR");
const MINOR: &str = env!("CARGO_PKG_VERSION_MINOR");
const PATCH: &str = env!("CARGO_PKG_VERSION_PATCH");
fn get_calver() -> String {
if PATCH == "0" {
format!("{MAJOR}.{MINOR}")
} else {
format!("{MAJOR}.{MINOR}.{PATCH}")
}
}
fn main() {
let git_hash = Command::new("git")
.args(["rev-parse", "HEAD"])
.output()
.ok()
.filter(|output| output.status.success())
.and_then(|x| String::from_utf8(x.stdout).ok());
.and_then(|x| String::from_utf8(x.stdout).ok())
.or_else(|| option_env!("HELIX_NIX_BUILD_REV").map(|s| s.to_string()));
let calver = get_calver();
let minor = if MINOR.len() == 1 {
// Print single-digit months in '0M' format
format!("0{MINOR}")
} else {
MINOR.to_string()
};
let calver = if PATCH == "0" {
format!("{MAJOR}.{minor}")
} else {
format!("{MAJOR}.{minor}.{PATCH}")
};
let version: Cow<_> = match &git_hash {
Some(git_hash) => format!("{} ({})", calver, &git_hash[..8]).into(),
None => calver.into(),

View file

@ -273,12 +273,12 @@ fn fetch_grammar(grammar: GrammarConfiguration) -> Result<FetchStatus> {
}
// ensure the remote matches the configured remote
if get_remote_url(&grammar_dir).map_or(true, |s| s != remote) {
if get_remote_url(&grammar_dir).as_ref() != Some(&remote) {
set_remote(&grammar_dir, &remote)?;
}
// ensure the revision matches the configured revision
if get_revision(&grammar_dir).map_or(true, |s| s != revision) {
if get_revision(&grammar_dir).as_ref() != Some(&revision) {
// Fetch the exact revision from the remote.
// Supported by server-side git since v2.5.0 (July 2015),
// enabled by default on major git hosts.
@ -496,9 +496,11 @@ fn build_tree_sitter_library(
.arg("/link")
.arg(format!("/out:{}", library_path.to_str().unwrap()));
} else {
#[cfg(not(windows))]
command.arg("-fPIC");
command
.arg("-shared")
.arg("-fPIC")
.arg("-fno-exceptions")
.arg("-I")
.arg(header_path)
@ -517,8 +519,11 @@ fn build_tree_sitter_library(
cpp_command.args(compiler.args());
let object_file =
library_path.with_file_name(format!("{}_scanner.o", &grammar.grammar_id));
#[cfg(not(windows))]
cpp_command.arg("-fPIC");
cpp_command
.arg("-fPIC")
.arg("-fno-exceptions")
.arg("-I")
.arg(header_path)
@ -592,6 +597,6 @@ fn mtime(path: &Path) -> Result<SystemTime> {
/// Gives the contents of a file from a language's `runtime/queries/<lang>`
/// directory
pub fn load_runtime_file(language: &str, filename: &str) -> Result<String, std::io::Error> {
let path = crate::runtime_file(&PathBuf::new().join("queries").join(language).join(filename));
let path = crate::runtime_file(PathBuf::new().join("queries").join(language).join(filename));
std::fs::read_to_string(path)
}

View file

@ -107,8 +107,8 @@ fn find_runtime_file(rel_path: &Path) -> Option<PathBuf> {
/// The valid runtime directories are searched in priority order and the first
/// file found to exist is returned, otherwise the path to the final attempt
/// that failed.
pub fn runtime_file(rel_path: &Path) -> PathBuf {
find_runtime_file(rel_path).unwrap_or_else(|| {
pub fn runtime_file(rel_path: impl AsRef<Path>) -> PathBuf {
find_runtime_file(rel_path.as_ref()).unwrap_or_else(|| {
RUNTIME_DIRS
.last()
.map(|dir| dir.join(rel_path))
@ -225,13 +225,17 @@ pub fn merge_toml_values(left: toml::Value, right: toml::Value, merge_depth: usi
/// Used as a ceiling dir for LSP root resolution, the filepicker and potentially as a future filewatching root
///
/// This function starts searching the FS upward from the CWD
/// and returns the first directory that contains either `.git` or `.helix`.
/// and returns the first directory that contains either `.git`, `.svn`, `.jj` or `.helix`.
/// If no workspace was found returns (CWD, true).
/// Otherwise (workspace, false) is returned
pub fn find_workspace() -> (PathBuf, bool) {
let current_dir = current_working_dir();
for ancestor in current_dir.ancestors() {
if ancestor.join(".git").exists() || ancestor.join(".helix").exists() {
if ancestor.join(".git").exists()
|| ancestor.join(".svn").exists()
|| ancestor.join(".jj").exists()
|| ancestor.join(".helix").exists()
{
return (ancestor.to_owned(), false);
}
}

View file

@ -0,0 +1,33 @@
[package]
name = "helix-lsp-types"
version = "0.95.1"
authors = [
# Original authors
"Markus Westerlind <marwes91@gmail.com>",
"Bruno Medeiros <bruno.do.medeiros@gmail.com>",
# Since forking
"Helix contributors"
]
edition = "2018"
description = "Types for interaction with a language server, using VSCode's Language Server Protocol"
repository = "https://github.com/gluon-lang/lsp-types"
documentation = "https://docs.rs/lsp-types"
readme = "README.md"
keywords = ["language", "server", "lsp", "vscode", "lsif"]
license = "MIT"
[dependencies]
bitflags.workspace = true
serde = { version = "1.0.219", features = ["derive"] }
serde_json = "1.0.140"
url = {version = "2.5.4", features = ["serde"]}
[features]
default = []
# Enables proposed LSP extensions.
# NOTE: No semver compatibility is guaranteed for types enabled by this feature.
proposed = []

22
helix-lsp-types/LICENSE Normal file
View file

@ -0,0 +1,22 @@
The MIT License (MIT)
Copyright (c) 2016 Markus Westerlind
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View file

@ -0,0 +1,3 @@
# Helix's `lsp-types`
This is a fork of the [`lsp-types`](https://crates.io/crates/lsp-types) crate ([`gluon-lang/lsp-types`](https://github.com/gluon-lang/lsp-types)) taken at version v0.95.1 (commit [3e6daee](https://github.com/gluon-lang/lsp-types/commit/3e6daee771d14db4094a554b8d03e29c310dfcbe)). This fork focuses usability improvements that make the types easier to work with for the Helix codebase. For example the URL type - the `uri` crate at this version of `lsp-types` - will be replaced with a wrapper around a string.

View file

@ -0,0 +1,126 @@
use serde::{Deserialize, Serialize};
use serde_json::Value;
use crate::{
DynamicRegistrationClientCapabilities, PartialResultParams, Range, SymbolKind, SymbolTag,
TextDocumentPositionParams, Url, WorkDoneProgressOptions, WorkDoneProgressParams,
};
pub type CallHierarchyClientCapabilities = DynamicRegistrationClientCapabilities;
#[derive(Debug, Eq, PartialEq, Clone, Default, Deserialize, Serialize, Copy)]
#[serde(rename_all = "camelCase")]
pub struct CallHierarchyOptions {
#[serde(flatten)]
pub work_done_progress_options: WorkDoneProgressOptions,
}
#[derive(Debug, Eq, PartialEq, Clone, Deserialize, Serialize, Copy)]
#[serde(untagged)]
pub enum CallHierarchyServerCapability {
Simple(bool),
Options(CallHierarchyOptions),
}
impl From<CallHierarchyOptions> for CallHierarchyServerCapability {
fn from(from: CallHierarchyOptions) -> Self {
Self::Options(from)
}
}
impl From<bool> for CallHierarchyServerCapability {
fn from(from: bool) -> Self {
Self::Simple(from)
}
}
#[derive(Debug, Eq, PartialEq, Clone, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct CallHierarchyPrepareParams {
#[serde(flatten)]
pub text_document_position_params: TextDocumentPositionParams,
#[serde(flatten)]
pub work_done_progress_params: WorkDoneProgressParams,
}
#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
#[serde(rename_all = "camelCase")]
pub struct CallHierarchyItem {
/// The name of this item.
pub name: String,
/// The kind of this item.
pub kind: SymbolKind,
/// Tags for this item.
#[serde(skip_serializing_if = "Option::is_none")]
pub tags: Option<Vec<SymbolTag>>,
/// More detail for this item, e.g. the signature of a function.
#[serde(skip_serializing_if = "Option::is_none")]
pub detail: Option<String>,
/// The resource identifier of this item.
pub uri: Url,
/// The range enclosing this symbol not including leading/trailing whitespace but everything else, e.g. comments and code.
pub range: Range,
/// The range that should be selected and revealed when this symbol is being picked, e.g. the name of a function.
/// Must be contained by the [`range`](#CallHierarchyItem.range).
pub selection_range: Range,
/// A data entry field that is preserved between a call hierarchy prepare and incoming calls or outgoing calls requests.
#[serde(skip_serializing_if = "Option::is_none")]
pub data: Option<Value>,
}
#[derive(Debug, PartialEq, Clone, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct CallHierarchyIncomingCallsParams {
pub item: CallHierarchyItem,
#[serde(flatten)]
pub work_done_progress_params: WorkDoneProgressParams,
#[serde(flatten)]
pub partial_result_params: PartialResultParams,
}
/// Represents an incoming call, e.g. a caller of a method or constructor.
#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
#[serde(rename_all = "camelCase")]
pub struct CallHierarchyIncomingCall {
/// The item that makes the call.
pub from: CallHierarchyItem,
/// The range at which at which the calls appears. This is relative to the caller
/// denoted by [`this.from`](#CallHierarchyIncomingCall.from).
pub from_ranges: Vec<Range>,
}
#[derive(Debug, PartialEq, Clone, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct CallHierarchyOutgoingCallsParams {
pub item: CallHierarchyItem,
#[serde(flatten)]
pub work_done_progress_params: WorkDoneProgressParams,
#[serde(flatten)]
pub partial_result_params: PartialResultParams,
}
/// Represents an outgoing call, e.g. calling a getter from a method or a method from a constructor etc.
#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
#[serde(rename_all = "camelCase")]
pub struct CallHierarchyOutgoingCall {
/// The item that is called.
pub to: CallHierarchyItem,
/// The range at which this item is called. This is the range relative to the caller, e.g the item
/// passed to [`provideCallHierarchyOutgoingCalls`](#CallHierarchyItemProvider.provideCallHierarchyOutgoingCalls)
/// and not [`this.to`](#CallHierarchyOutgoingCall.to).
pub from_ranges: Vec<Range>,
}

View file

@ -0,0 +1,395 @@
use crate::{
Command, Diagnostic, PartialResultParams, Range, TextDocumentIdentifier,
WorkDoneProgressOptions, WorkDoneProgressParams, WorkspaceEdit,
};
use serde::{Deserialize, Serialize};
use serde_json::Value;
use std::borrow::Cow;
#[derive(Debug, Eq, PartialEq, Clone, Deserialize, Serialize)]
#[serde(untagged)]
pub enum CodeActionProviderCapability {
Simple(bool),
Options(CodeActionOptions),
}
impl From<CodeActionOptions> for CodeActionProviderCapability {
fn from(from: CodeActionOptions) -> Self {
Self::Options(from)
}
}
impl From<bool> for CodeActionProviderCapability {
fn from(from: bool) -> Self {
Self::Simple(from)
}
}
#[derive(Debug, Eq, PartialEq, Clone, Default, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct CodeActionClientCapabilities {
///
/// This capability supports dynamic registration.
///
#[serde(skip_serializing_if = "Option::is_none")]
pub dynamic_registration: Option<bool>,
/// The client support code action literals as a valid
/// response of the `textDocument/codeAction` request.
#[serde(skip_serializing_if = "Option::is_none")]
pub code_action_literal_support: Option<CodeActionLiteralSupport>,
/// Whether code action supports the `isPreferred` property.
///
/// @since 3.15.0
#[serde(skip_serializing_if = "Option::is_none")]
pub is_preferred_support: Option<bool>,
/// Whether code action supports the `disabled` property.
///
/// @since 3.16.0
#[serde(skip_serializing_if = "Option::is_none")]
pub disabled_support: Option<bool>,
/// Whether code action supports the `data` property which is
/// preserved between a `textDocument/codeAction` and a
/// `codeAction/resolve` request.
///
/// @since 3.16.0
#[serde(skip_serializing_if = "Option::is_none")]
pub data_support: Option<bool>,
/// Whether the client supports resolving additional code action
/// properties via a separate `codeAction/resolve` request.
///
/// @since 3.16.0
#[serde(skip_serializing_if = "Option::is_none")]
pub resolve_support: Option<CodeActionCapabilityResolveSupport>,
/// Whether the client honors the change annotations in
/// text edits and resource operations returned via the
/// `CodeAction#edit` property by for example presenting
/// the workspace edit in the user interface and asking
/// for confirmation.
///
/// @since 3.16.0
#[serde(skip_serializing_if = "Option::is_none")]
pub honors_change_annotations: Option<bool>,
}
/// Whether the client supports resolving additional code action
/// properties via a separate `codeAction/resolve` request.
///
/// @since 3.16.0
#[derive(Debug, Eq, PartialEq, Clone, Default, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct CodeActionCapabilityResolveSupport {
/// The properties that a client can resolve lazily.
pub properties: Vec<String>,
}
#[derive(Debug, Eq, PartialEq, Clone, Default, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct CodeActionLiteralSupport {
/// The code action kind is support with the following value set.
pub code_action_kind: CodeActionKindLiteralSupport,
}
#[derive(Debug, Eq, PartialEq, Clone, Default, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct CodeActionKindLiteralSupport {
/// The code action kind values the client supports. When this
/// property exists the client also guarantees that it will
/// handle values outside its set gracefully and falls back
/// to a default value when unknown.
pub value_set: Vec<String>,
}
/// Params for the CodeActionRequest
#[derive(Debug, Eq, PartialEq, Clone, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct CodeActionParams {
/// The document in which the command was invoked.
pub text_document: TextDocumentIdentifier,
/// The range for which the command was invoked.
pub range: Range,
/// Context carrying additional information.
pub context: CodeActionContext,
#[serde(flatten)]
pub work_done_progress_params: WorkDoneProgressParams,
#[serde(flatten)]
pub partial_result_params: PartialResultParams,
}
/// response for CodeActionRequest
pub type CodeActionResponse = Vec<CodeActionOrCommand>;
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
#[serde(untagged)]
pub enum CodeActionOrCommand {
Command(Command),
CodeAction(CodeAction),
}
impl From<Command> for CodeActionOrCommand {
fn from(command: Command) -> Self {
CodeActionOrCommand::Command(command)
}
}
impl From<CodeAction> for CodeActionOrCommand {
fn from(action: CodeAction) -> Self {
CodeActionOrCommand::CodeAction(action)
}
}
#[derive(Debug, Eq, PartialEq, Hash, PartialOrd, Clone, Deserialize, Serialize)]
pub struct CodeActionKind(Cow<'static, str>);
impl CodeActionKind {
/// Empty kind.
pub const EMPTY: CodeActionKind = CodeActionKind::new("");
/// Base kind for quickfix actions: 'quickfix'
pub const QUICKFIX: CodeActionKind = CodeActionKind::new("quickfix");
/// Base kind for refactoring actions: 'refactor'
pub const REFACTOR: CodeActionKind = CodeActionKind::new("refactor");
/// Base kind for refactoring extraction actions: 'refactor.extract'
///
/// Example extract actions:
///
/// - Extract method
/// - Extract function
/// - Extract variable
/// - Extract interface from class
/// - ...
pub const REFACTOR_EXTRACT: CodeActionKind = CodeActionKind::new("refactor.extract");
/// Base kind for refactoring inline actions: 'refactor.inline'
///
/// Example inline actions:
///
/// - Inline function
/// - Inline variable
/// - Inline constant
/// - ...
pub const REFACTOR_INLINE: CodeActionKind = CodeActionKind::new("refactor.inline");
/// Base kind for refactoring rewrite actions: 'refactor.rewrite'
///
/// Example rewrite actions:
///
/// - Convert JavaScript function to class
/// - Add or remove parameter
/// - Encapsulate field
/// - Make method static
/// - Move method to base class
/// - ...
pub const REFACTOR_REWRITE: CodeActionKind = CodeActionKind::new("refactor.rewrite");
/// Base kind for source actions: `source`
///
/// Source code actions apply to the entire file.
pub const SOURCE: CodeActionKind = CodeActionKind::new("source");
/// Base kind for an organize imports source action: `source.organizeImports`
pub const SOURCE_ORGANIZE_IMPORTS: CodeActionKind =
CodeActionKind::new("source.organizeImports");
/// Base kind for a 'fix all' source action: `source.fixAll`.
///
/// 'Fix all' actions automatically fix errors that have a clear fix that
/// do not require user input. They should not suppress errors or perform
/// unsafe fixes such as generating new types or classes.
///
/// @since 3.17.0
pub const SOURCE_FIX_ALL: CodeActionKind = CodeActionKind::new("source.fixAll");
pub const fn new(tag: &'static str) -> Self {
CodeActionKind(Cow::Borrowed(tag))
}
pub fn as_str(&self) -> &str {
&self.0
}
}
impl From<String> for CodeActionKind {
fn from(from: String) -> Self {
CodeActionKind(Cow::from(from))
}
}
impl From<&'static str> for CodeActionKind {
fn from(from: &'static str) -> Self {
CodeActionKind::new(from)
}
}
#[derive(Debug, PartialEq, Clone, Default, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct CodeAction {
/// A short, human-readable, title for this code action.
pub title: String,
/// The kind of the code action.
/// Used to filter code actions.
#[serde(skip_serializing_if = "Option::is_none")]
pub kind: Option<CodeActionKind>,
/// The diagnostics that this code action resolves.
#[serde(skip_serializing_if = "Option::is_none")]
pub diagnostics: Option<Vec<Diagnostic>>,
/// The workspace edit this code action performs.
#[serde(skip_serializing_if = "Option::is_none")]
pub edit: Option<WorkspaceEdit>,
/// A command this code action executes. If a code action
/// provides an edit and a command, first the edit is
/// executed and then the command.
#[serde(skip_serializing_if = "Option::is_none")]
pub command: Option<Command>,
/// Marks this as a preferred action. Preferred actions are used by the `auto fix` command and can be targeted
/// by keybindings.
/// A quick fix should be marked preferred if it properly addresses the underlying error.
/// A refactoring should be marked preferred if it is the most reasonable choice of actions to take.
///
/// @since 3.15.0
#[serde(skip_serializing_if = "Option::is_none")]
pub is_preferred: Option<bool>,
/// Marks that the code action cannot currently be applied.
///
/// Clients should follow the following guidelines regarding disabled code actions:
///
/// - Disabled code actions are not shown in automatic
/// [lightbulb](https://code.visualstudio.com/docs/editor/editingevolved#_code-action)
/// code action menu.
///
/// - Disabled actions are shown as faded out in the code action menu when the user request
/// a more specific type of code action, such as refactorings.
///
/// - If the user has a keybinding that auto applies a code action and only a disabled code
/// actions are returned, the client should show the user an error message with `reason`
/// in the editor.
///
/// @since 3.16.0
#[serde(skip_serializing_if = "Option::is_none")]
pub disabled: Option<CodeActionDisabled>,
/// A data entry field that is preserved on a code action between
/// a `textDocument/codeAction` and a `codeAction/resolve` request.
///
/// @since 3.16.0
#[serde(skip_serializing_if = "Option::is_none")]
pub data: Option<Value>,
}
#[derive(Debug, Eq, PartialEq, Clone, Default, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct CodeActionDisabled {
/// Human readable description of why the code action is currently disabled.
///
/// This is displayed in the code actions UI.
pub reason: String,
}
/// The reason why code actions were requested.
///
/// @since 3.17.0
#[derive(Eq, PartialEq, Clone, Copy, Deserialize, Serialize)]
#[serde(transparent)]
pub struct CodeActionTriggerKind(i32);
lsp_enum! {
impl CodeActionTriggerKind {
/// Code actions were explicitly requested by the user or by an extension.
pub const INVOKED: CodeActionTriggerKind = CodeActionTriggerKind(1);
/// Code actions were requested automatically.
///
/// This typically happens when current selection in a file changes, but can
/// also be triggered when file content changes.
pub const AUTOMATIC: CodeActionTriggerKind = CodeActionTriggerKind(2);
}
}
/// Contains additional diagnostic information about the context in which
/// a code action is run.
#[derive(Debug, Eq, PartialEq, Clone, Default, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct CodeActionContext {
/// An array of diagnostics.
pub diagnostics: Vec<Diagnostic>,
/// Requested kind of actions to return.
///
/// Actions not of this kind are filtered out by the client before being shown. So servers
/// can omit computing them.
#[serde(skip_serializing_if = "Option::is_none")]
pub only: Option<Vec<CodeActionKind>>,
/// The reason why code actions were requested.
///
/// @since 3.17.0
#[serde(skip_serializing_if = "Option::is_none")]
pub trigger_kind: Option<CodeActionTriggerKind>,
}
#[derive(Debug, Eq, PartialEq, Clone, Deserialize, Serialize, Default)]
#[serde(rename_all = "camelCase")]
pub struct CodeActionOptions {
/// CodeActionKinds that this server may return.
///
/// The list of kinds may be generic, such as `CodeActionKind.Refactor`, or the server
/// may list out every specific kind they provide.
#[serde(skip_serializing_if = "Option::is_none")]
pub code_action_kinds: Option<Vec<CodeActionKind>>,
#[serde(flatten)]
pub work_done_progress_options: WorkDoneProgressOptions,
/// The server provides support to resolve additional
/// information for a code action.
///
/// @since 3.16.0
#[serde(skip_serializing_if = "Option::is_none")]
pub resolve_provider: Option<bool>,
}
#[cfg(test)]
mod tests {
use super::*;
use crate::tests::test_serialization;
#[test]
fn test_code_action_response() {
test_serialization(
&vec![
CodeActionOrCommand::Command(Command {
title: "title".to_string(),
command: "command".to_string(),
arguments: None,
}),
CodeActionOrCommand::CodeAction(CodeAction {
title: "title".to_string(),
kind: Some(CodeActionKind::QUICKFIX),
command: None,
diagnostics: None,
edit: None,
is_preferred: None,
..CodeAction::default()
}),
],
r#"[{"title":"title","command":"command"},{"title":"title","kind":"quickfix"}]"#,
)
}
}

View file

@ -0,0 +1,66 @@
use serde::{Deserialize, Serialize};
use serde_json::Value;
use crate::{
Command, DynamicRegistrationClientCapabilities, PartialResultParams, Range,
TextDocumentIdentifier, WorkDoneProgressParams,
};
pub type CodeLensClientCapabilities = DynamicRegistrationClientCapabilities;
/// Code Lens options.
#[derive(Debug, Eq, PartialEq, Clone, Deserialize, Serialize, Copy)]
#[serde(rename_all = "camelCase")]
pub struct CodeLensOptions {
/// Code lens has a resolve provider as well.
#[serde(skip_serializing_if = "Option::is_none")]
pub resolve_provider: Option<bool>,
}
#[derive(Debug, Eq, PartialEq, Clone, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct CodeLensParams {
/// The document to request code lens for.
pub text_document: TextDocumentIdentifier,
#[serde(flatten)]
pub work_done_progress_params: WorkDoneProgressParams,
#[serde(flatten)]
pub partial_result_params: PartialResultParams,
}
/// A code lens represents a command that should be shown along with
/// source text, like the number of references, a way to run tests, etc.
///
/// A code lens is _unresolved_ when no command is associated to it. For performance
/// reasons the creation of a code lens and resolving should be done in two stages.
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct CodeLens {
/// The range in which this code lens is valid. Should only span a single line.
pub range: Range,
/// The command this code lens represents.
#[serde(skip_serializing_if = "Option::is_none")]
pub command: Option<Command>,
/// A data entry field that is preserved on a code lens item between
/// a code lens and a code lens resolve request.
#[serde(skip_serializing_if = "Option::is_none")]
pub data: Option<Value>,
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct CodeLensWorkspaceClientCapabilities {
/// Whether the client implementation supports a refresh request sent from the
/// server to the client.
///
/// Note that this event is global and will force the client to refresh all
/// code lenses currently shown. It should be used with absolute care and is
/// useful for situation where a server for example detect a project wide
/// change that requires such a calculation.
#[serde(skip_serializing_if = "Option::is_none")]
pub refresh_support: Option<bool>,
}

Some files were not shown because too many files have changed in this diff Show more