mirror of https://github.com/helix-editor/helix
Merge branch 'master' into debug
commit
bf53aff27d
|
@ -118,3 +118,7 @@
|
||||||
path = helix-syntax/languages/tree-sitter-zig
|
path = helix-syntax/languages/tree-sitter-zig
|
||||||
url = https://github.com/maxxnino/tree-sitter-zig
|
url = https://github.com/maxxnino/tree-sitter-zig
|
||||||
shallow = true
|
shallow = true
|
||||||
|
[submodule "helix-syntax/languages/tree-sitter-svelte"]
|
||||||
|
path = helix-syntax/languages/tree-sitter-svelte
|
||||||
|
url = https://github.com/Himujjal/tree-sitter-svelte
|
||||||
|
shallow = true
|
||||||
|
|
|
@ -13,15 +13,15 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "anyhow"
|
name = "anyhow"
|
||||||
version = "1.0.43"
|
version = "1.0.44"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "28ae2b3dec75a406790005a200b1bd89785afc02517a00ca99ecfe093ee9e6cf"
|
checksum = "61604a8f862e1d5c3229fdd78f8b02c68dcf73a4c4b05fd636d12240aaa242c1"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "arc-swap"
|
name = "arc-swap"
|
||||||
version = "1.3.2"
|
version = "1.4.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b5ab7d9e73059c86c36473f459b52adbd99c3554a4fec492caef460806006f00"
|
checksum = "e6df5aef5c5830360ce5218cecb8f018af3438af5686ae945094affc86fdec63"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "autocfg"
|
name = "autocfg"
|
||||||
|
@ -41,9 +41,17 @@ version = "0.2.16"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "90682c8d613ad3373e66de8c6411e0ae2ab2571e879d2efbf73558cc66f21279"
|
checksum = "90682c8d613ad3373e66de8c6411e0ae2ab2571e879d2efbf73558cc66f21279"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"lazy_static",
|
||||||
"memchr",
|
"memchr",
|
||||||
|
"regex-automata",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bytecount"
|
||||||
|
version = "0.6.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "72feb31ffc86498dacdbd0fcebb56138e7177a8cc5cea4516031d15ae85a742e"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bytes"
|
name = "bytes"
|
||||||
version = "1.0.1"
|
version = "1.0.1"
|
||||||
|
@ -58,9 +66,9 @@ checksum = "df8670b8c7b9dae1793364eafadf7239c40d669904660c5960d74cfd80b46a53"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cc"
|
name = "cc"
|
||||||
version = "1.0.69"
|
version = "1.0.70"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e70cc2f62c6ce1868963827bd677764c62d07c3d9a3e1fb1177ee1a9ab199eb2"
|
checksum = "d26a6ce4b6a484fa3edb70f7efa6fc430fd2b87285fe8b84304fd0936faa0dc0"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cfg-if"
|
name = "cfg-if"
|
||||||
|
@ -174,6 +182,15 @@ dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "encoding_rs_io"
|
||||||
|
version = "0.1.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1cc3c5651fb62ab8aa3103998dade57efdd028544bd300516baa31840c252a83"
|
||||||
|
dependencies = [
|
||||||
|
"encoding_rs",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "error-code"
|
name = "error-code"
|
||||||
version = "2.3.0"
|
version = "2.3.0"
|
||||||
|
@ -300,6 +317,45 @@ dependencies = [
|
||||||
"regex",
|
"regex",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "grep-matcher"
|
||||||
|
version = "0.1.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6d27563c33062cd33003b166ade2bb4fd82db1fd6a86db764dfdad132d46c1cc"
|
||||||
|
dependencies = [
|
||||||
|
"memchr",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "grep-regex"
|
||||||
|
version = "0.1.9"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "121553c9768c363839b92fc2d7cdbbad44a3b70e8d6e7b1b72b05c977527bd06"
|
||||||
|
dependencies = [
|
||||||
|
"aho-corasick",
|
||||||
|
"bstr",
|
||||||
|
"grep-matcher",
|
||||||
|
"log",
|
||||||
|
"regex",
|
||||||
|
"regex-syntax",
|
||||||
|
"thread_local",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "grep-searcher"
|
||||||
|
version = "0.1.8"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7fbdbde90ba52adc240d2deef7b6ad1f99f53142d074b771fe9b7bede6c4c23d"
|
||||||
|
dependencies = [
|
||||||
|
"bstr",
|
||||||
|
"bytecount",
|
||||||
|
"encoding_rs",
|
||||||
|
"encoding_rs_io",
|
||||||
|
"grep-matcher",
|
||||||
|
"log",
|
||||||
|
"memmap2",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "helix-core"
|
name = "helix-core"
|
||||||
version = "0.4.1"
|
version = "0.4.1"
|
||||||
|
@ -375,6 +431,8 @@ dependencies = [
|
||||||
"fern",
|
"fern",
|
||||||
"futures-util",
|
"futures-util",
|
||||||
"fuzzy-matcher",
|
"fuzzy-matcher",
|
||||||
|
"grep-regex",
|
||||||
|
"grep-searcher",
|
||||||
"helix-core",
|
"helix-core",
|
||||||
"helix-dap",
|
"helix-dap",
|
||||||
"helix-lsp",
|
"helix-lsp",
|
||||||
|
@ -570,6 +628,15 @@ version = "2.4.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a"
|
checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "memmap2"
|
||||||
|
version = "0.3.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "00b6c2ebff6180198788f5db08d7ce3bc1d0b617176678831a7510825973e357"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "mio"
|
name = "mio"
|
||||||
version = "0.7.13"
|
version = "0.7.13"
|
||||||
|
@ -771,6 +838,12 @@ dependencies = [
|
||||||
"regex-syntax",
|
"regex-syntax",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "regex-automata"
|
||||||
|
version = "0.1.10"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "regex-syntax"
|
name = "regex-syntax"
|
||||||
version = "0.6.25"
|
version = "0.6.25"
|
||||||
|
@ -829,9 +902,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_json"
|
name = "serde_json"
|
||||||
version = "1.0.67"
|
version = "1.0.68"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a7f9e390c27c3c0ce8bc5d725f6e4d30a29d26659494aa4b17535f7522c5c950"
|
checksum = "0f690853975602e1bfe1ccbf50504d67174e3bcf340f23b5ea9992e0587a52d8"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"itoa",
|
"itoa",
|
||||||
"ryu",
|
"ryu",
|
||||||
|
@ -851,9 +924,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "signal-hook"
|
name = "signal-hook"
|
||||||
version = "0.3.9"
|
version = "0.3.10"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "470c5a6397076fae0094aaf06a08e6ba6f37acb77d3b1b91ea92b4d6c8650c39"
|
checksum = "9c98891d737e271a2954825ef19e46bd16bdb98e2746f2eec4f7a4ef7946efd1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
"signal-hook-registry",
|
"signal-hook-registry",
|
||||||
|
@ -893,9 +966,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "similar"
|
name = "similar"
|
||||||
version = "1.3.0"
|
version = "2.0.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1ad1d488a557b235fc46dae55512ffbfc429d2482b08b4d9435ab07384ca8aec"
|
checksum = "6bf11003835e462f07851028082d2a1c89d956180ce4b4b50e07fb085ec4131a"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "slab"
|
name = "slab"
|
||||||
|
@ -948,18 +1021,18 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "thiserror"
|
name = "thiserror"
|
||||||
version = "1.0.28"
|
version = "1.0.29"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "283d5230e63df9608ac7d9691adc1dfb6e701225436eb64d0b9a7f0a5a04f6ec"
|
checksum = "602eca064b2d83369e2b2f34b09c70b605402801927c65c11071ac911d299b88"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"thiserror-impl",
|
"thiserror-impl",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "thiserror-impl"
|
name = "thiserror-impl"
|
||||||
version = "1.0.28"
|
version = "1.0.29"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "fa3884228611f5cd3608e2d409bf7dce832e4eb3135e3f11addbd7e41bd68e71"
|
checksum = "bad553cc2c78e8de258400763a647e80e6d1b31ee237275d756f6836d204494c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
|
@ -1001,9 +1074,9 @@ checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tokio"
|
name = "tokio"
|
||||||
version = "1.10.1"
|
version = "1.11.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "92036be488bb6594459f2e03b60e42df6f937fe6ca5c5ffdcb539c6b84dc40f5"
|
checksum = "b4efe6fc2395938c8155973d7be49fe8d03a843726e285e100a8a383cc0154ce"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"autocfg",
|
"autocfg",
|
||||||
"bytes",
|
"bytes",
|
||||||
|
@ -1052,9 +1125,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tree-sitter"
|
name = "tree-sitter"
|
||||||
version = "0.19.5"
|
version = "0.20.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ad726ec26496bf4c083fff0f43d4eb3a2ad1bba305323af5ff91383c0b6ecac0"
|
checksum = "63ec02a07a782abef91279b72fe8fd2bee4c168a22112cedec5d3b0d49b9e4f9"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cc",
|
"cc",
|
||||||
"regex",
|
"regex",
|
||||||
|
@ -1098,9 +1171,9 @@ checksum = "8895849a949e7845e06bd6dc1aa51731a103c42707010a5b591c0038fb73385b"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unicode-width"
|
name = "unicode-width"
|
||||||
version = "0.1.8"
|
version = "0.1.9"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3"
|
checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unicode-xid"
|
name = "unicode-xid"
|
||||||
|
|
6
TODO.md
6
TODO.md
|
@ -1,12 +1,8 @@
|
||||||
|
|
||||||
- tree sitter:
|
- tree sitter:
|
||||||
- lua
|
|
||||||
- markdown
|
- markdown
|
||||||
- zig
|
|
||||||
- regex
|
- regex
|
||||||
- vue
|
|
||||||
- kotlin
|
- kotlin
|
||||||
- julia
|
|
||||||
- clojure
|
- clojure
|
||||||
- erlang
|
- erlang
|
||||||
|
|
||||||
|
@ -26,8 +22,6 @@ as you type completion!
|
||||||
|
|
||||||
- [ ] lsp: signature help
|
- [ ] lsp: signature help
|
||||||
|
|
||||||
- [ ] search: smart case by default: insensitive unless upper detected
|
|
||||||
|
|
||||||
2
|
2
|
||||||
- [ ] macro recording
|
- [ ] macro recording
|
||||||
- [ ] extend selection (treesitter select parent node) (replaces viw, vi(, va( etc )
|
- [ ] extend selection (treesitter select parent node) (replaces viw, vi(, va( etc )
|
||||||
|
|
|
@ -3,8 +3,9 @@ authors = ["Blaž Hrastnik"]
|
||||||
language = "en"
|
language = "en"
|
||||||
multilingual = false
|
multilingual = false
|
||||||
src = "src"
|
src = "src"
|
||||||
theme = "colibri"
|
|
||||||
edit-url-template = "https://github.com/helix-editor/helix/tree/master/book/{path}?mode=edit"
|
edit-url-template = "https://github.com/helix-editor/helix/tree/master/book/{path}?mode=edit"
|
||||||
|
|
||||||
[output.html]
|
[output.html]
|
||||||
cname = "docs.helix-editor.com"
|
cname = "docs.helix-editor.com"
|
||||||
|
default-theme = "colibri"
|
||||||
|
preferred-dark-theme = "colibri"
|
||||||
|
|
|
@ -5,6 +5,21 @@ To override global configuration parameters, create a `config.toml` file located
|
||||||
* Linux and Mac: `~/.config/helix/config.toml`
|
* Linux and Mac: `~/.config/helix/config.toml`
|
||||||
* Windows: `%AppData%\helix\config.toml`
|
* Windows: `%AppData%\helix\config.toml`
|
||||||
|
|
||||||
|
## Editor
|
||||||
|
|
||||||
|
`[editor]` section of the config.
|
||||||
|
|
||||||
|
| Key | Description | Default |
|
||||||
|
|--|--|---------|
|
||||||
|
| `scrolloff` | Number of lines of padding around the edge of the screen when scrolling. | `3` |
|
||||||
|
| `mouse` | Enable mouse mode. | `true` |
|
||||||
|
| `middle-click-paste` | Middle click paste support. | `true` |
|
||||||
|
| `scroll-lines` | Number of lines to scroll per scroll wheel step. | `3` |
|
||||||
|
| `shell` | Shell to use when running external commands. | Unix: `["sh", "-c"]`<br/>Windows: `["cmd", "/C"]` |
|
||||||
|
| `line-number` | Line number display (`absolute`, `relative`) | `absolute` |
|
||||||
|
| `smart-case` | Enable smart case regex searching (case insensitive unless pattern contains upper case characters) | `true` |
|
||||||
|
| `auto-pairs` | Enable automatic insertion of pairs to parenthese, brackets, etc. | `true` |
|
||||||
|
|
||||||
## LSP
|
## LSP
|
||||||
|
|
||||||
To display all language server messages in the status line add the following to your `config.toml`:
|
To display all language server messages in the status line add the following to your `config.toml`:
|
||||||
|
|
|
@ -7,4 +7,6 @@ going to act on (a word, a paragraph, a line, etc) is selected first and the
|
||||||
action itself (delete, change, yank, etc) comes second. A cursor is simply a
|
action itself (delete, change, yank, etc) comes second. A cursor is simply a
|
||||||
single width selection.
|
single width selection.
|
||||||
|
|
||||||
|
See also Kakoune's [Migrating from Vim](https://github.com/mawww/kakoune/wiki/Migrating-from-Vim).
|
||||||
|
|
||||||
> TODO: Mention texobjects, surround, registers
|
> TODO: Mention texobjects, surround, registers
|
||||||
|
|
|
@ -23,7 +23,9 @@ shell for working on Helix.
|
||||||
|
|
||||||
### Arch Linux
|
### Arch Linux
|
||||||
|
|
||||||
Binary packages are available on AUR:
|
Releases are available in the `community` repository.
|
||||||
|
|
||||||
|
Packages are also available on AUR:
|
||||||
- [helix-bin](https://aur.archlinux.org/packages/helix-bin/) contains the pre-built release
|
- [helix-bin](https://aur.archlinux.org/packages/helix-bin/) contains the pre-built release
|
||||||
- [helix-git](https://aur.archlinux.org/packages/helix-git/) builds the master branch
|
- [helix-git](https://aur.archlinux.org/packages/helix-git/) builds the master branch
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
|
|
||||||
### Movement
|
### Movement
|
||||||
|
|
||||||
> NOTE: `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 |
|
||||||
| ----- | ----------- | ------- |
|
| ----- | ----------- | ------- |
|
||||||
|
@ -28,16 +28,16 @@
|
||||||
| `PageDown` | Move page down | `page_down` |
|
| `PageDown` | Move page down | `page_down` |
|
||||||
| `Ctrl-u` | Move half page up | `half_page_up` |
|
| `Ctrl-u` | Move half page up | `half_page_up` |
|
||||||
| `Ctrl-d` | Move half page down | `half_page_down` |
|
| `Ctrl-d` | Move half page down | `half_page_down` |
|
||||||
| `Ctrl-i` | Jump forward on the jumplist TODO: conflicts tab | `jump_forward` |
|
| `Ctrl-i` | Jump forward on the jumplist | `jump_forward` |
|
||||||
| `Ctrl-o` | Jump backward on the jumplist | `jump_backward` |
|
| `Ctrl-o` | Jump backward on the jumplist | `jump_backward` |
|
||||||
| `v` | Enter [select (extend) mode](#select--extend-mode) | `select_mode` |
|
| `v` | Enter [select (extend) mode](#select--extend-mode) | `select_mode` |
|
||||||
| `g` | Enter [goto mode](#goto-mode) | N/A |
|
| `g` | Enter [goto mode](#goto-mode) | N/A |
|
||||||
| `m` | Enter [match mode](#match-mode) | N/A |
|
| `m` | Enter [match mode](#match-mode) | N/A |
|
||||||
| `:` | Enter command mode | `command_mode` |
|
| `:` | Enter command mode | `command_mode` |
|
||||||
| `z` | Enter [view mode](#view-mode) | N/A |
|
| `z` | Enter [view mode](#view-mode) | N/A |
|
||||||
| `Ctrl-w` | Enter [window mode](#window-mode) (maybe will be remove for spc w w later) | N/A |
|
| `Z` | Enter sticky [view mode](#view-mode) | N/A |
|
||||||
|
| `Ctrl-w` | Enter [window mode](#window-mode) | N/A |
|
||||||
| `Space` | Enter [space mode](#space-mode) | N/A |
|
| `Space` | Enter [space mode](#space-mode) | N/A |
|
||||||
| `K` | Show documentation for the item under the cursor | `hover` |
|
|
||||||
|
|
||||||
### Changes
|
### Changes
|
||||||
|
|
||||||
|
@ -66,6 +66,16 @@
|
||||||
| `d` | Delete selection | `delete_selection` |
|
| `d` | Delete selection | `delete_selection` |
|
||||||
| `c` | Change selection (delete and enter insert mode) | `change_selection` |
|
| `c` | Change selection (delete and enter insert mode) | `change_selection` |
|
||||||
|
|
||||||
|
#### Shell
|
||||||
|
|
||||||
|
| Key | Description | Command |
|
||||||
|
| ------ | ----------- | ------- |
|
||||||
|
| <code>|</code> | Pipe each selection through shell command, replacing with output | `shell_pipe` |
|
||||||
|
| <code>A-|</code> | Pipe each selection into shell command, ignoring output | `shell_pipe_to` |
|
||||||
|
| `!` | Run shell command, inserting output before each selection | `shell_insert_output` |
|
||||||
|
| `A-!` | Run shell command, appending output after each selection | `shell_append_output` |
|
||||||
|
|
||||||
|
|
||||||
### Selection manipulation
|
### Selection manipulation
|
||||||
|
|
||||||
| Key | Description | Command |
|
| Key | Description | Command |
|
||||||
|
@ -75,6 +85,7 @@
|
||||||
| `Alt-s` | Split selection on newlines | `split_selection_on_newline` |
|
| `Alt-s` | Split selection on newlines | `split_selection_on_newline` |
|
||||||
| `;` | 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` |
|
||||||
| `C` | Copy selection onto the next line | `copy_selection_on_next_line` |
|
| `C` | Copy selection onto the next line | `copy_selection_on_next_line` |
|
||||||
| `Alt-C` | Copy selection onto the previous line | `copy_selection_on_prev_line` |
|
| `Alt-C` | Copy selection onto the previous line | `copy_selection_on_prev_line` |
|
||||||
| `(` | Rotate main selection forward | `rotate_selections_backward` |
|
| `(` | Rotate main selection forward | `rotate_selections_backward` |
|
||||||
|
@ -86,22 +97,13 @@
|
||||||
| `X` | Extend selection to line bounds (line-wise selection) | `extend_to_line_bounds` |
|
| `X` | Extend selection to line bounds (line-wise selection) | `extend_to_line_bounds` |
|
||||||
| | Expand selection to parent syntax node TODO: pick a key | `expand_selection` |
|
| | Expand selection to parent syntax node TODO: pick a key | `expand_selection` |
|
||||||
| `J` | Join lines inside selection | `join_selections` |
|
| `J` | Join lines inside selection | `join_selections` |
|
||||||
| `K` | Keep selections matching the regex TODO: overlapped by hover help | `keep_selections` |
|
| `K` | Keep selections matching the regex | `keep_selections` |
|
||||||
| `Space` | Keep only the primary selection TODO: overlapped by space mode | `keep_primary_selection` |
|
| `$` | Pipe each selection into shell command, keep selections where command returned 0 | `shell_keep_pipe` |
|
||||||
| `Ctrl-c` | Comment/uncomment the selections | `toggle_comments` |
|
| `Ctrl-c` | Comment/uncomment the selections | `toggle_comments` |
|
||||||
|
|
||||||
### Insert Mode
|
|
||||||
|
|
||||||
| Key | Description | Command |
|
|
||||||
| ----- | ----------- | ------- |
|
|
||||||
| `Escape` | Switch to normal mode | `normal_mode` |
|
|
||||||
| `Ctrl-x` | Autocomplete | `completion` |
|
|
||||||
| `Ctrl-w` | Delete previous word | `delete_word_backward` |
|
|
||||||
|
|
||||||
### Search
|
### Search
|
||||||
|
|
||||||
> TODO: The search implementation isn't ideal yet -- we don't support searching
|
> TODO: The search implementation isn't ideal yet -- we don't support searching in reverse.
|
||||||
in reverse, or searching via smartcase.
|
|
||||||
|
|
||||||
| Key | Description | Command |
|
| Key | Description | Command |
|
||||||
| ----- | ----------- | ------- |
|
| ----- | ----------- | ------- |
|
||||||
|
@ -110,41 +112,17 @@ in reverse, or searching via smartcase.
|
||||||
| `N` | Add next search match to selection | `extend_search_next` |
|
| `N` | Add next search match to selection | `extend_search_next` |
|
||||||
| `*` | Use current selection as the search pattern | `search_selection` |
|
| `*` | Use current selection as the search pattern | `search_selection` |
|
||||||
|
|
||||||
### Unimpaired
|
### Minor modes
|
||||||
|
|
||||||
Mappings in the style of [vim-unimpaired](https://github.com/tpope/vim-unimpaired)
|
These sub-modes are accessible from normal mode and typically switch back to normal mode after a command.
|
||||||
|
|
||||||
| Key | Description | Command |
|
#### View mode
|
||||||
| ----- | ----------- | ------- |
|
|
||||||
| `[d` | Go to previous diagnostic | `goto_prev_diag` |
|
|
||||||
| `]d` | Go to next diagnostic | `goto_next_diag` |
|
|
||||||
| `[D` | Go to first diagnostic in document | `goto_first_diag` |
|
|
||||||
| `]D` | Go to last diagnostic in document | `goto_last_diag` |
|
|
||||||
| `[space` | Add newline above | `add_newline_above` |
|
|
||||||
| `]space` | Add newline below | `add_newline_below` |
|
|
||||||
|
|
||||||
### Shell
|
|
||||||
|
|
||||||
| Key | Description | Command |
|
|
||||||
| ------ | ----------- | ------- |
|
|
||||||
| `\|` | Pipe each selection through shell command, replacing with output | `shell_pipe` |
|
|
||||||
| `A-\|` | Pipe each selection into shell command, ignoring output | `shell_pipe_to` |
|
|
||||||
| `!` | Run shell command, inserting output before each selection | `shell_insert_output` |
|
|
||||||
| `A-!` | Run shell command, appending output after each selection | `shell_append_output` |
|
|
||||||
| `$` | Pipe each selection into shell command, keep selections where command returned 0 | `shell_keep_pipe` |
|
|
||||||
|
|
||||||
## Select / extend mode
|
|
||||||
|
|
||||||
I'm still pondering whether to keep this mode or not. It changes movement
|
|
||||||
commands to extend the existing selection instead of replacing it.
|
|
||||||
|
|
||||||
> NOTE: It's a bit confusing at the moment because extend hasn't been
|
|
||||||
> implemented for all movement commands yet.
|
|
||||||
|
|
||||||
## View mode
|
|
||||||
|
|
||||||
View mode is intended for scrolling and manipulating the view without changing
|
View mode is intended for scrolling and manipulating the view without changing
|
||||||
the selection.
|
the selection. The "sticky" variant of this mode is persistent; use the Escape
|
||||||
|
key to return to normal mode after usage (useful when you're simply looking
|
||||||
|
over text and not actively editing it).
|
||||||
|
|
||||||
|
|
||||||
| Key | Description | Command |
|
| Key | Description | Command |
|
||||||
| ----- | ----------- | ------- |
|
| ----- | ----------- | ------- |
|
||||||
|
@ -154,8 +132,12 @@ the selection.
|
||||||
| `m` | Align the line to the middle of the screen (horizontally) | `align_view_middle` |
|
| `m` | Align the line to the middle of the screen (horizontally) | `align_view_middle` |
|
||||||
| `j` | Scroll the view downwards | `scroll_down` |
|
| `j` | Scroll the view downwards | `scroll_down` |
|
||||||
| `k` | Scroll the view upwards | `scroll_up` |
|
| `k` | Scroll the view upwards | `scroll_up` |
|
||||||
|
| `f` | Move page down | `page_down` |
|
||||||
|
| `b` | Move page up | `page_up` |
|
||||||
|
| `d` | Move half page down | `half_page_down` |
|
||||||
|
| `u` | Move half page up | `half_page_up` |
|
||||||
|
|
||||||
## Goto mode
|
#### Goto mode
|
||||||
|
|
||||||
Jumps to various locations.
|
Jumps to various locations.
|
||||||
|
|
||||||
|
@ -177,7 +159,7 @@ Jumps to various locations.
|
||||||
| `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` |
|
||||||
|
|
||||||
## Match mode
|
#### Match mode
|
||||||
|
|
||||||
Enter this mode using `m` from normal mode. See the relavant section
|
Enter this mode using `m` from normal mode. See the relavant section
|
||||||
in [Usage](./usage.md) for an explanation about [surround](./usage.md#surround)
|
in [Usage](./usage.md) for an explanation about [surround](./usage.md#surround)
|
||||||
|
@ -192,11 +174,9 @@ and [textobject](./usage.md#textobject) usage.
|
||||||
| `a` `<object>` | Select around textobject | `select_textobject_around` |
|
| `a` `<object>` | Select around textobject | `select_textobject_around` |
|
||||||
| `i` `<object>` | Select inside textobject | `select_textobject_inner` |
|
| `i` `<object>` | Select inside textobject | `select_textobject_inner` |
|
||||||
|
|
||||||
## Object mode
|
|
||||||
|
|
||||||
TODO: Mappings for selecting syntax nodes (a superset of `[`).
|
TODO: Mappings for selecting syntax nodes (a superset of `[`).
|
||||||
|
|
||||||
## Window mode
|
#### Window mode
|
||||||
|
|
||||||
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.
|
||||||
|
|
||||||
|
@ -207,24 +187,56 @@ This layer is similar to vim keybindings as kakoune does not support window.
|
||||||
| `h`, `Ctrl-h` | Horizontal bottom split | `hsplit` |
|
| `h`, `Ctrl-h` | Horizontal bottom split | `hsplit` |
|
||||||
| `q`, `Ctrl-q` | Close current window | `wclose` |
|
| `q`, `Ctrl-q` | Close current window | `wclose` |
|
||||||
|
|
||||||
## Space mode
|
#### Space mode
|
||||||
|
|
||||||
This layer is a kludge of mappings I had under leader key in neovim.
|
This layer is a kludge of mappings, mostly pickers.
|
||||||
|
|
||||||
| Key | Description | Command |
|
| Key | Description | Command |
|
||||||
| ----- | ----------- | ------- |
|
| ----- | ----------- | ------- |
|
||||||
|
| `k` | Show documentation for the item under the cursor | `hover` |
|
||||||
| `f` | Open file picker | `file_picker` |
|
| `f` | Open file picker | `file_picker` |
|
||||||
| `b` | Open buffer picker | `buffer_picker` |
|
| `b` | Open buffer picker | `buffer_picker` |
|
||||||
| `s` | Open symbol picker (current document) | `symbol_picker` |
|
| `s` | Open symbol picker (current document) | `symbol_picker` |
|
||||||
| `a` | Apply code action | `code_action` |
|
| `a` | Apply code action | `code_action` |
|
||||||
| `'` | Open last fuzzy picker | `last_picker` |
|
| `'` | Open last fuzzy picker | `last_picker` |
|
||||||
| `w` | Enter [window mode](#window-mode) | N/A |
|
| `w` | Enter [window mode](#window-mode) | N/A |
|
||||||
| `space` | Keep primary selection TODO: it's here because space mode replaced it | `keep_primary_selection` |
|
|
||||||
| `p` | Paste system clipboard after selections | `paste_clipboard_after` |
|
| `p` | Paste system clipboard after selections | `paste_clipboard_after` |
|
||||||
| `P` | Paste system clipboard before selections | `paste_clipboard_before` |
|
| `P` | Paste system clipboard before selections | `paste_clipboard_before` |
|
||||||
| `y` | Join and yank selections to clipboard | `yank_joined_to_clipboard` |
|
| `y` | Join and yank selections to clipboard | `yank_joined_to_clipboard` |
|
||||||
| `Y` | Yank main selection to clipboard | `yank_main_selection_to_clipboard` |
|
| `Y` | Yank main selection to clipboard | `yank_main_selection_to_clipboard` |
|
||||||
| `R` | Replace selections by clipboard contents | `replace_selections_with_clipboard` |
|
| `R` | Replace selections by clipboard contents | `replace_selections_with_clipboard` |
|
||||||
|
| `/` | Global search in workspace folder | `global_search` |
|
||||||
|
|
||||||
|
> NOTE: Global search display results in a fuzzy picker, use `space + '` to bring it back up after opening a file.
|
||||||
|
|
||||||
|
#### Unimpaired
|
||||||
|
|
||||||
|
Mappings in the style of [vim-unimpaired](https://github.com/tpope/vim-unimpaired).
|
||||||
|
|
||||||
|
| Key | Description | Command |
|
||||||
|
| ----- | ----------- | ------- |
|
||||||
|
| `[d` | Go to previous diagnostic | `goto_prev_diag` |
|
||||||
|
| `]d` | Go to next diagnostic | `goto_next_diag` |
|
||||||
|
| `[D` | Go to first diagnostic in document | `goto_first_diag` |
|
||||||
|
| `]D` | Go to last diagnostic in document | `goto_last_diag` |
|
||||||
|
| `[space` | Add newline above | `add_newline_above` |
|
||||||
|
| `]space` | Add newline below | `add_newline_below` |
|
||||||
|
|
||||||
|
## Insert Mode
|
||||||
|
|
||||||
|
| Key | Description | Command |
|
||||||
|
| ----- | ----------- | ------- |
|
||||||
|
| `Escape` | Switch to normal mode | `normal_mode` |
|
||||||
|
| `Ctrl-x` | Autocomplete | `completion` |
|
||||||
|
| `Ctrl-w` | Delete previous word | `delete_word_backward` |
|
||||||
|
|
||||||
|
## Select / extend mode
|
||||||
|
|
||||||
|
I'm still pondering whether to keep this mode or not. It changes movement
|
||||||
|
commands (including goto) to extend the existing selection instead of replacing it.
|
||||||
|
|
||||||
|
> NOTE: It's a bit confusing at the moment because extend hasn't been
|
||||||
|
> implemented for all movement commands yet.
|
||||||
|
|
||||||
# Picker
|
# Picker
|
||||||
|
|
||||||
|
|
|
@ -49,4 +49,6 @@ Control, Shift and Alt modifiers are encoded respectively with the prefixes
|
||||||
| Null | `"null"` |
|
| Null | `"null"` |
|
||||||
| Escape | `"esc"` |
|
| Escape | `"esc"` |
|
||||||
|
|
||||||
|
Keys can be disabled by binding them to the `no_op` command.
|
||||||
|
|
||||||
Commands can be found in the source code at [`helix-term/src/commands.rs`](https://github.com/helix-editor/helix/blob/master/helix-term/src/commands.rs)
|
Commands can be found in the source code at [`helix-term/src/commands.rs`](https://github.com/helix-editor/helix/blob/master/helix-term/src/commands.rs)
|
||||||
|
|
|
@ -30,85 +30,9 @@ if the key contains a dot `'.'`, it must be quoted to prevent it being parsed as
|
||||||
"key.key" = "#ffffff"
|
"key.key" = "#ffffff"
|
||||||
```
|
```
|
||||||
|
|
||||||
Possible modifiers:
|
### Color palettes
|
||||||
|
|
||||||
| Modifier |
|
It's recommended define a palette of named colors, and refer to them from the
|
||||||
| --- |
|
|
||||||
| `bold` |
|
|
||||||
| `dim` |
|
|
||||||
| `italic` |
|
|
||||||
| `underlined` |
|
|
||||||
| `slow\_blink` |
|
|
||||||
| `rapid\_blink` |
|
|
||||||
| `reversed` |
|
|
||||||
| `hidden` |
|
|
||||||
| `crossed\_out` |
|
|
||||||
|
|
||||||
Possible keys:
|
|
||||||
|
|
||||||
| Key | Notes |
|
|
||||||
| --- | --- |
|
|
||||||
| `attribute` | |
|
|
||||||
| `keyword` | |
|
|
||||||
| `keyword.directive` | Preprocessor directives (\#if in C) |
|
|
||||||
| `keyword.control` | Control flow |
|
|
||||||
| `namespace` | |
|
|
||||||
| `punctuation` | |
|
|
||||||
| `punctuation.delimiter` | |
|
|
||||||
| `operator` | |
|
|
||||||
| `special` | |
|
|
||||||
| `property` | |
|
|
||||||
| `variable` | |
|
|
||||||
| `variable.parameter` | |
|
|
||||||
| `type` | |
|
|
||||||
| `type.builtin` | |
|
|
||||||
| `type.enum.variant` | Enum variants |
|
|
||||||
| `constructor` | |
|
|
||||||
| `function` | |
|
|
||||||
| `function.macro` | |
|
|
||||||
| `function.builtin` | |
|
|
||||||
| `comment` | |
|
|
||||||
| `variable.builtin` | |
|
|
||||||
| `constant` | |
|
|
||||||
| `constant.builtin` | |
|
|
||||||
| `string` | |
|
|
||||||
| `number` | |
|
|
||||||
| `escape` | Escaped characters |
|
|
||||||
| `label` | For lifetimes |
|
|
||||||
| `module` | |
|
|
||||||
| `ui.background` | |
|
|
||||||
| `ui.cursor` | |
|
|
||||||
| `ui.cursor.insert` | |
|
|
||||||
| `ui.cursor.select` | |
|
|
||||||
| `ui.cursor.match` | Matching bracket etc. |
|
|
||||||
| `ui.cursor.primary` | Cursor with primary selection |
|
|
||||||
| `ui.linenr` | |
|
|
||||||
| `ui.linenr.selected` | |
|
|
||||||
| `ui.statusline` | |
|
|
||||||
| `ui.statusline.inactive` | |
|
|
||||||
| `ui.popup` | |
|
|
||||||
| `ui.window` | |
|
|
||||||
| `ui.help` | |
|
|
||||||
| `ui.text` | |
|
|
||||||
| `ui.text.focus` | |
|
|
||||||
| `ui.info` | |
|
|
||||||
| `ui.info.text` | |
|
|
||||||
| `ui.menu` | |
|
|
||||||
| `ui.menu.selected` | |
|
|
||||||
| `ui.selection` | For selections in the editing area |
|
|
||||||
| `ui.selection.primary` | |
|
|
||||||
| `warning` | LSP warning |
|
|
||||||
| `error` | LSP error |
|
|
||||||
| `info` | LSP info |
|
|
||||||
| `hint` | LSP hint |
|
|
||||||
|
|
||||||
These keys match [tree-sitter scopes](https://tree-sitter.github.io/tree-sitter/syntax-highlighting#theme). We half-follow the common scopes from [macromates language grammars](https://macromates.com/manual/en/language_grammars) with some differences.
|
|
||||||
|
|
||||||
For a given highlight produced, styling will be determined based on the longest matching theme key. So it's enough to provide function to highlight `function.macro` and `function.builtin` as well, but you can use more specific scopes to highlight specific cases differently.
|
|
||||||
|
|
||||||
## Color palettes
|
|
||||||
|
|
||||||
You can define a palette of named colors, and refer to them from the
|
|
||||||
configuration values in your theme. To do this, add a table called
|
configuration values in your theme. To do this, add a table called
|
||||||
`palette` to your theme file:
|
`palette` to your theme file:
|
||||||
|
|
||||||
|
@ -123,3 +47,148 @@ black = "#000000"
|
||||||
|
|
||||||
Remember that the `[palette]` table includes all keys after its header,
|
Remember that the `[palette]` table includes all keys after its header,
|
||||||
so you should define the palette after normal theme options.
|
so you should define the palette after normal theme options.
|
||||||
|
|
||||||
|
The default palette uses the terminal's default 16 colors, and the colors names
|
||||||
|
are listed below. The `[palette]` section in the config file takes precedence
|
||||||
|
over it and is merged into the default palette.
|
||||||
|
|
||||||
|
| Color Name |
|
||||||
|
| --- |
|
||||||
|
| `black` |
|
||||||
|
| `red` |
|
||||||
|
| `green` |
|
||||||
|
| `yellow` |
|
||||||
|
| `blue` |
|
||||||
|
| `magenta` |
|
||||||
|
| `cyan` |
|
||||||
|
| `gray` |
|
||||||
|
| `light-red` |
|
||||||
|
| `light-green` |
|
||||||
|
| `light-yellow` |
|
||||||
|
| `light-blue` |
|
||||||
|
| `light-magenta` |
|
||||||
|
| `light-cyan` |
|
||||||
|
| `light-gray` |
|
||||||
|
| `white` |
|
||||||
|
|
||||||
|
### Modifiers
|
||||||
|
|
||||||
|
The following values may be used as modifiers.
|
||||||
|
|
||||||
|
Less common modifiers might not be supported by your terminal emulator.
|
||||||
|
|
||||||
|
| Modifier |
|
||||||
|
| --- |
|
||||||
|
| `bold` |
|
||||||
|
| `dim` |
|
||||||
|
| `italic` |
|
||||||
|
| `underlined` |
|
||||||
|
| `slow_blink` |
|
||||||
|
| `rapid_blink` |
|
||||||
|
| `reversed` |
|
||||||
|
| `hidden` |
|
||||||
|
| `crossed_out` |
|
||||||
|
|
||||||
|
### Scopes
|
||||||
|
|
||||||
|
The following is a list of scopes available to use for styling.
|
||||||
|
|
||||||
|
#### Syntax highlighting
|
||||||
|
|
||||||
|
These keys match [tree-sitter scopes](https://tree-sitter.github.io/tree-sitter/syntax-highlighting#theme).
|
||||||
|
|
||||||
|
For a given highlight produced, styling will be determined based on the longest matching theme key. For example, the highlight `function.builtin.static` would match the key `function.builtin` rather than `function`.
|
||||||
|
|
||||||
|
We use a similar set of scopes as
|
||||||
|
[SublimeText](https://www.sublimetext.com/docs/scope_naming.html). See also
|
||||||
|
[TextMate](https://macromates.com/manual/en/language_grammars) scopes.
|
||||||
|
|
||||||
|
- `escape` (TODO: rename to (constant).character.escape)
|
||||||
|
|
||||||
|
- `type` - Types
|
||||||
|
- `builtin` - Primitive types provided by the language (`int`, `usize`)
|
||||||
|
|
||||||
|
- `constant` (TODO: constant.other.placeholder for %v)
|
||||||
|
- `builtin` Special constants provided by the language (`true`, `false`, `nil` etc)
|
||||||
|
- `boolean`
|
||||||
|
- `character`
|
||||||
|
|
||||||
|
- `number` (TODO: rename to constant.number/.numeric.{integer, float, complex})
|
||||||
|
- `string` (TODO: string.quoted.{single, double}, string.raw/.unquoted)?
|
||||||
|
- `regexp` - Regular expressions
|
||||||
|
- `special`
|
||||||
|
- `path`
|
||||||
|
- `url`
|
||||||
|
|
||||||
|
- `comment` - Code comments
|
||||||
|
- `line` - Single line comments (`//`)
|
||||||
|
- `block` - Block comments (e.g. (`/* */`)
|
||||||
|
- `documentation` - Documentation comments (e.g. `///` in Rust)
|
||||||
|
|
||||||
|
- `variable` - Variables
|
||||||
|
- `builtin` - Reserved language variables (`self`, `this`, `super`, etc)
|
||||||
|
- `parameter` - Function parameters
|
||||||
|
- `property`
|
||||||
|
- `function` (TODO: ?)
|
||||||
|
|
||||||
|
- `label`
|
||||||
|
|
||||||
|
- `punctuation`
|
||||||
|
- `delimiter` - Commas, colons
|
||||||
|
- `bracket` - Parentheses, angle brackets, etc.
|
||||||
|
|
||||||
|
- `keyword`
|
||||||
|
- `control`
|
||||||
|
- `conditional` - `if`, `else`
|
||||||
|
- `repeat` - `for`, `while`, `loop`
|
||||||
|
- `import` - `import`, `export`
|
||||||
|
- (TODO: return?)
|
||||||
|
- `directive` - Preprocessor directives (`#if` in C)
|
||||||
|
- `function` - `fn`, `func`
|
||||||
|
|
||||||
|
- `operator` - `||`, `+=`, `>`, `or`
|
||||||
|
|
||||||
|
- `function`
|
||||||
|
- `builtin`
|
||||||
|
- `method`
|
||||||
|
- `macro`
|
||||||
|
- `special` (preprocesor in C)
|
||||||
|
|
||||||
|
- `tag` - Tags (e.g. `<body>` in HTML)
|
||||||
|
|
||||||
|
- `namespace`
|
||||||
|
|
||||||
|
#### Interface
|
||||||
|
|
||||||
|
These scopes are used for theming the editor interface.
|
||||||
|
|
||||||
|
|
||||||
|
| Key | Notes |
|
||||||
|
| --- | --- |
|
||||||
|
| `ui.background` | |
|
||||||
|
| `ui.cursor` | |
|
||||||
|
| `ui.cursor.insert` | |
|
||||||
|
| `ui.cursor.select` | |
|
||||||
|
| `ui.cursor.match` | Matching bracket etc. |
|
||||||
|
| `ui.cursor.primary` | Cursor with primary selection |
|
||||||
|
| `ui.linenr` | |
|
||||||
|
| `ui.linenr.selected` | |
|
||||||
|
| `ui.statusline` | Statusline |
|
||||||
|
| `ui.statusline.inactive` | Statusline (unfocused document) |
|
||||||
|
| `ui.popup` | |
|
||||||
|
| `ui.window` | |
|
||||||
|
| `ui.help` | |
|
||||||
|
| `ui.text` | |
|
||||||
|
| `ui.text.focus` | |
|
||||||
|
| `ui.info` | |
|
||||||
|
| `ui.info.text` | |
|
||||||
|
| `ui.menu` | |
|
||||||
|
| `ui.menu.selected` | |
|
||||||
|
| `ui.selection` | For selections in the editing area |
|
||||||
|
| `ui.selection.primary` | |
|
||||||
|
| `warning` | Diagnostics warning (gutter) |
|
||||||
|
| `error` | Diagnostics error (gutter) |
|
||||||
|
| `info` | Diagnostics info (gutter) |
|
||||||
|
| `hint` | Diagnostics hint (gutter) |
|
||||||
|
| `diagnostic` | For text in editing area |
|
||||||
|
|
||||||
|
|
|
@ -114,6 +114,19 @@ h6:target::before {
|
||||||
margin-bottom: .875em;
|
margin-bottom: .875em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.content ul li {
|
||||||
|
margin-bottom: .25rem;
|
||||||
|
}
|
||||||
|
.content ul {
|
||||||
|
list-style-type: square;
|
||||||
|
}
|
||||||
|
.content ul ul, .content ol ul {
|
||||||
|
margin-bottom: .5rem;
|
||||||
|
}
|
||||||
|
.content li p {
|
||||||
|
margin-bottom: .5em;
|
||||||
|
}
|
||||||
|
|
||||||
.content p { line-height: 1.45em; }
|
.content p { line-height: 1.45em; }
|
||||||
.content ol { line-height: 1.45em; }
|
.content ol { line-height: 1.45em; }
|
||||||
.content ul { line-height: 1.45em; }
|
.content ul { line-height: 1.45em; }
|
||||||
|
|
|
@ -69,7 +69,7 @@
|
||||||
|
|
||||||
--links: #2b79a2;
|
--links: #2b79a2;
|
||||||
|
|
||||||
--inline-code-color: #c5c8c6;;
|
--inline-code-color: #c5c8c6;
|
||||||
|
|
||||||
--theme-popup-bg: #141617;
|
--theme-popup-bg: #141617;
|
||||||
--theme-popup-border: #43484d;
|
--theme-popup-border: #43484d;
|
||||||
|
@ -110,7 +110,7 @@
|
||||||
|
|
||||||
--links: #20609f;
|
--links: #20609f;
|
||||||
|
|
||||||
--inline-code-color: #301900;
|
--inline-code-color: #a39e9b;
|
||||||
|
|
||||||
--theme-popup-bg: #fafafa;
|
--theme-popup-bg: #fafafa;
|
||||||
--theme-popup-border: #cccccc;
|
--theme-popup-border: #cccccc;
|
||||||
|
@ -151,7 +151,7 @@
|
||||||
|
|
||||||
--links: #2b79a2;
|
--links: #2b79a2;
|
||||||
|
|
||||||
--inline-code-color: #c5c8c6;;
|
--inline-code-color: #c5c8c6;
|
||||||
|
|
||||||
--theme-popup-bg: #161923;
|
--theme-popup-bg: #161923;
|
||||||
--theme-popup-border: #737480;
|
--theme-popup-border: #737480;
|
||||||
|
@ -192,7 +192,7 @@
|
||||||
|
|
||||||
--links: #2b79a2;
|
--links: #2b79a2;
|
||||||
|
|
||||||
--inline-code-color: #6e6b5e;
|
--inline-code-color: #c5c8c6;
|
||||||
|
|
||||||
--theme-popup-bg: #e1e1db;
|
--theme-popup-bg: #e1e1db;
|
||||||
--theme-popup-border: #b38f6b;
|
--theme-popup-border: #b38f6b;
|
||||||
|
@ -234,7 +234,7 @@
|
||||||
|
|
||||||
--links: #2b79a2;
|
--links: #2b79a2;
|
||||||
|
|
||||||
--inline-code-color: #c5c8c6;;
|
--inline-code-color: #6e6b5e;
|
||||||
|
|
||||||
--theme-popup-bg: #141617;
|
--theme-popup-bg: #141617;
|
||||||
--theme-popup-border: #43484d;
|
--theme-popup-border: #43484d;
|
||||||
|
@ -261,6 +261,7 @@
|
||||||
.colibri {
|
.colibri {
|
||||||
--bg: #3b224c;
|
--bg: #3b224c;
|
||||||
--fg: #bcbdd0;
|
--fg: #bcbdd0;
|
||||||
|
--heading-fg: #fff;
|
||||||
|
|
||||||
--sidebar-bg: #281733;
|
--sidebar-bg: #281733;
|
||||||
--sidebar-fg: #c8c9db;
|
--sidebar-fg: #c8c9db;
|
||||||
|
@ -276,18 +277,19 @@
|
||||||
/* --links: #a4a0e8; */
|
/* --links: #a4a0e8; */
|
||||||
--links: #ECCDBA;
|
--links: #ECCDBA;
|
||||||
|
|
||||||
--inline-code-color: #c5c8c6;;
|
--inline-code-color: hsl(48.7, 7.8%, 70%);
|
||||||
|
|
||||||
--theme-popup-bg: #161923;
|
--theme-popup-bg: #161923;
|
||||||
--theme-popup-border: #737480;
|
--theme-popup-border: #737480;
|
||||||
--theme-hover: rgba(0,0,0, .2);
|
--theme-hover: rgba(0,0,0, .2);
|
||||||
|
|
||||||
--quote-bg: hsl(226, 15%, 17%);
|
--quote-bg: #281733;
|
||||||
--quote-border: hsl(226, 15%, 22%);
|
--quote-border: hsl(226, 15%, 22%);
|
||||||
|
|
||||||
--table-border-color: hsl(226, 23%, 16%);
|
--table-border-color: hsl(226, 23%, 76%);
|
||||||
--table-header-bg: hsl(226, 23%, 31%);
|
--table-header-bg: hsla(226, 23%, 31%, 0);
|
||||||
--table-alternate-bg: hsl(226, 23%, 14%);
|
--table-alternate-bg: hsl(226, 23%, 14%);
|
||||||
|
--table-border-line: hsla(201deg, 20%, 92%, 0.2);
|
||||||
|
|
||||||
--searchbar-border-color: #aaa;
|
--searchbar-border-color: #aaa;
|
||||||
--searchbar-bg: #aeaec6;
|
--searchbar-bg: #aeaec6;
|
||||||
|
@ -300,6 +302,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.colibri {
|
.colibri {
|
||||||
|
/*
|
||||||
--bg: #ffffff;
|
--bg: #ffffff;
|
||||||
--fg: #452859;
|
--fg: #452859;
|
||||||
--fg: #5a5977;
|
--fg: #5a5977;
|
||||||
|
@ -318,7 +321,7 @@
|
||||||
|
|
||||||
--links: #6F44F0;
|
--links: #6F44F0;
|
||||||
|
|
||||||
--inline-code-color: #697C81;
|
--inline-code-color: #a39e9b;
|
||||||
|
|
||||||
--theme-popup-bg: #161923;
|
--theme-popup-bg: #161923;
|
||||||
--theme-popup-border: #737480;
|
--theme-popup-border: #737480;
|
||||||
|
@ -341,4 +344,5 @@
|
||||||
--searchresults-border-color: #5c5c68;
|
--searchresults-border-color: #5c5c68;
|
||||||
--searchresults-li-bg: #242430;
|
--searchresults-li-bg: #242430;
|
||||||
--search-mark-bg: #a2cff5;
|
--search-mark-bg: #a2cff5;
|
||||||
|
*/
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,83 +1,56 @@
|
||||||
/*
|
pre code.hljs {
|
||||||
* An increased contrast highlighting scheme loosely based on the
|
display:block;
|
||||||
* "Base16 Atelier Dune Light" theme by Bram de Haan
|
overflow-x:auto;
|
||||||
* (http://atelierbram.github.io/syntax-highlighting/atelier-schemes/dune)
|
padding:1em
|
||||||
* Original Base16 color scheme by Chris Kempson
|
}
|
||||||
* (https://github.com/chriskempson/base16)
|
code.hljs {
|
||||||
*/
|
padding:3px 5px
|
||||||
|
}
|
||||||
/* Comment */
|
.hljs {
|
||||||
|
background:#2f1e2e;
|
||||||
|
color:#a39e9b
|
||||||
|
}
|
||||||
.hljs-comment,
|
.hljs-comment,
|
||||||
.hljs-quote {
|
.hljs-quote {
|
||||||
color: #575757;
|
color:#8d8687
|
||||||
}
|
}
|
||||||
|
.hljs-link,
|
||||||
/* Red */
|
.hljs-meta,
|
||||||
.hljs-variable,
|
|
||||||
.hljs-template-variable,
|
|
||||||
.hljs-attribute,
|
|
||||||
.hljs-tag,
|
|
||||||
.hljs-name,
|
.hljs-name,
|
||||||
.hljs-regexp,
|
.hljs-regexp,
|
||||||
.hljs-link,
|
.hljs-selector-class,
|
||||||
.hljs-name,
|
|
||||||
.hljs-selector-id,
|
.hljs-selector-id,
|
||||||
.hljs-selector-class {
|
.hljs-tag,
|
||||||
color: #d70025;
|
.hljs-template-variable,
|
||||||
|
.hljs-variable {
|
||||||
|
color:#ef6155
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Orange */
|
|
||||||
.hljs-number,
|
|
||||||
.hljs-meta,
|
|
||||||
.hljs-built_in,
|
.hljs-built_in,
|
||||||
.hljs-builtin-name,
|
.hljs-deletion,
|
||||||
.hljs-literal,
|
.hljs-literal,
|
||||||
.hljs-type,
|
.hljs-number,
|
||||||
.hljs-params {
|
.hljs-params,
|
||||||
color: #b21e00;
|
.hljs-type {
|
||||||
|
color:#f99b15
|
||||||
}
|
}
|
||||||
|
.hljs-attribute,
|
||||||
/* Green */
|
.hljs-section,
|
||||||
|
.hljs-title {
|
||||||
|
color:#fec418
|
||||||
|
}
|
||||||
|
.hljs-addition,
|
||||||
|
.hljs-bullet,
|
||||||
.hljs-string,
|
.hljs-string,
|
||||||
.hljs-symbol,
|
.hljs-symbol {
|
||||||
.hljs-bullet {
|
color:#48b685
|
||||||
color: #008200;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Blue */
|
|
||||||
.hljs-title,
|
|
||||||
.hljs-section {
|
|
||||||
color: #0030f2;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Purple */
|
|
||||||
.hljs-keyword,
|
.hljs-keyword,
|
||||||
.hljs-selector-tag {
|
.hljs-selector-tag {
|
||||||
color: #9d00ec;
|
color:#815ba4
|
||||||
}
|
}
|
||||||
|
|
||||||
.hljs {
|
|
||||||
display: block;
|
|
||||||
overflow-x: auto;
|
|
||||||
background: #f6f7f6;
|
|
||||||
color: #000;
|
|
||||||
padding: 0.5em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.hljs-emphasis {
|
.hljs-emphasis {
|
||||||
font-style: italic;
|
font-style:italic
|
||||||
}
|
}
|
||||||
|
|
||||||
.hljs-strong {
|
.hljs-strong {
|
||||||
font-weight: bold;
|
font-weight:700
|
||||||
}
|
|
||||||
|
|
||||||
.hljs-addition {
|
|
||||||
color: #22863a;
|
|
||||||
background-color: #f0fff4;
|
|
||||||
}
|
|
||||||
|
|
||||||
.hljs-deletion {
|
|
||||||
color: #b31d28;
|
|
||||||
background-color: #ffeef0;
|
|
||||||
}
|
}
|
||||||
|
|
24
flake.lock
24
flake.lock
|
@ -2,11 +2,11 @@
|
||||||
"nodes": {
|
"nodes": {
|
||||||
"devshell": {
|
"devshell": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1625086391,
|
"lastModified": 1630239564,
|
||||||
"narHash": "sha256-IpNPv1v8s4L3CoxhwcgZIitGpcrnNgnj09X7TA0QV3k=",
|
"narHash": "sha256-lv7atkVE1+dFw0llmzONsbSIo5ao85KpNSRoFk4K8vU=",
|
||||||
"owner": "numtide",
|
"owner": "numtide",
|
||||||
"repo": "devshell",
|
"repo": "devshell",
|
||||||
"rev": "4b5ac7cf7d9a1cc60b965bb51b59922f2210cbc7",
|
"rev": "bd86d3a2bb28ce4d223315e0eca0d59fef8a0a73",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
@ -40,11 +40,11 @@
|
||||||
"rustOverlay": "rustOverlay"
|
"rustOverlay": "rustOverlay"
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1628489367,
|
"lastModified": 1631254163,
|
||||||
"narHash": "sha256-ADYKHf8aPo1qTw1J+eqVprnEbH8lES0yZamD/yM7RAM=",
|
"narHash": "sha256-8+nOGLH1fXwWnNMTQq/Igk434BzZF5Vld45xLDLiNDQ=",
|
||||||
"owner": "yusdacra",
|
"owner": "yusdacra",
|
||||||
"repo": "nix-cargo-integration",
|
"repo": "nix-cargo-integration",
|
||||||
"rev": "0dc8383aae5f791a48e34120edb04670b947dc0b",
|
"rev": "432d8504a32232e8d74710024d5bf5cc31767651",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
@ -55,11 +55,11 @@
|
||||||
},
|
},
|
||||||
"nixpkgs": {
|
"nixpkgs": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1628465643,
|
"lastModified": 1631206977,
|
||||||
"narHash": "sha256-QSNw9bDq9uGUniQQtakRuw4m21Jxugm23SXLVgEV4DM=",
|
"narHash": "sha256-o3Dct9aJ5ht5UaTUBzXrRcK1RZt2eG5/xSlWJuUCVZM=",
|
||||||
"owner": "nixos",
|
"owner": "nixos",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "6ef4f522d63f22b40004319778761040d3197390",
|
"rev": "4f6d8095fd51954120a1d08ea5896fe42dc3923b",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
@ -79,11 +79,11 @@
|
||||||
"rustOverlay": {
|
"rustOverlay": {
|
||||||
"flake": false,
|
"flake": false,
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1628475192,
|
"lastModified": 1631240108,
|
||||||
"narHash": "sha256-A32shcfPMCll7psCS0OBxVCkA+PKfeWvmU4y9lgNZzU=",
|
"narHash": "sha256-ffsTkAGyQLxu4E28nVcqwc8xFL/H1UEwrRw2ITI43Aw=",
|
||||||
"owner": "oxalica",
|
"owner": "oxalica",
|
||||||
"repo": "rust-overlay",
|
"repo": "rust-overlay",
|
||||||
"rev": "56a8ddb827cbe7a914be88f4a52998a5f93ff468",
|
"rev": "3a29d5e726b855d9463eb5dfe04f1ec14d413289",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
|
17
flake.nix
17
flake.nix
|
@ -30,22 +30,7 @@
|
||||||
};
|
};
|
||||||
# link languages and theme toml files since helix-view expects them
|
# link languages and theme toml files since helix-view expects them
|
||||||
helix-view = _: { preConfigure = "ln -s ${common.root}/{languages.toml,theme.toml} .."; };
|
helix-view = _: { preConfigure = "ln -s ${common.root}/{languages.toml,theme.toml} .."; };
|
||||||
helix-syntax = prev: {
|
helix-syntax = _prev: {
|
||||||
src =
|
|
||||||
let
|
|
||||||
pkgs = common.pkgs;
|
|
||||||
helix = pkgs.fetchgit {
|
|
||||||
url = "https://github.com/helix-editor/helix.git";
|
|
||||||
rev = "d4bd5b37669708361a0a6cd2917464b010e6b7f5";
|
|
||||||
fetchSubmodules = true;
|
|
||||||
sha256 = "sha256-KayR7K7UC0mT6EjHsZsCYY9IVDJzft63fGpPKGSY8nQ=";
|
|
||||||
};
|
|
||||||
in
|
|
||||||
pkgs.runCommand prev.src.name { } ''
|
|
||||||
mkdir -p $out
|
|
||||||
ln -s ${prev.src}/* $out
|
|
||||||
ln -sf ${helix}/helix-syntax/languages $out
|
|
||||||
'';
|
|
||||||
preConfigure = "mkdir -p ../runtime/grammars";
|
preConfigure = "mkdir -p ../runtime/grammars";
|
||||||
postInstall = "cp -r ../runtime $out/runtime";
|
postInstall = "cp -r ../runtime $out/runtime";
|
||||||
};
|
};
|
||||||
|
|
|
@ -23,7 +23,7 @@ unicode-segmentation = "1.8"
|
||||||
unicode-width = "0.1"
|
unicode-width = "0.1"
|
||||||
unicode-general-category = "0.4"
|
unicode-general-category = "0.4"
|
||||||
# slab = "0.4.2"
|
# slab = "0.4.2"
|
||||||
tree-sitter = "0.19"
|
tree-sitter = "0.20"
|
||||||
once_cell = "1.8"
|
once_cell = "1.8"
|
||||||
arc-swap = "1"
|
arc-swap = "1"
|
||||||
regex = "1"
|
regex = "1"
|
||||||
|
@ -31,7 +31,7 @@ regex = "1"
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
toml = "0.5"
|
toml = "0.5"
|
||||||
|
|
||||||
similar = "1.3"
|
similar = "2.0"
|
||||||
|
|
||||||
etcetera = "0.3"
|
etcetera = "0.3"
|
||||||
|
|
||||||
|
|
|
@ -316,8 +316,12 @@ pub fn suggested_indent_for_pos(
|
||||||
pub fn get_scopes(syntax: Option<&Syntax>, text: RopeSlice, pos: usize) -> Vec<&'static str> {
|
pub fn get_scopes(syntax: Option<&Syntax>, text: RopeSlice, pos: usize) -> Vec<&'static str> {
|
||||||
let mut scopes = Vec::new();
|
let mut scopes = Vec::new();
|
||||||
if let Some(syntax) = syntax {
|
if let Some(syntax) = syntax {
|
||||||
let byte_start = text.char_to_byte(pos);
|
let pos = text.char_to_byte(pos);
|
||||||
let node = match get_highest_syntax_node_at_bytepos(syntax, byte_start) {
|
let mut node = match syntax
|
||||||
|
.tree()
|
||||||
|
.root_node()
|
||||||
|
.descendant_for_byte_range(pos, pos)
|
||||||
|
{
|
||||||
Some(node) => node,
|
Some(node) => node,
|
||||||
None => return scopes,
|
None => return scopes,
|
||||||
};
|
};
|
||||||
|
@ -325,7 +329,8 @@ pub fn get_scopes(syntax: Option<&Syntax>, text: RopeSlice, pos: usize) -> Vec<&
|
||||||
scopes.push(node.kind());
|
scopes.push(node.kind());
|
||||||
|
|
||||||
while let Some(parent) = node.parent() {
|
while let Some(parent) = node.parent() {
|
||||||
scopes.push(parent.kind())
|
scopes.push(parent.kind());
|
||||||
|
node = parent;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -449,6 +454,7 @@ where
|
||||||
highlight_config: OnceCell::new(),
|
highlight_config: OnceCell::new(),
|
||||||
config: None,
|
config: None,
|
||||||
//
|
//
|
||||||
|
injection_regex: None,
|
||||||
roots: vec![],
|
roots: vec![],
|
||||||
comment_token: None,
|
comment_token: None,
|
||||||
auto_format: false,
|
auto_format: false,
|
||||||
|
|
|
@ -360,6 +360,15 @@ impl Selection {
|
||||||
self.normalize()
|
self.normalize()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Adds a new range to the selection and makes it the primary range.
|
||||||
|
pub fn remove(mut self, index: usize) -> Self {
|
||||||
|
self.ranges.remove(index);
|
||||||
|
if index < self.primary_index || self.primary_index == self.ranges.len() {
|
||||||
|
self.primary_index -= 1;
|
||||||
|
}
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
/// 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 {
|
||||||
|
|
|
@ -21,6 +21,15 @@ use std::{
|
||||||
use once_cell::sync::{Lazy, OnceCell};
|
use once_cell::sync::{Lazy, OnceCell};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
fn deserialize_regex<'de, D>(deserializer: D) -> Result<Option<Regex>, D::Error>
|
||||||
|
where
|
||||||
|
D: serde::Deserializer<'de>,
|
||||||
|
{
|
||||||
|
Option::<String>::deserialize(deserializer)?
|
||||||
|
.map(|buf| Regex::new(&buf).map_err(serde::de::Error::custom))
|
||||||
|
.transpose()
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
pub struct Configuration {
|
pub struct Configuration {
|
||||||
pub language: Vec<LanguageConfiguration>,
|
pub language: Vec<LanguageConfiguration>,
|
||||||
|
@ -42,7 +51,8 @@ pub struct LanguageConfiguration {
|
||||||
pub auto_format: bool,
|
pub auto_format: bool,
|
||||||
|
|
||||||
// content_regex
|
// content_regex
|
||||||
// injection_regex
|
#[serde(default, skip_serializing, deserialize_with = "deserialize_regex")]
|
||||||
|
pub injection_regex: Option<Regex>,
|
||||||
// first_line_regex
|
// first_line_regex
|
||||||
//
|
//
|
||||||
#[serde(skip)]
|
#[serde(skip)]
|
||||||
|
@ -182,8 +192,12 @@ impl LanguageConfiguration {
|
||||||
&highlights_query,
|
&highlights_query,
|
||||||
&injections_query,
|
&injections_query,
|
||||||
&locals_query,
|
&locals_query,
|
||||||
)
|
);
|
||||||
.unwrap(); // TODO: no unwrap
|
|
||||||
|
let config = match config {
|
||||||
|
Ok(config) => config,
|
||||||
|
Err(err) => panic!("{}", err),
|
||||||
|
}; // TODO: avoid panic
|
||||||
config.configure(scopes);
|
config.configure(scopes);
|
||||||
Some(Arc::new(config))
|
Some(Arc::new(config))
|
||||||
}
|
}
|
||||||
|
@ -277,6 +291,30 @@ impl Loader {
|
||||||
.cloned()
|
.cloned()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn language_configuration_for_injection_string(
|
||||||
|
&self,
|
||||||
|
string: &str,
|
||||||
|
) -> Option<Arc<LanguageConfiguration>> {
|
||||||
|
let mut best_match_length = 0;
|
||||||
|
let mut best_match_position = None;
|
||||||
|
for (i, configuration) in self.language_configs.iter().enumerate() {
|
||||||
|
if let Some(injection_regex) = &configuration.injection_regex {
|
||||||
|
if let Some(mat) = injection_regex.find(string) {
|
||||||
|
let length = mat.end() - mat.start();
|
||||||
|
if length > best_match_length {
|
||||||
|
best_match_position = Some(i);
|
||||||
|
best_match_length = length;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(i) = best_match_position {
|
||||||
|
let configuration = &self.language_configs[i];
|
||||||
|
return Some(configuration.clone());
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
pub fn language_configs_iter(&self) -> impl Iterator<Item = &Arc<LanguageConfiguration>> {
|
pub fn language_configs_iter(&self) -> impl Iterator<Item = &Arc<LanguageConfiguration>> {
|
||||||
self.language_configs.iter()
|
self.language_configs.iter()
|
||||||
}
|
}
|
||||||
|
@ -314,16 +352,6 @@ fn byte_range_to_str(range: std::ops::Range<usize>, source: RopeSlice) -> Cow<st
|
||||||
Cow::from(source.slice(start_char..end_char))
|
Cow::from(source.slice(start_char..end_char))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn node_to_bytes<'a>(node: Node, source: RopeSlice<'a>) -> Cow<'a, [u8]> {
|
|
||||||
let start_char = source.byte_to_char(node.start_byte());
|
|
||||||
let end_char = source.byte_to_char(node.end_byte());
|
|
||||||
let fragment = source.slice(start_char..end_char);
|
|
||||||
match fragment.as_str() {
|
|
||||||
Some(fragment) => Cow::Borrowed(fragment.as_bytes()),
|
|
||||||
None => Cow::Owned(String::from(fragment).into_bytes()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Syntax {
|
impl Syntax {
|
||||||
// buffer, grammar, config, grammars, sync_timeout?
|
// buffer, grammar, config, grammars, sync_timeout?
|
||||||
pub fn new(
|
pub fn new(
|
||||||
|
@ -416,16 +444,11 @@ impl Syntax {
|
||||||
let config_ref =
|
let config_ref =
|
||||||
unsafe { mem::transmute::<_, &'static HighlightConfiguration>(self.config.as_ref()) };
|
unsafe { mem::transmute::<_, &'static HighlightConfiguration>(self.config.as_ref()) };
|
||||||
|
|
||||||
// TODO: if reusing cursors this might need resetting
|
// if reusing cursors & no range this resets to whole range
|
||||||
if let Some(range) = &range {
|
cursor_ref.set_byte_range(range.clone().unwrap_or(0..usize::MAX));
|
||||||
cursor_ref.set_byte_range(range.start, range.end);
|
|
||||||
}
|
|
||||||
|
|
||||||
let captures = cursor_ref
|
let captures = cursor_ref
|
||||||
.captures(query_ref, tree_ref.root_node(), move |n: Node| {
|
.captures(query_ref, tree_ref.root_node(), RopeProvider(source))
|
||||||
// &source[n.byte_range()]
|
|
||||||
node_to_bytes(n, source)
|
|
||||||
})
|
|
||||||
.peekable();
|
.peekable();
|
||||||
|
|
||||||
// manually craft the root layer based on the existing tree
|
// manually craft the root layer based on the existing tree
|
||||||
|
@ -539,10 +562,7 @@ impl LanguageLayer {
|
||||||
// let mut injections_by_pattern_index =
|
// let mut injections_by_pattern_index =
|
||||||
// vec![(None, Vec::new(), false); combined_injections_query.pattern_count()];
|
// vec![(None, Vec::new(), false); combined_injections_query.pattern_count()];
|
||||||
// let matches =
|
// let matches =
|
||||||
// cursor.matches(combined_injections_query, tree.root_node(), |n: Node| {
|
// cursor.matches(combined_injections_query, tree.root_node(), RopeProvider(source));
|
||||||
// // &source[n.byte_range()]
|
|
||||||
// node_to_bytes(n, source)
|
|
||||||
// });
|
|
||||||
// for mat in matches {
|
// for mat in matches {
|
||||||
// let entry = &mut injections_by_pattern_index[mat.pattern_index];
|
// let entry = &mut injections_by_pattern_index[mat.pattern_index];
|
||||||
// let (language_name, content_node, include_children) =
|
// let (language_name, content_node, include_children) =
|
||||||
|
@ -754,7 +774,7 @@ use std::sync::atomic::{AtomicUsize, Ordering};
|
||||||
use std::{iter, mem, ops, str, usize};
|
use std::{iter, mem, ops, str, usize};
|
||||||
use tree_sitter::{
|
use tree_sitter::{
|
||||||
Language as Grammar, Node, Parser, Point, Query, QueryCaptures, QueryCursor, QueryError,
|
Language as Grammar, Node, Parser, Point, Query, QueryCaptures, QueryCursor, QueryError,
|
||||||
QueryMatch, Range, Tree,
|
QueryMatch, Range, TextProvider, Tree,
|
||||||
};
|
};
|
||||||
|
|
||||||
const CANCELLATION_CHECK_INTERVAL: usize = 100;
|
const CANCELLATION_CHECK_INTERVAL: usize = 100;
|
||||||
|
@ -814,7 +834,7 @@ struct LocalScope<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
struct HighlightIter<'a, 'tree: 'a, F>
|
struct HighlightIter<'a, F>
|
||||||
where
|
where
|
||||||
F: FnMut(&str) -> Option<&'a HighlightConfiguration> + 'a,
|
F: FnMut(&str) -> Option<&'a HighlightConfiguration> + 'a,
|
||||||
{
|
{
|
||||||
|
@ -822,16 +842,41 @@ where
|
||||||
byte_offset: usize,
|
byte_offset: usize,
|
||||||
injection_callback: F,
|
injection_callback: F,
|
||||||
cancellation_flag: Option<&'a AtomicUsize>,
|
cancellation_flag: Option<&'a AtomicUsize>,
|
||||||
layers: Vec<HighlightIterLayer<'a, 'tree>>,
|
layers: Vec<HighlightIterLayer<'a>>,
|
||||||
iter_count: usize,
|
iter_count: usize,
|
||||||
next_event: Option<HighlightEvent>,
|
next_event: Option<HighlightEvent>,
|
||||||
last_highlight_range: Option<(usize, usize, usize)>,
|
last_highlight_range: Option<(usize, usize, usize)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
struct HighlightIterLayer<'a, 'tree: 'a> {
|
// Adapter to convert rope chunks to bytes
|
||||||
|
struct ChunksBytes<'a> {
|
||||||
|
chunks: ropey::iter::Chunks<'a>,
|
||||||
|
}
|
||||||
|
impl<'a> Iterator for ChunksBytes<'a> {
|
||||||
|
type Item = &'a [u8];
|
||||||
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
|
self.chunks.next().map(str::as_bytes)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct RopeProvider<'a>(RopeSlice<'a>);
|
||||||
|
impl<'a> TextProvider<'a> for RopeProvider<'a> {
|
||||||
|
type I = ChunksBytes<'a>;
|
||||||
|
|
||||||
|
fn text(&mut self, node: Node) -> Self::I {
|
||||||
|
let start_char = self.0.byte_to_char(node.start_byte());
|
||||||
|
let end_char = self.0.byte_to_char(node.end_byte());
|
||||||
|
let fragment = self.0.slice(start_char..end_char);
|
||||||
|
ChunksBytes {
|
||||||
|
chunks: fragment.chunks(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct HighlightIterLayer<'a> {
|
||||||
_tree: Option<Tree>,
|
_tree: Option<Tree>,
|
||||||
cursor: QueryCursor,
|
cursor: QueryCursor,
|
||||||
captures: iter::Peekable<QueryCaptures<'a, 'tree, Cow<'a, [u8]>>>,
|
captures: iter::Peekable<QueryCaptures<'a, 'a, RopeProvider<'a>>>,
|
||||||
config: &'a HighlightConfiguration,
|
config: &'a HighlightConfiguration,
|
||||||
highlight_end_stack: Vec<usize>,
|
highlight_end_stack: Vec<usize>,
|
||||||
scope_stack: Vec<LocalScope<'a>>,
|
scope_stack: Vec<LocalScope<'a>>,
|
||||||
|
@ -839,7 +884,7 @@ struct HighlightIterLayer<'a, 'tree: 'a> {
|
||||||
depth: usize,
|
depth: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, 'tree: 'a> fmt::Debug for HighlightIterLayer<'a, 'tree> {
|
impl<'a> fmt::Debug for HighlightIterLayer<'a> {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
f.debug_struct("HighlightIterLayer").finish()
|
f.debug_struct("HighlightIterLayer").finish()
|
||||||
}
|
}
|
||||||
|
@ -1010,7 +1055,7 @@ impl HighlightConfiguration {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, 'tree: 'a> HighlightIterLayer<'a, 'tree> {
|
impl<'a> HighlightIterLayer<'a> {
|
||||||
/// Create a new 'layer' of highlighting for this document.
|
/// Create a new 'layer' of highlighting for this document.
|
||||||
///
|
///
|
||||||
/// In the even that the new layer contains "combined injections" (injections where multiple
|
/// In the even that the new layer contains "combined injections" (injections where multiple
|
||||||
|
@ -1067,10 +1112,7 @@ impl<'a, 'tree: 'a> HighlightIterLayer<'a, 'tree> {
|
||||||
let matches = cursor.matches(
|
let matches = cursor.matches(
|
||||||
combined_injections_query,
|
combined_injections_query,
|
||||||
tree.root_node(),
|
tree.root_node(),
|
||||||
|n: Node| {
|
RopeProvider(source),
|
||||||
// &source[n.byte_range()]
|
|
||||||
node_to_bytes(n, source)
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
for mat in matches {
|
for mat in matches {
|
||||||
let entry = &mut injections_by_pattern_index[mat.pattern_index];
|
let entry = &mut injections_by_pattern_index[mat.pattern_index];
|
||||||
|
@ -1117,10 +1159,7 @@ impl<'a, 'tree: 'a> HighlightIterLayer<'a, 'tree> {
|
||||||
let cursor_ref =
|
let cursor_ref =
|
||||||
unsafe { mem::transmute::<_, &'static mut QueryCursor>(&mut cursor) };
|
unsafe { mem::transmute::<_, &'static mut QueryCursor>(&mut cursor) };
|
||||||
let captures = cursor_ref
|
let captures = cursor_ref
|
||||||
.captures(&config.query, tree_ref.root_node(), move |n: Node| {
|
.captures(&config.query, tree_ref.root_node(), RopeProvider(source))
|
||||||
// &source[n.byte_range()]
|
|
||||||
node_to_bytes(n, source)
|
|
||||||
})
|
|
||||||
.peekable();
|
.peekable();
|
||||||
|
|
||||||
result.push(HighlightIterLayer {
|
result.push(HighlightIterLayer {
|
||||||
|
@ -1274,7 +1313,7 @@ impl<'a, 'tree: 'a> HighlightIterLayer<'a, 'tree> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, 'tree: 'a, F> HighlightIter<'a, 'tree, F>
|
impl<'a, F> HighlightIter<'a, F>
|
||||||
where
|
where
|
||||||
F: FnMut(&str) -> Option<&'a HighlightConfiguration> + 'a,
|
F: FnMut(&str) -> Option<&'a HighlightConfiguration> + 'a,
|
||||||
{
|
{
|
||||||
|
@ -1325,7 +1364,7 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn insert_layer(&mut self, mut layer: HighlightIterLayer<'a, 'tree>) {
|
fn insert_layer(&mut self, mut layer: HighlightIterLayer<'a>) {
|
||||||
if let Some(sort_key) = layer.sort_key() {
|
if let Some(sort_key) = layer.sort_key() {
|
||||||
let mut i = 1;
|
let mut i = 1;
|
||||||
while i < self.layers.len() {
|
while i < self.layers.len() {
|
||||||
|
@ -1344,7 +1383,7 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, 'tree: 'a, F> Iterator for HighlightIter<'a, 'tree, F>
|
impl<'a, F> Iterator for HighlightIter<'a, F>
|
||||||
where
|
where
|
||||||
F: FnMut(&str) -> Option<&'a HighlightConfiguration> + 'a,
|
F: FnMut(&str) -> Option<&'a HighlightConfiguration> + 'a,
|
||||||
{
|
{
|
||||||
|
@ -1608,7 +1647,7 @@ where
|
||||||
fn injection_for_match<'a>(
|
fn injection_for_match<'a>(
|
||||||
config: &HighlightConfiguration,
|
config: &HighlightConfiguration,
|
||||||
query: &'a Query,
|
query: &'a Query,
|
||||||
query_match: &QueryMatch<'a>,
|
query_match: &QueryMatch<'a, 'a>,
|
||||||
source: RopeSlice<'a>,
|
source: RopeSlice<'a>,
|
||||||
) -> (Option<Cow<'a, str>>, Option<Node<'a>>, bool) {
|
) -> (Option<Cow<'a, str>>, Option<Node<'a>>, bool) {
|
||||||
let content_capture_index = config.injection_content_capture_index;
|
let content_capture_index = config.injection_content_capture_index;
|
||||||
|
|
|
@ -23,5 +23,5 @@ lsp-types = { version = "0.89", 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.10", features = ["rt", "rt-multi-thread", "io-util", "io-std", "time", "process", "macros", "fs", "parking_lot", "sync"] }
|
tokio = { version = "1.11", features = ["rt", "rt-multi-thread", "io-util", "io-std", "time", "process", "macros", "fs", "parking_lot", "sync"] }
|
||||||
tokio-stream = "0.1"
|
tokio-stream = "0.1"
|
||||||
|
|
|
@ -3,17 +3,23 @@ use crate::{
|
||||||
Call, Error, OffsetEncoding, Result,
|
Call, Error, OffsetEncoding, Result,
|
||||||
};
|
};
|
||||||
|
|
||||||
use helix_core::{chars::char_is_line_ending, find_root, ChangeSet, Rope};
|
use helix_core::{find_root, ChangeSet, Rope};
|
||||||
use jsonrpc_core as jsonrpc;
|
use jsonrpc_core as jsonrpc;
|
||||||
use lsp_types as lsp;
|
use lsp_types as lsp;
|
||||||
use serde_json::Value;
|
use serde_json::Value;
|
||||||
use std::future::Future;
|
use std::future::Future;
|
||||||
use std::process::Stdio;
|
use std::process::Stdio;
|
||||||
use std::sync::atomic::{AtomicU64, Ordering};
|
use std::sync::{
|
||||||
|
atomic::{AtomicU64, Ordering},
|
||||||
|
Arc,
|
||||||
|
};
|
||||||
use tokio::{
|
use tokio::{
|
||||||
io::{BufReader, BufWriter},
|
io::{BufReader, BufWriter},
|
||||||
process::{Child, Command},
|
process::{Child, Command},
|
||||||
sync::mpsc::{channel, UnboundedReceiver, UnboundedSender},
|
sync::{
|
||||||
|
mpsc::{channel, UnboundedReceiver, UnboundedSender},
|
||||||
|
Notify, OnceCell,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
@ -22,18 +28,19 @@ pub struct Client {
|
||||||
_process: Child,
|
_process: Child,
|
||||||
server_tx: UnboundedSender<Payload>,
|
server_tx: UnboundedSender<Payload>,
|
||||||
request_counter: AtomicU64,
|
request_counter: AtomicU64,
|
||||||
capabilities: Option<lsp::ServerCapabilities>,
|
pub(crate) capabilities: OnceCell<lsp::ServerCapabilities>,
|
||||||
offset_encoding: OffsetEncoding,
|
offset_encoding: OffsetEncoding,
|
||||||
config: Option<Value>,
|
config: Option<Value>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Client {
|
impl Client {
|
||||||
|
#[allow(clippy::type_complexity)]
|
||||||
pub fn start(
|
pub fn start(
|
||||||
cmd: &str,
|
cmd: &str,
|
||||||
args: &[String],
|
args: &[String],
|
||||||
config: Option<Value>,
|
config: Option<Value>,
|
||||||
id: usize,
|
id: usize,
|
||||||
) -> Result<(Self, UnboundedReceiver<(usize, Call)>)> {
|
) -> Result<(Self, UnboundedReceiver<(usize, Call)>, Arc<Notify>)> {
|
||||||
let process = Command::new(cmd)
|
let process = Command::new(cmd)
|
||||||
.args(args)
|
.args(args)
|
||||||
.stdin(Stdio::piped())
|
.stdin(Stdio::piped())
|
||||||
|
@ -50,22 +57,20 @@ impl Client {
|
||||||
let reader = BufReader::new(process.stdout.take().expect("Failed to open stdout"));
|
let reader = BufReader::new(process.stdout.take().expect("Failed to open stdout"));
|
||||||
let stderr = BufReader::new(process.stderr.take().expect("Failed to open stderr"));
|
let stderr = BufReader::new(process.stderr.take().expect("Failed to open stderr"));
|
||||||
|
|
||||||
let (server_rx, server_tx) = Transport::start(reader, writer, stderr, id);
|
let (server_rx, server_tx, initialize_notify) =
|
||||||
|
Transport::start(reader, writer, stderr, id);
|
||||||
|
|
||||||
let client = Self {
|
let client = Self {
|
||||||
id,
|
id,
|
||||||
_process: process,
|
_process: process,
|
||||||
server_tx,
|
server_tx,
|
||||||
request_counter: AtomicU64::new(0),
|
request_counter: AtomicU64::new(0),
|
||||||
capabilities: None,
|
capabilities: OnceCell::new(),
|
||||||
offset_encoding: OffsetEncoding::Utf8,
|
offset_encoding: OffsetEncoding::Utf8,
|
||||||
config,
|
config,
|
||||||
};
|
};
|
||||||
|
|
||||||
// TODO: async client.initialize()
|
Ok((client, server_rx, initialize_notify))
|
||||||
// maybe use an arc<atomic> flag
|
|
||||||
|
|
||||||
Ok((client, server_rx))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn id(&self) -> usize {
|
pub fn id(&self) -> usize {
|
||||||
|
@ -88,9 +93,13 @@ impl Client {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn is_initialized(&self) -> bool {
|
||||||
|
self.capabilities.get().is_some()
|
||||||
|
}
|
||||||
|
|
||||||
pub fn capabilities(&self) -> &lsp::ServerCapabilities {
|
pub fn capabilities(&self) -> &lsp::ServerCapabilities {
|
||||||
self.capabilities
|
self.capabilities
|
||||||
.as_ref()
|
.get()
|
||||||
.expect("language server not yet initialized!")
|
.expect("language server not yet initialized!")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -143,7 +152,8 @@ impl Client {
|
||||||
})
|
})
|
||||||
.map_err(|e| Error::Other(e.into()))?;
|
.map_err(|e| Error::Other(e.into()))?;
|
||||||
|
|
||||||
timeout(Duration::from_secs(2), rx.recv())
|
// TODO: specifiable timeout, delay other calls until initialize success
|
||||||
|
timeout(Duration::from_secs(20), rx.recv())
|
||||||
.await
|
.await
|
||||||
.map_err(|_| Error::Timeout)? // return Timeout
|
.map_err(|_| Error::Timeout)? // return Timeout
|
||||||
.ok_or(Error::StreamClosed)?
|
.ok_or(Error::StreamClosed)?
|
||||||
|
@ -151,7 +161,7 @@ impl Client {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Send a RPC notification to the language server.
|
/// Send a RPC notification to the language server.
|
||||||
fn notify<R: lsp::notification::Notification>(
|
pub fn notify<R: lsp::notification::Notification>(
|
||||||
&self,
|
&self,
|
||||||
params: R::Params,
|
params: R::Params,
|
||||||
) -> impl Future<Output = Result<()>>
|
) -> impl Future<Output = Result<()>>
|
||||||
|
@ -213,7 +223,7 @@ impl Client {
|
||||||
// General messages
|
// General messages
|
||||||
// -------------------------------------------------------------------------------------------
|
// -------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
pub(crate) async fn initialize(&mut self) -> Result<()> {
|
pub(crate) async fn initialize(&self) -> Result<lsp::InitializeResult> {
|
||||||
// TODO: delay any requests that are triggered prior to initialize
|
// TODO: delay any requests that are triggered prior to initialize
|
||||||
let root = find_root(None).and_then(|root| lsp::Url::from_file_path(root).ok());
|
let root = find_root(None).and_then(|root| lsp::Url::from_file_path(root).ok());
|
||||||
|
|
||||||
|
@ -281,14 +291,7 @@ impl Client {
|
||||||
locale: None, // TODO
|
locale: None, // TODO
|
||||||
};
|
};
|
||||||
|
|
||||||
let response = self.request::<lsp::request::Initialize>(params).await?;
|
self.request::<lsp::request::Initialize>(params).await
|
||||||
self.capabilities = Some(response.capabilities);
|
|
||||||
|
|
||||||
// next up, notify<initialized>
|
|
||||||
self.notify::<lsp::notification::Initialized>(lsp::InitializedParams {})
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn shutdown(&self) -> Result<()> {
|
pub async fn shutdown(&self) -> Result<()> {
|
||||||
|
@ -356,7 +359,6 @@ impl Client {
|
||||||
//
|
//
|
||||||
// Calculation is therefore a bunch trickier.
|
// Calculation is therefore a bunch trickier.
|
||||||
|
|
||||||
// TODO: stolen from syntax.rs, share
|
|
||||||
use helix_core::RopeSlice;
|
use helix_core::RopeSlice;
|
||||||
fn traverse(pos: lsp::Position, text: RopeSlice) -> lsp::Position {
|
fn traverse(pos: lsp::Position, text: RopeSlice) -> lsp::Position {
|
||||||
let lsp::Position {
|
let lsp::Position {
|
||||||
|
@ -366,7 +368,12 @@ impl Client {
|
||||||
|
|
||||||
let mut chars = text.chars().peekable();
|
let mut chars = text.chars().peekable();
|
||||||
while let Some(ch) = chars.next() {
|
while let Some(ch) = chars.next() {
|
||||||
if char_is_line_ending(ch) && !(ch == '\r' && chars.peek() == Some(&'\n')) {
|
// LSP only considers \n, \r or \r\n as line endings
|
||||||
|
if ch == '\n' || ch == '\r' {
|
||||||
|
// consume a \r\n
|
||||||
|
if ch == '\r' && chars.peek() == Some(&'\n') {
|
||||||
|
chars.next();
|
||||||
|
}
|
||||||
line += 1;
|
line += 1;
|
||||||
character = 0;
|
character = 0;
|
||||||
} else {
|
} else {
|
||||||
|
@ -441,7 +448,7 @@ impl Client {
|
||||||
) -> Option<impl Future<Output = Result<()>>> {
|
) -> Option<impl Future<Output = Result<()>>> {
|
||||||
// figure out what kind of sync the server supports
|
// figure out what kind of sync the server supports
|
||||||
|
|
||||||
let capabilities = self.capabilities.as_ref().unwrap();
|
let capabilities = self.capabilities.get().unwrap();
|
||||||
|
|
||||||
let sync_capabilities = match capabilities.text_document_sync {
|
let sync_capabilities = match capabilities.text_document_sync {
|
||||||
Some(lsp::TextDocumentSyncCapability::Kind(kind))
|
Some(lsp::TextDocumentSyncCapability::Kind(kind))
|
||||||
|
@ -459,7 +466,7 @@ impl Client {
|
||||||
// range = None -> whole document
|
// range = None -> whole document
|
||||||
range: None, //Some(Range)
|
range: None, //Some(Range)
|
||||||
range_length: None, // u64 apparently deprecated
|
range_length: None, // u64 apparently deprecated
|
||||||
text: "".to_string(),
|
text: new_text.to_string(),
|
||||||
}]
|
}]
|
||||||
}
|
}
|
||||||
lsp::TextDocumentSyncKind::Incremental => {
|
lsp::TextDocumentSyncKind::Incremental => {
|
||||||
|
@ -487,12 +494,12 @@ impl Client {
|
||||||
|
|
||||||
// will_save / will_save_wait_until
|
// will_save / will_save_wait_until
|
||||||
|
|
||||||
pub async fn text_document_did_save(
|
pub fn text_document_did_save(
|
||||||
&self,
|
&self,
|
||||||
text_document: lsp::TextDocumentIdentifier,
|
text_document: lsp::TextDocumentIdentifier,
|
||||||
text: &Rope,
|
text: &Rope,
|
||||||
) -> Result<()> {
|
) -> Option<impl Future<Output = Result<()>>> {
|
||||||
let capabilities = self.capabilities.as_ref().unwrap();
|
let capabilities = self.capabilities.get().unwrap();
|
||||||
|
|
||||||
let include_text = match &capabilities.text_document_sync {
|
let include_text = match &capabilities.text_document_sync {
|
||||||
Some(lsp::TextDocumentSyncCapability::Options(lsp::TextDocumentSyncOptions {
|
Some(lsp::TextDocumentSyncCapability::Options(lsp::TextDocumentSyncOptions {
|
||||||
|
@ -504,17 +511,18 @@ impl Client {
|
||||||
include_text,
|
include_text,
|
||||||
}) => include_text.unwrap_or(false),
|
}) => include_text.unwrap_or(false),
|
||||||
// Supported(false)
|
// Supported(false)
|
||||||
_ => return Ok(()),
|
_ => return None,
|
||||||
},
|
},
|
||||||
// unsupported
|
// unsupported
|
||||||
_ => return Ok(()),
|
_ => return None,
|
||||||
};
|
};
|
||||||
|
|
||||||
self.notify::<lsp::notification::DidSaveTextDocument>(lsp::DidSaveTextDocumentParams {
|
Some(self.notify::<lsp::notification::DidSaveTextDocument>(
|
||||||
|
lsp::DidSaveTextDocumentParams {
|
||||||
text_document,
|
text_document,
|
||||||
text: include_text.then(|| text.into()),
|
text: include_text.then(|| text.into()),
|
||||||
})
|
},
|
||||||
.await
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn completion(
|
pub fn completion(
|
||||||
|
@ -580,19 +588,19 @@ impl Client {
|
||||||
|
|
||||||
// formatting
|
// formatting
|
||||||
|
|
||||||
pub async fn text_document_formatting(
|
pub fn text_document_formatting(
|
||||||
&self,
|
&self,
|
||||||
text_document: lsp::TextDocumentIdentifier,
|
text_document: lsp::TextDocumentIdentifier,
|
||||||
options: lsp::FormattingOptions,
|
options: lsp::FormattingOptions,
|
||||||
work_done_token: Option<lsp::ProgressToken>,
|
work_done_token: Option<lsp::ProgressToken>,
|
||||||
) -> anyhow::Result<Vec<lsp::TextEdit>> {
|
) -> Option<impl Future<Output = Result<Vec<lsp::TextEdit>>>> {
|
||||||
let capabilities = self.capabilities.as_ref().unwrap();
|
let capabilities = self.capabilities.get().unwrap();
|
||||||
|
|
||||||
// check if we're able to format
|
// check if we're able to format
|
||||||
match capabilities.document_formatting_provider {
|
match capabilities.document_formatting_provider {
|
||||||
Some(lsp::OneOf::Left(true)) | Some(lsp::OneOf::Right(_)) => (),
|
Some(lsp::OneOf::Left(true)) | Some(lsp::OneOf::Right(_)) => (),
|
||||||
// None | Some(false)
|
// None | Some(false)
|
||||||
_ => return Ok(Vec::new()),
|
_ => return None,
|
||||||
};
|
};
|
||||||
// TODO: return err::unavailable so we can fall back to tree sitter formatting
|
// TODO: return err::unavailable so we can fall back to tree sitter formatting
|
||||||
|
|
||||||
|
@ -602,9 +610,13 @@ impl Client {
|
||||||
work_done_progress_params: lsp::WorkDoneProgressParams { work_done_token },
|
work_done_progress_params: lsp::WorkDoneProgressParams { work_done_token },
|
||||||
};
|
};
|
||||||
|
|
||||||
let response = self.request::<lsp::request::Formatting>(params).await?;
|
let request = self.call::<lsp::request::Formatting>(params);
|
||||||
|
|
||||||
|
Some(async move {
|
||||||
|
let json = request.await?;
|
||||||
|
let response: Option<Vec<lsp::TextEdit>> = serde_json::from_value(json)?;
|
||||||
Ok(response.unwrap_or_default())
|
Ok(response.unwrap_or_default())
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn text_document_range_formatting(
|
pub async fn text_document_range_formatting(
|
||||||
|
@ -614,7 +626,7 @@ impl Client {
|
||||||
options: lsp::FormattingOptions,
|
options: lsp::FormattingOptions,
|
||||||
work_done_token: Option<lsp::ProgressToken>,
|
work_done_token: Option<lsp::ProgressToken>,
|
||||||
) -> anyhow::Result<Vec<lsp::TextEdit>> {
|
) -> anyhow::Result<Vec<lsp::TextEdit>> {
|
||||||
let capabilities = self.capabilities.as_ref().unwrap();
|
let capabilities = self.capabilities.get().unwrap();
|
||||||
|
|
||||||
// check if we're able to format
|
// check if we're able to format
|
||||||
match capabilities.document_range_formatting_provider {
|
match capabilities.document_range_formatting_provider {
|
||||||
|
|
|
@ -226,6 +226,8 @@ impl MethodCall {
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Clone)]
|
#[derive(Debug, PartialEq, Clone)]
|
||||||
pub enum Notification {
|
pub enum Notification {
|
||||||
|
// we inject this notification to signal the LSP is ready
|
||||||
|
Initialized,
|
||||||
PublishDiagnostics(lsp::PublishDiagnosticsParams),
|
PublishDiagnostics(lsp::PublishDiagnosticsParams),
|
||||||
ShowMessage(lsp::ShowMessageParams),
|
ShowMessage(lsp::ShowMessageParams),
|
||||||
LogMessage(lsp::LogMessageParams),
|
LogMessage(lsp::LogMessageParams),
|
||||||
|
@ -237,6 +239,7 @@ impl Notification {
|
||||||
use lsp::notification::Notification as _;
|
use lsp::notification::Notification as _;
|
||||||
|
|
||||||
let notification = match method {
|
let notification = match method {
|
||||||
|
lsp::notification::Initialized::METHOD => Self::Initialized,
|
||||||
lsp::notification::PublishDiagnostics::METHOD => {
|
lsp::notification::PublishDiagnostics::METHOD => {
|
||||||
let params: lsp::PublishDiagnosticsParams = params
|
let params: lsp::PublishDiagnosticsParams = params
|
||||||
.parse()
|
.parse()
|
||||||
|
@ -294,7 +297,7 @@ impl Registry {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_by_id(&mut self, id: usize) -> Option<&Client> {
|
pub fn get_by_id(&self, id: usize) -> Option<&Client> {
|
||||||
self.inner
|
self.inner
|
||||||
.values()
|
.values()
|
||||||
.find(|(client_id, _)| client_id == &id)
|
.find(|(client_id, _)| client_id == &id)
|
||||||
|
@ -302,34 +305,61 @@ impl Registry {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get(&mut self, language_config: &LanguageConfiguration) -> Result<Arc<Client>> {
|
pub fn get(&mut self, language_config: &LanguageConfiguration) -> Result<Arc<Client>> {
|
||||||
if let Some(config) = &language_config.language_server {
|
let config = match &language_config.language_server {
|
||||||
// avoid borrow issues
|
Some(config) => config,
|
||||||
let inner = &mut self.inner;
|
None => return Err(Error::LspNotDefined),
|
||||||
let s_incoming = &mut self.incoming;
|
};
|
||||||
|
|
||||||
match inner.entry(language_config.scope.clone()) {
|
match self.inner.entry(language_config.scope.clone()) {
|
||||||
Entry::Occupied(entry) => Ok(entry.get().1.clone()),
|
Entry::Occupied(entry) => Ok(entry.get().1.clone()),
|
||||||
Entry::Vacant(entry) => {
|
Entry::Vacant(entry) => {
|
||||||
// initialize a new client
|
// initialize a new client
|
||||||
let id = self.counter.fetch_add(1, Ordering::Relaxed);
|
let id = self.counter.fetch_add(1, Ordering::Relaxed);
|
||||||
let (mut client, incoming) = Client::start(
|
let (client, incoming, initialize_notify) = Client::start(
|
||||||
&config.command,
|
&config.command,
|
||||||
&config.args,
|
&config.args,
|
||||||
serde_json::from_str(language_config.config.as_deref().unwrap_or("")).ok(),
|
serde_json::from_str(language_config.config.as_deref().unwrap_or(""))
|
||||||
|
.map_err(|e| {
|
||||||
|
log::error!(
|
||||||
|
"LSP Config, {}, in `languages.toml` for `{}`",
|
||||||
|
e,
|
||||||
|
language_config.scope()
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.ok(),
|
||||||
id,
|
id,
|
||||||
)?;
|
)?;
|
||||||
// TODO: run this async without blocking
|
self.incoming.push(UnboundedReceiverStream::new(incoming));
|
||||||
futures_executor::block_on(client.initialize())?;
|
|
||||||
s_incoming.push(UnboundedReceiverStream::new(incoming));
|
|
||||||
let client = Arc::new(client);
|
let client = Arc::new(client);
|
||||||
|
|
||||||
|
// Initialize the client asynchronously
|
||||||
|
let _client = client.clone();
|
||||||
|
tokio::spawn(async move {
|
||||||
|
use futures_util::TryFutureExt;
|
||||||
|
let value = _client
|
||||||
|
.capabilities
|
||||||
|
.get_or_try_init(|| {
|
||||||
|
_client
|
||||||
|
.initialize()
|
||||||
|
.map_ok(|response| response.capabilities)
|
||||||
|
})
|
||||||
|
.await;
|
||||||
|
|
||||||
|
value.expect("failed to initialize capabilities");
|
||||||
|
|
||||||
|
// next up, notify<initialized>
|
||||||
|
_client
|
||||||
|
.notify::<lsp::notification::Initialized>(lsp::InitializedParams {})
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
initialize_notify.notify_one();
|
||||||
|
});
|
||||||
|
|
||||||
entry.insert((id, client.clone()));
|
entry.insert((id, client.clone()));
|
||||||
Ok(client)
|
Ok(client)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
Err(Error::LspNotDefined)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn iter_clients(&self) -> impl Iterator<Item = &Arc<Client>> {
|
pub fn iter_clients(&self) -> impl Iterator<Item = &Arc<Client>> {
|
||||||
|
@ -415,32 +445,6 @@ impl LspProgressMap {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// REGISTRY = HashMap<LanguageId, Lazy/OnceCell<Arc<RwLock<Client>>>
|
|
||||||
// spawn one server per language type, need to spawn one per workspace if server doesn't support
|
|
||||||
// workspaces
|
|
||||||
//
|
|
||||||
// could also be a client per root dir
|
|
||||||
//
|
|
||||||
// storing a copy of Option<Arc<RwLock<Client>>> on Document would make the LSP client easily
|
|
||||||
// accessible during edit/save callbacks
|
|
||||||
//
|
|
||||||
// the event loop needs to process all incoming streams, maybe we can just have that be a separate
|
|
||||||
// task that's continually running and store the state on the client, then use read lock to
|
|
||||||
// retrieve data during render
|
|
||||||
// -> PROBLEM: how do you trigger an update on the editor side when data updates?
|
|
||||||
//
|
|
||||||
// -> The data updates should pull all events until we run out so we don't frequently re-render
|
|
||||||
//
|
|
||||||
//
|
|
||||||
// v2:
|
|
||||||
//
|
|
||||||
// there should be a registry of lsp clients, one per language type (or workspace).
|
|
||||||
// the clients should lazy init on first access
|
|
||||||
// the client.initialize() should be called async and we buffer any requests until that completes
|
|
||||||
// there needs to be a way to process incoming lsp messages from all clients.
|
|
||||||
// -> notifications need to be dispatched to wherever
|
|
||||||
// -> requests need to generate a reply and travel back to the same lsp!
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::{lsp, util::*, OffsetEncoding};
|
use super::{lsp, util::*, OffsetEncoding};
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use crate::Result;
|
use crate::{Error, Result};
|
||||||
use anyhow::Context;
|
use anyhow::Context;
|
||||||
use jsonrpc_core as jsonrpc;
|
use jsonrpc_core as jsonrpc;
|
||||||
use log::{debug, error, info, warn};
|
use log::{error, info};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use serde_json::Value;
|
use serde_json::Value;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
@ -11,7 +11,7 @@ use tokio::{
|
||||||
process::{ChildStderr, ChildStdin, ChildStdout},
|
process::{ChildStderr, ChildStdin, ChildStdout},
|
||||||
sync::{
|
sync::{
|
||||||
mpsc::{unbounded_channel, Sender, UnboundedReceiver, UnboundedSender},
|
mpsc::{unbounded_channel, Sender, UnboundedReceiver, UnboundedSender},
|
||||||
Mutex,
|
Mutex, Notify,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -51,9 +51,11 @@ impl Transport {
|
||||||
) -> (
|
) -> (
|
||||||
UnboundedReceiver<(usize, jsonrpc::Call)>,
|
UnboundedReceiver<(usize, jsonrpc::Call)>,
|
||||||
UnboundedSender<Payload>,
|
UnboundedSender<Payload>,
|
||||||
|
Arc<Notify>,
|
||||||
) {
|
) {
|
||||||
let (client_tx, rx) = unbounded_channel();
|
let (client_tx, rx) = unbounded_channel();
|
||||||
let (tx, client_rx) = unbounded_channel();
|
let (tx, client_rx) = unbounded_channel();
|
||||||
|
let notify = Arc::new(Notify::new());
|
||||||
|
|
||||||
let transport = Self {
|
let transport = Self {
|
||||||
id,
|
id,
|
||||||
|
@ -62,11 +64,21 @@ impl Transport {
|
||||||
|
|
||||||
let transport = Arc::new(transport);
|
let transport = Arc::new(transport);
|
||||||
|
|
||||||
tokio::spawn(Self::recv(transport.clone(), server_stdout, client_tx));
|
tokio::spawn(Self::recv(
|
||||||
|
transport.clone(),
|
||||||
|
server_stdout,
|
||||||
|
client_tx.clone(),
|
||||||
|
));
|
||||||
tokio::spawn(Self::err(transport.clone(), server_stderr));
|
tokio::spawn(Self::err(transport.clone(), server_stderr));
|
||||||
tokio::spawn(Self::send(transport, server_stdin, client_rx));
|
tokio::spawn(Self::send(
|
||||||
|
transport,
|
||||||
|
server_stdin,
|
||||||
|
client_tx,
|
||||||
|
client_rx,
|
||||||
|
notify.clone(),
|
||||||
|
));
|
||||||
|
|
||||||
(rx, tx)
|
(rx, tx, notify)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn recv_server_message(
|
async fn recv_server_message(
|
||||||
|
@ -76,14 +88,18 @@ impl Transport {
|
||||||
let mut content_length = None;
|
let mut content_length = None;
|
||||||
loop {
|
loop {
|
||||||
buffer.truncate(0);
|
buffer.truncate(0);
|
||||||
reader.read_line(buffer).await?;
|
if reader.read_line(buffer).await? == 0 {
|
||||||
let header = buffer.trim();
|
return Err(Error::StreamClosed);
|
||||||
|
};
|
||||||
|
|
||||||
if header.is_empty() {
|
// debug!("<- header {:?}", buffer);
|
||||||
|
|
||||||
|
if buffer == "\r\n" {
|
||||||
|
// look for an empty CRLF line
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
debug!("<- header {}", header);
|
let header = buffer.trim();
|
||||||
|
|
||||||
let parts = header.split_once(": ");
|
let parts = header.split_once(": ");
|
||||||
|
|
||||||
|
@ -96,7 +112,8 @@ impl Transport {
|
||||||
// Workaround: Some non-conformant language servers will output logging and other garbage
|
// Workaround: Some non-conformant language servers will output logging and other garbage
|
||||||
// into the same stream as JSON-RPC messages. This can also happen from shell scripts that spawn
|
// into the same stream as JSON-RPC messages. This can also happen from shell scripts that spawn
|
||||||
// the server. Skip such lines and log a warning.
|
// the server. Skip such lines and log a warning.
|
||||||
warn!("Failed to parse header: {:?}", header);
|
|
||||||
|
// warn!("Failed to parse header: {:?}", header);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -121,8 +138,10 @@ impl Transport {
|
||||||
buffer: &mut String,
|
buffer: &mut String,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
buffer.truncate(0);
|
buffer.truncate(0);
|
||||||
err.read_line(buffer).await?;
|
if err.read_line(buffer).await? == 0 {
|
||||||
error!("err <- {}", buffer);
|
return Err(Error::StreamClosed);
|
||||||
|
};
|
||||||
|
error!("err <- {:?}", buffer);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -255,13 +274,61 @@ impl Transport {
|
||||||
async fn send(
|
async fn send(
|
||||||
transport: Arc<Self>,
|
transport: Arc<Self>,
|
||||||
mut server_stdin: BufWriter<ChildStdin>,
|
mut server_stdin: BufWriter<ChildStdin>,
|
||||||
|
client_tx: UnboundedSender<(usize, jsonrpc::Call)>,
|
||||||
mut client_rx: UnboundedReceiver<Payload>,
|
mut client_rx: UnboundedReceiver<Payload>,
|
||||||
|
initialize_notify: Arc<Notify>,
|
||||||
) {
|
) {
|
||||||
while let Some(msg) = client_rx.recv().await {
|
let mut pending_messages: Vec<Payload> = Vec::new();
|
||||||
match transport
|
let mut is_pending = true;
|
||||||
.send_payload_to_server(&mut server_stdin, msg)
|
|
||||||
.await
|
// Determine if a message is allowed to be sent early
|
||||||
|
fn is_initialize(payload: &Payload) -> bool {
|
||||||
|
use lsp_types::{
|
||||||
|
notification::{Initialized, Notification},
|
||||||
|
request::{Initialize, Request},
|
||||||
|
};
|
||||||
|
match payload {
|
||||||
|
Payload::Request {
|
||||||
|
value: jsonrpc::MethodCall { method, .. },
|
||||||
|
..
|
||||||
|
} if method == Initialize::METHOD => true,
|
||||||
|
Payload::Notification(jsonrpc::Notification { method, .. })
|
||||||
|
if method == Initialized::METHOD =>
|
||||||
{
|
{
|
||||||
|
true
|
||||||
|
}
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: events that use capabilities need to do the right thing
|
||||||
|
|
||||||
|
loop {
|
||||||
|
tokio::select! {
|
||||||
|
biased;
|
||||||
|
_ = initialize_notify.notified() => { // TODO: notified is technically not cancellation safe
|
||||||
|
// server successfully initialized
|
||||||
|
is_pending = false;
|
||||||
|
|
||||||
|
use lsp_types::notification::Notification;
|
||||||
|
// Hack: inject an initialized notification so we trigger code that needs to happen after init
|
||||||
|
let notification = ServerMessage::Call(jsonrpc::Call::Notification(jsonrpc::Notification {
|
||||||
|
jsonrpc: None,
|
||||||
|
|
||||||
|
method: lsp_types::notification::Initialized::METHOD.to_string(),
|
||||||
|
params: jsonrpc::Params::None,
|
||||||
|
}));
|
||||||
|
match transport.process_server_message(&client_tx, notification).await {
|
||||||
|
Ok(_) => {}
|
||||||
|
Err(err) => {
|
||||||
|
error!("err: <- {:?}", err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// drain the pending queue and send payloads to server
|
||||||
|
for msg in pending_messages.drain(..) {
|
||||||
|
log::info!("Draining pending message {:?}", msg);
|
||||||
|
match transport.send_payload_to_server(&mut server_stdin, msg).await {
|
||||||
Ok(_) => {}
|
Ok(_) => {}
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
error!("err: <- {:?}", err);
|
error!("err: <- {:?}", err);
|
||||||
|
@ -269,4 +336,30 @@ impl Transport {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
msg = client_rx.recv() => {
|
||||||
|
if let Some(msg) = msg {
|
||||||
|
if is_pending && !is_initialize(&msg) {
|
||||||
|
// ignore notifications
|
||||||
|
if let Payload::Notification(_) = msg {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
log::info!("Language server not initialized, delaying request");
|
||||||
|
pending_messages.push(msg);
|
||||||
|
} else {
|
||||||
|
match transport.send_payload_to_server(&mut server_stdin, msg).await {
|
||||||
|
Ok(_) => {}
|
||||||
|
Err(err) => {
|
||||||
|
error!("err: <- {:?}", err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// channel closed
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,7 +11,7 @@ homepage = "https://helix-editor.com"
|
||||||
include = ["src/**/*", "languages/**/*", "build.rs", "!**/docs/**/*", "!**/test/**/*", "!**/examples/**/*", "!**/build/**/*"]
|
include = ["src/**/*", "languages/**/*", "build.rs", "!**/docs/**/*", "!**/test/**/*", "!**/examples/**/*", "!**/build/**/*"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
tree-sitter = "0.19"
|
tree-sitter = "0.20"
|
||||||
libloading = "0.7"
|
libloading = "0.7"
|
||||||
anyhow = "1"
|
anyhow = "1"
|
||||||
|
|
||||||
|
|
|
@ -158,10 +158,9 @@ fn build_dir(dir: &str, language: &str) {
|
||||||
.is_none()
|
.is_none()
|
||||||
{
|
{
|
||||||
eprintln!(
|
eprintln!(
|
||||||
"The directory {} is empty, did you use 'git clone --recursive'?",
|
"The directory {} is empty, you probably need to use 'git submodule update --init --recursive'?",
|
||||||
dir
|
dir
|
||||||
);
|
);
|
||||||
eprintln!("You can fix in using 'git submodule init && git submodule update --recursive'.");
|
|
||||||
std::process::exit(1);
|
std::process::exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
Subproject commit 0ba7a24b062b671263ae08e707e9e94383b25bb7
|
Subproject commit 12ea597262125fc22fd2e91aa953ac69b19c26ca
|
|
@ -1 +1 @@
|
||||||
Subproject commit 72319504776f14193472a6ad14abec0af0225cbe
|
Subproject commit 0cdeb0e51411a3ba5493662952c3039de08939ca
|
|
@ -0,0 +1 @@
|
||||||
|
Subproject commit 349a5984513b4a4a9e143a6e746120c6ff6cf6ed
|
|
@ -56,5 +56,9 @@ toml = "0.5"
|
||||||
serde_json = "1.0"
|
serde_json = "1.0"
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
|
|
||||||
|
# ripgrep for global search
|
||||||
|
grep-regex = "0.1.9"
|
||||||
|
grep-searcher = "0.1.8"
|
||||||
|
|
||||||
[target.'cfg(not(windows))'.dependencies] # https://github.com/vorner/signal-hook/issues/100
|
[target.'cfg(not(windows))'.dependencies] # https://github.com/vorner/signal-hook/issues/100
|
||||||
signal-hook-tokio = { version = "0.3", features = ["futures-v0_3"] }
|
signal-hook-tokio = { version = "0.3", features = ["futures-v0_3"] }
|
||||||
|
|
|
@ -434,16 +434,42 @@ impl Application {
|
||||||
};
|
};
|
||||||
|
|
||||||
match notification {
|
match notification {
|
||||||
|
Notification::Initialized => {
|
||||||
|
let language_server =
|
||||||
|
match self.editor.language_servers.get_by_id(server_id) {
|
||||||
|
Some(language_server) => language_server,
|
||||||
|
None => {
|
||||||
|
warn!("can't find language server with id `{}`", server_id);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let docs = self.editor.documents().filter(|doc| {
|
||||||
|
doc.language_server().map(|server| server.id()) == Some(server_id)
|
||||||
|
});
|
||||||
|
|
||||||
|
// trigger textDocument/didOpen for docs that are already open
|
||||||
|
for doc in docs {
|
||||||
|
// TODO: extract and share with editor.open
|
||||||
|
let language_id = doc
|
||||||
|
.language()
|
||||||
|
.and_then(|s| s.split('.').last()) // source.rust
|
||||||
|
.map(ToOwned::to_owned)
|
||||||
|
.unwrap_or_default();
|
||||||
|
|
||||||
|
tokio::spawn(language_server.text_document_did_open(
|
||||||
|
doc.url().unwrap(),
|
||||||
|
doc.version(),
|
||||||
|
doc.text(),
|
||||||
|
language_id,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
Notification::PublishDiagnostics(params) => {
|
Notification::PublishDiagnostics(params) => {
|
||||||
let path = Some(params.uri.to_file_path().unwrap());
|
let path = params.uri.to_file_path().unwrap();
|
||||||
|
let doc = self.editor.document_by_path_mut(&path);
|
||||||
|
|
||||||
let doc = self
|
if let Some(doc) = doc {
|
||||||
.editor
|
|
||||||
.documents
|
|
||||||
.iter_mut()
|
|
||||||
.find(|(_, doc)| doc.path() == path.as_ref());
|
|
||||||
|
|
||||||
if let Some((_, doc)) = doc {
|
|
||||||
let text = doc.text();
|
let text = doc.text();
|
||||||
|
|
||||||
let diagnostics = params
|
let diagnostics = params
|
||||||
|
@ -506,7 +532,7 @@ impl Application {
|
||||||
log::warn!("unhandled window/showMessage: {:?}", params);
|
log::warn!("unhandled window/showMessage: {:?}", params);
|
||||||
}
|
}
|
||||||
Notification::LogMessage(params) => {
|
Notification::LogMessage(params) => {
|
||||||
log::warn!("unhandled window/logMessage: {:?}", params);
|
log::info!("window/logMessage: {:?}", params);
|
||||||
}
|
}
|
||||||
Notification::ProgressMessage(params) => {
|
Notification::ProgressMessage(params) => {
|
||||||
let lsp::ProgressParams { token, value } = params;
|
let lsp::ProgressParams { token, value } = params;
|
||||||
|
@ -588,10 +614,27 @@ impl Application {
|
||||||
Call::MethodCall(helix_lsp::jsonrpc::MethodCall {
|
Call::MethodCall(helix_lsp::jsonrpc::MethodCall {
|
||||||
method, params, id, ..
|
method, params, id, ..
|
||||||
}) => {
|
}) => {
|
||||||
|
let language_server = match self.editor.language_servers.get_by_id(server_id) {
|
||||||
|
Some(language_server) => language_server,
|
||||||
|
None => {
|
||||||
|
warn!("can't find language server with id `{}`", server_id);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
let call = match MethodCall::parse(&method, params) {
|
let call = match MethodCall::parse(&method, params) {
|
||||||
Some(call) => call,
|
Some(call) => call,
|
||||||
None => {
|
None => {
|
||||||
error!("Method not found {}", method);
|
error!("Method not found {}", method);
|
||||||
|
// language_server.reply(
|
||||||
|
// call.id,
|
||||||
|
// // TODO: make a Into trait that can cast to Err(jsonrpc::Error)
|
||||||
|
// Err(helix_lsp::jsonrpc::Error {
|
||||||
|
// code: helix_lsp::jsonrpc::ErrorCode::MethodNotFound,
|
||||||
|
// message: "Method not found".to_string(),
|
||||||
|
// data: None,
|
||||||
|
// }),
|
||||||
|
// );
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -604,54 +647,10 @@ impl Application {
|
||||||
if spinner.is_stopped() {
|
if spinner.is_stopped() {
|
||||||
spinner.start();
|
spinner.start();
|
||||||
}
|
}
|
||||||
|
tokio::spawn(language_server.reply(id, Ok(serde_json::Value::Null)));
|
||||||
let doc = self.editor.documents().find(|doc| {
|
|
||||||
doc.language_server()
|
|
||||||
.map(|server| server.id() == server_id)
|
|
||||||
.unwrap_or_default()
|
|
||||||
});
|
|
||||||
match doc {
|
|
||||||
Some(doc) => {
|
|
||||||
// it's ok to unwrap, we check for the language server before
|
|
||||||
let server = doc.language_server().unwrap();
|
|
||||||
tokio::spawn(server.reply(id, Ok(serde_json::Value::Null)));
|
|
||||||
}
|
|
||||||
None => {
|
|
||||||
if let Some(server) =
|
|
||||||
self.editor.language_servers.get_by_id(server_id)
|
|
||||||
{
|
|
||||||
log::warn!(
|
|
||||||
"missing document with language server id `{}`",
|
|
||||||
server_id
|
|
||||||
);
|
|
||||||
tokio::spawn(server.reply(
|
|
||||||
id,
|
|
||||||
Err(helix_lsp::jsonrpc::Error {
|
|
||||||
code: helix_lsp::jsonrpc::ErrorCode::InternalError,
|
|
||||||
message: "document missing".to_string(),
|
|
||||||
data: None,
|
|
||||||
}),
|
|
||||||
));
|
|
||||||
} else {
|
|
||||||
log::warn!(
|
|
||||||
"can't find language server with id `{}`",
|
|
||||||
server_id
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
// self.language_server.reply(
|
|
||||||
// call.id,
|
|
||||||
// // TODO: make a Into trait that can cast to Err(jsonrpc::Error)
|
|
||||||
// Err(helix_lsp::jsonrpc::Error {
|
|
||||||
// code: helix_lsp::jsonrpc::ErrorCode::MethodNotFound,
|
|
||||||
// message: "Method not found".to_string(),
|
|
||||||
// data: None,
|
|
||||||
// }),
|
|
||||||
// );
|
|
||||||
}
|
|
||||||
e => unreachable!("{:?}", e),
|
e => unreachable!("{:?}", e),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -61,7 +61,7 @@ impl Jobs {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn handle_callback(
|
pub fn handle_callback(
|
||||||
&mut self,
|
&self,
|
||||||
editor: &mut Editor,
|
editor: &mut Editor,
|
||||||
compositor: &mut Compositor,
|
compositor: &mut Compositor,
|
||||||
call: anyhow::Result<Option<Callback>>,
|
call: anyhow::Result<Option<Callback>>,
|
||||||
|
@ -84,7 +84,7 @@ impl Jobs {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn add(&mut self, j: Job) {
|
pub fn add(&self, j: Job) {
|
||||||
if j.wait {
|
if j.wait {
|
||||||
self.wait_futures.push(j.future);
|
self.wait_futures.push(j.future);
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -4,6 +4,7 @@ use helix_core::hashmap;
|
||||||
use helix_view::{document::Mode, info::Info, input::KeyEvent};
|
use helix_view::{document::Mode, info::Info, input::KeyEvent};
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use std::{
|
use std::{
|
||||||
|
borrow::Cow,
|
||||||
collections::HashMap,
|
collections::HashMap,
|
||||||
ops::{Deref, DerefMut},
|
ops::{Deref, DerefMut},
|
||||||
};
|
};
|
||||||
|
@ -47,13 +48,13 @@ macro_rules! keymap {
|
||||||
};
|
};
|
||||||
|
|
||||||
(@trie
|
(@trie
|
||||||
{ $label:literal $($($key:literal)|+ => $value:tt,)+ }
|
{ $label:literal $(sticky=$sticky:literal)? $($($key:literal)|+ => $value:tt,)+ }
|
||||||
) => {
|
) => {
|
||||||
keymap!({ $label $($($key)|+ => $value,)+ })
|
keymap!({ $label $(sticky=$sticky)? $($($key)|+ => $value,)+ })
|
||||||
};
|
};
|
||||||
|
|
||||||
(
|
(
|
||||||
{ $label:literal $($($key:literal)|+ => $value:tt,)+ }
|
{ $label:literal $(sticky=$sticky:literal)? $($($key:literal)|+ => $value:tt,)+ }
|
||||||
) => {
|
) => {
|
||||||
// modified from the hashmap! macro
|
// modified from the hashmap! macro
|
||||||
{
|
{
|
||||||
|
@ -70,7 +71,9 @@ macro_rules! keymap {
|
||||||
_order.push(_key);
|
_order.push(_key);
|
||||||
)+
|
)+
|
||||||
)*
|
)*
|
||||||
$crate::keymap::KeyTrie::Node($crate::keymap::KeyTrieNode::new($label, _map, _order))
|
let mut _node = $crate::keymap::KeyTrieNode::new($label, _map, _order);
|
||||||
|
$( _node.is_sticky = $sticky; )?
|
||||||
|
$crate::keymap::KeyTrie::Node(_node)
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -84,6 +87,8 @@ pub struct KeyTrieNode {
|
||||||
map: HashMap<KeyEvent, KeyTrie>,
|
map: HashMap<KeyEvent, KeyTrie>,
|
||||||
#[serde(skip)]
|
#[serde(skip)]
|
||||||
order: Vec<KeyEvent>,
|
order: Vec<KeyEvent>,
|
||||||
|
#[serde(skip)]
|
||||||
|
pub is_sticky: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl KeyTrieNode {
|
impl KeyTrieNode {
|
||||||
|
@ -92,6 +97,7 @@ impl KeyTrieNode {
|
||||||
name: name.to_string(),
|
name: name.to_string(),
|
||||||
map,
|
map,
|
||||||
order,
|
order,
|
||||||
|
is_sticky: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -119,12 +125,10 @@ impl KeyTrieNode {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
impl From<KeyTrieNode> for Info {
|
pub fn infobox(&self) -> Info {
|
||||||
fn from(node: KeyTrieNode) -> Self {
|
let mut body: Vec<(&str, Vec<KeyEvent>)> = Vec::with_capacity(self.len());
|
||||||
let mut body: Vec<(&str, Vec<KeyEvent>)> = Vec::with_capacity(node.len());
|
for (&key, trie) in self.iter() {
|
||||||
for (&key, trie) in node.iter() {
|
|
||||||
let desc = match trie {
|
let desc = match trie {
|
||||||
KeyTrie::Leaf(cmd) => cmd.doc(),
|
KeyTrie::Leaf(cmd) => cmd.doc(),
|
||||||
KeyTrie::Node(n) => n.name(),
|
KeyTrie::Node(n) => n.name(),
|
||||||
|
@ -136,16 +140,16 @@ impl From<KeyTrieNode> for Info {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
body.sort_unstable_by_key(|(_, keys)| {
|
body.sort_unstable_by_key(|(_, keys)| {
|
||||||
node.order.iter().position(|&k| k == keys[0]).unwrap()
|
self.order.iter().position(|&k| k == keys[0]).unwrap()
|
||||||
});
|
});
|
||||||
let prefix = format!("{} ", node.name());
|
let prefix = format!("{} ", self.name());
|
||||||
if body.iter().all(|(desc, _)| desc.starts_with(&prefix)) {
|
if body.iter().all(|(desc, _)| desc.starts_with(&prefix)) {
|
||||||
body = body
|
body = body
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|(desc, keys)| (desc.strip_prefix(&prefix).unwrap(), keys))
|
.map(|(desc, keys)| (desc.strip_prefix(&prefix).unwrap(), keys))
|
||||||
.collect();
|
.collect();
|
||||||
}
|
}
|
||||||
Info::new(node.name(), body)
|
Info::new(self.name(), body)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -218,7 +222,7 @@ impl KeyTrie {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
pub enum KeymapResult {
|
pub enum KeymapResultKind {
|
||||||
/// Needs more keys to execute a command. Contains valid keys for next keystroke.
|
/// Needs more keys to execute a command. Contains valid keys for next keystroke.
|
||||||
Pending(KeyTrieNode),
|
Pending(KeyTrieNode),
|
||||||
Matched(Command),
|
Matched(Command),
|
||||||
|
@ -229,14 +233,31 @@ pub enum KeymapResult {
|
||||||
Cancelled(Vec<KeyEvent>),
|
Cancelled(Vec<KeyEvent>),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returned after looking up a key in [`Keymap`]. The `sticky` field has a
|
||||||
|
/// reference to the sticky node if one is currently active.
|
||||||
|
pub struct KeymapResult<'a> {
|
||||||
|
pub kind: KeymapResultKind,
|
||||||
|
pub sticky: Option<&'a KeyTrieNode>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> KeymapResult<'a> {
|
||||||
|
pub fn new(kind: KeymapResultKind, sticky: Option<&'a KeyTrieNode>) -> Self {
|
||||||
|
Self { kind, sticky }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Deserialize)]
|
#[derive(Debug, Clone, PartialEq, Deserialize)]
|
||||||
pub struct Keymap {
|
pub struct Keymap {
|
||||||
/// Always a Node
|
/// Always a Node
|
||||||
#[serde(flatten)]
|
#[serde(flatten)]
|
||||||
root: KeyTrie,
|
root: KeyTrie,
|
||||||
/// Stores pending keys waiting for the next key
|
/// Stores pending keys waiting for the next key. This is relative to a
|
||||||
|
/// sticky node if one is in use.
|
||||||
#[serde(skip)]
|
#[serde(skip)]
|
||||||
state: Vec<KeyEvent>,
|
state: Vec<KeyEvent>,
|
||||||
|
/// Stores the sticky node if one is activated.
|
||||||
|
#[serde(skip)]
|
||||||
|
sticky: Option<KeyTrieNode>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Keymap {
|
impl Keymap {
|
||||||
|
@ -244,6 +265,7 @@ impl Keymap {
|
||||||
Keymap {
|
Keymap {
|
||||||
root,
|
root,
|
||||||
state: Vec::new(),
|
state: Vec::new(),
|
||||||
|
sticky: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -251,27 +273,61 @@ impl Keymap {
|
||||||
&self.root
|
&self.root
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn sticky(&self) -> Option<&KeyTrieNode> {
|
||||||
|
self.sticky.as_ref()
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns list of keys waiting to be disambiguated.
|
/// Returns list of keys waiting to be disambiguated.
|
||||||
pub fn pending(&self) -> &[KeyEvent] {
|
pub fn pending(&self) -> &[KeyEvent] {
|
||||||
&self.state
|
&self.state
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Lookup `key` in the keymap to try and find a command to execute
|
/// Lookup `key` in the keymap to try and find a command to execute. Escape
|
||||||
|
/// key cancels pending keystrokes. If there are no pending keystrokes but a
|
||||||
|
/// sticky node is in use, it will be cleared.
|
||||||
pub fn get(&mut self, key: KeyEvent) -> KeymapResult {
|
pub fn get(&mut self, key: KeyEvent) -> KeymapResult {
|
||||||
let &first = self.state.get(0).unwrap_or(&key);
|
if let key!(Esc) = key {
|
||||||
let trie = match self.root.search(&[first]) {
|
if !self.state.is_empty() {
|
||||||
Some(&KeyTrie::Leaf(cmd)) => return KeymapResult::Matched(cmd),
|
return KeymapResult::new(
|
||||||
None => return KeymapResult::NotFound,
|
// Note that Esc is not included here
|
||||||
|
KeymapResultKind::Cancelled(self.state.drain(..).collect()),
|
||||||
|
self.sticky(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
self.sticky = None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let first = self.state.get(0).unwrap_or(&key);
|
||||||
|
let trie_node = match self.sticky {
|
||||||
|
Some(ref trie) => Cow::Owned(KeyTrie::Node(trie.clone())),
|
||||||
|
None => Cow::Borrowed(&self.root),
|
||||||
|
};
|
||||||
|
|
||||||
|
let trie = match trie_node.search(&[*first]) {
|
||||||
|
Some(&KeyTrie::Leaf(cmd)) => {
|
||||||
|
return KeymapResult::new(KeymapResultKind::Matched(cmd), self.sticky())
|
||||||
|
}
|
||||||
|
None => return KeymapResult::new(KeymapResultKind::NotFound, self.sticky()),
|
||||||
Some(t) => t,
|
Some(t) => t,
|
||||||
};
|
};
|
||||||
|
|
||||||
self.state.push(key);
|
self.state.push(key);
|
||||||
match trie.search(&self.state[1..]) {
|
match trie.search(&self.state[1..]) {
|
||||||
Some(&KeyTrie::Node(ref map)) => KeymapResult::Pending(map.clone()),
|
Some(&KeyTrie::Node(ref map)) => {
|
||||||
Some(&KeyTrie::Leaf(command)) => {
|
if map.is_sticky {
|
||||||
self.state.clear();
|
self.state.clear();
|
||||||
KeymapResult::Matched(command)
|
self.sticky = Some(map.clone());
|
||||||
}
|
}
|
||||||
None => KeymapResult::Cancelled(self.state.drain(..).collect()),
|
KeymapResult::new(KeymapResultKind::Pending(map.clone()), self.sticky())
|
||||||
|
}
|
||||||
|
Some(&KeyTrie::Leaf(cmd)) => {
|
||||||
|
self.state.clear();
|
||||||
|
return KeymapResult::new(KeymapResultKind::Matched(cmd), self.sticky());
|
||||||
|
}
|
||||||
|
None => KeymapResult::new(
|
||||||
|
KeymapResultKind::Cancelled(self.state.drain(..).collect()),
|
||||||
|
self.sticky(),
|
||||||
|
),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -380,7 +436,6 @@ impl Default for Keymaps {
|
||||||
"A" => append_to_line,
|
"A" => append_to_line,
|
||||||
"o" => open_below,
|
"o" => open_below,
|
||||||
"O" => open_above,
|
"O" => open_above,
|
||||||
// [<space> ]<space> equivalents too (add blank new line, no edit)
|
|
||||||
|
|
||||||
"d" => delete_selection,
|
"d" => delete_selection,
|
||||||
// TODO: also delete without yanking
|
// TODO: also delete without yanking
|
||||||
|
@ -440,12 +495,11 @@ impl Default for Keymaps {
|
||||||
"<" => unindent,
|
"<" => unindent,
|
||||||
"=" => format_selections,
|
"=" => format_selections,
|
||||||
"J" => join_selections,
|
"J" => join_selections,
|
||||||
// TODO: conflicts hover/doc
|
|
||||||
"K" => keep_selections,
|
"K" => keep_selections,
|
||||||
// TODO: and another method for inverse
|
// TODO: and another method for inverse
|
||||||
|
|
||||||
// TODO: clashes with space mode
|
"," => keep_primary_selection,
|
||||||
"space" => keep_primary_selection,
|
"A-," => remove_primary_selection,
|
||||||
|
|
||||||
// "q" => record_macro,
|
// "q" => record_macro,
|
||||||
// "Q" => replay_macro,
|
// "Q" => replay_macro,
|
||||||
|
@ -473,7 +527,6 @@ impl Default for Keymaps {
|
||||||
|
|
||||||
// move under <space>c
|
// move under <space>c
|
||||||
"C-c" => toggle_comments,
|
"C-c" => toggle_comments,
|
||||||
"K" => hover,
|
|
||||||
|
|
||||||
// z family for save/restore/combine from/to sels from register
|
// z family for save/restore/combine from/to sels from register
|
||||||
|
|
||||||
|
@ -516,7 +569,8 @@ impl Default for Keymaps {
|
||||||
"p" => paste_clipboard_after,
|
"p" => paste_clipboard_after,
|
||||||
"P" => paste_clipboard_before,
|
"P" => paste_clipboard_before,
|
||||||
"R" => replace_selections_with_clipboard,
|
"R" => replace_selections_with_clipboard,
|
||||||
"space" => keep_primary_selection,
|
"/" => global_search,
|
||||||
|
"k" => hover,
|
||||||
},
|
},
|
||||||
"z" => { "View"
|
"z" => { "View"
|
||||||
"z" | "c" => align_view_center,
|
"z" | "c" => align_view_center,
|
||||||
|
@ -525,6 +579,22 @@ impl Default for Keymaps {
|
||||||
"m" => align_view_middle,
|
"m" => align_view_middle,
|
||||||
"k" => scroll_up,
|
"k" => scroll_up,
|
||||||
"j" => scroll_down,
|
"j" => scroll_down,
|
||||||
|
"b" => page_up,
|
||||||
|
"f" => page_down,
|
||||||
|
"u" => half_page_up,
|
||||||
|
"d" => half_page_down,
|
||||||
|
},
|
||||||
|
"Z" => { "View" sticky=true
|
||||||
|
"z" | "c" => align_view_center,
|
||||||
|
"t" => align_view_top,
|
||||||
|
"b" => align_view_bottom,
|
||||||
|
"m" => align_view_middle,
|
||||||
|
"k" => scroll_up,
|
||||||
|
"j" => scroll_down,
|
||||||
|
"b" => page_up,
|
||||||
|
"f" => page_down,
|
||||||
|
"u" => half_page_up,
|
||||||
|
"d" => half_page_down,
|
||||||
},
|
},
|
||||||
|
|
||||||
"\"" => select_register,
|
"\"" => select_register,
|
||||||
|
@ -545,14 +615,17 @@ impl Default for Keymaps {
|
||||||
"w" => extend_next_word_start,
|
"w" => extend_next_word_start,
|
||||||
"b" => extend_prev_word_start,
|
"b" => extend_prev_word_start,
|
||||||
"e" => extend_next_word_end,
|
"e" => extend_next_word_end,
|
||||||
|
"W" => extend_next_long_word_start,
|
||||||
|
"B" => extend_prev_long_word_start,
|
||||||
|
"E" => extend_next_long_word_end,
|
||||||
|
|
||||||
"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,
|
||||||
"F" => extend_prev_char,
|
"F" => extend_prev_char,
|
||||||
|
|
||||||
"home" => goto_line_start,
|
"home" => extend_to_line_start,
|
||||||
"end" => goto_line_end,
|
"end" => extend_to_line_end,
|
||||||
"esc" => exit_select_mode,
|
"esc" => exit_select_mode,
|
||||||
|
|
||||||
"v" => normal_mode,
|
"v" => normal_mode,
|
||||||
|
@ -617,19 +690,19 @@ fn merge_partial_keys() {
|
||||||
|
|
||||||
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')),
|
keymap.get(key!('i')).kind,
|
||||||
KeymapResult::Matched(Command::normal_mode),
|
KeymapResultKind::Matched(Command::normal_mode),
|
||||||
"Leaf should replace leaf"
|
"Leaf should replace leaf"
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
keymap.get(key!('无')),
|
keymap.get(key!('无')).kind,
|
||||||
KeymapResult::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')),
|
keymap.get(key!('z')).kind,
|
||||||
KeymapResult::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
|
||||||
|
|
|
@ -262,8 +262,7 @@ impl Component for Completion {
|
||||||
.cursor(doc.text().slice(..));
|
.cursor(doc.text().slice(..));
|
||||||
let cursor_pos = (helix_core::coords_at_pos(doc.text().slice(..), cursor_pos).row
|
let cursor_pos = (helix_core::coords_at_pos(doc.text().slice(..), cursor_pos).row
|
||||||
- view.offset.row) as u16;
|
- view.offset.row) as u16;
|
||||||
|
let mut markdown_doc = match &option.documentation {
|
||||||
let mut 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 {
|
||||||
kind: lsp::MarkupKind::PlainText,
|
kind: lsp::MarkupKind::PlainText,
|
||||||
|
@ -311,6 +310,23 @@ impl Component for Completion {
|
||||||
None => return,
|
None => return,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let (popup_x, popup_y) = self.popup.get_rel_position(area, cx);
|
||||||
|
let (popup_width, _popup_height) = self.popup.get_size();
|
||||||
|
let mut width = area
|
||||||
|
.width
|
||||||
|
.saturating_sub(popup_x)
|
||||||
|
.saturating_sub(popup_width);
|
||||||
|
let area = if width > 30 {
|
||||||
|
let mut height = area.height.saturating_sub(popup_y);
|
||||||
|
let x = popup_x + popup_width;
|
||||||
|
let y = popup_y;
|
||||||
|
|
||||||
|
if let Some((rel_width, rel_height)) = markdown_doc.required_size((width, height)) {
|
||||||
|
width = rel_width;
|
||||||
|
height = rel_height;
|
||||||
|
}
|
||||||
|
Rect::new(x, y, width, height)
|
||||||
|
} else {
|
||||||
let half = area.height / 2;
|
let half = area.height / 2;
|
||||||
let height = 15.min(half);
|
let height = 15.min(half);
|
||||||
// we want to make sure the cursor is visible (not hidden behind the documentation)
|
// we want to make sure the cursor is visible (not hidden behind the documentation)
|
||||||
|
@ -323,12 +339,13 @@ impl Component for Completion {
|
||||||
area.height.saturating_sub(height).saturating_sub(2)
|
area.height.saturating_sub(height).saturating_sub(2)
|
||||||
};
|
};
|
||||||
|
|
||||||
let area = Rect::new(0, y, area.width, height);
|
Rect::new(0, y, area.width, height)
|
||||||
|
};
|
||||||
|
|
||||||
// clear area
|
// clear area
|
||||||
let background = cx.editor.theme.get("ui.popup");
|
let background = cx.editor.theme.get("ui.popup");
|
||||||
surface.clear_with(area, background);
|
surface.clear_with(area, background);
|
||||||
doc.render(area, surface, cx);
|
markdown_doc.render(area, surface, cx);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,7 @@ use crate::{
|
||||||
compositor::{Component, Compositor, Context, EventResult},
|
compositor::{Component, Compositor, Context, EventResult},
|
||||||
job::Callback,
|
job::Callback,
|
||||||
key,
|
key,
|
||||||
keymap::{KeymapResult, Keymaps},
|
keymap::{KeymapResult, KeymapResultKind, Keymaps},
|
||||||
ui::{Completion, ProgressSpinners},
|
ui::{Completion, ProgressSpinners},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -165,8 +165,7 @@ impl EditorView {
|
||||||
let scopes = theme.scopes();
|
let scopes = theme.scopes();
|
||||||
syntax
|
syntax
|
||||||
.highlight_iter(text.slice(..), Some(range), None, |language| {
|
.highlight_iter(text.slice(..), Some(range), None, |language| {
|
||||||
loader
|
loader.language_configuration_for_injection_string(language)
|
||||||
.language_config_for_scope(&format!("source.{}", language))
|
|
||||||
.and_then(|language_config| {
|
.and_then(|language_config| {
|
||||||
let config = language_config.highlight_config(scopes)?;
|
let config = language_config.highlight_config(scopes)?;
|
||||||
let config_ref = config.as_ref();
|
let config_ref = config.as_ref();
|
||||||
|
@ -852,7 +851,7 @@ impl EditorView {
|
||||||
|
|
||||||
/// Handle events by looking them up in `self.keymaps`. Returns None
|
/// Handle events by looking them up in `self.keymaps`. Returns None
|
||||||
/// if event was handled (a command was executed or a subkeymap was
|
/// if event was handled (a command was executed or a subkeymap was
|
||||||
/// activated). Only KeymapResult::{NotFound, Cancelled} is returned
|
/// activated). Only KeymapResultKind::{NotFound, Cancelled} is returned
|
||||||
/// otherwise.
|
/// otherwise.
|
||||||
fn handle_keymap_event(
|
fn handle_keymap_event(
|
||||||
&mut self,
|
&mut self,
|
||||||
|
@ -860,8 +859,6 @@ impl EditorView {
|
||||||
cxt: &mut commands::Context,
|
cxt: &mut commands::Context,
|
||||||
event: KeyEvent,
|
event: KeyEvent,
|
||||||
) -> Option<KeymapResult> {
|
) -> Option<KeymapResult> {
|
||||||
self.autoinfo = None;
|
|
||||||
|
|
||||||
if let Some(picker) = cxt.editor.debug_config_picker.clone() {
|
if let Some(picker) = cxt.editor.debug_config_picker.clone() {
|
||||||
match event {
|
match event {
|
||||||
KeyEvent {
|
KeyEvent {
|
||||||
|
@ -912,29 +909,32 @@ impl EditorView {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
match self.keymaps.get_mut(&mode).unwrap().get(event) {
|
let key_result = self.keymaps.get_mut(&mode).unwrap().get(event);
|
||||||
KeymapResult::Matched(command) => command.execute(cxt),
|
self.autoinfo = key_result.sticky.map(|node| node.infobox());
|
||||||
KeymapResult::Pending(node) => self.autoinfo = Some(node.into()),
|
|
||||||
k @ KeymapResult::NotFound | k @ KeymapResult::Cancelled(_) => return Some(k),
|
match &key_result.kind {
|
||||||
|
KeymapResultKind::Matched(command) => command.execute(cxt),
|
||||||
|
KeymapResultKind::Pending(node) => self.autoinfo = Some(node.infobox()),
|
||||||
|
KeymapResultKind::NotFound | KeymapResultKind::Cancelled(_) => return Some(key_result),
|
||||||
}
|
}
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
fn insert_mode(&mut self, cx: &mut commands::Context, event: KeyEvent) {
|
fn insert_mode(&mut self, cx: &mut commands::Context, event: KeyEvent) {
|
||||||
if let Some(keyresult) = self.handle_keymap_event(Mode::Insert, cx, event) {
|
if let Some(keyresult) = self.handle_keymap_event(Mode::Insert, cx, event) {
|
||||||
match keyresult {
|
match keyresult.kind {
|
||||||
KeymapResult::NotFound => {
|
KeymapResultKind::NotFound => {
|
||||||
if let Some(ch) = event.char() {
|
if let Some(ch) = event.char() {
|
||||||
commands::insert::insert_char(cx, ch)
|
commands::insert::insert_char(cx, ch)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
KeymapResult::Cancelled(pending) => {
|
KeymapResultKind::Cancelled(pending) => {
|
||||||
for ev in pending {
|
for ev in pending {
|
||||||
match ev.char() {
|
match ev.char() {
|
||||||
Some(ch) => commands::insert::insert_char(cx, ch),
|
Some(ch) => commands::insert::insert_char(cx, ch),
|
||||||
None => {
|
None => {
|
||||||
if let KeymapResult::Matched(command) =
|
if let KeymapResultKind::Matched(command) =
|
||||||
self.keymaps.get_mut(&Mode::Insert).unwrap().get(ev)
|
self.keymaps.get_mut(&Mode::Insert).unwrap().get(ev).kind
|
||||||
{
|
{
|
||||||
command.execute(cx);
|
command.execute(cx);
|
||||||
}
|
}
|
||||||
|
@ -972,7 +972,7 @@ impl EditorView {
|
||||||
// debug_assert!(cxt.count != 0);
|
// debug_assert!(cxt.count != 0);
|
||||||
|
|
||||||
// set the register
|
// set the register
|
||||||
cxt.selected_register = cxt.editor.selected_register.take();
|
cxt.register = cxt.editor.selected_register.take();
|
||||||
|
|
||||||
self.handle_keymap_event(mode, cxt, event);
|
self.handle_keymap_event(mode, cxt, event);
|
||||||
if self.keymaps.pending().is_empty() {
|
if self.keymaps.pending().is_empty() {
|
||||||
|
@ -1196,9 +1196,9 @@ impl EditorView {
|
||||||
impl Component for EditorView {
|
impl Component for EditorView {
|
||||||
fn handle_event(&mut self, event: Event, cx: &mut Context) -> EventResult {
|
fn handle_event(&mut self, event: Event, cx: &mut Context) -> EventResult {
|
||||||
let mut cxt = commands::Context {
|
let mut cxt = commands::Context {
|
||||||
selected_register: helix_view::RegisterSelection::default(),
|
|
||||||
editor: &mut cx.editor,
|
editor: &mut cx.editor,
|
||||||
count: None,
|
count: None,
|
||||||
|
register: None,
|
||||||
callback: None,
|
callback: None,
|
||||||
on_next_key_callback: None,
|
on_next_key_callback: None,
|
||||||
jobs: cx.jobs,
|
jobs: cx.jobs,
|
||||||
|
@ -1288,8 +1288,9 @@ impl Component for EditorView {
|
||||||
// how we entered insert mode is important, and we should track that so
|
// how we entered insert mode is important, and we should track that so
|
||||||
// we can repeat the side effect.
|
// we can repeat the side effect.
|
||||||
|
|
||||||
self.last_insert.0 = match self.keymaps.get_mut(&mode).unwrap().get(key) {
|
self.last_insert.0 =
|
||||||
KeymapResult::Matched(command) => command,
|
match self.keymaps.get_mut(&mode).unwrap().get(key).kind {
|
||||||
|
KeymapResultKind::Matched(command) => command,
|
||||||
// FIXME: insert mode can only be entered through single KeyCodes
|
// FIXME: insert mode can only be entered through single KeyCodes
|
||||||
_ => unimplemented!(),
|
_ => unimplemented!(),
|
||||||
};
|
};
|
||||||
|
|
|
@ -88,7 +88,7 @@ fn parse<'a>(
|
||||||
if let Some(theme) = theme {
|
if let Some(theme) = theme {
|
||||||
let rope = Rope::from(text.as_ref());
|
let rope = Rope::from(text.as_ref());
|
||||||
let syntax = loader
|
let syntax = loader
|
||||||
.language_config_for_scope(&format!("source.{}", language))
|
.language_configuration_for_injection_string(language)
|
||||||
.and_then(|config| config.highlight_config(theme.scopes()))
|
.and_then(|config| config.highlight_config(theme.scopes()))
|
||||||
.map(|config| Syntax::new(&rope, config));
|
.map(|config| Syntax::new(&rope, config));
|
||||||
|
|
||||||
|
@ -215,10 +215,30 @@ impl Component for Markdown {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn required_size(&mut self, viewport: (u16, u16)) -> Option<(u16, u16)> {
|
fn required_size(&mut self, viewport: (u16, u16)) -> Option<(u16, u16)> {
|
||||||
let contents = parse(&self.contents, None, &self.config_loader);
|
|
||||||
let padding = 2;
|
let padding = 2;
|
||||||
let width = std::cmp::min(contents.width() as u16 + padding, viewport.0);
|
if padding >= viewport.1 || padding >= viewport.0 {
|
||||||
let height = std::cmp::min(contents.height() as u16 + padding, viewport.1);
|
return None;
|
||||||
Some((width, height))
|
}
|
||||||
|
let contents = parse(&self.contents, None, &self.config_loader);
|
||||||
|
let max_text_width = (viewport.0 - padding).min(120);
|
||||||
|
let mut text_width = 0;
|
||||||
|
let mut height = padding;
|
||||||
|
for content in contents {
|
||||||
|
height += 1;
|
||||||
|
let content_width = content.width() as u16;
|
||||||
|
if content_width > max_text_width {
|
||||||
|
text_width = max_text_width;
|
||||||
|
height += content_width / max_text_width;
|
||||||
|
} else if content_width > text_width {
|
||||||
|
text_width = content_width;
|
||||||
|
}
|
||||||
|
|
||||||
|
if height >= viewport.1 {
|
||||||
|
height = viewport.1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Some((text_width + padding, height))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,6 +33,8 @@ pub struct Menu<T: Item> {
|
||||||
|
|
||||||
scroll: usize,
|
scroll: usize,
|
||||||
size: (u16, u16),
|
size: (u16, u16),
|
||||||
|
viewport: (u16, u16),
|
||||||
|
recalculate: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: Item> Menu<T> {
|
impl<T: Item> Menu<T> {
|
||||||
|
@ -51,6 +53,8 @@ impl<T: Item> Menu<T> {
|
||||||
callback_fn: Box::new(callback_fn),
|
callback_fn: Box::new(callback_fn),
|
||||||
scroll: 0,
|
scroll: 0,
|
||||||
size: (0, 0),
|
size: (0, 0),
|
||||||
|
viewport: (0, 0),
|
||||||
|
recalculate: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
// TODO: scoring on empty input should just use a fastpath
|
// TODO: scoring on empty input should just use a fastpath
|
||||||
|
@ -83,6 +87,7 @@ impl<T: Item> Menu<T> {
|
||||||
// reset cursor position
|
// reset cursor position
|
||||||
self.cursor = None;
|
self.cursor = None;
|
||||||
self.scroll = 0;
|
self.scroll = 0;
|
||||||
|
self.recalculate = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn move_up(&mut self) {
|
pub fn move_up(&mut self) {
|
||||||
|
@ -99,6 +104,41 @@ impl<T: Item> Menu<T> {
|
||||||
self.adjust_scroll();
|
self.adjust_scroll();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn recalculate_size(&mut self, viewport: (u16, u16)) {
|
||||||
|
let n = self
|
||||||
|
.options
|
||||||
|
.first()
|
||||||
|
.map(|option| option.row().cells.len())
|
||||||
|
.unwrap_or_default();
|
||||||
|
let max_lens = self.options.iter().fold(vec![0; n], |mut acc, option| {
|
||||||
|
let row = option.row();
|
||||||
|
// maintain max for each column
|
||||||
|
for (acc, cell) in acc.iter_mut().zip(row.cells.iter()) {
|
||||||
|
let width = cell.content.width();
|
||||||
|
if width > *acc {
|
||||||
|
*acc = width;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
acc
|
||||||
|
});
|
||||||
|
let len = max_lens.iter().sum::<usize>() + n + 1; // +1: reserve some space for scrollbar
|
||||||
|
let width = len.min(viewport.0 as usize);
|
||||||
|
|
||||||
|
self.widths = max_lens
|
||||||
|
.into_iter()
|
||||||
|
.map(|len| Constraint::Length(len as u16))
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let height = self.matches.len().min(10).min(viewport.1 as usize);
|
||||||
|
|
||||||
|
self.size = (width as u16, height as u16);
|
||||||
|
|
||||||
|
// adjust scroll offsets if size changed
|
||||||
|
self.adjust_scroll();
|
||||||
|
self.recalculate = false;
|
||||||
|
}
|
||||||
|
|
||||||
fn adjust_scroll(&mut self) {
|
fn adjust_scroll(&mut self) {
|
||||||
let win_height = self.size.1 as usize;
|
let win_height = self.size.1 as usize;
|
||||||
if let Some(cursor) = self.cursor {
|
if let Some(cursor) = self.cursor {
|
||||||
|
@ -221,43 +261,13 @@ impl<T: Item + 'static> Component for Menu<T> {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn required_size(&mut self, viewport: (u16, u16)) -> Option<(u16, u16)> {
|
fn required_size(&mut self, viewport: (u16, u16)) -> Option<(u16, u16)> {
|
||||||
let n = self
|
if viewport != self.viewport || self.recalculate {
|
||||||
.options
|
self.recalculate_size(viewport);
|
||||||
.first()
|
|
||||||
.map(|option| option.row().cells.len())
|
|
||||||
.unwrap_or_default();
|
|
||||||
let max_lens = self.options.iter().fold(vec![0; n], |mut acc, option| {
|
|
||||||
let row = option.row();
|
|
||||||
// maintain max for each column
|
|
||||||
for (acc, cell) in acc.iter_mut().zip(row.cells.iter()) {
|
|
||||||
let width = cell.content.width();
|
|
||||||
if width > *acc {
|
|
||||||
*acc = width;
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
acc
|
|
||||||
});
|
|
||||||
let len = max_lens.iter().sum::<usize>() + n + 1; // +1: reserve some space for scrollbar
|
|
||||||
let width = len.min(viewport.0 as usize);
|
|
||||||
|
|
||||||
self.widths = max_lens
|
|
||||||
.into_iter()
|
|
||||||
.map(|len| Constraint::Length(len as u16))
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
let height = self.options.len().min(10).min(viewport.1 as usize);
|
|
||||||
|
|
||||||
self.size = (width as u16, height as u16);
|
|
||||||
|
|
||||||
// adjust scroll offsets if size changed
|
|
||||||
self.adjust_scroll();
|
|
||||||
|
|
||||||
Some(self.size)
|
Some(self.size)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: required size should re-trigger when we filter items so we can draw a smaller menu
|
|
||||||
|
|
||||||
fn render(&mut self, area: Rect, surface: &mut Surface, cx: &mut Context) {
|
fn render(&mut self, area: Rect, surface: &mut Surface, cx: &mut Context) {
|
||||||
let theme = &cx.editor.theme;
|
let theme = &cx.editor.theme;
|
||||||
let style = theme
|
let style = theme
|
||||||
|
|
|
@ -20,7 +20,7 @@ pub use spinner::{ProgressSpinners, Spinner};
|
||||||
pub use text::Text;
|
pub use text::Text;
|
||||||
|
|
||||||
use helix_core::regex::Regex;
|
use helix_core::regex::Regex;
|
||||||
use helix_core::register::Registers;
|
use helix_core::regex::RegexBuilder;
|
||||||
use helix_view::{Document, Editor, View};
|
use helix_view::{Document, Editor, View};
|
||||||
|
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
@ -28,7 +28,8 @@ use std::path::PathBuf;
|
||||||
pub fn regex_prompt(
|
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>,
|
||||||
fun: impl Fn(&mut View, &mut Document, &mut Registers, Regex) + 'static,
|
history_register: Option<char>,
|
||||||
|
fun: impl Fn(&mut View, &mut Document, Regex, PromptEvent) + 'static,
|
||||||
) -> Prompt {
|
) -> Prompt {
|
||||||
let (view, doc) = current!(cx.editor);
|
let (view, doc) = current!(cx.editor);
|
||||||
let view_id = view.id;
|
let view_id = view.id;
|
||||||
|
@ -36,7 +37,7 @@ pub fn regex_prompt(
|
||||||
|
|
||||||
Prompt::new(
|
Prompt::new(
|
||||||
prompt,
|
prompt,
|
||||||
None,
|
history_register,
|
||||||
|_input: &str| Vec::new(), // this is fine because Vec::new() doesn't allocate
|
|_input: &str| Vec::new(), // this is fine because Vec::new() doesn't allocate
|
||||||
move |cx: &mut crate::compositor::Context, input: &str, event: PromptEvent| {
|
move |cx: &mut crate::compositor::Context, input: &str, event: PromptEvent| {
|
||||||
match event {
|
match event {
|
||||||
|
@ -46,6 +47,14 @@ pub fn regex_prompt(
|
||||||
}
|
}
|
||||||
PromptEvent::Validate => {
|
PromptEvent::Validate => {
|
||||||
// TODO: push_jump to store selection just before jump
|
// TODO: push_jump to store selection just before jump
|
||||||
|
|
||||||
|
match Regex::new(input) {
|
||||||
|
Ok(regex) => {
|
||||||
|
let (view, doc) = current!(cx.editor);
|
||||||
|
fun(view, doc, regex, event);
|
||||||
|
}
|
||||||
|
Err(_err) => (), // TODO: mark command line as error
|
||||||
|
}
|
||||||
}
|
}
|
||||||
PromptEvent::Update => {
|
PromptEvent::Update => {
|
||||||
// skip empty input, TODO: trigger default
|
// skip empty input, TODO: trigger default
|
||||||
|
@ -53,15 +62,23 @@ pub fn regex_prompt(
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
match Regex::new(input) {
|
let case_insensitive = if cx.editor.config.smart_case {
|
||||||
|
!input.chars().any(char::is_uppercase)
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
};
|
||||||
|
|
||||||
|
match RegexBuilder::new(input)
|
||||||
|
.case_insensitive(case_insensitive)
|
||||||
|
.build()
|
||||||
|
{
|
||||||
Ok(regex) => {
|
Ok(regex) => {
|
||||||
let (view, doc) = current!(cx.editor);
|
let (view, doc) = current!(cx.editor);
|
||||||
let registers = &mut cx.editor.registers;
|
|
||||||
|
|
||||||
// revert state to what it was before the last update
|
// revert state to what it was before the last update
|
||||||
doc.set_selection(view.id, snapshot.clone());
|
doc.set_selection(view.id, snapshot.clone());
|
||||||
|
|
||||||
fun(view, doc, registers, regex);
|
fun(view, doc, regex, event);
|
||||||
|
|
||||||
view.ensure_cursor_in_view(doc, cx.editor.config.scrolloff);
|
view.ensure_cursor_in_view(doc, cx.editor.config.scrolloff);
|
||||||
}
|
}
|
||||||
|
|
|
@ -124,10 +124,13 @@ impl<T: 'static> Component for FilePicker<T> {
|
||||||
}) {
|
}) {
|
||||||
// align to middle
|
// align to middle
|
||||||
let first_line = line
|
let first_line = line
|
||||||
.map(|(s, e)| (s.min(doc.text().len_lines()), e.min(doc.text().len_lines())))
|
.map(|(start, end)| {
|
||||||
.map(|(start, _)| start)
|
let height = end.saturating_sub(start) + 1;
|
||||||
.unwrap_or(0)
|
let middle = start + (height.saturating_sub(1) / 2);
|
||||||
.saturating_sub(inner.height as usize / 2);
|
middle.saturating_sub(inner.height as usize / 2).min(start)
|
||||||
|
})
|
||||||
|
.unwrap_or(0);
|
||||||
|
|
||||||
let offset = Position::new(first_line, 0);
|
let offset = Position::new(first_line, 0);
|
||||||
|
|
||||||
let highlights = EditorView::doc_syntax_highlights(
|
let highlights = EditorView::doc_syntax_highlights(
|
||||||
|
@ -268,17 +271,15 @@ impl<T> Picker<T> {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn move_up(&mut self) {
|
pub fn move_up(&mut self) {
|
||||||
self.cursor = self.cursor.saturating_sub(1);
|
let len = self.matches.len();
|
||||||
|
let pos = ((self.cursor + len.saturating_sub(1)) % len) % len;
|
||||||
|
self.cursor = pos;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn move_down(&mut self) {
|
pub fn move_down(&mut self) {
|
||||||
if self.matches.is_empty() {
|
let len = self.matches.len();
|
||||||
return;
|
let pos = (self.cursor + 1) % len;
|
||||||
}
|
self.cursor = pos;
|
||||||
|
|
||||||
if self.cursor < self.matches.len() - 1 {
|
|
||||||
self.cursor += 1;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn selection(&self) -> Option<&T> {
|
pub fn selection(&self) -> Option<&T> {
|
||||||
|
|
|
@ -16,8 +16,6 @@ pub struct Popup<T: Component> {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: Component> Popup<T> {
|
impl<T: Component> Popup<T> {
|
||||||
// TODO: it's like a slimmed down picker, share code? (picker = menu + prompt with different
|
|
||||||
// rendering)
|
|
||||||
pub fn new(contents: T) -> Self {
|
pub fn new(contents: T) -> Self {
|
||||||
Self {
|
Self {
|
||||||
contents,
|
contents,
|
||||||
|
@ -31,6 +29,39 @@ impl<T: Component> Popup<T> {
|
||||||
self.position = pos;
|
self.position = pos;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_rel_position(&mut self, viewport: Rect, cx: &Context) -> (u16, u16) {
|
||||||
|
let position = self
|
||||||
|
.position
|
||||||
|
.get_or_insert_with(|| cx.editor.cursor().0.unwrap_or_default());
|
||||||
|
|
||||||
|
let (width, height) = self.size;
|
||||||
|
|
||||||
|
// if there's a orientation preference, use that
|
||||||
|
// if we're on the top part of the screen, do below
|
||||||
|
// if we're on the bottom part, do above
|
||||||
|
|
||||||
|
// -- make sure frame doesn't stick out of bounds
|
||||||
|
let mut rel_x = position.col as u16;
|
||||||
|
let mut rel_y = position.row as u16;
|
||||||
|
if viewport.width <= rel_x + width {
|
||||||
|
rel_x = rel_x.saturating_sub((rel_x + width).saturating_sub(viewport.width));
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: be able to specify orientation preference. We want above for most popups, below
|
||||||
|
// for menus/autocomplete.
|
||||||
|
if viewport.height > rel_y + height {
|
||||||
|
rel_y += 1 // position below point
|
||||||
|
} else {
|
||||||
|
rel_y = rel_y.saturating_sub(height) // position above point
|
||||||
|
}
|
||||||
|
|
||||||
|
(rel_x, rel_y)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_size(&self) -> (u16, u16) {
|
||||||
|
(self.size.0, self.size.1)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn scroll(&mut self, offset: usize, direction: bool) {
|
pub fn scroll(&mut self, offset: usize, direction: bool) {
|
||||||
if direction {
|
if direction {
|
||||||
self.scroll += offset;
|
self.scroll += offset;
|
||||||
|
@ -106,31 +137,15 @@ impl<T: Component> Component for Popup<T> {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render(&mut self, viewport: Rect, surface: &mut Surface, cx: &mut Context) {
|
fn render(&mut self, viewport: Rect, surface: &mut Surface, cx: &mut Context) {
|
||||||
|
// trigger required_size so we recalculate if the child changed
|
||||||
|
self.required_size((viewport.width, viewport.height));
|
||||||
|
|
||||||
cx.scroll = Some(self.scroll);
|
cx.scroll = Some(self.scroll);
|
||||||
|
|
||||||
let position = self
|
let (rel_x, rel_y) = self.get_rel_position(viewport, cx);
|
||||||
.position
|
|
||||||
.get_or_insert_with(|| cx.editor.cursor().0.unwrap_or_default());
|
|
||||||
|
|
||||||
let (width, height) = self.size;
|
|
||||||
|
|
||||||
// -- make sure frame doesn't stick out of bounds
|
|
||||||
let mut rel_x = position.col as u16;
|
|
||||||
let mut rel_y = position.row as u16;
|
|
||||||
if viewport.width <= rel_x + width {
|
|
||||||
rel_x = rel_x.saturating_sub((rel_x + width).saturating_sub(viewport.width));
|
|
||||||
};
|
|
||||||
|
|
||||||
// TODO: be able to specify orientation preference. We want above for most popups, below
|
|
||||||
// for menus/autocomplete.
|
|
||||||
if height <= rel_y {
|
|
||||||
rel_y = rel_y.saturating_sub(height) // position above point
|
|
||||||
} else {
|
|
||||||
rel_y += 1 // position below point
|
|
||||||
}
|
|
||||||
|
|
||||||
// clip to viewport
|
// clip to viewport
|
||||||
let area = viewport.intersection(Rect::new(rel_x, rel_y, width, height));
|
let area = viewport.intersection(Rect::new(rel_x, rel_y, self.size.0, self.size.1));
|
||||||
|
|
||||||
// clear area
|
// clear area
|
||||||
let background = cx.editor.theme.get("ui.popup");
|
let background = cx.editor.theme.get("ui.popup");
|
||||||
|
|
|
@ -5,11 +5,17 @@ use helix_view::graphics::Rect;
|
||||||
|
|
||||||
pub struct Text {
|
pub struct Text {
|
||||||
contents: String,
|
contents: String,
|
||||||
|
size: (u16, u16),
|
||||||
|
viewport: (u16, u16),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Text {
|
impl Text {
|
||||||
pub fn new(contents: String) -> Self {
|
pub fn new(contents: String) -> Self {
|
||||||
Self { contents }
|
Self {
|
||||||
|
contents,
|
||||||
|
size: (0, 0),
|
||||||
|
viewport: (0, 0),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl Component for Text {
|
impl Component for Text {
|
||||||
|
@ -24,9 +30,13 @@ impl Component for Text {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn required_size(&mut self, viewport: (u16, u16)) -> Option<(u16, u16)> {
|
fn required_size(&mut self, viewport: (u16, u16)) -> Option<(u16, u16)> {
|
||||||
|
if viewport != self.viewport {
|
||||||
let contents = tui::text::Text::from(self.contents.clone());
|
let contents = tui::text::Text::from(self.contents.clone());
|
||||||
let width = std::cmp::min(contents.width() as u16, viewport.0);
|
let width = std::cmp::min(contents.width() as u16, viewport.0);
|
||||||
let height = std::cmp::min(contents.height() as u16, viewport.1);
|
let height = std::cmp::min(contents.height() as u16, viewport.1);
|
||||||
Some((width, height))
|
self.size = (width, height);
|
||||||
|
self.viewport = viewport;
|
||||||
|
}
|
||||||
|
Some(self.size)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -386,21 +386,24 @@ impl Document {
|
||||||
/// If supported, returns the changes that should be applied to this document in order
|
/// If supported, returns the changes that should be applied to this document in order
|
||||||
/// to format it nicely.
|
/// to format it nicely.
|
||||||
pub fn format(&self) -> Option<impl Future<Output = LspFormatting> + 'static> {
|
pub fn format(&self) -> Option<impl Future<Output = LspFormatting> + 'static> {
|
||||||
if let Some(language_server) = self.language_server.clone() {
|
if let Some(language_server) = self.language_server() {
|
||||||
let text = self.text.clone();
|
let text = self.text.clone();
|
||||||
let id = self.identifier();
|
let offset_encoding = language_server.offset_encoding();
|
||||||
|
let request = language_server.text_document_formatting(
|
||||||
|
self.identifier(),
|
||||||
|
lsp::FormattingOptions::default(),
|
||||||
|
None,
|
||||||
|
)?;
|
||||||
|
|
||||||
let fut = async move {
|
let fut = async move {
|
||||||
let edits = language_server
|
let edits = request.await.unwrap_or_else(|e| {
|
||||||
.text_document_formatting(id, lsp::FormattingOptions::default(), None)
|
|
||||||
.await
|
|
||||||
.unwrap_or_else(|e| {
|
|
||||||
log::warn!("LSP formatting failed: {}", e);
|
log::warn!("LSP formatting failed: {}", e);
|
||||||
Default::default()
|
Default::default()
|
||||||
});
|
});
|
||||||
LspFormatting {
|
LspFormatting {
|
||||||
doc: text,
|
doc: text,
|
||||||
edits,
|
edits,
|
||||||
offset_encoding: language_server.offset_encoding(),
|
offset_encoding,
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
Some(fut)
|
Some(fut)
|
||||||
|
@ -469,9 +472,14 @@ impl Document {
|
||||||
to_writer(&mut file, encoding, &text).await?;
|
to_writer(&mut file, encoding, &text).await?;
|
||||||
|
|
||||||
if let Some(language_server) = language_server {
|
if let Some(language_server) = language_server {
|
||||||
language_server
|
if !language_server.is_initialized() {
|
||||||
.text_document_did_save(identifier, &text)
|
return Ok(());
|
||||||
.await?;
|
}
|
||||||
|
if let Some(notification) =
|
||||||
|
language_server.text_document_did_save(identifier, &text)
|
||||||
|
{
|
||||||
|
notification.await?;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -646,7 +654,7 @@ impl Document {
|
||||||
// }
|
// }
|
||||||
|
|
||||||
// emit lsp notification
|
// emit lsp notification
|
||||||
if let Some(language_server) = &self.language_server {
|
if let Some(language_server) = self.language_server() {
|
||||||
let notify = language_server.text_document_did_change(
|
let notify = language_server.text_document_did_change(
|
||||||
self.versioned_identifier(),
|
self.versioned_identifier(),
|
||||||
&old_doc,
|
&old_doc,
|
||||||
|
@ -795,9 +803,18 @@ impl Document {
|
||||||
self.version
|
self.version
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn language_server(&self) -> Option<&helix_lsp::Client> {
|
pub fn language_server(&self) -> Option<&helix_lsp::Client> {
|
||||||
self.language_server.as_deref()
|
let server = self.language_server.as_deref();
|
||||||
|
let initialized = server
|
||||||
|
.map(|server| server.is_initialized())
|
||||||
|
.unwrap_or(false);
|
||||||
|
|
||||||
|
// only resolve language_server if it's initialized
|
||||||
|
if initialized {
|
||||||
|
server
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
|
@ -891,6 +908,40 @@ impl Default for Document {
|
||||||
mod test {
|
mod test {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn changeset_to_changes_ignore_line_endings() {
|
||||||
|
use helix_lsp::{lsp, Client, OffsetEncoding};
|
||||||
|
let text = Rope::from("hello\r\nworld");
|
||||||
|
let mut doc = Document::from(text, None);
|
||||||
|
let view = ViewId::default();
|
||||||
|
doc.set_selection(view, Selection::single(0, 0));
|
||||||
|
|
||||||
|
let transaction =
|
||||||
|
Transaction::change(doc.text(), vec![(5, 7, Some("\n".into()))].into_iter());
|
||||||
|
let old_doc = doc.text().clone();
|
||||||
|
doc.apply(&transaction, view);
|
||||||
|
let changes = Client::changeset_to_changes(
|
||||||
|
&old_doc,
|
||||||
|
doc.text(),
|
||||||
|
transaction.changes(),
|
||||||
|
OffsetEncoding::Utf8,
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(doc.text(), "hello\nworld");
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
changes,
|
||||||
|
&[lsp::TextDocumentContentChangeEvent {
|
||||||
|
range: Some(lsp::Range::new(
|
||||||
|
lsp::Position::new(0, 5),
|
||||||
|
lsp::Position::new(1, 0)
|
||||||
|
)),
|
||||||
|
text: "\n".into(),
|
||||||
|
range_length: None,
|
||||||
|
}]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn changeset_to_changes() {
|
fn changeset_to_changes() {
|
||||||
use helix_lsp::{lsp, Client, OffsetEncoding};
|
use helix_lsp::{lsp, Client, OffsetEncoding};
|
||||||
|
|
|
@ -3,7 +3,7 @@ use crate::{
|
||||||
graphics::{CursorKind, Rect},
|
graphics::{CursorKind, Rect},
|
||||||
theme::{self, Theme},
|
theme::{self, Theme},
|
||||||
tree::Tree,
|
tree::Tree,
|
||||||
Document, DocumentId, RegisterSelection, View, ViewId,
|
Document, DocumentId, View, ViewId,
|
||||||
};
|
};
|
||||||
|
|
||||||
use futures_util::future;
|
use futures_util::future;
|
||||||
|
@ -44,6 +44,10 @@ pub struct Config {
|
||||||
pub line_number: LineNumber,
|
pub line_number: LineNumber,
|
||||||
/// Middle click paste support. Defaults to true
|
/// Middle click paste support. Defaults to true
|
||||||
pub middle_click_paste: bool,
|
pub middle_click_paste: bool,
|
||||||
|
/// Smart case: Case insensitive searching unless pattern contains upper case characters. Defaults to true.
|
||||||
|
pub smart_case: bool,
|
||||||
|
/// Automatic insertion of pairs to parentheses, brackets, etc. Defaults to true.
|
||||||
|
pub auto_pairs: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
|
#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
|
||||||
|
@ -69,6 +73,8 @@ impl Default for Config {
|
||||||
},
|
},
|
||||||
line_number: LineNumber::Absolute,
|
line_number: LineNumber::Absolute,
|
||||||
middle_click_paste: true,
|
middle_click_paste: true,
|
||||||
|
smart_case: true,
|
||||||
|
auto_pairs: true,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -78,7 +84,7 @@ pub struct Editor {
|
||||||
pub tree: Tree,
|
pub tree: Tree,
|
||||||
pub documents: SlotMap<DocumentId, Document>,
|
pub documents: SlotMap<DocumentId, Document>,
|
||||||
pub count: Option<std::num::NonZeroUsize>,
|
pub count: Option<std::num::NonZeroUsize>,
|
||||||
pub selected_register: RegisterSelection,
|
pub selected_register: Option<char>,
|
||||||
pub registers: Registers,
|
pub registers: Registers,
|
||||||
pub theme: Theme,
|
pub theme: Theme,
|
||||||
pub language_servers: helix_lsp::Registry,
|
pub language_servers: helix_lsp::Registry,
|
||||||
|
@ -125,7 +131,7 @@ impl Editor {
|
||||||
tree: Tree::new(area),
|
tree: Tree::new(area),
|
||||||
documents: SlotMap::with_key(),
|
documents: SlotMap::with_key(),
|
||||||
count: None,
|
count: None,
|
||||||
selected_register: RegisterSelection::default(),
|
selected_register: None,
|
||||||
theme: themes.default(),
|
theme: themes.default(),
|
||||||
language_servers,
|
language_servers,
|
||||||
debugger: None,
|
debugger: None,
|
||||||
|
@ -270,26 +276,31 @@ impl Editor {
|
||||||
let mut doc = Document::open(&path, None, Some(&self.theme), Some(&self.syn_loader))?;
|
let mut doc = Document::open(&path, None, Some(&self.theme), Some(&self.syn_loader))?;
|
||||||
|
|
||||||
// try to find a language server based on the language name
|
// try to find a language server based on the language name
|
||||||
let language_server = doc
|
let language_server = doc.language.as_ref().and_then(|language| {
|
||||||
.language
|
self.language_servers
|
||||||
.as_ref()
|
.get(language)
|
||||||
.and_then(|language| self.language_servers.get(language).ok());
|
.map_err(|e| {
|
||||||
|
log::error!("Failed to get LSP, {}, for `{}`", e, language.scope())
|
||||||
|
})
|
||||||
|
.ok()
|
||||||
|
});
|
||||||
|
|
||||||
if let Some(language_server) = language_server {
|
if let Some(language_server) = language_server {
|
||||||
doc.set_language_server(Some(language_server.clone()));
|
|
||||||
|
|
||||||
let language_id = doc
|
let language_id = doc
|
||||||
.language()
|
.language()
|
||||||
.and_then(|s| s.split('.').last()) // source.rust
|
.and_then(|s| s.split('.').last()) // source.rust
|
||||||
.map(ToOwned::to_owned)
|
.map(ToOwned::to_owned)
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
|
|
||||||
|
// TODO: this now races with on_init code if the init happens too quickly
|
||||||
tokio::spawn(language_server.text_document_did_open(
|
tokio::spawn(language_server.text_document_did_open(
|
||||||
doc.url().unwrap(),
|
doc.url().unwrap(),
|
||||||
doc.version(),
|
doc.version(),
|
||||||
doc.text(),
|
doc.text(),
|
||||||
language_id,
|
language_id,
|
||||||
));
|
));
|
||||||
|
|
||||||
|
doc.set_language_server(Some(language_server));
|
||||||
}
|
}
|
||||||
|
|
||||||
let id = self.documents.insert(doc);
|
let id = self.documents.insert(doc);
|
||||||
|
@ -308,14 +319,9 @@ impl Editor {
|
||||||
|
|
||||||
if close_buffer {
|
if close_buffer {
|
||||||
// get around borrowck issues
|
// get around borrowck issues
|
||||||
let language_servers = &mut self.language_servers;
|
|
||||||
let doc = &self.documents[view.doc];
|
let doc = &self.documents[view.doc];
|
||||||
|
|
||||||
let language_server = doc
|
if let Some(language_server) = doc.language_server() {
|
||||||
.language
|
|
||||||
.as_ref()
|
|
||||||
.and_then(|language| language_servers.get(language).ok());
|
|
||||||
if let Some(language_server) = 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);
|
||||||
|
@ -345,20 +351,24 @@ impl Editor {
|
||||||
view.ensure_cursor_in_view(doc, self.config.scrolloff)
|
view.ensure_cursor_in_view(doc, self.config.scrolloff)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[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]
|
||||||
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]
|
||||||
pub fn documents(&self) -> impl Iterator<Item = &Document> {
|
pub fn documents(&self) -> impl Iterator<Item = &Document> {
|
||||||
self.documents.iter().map(|(_id, doc)| doc)
|
self.documents.values()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
pub fn documents_mut(&mut self) -> impl Iterator<Item = &mut Document> {
|
pub fn documents_mut(&mut self) -> impl Iterator<Item = &mut Document> {
|
||||||
self.documents.iter_mut().map(|(_id, doc)| doc)
|
self.documents.values_mut()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn document_by_path<P: AsRef<Path>>(&self, path: P) -> Option<&Document> {
|
pub fn document_by_path<P: AsRef<Path>>(&self, path: P) -> Option<&Document> {
|
||||||
|
@ -366,10 +376,10 @@ impl Editor {
|
||||||
.find(|doc| doc.path().map(|p| p == path.as_ref()).unwrap_or(false))
|
.find(|doc| doc.path().map(|p| p == path.as_ref()).unwrap_or(false))
|
||||||
}
|
}
|
||||||
|
|
||||||
// pub fn current_document(&self) -> Document {
|
pub fn document_by_path_mut<P: AsRef<Path>>(&mut self, path: P) -> Option<&mut Document> {
|
||||||
// let id = self.view().doc;
|
self.documents_mut()
|
||||||
// let doc = &mut editor.documents[id];
|
.find(|doc| doc.path().map(|p| p == path.as_ref()).unwrap_or(false))
|
||||||
// }
|
}
|
||||||
|
|
||||||
pub fn cursor(&self) -> (Option<Position>, CursorKind) {
|
pub fn cursor(&self) -> (Option<Position>, CursorKind) {
|
||||||
let view = view!(self);
|
let view = view!(self);
|
||||||
|
|
|
@ -224,13 +224,13 @@ pub enum Color {
|
||||||
Magenta,
|
Magenta,
|
||||||
Cyan,
|
Cyan,
|
||||||
Gray,
|
Gray,
|
||||||
DarkGray,
|
|
||||||
LightRed,
|
LightRed,
|
||||||
LightGreen,
|
LightGreen,
|
||||||
LightYellow,
|
LightYellow,
|
||||||
LightBlue,
|
LightBlue,
|
||||||
LightMagenta,
|
LightMagenta,
|
||||||
LightCyan,
|
LightCyan,
|
||||||
|
LightGray,
|
||||||
White,
|
White,
|
||||||
Rgb(u8, u8, u8),
|
Rgb(u8, u8, u8),
|
||||||
Indexed(u8),
|
Indexed(u8),
|
||||||
|
@ -250,14 +250,14 @@ impl From<Color> for crossterm::style::Color {
|
||||||
Color::Blue => CColor::DarkBlue,
|
Color::Blue => CColor::DarkBlue,
|
||||||
Color::Magenta => CColor::DarkMagenta,
|
Color::Magenta => CColor::DarkMagenta,
|
||||||
Color::Cyan => CColor::DarkCyan,
|
Color::Cyan => CColor::DarkCyan,
|
||||||
Color::Gray => CColor::Grey,
|
Color::Gray => CColor::DarkGrey,
|
||||||
Color::DarkGray => CColor::DarkGrey,
|
|
||||||
Color::LightRed => CColor::Red,
|
Color::LightRed => CColor::Red,
|
||||||
Color::LightGreen => CColor::Green,
|
Color::LightGreen => CColor::Green,
|
||||||
Color::LightBlue => CColor::Blue,
|
Color::LightBlue => CColor::Blue,
|
||||||
Color::LightYellow => CColor::Yellow,
|
Color::LightYellow => CColor::Yellow,
|
||||||
Color::LightMagenta => CColor::Magenta,
|
Color::LightMagenta => CColor::Magenta,
|
||||||
Color::LightCyan => CColor::Cyan,
|
Color::LightCyan => CColor::Cyan,
|
||||||
|
Color::LightGray => CColor::Grey,
|
||||||
Color::White => CColor::White,
|
Color::White => CColor::White,
|
||||||
Color::Indexed(i) => CColor::AnsiValue(i),
|
Color::Indexed(i) => CColor::AnsiValue(i),
|
||||||
Color::Rgb(r, g, b) => CColor::Rgb { r, g, b },
|
Color::Rgb(r, g, b) => CColor::Rgb { r, g, b },
|
||||||
|
|
|
@ -8,7 +8,6 @@ pub mod graphics;
|
||||||
pub mod info;
|
pub mod info;
|
||||||
pub mod input;
|
pub mod input;
|
||||||
pub mod keyboard;
|
pub mod keyboard;
|
||||||
pub mod register_selection;
|
|
||||||
pub mod theme;
|
pub mod theme;
|
||||||
pub mod tree;
|
pub mod tree;
|
||||||
pub mod view;
|
pub mod view;
|
||||||
|
@ -20,6 +19,5 @@ slotmap::new_key_type! {
|
||||||
|
|
||||||
pub use document::Document;
|
pub use document::Document;
|
||||||
pub use editor::Editor;
|
pub use editor::Editor;
|
||||||
pub use register_selection::RegisterSelection;
|
|
||||||
pub use theme::Theme;
|
pub use theme::Theme;
|
||||||
pub use view::View;
|
pub use view::View;
|
||||||
|
|
|
@ -1,3 +1,14 @@
|
||||||
|
//! These are macros to make getting very nested fields in the `Editor` struct easier
|
||||||
|
//! These are macros instead of functions because functions will have to take `&mut self`
|
||||||
|
//! However, rust doesn't know that you only want a partial borrow instead of borrowing the
|
||||||
|
//! entire struct which `&mut self` says. This makes it impossible to do other mutable
|
||||||
|
//! stuff to the struct because it is already borrowed. Because macros are expanded,
|
||||||
|
//! this circumvents the problem because it is just like indexing fields by hand and then
|
||||||
|
//! putting a `&mut` in front of it. This way rust can see that we are only borrowing a
|
||||||
|
//! part of the struct and not the entire thing.
|
||||||
|
|
||||||
|
/// Get the current view and document mutably as a tuple.
|
||||||
|
/// Returns `(&mut View, &mut Document)`
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
macro_rules! current {
|
macro_rules! current {
|
||||||
( $( $editor:ident ).+ ) => {{
|
( $( $editor:ident ).+ ) => {{
|
||||||
|
@ -7,6 +18,8 @@ macro_rules! current {
|
||||||
}};
|
}};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get the current document mutably.
|
||||||
|
/// Returns `&mut Document`
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
macro_rules! doc_mut {
|
macro_rules! doc_mut {
|
||||||
( $( $editor:ident ).+ ) => {{
|
( $( $editor:ident ).+ ) => {{
|
||||||
|
@ -14,6 +27,8 @@ macro_rules! doc_mut {
|
||||||
}};
|
}};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get the current view mutably.
|
||||||
|
/// Returns `&mut View`
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
macro_rules! view_mut {
|
macro_rules! view_mut {
|
||||||
( $( $editor:ident ).+ ) => {{
|
( $( $editor:ident ).+ ) => {{
|
||||||
|
@ -21,6 +36,8 @@ macro_rules! view_mut {
|
||||||
}};
|
}};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get the current view immutably
|
||||||
|
/// Returns `&View`
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
macro_rules! view {
|
macro_rules! view {
|
||||||
( $( $editor:ident ).+ ) => {{
|
( $( $editor:ident ).+ ) => {{
|
||||||
|
|
|
@ -1,48 +0,0 @@
|
||||||
/// Register selection and configuration
|
|
||||||
///
|
|
||||||
/// This is a kind a of specialized `Option<char>` for register selection.
|
|
||||||
/// Point is to keep whether the register selection has been explicitely
|
|
||||||
/// set or not while being convenient by knowing the default register name.
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct RegisterSelection {
|
|
||||||
selected: char,
|
|
||||||
default_name: char,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl RegisterSelection {
|
|
||||||
pub fn new(default_name: char) -> Self {
|
|
||||||
Self {
|
|
||||||
selected: default_name,
|
|
||||||
default_name,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn select(&mut self, name: char) {
|
|
||||||
self.selected = name;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn take(&mut self) -> Self {
|
|
||||||
Self {
|
|
||||||
selected: std::mem::replace(&mut self.selected, self.default_name),
|
|
||||||
default_name: self.default_name,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn is_default(&self) -> bool {
|
|
||||||
self.selected == self.default_name
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn name(&self) -> char {
|
|
||||||
self.selected
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for RegisterSelection {
|
|
||||||
fn default() -> Self {
|
|
||||||
let default_name = '"';
|
|
||||||
Self {
|
|
||||||
selected: default_name,
|
|
||||||
default_name,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -5,6 +5,7 @@ use std::{
|
||||||
};
|
};
|
||||||
|
|
||||||
use anyhow::Context;
|
use anyhow::Context;
|
||||||
|
use helix_core::hashmap;
|
||||||
use log::warn;
|
use log::warn;
|
||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
use serde::{Deserialize, Deserializer};
|
use serde::{Deserialize, Deserializer};
|
||||||
|
@ -142,13 +143,37 @@ struct ThemePalette {
|
||||||
|
|
||||||
impl Default for ThemePalette {
|
impl Default for ThemePalette {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self::new(HashMap::new())
|
Self {
|
||||||
|
palette: hashmap! {
|
||||||
|
"black".to_string() => Color::Black,
|
||||||
|
"red".to_string() => Color::Red,
|
||||||
|
"green".to_string() => Color::Green,
|
||||||
|
"yellow".to_string() => Color::Yellow,
|
||||||
|
"blue".to_string() => Color::Blue,
|
||||||
|
"magenta".to_string() => Color::Magenta,
|
||||||
|
"cyan".to_string() => Color::Cyan,
|
||||||
|
"gray".to_string() => Color::Gray,
|
||||||
|
"light-red".to_string() => Color::LightRed,
|
||||||
|
"light-green".to_string() => Color::LightGreen,
|
||||||
|
"light-yellow".to_string() => Color::LightYellow,
|
||||||
|
"light-blue".to_string() => Color::LightBlue,
|
||||||
|
"light-magenta".to_string() => Color::LightMagenta,
|
||||||
|
"light-cyan".to_string() => Color::LightCyan,
|
||||||
|
"light-gray".to_string() => Color::LightGray,
|
||||||
|
"white".to_string() => Color::White,
|
||||||
|
},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ThemePalette {
|
impl ThemePalette {
|
||||||
pub fn new(palette: HashMap<String, Color>) -> Self {
|
pub fn new(palette: HashMap<String, Color>) -> Self {
|
||||||
Self { palette }
|
let ThemePalette {
|
||||||
|
palette: mut default,
|
||||||
|
} = ThemePalette::default();
|
||||||
|
|
||||||
|
default.extend(palette);
|
||||||
|
Self { palette: default }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn hex_string_to_rgb(s: &str) -> Result<Color, String> {
|
pub fn hex_string_to_rgb(s: &str) -> Result<Color, String> {
|
||||||
|
|
|
@ -65,6 +65,7 @@ file-types = ["ex", "exs"]
|
||||||
roots = []
|
roots = []
|
||||||
comment-token = "#"
|
comment-token = "#"
|
||||||
|
|
||||||
|
language-server = { command = "elixir-ls" }
|
||||||
indent = { tab-width = 2, unit = " " }
|
indent = { tab-width = 2, unit = " " }
|
||||||
|
|
||||||
[[language]]
|
[[language]]
|
||||||
|
@ -198,6 +199,17 @@ roots = []
|
||||||
language-server = { command = "typescript-language-server", args = ["--stdio"] }
|
language-server = { command = "typescript-language-server", args = ["--stdio"] }
|
||||||
indent = { tab-width = 2, unit = " " }
|
indent = { tab-width = 2, unit = " " }
|
||||||
|
|
||||||
|
[[language]]
|
||||||
|
name = "tsx"
|
||||||
|
scope = "source.tsx"
|
||||||
|
injection-regex = "^(tsx)$" # |typescript
|
||||||
|
file-types = ["tsx"]
|
||||||
|
roots = []
|
||||||
|
# TODO: highlights-jsx, highlights-params
|
||||||
|
|
||||||
|
language-server = { command = "typescript-language-server", args = ["--stdio"] }
|
||||||
|
indent = { tab-width = 2, unit = " " }
|
||||||
|
|
||||||
[[language]]
|
[[language]]
|
||||||
name = "css"
|
name = "css"
|
||||||
scope = "source.css"
|
scope = "source.css"
|
||||||
|
@ -236,6 +248,7 @@ file-types = ["nix"]
|
||||||
roots = []
|
roots = []
|
||||||
comment-token = "#"
|
comment-token = "#"
|
||||||
|
|
||||||
|
language-server = { command = "rnix-lsp" }
|
||||||
indent = { tab-width = 2, unit = " " }
|
indent = { tab-width = 2, unit = " " }
|
||||||
|
|
||||||
[[language]]
|
[[language]]
|
||||||
|
@ -286,7 +299,22 @@ injection-regex = "julia"
|
||||||
file-types = ["jl"]
|
file-types = ["jl"]
|
||||||
roots = []
|
roots = []
|
||||||
comment-token = "#"
|
comment-token = "#"
|
||||||
language-server = { command = "julia", args = [ "--startup-file=no", "--history-file=no", "-e", "using LanguageServer;using Pkg;import StaticLint;import SymbolServer;env_path = dirname(Pkg.Types.Context().env.project_file);server = LanguageServer.LanguageServerInstance(stdin, stdout, env_path, \"\");server.runlinter = true;run(server);" ] }
|
language-server = { command = "julia", args = [
|
||||||
|
"--startup-file=no",
|
||||||
|
"--history-file=no",
|
||||||
|
"--quiet",
|
||||||
|
"-e",
|
||||||
|
"""
|
||||||
|
using LanguageServer;
|
||||||
|
using Pkg;
|
||||||
|
import StaticLint;
|
||||||
|
env_path = dirname(Pkg.Types.Context().env.project_file);
|
||||||
|
|
||||||
|
server = LanguageServer.LanguageServerInstance(stdin, stdout, env_path, "");
|
||||||
|
server.runlinter = true;
|
||||||
|
run(server);
|
||||||
|
""",
|
||||||
|
] }
|
||||||
indent = { tab-width = 2, unit = " " }
|
indent = { tab-width = 2, unit = " " }
|
||||||
|
|
||||||
[[language]]
|
[[language]]
|
||||||
|
@ -331,6 +359,15 @@ roots = []
|
||||||
comment-token = "--"
|
comment-token = "--"
|
||||||
indent = { tab-width = 2, unit = " " }
|
indent = { tab-width = 2, unit = " " }
|
||||||
|
|
||||||
|
[[language]]
|
||||||
|
name = "svelte"
|
||||||
|
scope = "source.svelte"
|
||||||
|
injection-regex = "svelte"
|
||||||
|
file-types = ["svelte"]
|
||||||
|
roots = []
|
||||||
|
indent = { tab-width = 2, unit = " " }
|
||||||
|
language-server = { command = "svelteserver", args = ["--stdio"] }
|
||||||
|
|
||||||
[[language]]
|
[[language]]
|
||||||
name = "yaml"
|
name = "yaml"
|
||||||
scope = "source.yaml"
|
scope = "source.yaml"
|
||||||
|
|
|
@ -61,7 +61,7 @@
|
||||||
|
|
||||||
(null) @constant
|
(null) @constant
|
||||||
(number_literal) @number
|
(number_literal) @number
|
||||||
(char_literal) @number
|
(char_literal) @string
|
||||||
|
|
||||||
(call_expression
|
(call_expression
|
||||||
function: (identifier) @function)
|
function: (identifier) @function)
|
||||||
|
|
|
@ -17,9 +17,18 @@
|
||||||
|
|
||||||
; Identifiers
|
; Identifiers
|
||||||
|
|
||||||
|
((identifier) @constant (match? @constant "^[A-Z][A-Z\\d_]+$"))
|
||||||
|
(const_spec
|
||||||
|
name: (identifier) @constant)
|
||||||
|
|
||||||
|
(parameter_declaration (identifier) @variable.parameter)
|
||||||
|
(variadic_parameter_declaration (identifier) @variable.parameter)
|
||||||
|
|
||||||
(type_identifier) @type
|
(type_identifier) @type
|
||||||
(field_identifier) @property
|
(field_identifier) @property
|
||||||
(identifier) @variable
|
(identifier) @variable
|
||||||
|
(package_identifier) @variable
|
||||||
|
|
||||||
|
|
||||||
; Operators
|
; Operators
|
||||||
|
|
||||||
|
@ -79,10 +88,8 @@
|
||||||
"go"
|
"go"
|
||||||
"goto"
|
"goto"
|
||||||
"if"
|
"if"
|
||||||
"import"
|
|
||||||
"interface"
|
"interface"
|
||||||
"map"
|
"map"
|
||||||
"package"
|
|
||||||
"range"
|
"range"
|
||||||
"return"
|
"return"
|
||||||
"select"
|
"select"
|
||||||
|
@ -92,6 +99,29 @@
|
||||||
"var"
|
"var"
|
||||||
] @keyword
|
] @keyword
|
||||||
|
|
||||||
|
[
|
||||||
|
"import"
|
||||||
|
"package"
|
||||||
|
] @keyword.control.import
|
||||||
|
|
||||||
|
; Delimiters
|
||||||
|
|
||||||
|
[
|
||||||
|
":"
|
||||||
|
"."
|
||||||
|
","
|
||||||
|
";"
|
||||||
|
] @punctuation.delimiter
|
||||||
|
|
||||||
|
[
|
||||||
|
"("
|
||||||
|
")"
|
||||||
|
"["
|
||||||
|
"]"
|
||||||
|
"{"
|
||||||
|
"}"
|
||||||
|
] @punctuation.bracket
|
||||||
|
|
||||||
; Literals
|
; Literals
|
||||||
|
|
||||||
[
|
[
|
||||||
|
@ -111,7 +141,8 @@
|
||||||
[
|
[
|
||||||
(true)
|
(true)
|
||||||
(false)
|
(false)
|
||||||
(nil)
|
] @constant.builtin.boolean
|
||||||
] @constant.builtin
|
|
||||||
|
(nil) @constant.builtin
|
||||||
|
|
||||||
(comment) @comment
|
(comment) @comment
|
||||||
|
|
|
@ -0,0 +1,30 @@
|
||||||
|
; Scopes
|
||||||
|
|
||||||
|
(block) @local.scope
|
||||||
|
|
||||||
|
; Definitions
|
||||||
|
|
||||||
|
(parameter_declaration (identifier) @local.definition)
|
||||||
|
(variadic_parameter_declaration (identifier) @local.definition)
|
||||||
|
|
||||||
|
(short_var_declaration
|
||||||
|
left: (expression_list
|
||||||
|
(identifier) @local.definition))
|
||||||
|
|
||||||
|
(var_spec
|
||||||
|
name: (identifier) @local.definition)
|
||||||
|
|
||||||
|
(for_statement
|
||||||
|
(range_clause
|
||||||
|
left: (expression_list
|
||||||
|
(identifier) @local.definition)))
|
||||||
|
|
||||||
|
(const_declaration
|
||||||
|
(const_spec
|
||||||
|
name: (identifier) @local.definition))
|
||||||
|
|
||||||
|
; References
|
||||||
|
|
||||||
|
(identifier) @local.reference
|
||||||
|
(field_identifier) @local.reference
|
||||||
|
|
|
@ -2,19 +2,19 @@
|
||||||
(operator) @operator
|
(operator) @operator
|
||||||
(exp_name (constructor) @constructor)
|
(exp_name (constructor) @constructor)
|
||||||
(constructor_operator) @operator
|
(constructor_operator) @operator
|
||||||
(module) @module_name
|
(module) @namespace
|
||||||
(type) @type
|
(type) @type
|
||||||
(type) @class
|
(type) @class
|
||||||
(constructor) @constructor
|
(constructor) @constructor
|
||||||
(pragma) @pragma
|
(pragma) @pragma
|
||||||
(comment) @comment
|
(comment) @comment
|
||||||
(signature name: (variable) @fun_type_name)
|
(signature name: (variable) @fun_type_name)
|
||||||
(function name: (variable) @fun_name)
|
(function name: (variable) @function)
|
||||||
(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) @literal
|
(integer) @number
|
||||||
(exp_literal (float)) @literal
|
(exp_literal (float)) @number
|
||||||
(char) @literal
|
(char) @literal
|
||||||
(con_unit) @literal
|
(con_unit) @literal
|
||||||
(con_list) @literal
|
(con_list) @literal
|
||||||
|
@ -39,5 +39,7 @@
|
||||||
"do" @keyword
|
"do" @keyword
|
||||||
"mdo" @keyword
|
"mdo" @keyword
|
||||||
"rec" @keyword
|
"rec" @keyword
|
||||||
"(" @paren
|
[
|
||||||
")" @paren
|
"("
|
||||||
|
")"
|
||||||
|
] @punctuation.bracket
|
||||||
|
|
|
@ -87,7 +87,7 @@
|
||||||
(template_string)
|
(template_string)
|
||||||
] @string
|
] @string
|
||||||
|
|
||||||
(regex) @string.special
|
(regex) @string.regexp
|
||||||
(number) @number
|
(number) @number
|
||||||
|
|
||||||
; Tokens
|
; Tokens
|
||||||
|
|
|
@ -1,9 +1,3 @@
|
||||||
(identifier) @variable
|
|
||||||
;; In case you want type highlighting based on Julia naming conventions (this might collide with mathematical notation)
|
|
||||||
;((identifier) @type ; exception: mark `A_foo` sort of identifiers as variables
|
|
||||||
;(match? @type "^[A-Z][^_]"))
|
|
||||||
((identifier) @constant
|
|
||||||
(match? @constant "^[A-Z][A-Z_]{2}[A-Z_]*$"))
|
|
||||||
|
|
||||||
[
|
[
|
||||||
(triple_string)
|
(triple_string)
|
||||||
|
@ -28,43 +22,43 @@
|
||||||
(call_expression
|
(call_expression
|
||||||
(identifier) @function)
|
(identifier) @function)
|
||||||
(call_expression
|
(call_expression
|
||||||
(field_expression (identifier) @method .))
|
(field_expression (identifier) @function.method .))
|
||||||
(broadcast_call_expression
|
(broadcast_call_expression
|
||||||
(identifier) @function)
|
(identifier) @function)
|
||||||
(broadcast_call_expression
|
(broadcast_call_expression
|
||||||
(field_expression (identifier) @method .))
|
(field_expression (identifier) @function.method .))
|
||||||
(parameter_list
|
(parameter_list
|
||||||
(identifier) @parameter)
|
(identifier) @variable.parameter)
|
||||||
(parameter_list
|
(parameter_list
|
||||||
(optional_parameter .
|
(optional_parameter .
|
||||||
(identifier) @parameter))
|
(identifier) @variable.parameter))
|
||||||
(typed_parameter
|
(typed_parameter
|
||||||
(identifier) @parameter
|
(identifier) @variable.parameter
|
||||||
(identifier) @type)
|
(identifier) @type)
|
||||||
(type_parameter_list
|
(type_parameter_list
|
||||||
(identifier) @type)
|
(identifier) @type)
|
||||||
(typed_parameter
|
(typed_parameter
|
||||||
(identifier) @parameter
|
(identifier) @variable.parameter
|
||||||
(parameterized_identifier) @type)
|
(parameterized_identifier) @type)
|
||||||
(function_expression
|
(function_expression
|
||||||
. (identifier) @parameter)
|
. (identifier) @variable.parameter)
|
||||||
(spread_parameter) @parameter
|
(spread_parameter) @variable.parameter
|
||||||
(spread_parameter
|
(spread_parameter
|
||||||
(identifier) @parameter)
|
(identifier) @variable.parameter)
|
||||||
(named_argument
|
(named_argument
|
||||||
. (identifier) @parameter)
|
. (identifier) @variable.parameter)
|
||||||
(argument_list
|
(argument_list
|
||||||
(typed_expression
|
(typed_expression
|
||||||
(identifier) @parameter
|
(identifier) @variable.parameter
|
||||||
(identifier) @type))
|
(identifier) @type))
|
||||||
(argument_list
|
(argument_list
|
||||||
(typed_expression
|
(typed_expression
|
||||||
(identifier) @parameter
|
(identifier) @variable.parameter
|
||||||
(parameterized_identifier) @type))
|
(parameterized_identifier) @type))
|
||||||
|
|
||||||
;; Symbol expressions (:my-wanna-be-lisp-keyword)
|
;; Symbol expressions (:my-wanna-be-lisp-keyword)
|
||||||
(quote_expression
|
(quote_expression
|
||||||
(identifier)) @symbol
|
(identifier)) @string.special.symbol
|
||||||
|
|
||||||
;; Parsing error! foo (::Type) get's parsed as two quote expressions
|
;; Parsing error! foo (::Type) get's parsed as two quote expressions
|
||||||
(argument_list
|
(argument_list
|
||||||
|
@ -76,7 +70,7 @@
|
||||||
(identifier) @type)
|
(identifier) @type)
|
||||||
(parameterized_identifier (_)) @type
|
(parameterized_identifier (_)) @type
|
||||||
(argument_list
|
(argument_list
|
||||||
(typed_expression . (identifier) @parameter))
|
(typed_expression . (identifier) @variable.parameter))
|
||||||
|
|
||||||
(typed_expression
|
(typed_expression
|
||||||
(identifier) @type .)
|
(identifier) @type .)
|
||||||
|
@ -113,13 +107,13 @@
|
||||||
"end" @keyword
|
"end" @keyword
|
||||||
|
|
||||||
(if_statement
|
(if_statement
|
||||||
["if" "end"] @conditional)
|
["if" "end"] @keyword.control.conditional)
|
||||||
(elseif_clause
|
(elseif_clause
|
||||||
["elseif"] @conditional)
|
["elseif"] @keyword.control.conditional)
|
||||||
(else_clause
|
(else_clause
|
||||||
["else"] @conditional)
|
["else"] @keyword.control.conditional)
|
||||||
(ternary_expression
|
(ternary_expression
|
||||||
["?" ":"] @conditional)
|
["?" ":"] @keyword.control.conditional)
|
||||||
|
|
||||||
(function_definition ["function" "end"] @keyword.function)
|
(function_definition ["function" "end"] @keyword.function)
|
||||||
|
|
||||||
|
@ -134,47 +128,57 @@
|
||||||
"type"
|
"type"
|
||||||
] @keyword
|
] @keyword
|
||||||
|
|
||||||
((identifier) @keyword (#any-of? @keyword "global" "local"))
|
((identifier) @keyword (match? @keyword "global|local"))
|
||||||
|
|
||||||
(compound_expression
|
(compound_expression
|
||||||
["begin" "end"] @keyword)
|
["begin" "end"] @keyword)
|
||||||
(try_statement
|
(try_statement
|
||||||
["try" "end" ] @exception)
|
["try" "end" ] @keyword.control.exception)
|
||||||
(finally_clause
|
(finally_clause
|
||||||
"finally" @exception)
|
"finally" @keyword.control.exception)
|
||||||
(catch_clause
|
(catch_clause
|
||||||
"catch" @exception)
|
"catch" @keyword.control.exception)
|
||||||
(quote_statement
|
(quote_statement
|
||||||
["quote" "end"] @keyword)
|
["quote" "end"] @keyword)
|
||||||
(let_statement
|
(let_statement
|
||||||
["let" "end"] @keyword)
|
["let" "end"] @keyword)
|
||||||
(for_statement
|
(for_statement
|
||||||
["for" "end"] @repeat)
|
["for" "end"] @keyword.control.repeat)
|
||||||
(while_statement
|
(while_statement
|
||||||
["while" "end"] @repeat)
|
["while" "end"] @keyword.control.repeat)
|
||||||
(break_statement) @repeat
|
(break_statement) @keyword.control.repeat
|
||||||
(continue_statement) @repeat
|
(continue_statement) @keyword.control.repeat
|
||||||
(for_binding
|
(for_binding
|
||||||
"in" @repeat)
|
"in" @keyword.control.repeat)
|
||||||
(for_clause
|
(for_clause
|
||||||
"for" @repeat)
|
"for" @keyword.control.repeat)
|
||||||
(do_clause
|
(do_clause
|
||||||
["do" "end"] @keyword)
|
["do" "end"] @keyword)
|
||||||
|
|
||||||
(export_statement
|
(export_statement
|
||||||
["export"] @include)
|
["export"] @keyword.control.import)
|
||||||
|
|
||||||
[
|
[
|
||||||
"using"
|
"using"
|
||||||
"module"
|
"module"
|
||||||
"import"
|
"import"
|
||||||
] @include
|
] @keyword.control.import
|
||||||
|
|
||||||
((identifier) @include (#eq? @include "baremodule"))
|
((identifier) @keyword.control.import (#eq? @keyword.control.import "baremodule"))
|
||||||
|
|
||||||
(((identifier) @constant.builtin) (match? @constant.builtin "^(nothing|Inf|NaN)$"))
|
(((identifier) @constant.builtin) (match? @constant.builtin "^(nothing|Inf|NaN)$"))
|
||||||
(((identifier) @boolean) (eq? @boolean "true"))
|
(((identifier) @constant.builtin.boolean) (#eq? @constant.builtin.boolean "true"))
|
||||||
(((identifier) @boolean) (eq? @boolean "false"))
|
(((identifier) @constant.builtin.boolean) (#eq? @constant.builtin.boolean "false"))
|
||||||
|
|
||||||
|
|
||||||
["::" ":" "." "," "..." "!"] @punctuation.delimiter
|
["::" ":" "." "," "..." "!"] @punctuation.delimiter
|
||||||
["[" "]" "(" ")" "{" "}"] @punctuation.bracket
|
["[" "]" "(" ")" "{" "}"] @punctuation.bracket
|
||||||
|
|
||||||
|
["="] @operator
|
||||||
|
|
||||||
|
(identifier) @variable
|
||||||
|
;; In case you want type highlighting based on Julia naming conventions (this might collide with mathematical notation)
|
||||||
|
;((identifier) @type ; exception: mark `A_foo` sort of identifiers as variables
|
||||||
|
;(match? @type "^[A-Z][^_]"))
|
||||||
|
((identifier) @constant
|
||||||
|
(match? @constant "^[A-Z][A-Z_]{2}[A-Z_]*$"))
|
||||||
|
|
|
@ -259,7 +259,7 @@
|
||||||
|
|
||||||
(comment) @comment
|
(comment) @comment
|
||||||
|
|
||||||
(bracket_group) @parameter
|
(bracket_group) @variable.parameter
|
||||||
|
|
||||||
[(math_operator) "="] @operator
|
[(math_operator) "="] @operator
|
||||||
|
|
||||||
|
@ -312,7 +312,7 @@
|
||||||
key: (word) @text.reference)
|
key: (word) @text.reference)
|
||||||
|
|
||||||
(key_val_pair
|
(key_val_pair
|
||||||
key: (_) @parameter
|
key: (_) @variable.parameter
|
||||||
value: (_))
|
value: (_))
|
||||||
|
|
||||||
["[" "]" "{" "}"] @punctuation.bracket ;"(" ")" is has no special meaning in LaTeX
|
["[" "]" "{" "}"] @punctuation.bracket ;"(" ")" is has no special meaning in LaTeX
|
||||||
|
|
|
@ -1 +1,2 @@
|
||||||
(comment) @comment
|
(comment) @comment
|
||||||
|
(note) @comment
|
||||||
|
|
|
@ -23,27 +23,27 @@
|
||||||
"for"
|
"for"
|
||||||
"do"
|
"do"
|
||||||
"end"
|
"end"
|
||||||
] @keyword.control.loop)
|
] @keyword.control.repeat)
|
||||||
|
|
||||||
(for_in_statement
|
(for_in_statement
|
||||||
[
|
[
|
||||||
"for"
|
"for"
|
||||||
"do"
|
"do"
|
||||||
"end"
|
"end"
|
||||||
] @keyword.control.loop)
|
] @keyword.control.repeat)
|
||||||
|
|
||||||
(while_statement
|
(while_statement
|
||||||
[
|
[
|
||||||
"while"
|
"while"
|
||||||
"do"
|
"do"
|
||||||
"end"
|
"end"
|
||||||
] @keyword.control.loop)
|
] @keyword.control.repeat)
|
||||||
|
|
||||||
(repeat_statement
|
(repeat_statement
|
||||||
[
|
[
|
||||||
"repeat"
|
"repeat"
|
||||||
"until"
|
"until"
|
||||||
] @keyword.control.loop)
|
] @keyword.control.repeat)
|
||||||
|
|
||||||
(do_statement
|
(do_statement
|
||||||
[
|
[
|
||||||
|
@ -65,7 +65,7 @@
|
||||||
"not"
|
"not"
|
||||||
"and"
|
"and"
|
||||||
"or"
|
"or"
|
||||||
] @keyword.operator
|
] @operator
|
||||||
|
|
||||||
[
|
[
|
||||||
"="
|
"="
|
||||||
|
@ -108,7 +108,7 @@
|
||||||
[
|
[
|
||||||
(false)
|
(false)
|
||||||
(true)
|
(true)
|
||||||
] @boolean
|
] @constant.builtin.boolean
|
||||||
(nil) @constant.builtin
|
(nil) @constant.builtin
|
||||||
(spread) @constant ;; "..."
|
(spread) @constant ;; "..."
|
||||||
((identifier) @constant
|
((identifier) @constant
|
||||||
|
@ -116,7 +116,7 @@
|
||||||
|
|
||||||
;; Parameters
|
;; Parameters
|
||||||
(parameters
|
(parameters
|
||||||
(identifier) @parameter)
|
(identifier) @variable.parameter)
|
||||||
|
|
||||||
; ;; Functions
|
; ;; Functions
|
||||||
(function [(function_name) (identifier)] @function)
|
(function [(function_name) (identifier)] @function)
|
||||||
|
@ -139,8 +139,8 @@
|
||||||
|
|
||||||
(function_call
|
(function_call
|
||||||
[
|
[
|
||||||
((identifier) @variable (method) @method)
|
((identifier) @variable (method) @function.method)
|
||||||
((_) (method) @method)
|
((_) (method) @function.method)
|
||||||
(identifier) @function
|
(identifier) @function
|
||||||
(field_expression (property_identifier) @function)
|
(field_expression (property_identifier) @function)
|
||||||
]
|
]
|
||||||
|
|
|
@ -25,12 +25,12 @@
|
||||||
|
|
||||||
(external (value_name) @function)
|
(external (value_name) @function)
|
||||||
|
|
||||||
(method_name) @method
|
(method_name) @function.method
|
||||||
|
|
||||||
; Variables
|
; Variables
|
||||||
;----------
|
;----------
|
||||||
|
|
||||||
(value_pattern) @parameter
|
(value_pattern) @variable.parameter
|
||||||
|
|
||||||
; Application
|
; Application
|
||||||
;------------
|
;------------
|
||||||
|
@ -60,7 +60,7 @@
|
||||||
|
|
||||||
[(number) (signed_number)] @number
|
[(number) (signed_number)] @number
|
||||||
|
|
||||||
(character) @character
|
(character) @constant.character
|
||||||
|
|
||||||
(string) @string
|
(string) @string
|
||||||
|
|
||||||
|
@ -92,7 +92,7 @@
|
||||||
|
|
||||||
["include" "open"] @include
|
["include" "open"] @include
|
||||||
|
|
||||||
["for" "to" "downto" "while" "do" "done"] @keyword.control.loop
|
["for" "to" "downto" "while" "do" "done"] @keyword.control.repeat
|
||||||
|
|
||||||
; Macros
|
; Macros
|
||||||
;-------
|
;-------
|
||||||
|
|
|
@ -100,7 +100,7 @@
|
||||||
(bare_symbol)
|
(bare_symbol)
|
||||||
] @string.special.symbol
|
] @string.special.symbol
|
||||||
|
|
||||||
(regex) @string.special.regex
|
(regex) @string.regexp
|
||||||
(escape_sequence) @escape
|
(escape_sequence) @escape
|
||||||
|
|
||||||
[
|
[
|
||||||
|
|
|
@ -17,7 +17,7 @@
|
||||||
|
|
||||||
(escape_sequence) @escape
|
(escape_sequence) @escape
|
||||||
(primitive_type) @type.builtin
|
(primitive_type) @type.builtin
|
||||||
(boolean_literal) @constant.builtin
|
(boolean_literal) @constant.builtin.boolean
|
||||||
[
|
[
|
||||||
(integer_literal)
|
(integer_literal)
|
||||||
(float_literal)
|
(float_literal)
|
||||||
|
@ -149,7 +149,7 @@
|
||||||
|
|
||||||
(mutable_specifier) @keyword.mut
|
(mutable_specifier) @keyword.mut
|
||||||
|
|
||||||
|
; TODO: variable.mut to highlight mutable identifiers via locals.scm
|
||||||
|
|
||||||
; -------
|
; -------
|
||||||
; Guess Other Types
|
; Guess Other Types
|
||||||
|
|
|
@ -0,0 +1,17 @@
|
||||||
|
; Scopes
|
||||||
|
|
||||||
|
(block) @local.scope
|
||||||
|
|
||||||
|
; Definitions
|
||||||
|
|
||||||
|
(parameter
|
||||||
|
(identifier) @local.definition)
|
||||||
|
|
||||||
|
(let_declaration
|
||||||
|
pattern: (identifier) @local.definition)
|
||||||
|
|
||||||
|
(closure_parameters (identifier)) @local.definition
|
||||||
|
|
||||||
|
; References
|
||||||
|
(identifier) @local.reference
|
||||||
|
|
|
@ -0,0 +1,68 @@
|
||||||
|
; Special identifiers
|
||||||
|
;--------------------
|
||||||
|
|
||||||
|
; TODO:
|
||||||
|
((element (start_tag (tag_name) @_tag) (text) @markup.heading)
|
||||||
|
(#match? @_tag "^(h[0-9]|title)$"))
|
||||||
|
|
||||||
|
((element (start_tag (tag_name) @_tag) (text) @markup.bold)
|
||||||
|
(#match? @_tag "^(strong|b)$"))
|
||||||
|
|
||||||
|
((element (start_tag (tag_name) @_tag) (text) @markup.italic)
|
||||||
|
(#match? @_tag "^(em|i)$"))
|
||||||
|
|
||||||
|
; ((element (start_tag (tag_name) @_tag) (text) @markup.strike)
|
||||||
|
; (#match? @_tag "^(s|del)$"))
|
||||||
|
|
||||||
|
((element (start_tag (tag_name) @_tag) (text) @markup.underline)
|
||||||
|
(#eq? @_tag "u"))
|
||||||
|
|
||||||
|
((element (start_tag (tag_name) @_tag) (text) @markup.inline)
|
||||||
|
(#match? @_tag "^(code|kbd)$"))
|
||||||
|
|
||||||
|
((element (start_tag (tag_name) @_tag) (text) @markup.underline.link)
|
||||||
|
(#eq? @_tag "a"))
|
||||||
|
|
||||||
|
((attribute
|
||||||
|
(attribute_name) @_attr
|
||||||
|
(quoted_attribute_value (attribute_value) @markup.undeline.link))
|
||||||
|
(#match? @_attr "^(href|src)$"))
|
||||||
|
|
||||||
|
(tag_name) @tag
|
||||||
|
(attribute_name) @property
|
||||||
|
(erroneous_end_tag_name) @error
|
||||||
|
(comment) @comment
|
||||||
|
|
||||||
|
[
|
||||||
|
(attribute_value)
|
||||||
|
(quoted_attribute_value)
|
||||||
|
] @string
|
||||||
|
|
||||||
|
[
|
||||||
|
(text)
|
||||||
|
(raw_text_expr)
|
||||||
|
] @none
|
||||||
|
|
||||||
|
[
|
||||||
|
(special_block_keyword)
|
||||||
|
(then)
|
||||||
|
(as)
|
||||||
|
] @keyword
|
||||||
|
|
||||||
|
[
|
||||||
|
"{"
|
||||||
|
"}"
|
||||||
|
] @punctuation.brackets
|
||||||
|
|
||||||
|
"=" @operator
|
||||||
|
|
||||||
|
[
|
||||||
|
"<"
|
||||||
|
">"
|
||||||
|
"</"
|
||||||
|
"/>"
|
||||||
|
"#"
|
||||||
|
":"
|
||||||
|
"/"
|
||||||
|
"@"
|
||||||
|
] @punctuation.definition.tag
|
|
@ -0,0 +1,18 @@
|
||||||
|
indent = [
|
||||||
|
"element"
|
||||||
|
"if_statement"
|
||||||
|
"each_statement"
|
||||||
|
"await_statement"
|
||||||
|
]
|
||||||
|
|
||||||
|
outdent = [
|
||||||
|
"end_tag"
|
||||||
|
"else_statement"
|
||||||
|
"if_end_expr"
|
||||||
|
"each_end_expr"
|
||||||
|
"await_end_expr"
|
||||||
|
">"
|
||||||
|
"/>"
|
||||||
|
]
|
||||||
|
|
||||||
|
ignore = "comment"
|
|
@ -0,0 +1,30 @@
|
||||||
|
; injections.scm
|
||||||
|
; --------------
|
||||||
|
((style_element
|
||||||
|
(raw_text) @injection.content)
|
||||||
|
(#set! injection.language "css"))
|
||||||
|
|
||||||
|
((attribute
|
||||||
|
(attribute_name) @_attr
|
||||||
|
(quoted_attribute_value (attribute_value) @css))
|
||||||
|
(#eq? @_attr "style"))
|
||||||
|
|
||||||
|
((script_element
|
||||||
|
(raw_text) @injection.content)
|
||||||
|
(#set! injection.language "javascript"))
|
||||||
|
|
||||||
|
((raw_text_expr) @injection.content
|
||||||
|
(#set! injection.language "javascript"))
|
||||||
|
|
||||||
|
(
|
||||||
|
(script_element
|
||||||
|
(start_tag
|
||||||
|
(attribute
|
||||||
|
(quoted_attribute_value (attribute_value) @_lang)))
|
||||||
|
(raw_text) @injection.content)
|
||||||
|
(#match? @_lang "(ts|typescript)")
|
||||||
|
(#set! injection.language "typescript")
|
||||||
|
)
|
||||||
|
|
||||||
|
(comment) @comment
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
; inherits: typescript
|
|
@ -1,6 +1,6 @@
|
||||||
(block_mapping_pair key: (_) @property)
|
(block_mapping_pair key: (_) @property)
|
||||||
(flow_mapping (_ key: (_) @property))
|
(flow_mapping (_ key: (_) @property))
|
||||||
(boolean_scalar) @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
|
||||||
|
|
|
@ -34,6 +34,7 @@
|
||||||
"comment" = { fg = "#6A9955" }
|
"comment" = { fg = "#6A9955" }
|
||||||
|
|
||||||
"string" = { fg = "#ce9178" }
|
"string" = { fg = "#ce9178" }
|
||||||
|
"string.regexp" = { fg = "regex" }
|
||||||
"number" = { fg = "#b5cea8" }
|
"number" = { fg = "#b5cea8" }
|
||||||
"escape" = { fg = "#d7ba7d" }
|
"escape" = { fg = "#d7ba7d" }
|
||||||
|
|
||||||
|
@ -61,7 +62,7 @@
|
||||||
"ui.text.focus" = { fg = "#ffffff" }
|
"ui.text.focus" = { fg = "#ffffff" }
|
||||||
|
|
||||||
"warning" = { fg = "#cca700" }
|
"warning" = { fg = "#cca700" }
|
||||||
"error" = { fg = "#f48771" }
|
"error" = { fg = "#ff1212" }
|
||||||
"info" = { fg = "#75beff" }
|
"info" = { fg = "#75beff" }
|
||||||
"hint" = { fg = "#eeeeeeb3" }
|
"hint" = { fg = "#eeeeeeb3" }
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,87 @@
|
||||||
|
# Everforest (Dark Hard)
|
||||||
|
# Author: CptPotato
|
||||||
|
|
||||||
|
# Original Author:
|
||||||
|
# URL: https://github.com/sainnhe/everforest
|
||||||
|
# Filename: autoload/everforest.vim
|
||||||
|
# Author: sainnhe
|
||||||
|
# Email: sainnhe@gmail.com
|
||||||
|
# License: MIT License
|
||||||
|
|
||||||
|
"escape" = "orange"
|
||||||
|
"type" = "yellow"
|
||||||
|
"constant" = "purple"
|
||||||
|
"number" = "purple"
|
||||||
|
"string" = "grey2"
|
||||||
|
"comment" = "grey0"
|
||||||
|
"variable" = "fg"
|
||||||
|
"variable.builtin" = "blue"
|
||||||
|
"variable.parameter" = "fg"
|
||||||
|
"variable.property" = "fg"
|
||||||
|
"label" = "aqua"
|
||||||
|
"punctuation" = "grey2"
|
||||||
|
"punctuation.delimiter" = "grey2"
|
||||||
|
"punctuation.bracket" = "fg"
|
||||||
|
"keyword" = "red"
|
||||||
|
"operator" = "orange"
|
||||||
|
"function" = "green"
|
||||||
|
"function.builtin" = "blue"
|
||||||
|
"function.macro" = "aqua"
|
||||||
|
"tag" = "yellow"
|
||||||
|
"namespace" = "aqua"
|
||||||
|
"attribute" = "aqua"
|
||||||
|
"constructor" = "yellow"
|
||||||
|
"module" = "blue"
|
||||||
|
"property" = "fg"
|
||||||
|
"special" = "orange"
|
||||||
|
|
||||||
|
"ui.background" = { bg = "bg0" }
|
||||||
|
"ui.cursor" = { fg = "bg0", bg = "fg" }
|
||||||
|
"ui.cursor.match" = { fg = "orange", bg = "bg_yellow" }
|
||||||
|
"ui.cursor.insert" = { fg = "bg0", bg = "grey1" }
|
||||||
|
"ui.cursor.select" = { fg = "bg0", bg = "blue" }
|
||||||
|
"ui.linenr" = "grey0"
|
||||||
|
"ui.linenr.selected" = "fg"
|
||||||
|
"ui.statusline" = { fg = "grey2", bg = "bg2" }
|
||||||
|
"ui.statusline.inactive" = { fg = "grey0", bg = "bg1" }
|
||||||
|
"ui.popup" = { fg = "grey2", bg = "bg1" }
|
||||||
|
"ui.window" = { fg = "grey2", bg = "bg1" }
|
||||||
|
"ui.help" = { fg = "fg", bg = "bg1" }
|
||||||
|
"ui.text" = "fg"
|
||||||
|
"ui.text.focus" = "fg"
|
||||||
|
"ui.menu" = { fg = "fg", bg = "bg2" }
|
||||||
|
"ui.menu.selected" = { fg = "bg0", bg = "green" }
|
||||||
|
"ui.selection" = { bg = "bg3" }
|
||||||
|
|
||||||
|
"hint" = "blue"
|
||||||
|
"info" = "aqua"
|
||||||
|
"warning" = "yellow"
|
||||||
|
"error" = "red"
|
||||||
|
"diagnostic" = { modifiers = ["underlined"] }
|
||||||
|
|
||||||
|
|
||||||
|
[palette]
|
||||||
|
|
||||||
|
bg0 = "#2b3339"
|
||||||
|
bg1 = "#323c41"
|
||||||
|
bg2 = "#3a454a"
|
||||||
|
bg3 = "#445055"
|
||||||
|
bg4 = "#4c555b"
|
||||||
|
bg5 = "#53605c"
|
||||||
|
bg_visual = "#503946"
|
||||||
|
bg_red = "#4e3e43"
|
||||||
|
bg_green = "#404d44"
|
||||||
|
bg_blue = "#394f5a"
|
||||||
|
bg_yellow = "#4a4940"
|
||||||
|
|
||||||
|
fg = "#d3c6aa"
|
||||||
|
red = "#e67e80"
|
||||||
|
orange = "#e69875"
|
||||||
|
yellow = "#dbbc7f"
|
||||||
|
green = "#a7c080"
|
||||||
|
aqua = "#83c092"
|
||||||
|
blue = "#7fbbb3"
|
||||||
|
purple = "#d699b6"
|
||||||
|
grey0 = "#7a8478"
|
||||||
|
grey1 = "#859289"
|
||||||
|
grey2 = "#9da9a0"
|
|
@ -34,6 +34,7 @@
|
||||||
"comment" = { fg = "#88846F" }
|
"comment" = { fg = "#88846F" }
|
||||||
|
|
||||||
"string" = { fg = "#e6db74" }
|
"string" = { fg = "#e6db74" }
|
||||||
|
"string.regexp" = { fg = "regex" }
|
||||||
"number" = { fg = "#ae81ff" }
|
"number" = { fg = "#ae81ff" }
|
||||||
"escape" = { fg = "#ae81ff" }
|
"escape" = { fg = "#ae81ff" }
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
[toolchain]
|
||||||
|
channel = "stable"
|
||||||
|
components = ["rustfmt", "rust-src"]
|
|
@ -9,7 +9,8 @@ special = "honey"
|
||||||
property = "white"
|
property = "white"
|
||||||
variable = "lavender"
|
variable = "lavender"
|
||||||
# variable = "almond" # TODO: metavariables only
|
# variable = "almond" # TODO: metavariables only
|
||||||
"variable.parameter" = "lavender"
|
# "variable.parameter" = { fg = "lavender", modifiers = ["underlined"] }
|
||||||
|
"variable.parameter" = { fg = "lavender" }
|
||||||
"variable.builtin" = "mint"
|
"variable.builtin" = "mint"
|
||||||
type = "white"
|
type = "white"
|
||||||
"type.builtin" = "white" # TODO: distinguish?
|
"type.builtin" = "white" # TODO: distinguish?
|
||||||
|
@ -28,9 +29,7 @@ escape = "honey"
|
||||||
label = "honey"
|
label = "honey"
|
||||||
|
|
||||||
# TODO: diferentiate doc comment
|
# TODO: diferentiate doc comment
|
||||||
# concat (ERROR) @syntax-error and "MISSING ;" selectors for errors
|
# concat (ERROR) @error.syntax and "MISSING ;" selectors for errors
|
||||||
|
|
||||||
module = "#ff0000"
|
|
||||||
|
|
||||||
"ui.background" = { bg = "midnight" }
|
"ui.background" = { bg = "midnight" }
|
||||||
"ui.linenr" = { fg = "comet" }
|
"ui.linenr" = { fg = "comet" }
|
||||||
|
|
Loading…
Reference in New Issue