Merge branch 'master' into debug
This commit is contained in:
commit
f2b709a3c3
112 changed files with 2894 additions and 1188 deletions
18
.github/workflows/build.yml
vendored
18
.github/workflows/build.yml
vendored
|
@ -28,19 +28,19 @@ jobs:
|
||||||
uses: actions/cache@v2.1.6
|
uses: actions/cache@v2.1.6
|
||||||
with:
|
with:
|
||||||
path: ~/.cargo/registry
|
path: ~/.cargo/registry
|
||||||
key: ${{ runner.os }}-v1-cargo-registry-${{ hashFiles('**/Cargo.lock') }}
|
key: ${{ runner.os }}-v2-cargo-registry-${{ hashFiles('**/Cargo.lock') }}
|
||||||
|
|
||||||
- name: Cache cargo index
|
- name: Cache cargo index
|
||||||
uses: actions/cache@v2.1.6
|
uses: actions/cache@v2.1.6
|
||||||
with:
|
with:
|
||||||
path: ~/.cargo/git
|
path: ~/.cargo/git
|
||||||
key: ${{ runner.os }}-v1-cargo-index-${{ hashFiles('**/Cargo.lock') }}
|
key: ${{ runner.os }}-v2-cargo-index-${{ hashFiles('**/Cargo.lock') }}
|
||||||
|
|
||||||
- name: Cache cargo target dir
|
- name: Cache cargo target dir
|
||||||
uses: actions/cache@v2.1.6
|
uses: actions/cache@v2.1.6
|
||||||
with:
|
with:
|
||||||
path: target
|
path: target
|
||||||
key: ${{ runner.os }}-v1-cargo-build-target-${{ hashFiles('**/Cargo.lock') }}
|
key: ${{ runner.os }}-v2-cargo-build-target-${{ hashFiles('**/Cargo.lock') }}
|
||||||
|
|
||||||
- name: Run cargo check
|
- name: Run cargo check
|
||||||
uses: actions-rs/cargo@v1
|
uses: actions-rs/cargo@v1
|
||||||
|
@ -67,19 +67,19 @@ jobs:
|
||||||
uses: actions/cache@v2.1.6
|
uses: actions/cache@v2.1.6
|
||||||
with:
|
with:
|
||||||
path: ~/.cargo/registry
|
path: ~/.cargo/registry
|
||||||
key: ${{ runner.os }}-v1-cargo-registry-${{ hashFiles('**/Cargo.lock') }}
|
key: ${{ runner.os }}-v2-cargo-registry-${{ hashFiles('**/Cargo.lock') }}
|
||||||
|
|
||||||
- name: Cache cargo index
|
- name: Cache cargo index
|
||||||
uses: actions/cache@v2.1.6
|
uses: actions/cache@v2.1.6
|
||||||
with:
|
with:
|
||||||
path: ~/.cargo/git
|
path: ~/.cargo/git
|
||||||
key: ${{ runner.os }}-v1-cargo-index-${{ hashFiles('**/Cargo.lock') }}
|
key: ${{ runner.os }}-v2-cargo-index-${{ hashFiles('**/Cargo.lock') }}
|
||||||
|
|
||||||
- name: Cache cargo target dir
|
- name: Cache cargo target dir
|
||||||
uses: actions/cache@v2.1.6
|
uses: actions/cache@v2.1.6
|
||||||
with:
|
with:
|
||||||
path: target
|
path: target
|
||||||
key: ${{ runner.os }}-v1-cargo-build-target-${{ hashFiles('**/Cargo.lock') }}
|
key: ${{ runner.os }}-v2-cargo-build-target-${{ hashFiles('**/Cargo.lock') }}
|
||||||
|
|
||||||
- name: Run cargo test
|
- name: Run cargo test
|
||||||
uses: actions-rs/cargo@v1
|
uses: actions-rs/cargo@v1
|
||||||
|
@ -112,19 +112,19 @@ jobs:
|
||||||
uses: actions/cache@v2.1.6
|
uses: actions/cache@v2.1.6
|
||||||
with:
|
with:
|
||||||
path: ~/.cargo/registry
|
path: ~/.cargo/registry
|
||||||
key: ${{ runner.os }}-v1-cargo-registry-${{ hashFiles('**/Cargo.lock') }}
|
key: ${{ runner.os }}-v2-cargo-registry-${{ hashFiles('**/Cargo.lock') }}
|
||||||
|
|
||||||
- name: Cache cargo index
|
- name: Cache cargo index
|
||||||
uses: actions/cache@v2.1.6
|
uses: actions/cache@v2.1.6
|
||||||
with:
|
with:
|
||||||
path: ~/.cargo/git
|
path: ~/.cargo/git
|
||||||
key: ${{ runner.os }}-v1-cargo-index-${{ hashFiles('**/Cargo.lock') }}
|
key: ${{ runner.os }}-v2-cargo-index-${{ hashFiles('**/Cargo.lock') }}
|
||||||
|
|
||||||
- name: Cache cargo target dir
|
- name: Cache cargo target dir
|
||||||
uses: actions/cache@v2.1.6
|
uses: actions/cache@v2.1.6
|
||||||
with:
|
with:
|
||||||
path: target
|
path: target
|
||||||
key: ${{ runner.os }}-v1-cargo-build-target-${{ hashFiles('**/Cargo.lock') }}
|
key: ${{ runner.os }}-v2-cargo-build-target-${{ hashFiles('**/Cargo.lock') }}
|
||||||
|
|
||||||
- name: Run cargo fmt
|
- name: Run cargo fmt
|
||||||
uses: actions-rs/cargo@v1
|
uses: actions-rs/cargo@v1
|
||||||
|
|
6
.gitmodules
vendored
6
.gitmodules
vendored
|
@ -84,7 +84,7 @@
|
||||||
shallow = true
|
shallow = true
|
||||||
[submodule "helix-syntax/languages/tree-sitter-elixir"]
|
[submodule "helix-syntax/languages/tree-sitter-elixir"]
|
||||||
path = helix-syntax/languages/tree-sitter-elixir
|
path = helix-syntax/languages/tree-sitter-elixir
|
||||||
url = https://github.com/IceDragon200/tree-sitter-elixir
|
url = https://github.com/elixir-lang/tree-sitter-elixir
|
||||||
shallow = true
|
shallow = true
|
||||||
[submodule "helix-syntax/languages/tree-sitter-nix"]
|
[submodule "helix-syntax/languages/tree-sitter-nix"]
|
||||||
path = helix-syntax/languages/tree-sitter-nix
|
path = helix-syntax/languages/tree-sitter-nix
|
||||||
|
@ -130,3 +130,7 @@
|
||||||
path = helix-syntax/languages/tree-sitter-tsq
|
path = helix-syntax/languages/tree-sitter-tsq
|
||||||
url = https://github.com/tree-sitter/tree-sitter-tsq
|
url = https://github.com/tree-sitter/tree-sitter-tsq
|
||||||
shallow = true
|
shallow = true
|
||||||
|
[submodule "helix-syntax/languages/tree-sitter-cmake"]
|
||||||
|
path = helix-syntax/languages/tree-sitter-cmake
|
||||||
|
url = https://github.com/uyha/tree-sitter-cmake
|
||||||
|
shallow = true
|
||||||
|
|
87
CHANGELOG.md
87
CHANGELOG.md
|
@ -1,4 +1,85 @@
|
||||||
|
|
||||||
|
# 0.5.0 (2021-10-28)
|
||||||
|
|
||||||
|
A big shout out to all the contributors! We had 46 contributors in this release.
|
||||||
|
|
||||||
|
Helix has popped up in [Scoop, FreeBSD Ports and Gentu GURU](https://repology.org/project/helix/versions)!
|
||||||
|
|
||||||
|
The following is a quick rundown of the larger changes, there were many more
|
||||||
|
(check the git history for more details).
|
||||||
|
|
||||||
|
Breaking changes:
|
||||||
|
|
||||||
|
- A couple of keymaps moved to resolve a few conflicting keybinds.
|
||||||
|
- Documentation popups were moved from `K` to `space+k`
|
||||||
|
- `K` is now `keep_selections` which filters selections to only keeps ones matching the regex
|
||||||
|
- `keep_primary_selection` moved from `space+space` to `,`
|
||||||
|
- `Alt-,` is now `remove_primary_selection` which keeps all selections except the primary one
|
||||||
|
- Opening files in a split moved from `C-h` to `C-s`
|
||||||
|
- Some configuration options moved from a `[terminal]` section to `[editor]`. [Consult the documentation for more information.](https://docs.helix-editor.com/configuration.html)
|
||||||
|
|
||||||
|
Features:
|
||||||
|
|
||||||
|
- LSP compatibility greatly improved for some implementations (Julia, Python, Typescript)
|
||||||
|
- Autocompletion! Completion now triggers automatically after a set idle timeout
|
||||||
|
- Completion documentation is now displayed next to the popup ([#691](https://github.com/helix-editor/helix/pull/691))
|
||||||
|
- Treesitter textobjects (select a function via `mf`, class via `mc`) ([#728](https://github.com/helix-editor/helix/pull/728))
|
||||||
|
- Global search across entire workspace `space+/` ([#651](https://github.com/helix-editor/helix/pull/651))
|
||||||
|
- Relative line number support ([#485](https://github.com/helix-editor/helix/pull/485))
|
||||||
|
- Prompts now store a history (72cf86e)
|
||||||
|
- `:vsplit` and `:hsplit` commands ([#639](https://github.com/helix-editor/helix/pull/639))
|
||||||
|
- `C-w h/j/k/l` can now be used to navigate between splits ([#860](https://github.com/helix-editor/helix/pull/860))
|
||||||
|
- `C-j` and `C-k` are now alternative keybindings to `C-n` and `C-p` in the UI ([#876](https://github.com/helix-editor/helix/pull/876))
|
||||||
|
- Shell commands (shell-pipe, pipe-to, shell-insert-output, shell-append-output, keep-pipe) ([#547](https://github.com/helix-editor/helix/pull/547))
|
||||||
|
- Searching now defaults to smart case search (case insensitive unless uppercase is used) ([#761](https://github.com/helix-editor/helix/pull/761))
|
||||||
|
- The preview pane was improved to highlight and center line ranges
|
||||||
|
- The user `languages.toml` is now merged into defaults, no longer need to copy the entire file (dc57f8dc)
|
||||||
|
- Show hidden files in completions ([#648](https://github.com/helix-editor/helix/pull/648))
|
||||||
|
- Grammar injections are now properly handled (dd0b15e)
|
||||||
|
- `v` in select mode now switches back to normal mode ([#660](https://github.com/helix-editor/helix/pull/660))
|
||||||
|
- View mode can now be triggered as a "sticky" mode ([#719](https://github.com/helix-editor/helix/pull/719))
|
||||||
|
- `f`/`t` and object selection motions can now be repeated via `Alt-.` ([#891](https://github.com/helix-editor/helix/pull/891))
|
||||||
|
- Statusline now displays total selection count and diagnostics counts for both errors and warnings ([#916](https://github.com/helix-editor/helix/pull/916))
|
||||||
|
|
||||||
|
New grammars:
|
||||||
|
|
||||||
|
- Ledger ([#572](https://github.com/helix-editor/helix/pull/572))
|
||||||
|
- Protobuf ([#614](https://github.com/helix-editor/helix/pull/614))
|
||||||
|
- Zig ([#631](https://github.com/helix-editor/helix/pull/631))
|
||||||
|
- YAML ([#667](https://github.com/helix-editor/helix/pull/667))
|
||||||
|
- Lua ([#665](https://github.com/helix-editor/helix/pull/665))
|
||||||
|
- OCaml ([#666](https://github.com/helix-editor/helix/pull/666))
|
||||||
|
- Svelte ([#733](https://github.com/helix-editor/helix/pull/733))
|
||||||
|
- Vue ([#787](https://github.com/helix-editor/helix/pull/787))
|
||||||
|
- Tree-sitter queries ([#845](https://github.com/helix-editor/helix/pull/845))
|
||||||
|
- CMake ([#888](https://github.com/helix-editor/helix/pull/888))
|
||||||
|
- Elixir (we switched over to the official grammar) (6c0786e)
|
||||||
|
- Language server definitions for Nix and Elixir ([#725](https://github.com/helix-editor/helix/pull/725))
|
||||||
|
- Python now uses `pylsp` instead of `pyls`
|
||||||
|
- Python now supports indentation
|
||||||
|
|
||||||
|
New themes:
|
||||||
|
|
||||||
|
- Monokai ([#628](https://github.com/helix-editor/helix/pull/628))
|
||||||
|
- Everforest Dark ([#760](https://github.com/helix-editor/helix/pull/760))
|
||||||
|
- Nord ([#799](https://github.com/helix-editor/helix/pull/799))
|
||||||
|
- Base16 Default Dark ([#833](https://github.com/helix-editor/helix/pull/833))
|
||||||
|
- Rose Pine ([#897](https://github.com/helix-editor/helix/pull/897))
|
||||||
|
|
||||||
|
Fixes:
|
||||||
|
|
||||||
|
- Fix crash on empty rust file ([#592](https://github.com/helix-editor/helix/pull/592))
|
||||||
|
- Exit select mode after toggle comment ([#598](https://github.com/helix-editor/helix/pull/598))
|
||||||
|
- Pin popups with no positioning to the initial position (12ea3888)
|
||||||
|
- xsel copy should not freeze the editor (6dd7dc4)
|
||||||
|
- `*` now only sets the search register and doesn't jump to the next occurrence (3426285)
|
||||||
|
- Goto line start/end commands extend when in select mode ([#739](https://github.com/helix-editor/helix/pull/739))
|
||||||
|
- Fix documentation popups sometimes not getting fully highlighted (066367c)
|
||||||
|
- Refactor apply_workspace_edit to remove assert (b02d872)
|
||||||
|
- Wrap around the top of the picker menu when scrolling (c7d6e44)
|
||||||
|
- Don't allow closing the last split if there's unsaved changes (3ff5b00)
|
||||||
|
- Indentation used different default on hx vs hx new_file.txt (c913bad)
|
||||||
|
|
||||||
# 0.4.1 (2021-08-14)
|
# 0.4.1 (2021-08-14)
|
||||||
|
|
||||||
A minor release that includes:
|
A minor release that includes:
|
||||||
|
@ -7,6 +88,8 @@ A minor release that includes:
|
||||||
|
|
||||||
# 0.4.0 (2021-08-13)
|
# 0.4.0 (2021-08-13)
|
||||||
|
|
||||||
|
A big shout out to all the contributors! We had 28 contributors in this release.
|
||||||
|
|
||||||
Two months have passed, so this is another big release. A big thank you to all
|
Two months have passed, so this is another big release. A big thank you to all
|
||||||
the contributors and package maintainers!
|
the contributors and package maintainers!
|
||||||
|
|
||||||
|
@ -44,6 +127,8 @@ selections in the future as well as resolves many bugs and edge cases.
|
||||||
|
|
||||||
# 0.3.0 (2021-06-27)
|
# 0.3.0 (2021-06-27)
|
||||||
|
|
||||||
|
A big shout out to all the contributors! We had 24 contributors in this release.
|
||||||
|
|
||||||
Another big release.
|
Another big release.
|
||||||
|
|
||||||
Highlights:
|
Highlights:
|
||||||
|
@ -90,6 +175,8 @@ Includes a fix where wq/wqa could exit before file saving completed.
|
||||||
|
|
||||||
# 0.2.0
|
# 0.2.0
|
||||||
|
|
||||||
|
A big shout out to all the contributors! We had 18 contributors in this release.
|
||||||
|
|
||||||
Enough has changed to bump the version. We're skipping 0.1.x because
|
Enough has changed to bump the version. We're skipping 0.1.x because
|
||||||
previously the CLI would always report version as 0.1.0, and we'd like
|
previously the CLI would always report version as 0.1.0, and we'd like
|
||||||
to distinguish it in bug reports..
|
to distinguish it in bug reports..
|
||||||
|
|
91
Cargo.lock
generated
91
Cargo.lock
generated
|
@ -78,9 +78,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "chardetng"
|
name = "chardetng"
|
||||||
version = "0.1.14"
|
version = "0.1.15"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "36a5a2ca47925d19fb6835f53b3e70dec0d25659211c8ee5cc784f1fd6838f9c"
|
checksum = "83ee29c16b81c32fbc882ecc568305793338a8353952573db837f4f4a6cd5c2e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"encoding_rs",
|
"encoding_rs",
|
||||||
|
@ -101,15 +101,24 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "clipboard-win"
|
name = "clipboard-win"
|
||||||
version = "4.2.1"
|
version = "4.2.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "4e4ea1881992efc993e4dc50a324cdbd03216e41bdc8385720ff47efc9bd2ca8"
|
checksum = "3db8340083d28acb43451166543b98c838299b7e0863621be53a338adceea0ed"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"error-code",
|
"error-code",
|
||||||
"str-buf",
|
"str-buf",
|
||||||
"winapi",
|
"winapi",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "content_inspector"
|
||||||
|
version = "0.2.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b7bda66e858c683005a53a9a60c69a4aca7eeaa45d124526e389f7aec8e62f38"
|
||||||
|
dependencies = [
|
||||||
|
"memchr",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "crossbeam-utils"
|
name = "crossbeam-utils"
|
||||||
version = "0.8.5"
|
version = "0.8.5"
|
||||||
|
@ -122,9 +131,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "crossterm"
|
name = "crossterm"
|
||||||
version = "0.21.0"
|
version = "0.22.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "486d44227f71a1ef39554c0dc47e44b9f4139927c75043312690c3f476d1d788"
|
checksum = "c85525306c4291d1b73ce93c8acf9c339f9b213aef6c1d85c3830cbf1c16325c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags",
|
"bitflags",
|
||||||
"crossterm_winapi",
|
"crossterm_winapi",
|
||||||
|
@ -139,9 +148,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "crossterm_winapi"
|
name = "crossterm_winapi"
|
||||||
version = "0.8.0"
|
version = "0.9.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3a6966607622438301997d3dac0d2f6e9a90c68bb6bc1785ea98456ab93c0507"
|
checksum = "2ae1b35a484aa10e07fe0638d02301c5ad24de82d310ccbd2f3693da5f09bf1c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"winapi",
|
"winapi",
|
||||||
]
|
]
|
||||||
|
@ -175,9 +184,9 @@ checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "encoding_rs"
|
name = "encoding_rs"
|
||||||
version = "0.8.28"
|
version = "0.8.29"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "80df024fbc5ac80f87dfef0d9f5209a252f2a497f7f42944cff24d8253cac065"
|
checksum = "a74ea89a0a1b98f6332de42c95baff457ada66d1cb4030f9ff151b2041a1c746"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
]
|
]
|
||||||
|
@ -358,11 +367,12 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "helix-core"
|
name = "helix-core"
|
||||||
version = "0.4.1"
|
version = "0.5.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"arc-swap",
|
"arc-swap",
|
||||||
"etcetera",
|
"etcetera",
|
||||||
"helix-syntax",
|
"helix-syntax",
|
||||||
|
"log",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"quickcheck",
|
"quickcheck",
|
||||||
"regex",
|
"regex",
|
||||||
|
@ -395,7 +405,7 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "helix-lsp"
|
name = "helix-lsp"
|
||||||
version = "0.4.1"
|
version = "0.5.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"futures-executor",
|
"futures-executor",
|
||||||
|
@ -413,7 +423,7 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "helix-syntax"
|
name = "helix-syntax"
|
||||||
version = "0.4.1"
|
version = "0.5.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"cc",
|
"cc",
|
||||||
|
@ -424,10 +434,11 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "helix-term"
|
name = "helix-term"
|
||||||
version = "0.4.1"
|
version = "0.5.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"chrono",
|
"chrono",
|
||||||
|
"content_inspector",
|
||||||
"crossterm",
|
"crossterm",
|
||||||
"fern",
|
"fern",
|
||||||
"futures-util",
|
"futures-util",
|
||||||
|
@ -455,7 +466,7 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "helix-tui"
|
name = "helix-tui"
|
||||||
version = "0.4.1"
|
version = "0.5.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags",
|
"bitflags",
|
||||||
"cassowary",
|
"cassowary",
|
||||||
|
@ -468,7 +479,7 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "helix-view"
|
name = "helix-view"
|
||||||
version = "0.4.1"
|
version = "0.5.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"bitflags",
|
"bitflags",
|
||||||
|
@ -532,9 +543,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "instant"
|
name = "instant"
|
||||||
version = "0.1.11"
|
version = "0.1.12"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "716d3d89f35ac6a34fd0eed635395f4c3b76fa889338a4632e5231a8684216bd"
|
checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
]
|
]
|
||||||
|
@ -566,9 +577,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libc"
|
name = "libc"
|
||||||
version = "0.2.103"
|
version = "0.2.104"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "dd8f7255a17a627354f321ef0055d63b898c6fb27eff628af4d1b66b7331edf6"
|
checksum = "7b2f96d100e1cf1929e7719b7edb3b90ab5298072638fccd77be9ce942ecdfce"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libloading"
|
name = "libloading"
|
||||||
|
@ -600,9 +611,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lsp-types"
|
name = "lsp-types"
|
||||||
version = "0.90.1"
|
version = "0.91.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "6f3734ab1d7d157fc0c45110e06b587c31cd82bea2ccfd6b563cbff0aaeeb1d3"
|
checksum = "2368312c59425dd133cb9a327afee65be0a633a8ce471d248e2202a48f8f68ae"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags",
|
"bitflags",
|
||||||
"serde",
|
"serde",
|
||||||
|
@ -640,9 +651,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "mio"
|
name = "mio"
|
||||||
version = "0.7.13"
|
version = "0.7.14"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8c2bdb6314ec10835cd3293dd268473a835c02b7b352e788be788b3c6ca6bb16"
|
checksum = "8067b404fe97c70829f082dec8bcf4f71225d7eaea1d8645349cb76fa06205cc"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
"log",
|
"log",
|
||||||
|
@ -755,9 +766,9 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "proc-macro2"
|
name = "proc-macro2"
|
||||||
version = "1.0.29"
|
version = "1.0.30"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b9f5105d4fdaab20335ca9565e106a5d9b82b6219b5ba735731124ac6711d23d"
|
checksum = "edc3358ebc67bc8b7fa0c007f945b0b18226f78437d61bec735a9eb96b61ee70"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"unicode-xid",
|
"unicode-xid",
|
||||||
]
|
]
|
||||||
|
@ -784,9 +795,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "quote"
|
name = "quote"
|
||||||
version = "1.0.9"
|
version = "1.0.10"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c3d0b9745dc2debf507c8422de05d7226cc1f0644216dfdfead988f9b1ab32a7"
|
checksum = "38bc8cc6a5f2e3655e0899c1b848643b2562f853f114bfec7be120678e3ace05"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
]
|
]
|
||||||
|
@ -973,9 +984,9 @@ checksum = "2e24979f63a11545f5f2c60141afe249d4f19f84581ea2138065e400941d83d3"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "slab"
|
name = "slab"
|
||||||
version = "0.4.4"
|
version = "0.4.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c307a32c1c5c437f38c7fd45d753050587732ba8628319fbdf12a7e289ccc590"
|
checksum = "9def91fd1e018fe007022791f865d0ccc9b3a0d5001e01aabb8b40e46000afb5"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "slotmap"
|
name = "slotmap"
|
||||||
|
@ -1000,9 +1011,9 @@ checksum = "d44a3643b4ff9caf57abcee9c2c621d6c03d9135e0d8b589bd9afb5992cb176a"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "syn"
|
name = "syn"
|
||||||
version = "1.0.78"
|
version = "1.0.80"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a4eac2e6c19f5c3abc0c229bea31ff0b9b091c7b14990e8924b92902a303a0c0"
|
checksum = "d010a1623fbd906d51d650a9916aaefc05ffa0e4053ff7fe601167f3e715d194"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
|
@ -1075,9 +1086,9 @@ checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tokio"
|
name = "tokio"
|
||||||
version = "1.12.0"
|
version = "1.13.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c2c2416fdedca8443ae44b4527de1ea633af61d8f7169ffa6e72c5b53d24efcc"
|
checksum = "588b2d10a336da58d877567cd8fb8a14b463e2104910f8132cd054b4b96e29ee"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"autocfg",
|
"autocfg",
|
||||||
"bytes",
|
"bytes",
|
||||||
|
@ -1095,9 +1106,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tokio-macros"
|
name = "tokio-macros"
|
||||||
version = "1.4.1"
|
version = "1.5.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "154794c8f499c2619acd19e839294703e9e32e7630ef5f46ea80d4ef0fbee5eb"
|
checksum = "b2dd85aeaba7b68df939bd357c6afb36c87951be9e80bf9c859f2fc3e9fca0fd"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
|
@ -1106,9 +1117,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tokio-stream"
|
name = "tokio-stream"
|
||||||
version = "0.1.7"
|
version = "0.1.8"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7b2f3f698253f03119ac0102beaa64f67a67e08074d03a22d18784104543727f"
|
checksum = "50145484efff8818b5ccd256697f36863f587da82cf8b409c53adf1e840798e3"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"futures-core",
|
"futures-core",
|
||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
|
@ -1145,9 +1156,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unicode-bidi"
|
name = "unicode-bidi"
|
||||||
version = "0.3.6"
|
version = "0.3.7"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "246f4c42e67e7a4e3c6106ff716a5d067d4132a642840b242e357e468a2a0085"
|
checksum = "1a01404663e3db436ed2746d9fefef640d868edae3cceb81c3b8d5732fda678f"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unicode-general-category"
|
name = "unicode-general-category"
|
||||||
|
|
|
@ -27,11 +27,11 @@ All shortcuts/keymaps can be found [in the documentation on the website](https:/
|
||||||
It's a terminal-based editor first, but I'd like to explore a custom renderer
|
It's a terminal-based editor first, but I'd like to explore a custom renderer
|
||||||
(similar to emacs) in wgpu or skulpin.
|
(similar to emacs) in wgpu or skulpin.
|
||||||
|
|
||||||
# Installation
|
|
||||||
|
|
||||||
Note: Only certain languages have indentation definitions at the moment. Check
|
Note: Only certain languages have indentation definitions at the moment. Check
|
||||||
`runtime/queries/<lang>/` for `indents.toml`.
|
`runtime/queries/<lang>/` for `indents.toml`.
|
||||||
|
|
||||||
|
# Installation
|
||||||
|
|
||||||
We provide packaging for various distributions, but here's a quick method to
|
We provide packaging for various distributions, but here's a quick method to
|
||||||
build from source.
|
build from source.
|
||||||
|
|
||||||
|
@ -71,7 +71,7 @@ Some suggestions to get started:
|
||||||
|
|
||||||
- You can look at the [good first issue](https://github.com/helix-editor/helix/labels/E-easy) label on the issue tracker.
|
- You can look at the [good first issue](https://github.com/helix-editor/helix/labels/E-easy) label on the issue tracker.
|
||||||
- Help with packaging on various distributions needed!
|
- Help with packaging on various distributions needed!
|
||||||
- To use print debugging to the `~/.cache/helix/helix.log` file, you must:
|
- To use print debugging to the [Helix log file](https://github.com/helix-editor/helix/wiki/FAQ#access-the-log-file), you must:
|
||||||
* Print using `log::info!`, `warn!`, or `error!`. (`log::info!("helix!")`)
|
* Print using `log::info!`, `warn!`, or `error!`. (`log::info!("helix!")`)
|
||||||
* Pass the appropriate verbosity level option for the desired log level. (`hx -v <file>` for info, more `v`s for higher severity inclusive)
|
* Pass the appropriate verbosity level option for the desired log level. (`hx -v <file>` for info, more `v`s for higher severity inclusive)
|
||||||
- If your preferred language is missing, integrating a tree-sitter grammar for
|
- If your preferred language is missing, integrating a tree-sitter grammar for
|
||||||
|
|
6
TODO.md
6
TODO.md
|
@ -6,10 +6,6 @@
|
||||||
- clojure
|
- clojure
|
||||||
- erlang
|
- erlang
|
||||||
|
|
||||||
as you type completion!
|
|
||||||
- [ ] use signature_help_provider and completion_provider trigger characters in
|
|
||||||
a hook to trigger signature help text / autocompletion
|
|
||||||
- [ ] document.on_type provider triggers
|
|
||||||
- [ ] completion isIncomplete support
|
- [ ] completion isIncomplete support
|
||||||
|
|
||||||
1
|
1
|
||||||
|
@ -18,8 +14,6 @@ as you type completion!
|
||||||
|
|
||||||
- [ ] = for auto indent line/selection
|
- [ ] = for auto indent line/selection
|
||||||
- [ ] :x for closing buffers
|
- [ ] :x for closing buffers
|
||||||
- [ ] repeat selection
|
|
||||||
|
|
||||||
- [ ] lsp: signature help
|
- [ ] lsp: signature help
|
||||||
|
|
||||||
2
|
2
|
||||||
|
|
|
@ -8,3 +8,5 @@
|
||||||
- [Keymap](./keymap.md)
|
- [Keymap](./keymap.md)
|
||||||
- [Key Remapping](./remapping.md)
|
- [Key Remapping](./remapping.md)
|
||||||
- [Hooks](./hooks.md)
|
- [Hooks](./hooks.md)
|
||||||
|
- [Guides](./guides/README.md)
|
||||||
|
- [Adding Textobject Queries](./guides/textobject.md)
|
||||||
|
|
|
@ -21,6 +21,8 @@ To override global configuration parameters, create a `config.toml` file located
|
||||||
| `auto-pairs` | Enable automatic insertion of pairs to parenthese, brackets, etc. | `true` |
|
| `auto-pairs` | Enable automatic insertion of pairs to parenthese, brackets, etc. | `true` |
|
||||||
| `auto-completion` | Enable automatic pop up of auto-completion. | `true` |
|
| `auto-completion` | Enable automatic pop up of auto-completion. | `true` |
|
||||||
| `idle-timeout` | Time in milliseconds since last keypress before idle timers trigger. Used for autocompletion, set to 0 for instant. | `400` |
|
| `idle-timeout` | Time in milliseconds since last keypress before idle timers trigger. Used for autocompletion, set to 0 for instant. | `400` |
|
||||||
|
| `completion-trigger-len` | The min-length of word under cursor to trigger autocompletion | `2` |
|
||||||
|
| `auto-info` | Whether to display infoboxes | `true` |
|
||||||
|
|
||||||
## LSP
|
## LSP
|
||||||
|
|
||||||
|
|
4
book/src/guides/README.md
Normal file
4
book/src/guides/README.md
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
# Guides
|
||||||
|
|
||||||
|
This section contains guides for adding new language server configurations,
|
||||||
|
tree-sitter grammers, textobject queries, etc.
|
30
book/src/guides/textobject.md
Normal file
30
book/src/guides/textobject.md
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
# Adding Textobject Queries
|
||||||
|
|
||||||
|
Textobjects that are language specific ([like functions, classes, etc][textobjects])
|
||||||
|
require an accompanying tree-sitter grammar and a `textobjects.scm` query file
|
||||||
|
to work properly. Tree-sitter allows us to query the source code syntax tree
|
||||||
|
and capture specific parts of it. The queries are written in a lisp dialect.
|
||||||
|
More information on how to write queries can be found in the [official tree-sitter
|
||||||
|
documentation](tree-sitter-queries).
|
||||||
|
|
||||||
|
Query files should be placed in `runtime/queries/{language}/textobjects.scm`
|
||||||
|
when contributing. Note that to test the query files locally you should put
|
||||||
|
them under your local runtime directory (`~/.config/helix/runtime` on Linux
|
||||||
|
for example).
|
||||||
|
|
||||||
|
The following [captures][tree-sitter-captures] are recognized:
|
||||||
|
|
||||||
|
| Capture Name |
|
||||||
|
| --- |
|
||||||
|
| `function.inside` |
|
||||||
|
| `function.around` |
|
||||||
|
| `class.inside` |
|
||||||
|
| `class.around` |
|
||||||
|
| `parameter.inside` |
|
||||||
|
|
||||||
|
[Example query files][textobject-examples] can be found in the helix GitHub repository.
|
||||||
|
|
||||||
|
[textobjects]: ../usage.md#textobjects
|
||||||
|
[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
|
||||||
|
[textobject-examples]: https://github.com/search?q=repo%3Ahelix-editor%2Fhelix+filename%3Atextobjects.scm&type=Code&ref=advsearch&l=&l=
|
|
@ -6,38 +6,39 @@
|
||||||
|
|
||||||
> NOTE: Unlike vim, `f`, `F`, `t` and `T` are not confined to the current line.
|
> NOTE: Unlike vim, `f`, `F`, `t` and `T` are not confined to the current line.
|
||||||
|
|
||||||
| Key | Description | Command |
|
| Key | Description | Command |
|
||||||
| ----- | ----------- | ------- |
|
| ----- | ----------- | ------- |
|
||||||
| `h`, `Left` | Move left | `move_char_left` |
|
| `h`/`Left` | Move left | `move_char_left` |
|
||||||
| `j`, `Down` | Move down | `move_char_right` |
|
| `j`/`Down` | Move down | `move_line_down` |
|
||||||
| `k`, `Up` | Move up | `move_line_up` |
|
| `k`/`Up` | Move up | `move_line_up` |
|
||||||
| `l`, `Right` | Move right | `move_line_down` |
|
| `l`/`Right` | Move right | `move_char_right` |
|
||||||
| `w` | Move next word start | `move_next_word_start` |
|
| `w` | Move next word start | `move_next_word_start` |
|
||||||
| `b` | Move previous word start | `move_prev_word_start` |
|
| `b` | Move previous word start | `move_prev_word_start` |
|
||||||
| `e` | Move next word end | `move_next_word_end` |
|
| `e` | Move next word end | `move_next_word_end` |
|
||||||
| `W` | Move next WORD start | `move_next_long_word_start` |
|
| `W` | Move next WORD start | `move_next_long_word_start` |
|
||||||
| `B` | Move previous WORD start | `move_prev_long_word_start` |
|
| `B` | Move previous WORD start | `move_prev_long_word_start` |
|
||||||
| `E` | Move next WORD end | `move_next_long_word_end` |
|
| `E` | Move next WORD end | `move_next_long_word_end` |
|
||||||
| `t` | Find 'till next char | `find_till_char` |
|
| `t` | Find 'till next char | `find_till_char` |
|
||||||
| `f` | Find next char | `find_next_char` |
|
| `f` | Find next char | `find_next_char` |
|
||||||
| `T` | Find 'till previous char | `till_prev_char` |
|
| `T` | Find 'till previous char | `till_prev_char` |
|
||||||
| `F` | Find previous char | `find_prev_char` |
|
| `F` | Find previous char | `find_prev_char` |
|
||||||
| `Home` | Move to the start of the line | `goto_line_start` |
|
| `Alt-.` | Repeat last motion (`f`, `t` or `m`) | `repeat_last_motion` |
|
||||||
| `End` | Move to the end of the line | `goto_line_end` |
|
| `Home` | Move to the start of the line | `goto_line_start` |
|
||||||
| `PageUp` | Move page up | `page_up` |
|
| `End` | Move to the end of the line | `goto_line_end` |
|
||||||
| `PageDown` | Move page down | `page_down` |
|
| `PageUp` | Move page up | `page_up` |
|
||||||
| `Ctrl-u` | Move half page up | `half_page_up` |
|
| `PageDown` | Move page down | `page_down` |
|
||||||
| `Ctrl-d` | Move half page down | `half_page_down` |
|
| `Ctrl-u` | Move half page up | `half_page_up` |
|
||||||
| `Ctrl-i` | Jump forward on the jumplist | `jump_forward` |
|
| `Ctrl-d` | Move half page down | `half_page_down` |
|
||||||
| `Ctrl-o` | Jump backward on the jumplist | `jump_backward` |
|
| `Ctrl-i` | Jump forward on the jumplist | `jump_forward` |
|
||||||
| `v` | Enter [select (extend) mode](#select--extend-mode) | `select_mode` |
|
| `Ctrl-o` | Jump backward on the jumplist | `jump_backward` |
|
||||||
| `g` | Enter [goto mode](#goto-mode) | N/A |
|
| `v` | Enter [select (extend) mode](#select--extend-mode) | `select_mode` |
|
||||||
| `m` | Enter [match mode](#match-mode) | N/A |
|
| `g` | Enter [goto mode](#goto-mode) | N/A |
|
||||||
| `:` | Enter command mode | `command_mode` |
|
| `m` | Enter [match mode](#match-mode) | N/A |
|
||||||
| `z` | Enter [view mode](#view-mode) | N/A |
|
| `:` | Enter command mode | `command_mode` |
|
||||||
| `Z` | Enter sticky [view mode](#view-mode) | N/A |
|
| `z` | Enter [view mode](#view-mode) | N/A |
|
||||||
| `Ctrl-w` | Enter [window mode](#window-mode) | N/A |
|
| `Z` | Enter sticky [view mode](#view-mode) | N/A |
|
||||||
| `Space` | Enter [space mode](#space-mode) | N/A |
|
| `Ctrl-w` | Enter [window mode](#window-mode) | N/A |
|
||||||
|
| `Space` | Enter [space mode](#space-mode) | N/A |
|
||||||
|
|
||||||
### Changes
|
### Changes
|
||||||
|
|
||||||
|
@ -54,6 +55,7 @@
|
||||||
| `A` | Insert at the end of the line | `append_to_line` |
|
| `A` | Insert at the end of the line | `append_to_line` |
|
||||||
| `o` | Open new line below selection | `open_below` |
|
| `o` | Open new line below selection | `open_below` |
|
||||||
| `O` | Open new line above selection | `open_above` |
|
| `O` | Open new line above selection | `open_above` |
|
||||||
|
| `.` | Repeat last change | N/A |
|
||||||
| `u` | Undo change | `undo` |
|
| `u` | Undo change | `undo` |
|
||||||
| `U` | Redo change | `redo` |
|
| `U` | Redo change | `redo` |
|
||||||
| `y` | Yank selection | `yank` |
|
| `y` | Yank selection | `yank` |
|
||||||
|
@ -86,8 +88,9 @@
|
||||||
| `;` | Collapse selection onto a single cursor | `collapse_selection` |
|
| `;` | Collapse selection onto a single cursor | `collapse_selection` |
|
||||||
| `Alt-;` | Flip selection cursor and anchor | `flip_selections` |
|
| `Alt-;` | Flip selection cursor and anchor | `flip_selections` |
|
||||||
| `,` | Keep only the primary selection | `keep_primary_selection` |
|
| `,` | Keep only the primary selection | `keep_primary_selection` |
|
||||||
| `C` | Copy selection onto the next line | `copy_selection_on_next_line` |
|
| `Alt-,` | Remove the primary selection | `remove_primary_selection` |
|
||||||
| `Alt-C` | Copy selection onto the previous line | `copy_selection_on_prev_line` |
|
| `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 backward | `rotate_selections_backward` |
|
||||||
| `)` | Rotate main selection forward | `rotate_selections_forward` |
|
| `)` | Rotate main selection forward | `rotate_selections_forward` |
|
||||||
| `Alt-(` | Rotate selection contents backward | `rotate_selection_contents_backward` |
|
| `Alt-(` | Rotate selection contents backward | `rotate_selection_contents_backward` |
|
||||||
|
@ -103,13 +106,13 @@
|
||||||
|
|
||||||
### Search
|
### Search
|
||||||
|
|
||||||
> TODO: The search implementation isn't ideal yet -- we don't support searching in reverse.
|
|
||||||
|
|
||||||
| Key | Description | Command |
|
| Key | Description | Command |
|
||||||
| ----- | ----------- | ------- |
|
| ----- | ----------- | ------- |
|
||||||
| `/` | Search for regex pattern | `search` |
|
| `/` | Search for regex pattern | `search` |
|
||||||
|
| `?` | Search for previous pattern | `rsearch` |
|
||||||
| `n` | Select next search match | `search_next` |
|
| `n` | Select next search match | `search_next` |
|
||||||
| `N` | Add next search match to selection | `extend_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 | `search_selection` |
|
||||||
|
|
||||||
### Minor modes
|
### Minor modes
|
||||||
|
@ -158,6 +161,8 @@ Jumps to various locations.
|
||||||
| `r` | Go to references | `goto_reference` |
|
| `r` | Go to references | `goto_reference` |
|
||||||
| `i` | Go to implementation | `goto_implementation` |
|
| `i` | Go to implementation | `goto_implementation` |
|
||||||
| `a` | Go to the last accessed/alternate file | `goto_last_accessed_file` |
|
| `a` | Go to the last accessed/alternate file | `goto_last_accessed_file` |
|
||||||
|
| `n` | Go to next buffer | `goto_next_buffer` |
|
||||||
|
| `p` | Go to previous buffer | `goto_previous_buffer` |
|
||||||
|
|
||||||
#### Match mode
|
#### Match mode
|
||||||
|
|
||||||
|
@ -180,12 +185,16 @@ TODO: Mappings for selecting syntax nodes (a superset of `[`).
|
||||||
|
|
||||||
This layer is similar to vim keybindings as kakoune does not support window.
|
This layer is similar to vim keybindings as kakoune does not support window.
|
||||||
|
|
||||||
| Key | Description | Command |
|
| Key | Description | Command |
|
||||||
| ----- | ------------- | ------- |
|
| ----- | ------------- | ------- |
|
||||||
| `w`, `Ctrl-w` | Switch to next window | `rotate_view` |
|
| `w`, `Ctrl-w` | Switch to next window | `rotate_view` |
|
||||||
| `v`, `Ctrl-v` | Vertical right split | `vsplit` |
|
| `v`, `Ctrl-v` | Vertical right split | `vsplit` |
|
||||||
| `h`, `Ctrl-h` | Horizontal bottom split | `hsplit` |
|
| `s`, `Ctrl-s` | Horizontal bottom split | `hsplit` |
|
||||||
| `q`, `Ctrl-q` | Close current window | `wclose` |
|
| `h`, `Ctrl-h`, `left` | Move to left split | `jump_view_left` |
|
||||||
|
| `j`, `Ctrl-j`, `down` | Move to split below | `jump_view_down` |
|
||||||
|
| `k`, `Ctrl-k`, `up` | Move to split above | `jump_view_up` |
|
||||||
|
| `l`, `Ctrl-l`, `right` | Move to right split | `jump_view_right` |
|
||||||
|
| `q`, `Ctrl-q` | Close current window | `wclose` |
|
||||||
|
|
||||||
#### Space mode
|
#### Space mode
|
||||||
|
|
||||||
|
@ -213,12 +222,12 @@ This layer is a kludge of mappings, mostly pickers.
|
||||||
|
|
||||||
Mappings in the style of [vim-unimpaired](https://github.com/tpope/vim-unimpaired).
|
Mappings in the style of [vim-unimpaired](https://github.com/tpope/vim-unimpaired).
|
||||||
|
|
||||||
| Key | Description | Command |
|
| Key | Description | Command |
|
||||||
| ----- | ----------- | ------- |
|
| ----- | ----------- | ------- |
|
||||||
| `[d` | Go to previous diagnostic | `goto_prev_diag` |
|
| `[d` | Go to previous diagnostic | `goto_prev_diag` |
|
||||||
| `]d` | Go to next diagnostic | `goto_next_diag` |
|
| `]d` | Go to next diagnostic | `goto_next_diag` |
|
||||||
| `[D` | Go to first diagnostic in document | `goto_first_diag` |
|
| `[D` | Go to first diagnostic in document | `goto_first_diag` |
|
||||||
| `]D` | Go to last diagnostic in document | `goto_last_diag` |
|
| `]D` | Go to last diagnostic in document | `goto_last_diag` |
|
||||||
| `[space` | Add newline above | `add_newline_above` |
|
| `[space` | Add newline above | `add_newline_above` |
|
||||||
| `]space` | Add newline below | `add_newline_below` |
|
| `]space` | Add newline below | `add_newline_below` |
|
||||||
|
|
||||||
|
@ -242,12 +251,34 @@ commands (including goto) to extend the existing selection instead of replacing
|
||||||
|
|
||||||
Keys to use within picker. Remapping currently not supported.
|
Keys to use within picker. Remapping currently not supported.
|
||||||
|
|
||||||
| Key | Description |
|
| Key | Description |
|
||||||
| ----- | ------------- |
|
| ----- | ------------- |
|
||||||
| `Up`, `Ctrl-p` | Previous entry |
|
| `Up`, `Ctrl-k`, `Ctrl-p` | Previous entry |
|
||||||
| `Down`, `Ctrl-n` | Next entry |
|
| `Down`, `Ctrl-j`, `Ctrl-n` | Next entry |
|
||||||
| `Ctrl-space` | Filter options |
|
| `Ctrl-space` | Filter options |
|
||||||
| `Enter` | Open selected |
|
| `Enter` | Open selected |
|
||||||
| `Ctrl-h` | Open horizontally |
|
| `Ctrl-s` | Open horizontally |
|
||||||
| `Ctrl-v` | Open vertically |
|
| `Ctrl-v` | Open vertically |
|
||||||
| `Escape`, `Ctrl-c` | Close picker |
|
| `Escape`, `Ctrl-c` | Close picker |
|
||||||
|
|
||||||
|
# Prompt
|
||||||
|
Keys to use within prompt, Remapping currently not supported.
|
||||||
|
| Key | Description |
|
||||||
|
| ----- | ------------- |
|
||||||
|
| `Escape`, `Ctrl-c` | Close prompt |
|
||||||
|
| `Alt-b`, `Alt-Left` | Backward a word |
|
||||||
|
| `Ctrl-b`, `Left` | Backward a char |
|
||||||
|
| `Alt-f`, `Alt-Right` | Forward a word |
|
||||||
|
| `Ctrl-f`, `Right` | Forward a char |
|
||||||
|
| `Ctrl-e`, `End` | move prompt end |
|
||||||
|
| `Ctrl-a`, `Home` | move prompt start |
|
||||||
|
| `Ctrl-w` | delete previous word |
|
||||||
|
| `Ctrl-k` | delete to end of line |
|
||||||
|
| `backspace` | delete previous char |
|
||||||
|
| `Ctrl-s` | insert a word under doc cursor, may be changed to Ctrl-r Ctrl-w later |
|
||||||
|
| `Ctrl-p`, `Up` | select previous history |
|
||||||
|
| `Ctrl-n`, `Down` | select next history |
|
||||||
|
| `Tab` | slect next completion item |
|
||||||
|
| `BackTab` | slect previous completion item |
|
||||||
|
| `Enter` | Open selected |
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
One-way key remapping is temporarily supported via a simple TOML configuration
|
One-way key remapping is temporarily supported via a simple TOML configuration
|
||||||
file. (More powerful solutions such as rebinding via commands will be
|
file. (More powerful solutions such as rebinding via commands will be
|
||||||
available in the feature).
|
available in the future).
|
||||||
|
|
||||||
To remap keys, write a `config.toml` file in your `helix` configuration
|
To remap keys, write a `config.toml` file in your `helix` configuration
|
||||||
directory (default `~/.config/helix` in Linux systems) with a structure like
|
directory (default `~/.config/helix` in Linux systems) with a structure like
|
||||||
|
|
|
@ -103,8 +103,6 @@ We use a similar set of scopes as
|
||||||
[SublimeText](https://www.sublimetext.com/docs/scope_naming.html). See also
|
[SublimeText](https://www.sublimetext.com/docs/scope_naming.html). See also
|
||||||
[TextMate](https://macromates.com/manual/en/language_grammars) scopes.
|
[TextMate](https://macromates.com/manual/en/language_grammars) scopes.
|
||||||
|
|
||||||
- `escape` (TODO: rename to (constant).character.escape)
|
|
||||||
|
|
||||||
- `type` - Types
|
- `type` - Types
|
||||||
- `builtin` - Primitive types provided by the language (`int`, `usize`)
|
- `builtin` - Primitive types provided by the language (`int`, `usize`)
|
||||||
|
|
||||||
|
@ -112,13 +110,17 @@ We use a similar set of scopes as
|
||||||
- `builtin` Special constants provided by the language (`true`, `false`, `nil` etc)
|
- `builtin` Special constants provided by the language (`true`, `false`, `nil` etc)
|
||||||
- `boolean`
|
- `boolean`
|
||||||
- `character`
|
- `character`
|
||||||
|
- `escape`
|
||||||
|
- `numeric` (numbers)
|
||||||
|
- `integer`
|
||||||
|
- `float`
|
||||||
|
|
||||||
- `number` (TODO: rename to constant.number/.numeric.{integer, float, complex})
|
|
||||||
- `string` (TODO: string.quoted.{single, double}, string.raw/.unquoted)?
|
- `string` (TODO: string.quoted.{single, double}, string.raw/.unquoted)?
|
||||||
- `regexp` - Regular expressions
|
- `regexp` - Regular expressions
|
||||||
- `special`
|
- `special`
|
||||||
- `path`
|
- `path`
|
||||||
- `url`
|
- `url`
|
||||||
|
- `symbol` - Erlang/Elixir atoms, Ruby symbols, Clojure keywords
|
||||||
|
|
||||||
- `comment` - Code comments
|
- `comment` - Code comments
|
||||||
- `line` - Single line comments (`//`)
|
- `line` - Single line comments (`//`)
|
||||||
|
@ -128,7 +130,8 @@ We use a similar set of scopes as
|
||||||
- `variable` - Variables
|
- `variable` - Variables
|
||||||
- `builtin` - Reserved language variables (`self`, `this`, `super`, etc)
|
- `builtin` - Reserved language variables (`self`, `this`, `super`, etc)
|
||||||
- `parameter` - Function parameters
|
- `parameter` - Function parameters
|
||||||
- `property`
|
- `other`
|
||||||
|
- `member` - Fields of composite data types (e.g. structs, unions)
|
||||||
- `function` (TODO: ?)
|
- `function` (TODO: ?)
|
||||||
|
|
||||||
- `label`
|
- `label`
|
||||||
|
|
|
@ -2,6 +2,8 @@
|
||||||
|
|
||||||
(Currently not fully documented, see the [keymappings](./keymap.md) list for more.)
|
(Currently not fully documented, see the [keymappings](./keymap.md) list for more.)
|
||||||
|
|
||||||
|
See [tutor.txt](https://github.com/helix-editor/helix/blob/master/runtime/tutor.txt) (accessible via `hx --tutor` or `:tutor`) for a vimtutor-like introduction.
|
||||||
|
|
||||||
## Registers
|
## Registers
|
||||||
|
|
||||||
Vim-like registers can be used to yank and store text to be pasted later. Usage is similar, with `"` being used to select a register:
|
Vim-like registers can be used to yank and store text to be pasted later. Usage is similar, with `"` being used to select a register:
|
||||||
|
@ -49,9 +51,10 @@ Multiple characters are currently not supported, but planned.
|
||||||
|
|
||||||
## Textobjects
|
## Textobjects
|
||||||
|
|
||||||
Currently supported: `word`, `surround`.
|
Currently supported: `word`, `surround`, `function`, `class`, `parameter`.
|
||||||
|
|
||||||
![textobject-demo](https://user-images.githubusercontent.com/23398472/124231131-81a4bb00-db2d-11eb-9d10-8e577ca7b177.gif)
|
![textobject-demo](https://user-images.githubusercontent.com/23398472/124231131-81a4bb00-db2d-11eb-9d10-8e577ca7b177.gif)
|
||||||
|
![textobject-treesitter-demo](https://user-images.githubusercontent.com/23398472/132537398-2a2e0a54-582b-44ab-a77f-eb818942203d.gif)
|
||||||
|
|
||||||
- `ma` - Select around the object (`va` in vim, `<alt-a>` in kakoune)
|
- `ma` - Select around the object (`va` in vim, `<alt-a>` in kakoune)
|
||||||
- `mi` - Select inside the object (`vi` in vim, `<alt-i>` in kakoune)
|
- `mi` - Select inside the object (`vi` in vim, `<alt-i>` in kakoune)
|
||||||
|
@ -60,5 +63,11 @@ Currently supported: `word`, `surround`.
|
||||||
| --- | --- |
|
| --- | --- |
|
||||||
| `w` | Word |
|
| `w` | Word |
|
||||||
| `(`, `[`, `'`, etc | Specified surround pairs |
|
| `(`, `[`, `'`, etc | Specified surround pairs |
|
||||||
|
| `f` | Function |
|
||||||
|
| `c` | Class |
|
||||||
|
| `p` | Parameter |
|
||||||
|
|
||||||
Textobjects based on treesitter, like `function`, `class`, etc are planned.
|
Note: `f`, `c`, etc need a tree-sitter grammar active for the current
|
||||||
|
document and a special tree-sitter query file to work properly. [Only
|
||||||
|
some grammars](https://github.com/search?q=repo%3Ahelix-editor%2Fhelix+filename%3Atextobjects.scm&type=Code&ref=advsearch&l=&l=)
|
||||||
|
currently have the query file implemented. Contributions are welcome !
|
||||||
|
|
68
flake.lock
generated
68
flake.lock
generated
|
@ -2,11 +2,11 @@
|
||||||
"nodes": {
|
"nodes": {
|
||||||
"devshell": {
|
"devshell": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1630239564,
|
"lastModified": 1632436039,
|
||||||
"narHash": "sha256-lv7atkVE1+dFw0llmzONsbSIo5ao85KpNSRoFk4K8vU=",
|
"narHash": "sha256-OtITeVWcKXn1SpVEnImpTGH91FycCskGBPqmlxiykv4=",
|
||||||
"owner": "numtide",
|
"owner": "numtide",
|
||||||
"repo": "devshell",
|
"repo": "devshell",
|
||||||
"rev": "bd86d3a2bb28ce4d223315e0eca0d59fef8a0a73",
|
"rev": "7a7a7aa0adebe5488e5abaec688fd9ae0f8ea9c6",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
@ -15,6 +15,21 @@
|
||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"flake-utils": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1623875721,
|
||||||
|
"narHash": "sha256-A8BU7bjS5GirpAUv4QA+QnJ4CceLHkcXdRp4xITDB0s=",
|
||||||
|
"owner": "numtide",
|
||||||
|
"repo": "flake-utils",
|
||||||
|
"rev": "f7e004a55b120c02ecb6219596820fcd32ca8772",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "numtide",
|
||||||
|
"repo": "flake-utils",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
"flakeCompat": {
|
"flakeCompat": {
|
||||||
"flake": false,
|
"flake": false,
|
||||||
"locked": {
|
"locked": {
|
||||||
|
@ -37,14 +52,16 @@
|
||||||
"nixpkgs": [
|
"nixpkgs": [
|
||||||
"nixpkgs"
|
"nixpkgs"
|
||||||
],
|
],
|
||||||
"rustOverlay": "rustOverlay"
|
"rustOverlay": [
|
||||||
|
"rust-overlay"
|
||||||
|
]
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1631254163,
|
"lastModified": 1634796585,
|
||||||
"narHash": "sha256-8+nOGLH1fXwWnNMTQq/Igk434BzZF5Vld45xLDLiNDQ=",
|
"narHash": "sha256-CW4yx6omk5qCXUIwXHp/sztA7u0SpyLq9NEACPnkiz8=",
|
||||||
"owner": "yusdacra",
|
"owner": "yusdacra",
|
||||||
"repo": "nix-cargo-integration",
|
"repo": "nix-cargo-integration",
|
||||||
"rev": "432d8504a32232e8d74710024d5bf5cc31767651",
|
"rev": "a84a2137a396f303978f1d48341e0390b0e16a8b",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
@ -55,11 +72,11 @@
|
||||||
},
|
},
|
||||||
"nixpkgs": {
|
"nixpkgs": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1631206977,
|
"lastModified": 1634782485,
|
||||||
"narHash": "sha256-o3Dct9aJ5ht5UaTUBzXrRcK1RZt2eG5/xSlWJuUCVZM=",
|
"narHash": "sha256-psfh4OQSokGXG0lpq3zKFbhOo3QfoeudRcaUnwMRkQo=",
|
||||||
"owner": "nixos",
|
"owner": "nixos",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "4f6d8095fd51954120a1d08ea5896fe42dc3923b",
|
"rev": "34ad3ffe08adfca17fcb4e4a47bb5f3b113687be",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
@ -69,21 +86,40 @@
|
||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"nixpkgs_2": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1628186154,
|
||||||
|
"narHash": "sha256-r2d0wvywFnL9z4iptztdFMhaUIAaGzrSs7kSok0PgmE=",
|
||||||
|
"owner": "NixOS",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"rev": "06552b72346632b6943c8032e57e702ea12413bf",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "NixOS",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
"root": {
|
"root": {
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"flakeCompat": "flakeCompat",
|
"flakeCompat": "flakeCompat",
|
||||||
"nixCargoIntegration": "nixCargoIntegration",
|
"nixCargoIntegration": "nixCargoIntegration",
|
||||||
"nixpkgs": "nixpkgs"
|
"nixpkgs": "nixpkgs",
|
||||||
|
"rust-overlay": "rust-overlay"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"rustOverlay": {
|
"rust-overlay": {
|
||||||
"flake": false,
|
"inputs": {
|
||||||
|
"flake-utils": "flake-utils",
|
||||||
|
"nixpkgs": "nixpkgs_2"
|
||||||
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1631240108,
|
"lastModified": 1634869268,
|
||||||
"narHash": "sha256-ffsTkAGyQLxu4E28nVcqwc8xFL/H1UEwrRw2ITI43Aw=",
|
"narHash": "sha256-RVAcEFlFU3877Mm4q/nbXGEYTDg/wQNhzmXGMTV6wBs=",
|
||||||
"owner": "oxalica",
|
"owner": "oxalica",
|
||||||
"repo": "rust-overlay",
|
"repo": "rust-overlay",
|
||||||
"rev": "3a29d5e726b855d9463eb5dfe04f1ec14d413289",
|
"rev": "c02c2d86354327317546501af001886fbb53d374",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
|
|
@ -3,9 +3,11 @@
|
||||||
|
|
||||||
inputs = {
|
inputs = {
|
||||||
nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";
|
nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";
|
||||||
|
rust-overlay.url = "github:oxalica/rust-overlay";
|
||||||
nixCargoIntegration = {
|
nixCargoIntegration = {
|
||||||
url = "github:yusdacra/nix-cargo-integration";
|
url = "github:yusdacra/nix-cargo-integration";
|
||||||
inputs.nixpkgs.follows = "nixpkgs";
|
inputs.nixpkgs.follows = "nixpkgs";
|
||||||
|
inputs.rustOverlay.follows = "rust-overlay";
|
||||||
};
|
};
|
||||||
flakeCompat = {
|
flakeCompat = {
|
||||||
url = "github:edolstra/flake-compat";
|
url = "github:edolstra/flake-compat";
|
||||||
|
@ -61,7 +63,7 @@
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
shell = common: prev: {
|
shell = common: prev: {
|
||||||
packages = prev.packages ++ (with common.pkgs; [ lld_10 lldb cargo-tarpaulin ]);
|
packages = prev.packages ++ (with common.pkgs; [ lld_12 lldb cargo-tarpaulin ]);
|
||||||
env = prev.env ++ [
|
env = prev.env ++ [
|
||||||
{ name = "HELIX_RUNTIME"; eval = "$PWD/runtime"; }
|
{ name = "HELIX_RUNTIME"; eval = "$PWD/runtime"; }
|
||||||
{ name = "RUST_BACKTRACE"; value = "1"; }
|
{ name = "RUST_BACKTRACE"; value = "1"; }
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
[package]
|
[package]
|
||||||
name = "helix-core"
|
name = "helix-core"
|
||||||
version = "0.4.1"
|
version = "0.5.0"
|
||||||
authors = ["Blaž Hrastnik <blaz@mxxn.io>"]
|
authors = ["Blaž Hrastnik <blaz@mxxn.io>"]
|
||||||
edition = "2018"
|
edition = "2021"
|
||||||
license = "MPL-2.0"
|
license = "MPL-2.0"
|
||||||
description = "Helix editor core editing primitives"
|
description = "Helix editor core editing primitives"
|
||||||
categories = ["editor"]
|
categories = ["editor"]
|
||||||
|
@ -13,7 +13,7 @@ include = ["src/**/*", "README.md"]
|
||||||
[features]
|
[features]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
helix-syntax = { version = "0.4", path = "../helix-syntax" }
|
helix-syntax = { version = "0.5", path = "../helix-syntax" }
|
||||||
|
|
||||||
ropey = "1.3"
|
ropey = "1.3"
|
||||||
smallvec = "1.7"
|
smallvec = "1.7"
|
||||||
|
@ -27,6 +27,7 @@ once_cell = "1.8"
|
||||||
arc-swap = "1"
|
arc-swap = "1"
|
||||||
regex = "1"
|
regex = "1"
|
||||||
|
|
||||||
|
log = "0.4"
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
serde_json = "1.0"
|
serde_json = "1.0"
|
||||||
toml = "0.5"
|
toml = "0.5"
|
||||||
|
|
|
@ -1,3 +1,6 @@
|
||||||
|
//! When typing the opening character of one of the possible pairs defined below,
|
||||||
|
//! this module provides the functionality to insert the paired closing character.
|
||||||
|
|
||||||
use crate::{Range, Rope, Selection, Tendril, Transaction};
|
use crate::{Range, Rope, Selection, Tendril, Transaction};
|
||||||
use smallvec::SmallVec;
|
use smallvec::SmallVec;
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
//! Utility functions to categorize a `char`.
|
||||||
|
|
||||||
use crate::LineEnding;
|
use crate::LineEnding;
|
||||||
|
|
||||||
#[derive(Debug, Eq, PartialEq)]
|
#[derive(Debug, Eq, PartialEq)]
|
||||||
|
|
|
@ -1,3 +1,6 @@
|
||||||
|
//! This module contains the functionality toggle comments on lines over the selection
|
||||||
|
//! using the comment character defined in the user's `languages.toml`
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
find_first_non_whitespace_char, Change, Rope, RopeSlice, Selection, Tendril, Transaction,
|
find_first_non_whitespace_char, Change, Rope, RopeSlice, Selection, Tendril, Transaction,
|
||||||
};
|
};
|
||||||
|
@ -60,7 +63,7 @@ pub fn toggle_line_comments(doc: &Rope, selection: &Selection, token: Option<&st
|
||||||
let token = token.unwrap_or("//");
|
let token = token.unwrap_or("//");
|
||||||
let comment = Tendril::from(format!("{} ", token));
|
let comment = Tendril::from(format!("{} ", token));
|
||||||
|
|
||||||
let mut lines: Vec<usize> = Vec::new();
|
let mut lines: Vec<usize> = Vec::with_capacity(selection.len());
|
||||||
|
|
||||||
let mut min_next_line = 0;
|
let mut min_next_line = 0;
|
||||||
for selection in selection {
|
for selection in selection {
|
||||||
|
|
|
@ -1,3 +1,6 @@
|
||||||
|
//! LSP diagnostic utility types.
|
||||||
|
|
||||||
|
/// Describes the severity level of a [`Diagnostic`].
|
||||||
#[derive(Debug, Eq, PartialEq)]
|
#[derive(Debug, Eq, PartialEq)]
|
||||||
pub enum Severity {
|
pub enum Severity {
|
||||||
Error,
|
Error,
|
||||||
|
@ -6,12 +9,14 @@ pub enum Severity {
|
||||||
Hint,
|
Hint,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
/// A range of `char`s within the text.
|
||||||
|
#[derive(Debug, Clone, Copy, PartialOrd, Ord, PartialEq, Eq)]
|
||||||
pub struct Range {
|
pub struct Range {
|
||||||
pub start: usize,
|
pub start: usize,
|
||||||
pub end: usize,
|
pub end: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Corresponds to [`lsp_types::Diagnostic`](https://docs.rs/lsp-types/0.91.0/lsp_types/struct.Diagnostic.html)
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Diagnostic {
|
pub struct Diagnostic {
|
||||||
pub range: Range,
|
pub range: Range,
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
// Based on https://github.com/cessen/led/blob/c4fa72405f510b7fd16052f90a598c429b3104a6/src/graphemes.rs
|
//! 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::{iter::Chunks, str_utils::byte_to_char_idx, RopeSlice};
|
||||||
use unicode_segmentation::{GraphemeCursor, GraphemeIncomplete};
|
use unicode_segmentation::{GraphemeCursor, GraphemeIncomplete};
|
||||||
use unicode_width::UnicodeWidthStr;
|
use unicode_width::UnicodeWidthStr;
|
||||||
|
|
|
@ -4,48 +4,50 @@ use regex::Regex;
|
||||||
use std::num::NonZeroUsize;
|
use std::num::NonZeroUsize;
|
||||||
use std::time::{Duration, Instant};
|
use std::time::{Duration, Instant};
|
||||||
|
|
||||||
// Stores the history of changes to a buffer.
|
/// Stores the history of changes to a buffer.
|
||||||
//
|
///
|
||||||
// Currently the history is represented as a vector of revisions. The vector
|
/// Currently the history is represented as a vector of revisions. The vector
|
||||||
// always has at least one element: the empty root revision. Each revision
|
/// always has at least one element: the empty root revision. Each revision
|
||||||
// with the exception of the root has a parent revision, a [Transaction]
|
/// with the exception of the root has a parent revision, a [Transaction]
|
||||||
// that can be applied to its parent to transition from the parent to itself,
|
/// that can be applied to its parent to transition from the parent to itself,
|
||||||
// and an inversion of that transaction to transition from the parent to its
|
/// and an inversion of that transaction to transition from the parent to its
|
||||||
// latest child.
|
/// latest child.
|
||||||
//
|
///
|
||||||
// When using `u` to undo a change, an inverse of the stored transaction will
|
/// When using `u` to undo a change, an inverse of the stored transaction will
|
||||||
// be applied which will transition the buffer to the parent state.
|
/// be applied which will transition the buffer to the parent state.
|
||||||
//
|
///
|
||||||
// Each revision with the exception of the last in the vector also has a
|
/// Each revision with the exception of the last in the vector also has a
|
||||||
// last child revision. When using `U` to redo a change, the last child transaction
|
/// last child revision. When using `U` to redo a change, the last child transaction
|
||||||
// will be applied to the current state of the buffer.
|
/// will be applied to the current state of the buffer.
|
||||||
//
|
///
|
||||||
// The current revision is the one currently displayed in the buffer.
|
/// The current revision is the one currently displayed in the buffer.
|
||||||
//
|
///
|
||||||
// Commiting a new revision to the history will update the last child of the
|
/// Commiting a new revision to the history will update the last child of the
|
||||||
// current revision, and push a new revision to the end of the vector.
|
/// current revision, and push a new revision to the end of the vector.
|
||||||
//
|
///
|
||||||
// Revisions are commited with a timestamp. :earlier and :later can be used
|
/// Revisions are commited with a timestamp. :earlier and :later can be used
|
||||||
// to jump to the closest revision to a moment in time relative to the timestamp
|
/// to jump to the closest revision to a moment in time relative to the timestamp
|
||||||
// of the current revision plus (:later) or minus (:earlier) the duration
|
/// of the current revision plus (:later) or minus (:earlier) the duration
|
||||||
// given to the command. If a single integer is given, the editor will instead
|
/// given to the command. If a single integer is given, the editor will instead
|
||||||
// jump the given number of revisions in the vector.
|
/// jump the given number of revisions in the vector.
|
||||||
//
|
///
|
||||||
// Limitations:
|
/// Limitations:
|
||||||
// * Changes in selections currently don't commit history changes. The selection
|
/// * Changes in selections currently don't commit history changes. The selection
|
||||||
// will only be updated to the state after a commited buffer change.
|
/// will only be updated to the state after a commited buffer change.
|
||||||
// * The vector of history revisions is currently unbounded. This might
|
/// * The vector of history revisions is currently unbounded. This might
|
||||||
// cause the memory consumption to grow significantly large during long
|
/// cause the memory consumption to grow significantly large during long
|
||||||
// editing sessions.
|
/// editing sessions.
|
||||||
// * Because delete transactions currently don't store the text that they
|
/// * Because delete transactions currently don't store the text that they
|
||||||
// delete, we also store an inversion of the transaction.
|
/// delete, we also store an inversion of the transaction.
|
||||||
|
///
|
||||||
|
/// Using time to navigate the history: <https://github.com/helix-editor/helix/pull/194>
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct History {
|
pub struct History {
|
||||||
revisions: Vec<Revision>,
|
revisions: Vec<Revision>,
|
||||||
current: usize,
|
current: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
// A single point in history. See [History] for more information.
|
/// A single point in history. See [History] for more information.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
struct Revision {
|
struct Revision {
|
||||||
parent: usize,
|
parent: usize,
|
||||||
|
@ -111,6 +113,7 @@ impl History {
|
||||||
self.current == 0
|
self.current == 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Undo the last edit.
|
||||||
pub fn undo(&mut self) -> Option<&Transaction> {
|
pub fn undo(&mut self) -> Option<&Transaction> {
|
||||||
if self.at_root() {
|
if self.at_root() {
|
||||||
return None;
|
return None;
|
||||||
|
@ -121,6 +124,7 @@ impl History {
|
||||||
Some(¤t_revision.inversion)
|
Some(¤t_revision.inversion)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Redo the last edit.
|
||||||
pub fn redo(&mut self) -> Option<&Transaction> {
|
pub fn redo(&mut self) -> Option<&Transaction> {
|
||||||
let current_revision = &self.revisions[self.current];
|
let current_revision = &self.revisions[self.current];
|
||||||
let last_child = current_revision.last_child?;
|
let last_child = current_revision.last_child?;
|
||||||
|
@ -147,8 +151,8 @@ impl History {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// List of nodes on the way from `n` to 'a`. Doesn`t include `a`.
|
/// List of nodes on the way from `n` to 'a`. Doesn`t include `a`.
|
||||||
// Includes `n` unless `a == n`. `a` must be an ancestor of `n`.
|
/// Includes `n` unless `a == n`. `a` must be an ancestor of `n`.
|
||||||
fn path_up(&self, mut n: usize, a: usize) -> Vec<usize> {
|
fn path_up(&self, mut n: usize, a: usize) -> Vec<usize> {
|
||||||
let mut path = Vec::new();
|
let mut path = Vec::new();
|
||||||
while n != a {
|
while n != a {
|
||||||
|
@ -158,6 +162,7 @@ impl History {
|
||||||
path
|
path
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Create a [`Transaction`] that will jump to a specific revision in the history.
|
||||||
fn jump_to(&mut self, to: usize) -> Vec<Transaction> {
|
fn jump_to(&mut self, to: usize) -> Vec<Transaction> {
|
||||||
let lca = self.lowest_common_ancestor(self.current, to);
|
let lca = self.lowest_common_ancestor(self.current, to);
|
||||||
let up = self.path_up(self.current, lca);
|
let up = self.path_up(self.current, lca);
|
||||||
|
@ -171,10 +176,12 @@ impl History {
|
||||||
up_txns.chain(down_txns).collect()
|
up_txns.chain(down_txns).collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Creates a [`Transaction`] that will undo `delta` revisions.
|
||||||
fn jump_backward(&mut self, delta: usize) -> Vec<Transaction> {
|
fn jump_backward(&mut self, delta: usize) -> Vec<Transaction> {
|
||||||
self.jump_to(self.current.saturating_sub(delta))
|
self.jump_to(self.current.saturating_sub(delta))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Creates a [`Transaction`] that will redo `delta` revisions.
|
||||||
fn jump_forward(&mut self, delta: usize) -> Vec<Transaction> {
|
fn jump_forward(&mut self, delta: usize) -> Vec<Transaction> {
|
||||||
self.jump_to(
|
self.jump_to(
|
||||||
self.current
|
self.current
|
||||||
|
@ -183,7 +190,7 @@ impl History {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Helper for a binary search case below.
|
/// Helper for a binary search case below.
|
||||||
fn revision_closer_to_instant(&self, i: usize, instant: Instant) -> usize {
|
fn revision_closer_to_instant(&self, i: usize, instant: Instant) -> usize {
|
||||||
let dur_im1 = instant.duration_since(self.revisions[i - 1].timestamp);
|
let dur_im1 = instant.duration_since(self.revisions[i - 1].timestamp);
|
||||||
let dur_i = self.revisions[i].timestamp.duration_since(instant);
|
let dur_i = self.revisions[i].timestamp.duration_since(instant);
|
||||||
|
@ -194,6 +201,8 @@ impl History {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Creates a [`Transaction`] that will match a revision created at around
|
||||||
|
/// `instant`.
|
||||||
fn jump_instant(&mut self, instant: Instant) -> Vec<Transaction> {
|
fn jump_instant(&mut self, instant: Instant) -> Vec<Transaction> {
|
||||||
let search_result = self
|
let search_result = self
|
||||||
.revisions
|
.revisions
|
||||||
|
@ -209,6 +218,8 @@ impl History {
|
||||||
self.jump_to(revision)
|
self.jump_to(revision)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Creates a [`Transaction`] that will match a revision created `duration` ago
|
||||||
|
/// from the timestamp of current revision.
|
||||||
fn jump_duration_backward(&mut self, duration: Duration) -> Vec<Transaction> {
|
fn jump_duration_backward(&mut self, duration: Duration) -> Vec<Transaction> {
|
||||||
match self.revisions[self.current].timestamp.checked_sub(duration) {
|
match self.revisions[self.current].timestamp.checked_sub(duration) {
|
||||||
Some(instant) => self.jump_instant(instant),
|
Some(instant) => self.jump_instant(instant),
|
||||||
|
@ -216,6 +227,8 @@ impl History {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Creates a [`Transaction`] that will match a revision created `duration` in
|
||||||
|
/// the future from the timestamp of the current revision.
|
||||||
fn jump_duration_forward(&mut self, duration: Duration) -> Vec<Transaction> {
|
fn jump_duration_forward(&mut self, duration: Duration) -> Vec<Transaction> {
|
||||||
match self.revisions[self.current].timestamp.checked_add(duration) {
|
match self.revisions[self.current].timestamp.checked_add(duration) {
|
||||||
Some(instant) => self.jump_instant(instant),
|
Some(instant) => self.jump_instant(instant),
|
||||||
|
@ -223,6 +236,7 @@ impl History {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Creates an undo [`Transaction`].
|
||||||
pub fn earlier(&mut self, uk: UndoKind) -> Vec<Transaction> {
|
pub fn earlier(&mut self, uk: UndoKind) -> Vec<Transaction> {
|
||||||
use UndoKind::*;
|
use UndoKind::*;
|
||||||
match uk {
|
match uk {
|
||||||
|
@ -231,6 +245,7 @@ impl History {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Creates a redo [`Transaction`].
|
||||||
pub fn later(&mut self, uk: UndoKind) -> Vec<Transaction> {
|
pub fn later(&mut self, uk: UndoKind) -> Vec<Transaction> {
|
||||||
use UndoKind::*;
|
use UndoKind::*;
|
||||||
match uk {
|
match uk {
|
||||||
|
@ -240,13 +255,14 @@ impl History {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Whether to undo by a number of edits or a duration of time.
|
||||||
#[derive(Debug, PartialEq)]
|
#[derive(Debug, PartialEq)]
|
||||||
pub enum UndoKind {
|
pub enum UndoKind {
|
||||||
Steps(usize),
|
Steps(usize),
|
||||||
TimePeriod(std::time::Duration),
|
TimePeriod(std::time::Duration),
|
||||||
}
|
}
|
||||||
|
|
||||||
// A subset of sytemd.time time span syntax units.
|
/// A subset of sytemd.time time span syntax units.
|
||||||
const TIME_UNITS: &[(&[&str], &str, u64)] = &[
|
const TIME_UNITS: &[(&[&str], &str, u64)] = &[
|
||||||
(&["seconds", "second", "sec", "s"], "seconds", 1),
|
(&["seconds", "second", "sec", "s"], "seconds", 1),
|
||||||
(&["minutes", "minute", "min", "m"], "minutes", 60),
|
(&["minutes", "minute", "min", "m"], "minutes", 60),
|
||||||
|
@ -254,11 +270,20 @@ const TIME_UNITS: &[(&[&str], &str, u64)] = &[
|
||||||
(&["days", "day", "d"], "days", 24 * 60 * 60),
|
(&["days", "day", "d"], "days", 24 * 60 * 60),
|
||||||
];
|
];
|
||||||
|
|
||||||
|
/// Checks if the duration input can be turned into a valid duration. It must be a
|
||||||
|
/// positive integer and denote the [unit of time.](`TIME_UNITS`)
|
||||||
|
/// Examples of valid durations:
|
||||||
|
/// * `5 sec`
|
||||||
|
/// * `5 min`
|
||||||
|
/// * `5 hr`
|
||||||
|
/// * `5 days`
|
||||||
static DURATION_VALIDATION_REGEX: Lazy<Regex> =
|
static DURATION_VALIDATION_REGEX: Lazy<Regex> =
|
||||||
Lazy::new(|| Regex::new(r"^(?:\d+\s*[a-z]+\s*)+$").unwrap());
|
Lazy::new(|| Regex::new(r"^(?:\d+\s*[a-z]+\s*)+$").unwrap());
|
||||||
|
|
||||||
|
/// Captures both the number and unit as separate capture groups.
|
||||||
static NUMBER_UNIT_REGEX: Lazy<Regex> = Lazy::new(|| Regex::new(r"(\d+)\s*([a-z]+)").unwrap());
|
static NUMBER_UNIT_REGEX: Lazy<Regex> = Lazy::new(|| Regex::new(r"(\d+)\s*([a-z]+)").unwrap());
|
||||||
|
|
||||||
|
/// Parse a string (e.g. "5 sec") and try to convert it into a [`Duration`].
|
||||||
fn parse_human_duration(s: &str) -> Result<Duration, String> {
|
fn parse_human_duration(s: &str) -> Result<Duration, String> {
|
||||||
if !DURATION_VALIDATION_REGEX.is_match(s) {
|
if !DURATION_VALIDATION_REGEX.is_match(s) {
|
||||||
return Err("duration should be composed \
|
return Err("duration should be composed \
|
||||||
|
|
|
@ -464,6 +464,7 @@ where
|
||||||
unit: String::from(" "),
|
unit: String::from(" "),
|
||||||
}),
|
}),
|
||||||
indent_query: OnceCell::new(),
|
indent_query: OnceCell::new(),
|
||||||
|
textobject_query: OnceCell::new(),
|
||||||
debugger: None,
|
debugger: None,
|
||||||
}],
|
}],
|
||||||
});
|
});
|
||||||
|
|
|
@ -35,6 +35,7 @@ pub fn find_first_non_whitespace_char(line: RopeSlice) -> Option<usize> {
|
||||||
line.chars().position(|ch| !ch.is_whitespace())
|
line.chars().position(|ch| !ch.is_whitespace())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Find `.git` root.
|
||||||
pub fn find_root(root: Option<&str>) -> Option<std::path::PathBuf> {
|
pub fn find_root(root: Option<&str>) -> Option<std::path::PathBuf> {
|
||||||
let current_dir = std::env::current_dir().expect("unable to determine current directory");
|
let current_dir = std::env::current_dir().expect("unable to determine current directory");
|
||||||
|
|
||||||
|
@ -193,7 +194,7 @@ pub use tendril::StrTendril as Tendril;
|
||||||
pub use {regex, tree_sitter};
|
pub use {regex, tree_sitter};
|
||||||
|
|
||||||
pub use graphemes::RopeGraphemes;
|
pub use graphemes::RopeGraphemes;
|
||||||
pub use position::{coords_at_pos, pos_at_coords, Position};
|
pub use position::{coords_at_pos, pos_at_coords, visual_coords_at_pos, Position};
|
||||||
pub use selection::{Range, Selection};
|
pub use selection::{Range, Selection};
|
||||||
pub use smallvec::{smallvec, SmallVec};
|
pub use smallvec::{smallvec, SmallVec};
|
||||||
pub use syntax::Syntax;
|
pub use syntax::Syntax;
|
||||||
|
|
|
@ -20,7 +20,7 @@ pub enum LineEnding {
|
||||||
|
|
||||||
impl LineEnding {
|
impl LineEnding {
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn len_chars(&self) -> usize {
|
pub const fn len_chars(&self) -> usize {
|
||||||
match self {
|
match self {
|
||||||
Self::Crlf => 2,
|
Self::Crlf => 2,
|
||||||
_ => 1,
|
_ => 1,
|
||||||
|
@ -28,7 +28,7 @@ impl LineEnding {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn as_str(&self) -> &'static str {
|
pub const fn as_str(&self) -> &'static str {
|
||||||
match self {
|
match self {
|
||||||
Self::Crlf => "\u{000D}\u{000A}",
|
Self::Crlf => "\u{000D}\u{000A}",
|
||||||
Self::LF => "\u{000A}",
|
Self::LF => "\u{000A}",
|
||||||
|
@ -42,7 +42,7 @@ impl LineEnding {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn from_char(ch: char) -> Option<LineEnding> {
|
pub const fn from_char(ch: char) -> Option<LineEnding> {
|
||||||
match ch {
|
match ch {
|
||||||
'\u{000A}' => Some(LineEnding::LF),
|
'\u{000A}' => Some(LineEnding::LF),
|
||||||
'\u{000B}' => Some(LineEnding::VT),
|
'\u{000B}' => Some(LineEnding::VT),
|
||||||
|
|
|
@ -53,6 +53,10 @@ pub fn move_vertically(
|
||||||
let pos = range.cursor(slice);
|
let pos = range.cursor(slice);
|
||||||
|
|
||||||
// Compute the current position's 2d coordinates.
|
// Compute the current position's 2d coordinates.
|
||||||
|
// TODO: switch this to use `visual_coords_at_pos` rather than
|
||||||
|
// `coords_at_pos` as this will cause a jerky movement when the visual
|
||||||
|
// position does not match, like moving from a line with tabs/CJK to
|
||||||
|
// a line without
|
||||||
let Position { row, col } = coords_at_pos(slice, pos);
|
let Position { row, col } = coords_at_pos(slice, pos);
|
||||||
let horiz = range.horiz.unwrap_or(col as u32);
|
let horiz = range.horiz.unwrap_or(col as u32);
|
||||||
|
|
||||||
|
|
|
@ -13,8 +13,13 @@ pub fn expand_selection(syntax: &Syntax, text: RopeSlice, selection: &Selection)
|
||||||
let parent = match tree
|
let parent = match tree
|
||||||
.root_node()
|
.root_node()
|
||||||
.descendant_for_byte_range(from, to)
|
.descendant_for_byte_range(from, to)
|
||||||
.and_then(|node| node.parent())
|
.and_then(|node| {
|
||||||
{
|
if node.child_count() == 0 || (node.start_byte() == from && node.end_byte() == to) {
|
||||||
|
node.parent()
|
||||||
|
} else {
|
||||||
|
Some(node)
|
||||||
|
}
|
||||||
|
}) {
|
||||||
Some(parent) => parent,
|
Some(parent) => parent,
|
||||||
None => return range,
|
None => return range,
|
||||||
};
|
};
|
||||||
|
|
|
@ -2,6 +2,7 @@ use crate::{
|
||||||
chars::char_is_line_ending,
|
chars::char_is_line_ending,
|
||||||
graphemes::{ensure_grapheme_boundary_prev, RopeGraphemes},
|
graphemes::{ensure_grapheme_boundary_prev, RopeGraphemes},
|
||||||
line_ending::line_end_char_index,
|
line_ending::line_end_char_index,
|
||||||
|
unicode::width::UnicodeWidthChar,
|
||||||
RopeSlice,
|
RopeSlice,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -54,11 +55,8 @@ impl From<Position> for tree_sitter::Point {
|
||||||
}
|
}
|
||||||
/// Convert a character index to (line, column) coordinates.
|
/// Convert a character index to (line, column) coordinates.
|
||||||
///
|
///
|
||||||
/// TODO: this should be split into two methods: one for visual
|
/// column in `char` count which can be used for row:column display in
|
||||||
/// row/column, and one for "objective" row/column (possibly with
|
/// status line. See [`visual_coords_at_pos`] for a visual one.
|
||||||
/// the column specified in `char`s). The former would be used
|
|
||||||
/// for cursor movement, and the latter would be used for e.g. the
|
|
||||||
/// row:column display in the status line.
|
|
||||||
pub fn coords_at_pos(text: RopeSlice, pos: usize) -> Position {
|
pub fn coords_at_pos(text: RopeSlice, pos: usize) -> Position {
|
||||||
let line = text.char_to_line(pos);
|
let line = text.char_to_line(pos);
|
||||||
|
|
||||||
|
@ -69,6 +67,28 @@ pub fn coords_at_pos(text: RopeSlice, pos: usize) -> Position {
|
||||||
Position::new(line, col)
|
Position::new(line, col)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Convert a character index to (line, column) coordinates visually.
|
||||||
|
///
|
||||||
|
/// Takes \t, double-width characters (CJK) into account as well as text
|
||||||
|
/// not in the document in the future.
|
||||||
|
/// See [`coords_at_pos`] for an "objective" one.
|
||||||
|
pub fn visual_coords_at_pos(text: RopeSlice, pos: usize, tab_width: usize) -> Position {
|
||||||
|
let line = text.char_to_line(pos);
|
||||||
|
|
||||||
|
let line_start = text.line_to_char(line);
|
||||||
|
let pos = ensure_grapheme_boundary_prev(text, pos);
|
||||||
|
let col = text
|
||||||
|
.slice(line_start..pos)
|
||||||
|
.chars()
|
||||||
|
.flat_map(|c| match c {
|
||||||
|
'\t' => Some(tab_width),
|
||||||
|
c => UnicodeWidthChar::width(c),
|
||||||
|
})
|
||||||
|
.sum();
|
||||||
|
|
||||||
|
Position::new(line, col)
|
||||||
|
}
|
||||||
|
|
||||||
/// Convert (line, column) coordinates to a character index.
|
/// Convert (line, column) coordinates to a character index.
|
||||||
///
|
///
|
||||||
/// If the `line` coordinate is beyond the end of the file, the EOF
|
/// If the `line` coordinate is beyond the end of the file, the EOF
|
||||||
|
@ -130,7 +150,6 @@ mod test {
|
||||||
assert_eq!(coords_at_pos(slice, 10), (1, 4).into()); // position on d
|
assert_eq!(coords_at_pos(slice, 10), (1, 4).into()); // position on d
|
||||||
|
|
||||||
// Test with wide characters.
|
// Test with wide characters.
|
||||||
// TODO: account for character width.
|
|
||||||
let text = Rope::from("今日はいい\n");
|
let text = Rope::from("今日はいい\n");
|
||||||
let slice = text.slice(..);
|
let slice = text.slice(..);
|
||||||
assert_eq!(coords_at_pos(slice, 0), (0, 0).into());
|
assert_eq!(coords_at_pos(slice, 0), (0, 0).into());
|
||||||
|
@ -151,7 +170,6 @@ mod test {
|
||||||
assert_eq!(coords_at_pos(slice, 9), (1, 0).into());
|
assert_eq!(coords_at_pos(slice, 9), (1, 0).into());
|
||||||
|
|
||||||
// Test with wide-character grapheme clusters.
|
// Test with wide-character grapheme clusters.
|
||||||
// TODO: account for character width.
|
|
||||||
let text = Rope::from("किमपि\n");
|
let text = Rope::from("किमपि\n");
|
||||||
let slice = text.slice(..);
|
let slice = text.slice(..);
|
||||||
assert_eq!(coords_at_pos(slice, 0), (0, 0).into());
|
assert_eq!(coords_at_pos(slice, 0), (0, 0).into());
|
||||||
|
@ -161,7 +179,6 @@ mod test {
|
||||||
assert_eq!(coords_at_pos(slice, 6), (1, 0).into());
|
assert_eq!(coords_at_pos(slice, 6), (1, 0).into());
|
||||||
|
|
||||||
// Test with tabs.
|
// Test with tabs.
|
||||||
// Todo: account for tab stops.
|
|
||||||
let text = Rope::from("\tHello\n");
|
let text = Rope::from("\tHello\n");
|
||||||
let slice = text.slice(..);
|
let slice = text.slice(..);
|
||||||
assert_eq!(coords_at_pos(slice, 0), (0, 0).into());
|
assert_eq!(coords_at_pos(slice, 0), (0, 0).into());
|
||||||
|
@ -169,6 +186,54 @@ mod test {
|
||||||
assert_eq!(coords_at_pos(slice, 2), (0, 2).into());
|
assert_eq!(coords_at_pos(slice, 2), (0, 2).into());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_visual_coords_at_pos() {
|
||||||
|
let text = Rope::from("ḧëḷḷö\nẅöṛḷḋ");
|
||||||
|
let slice = text.slice(..);
|
||||||
|
assert_eq!(visual_coords_at_pos(slice, 0, 8), (0, 0).into());
|
||||||
|
assert_eq!(visual_coords_at_pos(slice, 5, 8), (0, 5).into()); // position on \n
|
||||||
|
assert_eq!(visual_coords_at_pos(slice, 6, 8), (1, 0).into()); // position on w
|
||||||
|
assert_eq!(visual_coords_at_pos(slice, 7, 8), (1, 1).into()); // position on o
|
||||||
|
assert_eq!(visual_coords_at_pos(slice, 10, 8), (1, 4).into()); // position on d
|
||||||
|
|
||||||
|
// Test with wide characters.
|
||||||
|
let text = Rope::from("今日はいい\n");
|
||||||
|
let slice = text.slice(..);
|
||||||
|
assert_eq!(visual_coords_at_pos(slice, 0, 8), (0, 0).into());
|
||||||
|
assert_eq!(visual_coords_at_pos(slice, 1, 8), (0, 2).into());
|
||||||
|
assert_eq!(visual_coords_at_pos(slice, 2, 8), (0, 4).into());
|
||||||
|
assert_eq!(visual_coords_at_pos(slice, 3, 8), (0, 6).into());
|
||||||
|
assert_eq!(visual_coords_at_pos(slice, 4, 8), (0, 8).into());
|
||||||
|
assert_eq!(visual_coords_at_pos(slice, 5, 8), (0, 10).into());
|
||||||
|
assert_eq!(visual_coords_at_pos(slice, 6, 8), (1, 0).into());
|
||||||
|
|
||||||
|
// Test with grapheme clusters.
|
||||||
|
let text = Rope::from("a̐éö̲\r\n");
|
||||||
|
let slice = text.slice(..);
|
||||||
|
assert_eq!(visual_coords_at_pos(slice, 0, 8), (0, 0).into());
|
||||||
|
assert_eq!(visual_coords_at_pos(slice, 2, 8), (0, 1).into());
|
||||||
|
assert_eq!(visual_coords_at_pos(slice, 4, 8), (0, 2).into());
|
||||||
|
assert_eq!(visual_coords_at_pos(slice, 7, 8), (0, 3).into());
|
||||||
|
assert_eq!(visual_coords_at_pos(slice, 9, 8), (1, 0).into());
|
||||||
|
|
||||||
|
// Test with wide-character grapheme clusters.
|
||||||
|
// TODO: account for cluster.
|
||||||
|
let text = Rope::from("किमपि\n");
|
||||||
|
let slice = text.slice(..);
|
||||||
|
assert_eq!(visual_coords_at_pos(slice, 0, 8), (0, 0).into());
|
||||||
|
assert_eq!(visual_coords_at_pos(slice, 2, 8), (0, 2).into());
|
||||||
|
assert_eq!(visual_coords_at_pos(slice, 3, 8), (0, 3).into());
|
||||||
|
assert_eq!(visual_coords_at_pos(slice, 5, 8), (0, 5).into());
|
||||||
|
assert_eq!(visual_coords_at_pos(slice, 6, 8), (1, 0).into());
|
||||||
|
|
||||||
|
// Test with tabs.
|
||||||
|
let text = Rope::from("\tHello\n");
|
||||||
|
let slice = text.slice(..);
|
||||||
|
assert_eq!(visual_coords_at_pos(slice, 0, 8), (0, 0).into());
|
||||||
|
assert_eq!(visual_coords_at_pos(slice, 1, 8), (0, 8).into());
|
||||||
|
assert_eq!(visual_coords_at_pos(slice, 2, 8), (0, 9).into());
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_pos_at_coords() {
|
fn test_pos_at_coords() {
|
||||||
let text = Rope::from("ḧëḷḷö\nẅöṛḷḋ");
|
let text = Rope::from("ḧëḷḷö\nẅöṛḷḋ");
|
||||||
|
|
|
@ -7,7 +7,7 @@ pub struct Register {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Register {
|
impl Register {
|
||||||
pub fn new(name: char) -> Self {
|
pub const fn new(name: char) -> Self {
|
||||||
Self {
|
Self {
|
||||||
name,
|
name,
|
||||||
values: Vec::new(),
|
values: Vec::new(),
|
||||||
|
@ -18,7 +18,7 @@ impl Register {
|
||||||
Self { name, values }
|
Self { name, values }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn name(&self) -> char {
|
pub const fn name(&self) -> char {
|
||||||
self.name
|
self.name
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -362,6 +362,11 @@ impl Selection {
|
||||||
|
|
||||||
/// Adds a new range to the selection and makes it the primary range.
|
/// Adds a new range to the selection and makes it the primary range.
|
||||||
pub fn remove(mut self, index: usize) -> Self {
|
pub fn remove(mut self, index: usize) -> Self {
|
||||||
|
assert!(
|
||||||
|
self.ranges.len() > 1,
|
||||||
|
"can't remove the last range from a selection!"
|
||||||
|
);
|
||||||
|
|
||||||
self.ranges.remove(index);
|
self.ranges.remove(index);
|
||||||
if index < self.primary_index || self.primary_index == self.ranges.len() {
|
if index < self.primary_index || self.primary_index == self.ranges.len() {
|
||||||
self.primary_index -= 1;
|
self.primary_index -= 1;
|
||||||
|
@ -369,6 +374,12 @@ impl Selection {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Replace a range in the selection with a new range.
|
||||||
|
pub fn replace(mut self, index: usize, range: Range) -> Self {
|
||||||
|
self.ranges[index] = range;
|
||||||
|
self.normalize()
|
||||||
|
}
|
||||||
|
|
||||||
/// Map selections over a set of changes. Useful for adjusting the selection position after
|
/// Map selections over a set of changes. Useful for adjusting the selection position after
|
||||||
/// applying changes to a document.
|
/// applying changes to a document.
|
||||||
pub fn map(self, changes: &ChangeSet) -> Self {
|
pub fn map(self, changes: &ChangeSet) -> Self {
|
||||||
|
|
|
@ -49,7 +49,7 @@ pub struct Configuration {
|
||||||
#[serde(rename_all = "kebab-case")]
|
#[serde(rename_all = "kebab-case")]
|
||||||
pub struct LanguageConfiguration {
|
pub struct LanguageConfiguration {
|
||||||
#[serde(rename = "name")]
|
#[serde(rename = "name")]
|
||||||
pub(crate) language_id: String,
|
pub language_id: String,
|
||||||
pub scope: String, // source.rust
|
pub scope: String, // source.rust
|
||||||
pub file_types: Vec<String>, // filename ends_with? <Gemfile, rb, etc>
|
pub file_types: Vec<String>, // filename ends_with? <Gemfile, rb, etc>
|
||||||
pub roots: Vec<String>, // these indicate project roots <.git, Cargo.toml>
|
pub roots: Vec<String>, // these indicate project roots <.git, Cargo.toml>
|
||||||
|
@ -76,6 +76,8 @@ pub struct LanguageConfiguration {
|
||||||
|
|
||||||
#[serde(skip)]
|
#[serde(skip)]
|
||||||
pub(crate) indent_query: OnceCell<Option<IndentQuery>>,
|
pub(crate) indent_query: OnceCell<Option<IndentQuery>>,
|
||||||
|
#[serde(skip)]
|
||||||
|
pub(crate) textobject_query: OnceCell<Option<TextObjectQuery>>,
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub debugger: Option<DebugAdapterConfig>,
|
pub debugger: Option<DebugAdapterConfig>,
|
||||||
}
|
}
|
||||||
|
@ -160,6 +162,32 @@ pub struct IndentQuery {
|
||||||
pub outdent: HashSet<String>,
|
pub outdent: HashSet<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct TextObjectQuery {
|
||||||
|
pub query: Query,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TextObjectQuery {
|
||||||
|
/// Run the query on the given node and return sub nodes which match given
|
||||||
|
/// capture ("function.inside", "class.around", etc).
|
||||||
|
pub fn capture_nodes<'a>(
|
||||||
|
&'a self,
|
||||||
|
capture_name: &str,
|
||||||
|
node: Node<'a>,
|
||||||
|
slice: RopeSlice<'a>,
|
||||||
|
cursor: &'a mut QueryCursor,
|
||||||
|
) -> Option<impl Iterator<Item = Node<'a>>> {
|
||||||
|
let capture_idx = self.query.capture_index_for_name(capture_name)?;
|
||||||
|
let captures = cursor.captures(&self.query, node, RopeProvider(slice));
|
||||||
|
|
||||||
|
captures
|
||||||
|
.filter_map(move |(mat, idx)| {
|
||||||
|
(mat.captures[idx].index == capture_idx).then(|| mat.captures[idx].node)
|
||||||
|
})
|
||||||
|
.into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn load_runtime_file(language: &str, filename: &str) -> Result<String, std::io::Error> {
|
fn load_runtime_file(language: &str, filename: &str) -> Result<String, std::io::Error> {
|
||||||
let path = crate::RUNTIME_DIR
|
let path = crate::RUNTIME_DIR
|
||||||
.join("queries")
|
.join("queries")
|
||||||
|
@ -208,13 +236,14 @@ impl LanguageConfiguration {
|
||||||
// highlights_query += "\n(ERROR) @error";
|
// highlights_query += "\n(ERROR) @error";
|
||||||
|
|
||||||
let injections_query = read_query(&language, "injections.scm");
|
let injections_query = read_query(&language, "injections.scm");
|
||||||
|
|
||||||
let locals_query = read_query(&language, "locals.scm");
|
let locals_query = read_query(&language, "locals.scm");
|
||||||
|
|
||||||
if highlights_query.is_empty() {
|
if highlights_query.is_empty() {
|
||||||
None
|
None
|
||||||
} else {
|
} else {
|
||||||
let language = get_language(&crate::RUNTIME_DIR, &self.language_id).ok()?;
|
let language = get_language(&crate::RUNTIME_DIR, &self.language_id)
|
||||||
|
.map_err(|e| log::info!("{}", e))
|
||||||
|
.ok()?;
|
||||||
let config = HighlightConfiguration::new(
|
let config = HighlightConfiguration::new(
|
||||||
language,
|
language,
|
||||||
&highlights_query,
|
&highlights_query,
|
||||||
|
@ -258,6 +287,18 @@ impl LanguageConfiguration {
|
||||||
.as_ref()
|
.as_ref()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn textobject_query(&self) -> Option<&TextObjectQuery> {
|
||||||
|
self.textobject_query
|
||||||
|
.get_or_init(|| -> Option<TextObjectQuery> {
|
||||||
|
let lang_name = self.language_id.to_ascii_lowercase();
|
||||||
|
let query_text = read_query(&lang_name, "textobjects.scm");
|
||||||
|
let lang = self.highlight_config.get()?.as_ref()?.language;
|
||||||
|
let query = Query::new(lang, &query_text).ok()?;
|
||||||
|
Some(TextObjectQuery { query })
|
||||||
|
})
|
||||||
|
.as_ref()
|
||||||
|
}
|
||||||
|
|
||||||
pub fn scope(&self) -> &str {
|
pub fn scope(&self) -> &str {
|
||||||
&self.scope
|
&self.scope
|
||||||
}
|
}
|
||||||
|
@ -451,7 +492,7 @@ impl Syntax {
|
||||||
|
|
||||||
/// Iterate over the highlighted regions for a given slice of source code.
|
/// Iterate over the highlighted regions for a given slice of source code.
|
||||||
pub fn highlight_iter<'a>(
|
pub fn highlight_iter<'a>(
|
||||||
&self,
|
&'a self,
|
||||||
source: RopeSlice<'a>,
|
source: RopeSlice<'a>,
|
||||||
range: Option<std::ops::Range<usize>>,
|
range: Option<std::ops::Range<usize>>,
|
||||||
cancellation_flag: Option<&'a AtomicUsize>,
|
cancellation_flag: Option<&'a AtomicUsize>,
|
||||||
|
@ -466,11 +507,10 @@ impl Syntax {
|
||||||
let highlighter = &mut ts_parser.borrow_mut();
|
let highlighter = &mut ts_parser.borrow_mut();
|
||||||
highlighter.cursors.pop().unwrap_or_else(QueryCursor::new)
|
highlighter.cursors.pop().unwrap_or_else(QueryCursor::new)
|
||||||
});
|
});
|
||||||
let tree_ref = unsafe { mem::transmute::<_, &'static Tree>(self.tree()) };
|
let tree_ref = self.tree();
|
||||||
let cursor_ref = unsafe { mem::transmute::<_, &'static mut QueryCursor>(&mut cursor) };
|
let cursor_ref = unsafe { mem::transmute::<_, &'static mut QueryCursor>(&mut cursor) };
|
||||||
let query_ref = unsafe { mem::transmute::<_, &'static Query>(&self.config.query) };
|
let query_ref = &self.config.query;
|
||||||
let config_ref =
|
let config_ref = self.config.as_ref();
|
||||||
unsafe { mem::transmute::<_, &'static HighlightConfiguration>(self.config.as_ref()) };
|
|
||||||
|
|
||||||
// if reusing cursors & no range this resets to whole range
|
// if reusing cursors & no range this resets to whole range
|
||||||
cursor_ref.set_byte_range(range.clone().unwrap_or(0..usize::MAX));
|
cursor_ref.set_byte_range(range.clone().unwrap_or(0..usize::MAX));
|
||||||
|
@ -582,39 +622,7 @@ impl LanguageLayer {
|
||||||
self.tree.as_ref(),
|
self.tree.as_ref(),
|
||||||
)
|
)
|
||||||
.ok_or(Error::Cancelled)?;
|
.ok_or(Error::Cancelled)?;
|
||||||
// unsafe { syntax.parser.set_cancellation_flag(None) };
|
|
||||||
// let mut cursor = syntax.cursors.pop().unwrap_or_else(QueryCursor::new);
|
|
||||||
|
|
||||||
// Process combined injections. (ERB, EJS, etc https://github.com/tree-sitter/tree-sitter/pull/526)
|
|
||||||
// if let Some(combined_injections_query) = &config.combined_injections_query {
|
|
||||||
// let mut injections_by_pattern_index =
|
|
||||||
// vec![(None, Vec::new(), false); combined_injections_query.pattern_count()];
|
|
||||||
// let matches =
|
|
||||||
// cursor.matches(combined_injections_query, tree.root_node(), RopeProvider(source));
|
|
||||||
// for mat in matches {
|
|
||||||
// let entry = &mut injections_by_pattern_index[mat.pattern_index];
|
|
||||||
// let (language_name, content_node, include_children) =
|
|
||||||
// injection_for_match(config, combined_injections_query, &mat, source);
|
|
||||||
// if language_name.is_some() {
|
|
||||||
// entry.0 = language_name;
|
|
||||||
// }
|
|
||||||
// if let Some(content_node) = content_node {
|
|
||||||
// entry.1.push(content_node);
|
|
||||||
// }
|
|
||||||
// entry.2 = include_children;
|
|
||||||
// }
|
|
||||||
// for (lang_name, content_nodes, includes_children) in injections_by_pattern_index {
|
|
||||||
// if let (Some(lang_name), false) = (lang_name, content_nodes.is_empty()) {
|
|
||||||
// if let Some(next_config) = (injection_callback)(lang_name) {
|
|
||||||
// let ranges =
|
|
||||||
// Self::intersect_ranges(&ranges, &content_nodes, includes_children);
|
|
||||||
// if !ranges.is_empty() {
|
|
||||||
// queue.push((next_config, depth + 1, ranges));
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
self.tree = Some(tree)
|
self.tree = Some(tree)
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
|
@ -1,9 +1,13 @@
|
||||||
|
use std::fmt::Display;
|
||||||
|
|
||||||
use ropey::RopeSlice;
|
use ropey::RopeSlice;
|
||||||
|
use tree_sitter::{Node, QueryCursor};
|
||||||
|
|
||||||
use crate::chars::{categorize_char, char_is_whitespace, CharCategory};
|
use crate::chars::{categorize_char, char_is_whitespace, CharCategory};
|
||||||
use crate::graphemes::next_grapheme_boundary;
|
use crate::graphemes::next_grapheme_boundary;
|
||||||
use crate::movement::Direction;
|
use crate::movement::Direction;
|
||||||
use crate::surround;
|
use crate::surround;
|
||||||
|
use crate::syntax::LanguageConfiguration;
|
||||||
use crate::Range;
|
use crate::Range;
|
||||||
|
|
||||||
fn find_word_boundary(slice: RopeSlice, mut pos: usize, direction: Direction) -> usize {
|
fn find_word_boundary(slice: RopeSlice, mut pos: usize, direction: Direction) -> usize {
|
||||||
|
@ -51,6 +55,15 @@ pub enum TextObject {
|
||||||
Inside,
|
Inside,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Display for TextObject {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
f.write_str(match self {
|
||||||
|
Self::Around => "around",
|
||||||
|
Self::Inside => "inside",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// count doesn't do anything yet
|
// count doesn't do anything yet
|
||||||
pub fn textobject_word(
|
pub fn textobject_word(
|
||||||
slice: RopeSlice,
|
slice: RopeSlice,
|
||||||
|
@ -108,6 +121,44 @@ pub fn textobject_surround(
|
||||||
.unwrap_or(range)
|
.unwrap_or(range)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Transform the given range to select text objects based on tree-sitter.
|
||||||
|
/// `object_name` is a query capture base name like "function", "class", etc.
|
||||||
|
/// `slice_tree` is the tree-sitter node corresponding to given text slice.
|
||||||
|
pub fn textobject_treesitter(
|
||||||
|
slice: RopeSlice,
|
||||||
|
range: Range,
|
||||||
|
textobject: TextObject,
|
||||||
|
object_name: &str,
|
||||||
|
slice_tree: Node,
|
||||||
|
lang_config: &LanguageConfiguration,
|
||||||
|
_count: usize,
|
||||||
|
) -> Range {
|
||||||
|
let get_range = move || -> Option<Range> {
|
||||||
|
let byte_pos = slice.char_to_byte(range.cursor(slice));
|
||||||
|
|
||||||
|
let capture_name = format!("{}.{}", object_name, textobject); // eg. function.inner
|
||||||
|
let mut cursor = QueryCursor::new();
|
||||||
|
let node = lang_config
|
||||||
|
.textobject_query()?
|
||||||
|
.capture_nodes(&capture_name, slice_tree, slice, &mut cursor)?
|
||||||
|
.filter(|node| node.byte_range().contains(&byte_pos))
|
||||||
|
.min_by_key(|node| node.byte_range().len())?;
|
||||||
|
|
||||||
|
let len = slice.len_bytes();
|
||||||
|
let start_byte = node.start_byte();
|
||||||
|
let end_byte = node.end_byte();
|
||||||
|
if start_byte >= len || end_byte >= len {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let start_char = slice.byte_to_char(start_byte);
|
||||||
|
let end_char = slice.byte_to_char(end_byte);
|
||||||
|
|
||||||
|
Some(Range::new(start_char, end_char))
|
||||||
|
};
|
||||||
|
get_range().unwrap_or(range)
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use super::TextObject::*;
|
use super::TextObject::*;
|
||||||
|
|
|
@ -132,6 +132,9 @@ impl ChangeSet {
|
||||||
if self.changes.is_empty() {
|
if self.changes.is_empty() {
|
||||||
return other;
|
return other;
|
||||||
}
|
}
|
||||||
|
if other.changes.is_empty() {
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
let len = self.changes.len();
|
let len = self.changes.len();
|
||||||
|
|
||||||
|
@ -465,6 +468,13 @@ impl Transaction {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn compose(mut self, other: Self) -> Self {
|
||||||
|
self.changes = self.changes.compose(other.changes);
|
||||||
|
// Other selection takes precedence
|
||||||
|
self.selection = other.selection;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
pub fn with_selection(mut self, selection: Selection) -> Self {
|
pub fn with_selection(mut self, selection: Selection) -> Self {
|
||||||
self.selection = Some(selection);
|
self.selection = Some(selection);
|
||||||
self
|
self
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "helix-dap"
|
name = "helix-dap"
|
||||||
version = "0.4.1"
|
version = "0.5.0"
|
||||||
authors = ["Blaž Hrastnik <blaz@mxxn.io>"]
|
authors = ["Blaž Hrastnik <blaz@mxxn.io>"]
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
license = "MPL-2.0"
|
license = "MPL-2.0"
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
[package]
|
[package]
|
||||||
name = "helix-lsp"
|
name = "helix-lsp"
|
||||||
version = "0.4.1"
|
version = "0.5.0"
|
||||||
authors = ["Blaž Hrastnik <blaz@mxxn.io>"]
|
authors = ["Blaž Hrastnik <blaz@mxxn.io>"]
|
||||||
edition = "2018"
|
edition = "2021"
|
||||||
license = "MPL-2.0"
|
license = "MPL-2.0"
|
||||||
description = "LSP client implementation for Helix project"
|
description = "LSP client implementation for Helix project"
|
||||||
categories = ["editor"]
|
categories = ["editor"]
|
||||||
|
@ -12,16 +12,16 @@ homepage = "https://helix-editor.com"
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
helix-core = { version = "0.4", path = "../helix-core" }
|
helix-core = { version = "0.5", path = "../helix-core" }
|
||||||
|
|
||||||
anyhow = "1.0"
|
anyhow = "1.0"
|
||||||
futures-executor = "0.3"
|
futures-executor = "0.3"
|
||||||
futures-util = { version = "0.3", features = ["std", "async-await"], default-features = false }
|
futures-util = { version = "0.3", features = ["std", "async-await"], default-features = false }
|
||||||
jsonrpc-core = { version = "18.0", default-features = false } # don't pull in all of futures
|
jsonrpc-core = { version = "18.0", default-features = false } # don't pull in all of futures
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
lsp-types = { version = "0.90", features = ["proposed"] }
|
lsp-types = { version = "0.91", features = ["proposed"] }
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
serde_json = "1.0"
|
serde_json = "1.0"
|
||||||
thiserror = "1.0"
|
thiserror = "1.0"
|
||||||
tokio = { version = "1.12", features = ["rt", "rt-multi-thread", "io-util", "io-std", "time", "process", "macros", "fs", "parking_lot", "sync"] }
|
tokio = { version = "1.13", features = ["rt", "rt-multi-thread", "io-util", "io-std", "time", "process", "macros", "fs", "parking_lot", "sync"] }
|
||||||
tokio-stream = "0.1.7"
|
tokio-stream = "0.1.8"
|
||||||
|
|
|
@ -461,7 +461,7 @@ impl Client {
|
||||||
};
|
};
|
||||||
|
|
||||||
let changes = match sync_capabilities {
|
let changes = match sync_capabilities {
|
||||||
lsp::TextDocumentSyncKind::Full => {
|
lsp::TextDocumentSyncKind::FULL => {
|
||||||
vec![lsp::TextDocumentContentChangeEvent {
|
vec![lsp::TextDocumentContentChangeEvent {
|
||||||
// range = None -> whole document
|
// range = None -> whole document
|
||||||
range: None, //Some(Range)
|
range: None, //Some(Range)
|
||||||
|
@ -469,10 +469,11 @@ impl Client {
|
||||||
text: new_text.to_string(),
|
text: new_text.to_string(),
|
||||||
}]
|
}]
|
||||||
}
|
}
|
||||||
lsp::TextDocumentSyncKind::Incremental => {
|
lsp::TextDocumentSyncKind::INCREMENTAL => {
|
||||||
Self::changeset_to_changes(old_text, new_text, changes, self.offset_encoding)
|
Self::changeset_to_changes(old_text, new_text, changes, self.offset_encoding)
|
||||||
}
|
}
|
||||||
lsp::TextDocumentSyncKind::None => return None,
|
lsp::TextDocumentSyncKind::NONE => return None,
|
||||||
|
kind => unimplemented!("{:?}", kind),
|
||||||
};
|
};
|
||||||
|
|
||||||
Some(self.notify::<lsp::notification::DidChangeTextDocument>(
|
Some(self.notify::<lsp::notification::DidChangeTextDocument>(
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
[package]
|
[package]
|
||||||
name = "helix-syntax"
|
name = "helix-syntax"
|
||||||
version = "0.4.1"
|
version = "0.5.0"
|
||||||
authors = ["Blaž Hrastnik <blaz@mxxn.io>"]
|
authors = ["Blaž Hrastnik <blaz@mxxn.io>"]
|
||||||
edition = "2018"
|
edition = "2021"
|
||||||
license = "MPL-2.0"
|
license = "MPL-2.0"
|
||||||
description = "Tree-sitter grammars support"
|
description = "Tree-sitter grammars support"
|
||||||
categories = ["editor"]
|
categories = ["editor"]
|
||||||
|
|
1
helix-syntax/languages/tree-sitter-cmake
Submodule
1
helix-syntax/languages/tree-sitter-cmake
Submodule
|
@ -0,0 +1 @@
|
||||||
|
Subproject commit f6616f1e417ee8b62daf251aa1daa5d73781c596
|
|
@ -1 +1 @@
|
||||||
Subproject commit c61212414a3e95b5f7507f98e83de1d638044adc
|
Subproject commit e8dcc9d2b404c542fd236ea5f7208f90be8a6e89
|
|
@ -1 +1 @@
|
||||||
Subproject commit 295e62a43b92cea909cfabe57e8818d177f4857b
|
Subproject commit f5d7bda543da788bd507b05bd722627dde66c9ec
|
|
@ -1,9 +1,9 @@
|
||||||
[package]
|
[package]
|
||||||
name = "helix-term"
|
name = "helix-term"
|
||||||
version = "0.4.1"
|
version = "0.5.0"
|
||||||
description = "A post-modern text editor."
|
description = "A post-modern text editor."
|
||||||
authors = ["Blaž Hrastnik <blaz@mxxn.io>"]
|
authors = ["Blaž Hrastnik <blaz@mxxn.io>"]
|
||||||
edition = "2018"
|
edition = "2021"
|
||||||
license = "MPL-2.0"
|
license = "MPL-2.0"
|
||||||
categories = ["editor", "command-line-utilities"]
|
categories = ["editor", "command-line-utilities"]
|
||||||
repository = "https://github.com/helix-editor/helix"
|
repository = "https://github.com/helix-editor/helix"
|
||||||
|
@ -21,10 +21,10 @@ name = "hx"
|
||||||
path = "src/main.rs"
|
path = "src/main.rs"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
helix-core = { version = "0.4", path = "../helix-core" }
|
helix-core = { version = "0.5", path = "../helix-core" }
|
||||||
helix-view = { version = "0.4", path = "../helix-view" }
|
helix-view = { version = "0.5", path = "../helix-view" }
|
||||||
helix-lsp = { version = "0.4", path = "../helix-lsp" }
|
helix-lsp = { version = "0.5", path = "../helix-lsp" }
|
||||||
helix-dap = { version = "0.4", path = "../helix-dap" }
|
helix-dap = { version = "0.5", path = "../helix-dap" }
|
||||||
|
|
||||||
anyhow = "1"
|
anyhow = "1"
|
||||||
once_cell = "1.8"
|
once_cell = "1.8"
|
||||||
|
@ -32,7 +32,7 @@ once_cell = "1.8"
|
||||||
tokio = { version = "1", features = ["rt", "rt-multi-thread", "io-util", "io-std", "time", "process", "macros", "fs", "parking_lot"] }
|
tokio = { version = "1", features = ["rt", "rt-multi-thread", "io-util", "io-std", "time", "process", "macros", "fs", "parking_lot"] }
|
||||||
num_cpus = "1"
|
num_cpus = "1"
|
||||||
tui = { path = "../helix-tui", package = "helix-tui", default-features = false, features = ["crossterm"] }
|
tui = { path = "../helix-tui", package = "helix-tui", default-features = false, features = ["crossterm"] }
|
||||||
crossterm = { version = "0.21", features = ["event-stream"] }
|
crossterm = { version = "0.22", features = ["event-stream"] }
|
||||||
signal-hook = "0.3"
|
signal-hook = "0.3"
|
||||||
tokio-stream = "0.1"
|
tokio-stream = "0.1"
|
||||||
futures-util = { version = "0.3", features = ["std", "async-await"], default-features = false }
|
futures-util = { version = "0.3", features = ["std", "async-await"], default-features = false }
|
||||||
|
@ -45,10 +45,10 @@ log = "0.4"
|
||||||
# File picker
|
# File picker
|
||||||
fuzzy-matcher = "0.3"
|
fuzzy-matcher = "0.3"
|
||||||
ignore = "0.4"
|
ignore = "0.4"
|
||||||
# shellexpand = "2.1"
|
|
||||||
# dirs-next = "2.0"
|
|
||||||
# markdown doc rendering
|
# markdown doc rendering
|
||||||
pulldown-cmark = { version = "0.8", default-features = false }
|
pulldown-cmark = { version = "0.8", default-features = false }
|
||||||
|
# file type detection
|
||||||
|
content_inspector = "0.2.4"
|
||||||
|
|
||||||
# config
|
# config
|
||||||
toml = "0.5"
|
toml = "0.5"
|
||||||
|
|
|
@ -99,12 +99,17 @@ impl Application {
|
||||||
let editor_view = Box::new(ui::EditorView::new(std::mem::take(&mut config.keys)));
|
let editor_view = Box::new(ui::EditorView::new(std::mem::take(&mut config.keys)));
|
||||||
compositor.push(editor_view);
|
compositor.push(editor_view);
|
||||||
|
|
||||||
if !args.files.is_empty() {
|
if args.load_tutor {
|
||||||
|
let path = helix_core::runtime_dir().join("tutor.txt");
|
||||||
|
editor.open(path, Action::VerticalSplit)?;
|
||||||
|
// Unset path to prevent accidentally saving to the original tutor file.
|
||||||
|
doc_mut!(editor).set_path(None)?;
|
||||||
|
} else if !args.files.is_empty() {
|
||||||
let first = &args.files[0]; // we know it's not empty
|
let first = &args.files[0]; // we know it's not empty
|
||||||
if first.is_dir() {
|
if first.is_dir() {
|
||||||
std::env::set_current_dir(&first)?;
|
std::env::set_current_dir(&first)?;
|
||||||
editor.new_file(Action::VerticalSplit);
|
editor.new_file(Action::VerticalSplit);
|
||||||
compositor.push(Box::new(ui::file_picker(first.clone())));
|
compositor.push(Box::new(ui::file_picker(".".into())));
|
||||||
} else {
|
} else {
|
||||||
let nr_of_files = args.files.len();
|
let nr_of_files = args.files.len();
|
||||||
editor.open(first.to_path_buf(), Action::VerticalSplit)?;
|
editor.open(first.to_path_buf(), Action::VerticalSplit)?;
|
||||||
|
@ -240,7 +245,7 @@ impl Application {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn handle_idle_timeout(&mut self) {
|
pub fn handle_idle_timeout(&mut self) {
|
||||||
use crate::commands::{completion, Context};
|
use crate::commands::{insert::idle_completion, Context};
|
||||||
use helix_view::document::Mode;
|
use helix_view::document::Mode;
|
||||||
|
|
||||||
if doc_mut!(self.editor).mode != Mode::Insert || !self.config.editor.auto_completion {
|
if doc_mut!(self.editor).mode != Mode::Insert || !self.config.editor.auto_completion {
|
||||||
|
@ -267,7 +272,7 @@ impl Application {
|
||||||
callback: None,
|
callback: None,
|
||||||
on_next_key_callback: None,
|
on_next_key_callback: None,
|
||||||
};
|
};
|
||||||
completion(&mut cx);
|
idle_completion(&mut cx);
|
||||||
self.render();
|
self.render();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -548,10 +553,11 @@ impl Application {
|
||||||
message: diagnostic.message,
|
message: diagnostic.message,
|
||||||
severity: diagnostic.severity.map(
|
severity: diagnostic.severity.map(
|
||||||
|severity| match severity {
|
|severity| match severity {
|
||||||
DiagnosticSeverity::Error => Error,
|
DiagnosticSeverity::ERROR => Error,
|
||||||
DiagnosticSeverity::Warning => Warning,
|
DiagnosticSeverity::WARNING => Warning,
|
||||||
DiagnosticSeverity::Information => Info,
|
DiagnosticSeverity::INFORMATION => Info,
|
||||||
DiagnosticSeverity::Hint => Hint,
|
DiagnosticSeverity::HINT => Hint,
|
||||||
|
severity => unimplemented!("{:?}", severity),
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
// code
|
// code
|
||||||
|
@ -727,7 +733,9 @@ impl Application {
|
||||||
let mut stdout = stdout();
|
let mut stdout = stdout();
|
||||||
// reset cursor shape
|
// reset cursor shape
|
||||||
write!(stdout, "\x1B[2 q")?;
|
write!(stdout, "\x1B[2 q")?;
|
||||||
execute!(stdout, DisableMouseCapture)?;
|
// Ignore errors on disabling, this might trigger on windows if we call
|
||||||
|
// disable without calling enable previously
|
||||||
|
let _ = execute!(stdout, DisableMouseCapture);
|
||||||
execute!(stdout, terminal::LeaveAlternateScreen)?;
|
execute!(stdout, terminal::LeaveAlternateScreen)?;
|
||||||
terminal::disable_raw_mode()?;
|
terminal::disable_raw_mode()?;
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
|
@ -5,6 +5,7 @@ use std::path::PathBuf;
|
||||||
pub struct Args {
|
pub struct Args {
|
||||||
pub display_help: bool,
|
pub display_help: bool,
|
||||||
pub display_version: bool,
|
pub display_version: bool,
|
||||||
|
pub load_tutor: bool,
|
||||||
pub verbosity: u64,
|
pub verbosity: u64,
|
||||||
pub files: Vec<PathBuf>,
|
pub files: Vec<PathBuf>,
|
||||||
}
|
}
|
||||||
|
@ -22,6 +23,7 @@ impl Args {
|
||||||
"--" => break, // stop parsing at this point treat the remaining as files
|
"--" => break, // stop parsing at this point treat the remaining as files
|
||||||
"--version" => args.display_version = true,
|
"--version" => args.display_version = true,
|
||||||
"--help" => args.display_help = true,
|
"--help" => args.display_help = true,
|
||||||
|
"--tutor" => args.load_tutor = true,
|
||||||
arg if arg.starts_with("--") => {
|
arg if arg.starts_with("--") => {
|
||||||
return Err(Error::msg(format!(
|
return Err(Error::msg(format!(
|
||||||
"unexpected double dash argument: {}",
|
"unexpected double dash argument: {}",
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -207,7 +207,7 @@ pub trait AnyComponent {
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// use helix_term::{ui::Text, compositor::Component};
|
/// use helix_term::{ui::Text, compositor::Component};
|
||||||
/// let boxed: Box<Component> = Box::new(Text::new("text".to_string()));
|
/// let boxed: Box<dyn Component> = Box::new(Text::new("text".to_string()));
|
||||||
/// let text: Box<Text> = boxed.as_boxed_any().downcast().unwrap();
|
/// let text: Box<Text> = boxed.as_boxed_any().downcast().unwrap();
|
||||||
/// ```
|
/// ```
|
||||||
fn as_boxed_any(self: Box<Self>) -> Box<dyn Any>;
|
fn as_boxed_any(self: Box<Self>) -> Box<dyn Any>;
|
||||||
|
|
|
@ -5,20 +5,20 @@ use helix_view::{document::Mode, info::Info, input::KeyEvent};
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use std::{
|
use std::{
|
||||||
borrow::Cow,
|
borrow::Cow,
|
||||||
collections::HashMap,
|
collections::{BTreeSet, HashMap},
|
||||||
ops::{Deref, DerefMut},
|
ops::{Deref, DerefMut},
|
||||||
};
|
};
|
||||||
|
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
macro_rules! key {
|
macro_rules! key {
|
||||||
($key:ident) => {
|
($key:ident) => {
|
||||||
KeyEvent {
|
::helix_view::input::KeyEvent {
|
||||||
code: ::helix_view::keyboard::KeyCode::$key,
|
code: ::helix_view::keyboard::KeyCode::$key,
|
||||||
modifiers: ::helix_view::keyboard::KeyModifiers::NONE,
|
modifiers: ::helix_view::keyboard::KeyModifiers::NONE,
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
($($ch:tt)*) => {
|
($($ch:tt)*) => {
|
||||||
KeyEvent {
|
::helix_view::input::KeyEvent {
|
||||||
code: ::helix_view::keyboard::KeyCode::Char($($ch)*),
|
code: ::helix_view::keyboard::KeyCode::Char($($ch)*),
|
||||||
modifiers: ::helix_view::keyboard::KeyModifiers::NONE,
|
modifiers: ::helix_view::keyboard::KeyModifiers::NONE,
|
||||||
}
|
}
|
||||||
|
@ -78,19 +78,30 @@ macro_rules! keymap {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Deserialize)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct KeyTrieNode {
|
pub struct KeyTrieNode {
|
||||||
/// A label for keys coming under this node, like "Goto mode"
|
/// A label for keys coming under this node, like "Goto mode"
|
||||||
#[serde(skip)]
|
|
||||||
name: String,
|
name: String,
|
||||||
#[serde(flatten)]
|
|
||||||
map: HashMap<KeyEvent, KeyTrie>,
|
map: HashMap<KeyEvent, KeyTrie>,
|
||||||
#[serde(skip)]
|
|
||||||
order: Vec<KeyEvent>,
|
order: Vec<KeyEvent>,
|
||||||
#[serde(skip)]
|
|
||||||
pub is_sticky: bool,
|
pub is_sticky: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<'de> Deserialize<'de> for KeyTrieNode {
|
||||||
|
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||||
|
where
|
||||||
|
D: serde::Deserializer<'de>,
|
||||||
|
{
|
||||||
|
let map = HashMap::<KeyEvent, KeyTrie>::deserialize(deserializer)?;
|
||||||
|
let order = map.keys().copied().collect::<Vec<_>>(); // NOTE: map.keys() has arbitrary order
|
||||||
|
Ok(Self {
|
||||||
|
map,
|
||||||
|
order,
|
||||||
|
..Default::default()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl KeyTrieNode {
|
impl KeyTrieNode {
|
||||||
pub fn new(name: &str, map: HashMap<KeyEvent, KeyTrie>, order: Vec<KeyEvent>) -> Self {
|
pub fn new(name: &str, map: HashMap<KeyEvent, KeyTrie>, order: Vec<KeyEvent>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
|
@ -118,7 +129,6 @@ impl KeyTrieNode {
|
||||||
}
|
}
|
||||||
self.map.insert(key, trie);
|
self.map.insert(key, trie);
|
||||||
}
|
}
|
||||||
|
|
||||||
for &key in self.map.keys() {
|
for &key in self.map.keys() {
|
||||||
if !self.order.contains(&key) {
|
if !self.order.contains(&key) {
|
||||||
self.order.push(key);
|
self.order.push(key);
|
||||||
|
@ -127,20 +137,29 @@ impl KeyTrieNode {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn infobox(&self) -> Info {
|
pub fn infobox(&self) -> Info {
|
||||||
let mut body: Vec<(&str, Vec<KeyEvent>)> = Vec::with_capacity(self.len());
|
let mut body: Vec<(&str, BTreeSet<KeyEvent>)> = Vec::with_capacity(self.len());
|
||||||
for (&key, trie) in self.iter() {
|
for (&key, trie) in self.iter() {
|
||||||
let desc = match trie {
|
let desc = match trie {
|
||||||
KeyTrie::Leaf(cmd) => cmd.doc(),
|
KeyTrie::Leaf(cmd) => {
|
||||||
|
if cmd.name() == "no_op" {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
cmd.doc()
|
||||||
|
}
|
||||||
KeyTrie::Node(n) => n.name(),
|
KeyTrie::Node(n) => n.name(),
|
||||||
};
|
};
|
||||||
match body.iter().position(|(d, _)| d == &desc) {
|
match body.iter().position(|(d, _)| d == &desc) {
|
||||||
// FIXME: multiple keys are ordered randomly (use BTreeSet)
|
Some(pos) => {
|
||||||
Some(pos) => body[pos].1.push(key),
|
body[pos].1.insert(key);
|
||||||
None => body.push((desc, vec![key])),
|
}
|
||||||
|
None => body.push((desc, BTreeSet::from([key]))),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
body.sort_unstable_by_key(|(_, keys)| {
|
body.sort_unstable_by_key(|(_, keys)| {
|
||||||
self.order.iter().position(|&k| k == keys[0]).unwrap()
|
self.order
|
||||||
|
.iter()
|
||||||
|
.position(|&k| k == *keys.iter().next().unwrap())
|
||||||
|
.unwrap()
|
||||||
});
|
});
|
||||||
let prefix = format!("{} ", self.name());
|
let prefix = format!("{} ", self.name());
|
||||||
if body.iter().all(|(desc, _)| desc.starts_with(&prefix)) {
|
if body.iter().all(|(desc, _)| desc.starts_with(&prefix)) {
|
||||||
|
@ -151,6 +170,11 @@ impl KeyTrieNode {
|
||||||
}
|
}
|
||||||
Info::new(self.name(), body)
|
Info::new(self.name(), body)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get a reference to the key trie node's order.
|
||||||
|
pub fn order(&self) -> &[KeyEvent] {
|
||||||
|
self.order.as_slice()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for KeyTrieNode {
|
impl Default for KeyTrieNode {
|
||||||
|
@ -235,6 +259,7 @@ pub enum KeymapResultKind {
|
||||||
|
|
||||||
/// Returned after looking up a key in [`Keymap`]. The `sticky` field has a
|
/// Returned after looking up a key in [`Keymap`]. The `sticky` field has a
|
||||||
/// reference to the sticky node if one is currently active.
|
/// reference to the sticky node if one is currently active.
|
||||||
|
#[derive(Debug)]
|
||||||
pub struct KeymapResult<'a> {
|
pub struct KeymapResult<'a> {
|
||||||
pub kind: KeymapResultKind,
|
pub kind: KeymapResultKind,
|
||||||
pub sticky: Option<&'a KeyTrieNode>,
|
pub sticky: Option<&'a KeyTrieNode>,
|
||||||
|
@ -395,6 +420,7 @@ impl Default for Keymaps {
|
||||||
"F" => find_prev_char,
|
"F" => find_prev_char,
|
||||||
"r" => replace,
|
"r" => replace,
|
||||||
"R" => replace_with_yanked,
|
"R" => replace_with_yanked,
|
||||||
|
"A-." => repeat_last_motion,
|
||||||
|
|
||||||
"~" => switch_case,
|
"~" => switch_case,
|
||||||
"`" => switch_to_lowercase,
|
"`" => switch_to_lowercase,
|
||||||
|
@ -427,6 +453,8 @@ impl Default for Keymaps {
|
||||||
"m" => goto_window_middle,
|
"m" => goto_window_middle,
|
||||||
"b" => goto_window_bottom,
|
"b" => goto_window_bottom,
|
||||||
"a" => goto_last_accessed_file,
|
"a" => goto_last_accessed_file,
|
||||||
|
"n" => goto_next_buffer,
|
||||||
|
"p" => goto_previous_buffer,
|
||||||
},
|
},
|
||||||
":" => command_mode,
|
":" => command_mode,
|
||||||
|
|
||||||
|
@ -476,10 +504,9 @@ impl Default for Keymaps {
|
||||||
},
|
},
|
||||||
|
|
||||||
"/" => search,
|
"/" => search,
|
||||||
// ? for search_reverse
|
"?" => rsearch,
|
||||||
"n" => search_next,
|
"n" => search_next,
|
||||||
"N" => extend_search_next,
|
"N" => search_prev,
|
||||||
// N for search_prev
|
|
||||||
"*" => search_selection,
|
"*" => search_selection,
|
||||||
|
|
||||||
"u" => undo,
|
"u" => undo,
|
||||||
|
@ -520,9 +547,13 @@ impl Default for Keymaps {
|
||||||
|
|
||||||
"C-w" => { "Window"
|
"C-w" => { "Window"
|
||||||
"C-w" | "w" => rotate_view,
|
"C-w" | "w" => rotate_view,
|
||||||
"C-h" | "h" => hsplit,
|
"C-s" | "s" => hsplit,
|
||||||
"C-v" | "v" => vsplit,
|
"C-v" | "v" => vsplit,
|
||||||
"C-q" | "q" => wclose,
|
"C-q" | "q" => wclose,
|
||||||
|
"C-h" | "h" | "left" => jump_view_left,
|
||||||
|
"C-j" | "j" | "down" => jump_view_down,
|
||||||
|
"C-k" | "k" | "up" => jump_view_up,
|
||||||
|
"C-l" | "l" | "right" => jump_view_right,
|
||||||
},
|
},
|
||||||
|
|
||||||
// move under <space>c
|
// move under <space>c
|
||||||
|
@ -621,6 +652,9 @@ impl Default for Keymaps {
|
||||||
"B" => extend_prev_long_word_start,
|
"B" => extend_prev_long_word_start,
|
||||||
"E" => extend_next_long_word_end,
|
"E" => extend_next_long_word_end,
|
||||||
|
|
||||||
|
"n" => extend_search_next,
|
||||||
|
"N" => extend_search_prev,
|
||||||
|
|
||||||
"t" => extend_till_char,
|
"t" => extend_till_char,
|
||||||
"f" => extend_next_char,
|
"f" => extend_next_char,
|
||||||
"T" => extend_till_prev_char,
|
"T" => extend_till_prev_char,
|
||||||
|
@ -669,63 +703,101 @@ pub fn merge_keys(mut config: Config) -> Config {
|
||||||
config
|
config
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[cfg(test)]
|
||||||
fn merge_partial_keys() {
|
mod tests {
|
||||||
let config = Config {
|
use super::*;
|
||||||
keys: Keymaps(hashmap! {
|
#[test]
|
||||||
Mode::Normal => Keymap::new(
|
fn merge_partial_keys() {
|
||||||
keymap!({ "Normal mode"
|
let config = Config {
|
||||||
"i" => normal_mode,
|
keys: Keymaps(hashmap! {
|
||||||
"无" => insert_mode,
|
Mode::Normal => Keymap::new(
|
||||||
"z" => jump_backward,
|
keymap!({ "Normal mode"
|
||||||
"g" => { "Merge into goto mode"
|
"i" => normal_mode,
|
||||||
"$" => goto_line_end,
|
"无" => insert_mode,
|
||||||
"g" => delete_char_forward,
|
"z" => jump_backward,
|
||||||
},
|
"g" => { "Merge into goto mode"
|
||||||
})
|
"$" => goto_line_end,
|
||||||
)
|
"g" => delete_char_forward,
|
||||||
}),
|
},
|
||||||
..Default::default()
|
})
|
||||||
};
|
)
|
||||||
let mut merged_config = merge_keys(config.clone());
|
}),
|
||||||
assert_ne!(config, merged_config);
|
..Default::default()
|
||||||
|
};
|
||||||
|
let mut merged_config = merge_keys(config.clone());
|
||||||
|
assert_ne!(config, merged_config);
|
||||||
|
|
||||||
let keymap = merged_config.keys.0.get_mut(&Mode::Normal).unwrap();
|
let keymap = merged_config.keys.0.get_mut(&Mode::Normal).unwrap();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
keymap.get(key!('i')).kind,
|
keymap.get(key!('i')).kind,
|
||||||
KeymapResultKind::Matched(Command::normal_mode),
|
KeymapResultKind::Matched(Command::normal_mode),
|
||||||
"Leaf should replace leaf"
|
"Leaf should replace leaf"
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
keymap.get(key!('无')).kind,
|
keymap.get(key!('无')).kind,
|
||||||
KeymapResultKind::Matched(Command::insert_mode),
|
KeymapResultKind::Matched(Command::insert_mode),
|
||||||
"New leaf should be present in merged keymap"
|
"New leaf should be present in merged keymap"
|
||||||
);
|
);
|
||||||
// Assumes that z is a node in the default keymap
|
// Assumes that z is a node in the default keymap
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
keymap.get(key!('z')).kind,
|
keymap.get(key!('z')).kind,
|
||||||
KeymapResultKind::Matched(Command::jump_backward),
|
KeymapResultKind::Matched(Command::jump_backward),
|
||||||
"Leaf should replace node"
|
"Leaf should replace node"
|
||||||
);
|
);
|
||||||
// Assumes that `g` is a node in default keymap
|
// Assumes that `g` is a node in default keymap
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
keymap.root().search(&[key!('g'), key!('$')]).unwrap(),
|
keymap.root().search(&[key!('g'), key!('$')]).unwrap(),
|
||||||
&KeyTrie::Leaf(Command::goto_line_end),
|
&KeyTrie::Leaf(Command::goto_line_end),
|
||||||
"Leaf should be present in merged subnode"
|
"Leaf should be present in merged subnode"
|
||||||
);
|
);
|
||||||
// Assumes that `gg` is in default keymap
|
// Assumes that `gg` is in default keymap
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
keymap.root().search(&[key!('g'), key!('g')]).unwrap(),
|
keymap.root().search(&[key!('g'), key!('g')]).unwrap(),
|
||||||
&KeyTrie::Leaf(Command::delete_char_forward),
|
&KeyTrie::Leaf(Command::delete_char_forward),
|
||||||
"Leaf should replace old leaf in merged subnode"
|
"Leaf should replace old leaf in merged subnode"
|
||||||
);
|
);
|
||||||
// Assumes that `ge` is in default keymap
|
// Assumes that `ge` is in default keymap
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
keymap.root().search(&[key!('g'), key!('e')]).unwrap(),
|
keymap.root().search(&[key!('g'), key!('e')]).unwrap(),
|
||||||
&KeyTrie::Leaf(Command::goto_last_line),
|
&KeyTrie::Leaf(Command::goto_last_line),
|
||||||
"Old leaves in subnode should be present in merged node"
|
"Old leaves in subnode should be present in merged node"
|
||||||
);
|
);
|
||||||
|
|
||||||
assert!(merged_config.keys.0.get(&Mode::Normal).unwrap().len() > 1);
|
assert!(merged_config.keys.0.get(&Mode::Normal).unwrap().len() > 1);
|
||||||
assert!(merged_config.keys.0.get(&Mode::Insert).unwrap().len() > 0);
|
assert!(merged_config.keys.0.get(&Mode::Insert).unwrap().len() > 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn order_should_be_set() {
|
||||||
|
let config = Config {
|
||||||
|
keys: Keymaps(hashmap! {
|
||||||
|
Mode::Normal => Keymap::new(
|
||||||
|
keymap!({ "Normal mode"
|
||||||
|
"space" => { ""
|
||||||
|
"s" => { ""
|
||||||
|
"v" => vsplit,
|
||||||
|
"c" => hsplit,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
)
|
||||||
|
}),
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
let mut merged_config = merge_keys(config.clone());
|
||||||
|
assert_ne!(config, merged_config);
|
||||||
|
let keymap = merged_config.keys.0.get_mut(&Mode::Normal).unwrap();
|
||||||
|
// Make sure mapping works
|
||||||
|
assert_eq!(
|
||||||
|
keymap
|
||||||
|
.root()
|
||||||
|
.search(&[key!(' '), key!('s'), key!('v')])
|
||||||
|
.unwrap(),
|
||||||
|
&KeyTrie::Leaf(Command::vsplit),
|
||||||
|
"Leaf should be present in merged subnode"
|
||||||
|
);
|
||||||
|
// Make sure an order was set during merge
|
||||||
|
let node = keymap.root().search(&[crate::key!(' ')]).unwrap();
|
||||||
|
assert!(!node.node().unwrap().order().is_empty())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,6 +16,11 @@ fn setup_logging(logpath: PathBuf, verbosity: u64) -> Result<()> {
|
||||||
};
|
};
|
||||||
|
|
||||||
// Separate file config so we can include year, month and day in file logs
|
// Separate file config so we can include year, month and day in file logs
|
||||||
|
let file = std::fs::OpenOptions::new()
|
||||||
|
.write(true)
|
||||||
|
.create(true)
|
||||||
|
.truncate(true)
|
||||||
|
.open(logpath)?;
|
||||||
let file_config = fern::Dispatch::new()
|
let file_config = fern::Dispatch::new()
|
||||||
.format(|out, message, record| {
|
.format(|out, message, record| {
|
||||||
out.finish(format_args!(
|
out.finish(format_args!(
|
||||||
|
@ -26,7 +31,7 @@ fn setup_logging(logpath: PathBuf, verbosity: u64) -> Result<()> {
|
||||||
message
|
message
|
||||||
))
|
))
|
||||||
})
|
})
|
||||||
.chain(fern::log_file(logpath)?);
|
.chain(file);
|
||||||
|
|
||||||
base_config.chain(file_config).apply()?;
|
base_config.chain(file_config).apply()?;
|
||||||
|
|
||||||
|
@ -55,6 +60,7 @@ ARGS:
|
||||||
|
|
||||||
FLAGS:
|
FLAGS:
|
||||||
-h, --help Prints help information
|
-h, --help Prints help information
|
||||||
|
--tutor Loads the tutorial
|
||||||
-v Increases logging verbosity each use for up to 3 times
|
-v Increases logging verbosity each use for up to 3 times
|
||||||
(default file: {})
|
(default file: {})
|
||||||
-V, --version Prints version information
|
-V, --version Prints version information
|
||||||
|
|
|
@ -5,7 +5,7 @@ use tui::buffer::Buffer as Surface;
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
|
|
||||||
use helix_core::Transaction;
|
use helix_core::Transaction;
|
||||||
use helix_view::{graphics::Rect, Document, Editor, View};
|
use helix_view::{graphics::Rect, Document, Editor};
|
||||||
|
|
||||||
use crate::commands;
|
use crate::commands;
|
||||||
use crate::ui::{menu, Markdown, Menu, Popup, PromptEvent};
|
use crate::ui::{menu, Markdown, Menu, Popup, PromptEvent};
|
||||||
|
@ -30,31 +30,32 @@ impl menu::Item for CompletionItem {
|
||||||
menu::Row::new(vec![
|
menu::Row::new(vec![
|
||||||
menu::Cell::from(self.label.as_str()),
|
menu::Cell::from(self.label.as_str()),
|
||||||
menu::Cell::from(match self.kind {
|
menu::Cell::from(match self.kind {
|
||||||
Some(lsp::CompletionItemKind::Text) => "text",
|
Some(lsp::CompletionItemKind::TEXT) => "text",
|
||||||
Some(lsp::CompletionItemKind::Method) => "method",
|
Some(lsp::CompletionItemKind::METHOD) => "method",
|
||||||
Some(lsp::CompletionItemKind::Function) => "function",
|
Some(lsp::CompletionItemKind::FUNCTION) => "function",
|
||||||
Some(lsp::CompletionItemKind::Constructor) => "constructor",
|
Some(lsp::CompletionItemKind::CONSTRUCTOR) => "constructor",
|
||||||
Some(lsp::CompletionItemKind::Field) => "field",
|
Some(lsp::CompletionItemKind::FIELD) => "field",
|
||||||
Some(lsp::CompletionItemKind::Variable) => "variable",
|
Some(lsp::CompletionItemKind::VARIABLE) => "variable",
|
||||||
Some(lsp::CompletionItemKind::Class) => "class",
|
Some(lsp::CompletionItemKind::CLASS) => "class",
|
||||||
Some(lsp::CompletionItemKind::Interface) => "interface",
|
Some(lsp::CompletionItemKind::INTERFACE) => "interface",
|
||||||
Some(lsp::CompletionItemKind::Module) => "module",
|
Some(lsp::CompletionItemKind::MODULE) => "module",
|
||||||
Some(lsp::CompletionItemKind::Property) => "property",
|
Some(lsp::CompletionItemKind::PROPERTY) => "property",
|
||||||
Some(lsp::CompletionItemKind::Unit) => "unit",
|
Some(lsp::CompletionItemKind::UNIT) => "unit",
|
||||||
Some(lsp::CompletionItemKind::Value) => "value",
|
Some(lsp::CompletionItemKind::VALUE) => "value",
|
||||||
Some(lsp::CompletionItemKind::Enum) => "enum",
|
Some(lsp::CompletionItemKind::ENUM) => "enum",
|
||||||
Some(lsp::CompletionItemKind::Keyword) => "keyword",
|
Some(lsp::CompletionItemKind::KEYWORD) => "keyword",
|
||||||
Some(lsp::CompletionItemKind::Snippet) => "snippet",
|
Some(lsp::CompletionItemKind::SNIPPET) => "snippet",
|
||||||
Some(lsp::CompletionItemKind::Color) => "color",
|
Some(lsp::CompletionItemKind::COLOR) => "color",
|
||||||
Some(lsp::CompletionItemKind::File) => "file",
|
Some(lsp::CompletionItemKind::FILE) => "file",
|
||||||
Some(lsp::CompletionItemKind::Reference) => "reference",
|
Some(lsp::CompletionItemKind::REFERENCE) => "reference",
|
||||||
Some(lsp::CompletionItemKind::Folder) => "folder",
|
Some(lsp::CompletionItemKind::FOLDER) => "folder",
|
||||||
Some(lsp::CompletionItemKind::EnumMember) => "enum_member",
|
Some(lsp::CompletionItemKind::ENUM_MEMBER) => "enum_member",
|
||||||
Some(lsp::CompletionItemKind::Constant) => "constant",
|
Some(lsp::CompletionItemKind::CONSTANT) => "constant",
|
||||||
Some(lsp::CompletionItemKind::Struct) => "struct",
|
Some(lsp::CompletionItemKind::STRUCT) => "struct",
|
||||||
Some(lsp::CompletionItemKind::Event) => "event",
|
Some(lsp::CompletionItemKind::EVENT) => "event",
|
||||||
Some(lsp::CompletionItemKind::Operator) => "operator",
|
Some(lsp::CompletionItemKind::OPERATOR) => "operator",
|
||||||
Some(lsp::CompletionItemKind::TypeParameter) => "type_param",
|
Some(lsp::CompletionItemKind::TYPE_PARAMETER) => "type_param",
|
||||||
|
Some(kind) => unimplemented!("{:?}", kind),
|
||||||
None => "",
|
None => "",
|
||||||
}),
|
}),
|
||||||
// self.detail.as_deref().unwrap_or("")
|
// self.detail.as_deref().unwrap_or("")
|
||||||
|
@ -83,13 +84,13 @@ impl Completion {
|
||||||
start_offset: usize,
|
start_offset: usize,
|
||||||
trigger_offset: usize,
|
trigger_offset: usize,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
// let items: Vec<CompletionItem> = Vec::new();
|
|
||||||
let menu = Menu::new(items, move |editor: &mut Editor, item, event| {
|
let menu = Menu::new(items, move |editor: &mut Editor, item, event| {
|
||||||
fn item_to_transaction(
|
fn item_to_transaction(
|
||||||
doc: &Document,
|
doc: &Document,
|
||||||
view: &View,
|
|
||||||
item: &CompletionItem,
|
item: &CompletionItem,
|
||||||
offset_encoding: helix_lsp::OffsetEncoding,
|
offset_encoding: helix_lsp::OffsetEncoding,
|
||||||
|
start_offset: usize,
|
||||||
|
trigger_offset: usize,
|
||||||
) -> Transaction {
|
) -> Transaction {
|
||||||
if let Some(edit) = &item.text_edit {
|
if let Some(edit) = &item.text_edit {
|
||||||
let edit = match edit {
|
let edit = match edit {
|
||||||
|
@ -105,63 +106,52 @@ impl Completion {
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
let text = item.insert_text.as_ref().unwrap_or(&item.label);
|
let text = item.insert_text.as_ref().unwrap_or(&item.label);
|
||||||
let cursor = doc
|
// Some LSPs just give you an insertText with no offset ¯\_(ツ)_/¯
|
||||||
.selection(view.id)
|
// in these cases we need to check for a common prefix and remove it
|
||||||
.primary()
|
let prefix = Cow::from(doc.text().slice(start_offset..trigger_offset));
|
||||||
.cursor(doc.text().slice(..));
|
let text = text.trim_start_matches::<&str>(&prefix);
|
||||||
Transaction::change(
|
Transaction::change(
|
||||||
doc.text(),
|
doc.text(),
|
||||||
vec![(cursor, cursor, Some(text.as_str().into()))].into_iter(),
|
vec![(trigger_offset, trigger_offset, Some(text.into()))].into_iter(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let (view, doc) = current!(editor);
|
||||||
|
|
||||||
|
// if more text was entered, remove it
|
||||||
|
doc.restore(view.id);
|
||||||
|
|
||||||
match event {
|
match event {
|
||||||
PromptEvent::Abort => {}
|
PromptEvent::Abort => {}
|
||||||
PromptEvent::Update => {
|
PromptEvent::Update => {
|
||||||
let (view, doc) = current!(editor);
|
|
||||||
|
|
||||||
// always present here
|
// always present here
|
||||||
let item = item.unwrap();
|
let item = item.unwrap();
|
||||||
|
|
||||||
// if more text was entered, remove it
|
let transaction = item_to_transaction(
|
||||||
// TODO: ideally to undo we should keep the last completion tx revert, and map it over new changes
|
doc,
|
||||||
let cursor = doc
|
item,
|
||||||
.selection(view.id)
|
offset_encoding,
|
||||||
.primary()
|
start_offset,
|
||||||
.cursor(doc.text().slice(..));
|
trigger_offset,
|
||||||
if trigger_offset < cursor {
|
);
|
||||||
let remove = Transaction::change(
|
|
||||||
doc.text(),
|
// initialize a savepoint
|
||||||
vec![(trigger_offset, cursor, None)].into_iter(),
|
doc.savepoint();
|
||||||
);
|
|
||||||
doc.apply(&remove, view.id);
|
|
||||||
}
|
|
||||||
|
|
||||||
let transaction = item_to_transaction(doc, view, item, offset_encoding);
|
|
||||||
doc.apply(&transaction, view.id);
|
doc.apply(&transaction, view.id);
|
||||||
}
|
}
|
||||||
PromptEvent::Validate => {
|
PromptEvent::Validate => {
|
||||||
let (view, doc) = current!(editor);
|
|
||||||
|
|
||||||
// always present here
|
// always present here
|
||||||
let item = item.unwrap();
|
let item = item.unwrap();
|
||||||
|
|
||||||
// if more text was entered, remove it
|
let transaction = item_to_transaction(
|
||||||
// TODO: ideally to undo we should keep the last completion tx revert, and map it over new changes
|
doc,
|
||||||
let cursor = doc
|
item,
|
||||||
.selection(view.id)
|
offset_encoding,
|
||||||
.primary()
|
start_offset,
|
||||||
.cursor(doc.text().slice(..));
|
trigger_offset,
|
||||||
if trigger_offset < cursor {
|
);
|
||||||
let remove = Transaction::change(
|
|
||||||
doc.text(),
|
|
||||||
vec![(trigger_offset, cursor, None)].into_iter(),
|
|
||||||
);
|
|
||||||
doc.apply(&remove, view.id);
|
|
||||||
}
|
|
||||||
|
|
||||||
let transaction = item_to_transaction(doc, view, item, offset_encoding);
|
|
||||||
doc.apply(&transaction, view.id);
|
doc.apply(&transaction, view.id);
|
||||||
|
|
||||||
if let Some(additional_edits) = &item.additional_text_edits {
|
if let Some(additional_edits) = &item.additional_text_edits {
|
||||||
|
@ -210,7 +200,7 @@ impl Completion {
|
||||||
.selection(view.id)
|
.selection(view.id)
|
||||||
.primary()
|
.primary()
|
||||||
.cursor(doc.text().slice(..));
|
.cursor(doc.text().slice(..));
|
||||||
if self.start_offset <= cursor {
|
if self.trigger_offset <= cursor {
|
||||||
let fragment = doc.text().slice(self.start_offset..cursor);
|
let fragment = doc.text().slice(self.start_offset..cursor);
|
||||||
let text = Cow::from(fragment);
|
let text = Cow::from(fragment);
|
||||||
// TODO: logic is same as ui/picker
|
// TODO: logic is same as ui/picker
|
||||||
|
@ -274,12 +264,10 @@ impl Component for Completion {
|
||||||
.language()
|
.language()
|
||||||
.and_then(|scope| scope.strip_prefix("source."))
|
.and_then(|scope| scope.strip_prefix("source."))
|
||||||
.unwrap_or("");
|
.unwrap_or("");
|
||||||
let cursor_pos = doc
|
let text = doc.text().slice(..);
|
||||||
.selection(view.id)
|
let cursor_pos = doc.selection(view.id).primary().cursor(text);
|
||||||
.primary()
|
let coords = helix_core::visual_coords_at_pos(text, cursor_pos, doc.tab_width());
|
||||||
.cursor(doc.text().slice(..));
|
let cursor_pos = (coords.row - view.offset.row) as u16;
|
||||||
let cursor_pos = (helix_core::coords_at_pos(doc.text().slice(..), cursor_pos).row
|
|
||||||
- view.offset.row) as u16;
|
|
||||||
let mut markdown_doc = match &option.documentation {
|
let mut markdown_doc = match &option.documentation {
|
||||||
Some(lsp::Documentation::String(contents))
|
Some(lsp::Documentation::String(contents))
|
||||||
| Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
|
| Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
|
||||||
|
|
|
@ -689,6 +689,8 @@ impl EditorView {
|
||||||
theme: &Theme,
|
theme: &Theme,
|
||||||
is_focused: bool,
|
is_focused: bool,
|
||||||
) {
|
) {
|
||||||
|
use tui::text::{Span, Spans};
|
||||||
|
|
||||||
//-------------------------------
|
//-------------------------------
|
||||||
// Left side of the status line.
|
// Left side of the status line.
|
||||||
//-------------------------------
|
//-------------------------------
|
||||||
|
@ -707,17 +709,17 @@ impl EditorView {
|
||||||
})
|
})
|
||||||
.unwrap_or("");
|
.unwrap_or("");
|
||||||
|
|
||||||
let style = if is_focused {
|
let base_style = if is_focused {
|
||||||
theme.get("ui.statusline")
|
theme.get("ui.statusline")
|
||||||
} else {
|
} else {
|
||||||
theme.get("ui.statusline.inactive")
|
theme.get("ui.statusline.inactive")
|
||||||
};
|
};
|
||||||
// statusline
|
// statusline
|
||||||
surface.set_style(viewport.with_height(1), style);
|
surface.set_style(viewport.with_height(1), base_style);
|
||||||
if is_focused {
|
if is_focused {
|
||||||
surface.set_string(viewport.x + 1, viewport.y, mode, style);
|
surface.set_string(viewport.x + 1, viewport.y, mode, base_style);
|
||||||
}
|
}
|
||||||
surface.set_string(viewport.x + 5, viewport.y, progress, style);
|
surface.set_string(viewport.x + 5, viewport.y, progress, base_style);
|
||||||
|
|
||||||
if let Some(path) = doc.relative_path() {
|
if let Some(path) = doc.relative_path() {
|
||||||
let path = path.to_string_lossy();
|
let path = path.to_string_lossy();
|
||||||
|
@ -728,7 +730,7 @@ impl EditorView {
|
||||||
viewport.y,
|
viewport.y,
|
||||||
title,
|
title,
|
||||||
viewport.width.saturating_sub(6) as usize,
|
viewport.width.saturating_sub(6) as usize,
|
||||||
style,
|
base_style,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -736,8 +738,50 @@ impl EditorView {
|
||||||
// Right side of the status line.
|
// Right side of the status line.
|
||||||
//-------------------------------
|
//-------------------------------
|
||||||
|
|
||||||
// Compute the individual info strings.
|
let mut right_side_text = Spans::default();
|
||||||
let diag_count = format!("{}", doc.diagnostics().len());
|
|
||||||
|
// Compute the individual info strings and add them to `right_side_text`.
|
||||||
|
|
||||||
|
// Diagnostics
|
||||||
|
let diags = doc.diagnostics().iter().fold((0, 0), |mut counts, diag| {
|
||||||
|
use helix_core::diagnostic::Severity;
|
||||||
|
match diag.severity {
|
||||||
|
Some(Severity::Warning) => counts.0 += 1,
|
||||||
|
Some(Severity::Error) | None => counts.1 += 1,
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
counts
|
||||||
|
});
|
||||||
|
let (warnings, errors) = diags;
|
||||||
|
let warning_style = theme.get("warning");
|
||||||
|
let error_style = theme.get("error");
|
||||||
|
for i in 0..2 {
|
||||||
|
let (count, style) = match i {
|
||||||
|
0 => (warnings, warning_style),
|
||||||
|
1 => (errors, error_style),
|
||||||
|
_ => unreachable!(),
|
||||||
|
};
|
||||||
|
if count == 0 {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let style = base_style.patch(style);
|
||||||
|
right_side_text.0.push(Span::styled("●", style));
|
||||||
|
right_side_text
|
||||||
|
.0
|
||||||
|
.push(Span::styled(format!(" {} ", count), base_style));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Selections
|
||||||
|
let sels_count = doc.selection(view.id).len();
|
||||||
|
right_side_text.0.push(Span::styled(
|
||||||
|
format!(
|
||||||
|
" {} sel{} ",
|
||||||
|
sels_count,
|
||||||
|
if sels_count == 1 { "" } else { "s" }
|
||||||
|
),
|
||||||
|
base_style,
|
||||||
|
));
|
||||||
|
|
||||||
// let indent_info = match doc.indent_style {
|
// let indent_info = match doc.indent_style {
|
||||||
// IndentStyle::Tabs => "tabs",
|
// IndentStyle::Tabs => "tabs",
|
||||||
// IndentStyle::Spaces(1) => "spaces:1",
|
// IndentStyle::Spaces(1) => "spaces:1",
|
||||||
|
@ -750,29 +794,28 @@ impl EditorView {
|
||||||
// IndentStyle::Spaces(8) => "spaces:8",
|
// IndentStyle::Spaces(8) => "spaces:8",
|
||||||
// _ => "indent:ERROR",
|
// _ => "indent:ERROR",
|
||||||
// };
|
// };
|
||||||
let position_info = {
|
|
||||||
let pos = coords_at_pos(
|
|
||||||
doc.text().slice(..),
|
|
||||||
doc.selection(view.id)
|
|
||||||
.primary()
|
|
||||||
.cursor(doc.text().slice(..)),
|
|
||||||
);
|
|
||||||
format!("{}:{}", pos.row + 1, pos.col + 1) // convert to 1-indexing
|
|
||||||
};
|
|
||||||
|
|
||||||
// Render them to the status line together.
|
// Position
|
||||||
let right_side_text = format!(
|
let pos = coords_at_pos(
|
||||||
"{} {} ",
|
doc.text().slice(..),
|
||||||
&diag_count[..diag_count.len().min(4)],
|
doc.selection(view.id)
|
||||||
// indent_info,
|
.primary()
|
||||||
position_info
|
.cursor(doc.text().slice(..)),
|
||||||
);
|
);
|
||||||
let text_len = right_side_text.len() as u16;
|
right_side_text.0.push(Span::styled(
|
||||||
surface.set_string(
|
format!(" {}:{} ", pos.row + 1, pos.col + 1), // Convert to 1-indexing.
|
||||||
viewport.x + viewport.width.saturating_sub(text_len),
|
base_style,
|
||||||
|
));
|
||||||
|
|
||||||
|
// Render to the statusline.
|
||||||
|
surface.set_spans(
|
||||||
|
viewport.x
|
||||||
|
+ viewport
|
||||||
|
.width
|
||||||
|
.saturating_sub(right_side_text.width() as u16),
|
||||||
viewport.y,
|
viewport.y,
|
||||||
right_side_text,
|
&right_side_text,
|
||||||
style,
|
right_side_text.width() as u16,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -984,7 +1027,7 @@ impl EditorView {
|
||||||
|
|
||||||
pub fn set_completion(
|
pub fn set_completion(
|
||||||
&mut self,
|
&mut self,
|
||||||
editor: &Editor,
|
editor: &mut Editor,
|
||||||
items: Vec<helix_lsp::lsp::CompletionItem>,
|
items: Vec<helix_lsp::lsp::CompletionItem>,
|
||||||
offset_encoding: helix_lsp::OffsetEncoding,
|
offset_encoding: helix_lsp::OffsetEncoding,
|
||||||
start_offset: usize,
|
start_offset: usize,
|
||||||
|
@ -999,10 +1042,21 @@ impl EditorView {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Immediately initialize a savepoint
|
||||||
|
doc_mut!(editor).savepoint();
|
||||||
|
|
||||||
// TODO : propagate required size on resize to completion too
|
// TODO : propagate required size on resize to completion too
|
||||||
completion.required_size((size.width, size.height));
|
completion.required_size((size.width, size.height));
|
||||||
self.completion = Some(completion);
|
self.completion = Some(completion);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn clear_completion(&mut self, editor: &mut Editor) {
|
||||||
|
self.completion = None;
|
||||||
|
// Clear any savepoints
|
||||||
|
let (_, doc) = current!(editor);
|
||||||
|
doc.savepoint = None;
|
||||||
|
editor.clear_idle_timer(); // don't retrigger
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl EditorView {
|
impl EditorView {
|
||||||
|
@ -1022,12 +1076,12 @@ impl EditorView {
|
||||||
let editor = &mut cxt.editor;
|
let editor = &mut cxt.editor;
|
||||||
|
|
||||||
let result = editor.tree.views().find_map(|(view, _focus)| {
|
let result = editor.tree.views().find_map(|(view, _focus)| {
|
||||||
view.pos_at_screen_coords(&editor.documents[view.doc], row, column)
|
view.pos_at_screen_coords(&editor.documents[&view.doc], row, column)
|
||||||
.map(|pos| (pos, view.id))
|
.map(|pos| (pos, view.id))
|
||||||
});
|
});
|
||||||
|
|
||||||
if let Some((pos, view_id)) = result {
|
if let Some((pos, view_id)) = result {
|
||||||
let doc = &mut editor.documents[editor.tree.get(view_id).doc];
|
let doc = editor.document_mut(editor.tree.get(view_id).doc).unwrap();
|
||||||
|
|
||||||
if modifiers == crossterm::event::KeyModifiers::ALT {
|
if modifiers == crossterm::event::KeyModifiers::ALT {
|
||||||
let selection = doc.selection(view_id).clone();
|
let selection = doc.selection(view_id).clone();
|
||||||
|
@ -1096,7 +1150,7 @@ impl EditorView {
|
||||||
};
|
};
|
||||||
|
|
||||||
let result = cxt.editor.tree.views().find_map(|(view, _focus)| {
|
let result = cxt.editor.tree.views().find_map(|(view, _focus)| {
|
||||||
view.pos_at_screen_coords(&cxt.editor.documents[view.doc], row, column)
|
view.pos_at_screen_coords(&cxt.editor.documents[&view.doc], row, column)
|
||||||
.map(|_| view.id)
|
.map(|_| view.id)
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -1182,12 +1236,12 @@ impl EditorView {
|
||||||
}
|
}
|
||||||
|
|
||||||
let result = editor.tree.views().find_map(|(view, _focus)| {
|
let result = editor.tree.views().find_map(|(view, _focus)| {
|
||||||
view.pos_at_screen_coords(&editor.documents[view.doc], row, column)
|
view.pos_at_screen_coords(&editor.documents[&view.doc], row, column)
|
||||||
.map(|pos| (pos, view.id))
|
.map(|pos| (pos, view.id))
|
||||||
});
|
});
|
||||||
|
|
||||||
if let Some((pos, view_id)) = result {
|
if let Some((pos, view_id)) = result {
|
||||||
let doc = &mut editor.documents[editor.tree.get(view_id).doc];
|
let doc = editor.document_mut(editor.tree.get(view_id).doc).unwrap();
|
||||||
doc.set_selection(view_id, Selection::point(pos));
|
doc.set_selection(view_id, Selection::point(pos));
|
||||||
editor.tree.focus = view_id;
|
editor.tree.focus = view_id;
|
||||||
commands::Command::paste_primary_clipboard_before.execute(cxt);
|
commands::Command::paste_primary_clipboard_before.execute(cxt);
|
||||||
|
@ -1254,8 +1308,7 @@ impl Component for EditorView {
|
||||||
|
|
||||||
if callback.is_some() {
|
if callback.is_some() {
|
||||||
// assume close_fn
|
// assume close_fn
|
||||||
self.completion = None;
|
self.clear_completion(cxt.editor);
|
||||||
cxt.editor.clear_idle_timer(); // don't retrigger
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1268,8 +1321,7 @@ impl Component for EditorView {
|
||||||
if let Some(completion) = &mut self.completion {
|
if let Some(completion) = &mut self.completion {
|
||||||
completion.update(&mut cxt);
|
completion.update(&mut cxt);
|
||||||
if completion.is_empty() {
|
if completion.is_empty() {
|
||||||
self.completion = None;
|
self.clear_completion(cxt.editor);
|
||||||
cxt.editor.clear_idle_timer(); // don't retrigger
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1397,8 +1449,10 @@ impl Component for EditorView {
|
||||||
info.render(area, surface, cx);
|
info.render(area, surface, cx);
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(ref mut info) = self.autoinfo {
|
if cx.editor.config.auto_info {
|
||||||
info.render(area, surface, cx);
|
if let Some(ref mut info) = self.autoinfo {
|
||||||
|
info.render(area, surface, cx);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let key_width = 15u16; // for showing pending keys
|
let key_width = 15u16; // for showing pending keys
|
||||||
|
@ -1469,7 +1523,7 @@ fn canonicalize_key(key: &mut KeyEvent) {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn abs_diff(a: usize, b: usize) -> usize {
|
const fn abs_diff(a: usize, b: usize) -> usize {
|
||||||
if a > b {
|
if a > b {
|
||||||
a - b
|
a - b
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -64,25 +64,23 @@ impl<T: Item> Menu<T> {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn score(&mut self, pattern: &str) {
|
pub fn score(&mut self, pattern: &str) {
|
||||||
// need to borrow via pattern match otherwise it complains about simultaneous borrow
|
|
||||||
let Self {
|
|
||||||
ref mut matcher,
|
|
||||||
ref mut matches,
|
|
||||||
ref options,
|
|
||||||
..
|
|
||||||
} = *self;
|
|
||||||
|
|
||||||
// reuse the matches allocation
|
// reuse the matches allocation
|
||||||
matches.clear();
|
self.matches.clear();
|
||||||
matches.extend(options.iter().enumerate().filter_map(|(index, option)| {
|
self.matches.extend(
|
||||||
let text = option.filter_text();
|
self.options
|
||||||
// TODO: using fuzzy_indices could give us the char idx for match highlighting
|
.iter()
|
||||||
matcher
|
.enumerate()
|
||||||
.fuzzy_match(text, pattern)
|
.filter_map(|(index, option)| {
|
||||||
.map(|score| (index, score))
|
let text = option.filter_text();
|
||||||
}));
|
// TODO: using fuzzy_indices could give us the char idx for match highlighting
|
||||||
|
self.matcher
|
||||||
|
.fuzzy_match(text, pattern)
|
||||||
|
.map(|score| (index, score))
|
||||||
|
}),
|
||||||
|
);
|
||||||
// matches.sort_unstable_by_key(|(_, score)| -score);
|
// matches.sort_unstable_by_key(|(_, score)| -score);
|
||||||
matches.sort_unstable_by_key(|(index, _score)| options[*index].sort_text());
|
self.matches
|
||||||
|
.sort_unstable_by_key(|(index, _score)| self.options[*index].sort_text());
|
||||||
|
|
||||||
// reset cursor position
|
// reset cursor position
|
||||||
self.cursor = None;
|
self.cursor = None;
|
||||||
|
@ -100,7 +98,8 @@ impl<T: Item> Menu<T> {
|
||||||
|
|
||||||
pub fn move_up(&mut self) {
|
pub fn move_up(&mut self) {
|
||||||
let len = self.matches.len();
|
let len = self.matches.len();
|
||||||
let pos = self.cursor.map_or(0, |i| (i + len.saturating_sub(1)) % len) % len;
|
let max_index = len.saturating_sub(1);
|
||||||
|
let pos = self.cursor.map_or(max_index, |i| (i + max_index) % len) % len;
|
||||||
self.cursor = Some(pos);
|
self.cursor = Some(pos);
|
||||||
self.adjust_scroll();
|
self.adjust_scroll();
|
||||||
}
|
}
|
||||||
|
@ -216,6 +215,10 @@ impl<T: Item + 'static> Component for Menu<T> {
|
||||||
| KeyEvent {
|
| KeyEvent {
|
||||||
code: KeyCode::Char('p'),
|
code: KeyCode::Char('p'),
|
||||||
modifiers: KeyModifiers::CONTROL,
|
modifiers: KeyModifiers::CONTROL,
|
||||||
|
}
|
||||||
|
| KeyEvent {
|
||||||
|
code: KeyCode::Char('k'),
|
||||||
|
modifiers: KeyModifiers::CONTROL,
|
||||||
} => {
|
} => {
|
||||||
self.move_up();
|
self.move_up();
|
||||||
(self.callback_fn)(cx.editor, self.selection(), MenuEvent::Update);
|
(self.callback_fn)(cx.editor, self.selection(), MenuEvent::Update);
|
||||||
|
@ -233,6 +236,10 @@ impl<T: Item + 'static> Component for Menu<T> {
|
||||||
| KeyEvent {
|
| KeyEvent {
|
||||||
code: KeyCode::Char('n'),
|
code: KeyCode::Char('n'),
|
||||||
modifiers: KeyModifiers::CONTROL,
|
modifiers: KeyModifiers::CONTROL,
|
||||||
|
}
|
||||||
|
| KeyEvent {
|
||||||
|
code: KeyCode::Char('j'),
|
||||||
|
modifiers: KeyModifiers::CONTROL,
|
||||||
} => {
|
} => {
|
||||||
self.move_down();
|
self.move_down();
|
||||||
(self.callback_fn)(cx.editor, self.selection(), MenuEvent::Update);
|
(self.callback_fn)(cx.editor, self.selection(), MenuEvent::Update);
|
||||||
|
|
|
@ -29,6 +29,7 @@ pub fn regex_prompt(
|
||||||
cx: &mut crate::commands::Context,
|
cx: &mut crate::commands::Context,
|
||||||
prompt: std::borrow::Cow<'static, str>,
|
prompt: std::borrow::Cow<'static, str>,
|
||||||
history_register: Option<char>,
|
history_register: Option<char>,
|
||||||
|
completion_fn: impl FnMut(&str) -> Vec<prompt::Completion> + 'static,
|
||||||
fun: impl Fn(&mut View, &mut Document, Regex, PromptEvent) + 'static,
|
fun: impl Fn(&mut View, &mut Document, Regex, PromptEvent) + 'static,
|
||||||
) -> Prompt {
|
) -> Prompt {
|
||||||
let (view, doc) = current!(cx.editor);
|
let (view, doc) = current!(cx.editor);
|
||||||
|
@ -38,7 +39,7 @@ pub fn regex_prompt(
|
||||||
Prompt::new(
|
Prompt::new(
|
||||||
prompt,
|
prompt,
|
||||||
history_register,
|
history_register,
|
||||||
|_input: &str| Vec::new(), // this is fine because Vec::new() doesn't allocate
|
completion_fn,
|
||||||
move |cx: &mut crate::compositor::Context, input: &str, event: PromptEvent| {
|
move |cx: &mut crate::compositor::Context, input: &str, event: PromptEvent| {
|
||||||
match event {
|
match event {
|
||||||
PromptEvent::Abort => {
|
PromptEvent::Abort => {
|
||||||
|
@ -92,9 +93,25 @@ pub fn regex_prompt(
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn file_picker(root: PathBuf) -> FilePicker<PathBuf> {
|
pub fn file_picker(root: PathBuf) -> FilePicker<PathBuf> {
|
||||||
use ignore::Walk;
|
use ignore::{types::TypesBuilder, WalkBuilder};
|
||||||
use std::time;
|
use std::time;
|
||||||
let files = Walk::new(&root).filter_map(|entry| {
|
|
||||||
|
// We want to exclude files that the editor can't handle yet
|
||||||
|
let mut type_builder = TypesBuilder::new();
|
||||||
|
let mut walk_builder = WalkBuilder::new(&root);
|
||||||
|
let walk_builder = match type_builder.add(
|
||||||
|
"compressed",
|
||||||
|
"*.{zip,gz,bz2,zst,lzo,sz,tgz,tbz2,lz,lz4,lzma,lzo,z,Z,xz,7z,rar,cab}",
|
||||||
|
) {
|
||||||
|
Err(_) => &walk_builder,
|
||||||
|
_ => {
|
||||||
|
type_builder.negate("all");
|
||||||
|
let excluded_types = type_builder.build().unwrap();
|
||||||
|
walk_builder.types(excluded_types)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let files = walk_builder.build().filter_map(|entry| {
|
||||||
let entry = entry.ok()?;
|
let entry = entry.ok()?;
|
||||||
// Path::is_dir() traverses symlinks, so we use it over DirEntry::is_dir
|
// Path::is_dir() traverses symlinks, so we use it over DirEntry::is_dir
|
||||||
if entry.path().is_dir() {
|
if entry.path().is_dir() {
|
||||||
|
|
|
@ -12,7 +12,12 @@ use fuzzy_matcher::skim::SkimMatcherV2 as Matcher;
|
||||||
use fuzzy_matcher::FuzzyMatcher;
|
use fuzzy_matcher::FuzzyMatcher;
|
||||||
use tui::widgets::Widget;
|
use tui::widgets::Widget;
|
||||||
|
|
||||||
use std::{borrow::Cow, collections::HashMap, path::PathBuf};
|
use std::{
|
||||||
|
borrow::Cow,
|
||||||
|
collections::HashMap,
|
||||||
|
io::Read,
|
||||||
|
path::{Path, PathBuf},
|
||||||
|
};
|
||||||
|
|
||||||
use crate::ui::{Prompt, PromptEvent};
|
use crate::ui::{Prompt, PromptEvent};
|
||||||
use helix_core::Position;
|
use helix_core::Position;
|
||||||
|
@ -23,18 +28,58 @@ use helix_view::{
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const MIN_SCREEN_WIDTH_FOR_PREVIEW: u16 = 80;
|
pub const MIN_SCREEN_WIDTH_FOR_PREVIEW: u16 = 80;
|
||||||
|
/// Biggest file size to preview in bytes
|
||||||
|
pub const MAX_FILE_SIZE_FOR_PREVIEW: u64 = 10 * 1024 * 1024;
|
||||||
|
|
||||||
/// File path and line number (used to align and highlight a line)
|
/// File path and range of lines (used to align and highlight lines)
|
||||||
type FileLocation = (PathBuf, Option<(usize, usize)>);
|
type FileLocation = (PathBuf, Option<(usize, usize)>);
|
||||||
|
|
||||||
pub struct FilePicker<T> {
|
pub struct FilePicker<T> {
|
||||||
picker: Picker<T>,
|
picker: Picker<T>,
|
||||||
/// Caches paths to documents
|
/// Caches paths to documents
|
||||||
preview_cache: HashMap<PathBuf, Document>,
|
preview_cache: HashMap<PathBuf, CachedPreview>,
|
||||||
|
read_buffer: Vec<u8>,
|
||||||
/// Given an item in the picker, return the file path and line number to display.
|
/// Given an item in the picker, return the file path and line number to display.
|
||||||
file_fn: Box<dyn Fn(&Editor, &T) -> Option<FileLocation>>,
|
file_fn: Box<dyn Fn(&Editor, &T) -> Option<FileLocation>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub enum CachedPreview {
|
||||||
|
Document(Document),
|
||||||
|
Binary,
|
||||||
|
LargeFile,
|
||||||
|
NotFound,
|
||||||
|
}
|
||||||
|
|
||||||
|
// We don't store this enum in the cache so as to avoid lifetime constraints
|
||||||
|
// from borrowing a document already opened in the editor.
|
||||||
|
pub enum Preview<'picker, 'editor> {
|
||||||
|
Cached(&'picker CachedPreview),
|
||||||
|
EditorDocument(&'editor Document),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Preview<'_, '_> {
|
||||||
|
fn document(&self) -> Option<&Document> {
|
||||||
|
match self {
|
||||||
|
Preview::EditorDocument(doc) => Some(doc),
|
||||||
|
Preview::Cached(CachedPreview::Document(doc)) => Some(doc),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Alternate text to show for the preview.
|
||||||
|
fn placeholder(&self) -> &str {
|
||||||
|
match *self {
|
||||||
|
Self::EditorDocument(_) => "<File preview>",
|
||||||
|
Self::Cached(preview) => match preview {
|
||||||
|
CachedPreview::Document(_) => "<File preview>",
|
||||||
|
CachedPreview::Binary => "<Binary file>",
|
||||||
|
CachedPreview::LargeFile => "<File too large to preview>",
|
||||||
|
CachedPreview::NotFound => "<File not found>",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl<T> FilePicker<T> {
|
impl<T> FilePicker<T> {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
options: Vec<T>,
|
options: Vec<T>,
|
||||||
|
@ -45,6 +90,7 @@ impl<T> FilePicker<T> {
|
||||||
Self {
|
Self {
|
||||||
picker: Picker::new(false, options, format_fn, callback_fn),
|
picker: Picker::new(false, options, format_fn, callback_fn),
|
||||||
preview_cache: HashMap::new(),
|
preview_cache: HashMap::new(),
|
||||||
|
read_buffer: Vec::with_capacity(1024),
|
||||||
file_fn: Box::new(preview_fn),
|
file_fn: Box::new(preview_fn),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -60,14 +106,45 @@ impl<T> FilePicker<T> {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn calculate_preview(&mut self, editor: &Editor) {
|
/// Get (cached) preview for a given path. If a document corresponding
|
||||||
if let Some((path, _line)) = self.current_file(editor) {
|
/// to the path is already open in the editor, it is used instead.
|
||||||
if !self.preview_cache.contains_key(&path) && editor.document_by_path(&path).is_none() {
|
fn get_preview<'picker, 'editor>(
|
||||||
// TODO: enable syntax highlighting; blocked by async rendering
|
&'picker mut self,
|
||||||
let doc = Document::open(&path, None, Some(&editor.theme), None).unwrap();
|
path: &Path,
|
||||||
self.preview_cache.insert(path, doc);
|
editor: &'editor Editor,
|
||||||
}
|
) -> Preview<'picker, 'editor> {
|
||||||
|
if let Some(doc) = editor.document_by_path(path) {
|
||||||
|
return Preview::EditorDocument(doc);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if self.preview_cache.contains_key(path) {
|
||||||
|
return Preview::Cached(&self.preview_cache[path]);
|
||||||
|
}
|
||||||
|
|
||||||
|
let data = std::fs::File::open(path).and_then(|file| {
|
||||||
|
let metadata = file.metadata()?;
|
||||||
|
// Read up to 1kb to detect the content type
|
||||||
|
let n = file.take(1024).read_to_end(&mut self.read_buffer)?;
|
||||||
|
let content_type = content_inspector::inspect(&self.read_buffer[..n]);
|
||||||
|
self.read_buffer.clear();
|
||||||
|
Ok((metadata, content_type))
|
||||||
|
});
|
||||||
|
let preview = data
|
||||||
|
.map(
|
||||||
|
|(metadata, content_type)| match (metadata.len(), content_type) {
|
||||||
|
(_, content_inspector::ContentType::BINARY) => CachedPreview::Binary,
|
||||||
|
(size, _) if size > MAX_FILE_SIZE_FOR_PREVIEW => CachedPreview::LargeFile,
|
||||||
|
_ => {
|
||||||
|
// TODO: enable syntax highlighting; blocked by async rendering
|
||||||
|
Document::open(path, None, Some(&editor.theme), None)
|
||||||
|
.map(CachedPreview::Document)
|
||||||
|
.unwrap_or(CachedPreview::NotFound)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.unwrap_or(CachedPreview::NotFound);
|
||||||
|
self.preview_cache.insert(path.to_owned(), preview);
|
||||||
|
Preview::Cached(&self.preview_cache[path])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -79,12 +156,12 @@ impl<T: 'static> Component for FilePicker<T> {
|
||||||
// |picker | | |
|
// |picker | | |
|
||||||
// | | | |
|
// | | | |
|
||||||
// +---------+ +---------+
|
// +---------+ +---------+
|
||||||
self.calculate_preview(cx.editor);
|
|
||||||
let render_preview = area.width > MIN_SCREEN_WIDTH_FOR_PREVIEW;
|
let render_preview = area.width > MIN_SCREEN_WIDTH_FOR_PREVIEW;
|
||||||
let area = inner_rect(area);
|
let area = inner_rect(area);
|
||||||
// -- Render the frame:
|
// -- Render the frame:
|
||||||
// clear area
|
// clear area
|
||||||
let background = cx.editor.theme.get("ui.background");
|
let background = cx.editor.theme.get("ui.background");
|
||||||
|
let text = cx.editor.theme.get("ui.text");
|
||||||
surface.clear_with(area, background);
|
surface.clear_with(area, background);
|
||||||
|
|
||||||
let picker_width = if render_preview {
|
let picker_width = if render_preview {
|
||||||
|
@ -113,17 +190,23 @@ impl<T: 'static> Component for FilePicker<T> {
|
||||||
horizontal: 1,
|
horizontal: 1,
|
||||||
};
|
};
|
||||||
let inner = inner.inner(&margin);
|
let inner = inner.inner(&margin);
|
||||||
|
|
||||||
block.render(preview_area, surface);
|
block.render(preview_area, surface);
|
||||||
|
|
||||||
if let Some((doc, line)) = self.current_file(cx.editor).and_then(|(path, range)| {
|
if let Some((path, range)) = self.current_file(cx.editor) {
|
||||||
cx.editor
|
let preview = self.get_preview(&path, cx.editor);
|
||||||
.document_by_path(&path)
|
let doc = match preview.document() {
|
||||||
.or_else(|| self.preview_cache.get(&path))
|
Some(doc) => doc,
|
||||||
.zip(Some(range))
|
None => {
|
||||||
}) {
|
let alt_text = preview.placeholder();
|
||||||
|
let x = inner.x + inner.width.saturating_sub(alt_text.len() as u16) / 2;
|
||||||
|
let y = inner.y + inner.height / 2;
|
||||||
|
surface.set_stringn(x, y, alt_text, inner.width as usize, text);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// align to middle
|
// align to middle
|
||||||
let first_line = line
|
let first_line = range
|
||||||
.map(|(start, end)| {
|
.map(|(start, end)| {
|
||||||
let height = end.saturating_sub(start) + 1;
|
let height = end.saturating_sub(start) + 1;
|
||||||
let middle = start + (height.saturating_sub(1) / 2);
|
let middle = start + (height.saturating_sub(1) / 2);
|
||||||
|
@ -150,7 +233,7 @@ impl<T: 'static> Component for FilePicker<T> {
|
||||||
);
|
);
|
||||||
|
|
||||||
// highlight the line
|
// highlight the line
|
||||||
if let Some((start, end)) = line {
|
if let Some((start, end)) = range {
|
||||||
let offset = start.saturating_sub(first_line) as u16;
|
let offset = start.saturating_sub(first_line) as u16;
|
||||||
surface.set_style(
|
surface.set_style(
|
||||||
Rect::new(
|
Rect::new(
|
||||||
|
@ -234,37 +317,28 @@ impl<T> Picker<T> {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn score(&mut self) {
|
pub fn score(&mut self) {
|
||||||
// need to borrow via pattern match otherwise it complains about simultaneous borrow
|
|
||||||
let Self {
|
|
||||||
ref mut matcher,
|
|
||||||
ref mut matches,
|
|
||||||
ref filters,
|
|
||||||
ref format_fn,
|
|
||||||
..
|
|
||||||
} = *self;
|
|
||||||
|
|
||||||
let pattern = &self.prompt.line;
|
let pattern = &self.prompt.line;
|
||||||
|
|
||||||
// reuse the matches allocation
|
// reuse the matches allocation
|
||||||
matches.clear();
|
self.matches.clear();
|
||||||
matches.extend(
|
self.matches.extend(
|
||||||
self.options
|
self.options
|
||||||
.iter()
|
.iter()
|
||||||
.enumerate()
|
.enumerate()
|
||||||
.filter_map(|(index, option)| {
|
.filter_map(|(index, option)| {
|
||||||
// filter options first before matching
|
// filter options first before matching
|
||||||
if !filters.is_empty() {
|
if !self.filters.is_empty() {
|
||||||
filters.binary_search(&index).ok()?;
|
self.filters.binary_search(&index).ok()?;
|
||||||
}
|
}
|
||||||
// TODO: maybe using format_fn isn't the best idea here
|
// TODO: maybe using format_fn isn't the best idea here
|
||||||
let text = (format_fn)(option);
|
let text = (self.format_fn)(option);
|
||||||
// TODO: using fuzzy_indices could give us the char idx for match highlighting
|
// TODO: using fuzzy_indices could give us the char idx for match highlighting
|
||||||
matcher
|
self.matcher
|
||||||
.fuzzy_match(&text, pattern)
|
.fuzzy_match(&text, pattern)
|
||||||
.map(|score| (index, score))
|
.map(|score| (index, score))
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
matches.sort_unstable_by_key(|(_, score)| -score);
|
self.matches.sort_unstable_by_key(|(_, score)| -score);
|
||||||
|
|
||||||
// reset cursor position
|
// reset cursor position
|
||||||
self.cursor = 0;
|
self.cursor = 0;
|
||||||
|
@ -337,6 +411,10 @@ impl<T: 'static> Component for Picker<T> {
|
||||||
code: KeyCode::BackTab,
|
code: KeyCode::BackTab,
|
||||||
..
|
..
|
||||||
}
|
}
|
||||||
|
| KeyEvent {
|
||||||
|
code: KeyCode::Char('k'),
|
||||||
|
modifiers: KeyModifiers::CONTROL,
|
||||||
|
}
|
||||||
| KeyEvent {
|
| KeyEvent {
|
||||||
code: KeyCode::Char('p'),
|
code: KeyCode::Char('p'),
|
||||||
modifiers: KeyModifiers::CONTROL,
|
modifiers: KeyModifiers::CONTROL,
|
||||||
|
@ -350,6 +428,10 @@ impl<T: 'static> Component for Picker<T> {
|
||||||
| KeyEvent {
|
| KeyEvent {
|
||||||
code: KeyCode::Tab, ..
|
code: KeyCode::Tab, ..
|
||||||
}
|
}
|
||||||
|
| KeyEvent {
|
||||||
|
code: KeyCode::Char('j'),
|
||||||
|
modifiers: KeyModifiers::CONTROL,
|
||||||
|
}
|
||||||
| KeyEvent {
|
| KeyEvent {
|
||||||
code: KeyCode::Char('n'),
|
code: KeyCode::Char('n'),
|
||||||
modifiers: KeyModifiers::CONTROL,
|
modifiers: KeyModifiers::CONTROL,
|
||||||
|
@ -375,7 +457,7 @@ impl<T: 'static> Component for Picker<T> {
|
||||||
return close_fn;
|
return close_fn;
|
||||||
}
|
}
|
||||||
KeyEvent {
|
KeyEvent {
|
||||||
code: KeyCode::Char('h'),
|
code: KeyCode::Char('s'),
|
||||||
modifiers: KeyModifiers::CONTROL,
|
modifiers: KeyModifiers::CONTROL,
|
||||||
} => {
|
} => {
|
||||||
if let Some(option) = self.selection() {
|
if let Some(option) = self.selection() {
|
||||||
|
@ -485,6 +567,7 @@ impl<T: 'static> Component for Picker<T> {
|
||||||
text_style
|
text_style
|
||||||
},
|
},
|
||||||
true,
|
true,
|
||||||
|
true,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -186,6 +186,11 @@ impl Prompt {
|
||||||
self.exit_selection();
|
self.exit_selection();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn insert_str(&mut self, s: &str) {
|
||||||
|
self.line.insert_str(self.cursor, s);
|
||||||
|
self.cursor += s.len();
|
||||||
|
}
|
||||||
|
|
||||||
pub fn move_cursor(&mut self, movement: Movement) {
|
pub fn move_cursor(&mut self, movement: Movement) {
|
||||||
let pos = self.eval_movement(movement);
|
let pos = self.eval_movement(movement);
|
||||||
self.cursor = pos
|
self.cursor = pos
|
||||||
|
@ -474,6 +479,26 @@ impl Component for Prompt {
|
||||||
self.delete_char_backwards();
|
self.delete_char_backwards();
|
||||||
(self.callback_fn)(cx, &self.line, PromptEvent::Update);
|
(self.callback_fn)(cx, &self.line, PromptEvent::Update);
|
||||||
}
|
}
|
||||||
|
KeyEvent {
|
||||||
|
code: KeyCode::Char('s'),
|
||||||
|
modifiers: KeyModifiers::CONTROL,
|
||||||
|
} => {
|
||||||
|
let (view, doc) = current!(cx.editor);
|
||||||
|
let text = doc.text().slice(..);
|
||||||
|
|
||||||
|
use helix_core::textobject;
|
||||||
|
let range = textobject::textobject_word(
|
||||||
|
text,
|
||||||
|
doc.selection(view.id).primary(),
|
||||||
|
textobject::TextObject::Inside,
|
||||||
|
1,
|
||||||
|
);
|
||||||
|
let line = text.slice(range.from()..range.to()).to_string();
|
||||||
|
if !line.is_empty() {
|
||||||
|
self.insert_str(line.as_str());
|
||||||
|
(self.callback_fn)(cx, &self.line, PromptEvent::Update);
|
||||||
|
}
|
||||||
|
}
|
||||||
KeyEvent {
|
KeyEvent {
|
||||||
code: KeyCode::Enter,
|
code: KeyCode::Enter,
|
||||||
..
|
..
|
||||||
|
@ -502,6 +527,7 @@ impl Component for Prompt {
|
||||||
if let Some(register) = self.history_register {
|
if let Some(register) = self.history_register {
|
||||||
let register = cx.editor.registers.get_mut(register);
|
let register = cx.editor.registers.get_mut(register);
|
||||||
self.change_history(register.read(), CompletionDirection::Backward);
|
self.change_history(register.read(), CompletionDirection::Backward);
|
||||||
|
(self.callback_fn)(cx, &self.line, PromptEvent::Update);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
KeyEvent {
|
KeyEvent {
|
||||||
|
@ -515,15 +541,22 @@ impl Component for Prompt {
|
||||||
if let Some(register) = self.history_register {
|
if let Some(register) = self.history_register {
|
||||||
let register = cx.editor.registers.get_mut(register);
|
let register = cx.editor.registers.get_mut(register);
|
||||||
self.change_history(register.read(), CompletionDirection::Forward);
|
self.change_history(register.read(), CompletionDirection::Forward);
|
||||||
|
(self.callback_fn)(cx, &self.line, PromptEvent::Update);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
KeyEvent {
|
KeyEvent {
|
||||||
code: KeyCode::Tab, ..
|
code: KeyCode::Tab, ..
|
||||||
} => self.change_completion_selection(CompletionDirection::Forward),
|
} => {
|
||||||
|
self.change_completion_selection(CompletionDirection::Forward);
|
||||||
|
(self.callback_fn)(cx, &self.line, PromptEvent::Update)
|
||||||
|
}
|
||||||
KeyEvent {
|
KeyEvent {
|
||||||
code: KeyCode::BackTab,
|
code: KeyCode::BackTab,
|
||||||
..
|
..
|
||||||
} => self.change_completion_selection(CompletionDirection::Backward),
|
} => {
|
||||||
|
self.change_completion_selection(CompletionDirection::Backward);
|
||||||
|
(self.callback_fn)(cx, &self.line, PromptEvent::Update)
|
||||||
|
}
|
||||||
KeyEvent {
|
KeyEvent {
|
||||||
code: KeyCode::Char('q'),
|
code: KeyCode::Char('q'),
|
||||||
modifiers: KeyModifiers::CONTROL,
|
modifiers: KeyModifiers::CONTROL,
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
[package]
|
[package]
|
||||||
name = "helix-tui"
|
name = "helix-tui"
|
||||||
version = "0.4.1"
|
version = "0.5.0"
|
||||||
authors = ["Blaž Hrastnik <blaz@mxxn.io>"]
|
authors = ["Blaž Hrastnik <blaz@mxxn.io>"]
|
||||||
description = """
|
description = """
|
||||||
A library to build rich terminal user interfaces or dashboards
|
A library to build rich terminal user interfaces or dashboards
|
||||||
"""
|
"""
|
||||||
edition = "2018"
|
edition = "2021"
|
||||||
license = "MPL-2.0"
|
license = "MPL-2.0"
|
||||||
categories = ["editor"]
|
categories = ["editor"]
|
||||||
repository = "https://github.com/helix-editor/helix"
|
repository = "https://github.com/helix-editor/helix"
|
||||||
|
@ -19,7 +19,7 @@ default = ["crossterm"]
|
||||||
bitflags = "1.3"
|
bitflags = "1.3"
|
||||||
cassowary = "0.3"
|
cassowary = "0.3"
|
||||||
unicode-segmentation = "1.8"
|
unicode-segmentation = "1.8"
|
||||||
crossterm = { version = "0.21", optional = true }
|
crossterm = { version = "0.22", optional = true }
|
||||||
serde = { version = "1", "optional" = true, features = ["derive"]}
|
serde = { version = "1", "optional" = true, features = ["derive"]}
|
||||||
helix-view = { version = "0.4", path = "../helix-view", features = ["term"] }
|
helix-view = { version = "0.5", path = "../helix-view", features = ["term"] }
|
||||||
helix-core = { version = "0.4", path = "../helix-core" }
|
helix-core = { version = "0.5", path = "../helix-core" }
|
||||||
|
|
|
@ -266,12 +266,14 @@ impl Buffer {
|
||||||
where
|
where
|
||||||
S: AsRef<str>,
|
S: AsRef<str>,
|
||||||
{
|
{
|
||||||
self.set_string_truncated(x, y, string, width, style, false)
|
self.set_string_truncated(x, y, string, width, style, false, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Print at most the first `width` characters of a string if enough space is available
|
/// Print at most the first `width` characters of a string if enough space is available
|
||||||
/// until the end of the line. If `markend` is true appends a `…` at the end of
|
/// until the end of the line. If `ellipsis` is true appends a `…` at the end of
|
||||||
/// truncated lines.
|
/// truncated lines. If `truncate_start` is `true`, truncate the beginning of the string
|
||||||
|
/// instead of the end.
|
||||||
|
#[allow(clippy::too_many_arguments)]
|
||||||
pub fn set_string_truncated<S>(
|
pub fn set_string_truncated<S>(
|
||||||
&mut self,
|
&mut self,
|
||||||
x: u16,
|
x: u16,
|
||||||
|
@ -280,6 +282,7 @@ impl Buffer {
|
||||||
width: usize,
|
width: usize,
|
||||||
style: Style,
|
style: Style,
|
||||||
ellipsis: bool,
|
ellipsis: bool,
|
||||||
|
truncate_start: bool,
|
||||||
) -> (u16, u16)
|
) -> (u16, u16)
|
||||||
where
|
where
|
||||||
S: AsRef<str>,
|
S: AsRef<str>,
|
||||||
|
@ -289,28 +292,59 @@ impl Buffer {
|
||||||
let width = if ellipsis { width - 1 } else { width };
|
let width = if ellipsis { width - 1 } else { width };
|
||||||
let graphemes = UnicodeSegmentation::graphemes(string.as_ref(), true);
|
let graphemes = UnicodeSegmentation::graphemes(string.as_ref(), true);
|
||||||
let max_offset = min(self.area.right() as usize, width.saturating_add(x as usize));
|
let max_offset = min(self.area.right() as usize, width.saturating_add(x as usize));
|
||||||
for s in graphemes {
|
if !truncate_start {
|
||||||
let width = s.width();
|
for s in graphemes {
|
||||||
if width == 0 {
|
let width = s.width();
|
||||||
continue;
|
if width == 0 {
|
||||||
}
|
continue;
|
||||||
// `x_offset + width > max_offset` could be integer overflow on 32-bit machines if we
|
}
|
||||||
// change dimenstions to usize or u32 and someone resizes the terminal to 1x2^32.
|
// `x_offset + width > max_offset` could be integer overflow on 32-bit machines if we
|
||||||
if width > max_offset.saturating_sub(x_offset) {
|
// change dimenstions to usize or u32 and someone resizes the terminal to 1x2^32.
|
||||||
break;
|
if width > max_offset.saturating_sub(x_offset) {
|
||||||
}
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
self.content[index].set_symbol(s);
|
self.content[index].set_symbol(s);
|
||||||
self.content[index].set_style(style);
|
self.content[index].set_style(style);
|
||||||
// Reset following cells if multi-width (they would be hidden by the grapheme),
|
// Reset following cells if multi-width (they would be hidden by the grapheme),
|
||||||
for i in index + 1..index + width {
|
for i in index + 1..index + width {
|
||||||
self.content[i].reset();
|
self.content[i].reset();
|
||||||
|
}
|
||||||
|
index += width;
|
||||||
|
x_offset += width;
|
||||||
|
}
|
||||||
|
if ellipsis && x_offset - (x as usize) < string.as_ref().width() {
|
||||||
|
self.content[index].set_symbol("…");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let mut start_index = self.index_of(x, y);
|
||||||
|
let mut index = self.index_of(max_offset as u16, y);
|
||||||
|
|
||||||
|
let total_width = string.as_ref().width();
|
||||||
|
let truncated = total_width > width;
|
||||||
|
if ellipsis && truncated {
|
||||||
|
self.content[start_index].set_symbol("…");
|
||||||
|
start_index += 1;
|
||||||
|
}
|
||||||
|
if !truncated {
|
||||||
|
index -= width - total_width;
|
||||||
|
}
|
||||||
|
for s in graphemes.rev() {
|
||||||
|
let width = s.width();
|
||||||
|
if width == 0 {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let start = index - width;
|
||||||
|
if start < start_index {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
self.content[start].set_symbol(s);
|
||||||
|
self.content[start].set_style(style);
|
||||||
|
for i in start + 1..index {
|
||||||
|
self.content[i].reset();
|
||||||
|
}
|
||||||
|
index -= width;
|
||||||
}
|
}
|
||||||
index += width;
|
|
||||||
x_offset += width;
|
|
||||||
}
|
|
||||||
if ellipsis && x_offset - (x as usize) < string.as_ref().width() {
|
|
||||||
self.content[index].set_symbol("…");
|
|
||||||
}
|
}
|
||||||
(x_offset as u16, y)
|
(x_offset as u16, y)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
[package]
|
[package]
|
||||||
name = "helix-view"
|
name = "helix-view"
|
||||||
version = "0.4.1"
|
version = "0.5.0"
|
||||||
authors = ["Blaž Hrastnik <blaz@mxxn.io>"]
|
authors = ["Blaž Hrastnik <blaz@mxxn.io>"]
|
||||||
edition = "2018"
|
edition = "2021"
|
||||||
license = "MPL-2.0"
|
license = "MPL-2.0"
|
||||||
description = "UI abstractions for use in backends"
|
description = "UI abstractions for use in backends"
|
||||||
categories = ["editor"]
|
categories = ["editor"]
|
||||||
|
@ -16,10 +16,10 @@ term = ["crossterm"]
|
||||||
[dependencies]
|
[dependencies]
|
||||||
bitflags = "1.3"
|
bitflags = "1.3"
|
||||||
anyhow = "1"
|
anyhow = "1"
|
||||||
helix-core = { version = "0.4", path = "../helix-core" }
|
helix-core = { version = "0.5", path = "../helix-core" }
|
||||||
helix-lsp = { version = "0.4", path = "../helix-lsp"}
|
helix-lsp = { version = "0.5", path = "../helix-lsp"}
|
||||||
helix-dap = { version = "0.4", path = "../helix-dap"}
|
helix-dap = { version = "0.5", path = "../helix-dap"}
|
||||||
crossterm = { version = "0.21", optional = true }
|
crossterm = { version = "0.22", optional = true }
|
||||||
|
|
||||||
# Conversion traits
|
# Conversion traits
|
||||||
once_cell = "1.8"
|
once_cell = "1.8"
|
||||||
|
|
|
@ -116,7 +116,7 @@ pub fn get_clipboard_provider() -> Box<dyn ClipboardProvider> {
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
#[cfg(target_os = "windows")]
|
#[cfg(target_os = "windows")]
|
||||||
return Box::new(provider::WindowsProvider::new());
|
return Box::new(provider::WindowsProvider::default());
|
||||||
|
|
||||||
#[cfg(not(target_os = "windows"))]
|
#[cfg(not(target_os = "windows"))]
|
||||||
return Box::new(provider::NopProvider::new());
|
return Box::new(provider::NopProvider::new());
|
||||||
|
@ -145,15 +145,15 @@ mod provider {
|
||||||
use anyhow::{bail, Context as _, Result};
|
use anyhow::{bail, Context as _, Result};
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
|
|
||||||
|
#[cfg(not(target_os = "windows"))]
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct NopProvider {
|
pub struct NopProvider {
|
||||||
buf: String,
|
buf: String,
|
||||||
primary_buf: String,
|
primary_buf: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(not(target_os = "windows"))]
|
||||||
impl NopProvider {
|
impl NopProvider {
|
||||||
#[allow(dead_code)]
|
|
||||||
// Only dead_code on Windows.
|
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
buf: String::new(),
|
buf: String::new(),
|
||||||
|
@ -162,6 +162,7 @@ mod provider {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(not(target_os = "windows"))]
|
||||||
impl ClipboardProvider for NopProvider {
|
impl ClipboardProvider for NopProvider {
|
||||||
fn name(&self) -> Cow<str> {
|
fn name(&self) -> Cow<str> {
|
||||||
Cow::Borrowed("none")
|
Cow::Borrowed("none")
|
||||||
|
@ -186,19 +187,8 @@ mod provider {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(target_os = "windows")]
|
#[cfg(target_os = "windows")]
|
||||||
#[derive(Debug)]
|
#[derive(Default, Debug)]
|
||||||
pub struct WindowsProvider {
|
pub struct WindowsProvider;
|
||||||
selection_buf: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(target_os = "windows")]
|
|
||||||
impl WindowsProvider {
|
|
||||||
pub fn new() -> Self {
|
|
||||||
Self {
|
|
||||||
selection_buf: String::new(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(target_os = "windows")]
|
#[cfg(target_os = "windows")]
|
||||||
impl ClipboardProvider for WindowsProvider {
|
impl ClipboardProvider for WindowsProvider {
|
||||||
|
|
|
@ -23,6 +23,8 @@ use crate::{DocumentId, Theme, ViewId};
|
||||||
/// 8kB of buffer space for encoding and decoding `Rope`s.
|
/// 8kB of buffer space for encoding and decoding `Rope`s.
|
||||||
const BUF_SIZE: usize = 8192;
|
const BUF_SIZE: usize = 8192;
|
||||||
|
|
||||||
|
const DEFAULT_INDENT: IndentStyle = IndentStyle::Spaces(4);
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
|
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
|
||||||
pub enum Mode {
|
pub enum Mode {
|
||||||
Normal,
|
Normal,
|
||||||
|
@ -95,6 +97,9 @@ pub struct Document {
|
||||||
// it back as it separated from the edits. We could split out the parts manually but that will
|
// it back as it separated from the edits. We could split out the parts manually but that will
|
||||||
// be more troublesome.
|
// be more troublesome.
|
||||||
history: Cell<History>,
|
history: Cell<History>,
|
||||||
|
|
||||||
|
pub savepoint: Option<Transaction>,
|
||||||
|
|
||||||
last_saved_revision: usize,
|
last_saved_revision: usize,
|
||||||
version: i32, // should be usize?
|
version: i32, // should be usize?
|
||||||
|
|
||||||
|
@ -306,8 +311,7 @@ where
|
||||||
T: Default,
|
T: Default,
|
||||||
F: FnOnce(T) -> T,
|
F: FnOnce(T) -> T,
|
||||||
{
|
{
|
||||||
let t = mem::take(mut_ref);
|
*mut_ref = f(mem::take(mut_ref));
|
||||||
let _ = mem::replace(mut_ref, f(t));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
use helix_lsp::lsp;
|
use helix_lsp::lsp;
|
||||||
|
@ -325,7 +329,8 @@ impl Document {
|
||||||
encoding,
|
encoding,
|
||||||
text,
|
text,
|
||||||
selections: HashMap::default(),
|
selections: HashMap::default(),
|
||||||
indent_style: IndentStyle::Spaces(4),
|
indent_style: DEFAULT_INDENT,
|
||||||
|
line_ending: DEFAULT_LINE_ENDING,
|
||||||
mode: Mode::Normal,
|
mode: Mode::Normal,
|
||||||
restore_cursor: false,
|
restore_cursor: false,
|
||||||
syntax: None,
|
syntax: None,
|
||||||
|
@ -335,9 +340,9 @@ impl Document {
|
||||||
diagnostics: Vec::new(),
|
diagnostics: Vec::new(),
|
||||||
version: 0,
|
version: 0,
|
||||||
history: Cell::new(History::default()),
|
history: Cell::new(History::default()),
|
||||||
|
savepoint: None,
|
||||||
last_saved_revision: 0,
|
last_saved_revision: 0,
|
||||||
language_server: None,
|
language_server: None,
|
||||||
line_ending: DEFAULT_LINE_ENDING,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -363,7 +368,7 @@ impl Document {
|
||||||
let mut doc = Self::from(rope, Some(encoding));
|
let mut doc = Self::from(rope, Some(encoding));
|
||||||
|
|
||||||
// set the path and try detecting the language
|
// set the path and try detecting the language
|
||||||
doc.set_path(path)?;
|
doc.set_path(Some(path))?;
|
||||||
if let Some(loader) = config_loader {
|
if let Some(loader) = config_loader {
|
||||||
doc.detect_language(theme, loader);
|
doc.detect_language(theme, loader);
|
||||||
}
|
}
|
||||||
|
@ -495,17 +500,15 @@ impl Document {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Detect the indentation used in the file, or otherwise defaults to the language indentation
|
/// Detect the indentation used in the file, or otherwise defaults to the language indentation
|
||||||
/// configured in `languages.toml`, with a fallback back to 2 space indentation if it isn't
|
/// configured in `languages.toml`, with a fallback to 4 space indentation if it isn't
|
||||||
/// specified. Line ending is likewise auto-detected, and will fallback to the default OS
|
/// specified. Line ending is likewise auto-detected, and will fallback to the default OS
|
||||||
/// line ending.
|
/// line ending.
|
||||||
pub fn detect_indent_and_line_ending(&mut self) {
|
pub fn detect_indent_and_line_ending(&mut self) {
|
||||||
self.indent_style = auto_detect_indent_style(&self.text).unwrap_or_else(|| {
|
self.indent_style = auto_detect_indent_style(&self.text).unwrap_or_else(|| {
|
||||||
IndentStyle::from_str(
|
self.language
|
||||||
self.language
|
.as_ref()
|
||||||
.as_ref()
|
.and_then(|config| config.indent.as_ref())
|
||||||
.and_then(|config| config.indent.as_ref())
|
.map_or(DEFAULT_INDENT, |config| IndentStyle::from_str(&config.unit))
|
||||||
.map_or(" ", |config| config.unit.as_str()), // Fallback to 2 spaces.
|
|
||||||
)
|
|
||||||
});
|
});
|
||||||
self.line_ending = auto_detect_line_ending(&self.text).unwrap_or(DEFAULT_LINE_ENDING);
|
self.line_ending = auto_detect_line_ending(&self.text).unwrap_or(DEFAULT_LINE_ENDING);
|
||||||
}
|
}
|
||||||
|
@ -550,12 +553,14 @@ impl Document {
|
||||||
self.encoding
|
self.encoding
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_path(&mut self, path: &Path) -> Result<(), std::io::Error> {
|
pub fn set_path(&mut self, path: Option<&Path>) -> Result<(), std::io::Error> {
|
||||||
let path = helix_core::path::get_canonicalized_path(path)?;
|
let path = path
|
||||||
|
.map(helix_core::path::get_canonicalized_path)
|
||||||
|
.transpose()?;
|
||||||
|
|
||||||
// if parent doesn't exist we still want to open the document
|
// if parent doesn't exist we still want to open the document
|
||||||
// and error out when document is saved
|
// and error out when document is saved
|
||||||
self.path = Some(path);
|
self.path = path;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -635,6 +640,14 @@ impl Document {
|
||||||
if !transaction.changes().is_empty() {
|
if !transaction.changes().is_empty() {
|
||||||
self.version += 1;
|
self.version += 1;
|
||||||
|
|
||||||
|
// generate revert to savepoint
|
||||||
|
if self.savepoint.is_some() {
|
||||||
|
take_with(&mut self.savepoint, |prev_revert| {
|
||||||
|
let revert = transaction.invert(&old_doc);
|
||||||
|
Some(revert.compose(prev_revert.unwrap()))
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// update tree-sitter syntax tree
|
// update tree-sitter syntax tree
|
||||||
if let Some(syntax) = &mut self.syntax {
|
if let Some(syntax) = &mut self.syntax {
|
||||||
// TODO: no unwrap
|
// TODO: no unwrap
|
||||||
|
@ -644,14 +657,13 @@ impl Document {
|
||||||
}
|
}
|
||||||
|
|
||||||
// map state.diagnostics over changes::map_pos too
|
// map state.diagnostics over changes::map_pos too
|
||||||
// NOTE: seems to do nothing since the language server resends diagnostics on each edit
|
for diagnostic in &mut self.diagnostics {
|
||||||
// for diagnostic in &mut self.diagnostics {
|
use helix_core::Assoc;
|
||||||
// use helix_core::Assoc;
|
let changes = transaction.changes();
|
||||||
// let changes = transaction.changes();
|
diagnostic.range.start = changes.map_pos(diagnostic.range.start, Assoc::After);
|
||||||
// diagnostic.range.start = changes.map_pos(diagnostic.range.start, Assoc::After);
|
diagnostic.range.end = changes.map_pos(diagnostic.range.end, Assoc::After);
|
||||||
// diagnostic.range.end = changes.map_pos(diagnostic.range.end, Assoc::After);
|
diagnostic.line = self.text.char_to_line(diagnostic.range.start);
|
||||||
// diagnostic.line = self.text.char_to_line(diagnostic.range.start);
|
}
|
||||||
// }
|
|
||||||
|
|
||||||
// emit lsp notification
|
// emit lsp notification
|
||||||
if let Some(language_server) = self.language_server() {
|
if let Some(language_server) = self.language_server() {
|
||||||
|
@ -692,8 +704,8 @@ impl Document {
|
||||||
success
|
success
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Undo the last modification to the [`Document`].
|
/// Undo the last modification to the [`Document`]. Returns whether the undo was successful.
|
||||||
pub fn undo(&mut self, view_id: ViewId) {
|
pub fn undo(&mut self, view_id: ViewId) -> bool {
|
||||||
let mut history = self.history.take();
|
let mut history = self.history.take();
|
||||||
let success = if let Some(transaction) = history.undo() {
|
let success = if let Some(transaction) = history.undo() {
|
||||||
self.apply_impl(transaction, view_id)
|
self.apply_impl(transaction, view_id)
|
||||||
|
@ -706,10 +718,11 @@ impl Document {
|
||||||
// reset changeset to fix len
|
// reset changeset to fix len
|
||||||
self.changes = ChangeSet::new(self.text());
|
self.changes = ChangeSet::new(self.text());
|
||||||
}
|
}
|
||||||
|
success
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Redo the last modification to the [`Document`].
|
/// Redo the last modification to the [`Document`]. Returns whether the redo was sucessful.
|
||||||
pub fn redo(&mut self, view_id: ViewId) {
|
pub fn redo(&mut self, view_id: ViewId) -> bool {
|
||||||
let mut history = self.history.take();
|
let mut history = self.history.take();
|
||||||
let success = if let Some(transaction) = history.redo() {
|
let success = if let Some(transaction) = history.redo() {
|
||||||
self.apply_impl(transaction, view_id)
|
self.apply_impl(transaction, view_id)
|
||||||
|
@ -722,6 +735,17 @@ impl Document {
|
||||||
// reset changeset to fix len
|
// reset changeset to fix len
|
||||||
self.changes = ChangeSet::new(self.text());
|
self.changes = ChangeSet::new(self.text());
|
||||||
}
|
}
|
||||||
|
success
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn savepoint(&mut self) {
|
||||||
|
self.savepoint = Some(Transaction::new(self.text()));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn restore(&mut self, view_id: ViewId) {
|
||||||
|
if let Some(revert) = self.savepoint.take() {
|
||||||
|
self.apply(&revert, view_id);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Undo modifications to the [`Document`] according to `uk`.
|
/// Undo modifications to the [`Document`] according to `uk`.
|
||||||
|
@ -894,6 +918,9 @@ impl Document {
|
||||||
|
|
||||||
pub fn set_diagnostics(&mut self, diagnostics: Vec<Diagnostic>) {
|
pub fn set_diagnostics(&mut self, diagnostics: Vec<Diagnostic>) {
|
||||||
self.diagnostics = diagnostics;
|
self.diagnostics = diagnostics;
|
||||||
|
// sort by range
|
||||||
|
self.diagnostics
|
||||||
|
.sort_unstable_by_key(|diagnostic| diagnostic.range);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,7 @@ use crate::{
|
||||||
clipboard::{get_clipboard_provider, ClipboardProvider},
|
clipboard::{get_clipboard_provider, ClipboardProvider},
|
||||||
graphics::{CursorKind, Rect},
|
graphics::{CursorKind, Rect},
|
||||||
theme::{self, Theme},
|
theme::{self, Theme},
|
||||||
tree::Tree,
|
tree::{self, Tree},
|
||||||
Document, DocumentId, View, ViewId,
|
Document, DocumentId, View, ViewId,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -12,6 +12,7 @@ use tokio_stream::wrappers::UnboundedReceiverStream;
|
||||||
|
|
||||||
use std::{
|
use std::{
|
||||||
collections::HashMap,
|
collections::HashMap,
|
||||||
|
collections::BTreeMap,
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
pin::Pin,
|
pin::Pin,
|
||||||
sync::Arc,
|
sync::Arc,
|
||||||
|
@ -19,8 +20,6 @@ use std::{
|
||||||
|
|
||||||
use tokio::time::{sleep, Duration, Instant, Sleep};
|
use tokio::time::{sleep, Duration, Instant, Sleep};
|
||||||
|
|
||||||
use slotmap::SlotMap;
|
|
||||||
|
|
||||||
use anyhow::Error;
|
use anyhow::Error;
|
||||||
|
|
||||||
pub use helix_core::diagnostic::Severity;
|
pub use helix_core::diagnostic::Severity;
|
||||||
|
@ -63,6 +62,9 @@ pub struct Config {
|
||||||
/// Time in milliseconds since last keypress before idle timers trigger. Used for autocompletion, set to 0 for instant. Defaults to 400ms.
|
/// Time in milliseconds since last keypress before idle timers trigger. Used for autocompletion, set to 0 for instant. Defaults to 400ms.
|
||||||
#[serde(skip_serializing, deserialize_with = "deserialize_duration_millis")]
|
#[serde(skip_serializing, deserialize_with = "deserialize_duration_millis")]
|
||||||
pub idle_timeout: Duration,
|
pub idle_timeout: Duration,
|
||||||
|
pub completion_trigger_len: u8,
|
||||||
|
/// Whether to display infoboxes. Defaults to true.
|
||||||
|
pub auto_info: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
|
#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
|
||||||
|
@ -92,14 +94,29 @@ impl Default for Config {
|
||||||
auto_pairs: true,
|
auto_pairs: true,
|
||||||
auto_completion: true,
|
auto_completion: true,
|
||||||
idle_timeout: Duration::from_millis(400),
|
idle_timeout: Duration::from_millis(400),
|
||||||
|
completion_trigger_len: 2,
|
||||||
|
auto_info: true,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct Motion(pub Box<dyn Fn(&mut Editor)>);
|
||||||
|
impl Motion {
|
||||||
|
pub fn run(&self, e: &mut Editor) {
|
||||||
|
(self.0)(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl std::fmt::Debug for Motion {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
f.write_str("motion")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Editor {
|
pub struct Editor {
|
||||||
pub tree: Tree,
|
pub tree: Tree,
|
||||||
pub documents: SlotMap<DocumentId, Document>,
|
pub next_document_id: usize,
|
||||||
|
pub documents: BTreeMap<DocumentId, Document>,
|
||||||
pub count: Option<std::num::NonZeroUsize>,
|
pub count: Option<std::num::NonZeroUsize>,
|
||||||
pub selected_register: Option<char>,
|
pub selected_register: Option<char>,
|
||||||
pub registers: Registers,
|
pub registers: Registers,
|
||||||
|
@ -124,6 +141,7 @@ pub struct Editor {
|
||||||
pub config: Config,
|
pub config: Config,
|
||||||
|
|
||||||
pub idle_timer: Pin<Box<Sleep>>,
|
pub idle_timer: Pin<Box<Sleep>>,
|
||||||
|
pub last_motion: Option<Motion>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone)]
|
#[derive(Debug, Copy, Clone)]
|
||||||
|
@ -148,7 +166,8 @@ impl Editor {
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
tree: Tree::new(area),
|
tree: Tree::new(area),
|
||||||
documents: SlotMap::with_key(),
|
next_document_id: 0,
|
||||||
|
documents: BTreeMap::new(),
|
||||||
count: None,
|
count: None,
|
||||||
selected_register: None,
|
selected_register: None,
|
||||||
theme: themes.default(),
|
theme: themes.default(),
|
||||||
|
@ -166,6 +185,7 @@ impl Editor {
|
||||||
clipboard_provider: get_clipboard_provider(),
|
clipboard_provider: get_clipboard_provider(),
|
||||||
status_msg: None,
|
status_msg: None,
|
||||||
idle_timer: Box::pin(sleep(config.idle_timeout)),
|
idle_timer: Box::pin(sleep(config.idle_timeout)),
|
||||||
|
last_motion: None,
|
||||||
config,
|
config,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -221,7 +241,7 @@ impl Editor {
|
||||||
|
|
||||||
fn _refresh(&mut self) {
|
fn _refresh(&mut self) {
|
||||||
for (view, _) in self.tree.views_mut() {
|
for (view, _) in self.tree.views_mut() {
|
||||||
let doc = &self.documents[view.doc];
|
let doc = &self.documents[&view.doc];
|
||||||
view.ensure_cursor_in_view(doc, self.config.scrolloff)
|
view.ensure_cursor_in_view(doc, self.config.scrolloff)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -230,22 +250,38 @@ impl Editor {
|
||||||
use crate::tree::Layout;
|
use crate::tree::Layout;
|
||||||
use helix_core::Selection;
|
use helix_core::Selection;
|
||||||
|
|
||||||
if !self.documents.contains_key(id) {
|
if !self.documents.contains_key(&id) {
|
||||||
log::error!("cannot switch to document that does not exist (anymore)");
|
log::error!("cannot switch to document that does not exist (anymore)");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
match action {
|
match action {
|
||||||
Action::Replace => {
|
Action::Replace => {
|
||||||
let view = view!(self);
|
let (view, doc) = current_ref!(self);
|
||||||
let jump = (
|
// If the current view is an empty scratch buffer and is not displayed in any other views, delete it.
|
||||||
view.doc,
|
// Boolean value is determined before the call to `view_mut` because the operation requires a borrow
|
||||||
self.documents[view.doc].selection(view.id).clone(),
|
// of `self.tree`, which is mutably borrowed when `view_mut` is called.
|
||||||
);
|
let remove_empty_scratch = !doc.is_modified()
|
||||||
|
// If the buffer has no path and is not modified, it is an empty scratch buffer.
|
||||||
|
&& doc.path().is_none()
|
||||||
|
// If the buffer we are changing to is not this buffer
|
||||||
|
&& id != doc.id
|
||||||
|
// Ensure the buffer is not displayed in any other splits.
|
||||||
|
&& !self
|
||||||
|
.tree
|
||||||
|
.traverse()
|
||||||
|
.any(|(_, v)| v.doc == doc.id && v.id != view.id);
|
||||||
let view = view_mut!(self);
|
let view = view_mut!(self);
|
||||||
view.jumps.push(jump);
|
if remove_empty_scratch {
|
||||||
view.last_accessed_doc = Some(view.doc);
|
// Copy `doc.id` into a variable before calling `self.documents.remove`, which requires a mutable
|
||||||
|
// borrow, invalidating direct access to `doc.id`.
|
||||||
|
let id = doc.id;
|
||||||
|
self.documents.remove(&id);
|
||||||
|
} else {
|
||||||
|
let jump = (view.doc, doc.selection(view.id).clone());
|
||||||
|
view.jumps.push(jump);
|
||||||
|
view.last_accessed_doc = Some(view.doc);
|
||||||
|
}
|
||||||
view.doc = id;
|
view.doc = id;
|
||||||
view.offset = Position::default();
|
view.offset = Position::default();
|
||||||
|
|
||||||
|
@ -272,14 +308,14 @@ impl Editor {
|
||||||
let view = View::new(id);
|
let view = View::new(id);
|
||||||
let view_id = self.tree.split(view, Layout::Horizontal);
|
let view_id = self.tree.split(view, Layout::Horizontal);
|
||||||
// initialize selection for view
|
// initialize selection for view
|
||||||
let doc = &mut self.documents[id];
|
let doc = self.documents.get_mut(&id).unwrap();
|
||||||
doc.selections.insert(view_id, Selection::point(0));
|
doc.selections.insert(view_id, Selection::point(0));
|
||||||
}
|
}
|
||||||
Action::VerticalSplit => {
|
Action::VerticalSplit => {
|
||||||
let view = View::new(id);
|
let view = View::new(id);
|
||||||
let view_id = self.tree.split(view, Layout::Vertical);
|
let view_id = self.tree.split(view, Layout::Vertical);
|
||||||
// initialize selection for view
|
// initialize selection for view
|
||||||
let doc = &mut self.documents[id];
|
let doc = self.documents.get_mut(&id).unwrap();
|
||||||
doc.selections.insert(view_id, Selection::point(0));
|
doc.selections.insert(view_id, Selection::point(0));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -288,9 +324,11 @@ impl Editor {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn new_file(&mut self, action: Action) -> DocumentId {
|
pub fn new_file(&mut self, action: Action) -> DocumentId {
|
||||||
let doc = Document::default();
|
let id = DocumentId(self.next_document_id);
|
||||||
let id = self.documents.insert(doc);
|
self.next_document_id += 1;
|
||||||
self.documents[id].id = id;
|
let mut doc = Document::default();
|
||||||
|
doc.id = id;
|
||||||
|
self.documents.insert(id, doc);
|
||||||
self.switch(id, action);
|
self.switch(id, action);
|
||||||
id
|
id
|
||||||
}
|
}
|
||||||
|
@ -313,7 +351,11 @@ impl Editor {
|
||||||
self.language_servers
|
self.language_servers
|
||||||
.get(language)
|
.get(language)
|
||||||
.map_err(|e| {
|
.map_err(|e| {
|
||||||
log::error!("Failed to get LSP, {}, for `{}`", e, language.scope())
|
log::error!(
|
||||||
|
"Failed to initialize the LSP for `{}` {{ {} }}",
|
||||||
|
language.scope(),
|
||||||
|
e
|
||||||
|
)
|
||||||
})
|
})
|
||||||
.ok()
|
.ok()
|
||||||
});
|
});
|
||||||
|
@ -336,8 +378,10 @@ impl Editor {
|
||||||
doc.set_language_server(Some(language_server));
|
doc.set_language_server(Some(language_server));
|
||||||
}
|
}
|
||||||
|
|
||||||
let id = self.documents.insert(doc);
|
let id = DocumentId(self.next_document_id);
|
||||||
self.documents[id].id = id;
|
self.next_document_id += 1;
|
||||||
|
doc.id = id;
|
||||||
|
self.documents.insert(id, doc);
|
||||||
id
|
id
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -348,16 +392,20 @@ impl Editor {
|
||||||
pub fn close(&mut self, id: ViewId, close_buffer: bool) {
|
pub fn close(&mut self, id: ViewId, close_buffer: bool) {
|
||||||
let view = self.tree.get(self.tree.focus);
|
let view = self.tree.get(self.tree.focus);
|
||||||
// remove selection
|
// remove selection
|
||||||
self.documents[view.doc].selections.remove(&id);
|
self.documents
|
||||||
|
.get_mut(&view.doc)
|
||||||
|
.unwrap()
|
||||||
|
.selections
|
||||||
|
.remove(&id);
|
||||||
|
|
||||||
if close_buffer {
|
if close_buffer {
|
||||||
// get around borrowck issues
|
// get around borrowck issues
|
||||||
let doc = &self.documents[view.doc];
|
let doc = &self.documents[&view.doc];
|
||||||
|
|
||||||
if let Some(language_server) = doc.language_server() {
|
if let Some(language_server) = doc.language_server() {
|
||||||
tokio::spawn(language_server.text_document_did_close(doc.identifier()));
|
tokio::spawn(language_server.text_document_did_close(doc.identifier()));
|
||||||
}
|
}
|
||||||
self.documents.remove(view.doc);
|
self.documents.remove(&view.doc);
|
||||||
}
|
}
|
||||||
|
|
||||||
self.tree.remove(id);
|
self.tree.remove(id);
|
||||||
|
@ -374,24 +422,40 @@ impl Editor {
|
||||||
self.tree.focus_next();
|
self.tree.focus_next();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn focus_right(&mut self) {
|
||||||
|
self.tree.focus_direction(tree::Direction::Right);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn focus_left(&mut self) {
|
||||||
|
self.tree.focus_direction(tree::Direction::Left);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn focus_up(&mut self) {
|
||||||
|
self.tree.focus_direction(tree::Direction::Up);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn focus_down(&mut self) {
|
||||||
|
self.tree.focus_direction(tree::Direction::Down);
|
||||||
|
}
|
||||||
|
|
||||||
pub fn should_close(&self) -> bool {
|
pub fn should_close(&self) -> bool {
|
||||||
self.tree.is_empty()
|
self.tree.is_empty()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn ensure_cursor_in_view(&mut self, id: ViewId) {
|
pub fn ensure_cursor_in_view(&mut self, id: ViewId) {
|
||||||
let view = self.tree.get_mut(id);
|
let view = self.tree.get_mut(id);
|
||||||
let doc = &self.documents[view.doc];
|
let doc = &self.documents[&view.doc];
|
||||||
view.ensure_cursor_in_view(doc, self.config.scrolloff)
|
view.ensure_cursor_in_view(doc, self.config.scrolloff)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn document(&self, id: DocumentId) -> Option<&Document> {
|
pub fn document(&self, id: DocumentId) -> Option<&Document> {
|
||||||
self.documents.get(id)
|
self.documents.get(&id)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn document_mut(&mut self, id: DocumentId) -> Option<&mut Document> {
|
pub fn document_mut(&mut self, id: DocumentId) -> Option<&mut Document> {
|
||||||
self.documents.get_mut(id)
|
self.documents.get_mut(&id)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
|
@ -416,7 +480,7 @@ impl Editor {
|
||||||
|
|
||||||
pub fn cursor(&self) -> (Option<Position>, CursorKind) {
|
pub fn cursor(&self) -> (Option<Position>, CursorKind) {
|
||||||
let view = view!(self);
|
let view = view!(self);
|
||||||
let doc = &self.documents[view.doc];
|
let doc = &self.documents[&view.doc];
|
||||||
let cursor = doc
|
let cursor = doc
|
||||||
.selection(view.id)
|
.selection(view.id)
|
||||||
.primary()
|
.primary()
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use crate::input::KeyEvent;
|
use crate::input::KeyEvent;
|
||||||
use helix_core::unicode::width::UnicodeWidthStr;
|
use helix_core::unicode::width::UnicodeWidthStr;
|
||||||
use std::fmt::Write;
|
use std::{collections::BTreeSet, fmt::Write};
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
/// Info box used in editor. Rendering logic will be in other crate.
|
/// Info box used in editor. Rendering logic will be in other crate.
|
||||||
|
@ -16,7 +16,7 @@ pub struct Info {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Info {
|
impl Info {
|
||||||
pub fn new(title: &str, body: Vec<(&str, Vec<KeyEvent>)>) -> Info {
|
pub fn new(title: &str, body: Vec<(&str, BTreeSet<KeyEvent>)>) -> Info {
|
||||||
let body = body
|
let body = body
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|(desc, events)| {
|
.map(|(desc, events)| {
|
||||||
|
|
|
@ -8,7 +8,7 @@ use crate::keyboard::{KeyCode, KeyModifiers};
|
||||||
|
|
||||||
/// Represents a key event.
|
/// Represents a key event.
|
||||||
// We use a newtype here because we want to customize Deserialize and Display.
|
// We use a newtype here because we want to customize Deserialize and Display.
|
||||||
#[derive(Debug, PartialEq, Eq, PartialOrd, Clone, Copy, Hash)]
|
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Hash)]
|
||||||
pub struct KeyEvent {
|
pub struct KeyEvent {
|
||||||
pub code: KeyCode,
|
pub code: KeyCode,
|
||||||
pub modifiers: KeyModifiers,
|
pub modifiers: KeyModifiers,
|
||||||
|
|
|
@ -54,7 +54,7 @@ impl From<crossterm::event::KeyModifiers> for KeyModifiers {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Represents a key.
|
/// Represents a key.
|
||||||
#[derive(Debug, PartialOrd, PartialEq, Eq, Clone, Copy, Hash)]
|
#[derive(Debug, PartialOrd, Ord, PartialEq, Eq, Clone, Copy, Hash)]
|
||||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||||
pub enum KeyCode {
|
pub enum KeyCode {
|
||||||
/// Backspace key.
|
/// Backspace key.
|
||||||
|
|
|
@ -12,8 +12,10 @@ pub mod theme;
|
||||||
pub mod tree;
|
pub mod tree;
|
||||||
pub mod view;
|
pub mod view;
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Debug)]
|
||||||
|
pub struct DocumentId(usize);
|
||||||
|
|
||||||
slotmap::new_key_type! {
|
slotmap::new_key_type! {
|
||||||
pub struct DocumentId;
|
|
||||||
pub struct ViewId;
|
pub struct ViewId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -13,7 +13,8 @@
|
||||||
macro_rules! current {
|
macro_rules! current {
|
||||||
( $( $editor:ident ).+ ) => {{
|
( $( $editor:ident ).+ ) => {{
|
||||||
let view = $crate::view_mut!( $( $editor ).+ );
|
let view = $crate::view_mut!( $( $editor ).+ );
|
||||||
let doc = &mut $( $editor ).+ .documents[view.doc];
|
let id = view.doc;
|
||||||
|
let doc = $( $editor ).+ .documents.get_mut(&id).unwrap();
|
||||||
(view, doc)
|
(view, doc)
|
||||||
}};
|
}};
|
||||||
}
|
}
|
||||||
|
@ -56,7 +57,7 @@ macro_rules! doc {
|
||||||
macro_rules! current_ref {
|
macro_rules! current_ref {
|
||||||
( $( $editor:ident ).+ ) => {{
|
( $( $editor:ident ).+ ) => {{
|
||||||
let view = $( $editor ).+ .tree.get($( $editor ).+ .tree.focus);
|
let view = $( $editor ).+ .tree.get($( $editor ).+ .tree.focus);
|
||||||
let doc = &$( $editor ).+ .documents[view.doc];
|
let doc = &$( $editor ).+ .documents[&view.doc];
|
||||||
(view, doc)
|
(view, doc)
|
||||||
}};
|
}};
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
use std::{
|
use std::{
|
||||||
collections::HashMap,
|
collections::HashMap,
|
||||||
convert::TryFrom,
|
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -47,13 +47,21 @@ impl Node {
|
||||||
|
|
||||||
// TODO: screen coord to container + container coordinate helpers
|
// TODO: screen coord to container + container coordinate helpers
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
pub enum Layout {
|
pub enum Layout {
|
||||||
Horizontal,
|
Horizontal,
|
||||||
Vertical,
|
Vertical,
|
||||||
// could explore stacked/tabbed
|
// could explore stacked/tabbed
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
pub enum Direction {
|
||||||
|
Up,
|
||||||
|
Down,
|
||||||
|
Left,
|
||||||
|
Right,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Container {
|
pub struct Container {
|
||||||
layout: Layout,
|
layout: Layout,
|
||||||
|
@ -150,7 +158,6 @@ impl Tree {
|
||||||
} => container,
|
} => container,
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
};
|
};
|
||||||
|
|
||||||
if container.layout == layout {
|
if container.layout == layout {
|
||||||
// insert node after the current item if there is children already
|
// insert node after the current item if there is children already
|
||||||
let pos = if container.children.is_empty() {
|
let pos = if container.children.is_empty() {
|
||||||
|
@ -393,6 +400,112 @@ impl Tree {
|
||||||
Traverse::new(self)
|
Traverse::new(self)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Finds the split in the given direction if it exists
|
||||||
|
pub fn find_split_in_direction(&self, id: ViewId, direction: Direction) -> Option<ViewId> {
|
||||||
|
let parent = self.nodes[id].parent;
|
||||||
|
// Base case, we found the root of the tree
|
||||||
|
if parent == id {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
// Parent must always be a container
|
||||||
|
let parent_container = match &self.nodes[parent].content {
|
||||||
|
Content::Container(container) => container,
|
||||||
|
Content::View(_) => unreachable!(),
|
||||||
|
};
|
||||||
|
|
||||||
|
match (direction, parent_container.layout) {
|
||||||
|
(Direction::Up, Layout::Vertical)
|
||||||
|
| (Direction::Left, Layout::Horizontal)
|
||||||
|
| (Direction::Right, Layout::Horizontal)
|
||||||
|
| (Direction::Down, Layout::Vertical) => {
|
||||||
|
// The desired direction of movement is not possible within
|
||||||
|
// the parent container so the search must continue closer to
|
||||||
|
// the root of the split tree.
|
||||||
|
self.find_split_in_direction(parent, direction)
|
||||||
|
}
|
||||||
|
(Direction::Up, Layout::Horizontal)
|
||||||
|
| (Direction::Down, Layout::Horizontal)
|
||||||
|
| (Direction::Left, Layout::Vertical)
|
||||||
|
| (Direction::Right, Layout::Vertical) => {
|
||||||
|
// It's possible to move in the desired direction within
|
||||||
|
// the parent container so an attempt is made to find the
|
||||||
|
// correct child.
|
||||||
|
match self.find_child(id, &parent_container.children, direction) {
|
||||||
|
// Child is found, search is ended
|
||||||
|
Some(id) => Some(id),
|
||||||
|
// A child is not found. This could be because of either two scenarios
|
||||||
|
// 1. Its not possible to move in the desired direction, and search should end
|
||||||
|
// 2. A layout like the following with focus at X and desired direction Right
|
||||||
|
// | _ | x | |
|
||||||
|
// | _ _ _ | |
|
||||||
|
// | _ _ _ | |
|
||||||
|
// The container containing X ends at X so no rightward movement is possible
|
||||||
|
// however there still exists another view/container to the right that hasn't
|
||||||
|
// been explored. Thus another search is done here in the parent container
|
||||||
|
// before concluding it's not possible to move in the desired direction.
|
||||||
|
None => self.find_split_in_direction(parent, direction),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn find_child(&self, id: ViewId, children: &[ViewId], direction: Direction) -> Option<ViewId> {
|
||||||
|
let mut child_id = match direction {
|
||||||
|
// index wise in the child list the Up and Left represents a -1
|
||||||
|
// thus reversed iterator.
|
||||||
|
Direction::Up | Direction::Left => children
|
||||||
|
.iter()
|
||||||
|
.rev()
|
||||||
|
.skip_while(|i| **i != id)
|
||||||
|
.copied()
|
||||||
|
.nth(1)?,
|
||||||
|
// Down and Right => +1 index wise in the child list
|
||||||
|
Direction::Down | Direction::Right => {
|
||||||
|
children.iter().skip_while(|i| **i != id).copied().nth(1)?
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let (current_x, current_y) = match &self.nodes[self.focus].content {
|
||||||
|
Content::View(current_view) => (current_view.area.left(), current_view.area.top()),
|
||||||
|
Content::Container(_) => unreachable!(),
|
||||||
|
};
|
||||||
|
|
||||||
|
// If the child is a container the search finds the closest container child
|
||||||
|
// visually based on screen location.
|
||||||
|
while let Content::Container(container) = &self.nodes[child_id].content {
|
||||||
|
match (direction, container.layout) {
|
||||||
|
(_, Layout::Vertical) => {
|
||||||
|
// find closest split based on x because y is irrelevant
|
||||||
|
// in a vertical container (and already correct based on previous search)
|
||||||
|
child_id = *container.children.iter().min_by_key(|id| {
|
||||||
|
let x = match &self.nodes[**id].content {
|
||||||
|
Content::View(view) => view.inner_area().left(),
|
||||||
|
Content::Container(container) => container.area.left(),
|
||||||
|
};
|
||||||
|
(current_x as i16 - x as i16).abs()
|
||||||
|
})?;
|
||||||
|
}
|
||||||
|
(_, Layout::Horizontal) => {
|
||||||
|
// find closest split based on y because x is irrelevant
|
||||||
|
// in a horizontal container (and already correct based on previous search)
|
||||||
|
child_id = *container.children.iter().min_by_key(|id| {
|
||||||
|
let y = match &self.nodes[**id].content {
|
||||||
|
Content::View(view) => view.inner_area().top(),
|
||||||
|
Content::Container(container) => container.area.top(),
|
||||||
|
};
|
||||||
|
(current_y as i16 - y as i16).abs()
|
||||||
|
})?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Some(child_id)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn focus_direction(&mut self, direction: Direction) {
|
||||||
|
if let Some(id) = self.find_split_in_direction(self.focus, direction) {
|
||||||
|
self.focus = id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn focus_next(&mut self) {
|
pub fn focus_next(&mut self) {
|
||||||
// This function is very dumb, but that's because we don't store any parent links.
|
// This function is very dumb, but that's because we don't store any parent links.
|
||||||
// (we'd be able to go parent.next_sibling() recursively until we find something)
|
// (we'd be able to go parent.next_sibling() recursively until we find something)
|
||||||
|
@ -420,13 +533,12 @@ impl Tree {
|
||||||
// if found = container -> found = first child
|
// if found = container -> found = first child
|
||||||
// }
|
// }
|
||||||
|
|
||||||
let iter = self.traverse();
|
let mut views = self
|
||||||
|
.traverse()
|
||||||
let mut iter = iter.skip_while(|&(key, _view)| key != self.focus);
|
.skip_while(|&(id, _view)| id != self.focus)
|
||||||
iter.next(); // take the focused value
|
.skip(1); // Skip focused value
|
||||||
|
if let Some((id, _)) = views.next() {
|
||||||
if let Some((key, _)) = iter.next() {
|
self.focus = id;
|
||||||
self.focus = key;
|
|
||||||
} else {
|
} else {
|
||||||
// extremely crude, take the first item again
|
// extremely crude, take the first item again
|
||||||
let (key, _) = self.traverse().next().unwrap();
|
let (key, _) = self.traverse().next().unwrap();
|
||||||
|
@ -472,3 +584,64 @@ impl<'a> Iterator for Traverse<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use super::*;
|
||||||
|
use crate::DocumentId;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn find_split_in_direction() {
|
||||||
|
let mut tree = Tree::new(Rect {
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
width: 180,
|
||||||
|
height: 80,
|
||||||
|
});
|
||||||
|
let mut view = View::new(DocumentId::default());
|
||||||
|
view.area = Rect::new(0, 0, 180, 80);
|
||||||
|
tree.insert(view);
|
||||||
|
|
||||||
|
let l0 = tree.focus;
|
||||||
|
let view = View::new(DocumentId::default());
|
||||||
|
tree.split(view, Layout::Vertical);
|
||||||
|
let r0 = tree.focus;
|
||||||
|
|
||||||
|
tree.focus = l0;
|
||||||
|
let view = View::new(DocumentId::default());
|
||||||
|
tree.split(view, Layout::Horizontal);
|
||||||
|
let l1 = tree.focus;
|
||||||
|
|
||||||
|
tree.focus = l0;
|
||||||
|
let view = View::new(DocumentId::default());
|
||||||
|
tree.split(view, Layout::Vertical);
|
||||||
|
let l2 = tree.focus;
|
||||||
|
|
||||||
|
// Tree in test
|
||||||
|
// | L0 | L2 | |
|
||||||
|
// | L1 | R0 |
|
||||||
|
tree.focus = l2;
|
||||||
|
assert_eq!(Some(l0), tree.find_split_in_direction(l2, Direction::Left));
|
||||||
|
assert_eq!(Some(l1), tree.find_split_in_direction(l2, Direction::Down));
|
||||||
|
assert_eq!(Some(r0), tree.find_split_in_direction(l2, Direction::Right));
|
||||||
|
assert_eq!(None, tree.find_split_in_direction(l2, Direction::Up));
|
||||||
|
|
||||||
|
tree.focus = l1;
|
||||||
|
assert_eq!(None, tree.find_split_in_direction(l1, Direction::Left));
|
||||||
|
assert_eq!(None, tree.find_split_in_direction(l1, Direction::Down));
|
||||||
|
assert_eq!(Some(r0), tree.find_split_in_direction(l1, Direction::Right));
|
||||||
|
assert_eq!(Some(l0), tree.find_split_in_direction(l1, Direction::Up));
|
||||||
|
|
||||||
|
tree.focus = l0;
|
||||||
|
assert_eq!(None, tree.find_split_in_direction(l0, Direction::Left));
|
||||||
|
assert_eq!(Some(l1), tree.find_split_in_direction(l0, Direction::Down));
|
||||||
|
assert_eq!(Some(l2), tree.find_split_in_direction(l0, Direction::Right));
|
||||||
|
assert_eq!(None, tree.find_split_in_direction(l0, Direction::Up));
|
||||||
|
|
||||||
|
tree.focus = r0;
|
||||||
|
assert_eq!(Some(l2), tree.find_split_in_direction(r0, Direction::Left));
|
||||||
|
assert_eq!(None, tree.find_split_in_direction(r0, Direction::Down));
|
||||||
|
assert_eq!(None, tree.find_split_in_direction(r0, Direction::Right));
|
||||||
|
assert_eq!(None, tree.find_split_in_direction(r0, Direction::Up));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -2,10 +2,9 @@ use std::borrow::Cow;
|
||||||
|
|
||||||
use crate::{graphics::Rect, Document, DocumentId, ViewId};
|
use crate::{graphics::Rect, Document, DocumentId, ViewId};
|
||||||
use helix_core::{
|
use helix_core::{
|
||||||
coords_at_pos,
|
|
||||||
graphemes::{grapheme_width, RopeGraphemes},
|
graphemes::{grapheme_width, RopeGraphemes},
|
||||||
line_ending::line_end_char_index,
|
line_ending::line_end_char_index,
|
||||||
Position, RopeSlice, Selection,
|
visual_coords_at_pos, Position, RopeSlice, Selection,
|
||||||
};
|
};
|
||||||
|
|
||||||
type Jump = (DocumentId, Selection);
|
type Jump = (DocumentId, Selection);
|
||||||
|
@ -91,7 +90,10 @@ impl View {
|
||||||
.selection(self.id)
|
.selection(self.id)
|
||||||
.primary()
|
.primary()
|
||||||
.cursor(doc.text().slice(..));
|
.cursor(doc.text().slice(..));
|
||||||
let Position { col, row: line } = coords_at_pos(doc.text().slice(..), cursor);
|
|
||||||
|
let Position { col, row: line } =
|
||||||
|
visual_coords_at_pos(doc.text().slice(..), cursor, doc.tab_width());
|
||||||
|
|
||||||
let inner_area = self.inner_area();
|
let inner_area = self.inner_area();
|
||||||
let last_line = (self.offset.row + inner_area.height as usize).saturating_sub(1);
|
let last_line = (self.offset.row + inner_area.height as usize).saturating_sub(1);
|
||||||
|
|
||||||
|
|
|
@ -312,7 +312,7 @@ injection-regex = "php"
|
||||||
file-types = ["php"]
|
file-types = ["php"]
|
||||||
roots = []
|
roots = []
|
||||||
|
|
||||||
indent = { tab-width = 2, unit = " " }
|
indent = { tab-width = 4, unit = " " }
|
||||||
|
|
||||||
[[language]]
|
[[language]]
|
||||||
name = "latex"
|
name = "latex"
|
||||||
|
@ -458,3 +458,12 @@ file-types = ["scm"]
|
||||||
roots = []
|
roots = []
|
||||||
comment-token = ";"
|
comment-token = ";"
|
||||||
indent = { tab-width = 2, unit = " " }
|
indent = { tab-width = 2, unit = " " }
|
||||||
|
|
||||||
|
[[language]]
|
||||||
|
name = "cmake"
|
||||||
|
scope = "source.cmake"
|
||||||
|
file-types = ["cmake", "CMakeLists.txt"]
|
||||||
|
roots = []
|
||||||
|
comment-token = "#"
|
||||||
|
indent = { tab-width = 2, unit = " " }
|
||||||
|
language-server = { command = "cmake-language-server" }
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
|
|
||||||
(command_name) @function
|
(command_name) @function
|
||||||
|
|
||||||
(variable_name) @property
|
(variable_name) @variable.other.member
|
||||||
|
|
||||||
[
|
[
|
||||||
"case"
|
"case"
|
||||||
|
@ -31,7 +31,7 @@
|
||||||
|
|
||||||
(function_definition name: (word) @function)
|
(function_definition name: (word) @function)
|
||||||
|
|
||||||
(file_descriptor) @number
|
(file_descriptor) @constant.numeric.integer
|
||||||
|
|
||||||
[
|
[
|
||||||
(command_substitution)
|
(command_substitution)
|
||||||
|
|
|
@ -20,16 +20,16 @@
|
||||||
] @type.builtin
|
] @type.builtin
|
||||||
|
|
||||||
;; Enum
|
;; Enum
|
||||||
(enum_member_declaration (identifier) @variable.property)
|
(enum_member_declaration (identifier) @variable.other.member)
|
||||||
|
|
||||||
;; Literals
|
;; Literals
|
||||||
[
|
[
|
||||||
(real_literal)
|
(real_literal)
|
||||||
(integer_literal)
|
(integer_literal)
|
||||||
] @number
|
] @constant.numeric.integer
|
||||||
|
|
||||||
|
(character_literal) @constant.character
|
||||||
[
|
[
|
||||||
(character_literal)
|
|
||||||
(string_literal)
|
(string_literal)
|
||||||
(verbatim_string_literal)
|
(verbatim_string_literal)
|
||||||
(interpolated_string_text)
|
(interpolated_string_text)
|
||||||
|
@ -40,8 +40,8 @@
|
||||||
"$@\""
|
"$@\""
|
||||||
] @string
|
] @string
|
||||||
|
|
||||||
|
(boolean_literal) @constant.builtin.boolean
|
||||||
[
|
[
|
||||||
(boolean_literal)
|
|
||||||
(null_literal)
|
(null_literal)
|
||||||
(void_keyword)
|
(void_keyword)
|
||||||
] @constant.builtin
|
] @constant.builtin
|
||||||
|
@ -98,7 +98,7 @@
|
||||||
;; Keywords
|
;; Keywords
|
||||||
(modifier) @keyword
|
(modifier) @keyword
|
||||||
(this_expression) @keyword
|
(this_expression) @keyword
|
||||||
(escape_sequence) @keyword
|
(escape_sequence) @constant.character.escape
|
||||||
|
|
||||||
[
|
[
|
||||||
"as"
|
"as"
|
||||||
|
|
|
@ -60,8 +60,8 @@
|
||||||
(system_lib_string) @string
|
(system_lib_string) @string
|
||||||
|
|
||||||
(null) @constant
|
(null) @constant
|
||||||
(number_literal) @number
|
(number_literal) @constant.numeric.integer
|
||||||
(char_literal) @string
|
(char_literal) @constant.character
|
||||||
|
|
||||||
(call_expression
|
(call_expression
|
||||||
function: (identifier) @function)
|
function: (identifier) @function)
|
||||||
|
@ -73,7 +73,7 @@
|
||||||
(preproc_function_def
|
(preproc_function_def
|
||||||
name: (identifier) @function.special)
|
name: (identifier) @function.special)
|
||||||
|
|
||||||
(field_identifier) @property
|
(field_identifier) @variable.other.member
|
||||||
(statement_identifier) @label
|
(statement_identifier) @label
|
||||||
(type_identifier) @type
|
(type_identifier) @type
|
||||||
(primitive_type) @type
|
(primitive_type) @type
|
||||||
|
|
97
runtime/queries/cmake/highlights.scm
Normal file
97
runtime/queries/cmake/highlights.scm
Normal file
|
@ -0,0 +1,97 @@
|
||||||
|
[
|
||||||
|
(quoted_argument)
|
||||||
|
(bracket_argument)
|
||||||
|
] @string
|
||||||
|
|
||||||
|
(variable) @variable
|
||||||
|
|
||||||
|
[
|
||||||
|
(bracket_comment)
|
||||||
|
(line_comment)
|
||||||
|
] @comment
|
||||||
|
|
||||||
|
(normal_command (identifier) @function)
|
||||||
|
|
||||||
|
["ENV" "CACHE"] @string.special.symbol
|
||||||
|
["$" "{" "}" "<" ">"] @punctuation
|
||||||
|
["(" ")"] @punctuation.bracket
|
||||||
|
|
||||||
|
[
|
||||||
|
(function)
|
||||||
|
(endfunction)
|
||||||
|
(macro)
|
||||||
|
(endmacro)
|
||||||
|
] @keyword.function
|
||||||
|
|
||||||
|
[
|
||||||
|
(if)
|
||||||
|
(elseif)
|
||||||
|
(else)
|
||||||
|
(endif)
|
||||||
|
] @keyword.control.conditional
|
||||||
|
|
||||||
|
[
|
||||||
|
(foreach)
|
||||||
|
(endforeach)
|
||||||
|
(while)
|
||||||
|
(endwhile)
|
||||||
|
] @keyword.control.repeat
|
||||||
|
|
||||||
|
(function_command
|
||||||
|
(function)
|
||||||
|
. (argument) @function
|
||||||
|
(argument)* @variable.parameter
|
||||||
|
)
|
||||||
|
|
||||||
|
(macro_command
|
||||||
|
(macro)
|
||||||
|
. (argument) @function.macro
|
||||||
|
(argument)* @variable.parameter
|
||||||
|
)
|
||||||
|
|
||||||
|
(normal_command
|
||||||
|
(identifier) @function.builtin
|
||||||
|
. (argument) @variable
|
||||||
|
(#match? @function.builtin "^(?i)(set)$"))
|
||||||
|
|
||||||
|
(normal_command
|
||||||
|
(identifier) @function.builtin
|
||||||
|
. (argument)
|
||||||
|
(argument) @constant
|
||||||
|
(#match? @constant "^(?:PARENT_SCOPE|CACHE)$")
|
||||||
|
(#match? @function.builtin "^(?i)(unset)$"))
|
||||||
|
|
||||||
|
(normal_command
|
||||||
|
(identifier) @function.builtin
|
||||||
|
. (argument)
|
||||||
|
. (argument)
|
||||||
|
(argument) @constant
|
||||||
|
(#match? @constant "^(?:PARENT_SCOPE|CACHE|FORCE)$")
|
||||||
|
(#match? @function.builtin "^(?i)(set)$")
|
||||||
|
)
|
||||||
|
|
||||||
|
((argument) @constant.builtin.boolean
|
||||||
|
(#match? @constant.builtin.boolean "^(?i)(?:1|on|yes|true|y|0|off|no|false|n|ignore|notfound|.*-notfound)$")
|
||||||
|
)
|
||||||
|
|
||||||
|
(if_command
|
||||||
|
(if)
|
||||||
|
(argument) @operator
|
||||||
|
(#match? @operator "^(?:NOT|AND|OR|COMMAND|POLICY|TARGET|TEST|DEFINED|IN_LIST|EXISTS|IS_NEWER_THAN|IS_DIRECTORY|IS_SYMLINK|IS_ABSOLUTE|MATCHES|LESS|GREATER|EQUAL|LESS_EQUAL|GREATER_EQUAL|STRLESS|STRGREATER|STREQUAL|STRLESS_EQUAL|STRGREATER_EQUAL|VERSION_LESS|VERSION_GREATER|VERSION_EQUAL|VERSION_LESS_EQUAL|VERSION_GREATER_EQUAL)$")
|
||||||
|
)
|
||||||
|
|
||||||
|
(normal_command
|
||||||
|
(identifier) @function.builtin
|
||||||
|
. (argument)
|
||||||
|
(argument) @constant
|
||||||
|
(#match? @constant "^(?:ALL|COMMAND|DEPENDS|BYPRODUCTS|WORKING_DIRECTORY|COMMENT|JOB_POOL|VERBATIM|USES_TERMINAL|COMMAND_EXPAND_LISTS|SOURCES)$")
|
||||||
|
(#match? @function.builtin "^(?i)(add_custom_target)$")
|
||||||
|
)
|
||||||
|
|
||||||
|
(normal_command
|
||||||
|
(identifier) @function.builtin
|
||||||
|
(argument) @constant
|
||||||
|
(#match? @constant "^(?:OUTPUT|COMMAND|MAIN_DEPENDENCY|DEPENDS|BYPRODUCTS|IMPLICIT_DEPENDS|WORKING_DIRECTORY|COMMENT|DEPFILE|JOB_POOL|VERBATIM|APPEND|USES_TERMINAL|COMMAND_EXPAND_LISTS)$")
|
||||||
|
(#match? @function.builtin "^(?i)(add_custom_command)$")
|
||||||
|
)
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
; Functions
|
; Functions
|
||||||
|
|
||||||
(call_expression
|
(call_expression
|
||||||
function: (scoped_identifier
|
function: (qualified_identifier
|
||||||
name: (identifier) @function))
|
name: (identifier) @function))
|
||||||
|
|
||||||
(template_function
|
(template_function
|
||||||
|
@ -13,15 +13,14 @@
|
||||||
name: (field_identifier) @function)
|
name: (field_identifier) @function)
|
||||||
|
|
||||||
(template_function
|
(template_function
|
||||||
name: (scoped_identifier
|
name: (identifier) @function)
|
||||||
|
|
||||||
|
(function_declarator
|
||||||
|
declarator: (qualified_identifier
|
||||||
name: (identifier) @function))
|
name: (identifier) @function))
|
||||||
|
|
||||||
(function_declarator
|
(function_declarator
|
||||||
declarator: (scoped_identifier
|
declarator: (qualified_identifier
|
||||||
name: (identifier) @function))
|
|
||||||
|
|
||||||
(function_declarator
|
|
||||||
declarator: (scoped_identifier
|
|
||||||
name: (identifier) @function))
|
name: (identifier) @function))
|
||||||
|
|
||||||
(function_declarator
|
(function_declarator
|
||||||
|
|
|
@ -26,11 +26,11 @@
|
||||||
(pseudo_element_selector (tag_name) @attribute)
|
(pseudo_element_selector (tag_name) @attribute)
|
||||||
(pseudo_class_selector (class_name) @attribute)
|
(pseudo_class_selector (class_name) @attribute)
|
||||||
|
|
||||||
(class_name) @property
|
(class_name) @variable.other.member
|
||||||
(id_name) @property
|
(id_name) @variable.other.member
|
||||||
(namespace_name) @property
|
(namespace_name) @variable.other.member
|
||||||
(property_name) @property
|
(property_name) @variable.other.member
|
||||||
(feature_name) @property
|
(feature_name) @variable.other.member
|
||||||
|
|
||||||
(attribute_name) @attribute
|
(attribute_name) @attribute
|
||||||
|
|
||||||
|
@ -55,8 +55,8 @@
|
||||||
(string_value) @string
|
(string_value) @string
|
||||||
(color_value) @string.special
|
(color_value) @string.special
|
||||||
|
|
||||||
(integer_value) @number
|
(integer_value) @constant.numeric.integer
|
||||||
(float_value) @number
|
(float_value) @constant.numeric.float
|
||||||
(unit) @type
|
(unit) @type
|
||||||
|
|
||||||
"#" @punctuation.delimiter
|
"#" @punctuation.delimiter
|
||||||
|
|
|
@ -1,125 +1,210 @@
|
||||||
["when" "and" "or" "not in" "not" "in" "fn" "do" "end" "catch" "rescue" "after" "else"] @keyword
|
; The following code originates mostly from
|
||||||
|
; https://github.com/elixir-lang/tree-sitter-elixir, with minor edits to
|
||||||
|
; align the captures with helix. The following should be considered
|
||||||
|
; Copyright 2021 The Elixir Team
|
||||||
|
;
|
||||||
|
; Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
; you may not use this file except in compliance with the License.
|
||||||
|
; You may obtain a copy of the License at
|
||||||
|
;
|
||||||
|
; https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
;
|
||||||
|
; Unless required by applicable law or agreed to in writing, software
|
||||||
|
; distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
; WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
; See the License for the specific language governing permissions and
|
||||||
|
; limitations under the License.
|
||||||
|
|
||||||
[(true) (false) (nil)] @constant.builtin
|
; Reserved keywords
|
||||||
|
|
||||||
(keyword
|
["when" "and" "or" "not" "in" "not in" "fn" "do" "end" "catch" "rescue" "after" "else"] @keyword
|
||||||
[(keyword_literal)
|
|
||||||
":"] @tag)
|
|
||||||
|
|
||||||
(keyword
|
; Operators
|
||||||
(keyword_string
|
|
||||||
[(string_start)
|
|
||||||
(string_content)
|
|
||||||
(string_end)] @tag))
|
|
||||||
|
|
||||||
[(atom_literal)
|
; * doc string
|
||||||
(atom_start)
|
(unary_operator
|
||||||
(atom_content)
|
operator: "@" @comment.block.documentation
|
||||||
(atom_end)] @tag
|
operand: (call
|
||||||
|
target: (identifier) @comment.block.documentation.__attribute__
|
||||||
|
(arguments
|
||||||
|
[
|
||||||
|
(string) @comment.block.documentation
|
||||||
|
(charlist) @comment.block.documentation
|
||||||
|
(sigil
|
||||||
|
quoted_start: _ @comment.block.documentation
|
||||||
|
quoted_end: _ @comment.block.documentation) @comment.block.documentation
|
||||||
|
(boolean) @comment.block.documentation
|
||||||
|
]))
|
||||||
|
(#match? @comment.block.documentation.__attribute__ "^(moduledoc|typedoc|doc)$"))
|
||||||
|
|
||||||
[(comment)
|
; * module attribute
|
||||||
(unused_identifier)] @comment
|
(unary_operator
|
||||||
|
operator: "@" @variable.other.member
|
||||||
|
operand: [
|
||||||
|
(identifier) @variable.other.member
|
||||||
|
(call
|
||||||
|
target: (identifier) @variable.other.member)
|
||||||
|
(boolean) @variable.other.member
|
||||||
|
(nil) @variable.other.member
|
||||||
|
])
|
||||||
|
|
||||||
(escape_sequence) @escape
|
; * capture operator
|
||||||
|
(unary_operator
|
||||||
|
operator: "&"
|
||||||
|
operand: [
|
||||||
|
(integer) @operator
|
||||||
|
(binary_operator
|
||||||
|
left: [
|
||||||
|
(call target: (dot left: (_) right: (identifier) @function))
|
||||||
|
(identifier) @function
|
||||||
|
] operator: "/" right: (integer) @operator)
|
||||||
|
])
|
||||||
|
|
||||||
(call function: (function_identifier) @keyword
|
(operator_identifier) @operator
|
||||||
(#match? @keyword "^(defmodule|defexception|defp|def|with|case|cond|raise|import|require|use|defmacrop|defmacro|defguardp|defguard|defdelegate|defstruct|alias|defimpl|defprotocol|defoverridable|receive|if|for|try|throw|unless|reraise|super|quote|unquote|unquote_splicing)$"))
|
|
||||||
|
|
||||||
(call function: (function_identifier) @keyword
|
(unary_operator
|
||||||
[(call
|
operator: _ @operator)
|
||||||
function: (function_identifier) @function
|
|
||||||
(arguments
|
(binary_operator
|
||||||
[(identifier) @variable.parameter
|
operator: _ @operator)
|
||||||
(_ (identifier) @variable.parameter)
|
|
||||||
(_ (_ (identifier) @variable.parameter))
|
(dot
|
||||||
(_ (_ (_ (identifier) @variable.parameter)))
|
operator: _ @operator)
|
||||||
(_ (_ (_ (_ (identifier) @variable.parameter))))
|
|
||||||
(_ (_ (_ (_ (_ (identifier) @variable.parameter)))))]))
|
(stab_clause
|
||||||
(binary_op
|
operator: _ @operator)
|
||||||
left:
|
|
||||||
(call
|
; Literals
|
||||||
function: (function_identifier) @function
|
|
||||||
(arguments
|
(nil) @constant.builtin
|
||||||
[(identifier) @variable.parameter
|
|
||||||
(_ (identifier) @variable.parameter)
|
(boolean) @constant.builtin.boolean
|
||||||
(_ (_ (identifier) @variable.parameter))
|
(integer) @constant.numeric.integer
|
||||||
(_ (_ (_ (identifier) @variable.parameter)))
|
(float) @constant.numeric.float
|
||||||
(_ (_ (_ (_ (identifier) @variable.parameter))))
|
|
||||||
(_ (_ (_ (_ (_ (identifier) @variable.parameter)))))]))
|
(alias) @type
|
||||||
|
|
||||||
|
(call
|
||||||
|
target: (dot
|
||||||
|
left: (atom) @type))
|
||||||
|
|
||||||
|
(char) @constant.character
|
||||||
|
|
||||||
|
; Quoted content
|
||||||
|
|
||||||
|
(interpolation "#{" @punctuation.special "}" @punctuation.special) @embedded
|
||||||
|
|
||||||
|
(escape_sequence) @constant.character.escape
|
||||||
|
|
||||||
|
[
|
||||||
|
(atom)
|
||||||
|
(quoted_atom)
|
||||||
|
(keyword)
|
||||||
|
(quoted_keyword)
|
||||||
|
] @string.special.symbol
|
||||||
|
|
||||||
|
[
|
||||||
|
(string)
|
||||||
|
(charlist)
|
||||||
|
] @string
|
||||||
|
|
||||||
|
; Note that we explicitly target sigil quoted start/end, so they are not overridden by delimiters
|
||||||
|
|
||||||
|
(sigil
|
||||||
|
(sigil_name) @__name__
|
||||||
|
quoted_start: _ @string
|
||||||
|
quoted_end: _ @string
|
||||||
|
(#match? @__name__ "^[sS]$")) @string
|
||||||
|
|
||||||
|
(sigil
|
||||||
|
(sigil_name) @__name__
|
||||||
|
quoted_start: _ @string.regexp
|
||||||
|
quoted_end: _ @string.regexp
|
||||||
|
(#match? @__name__ "^[rR]$")) @string.regexp
|
||||||
|
|
||||||
|
(sigil
|
||||||
|
(sigil_name) @__name__
|
||||||
|
quoted_start: _ @string.special
|
||||||
|
quoted_end: _ @string.special) @string.special
|
||||||
|
|
||||||
|
; Calls
|
||||||
|
|
||||||
|
; * definition keyword
|
||||||
|
(call
|
||||||
|
target: (identifier) @keyword
|
||||||
|
(#match? @keyword "^(def|defdelegate|defexception|defguard|defguardp|defimpl|defmacro|defmacrop|defmodule|defn|defnp|defoverridable|defp|defprotocol|defstruct)$"))
|
||||||
|
|
||||||
|
; * kernel or special forms keyword
|
||||||
|
(call
|
||||||
|
target: (identifier) @keyword
|
||||||
|
(#match? @keyword "^(alias|case|cond|else|for|if|import|quote|raise|receive|require|reraise|super|throw|try|unless|unquote|unquote_splicing|use|with)$"))
|
||||||
|
|
||||||
|
; * function call
|
||||||
|
(call
|
||||||
|
target: [
|
||||||
|
; local
|
||||||
|
(identifier) @function
|
||||||
|
; remote
|
||||||
|
(dot
|
||||||
|
right: (identifier) @function)
|
||||||
|
])
|
||||||
|
|
||||||
|
; * just identifier in function definition
|
||||||
|
(call
|
||||||
|
target: (identifier) @keyword
|
||||||
|
(arguments
|
||||||
|
[
|
||||||
|
(identifier) @function
|
||||||
|
(binary_operator
|
||||||
|
left: (identifier) @function
|
||||||
operator: "when")
|
operator: "when")
|
||||||
(binary_op
|
])
|
||||||
left: (identifier) @variable.parameter
|
(#match? @keyword "^(def|defdelegate|defguard|defguardp|defmacro|defmacrop|defn|defnp|defp)$"))
|
||||||
operator: _ @function
|
|
||||||
right: (identifier) @variable.parameter)]
|
|
||||||
(#match? @keyword "^(defp|def|defmacrop|defmacro|defguardp|defguard|defdelegate)$"))
|
|
||||||
|
|
||||||
(call (function_identifier) @keyword
|
; * pipe into identifier (definition)
|
||||||
[(call
|
(call
|
||||||
function: (function_identifier) @function)
|
target: (identifier) @keyword
|
||||||
(identifier) @function
|
(arguments
|
||||||
(binary_op
|
(binary_operator
|
||||||
left:
|
operator: "|>"
|
||||||
[(call
|
right: (identifier) @variable))
|
||||||
function: (function_identifier) @function)
|
(#match? @keyword "^(def|defdelegate|defguard|defguardp|defmacro|defmacrop|defn|defnp|defp)$"))
|
||||||
(identifier) @function]
|
|
||||||
operator: "when")]
|
|
||||||
(#match? @keyword "^(defp|def|defmacrop|defmacro|defguardp|defguard|defdelegate)$"))
|
|
||||||
|
|
||||||
(anonymous_function
|
; * pipe into identifier (function call)
|
||||||
(stab_expression
|
(binary_operator
|
||||||
left: (bare_arguments
|
operator: "|>"
|
||||||
[(identifier) @variable.parameter
|
right: (identifier) @function)
|
||||||
(_ (identifier) @variable.parameter)
|
|
||||||
(_ (_ (identifier) @variable.parameter))
|
|
||||||
(_ (_ (_ (identifier) @variable.parameter)))
|
|
||||||
(_ (_ (_ (_ (identifier) @variable.parameter))))
|
|
||||||
(_ (_ (_ (_ (_ (identifier) @variable.parameter)))))])))
|
|
||||||
|
|
||||||
(unary_op
|
; Identifiers
|
||||||
operator: "@"
|
|
||||||
(call (identifier) @attribute
|
|
||||||
(heredoc
|
|
||||||
[(heredoc_start)
|
|
||||||
(heredoc_content)
|
|
||||||
(heredoc_end)] @doc))
|
|
||||||
(#match? @attribute "^(doc|moduledoc)$"))
|
|
||||||
|
|
||||||
(module) @type
|
; * special
|
||||||
|
(
|
||||||
|
(identifier) @constant.builtin
|
||||||
|
(#match? @constant.builtin "^(__MODULE__|__DIR__|__ENV__|__CALLER__|__STACKTRACE__)$")
|
||||||
|
)
|
||||||
|
|
||||||
(unary_op
|
; * unused
|
||||||
operator: "@" @attribute
|
(
|
||||||
[(call
|
(identifier) @comment
|
||||||
function: (function_identifier) @attribute)
|
(#match? @comment "^_")
|
||||||
(identifier) @attribute])
|
)
|
||||||
|
|
||||||
(unary_op
|
; * regular
|
||||||
operator: _ @operator)
|
(identifier) @variable
|
||||||
|
|
||||||
(binary_op
|
; Comment
|
||||||
operator: _ @operator)
|
|
||||||
|
|
||||||
(heredoc
|
(comment) @comment
|
||||||
[(heredoc_start)
|
|
||||||
(heredoc_content)
|
|
||||||
(heredoc_end)] @string)
|
|
||||||
|
|
||||||
(string
|
; Punctuation
|
||||||
[(string_start)
|
|
||||||
(string_content)
|
|
||||||
(string_end)] @string)
|
|
||||||
|
|
||||||
(sigil_start) @string.special
|
[
|
||||||
(sigil_content) @string
|
"%"
|
||||||
(sigil_end) @string.special
|
] @punctuation
|
||||||
|
|
||||||
(interpolation
|
|
||||||
"#{" @punctuation.special
|
|
||||||
"}" @punctuation.special)
|
|
||||||
|
|
||||||
[
|
[
|
||||||
","
|
","
|
||||||
"->"
|
";"
|
||||||
"."
|
|
||||||
] @punctuation.delimiter
|
] @punctuation.delimiter
|
||||||
|
|
||||||
[
|
[
|
||||||
|
@ -133,6 +218,4 @@
|
||||||
">>"
|
">>"
|
||||||
] @punctuation.bracket
|
] @punctuation.bracket
|
||||||
|
|
||||||
(special_identifier) @function.special
|
|
||||||
|
|
||||||
(ERROR) @warning
|
(ERROR) @warning
|
||||||
|
|
|
@ -25,7 +25,7 @@
|
||||||
(variadic_parameter_declaration (identifier) @variable.parameter)
|
(variadic_parameter_declaration (identifier) @variable.parameter)
|
||||||
|
|
||||||
(type_identifier) @type
|
(type_identifier) @type
|
||||||
(field_identifier) @property
|
(field_identifier) @variable.other.member
|
||||||
(identifier) @variable
|
(identifier) @variable
|
||||||
(package_identifier) @variable
|
(package_identifier) @variable
|
||||||
|
|
||||||
|
@ -130,13 +130,13 @@
|
||||||
(rune_literal)
|
(rune_literal)
|
||||||
] @string
|
] @string
|
||||||
|
|
||||||
(escape_sequence) @escape
|
(escape_sequence) @constant.character.escape
|
||||||
|
|
||||||
[
|
[
|
||||||
(int_literal)
|
(int_literal)
|
||||||
(float_literal)
|
(float_literal)
|
||||||
(imaginary_literal)
|
(imaginary_literal)
|
||||||
] @number
|
] @constant.numeric.integer
|
||||||
|
|
||||||
[
|
[
|
||||||
(true)
|
(true)
|
||||||
|
|
21
runtime/queries/go/textobjects.scm
Normal file
21
runtime/queries/go/textobjects.scm
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
(function_declaration
|
||||||
|
body: (block)? @function.inside) @function.around
|
||||||
|
|
||||||
|
(func_literal
|
||||||
|
(_)? @function.inside) @function.around
|
||||||
|
|
||||||
|
(method_declaration
|
||||||
|
body: (block)? @function.inside) @function.around
|
||||||
|
|
||||||
|
;; struct and interface declaration as class textobject?
|
||||||
|
(type_declaration
|
||||||
|
(type_spec (type_identifier) (struct_type (field_declaration_list (_)?) @class.inside))) @class.around
|
||||||
|
|
||||||
|
(type_declaration
|
||||||
|
(type_spec (type_identifier) (interface_type (method_spec_list (_)?) @class.inside))) @class.around
|
||||||
|
|
||||||
|
(parameter_list
|
||||||
|
(_) @parameter.inside)
|
||||||
|
|
||||||
|
(argument_list
|
||||||
|
(_) @parameter.inside)
|
|
@ -13,9 +13,9 @@
|
||||||
(constraint class: (class_name (type)) @class)
|
(constraint class: (class_name (type)) @class)
|
||||||
(class (class_head class: (class_name (type)) @class))
|
(class (class_head class: (class_name (type)) @class))
|
||||||
(instance (instance_head class: (class_name (type)) @class))
|
(instance (instance_head class: (class_name (type)) @class))
|
||||||
(integer) @number
|
(integer) @constant.numeric.integer
|
||||||
(exp_literal (float)) @number
|
(exp_literal (float)) @constant.numeric.float
|
||||||
(char) @literal
|
(char) @constant.character
|
||||||
(con_unit) @literal
|
(con_unit) @literal
|
||||||
(con_list) @literal
|
(con_list) @literal
|
||||||
(tycon_arrow) @operator
|
(tycon_arrow) @operator
|
||||||
|
|
|
@ -59,14 +59,15 @@
|
||||||
(hex_integer_literal)
|
(hex_integer_literal)
|
||||||
(decimal_integer_literal)
|
(decimal_integer_literal)
|
||||||
(octal_integer_literal)
|
(octal_integer_literal)
|
||||||
(decimal_floating_point_literal)
|
] @constant.numeric.integer
|
||||||
(hex_floating_point_literal)
|
|
||||||
] @number
|
|
||||||
|
|
||||||
[
|
[
|
||||||
(character_literal)
|
(decimal_floating_point_literal)
|
||||||
(string_literal)
|
(hex_floating_point_literal)
|
||||||
] @string
|
] @constant.numeric.float
|
||||||
|
|
||||||
|
(character_literal) @constant.character
|
||||||
|
(string_literal) @string
|
||||||
|
|
||||||
[
|
[
|
||||||
(true)
|
(true)
|
||||||
|
|
|
@ -65,7 +65,7 @@
|
||||||
; Properties
|
; Properties
|
||||||
;-----------
|
;-----------
|
||||||
|
|
||||||
(property_identifier) @property
|
(property_identifier) @variable.other.member
|
||||||
|
|
||||||
; Literals
|
; Literals
|
||||||
;---------
|
;---------
|
||||||
|
@ -88,7 +88,7 @@
|
||||||
] @string
|
] @string
|
||||||
|
|
||||||
(regex) @string.regexp
|
(regex) @string.regexp
|
||||||
(number) @number
|
(number) @constant.numeric.integer
|
||||||
|
|
||||||
; Tokens
|
; Tokens
|
||||||
;-------
|
;-------
|
||||||
|
|
|
@ -1,9 +1,20 @@
|
||||||
|
[
|
||||||
|
(true)
|
||||||
|
(false)
|
||||||
|
] @constant.builtin.boolean
|
||||||
|
(null) @constant.builtin
|
||||||
|
(number) @constant.numeric
|
||||||
(pair
|
(pair
|
||||||
key: (_) @keyword)
|
key: (_) @keyword)
|
||||||
|
|
||||||
(string) @string
|
(string) @string
|
||||||
|
(escape_sequence) @constant.character.escape
|
||||||
|
(ERROR) @error
|
||||||
|
|
||||||
(object
|
"," @punctuation.delimiter
|
||||||
"{" @escape
|
[
|
||||||
(_)
|
"["
|
||||||
"}" @escape)
|
"]"
|
||||||
|
"{"
|
||||||
|
"}"
|
||||||
|
] @punctuation.bracket
|
||||||
|
|
|
@ -15,7 +15,7 @@
|
||||||
|
|
||||||
(field_expression
|
(field_expression
|
||||||
(identifier)
|
(identifier)
|
||||||
(identifier) @field .)
|
(identifier) @variable.other.member .)
|
||||||
|
|
||||||
(function_definition
|
(function_definition
|
||||||
name: (identifier) @function)
|
name: (identifier) @function)
|
||||||
|
@ -80,14 +80,14 @@
|
||||||
(struct_definition
|
(struct_definition
|
||||||
name: (identifier) @type)
|
name: (identifier) @type)
|
||||||
|
|
||||||
(number) @number
|
(number) @constant.numeric.integer
|
||||||
(range_expression
|
(range_expression
|
||||||
(identifier) @number
|
(identifier) @constant.numeric.integer
|
||||||
(eq? @number "end"))
|
(eq? @constant.numeric.integer "end"))
|
||||||
(range_expression
|
(range_expression
|
||||||
(_
|
(_
|
||||||
(identifier) @number
|
(identifier) @constant.numeric.integer
|
||||||
(eq? @number "end")))
|
(eq? @constant.numeric.integer "end")))
|
||||||
(coefficient_expression
|
(coefficient_expression
|
||||||
(number)
|
(number)
|
||||||
(identifier) @constant.builtin)
|
(identifier) @constant.builtin)
|
||||||
|
|
|
@ -7,9 +7,9 @@
|
||||||
(date)
|
(date)
|
||||||
(interval)
|
(interval)
|
||||||
(quantity)
|
(quantity)
|
||||||
] @number
|
] @constant.numeric.integer
|
||||||
|
|
||||||
((account) @field)
|
((account) @variable.other.member)
|
||||||
((commodity) @text.literal)
|
((commodity) @text.literal)
|
||||||
|
|
||||||
"include" @include
|
"include" @include
|
||||||
|
|
|
@ -150,14 +150,14 @@
|
||||||
(table ["{" "}"] @constructor)
|
(table ["{" "}"] @constructor)
|
||||||
(comment) @comment
|
(comment) @comment
|
||||||
(string) @string
|
(string) @string
|
||||||
(number) @number
|
(number) @constant.numeric.integer
|
||||||
(label_statement) @label
|
(label_statement) @label
|
||||||
; A bit of a tricky one, this will only match field names
|
; A bit of a tricky one, this will only match field names
|
||||||
(field . (identifier) @property (_))
|
(field . (identifier) @variable.other.member (_))
|
||||||
(shebang) @comment
|
(shebang) @comment
|
||||||
|
|
||||||
;; Property
|
;; Property
|
||||||
(property_identifier) @property
|
(property_identifier) @variable.other.member
|
||||||
|
|
||||||
;; Variable
|
;; Variable
|
||||||
(identifier) @variable
|
(identifier) @variable
|
||||||
|
|
|
@ -33,16 +33,14 @@
|
||||||
|
|
||||||
(uri) @string.special.uri
|
(uri) @string.special.uri
|
||||||
|
|
||||||
[
|
(integer) @constant.numeric.integer
|
||||||
(integer)
|
(float) @constant.numeric.float
|
||||||
(float)
|
|
||||||
] @number
|
|
||||||
|
|
||||||
(interpolation
|
(interpolation
|
||||||
"${" @punctuation.special
|
"${" @punctuation.special
|
||||||
"}" @punctuation.special) @embedded
|
"}" @punctuation.special) @embedded
|
||||||
|
|
||||||
(escape_sequence) @escape
|
(escape_sequence) @constant.character.escape
|
||||||
|
|
||||||
(function
|
(function
|
||||||
universal: (identifier) @variable.parameter
|
universal: (identifier) @variable.parameter
|
||||||
|
@ -66,8 +64,8 @@
|
||||||
(binary
|
(binary
|
||||||
operator: _ @operator)
|
operator: _ @operator)
|
||||||
|
|
||||||
(attr_identifier) @property
|
(attr_identifier) @variable.other.member
|
||||||
(inherit attrs: (attrs_inherited (identifier) @property) )
|
(inherit attrs: (attrs_inherited (identifier) @variable.other.member) )
|
||||||
|
|
||||||
[
|
[
|
||||||
";"
|
";"
|
||||||
|
|
|
@ -51,14 +51,14 @@
|
||||||
; Properties
|
; Properties
|
||||||
;-----------
|
;-----------
|
||||||
|
|
||||||
[(label_name) (field_name) (instance_variable_name)] @property
|
[(label_name) (field_name) (instance_variable_name)] @variable.other.member
|
||||||
|
|
||||||
; Constants
|
; Constants
|
||||||
;----------
|
;----------
|
||||||
|
|
||||||
[(boolean) (unit)] @constant
|
[(boolean) (unit)] @constant
|
||||||
|
|
||||||
[(number) (signed_number)] @number
|
[(number) (signed_number)] @constant.numeric.integer
|
||||||
|
|
||||||
(character) @constant.character
|
(character) @constant.character
|
||||||
|
|
||||||
|
@ -66,7 +66,7 @@
|
||||||
|
|
||||||
(quoted_string "{" @string "}" @string) @string
|
(quoted_string "{" @string "}" @string) @string
|
||||||
|
|
||||||
(escape_sequence) @string.escape
|
(escape_sequence) @constant.character.escape
|
||||||
|
|
||||||
[
|
[
|
||||||
(conversion_specification)
|
(conversion_specification)
|
||||||
|
@ -145,7 +145,7 @@
|
||||||
; Attributes
|
; Attributes
|
||||||
;-----------
|
;-----------
|
||||||
|
|
||||||
(attribute_id) @property
|
(attribute_id) @variable.other.member
|
||||||
|
|
||||||
; Comments
|
; Comments
|
||||||
;---------
|
;---------
|
||||||
|
|
|
@ -30,12 +30,12 @@
|
||||||
; Member
|
; Member
|
||||||
|
|
||||||
(property_element
|
(property_element
|
||||||
(variable_name) @property)
|
(variable_name) @variable.other.member)
|
||||||
|
|
||||||
(member_access_expression
|
(member_access_expression
|
||||||
name: (variable_name (name)) @property)
|
name: (variable_name (name)) @variable.other.member)
|
||||||
(member_access_expression
|
(member_access_expression
|
||||||
name: (name) @property)
|
name: (name) @variable.other.member)
|
||||||
|
|
||||||
; Variables
|
; Variables
|
||||||
|
|
||||||
|
@ -56,10 +56,10 @@
|
||||||
|
|
||||||
(string) @string
|
(string) @string
|
||||||
(heredoc) @string
|
(heredoc) @string
|
||||||
(boolean) @constant.builtin
|
(boolean) @constant.builtin.boolean
|
||||||
(null) @constant.builtin
|
(null) @constant.builtin
|
||||||
(integer) @number
|
(integer) @constant.numeric.integer
|
||||||
(float) @number
|
(float) @constant.numeric.float
|
||||||
(comment) @comment
|
(comment) @comment
|
||||||
|
|
||||||
"$" @operator
|
"$" @operator
|
||||||
|
|
17
runtime/queries/php/indents.toml
Normal file
17
runtime/queries/php/indents.toml
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
indent = [
|
||||||
|
"array_creation_expression",
|
||||||
|
"arguments",
|
||||||
|
"formal_parameters",
|
||||||
|
"compound_statement",
|
||||||
|
"declaration_list",
|
||||||
|
"binary_expression",
|
||||||
|
"return_statement",
|
||||||
|
"expression_statement",
|
||||||
|
"switch_block",
|
||||||
|
"anonymous_function_use_clause",
|
||||||
|
]
|
||||||
|
|
||||||
|
oudent = [
|
||||||
|
"}",
|
||||||
|
")",
|
||||||
|
]
|
|
@ -34,16 +34,14 @@
|
||||||
[
|
[
|
||||||
(fieldName)
|
(fieldName)
|
||||||
(optionName)
|
(optionName)
|
||||||
] @property
|
] @variable.other.member
|
||||||
(enumVariantName) @type.enum.variant
|
(enumVariantName) @type.enum.variant
|
||||||
|
|
||||||
(fullIdent) @namespace
|
(fullIdent) @namespace
|
||||||
|
|
||||||
[
|
(intLit) @constant.numeric.integer
|
||||||
(intLit)
|
(floatLit) @constant.numeric.float
|
||||||
(floatLit)
|
(boolLit) @constant.builtin.boolean
|
||||||
] @number
|
|
||||||
(boolLit) @constant.builtin
|
|
||||||
(strLit) @string
|
(strLit) @string
|
||||||
|
|
||||||
(constant) @constant
|
(constant) @constant
|
||||||
|
|
|
@ -29,7 +29,7 @@
|
||||||
name: (identifier) @function)
|
name: (identifier) @function)
|
||||||
|
|
||||||
(identifier) @variable
|
(identifier) @variable
|
||||||
(attribute attribute: (identifier) @property)
|
(attribute attribute: (identifier) @variable.other.member)
|
||||||
(type (identifier) @type)
|
(type (identifier) @type)
|
||||||
|
|
||||||
; Literals
|
; Literals
|
||||||
|
@ -40,14 +40,11 @@
|
||||||
(false)
|
(false)
|
||||||
] @constant.builtin
|
] @constant.builtin
|
||||||
|
|
||||||
[
|
(integer) @constant.numeric.integer
|
||||||
(integer)
|
(float) @constant.numeric.float
|
||||||
(float)
|
|
||||||
] @number
|
|
||||||
|
|
||||||
(comment) @comment
|
(comment) @comment
|
||||||
(string) @string
|
(string) @string
|
||||||
(escape_sequence) @escape
|
(escape_sequence) @constant.character.escape
|
||||||
|
|
||||||
(interpolation
|
(interpolation
|
||||||
"{" @punctuation.special
|
"{" @punctuation.special
|
||||||
|
|
39
runtime/queries/python/indents.toml
Normal file
39
runtime/queries/python/indents.toml
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
indent = [
|
||||||
|
"list",
|
||||||
|
"tuple",
|
||||||
|
"dictionary",
|
||||||
|
"set",
|
||||||
|
|
||||||
|
"if_statement",
|
||||||
|
"for_statement",
|
||||||
|
"while_statement",
|
||||||
|
"with_statement",
|
||||||
|
"try_statement",
|
||||||
|
"import_from_statement",
|
||||||
|
|
||||||
|
"parenthesized_expression",
|
||||||
|
"generator_expression",
|
||||||
|
"list_comprehension",
|
||||||
|
"set_comprehension",
|
||||||
|
"dictionary_comprehension",
|
||||||
|
|
||||||
|
"tuple_pattern",
|
||||||
|
"list_pattern",
|
||||||
|
"argument_list",
|
||||||
|
"parameters",
|
||||||
|
"binary_operator",
|
||||||
|
|
||||||
|
"function_definition",
|
||||||
|
"class_definition",
|
||||||
|
]
|
||||||
|
|
||||||
|
outdent = [
|
||||||
|
")",
|
||||||
|
"]",
|
||||||
|
"}",
|
||||||
|
"return_statement",
|
||||||
|
"pass_statement",
|
||||||
|
"raise_statement",
|
||||||
|
]
|
||||||
|
|
||||||
|
ignore = ["string"]
|
14
runtime/queries/python/textobjects.scm
Normal file
14
runtime/queries/python/textobjects.scm
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
(function_definition
|
||||||
|
body: (block)? @function.inside) @function.around
|
||||||
|
|
||||||
|
(class_definition
|
||||||
|
body: (block)? @class.inside) @class.around
|
||||||
|
|
||||||
|
(parameters
|
||||||
|
(_) @parameter.inside)
|
||||||
|
|
||||||
|
(lambda_parameters
|
||||||
|
(_) @parameter.inside)
|
||||||
|
|
||||||
|
(argument_list
|
||||||
|
(_) @parameter.inside)
|
|
@ -55,7 +55,7 @@
|
||||||
[
|
[
|
||||||
(class_variable)
|
(class_variable)
|
||||||
(instance_variable)
|
(instance_variable)
|
||||||
] @property
|
] @variable.other.member
|
||||||
|
|
||||||
((identifier) @constant.builtin
|
((identifier) @constant.builtin
|
||||||
(#match? @constant.builtin "^__(FILE|LINE|ENCODING)__$"))
|
(#match? @constant.builtin "^__(FILE|LINE|ENCODING)__$"))
|
||||||
|
@ -101,12 +101,12 @@
|
||||||
] @string.special.symbol
|
] @string.special.symbol
|
||||||
|
|
||||||
(regex) @string.regexp
|
(regex) @string.regexp
|
||||||
(escape_sequence) @escape
|
(escape_sequence) @constant.character.escape
|
||||||
|
|
||||||
[
|
[
|
||||||
(integer)
|
(integer)
|
||||||
(float)
|
(float)
|
||||||
] @number
|
] @constant.numeric.integer
|
||||||
|
|
||||||
[
|
[
|
||||||
(nil)
|
(nil)
|
||||||
|
|
|
@ -15,15 +15,13 @@
|
||||||
; Primitives
|
; Primitives
|
||||||
; ---
|
; ---
|
||||||
|
|
||||||
(escape_sequence) @escape
|
(escape_sequence) @constant.character.escape
|
||||||
(primitive_type) @type.builtin
|
(primitive_type) @type.builtin
|
||||||
(boolean_literal) @constant.builtin.boolean
|
(boolean_literal) @constant.builtin.boolean
|
||||||
|
(integer_literal) @constant.numeric.integer
|
||||||
|
(float_literal) @constant.numeric.float
|
||||||
|
(char_literal) @constant.character
|
||||||
[
|
[
|
||||||
(integer_literal)
|
|
||||||
(float_literal)
|
|
||||||
] @number
|
|
||||||
[
|
|
||||||
(char_literal)
|
|
||||||
(string_literal)
|
(string_literal)
|
||||||
(raw_string_literal)
|
(raw_string_literal)
|
||||||
] @string
|
] @string
|
||||||
|
@ -40,10 +38,10 @@
|
||||||
(enum_variant (identifier) @type.enum.variant)
|
(enum_variant (identifier) @type.enum.variant)
|
||||||
|
|
||||||
(field_initializer
|
(field_initializer
|
||||||
(field_identifier) @property)
|
(field_identifier) @variable.other.member)
|
||||||
(shorthand_field_initializer
|
(shorthand_field_initializer
|
||||||
(identifier) @variable.property)
|
(identifier) @variable.other.member)
|
||||||
(shorthand_field_identifier) @variable.property
|
(shorthand_field_identifier) @variable.other.member
|
||||||
|
|
||||||
(lifetime
|
(lifetime
|
||||||
"'" @label
|
"'" @label
|
||||||
|
@ -81,9 +79,24 @@
|
||||||
] @punctuation.bracket)
|
] @punctuation.bracket)
|
||||||
|
|
||||||
; ---
|
; ---
|
||||||
; Parameters
|
; Variables
|
||||||
; ---
|
; ---
|
||||||
|
|
||||||
|
(let_declaration
|
||||||
|
pattern: [
|
||||||
|
((identifier) @variable)
|
||||||
|
((tuple_pattern
|
||||||
|
(identifier) @variable))
|
||||||
|
])
|
||||||
|
|
||||||
|
; It needs to be anonymous to not conflict with `call_expression` further below.
|
||||||
|
(_
|
||||||
|
value: (field_expression
|
||||||
|
value: (identifier)? @variable
|
||||||
|
field: (field_identifier) @variable.other.member))
|
||||||
|
|
||||||
|
(arguments
|
||||||
|
(identifier) @variable.parameter)
|
||||||
(parameter
|
(parameter
|
||||||
pattern: (identifier) @variable.parameter)
|
pattern: (identifier) @variable.parameter)
|
||||||
(closure_parameters
|
(closure_parameters
|
||||||
|
@ -336,4 +349,4 @@
|
||||||
|
|
||||||
(type_identifier) @type
|
(type_identifier) @type
|
||||||
(identifier) @variable
|
(identifier) @variable
|
||||||
(field_identifier) @property
|
(field_identifier) @variable.other.member
|
||||||
|
|
26
runtime/queries/rust/textobjects.scm
Normal file
26
runtime/queries/rust/textobjects.scm
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
(function_item
|
||||||
|
body: (_) @function.inside) @function.around
|
||||||
|
|
||||||
|
(struct_item
|
||||||
|
body: (_) @class.inside) @class.around
|
||||||
|
|
||||||
|
(enum_item
|
||||||
|
body: (_) @class.inside) @class.around
|
||||||
|
|
||||||
|
(union_item
|
||||||
|
body: (_) @class.inside) @class.around
|
||||||
|
|
||||||
|
(trait_item
|
||||||
|
body: (_) @class.inside) @class.around
|
||||||
|
|
||||||
|
(impl_item
|
||||||
|
body: (_) @class.inside) @class.around
|
||||||
|
|
||||||
|
(parameters
|
||||||
|
(_) @parameter.inside)
|
||||||
|
|
||||||
|
(closure_parameters
|
||||||
|
(_) @parameter.inside)
|
||||||
|
|
||||||
|
(arguments
|
||||||
|
(_) @parameter.inside)
|
|
@ -29,7 +29,7 @@
|
||||||
(#match? @_attr "^(href|src)$"))
|
(#match? @_attr "^(href|src)$"))
|
||||||
|
|
||||||
(tag_name) @tag
|
(tag_name) @tag
|
||||||
(attribute_name) @property
|
(attribute_name) @variable.other.member
|
||||||
(erroneous_end_tag_name) @error
|
(erroneous_end_tag_name) @error
|
||||||
(comment) @comment
|
(comment) @comment
|
||||||
|
|
||||||
|
|
|
@ -1,17 +1,17 @@
|
||||||
; Properties
|
; Properties
|
||||||
;-----------
|
;-----------
|
||||||
|
|
||||||
(bare_key) @property
|
(bare_key) @variable.other.member
|
||||||
(quoted_key) @string
|
(quoted_key) @string
|
||||||
|
|
||||||
; Literals
|
; Literals
|
||||||
;---------
|
;---------
|
||||||
|
|
||||||
(boolean) @constant.builtin
|
(boolean) @constant.builtin.boolean
|
||||||
(comment) @comment
|
(comment) @comment
|
||||||
(string) @string
|
(string) @string
|
||||||
(integer) @number
|
(integer) @constant.numeric.integer
|
||||||
(float) @number
|
(float) @constant.numeric.float
|
||||||
(offset_date_time) @string.special
|
(offset_date_time) @string.special
|
||||||
(local_date_time) @string.special
|
(local_date_time) @string.special
|
||||||
(local_date) @string.special
|
(local_date) @string.special
|
||||||
|
|
|
@ -35,12 +35,12 @@
|
||||||
|
|
||||||
(comment) @comment
|
(comment) @comment
|
||||||
|
|
||||||
(field_name) @property
|
(field_name) @variable.other.member
|
||||||
|
|
||||||
(capture) @label
|
(capture) @label
|
||||||
|
|
||||||
(predicate_name) @function
|
(predicate_name) @function
|
||||||
|
|
||||||
(escape_sequence) @escape
|
(escape_sequence) @constant.character.escape
|
||||||
|
|
||||||
(node_name) @variable
|
(node_name) @variable
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
(block_mapping_pair key: (_) @property)
|
(block_mapping_pair key: (_) @variable.other.member)
|
||||||
(flow_mapping (_ key: (_) @property))
|
(flow_mapping (_ key: (_) @variable.other.member))
|
||||||
(boolean_scalar) @constant.builtin.boolean
|
(boolean_scalar) @constant.builtin.boolean
|
||||||
(null_scalar) @constant.builtin
|
(null_scalar) @constant.builtin
|
||||||
(double_quote_scalar) @string
|
(double_quote_scalar) @string
|
||||||
(single_quote_scalar) @string
|
(single_quote_scalar) @string
|
||||||
(escape_sequence) @string.escape
|
(escape_sequence) @constant.character.escape
|
||||||
(integer_scalar) @number
|
(integer_scalar) @constant.numeric.integer
|
||||||
(float_scalar) @number
|
(float_scalar) @constant.numeric.float
|
||||||
(comment) @comment
|
(comment) @comment
|
||||||
(anchor_name) @type
|
(anchor_name) @type
|
||||||
(alias_name) @type
|
(alias_name) @type
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue