Merge remote-tracking branch 'origin/master' into goto_next_reference

pull/6465/head
Anthony Templeton 1 year ago
commit 3ed8f7ef39

@ -14,7 +14,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout sources
uses: actions/checkout@v3
uses: actions/checkout@v4
- name: Install stable toolchain
uses: helix-editor/rust-toolchain@v1
with:
@ -34,7 +34,7 @@ jobs:
HELIX_LOG_LEVEL: info
steps:
- name: Checkout sources
uses: actions/checkout@v3
uses: actions/checkout@v4
- name: Install stable toolchain
uses: dtolnay/rust-toolchain@1.65
@ -63,7 +63,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout sources
uses: actions/checkout@v3
uses: actions/checkout@v4
- name: Install stable toolchain
uses: dtolnay/rust-toolchain@1.65
@ -88,7 +88,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout sources
uses: actions/checkout@v3
uses: actions/checkout@v4
- name: Install stable toolchain
uses: dtolnay/rust-toolchain@1.65

@ -11,10 +11,10 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout sources
uses: actions/checkout@v3
uses: actions/checkout@v4
- name: Install nix
uses: cachix/install-nix-action@v22
uses: cachix/install-nix-action@v23
- name: Authenticate with Cachix
uses: cachix/cachix-action@v12

@ -11,7 +11,7 @@ jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Setup mdBook
uses: peaceiris/actions-mdbook@v1

@ -23,7 +23,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout sources
uses: actions/checkout@v3
uses: actions/checkout@v4
- name: Install stable toolchain
uses: dtolnay/rust-toolchain@stable
@ -103,7 +103,7 @@ jobs:
steps:
- name: Checkout sources
uses: actions/checkout@v3
uses: actions/checkout@v4
- name: Download grammars
uses: actions/download-artifact@v3
@ -231,7 +231,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout sources
uses: actions/checkout@v3
uses: actions/checkout@v4
- uses: actions/download-artifact@v3

150
Cargo.lock generated

@ -17,17 +17,6 @@ version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
[[package]]
name = "ahash"
version = "0.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47"
dependencies = [
"getrandom",
"once_cell",
"version_check",
]
[[package]]
name = "ahash"
version = "0.8.3"
@ -107,7 +96,7 @@ dependencies = [
"cc",
"cfg-if",
"libc",
"miniz_oxide 0.7.1",
"miniz_oxide",
"object",
"rustc-demangle",
]
@ -196,14 +185,14 @@ dependencies = [
[[package]]
name = "chrono"
version = "0.4.26"
version = "0.4.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec837a71355b28f6556dbd569b37b3f363091c0bd4b2e735674521b4c5fd9bc5"
checksum = "defd4e7873dbddba6c7c91e199c7fcb946abc4a6a4ac3195400bcfb01b5de877"
dependencies = [
"android-tzdata",
"iana-time-zone",
"num-traits",
"winapi",
"windows-targets 0.48.0",
]
[[package]]
@ -377,6 +366,27 @@ dependencies = [
"syn 2.0.28",
]
[[package]]
name = "dirs"
version = "5.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "44c45a9d03d6676652bcb5e724c7e988de1acad23a711b5217ab9cbecbec2225"
dependencies = [
"dirs-sys",
]
[[package]]
name = "dirs-sys"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c"
dependencies = [
"libc",
"option-ext",
"redox_users",
"windows-sys 0.48.0",
]
[[package]]
name = "dunce"
version = "1.0.4"
@ -495,12 +505,12 @@ dependencies = [
[[package]]
name = "flate2"
version = "1.0.25"
version = "1.0.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a8a2db397cb1c8772f31494cb8917e48cd1e64f0fa7efac59fbd741a0a8ce841"
checksum = "c6c98ee8095e9d1dcbf2fcc6d95acccb90d1c81db1e44725c6a984b1dbdfb010"
dependencies = [
"crc32fast",
"miniz_oxide 0.6.2",
"miniz_oxide",
]
[[package]]
@ -1237,9 +1247,6 @@ name = "hashbrown"
version = "0.12.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
dependencies = [
"ahash 0.7.6",
]
[[package]]
name = "hashbrown"
@ -1247,7 +1254,7 @@ version = "0.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2c6201b9ff9fd90a5a3bac2e56a830d0caa509576f0e503818ee82c181b3437a"
dependencies = [
"ahash 0.8.3",
"ahash",
"allocator-api2",
]
@ -1255,7 +1262,7 @@ dependencies = [
name = "helix-core"
version = "0.6.0"
dependencies = [
"ahash 0.8.3",
"ahash",
"arc-swap",
"bitflags 2.4.0",
"chrono",
@ -1448,7 +1455,7 @@ dependencies = [
"log",
"once_cell",
"parking_lot",
"rustix 0.38.8",
"rustix 0.38.13",
"serde",
"serde_json",
"slotmap",
@ -1546,7 +1553,7 @@ version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e98c1d0ad70fc91b8b9654b1f33db55e59579d3b3de2bffdced0fdb810570cb8"
dependencies = [
"ahash 0.8.3",
"ahash",
"hashbrown 0.12.3",
]
@ -1650,9 +1657,9 @@ checksum = "36eb31c1778188ae1e64398743890d0877fef36d11521ac60406b42016e8c2cf"
[[package]]
name = "linux-raw-sys"
version = "0.4.3"
version = "0.4.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09fc20d2ca12cb9f044c93e3bd6d32d523e6e2ec3db4f7b2939cd99026ecd3f0"
checksum = "1a9bad9f94746442c783ca431b22403b519cd7fbeed0533fdd6328b2f2212128"
[[package]]
name = "lock_api"
@ -1685,9 +1692,9 @@ dependencies = [
[[package]]
name = "memchr"
version = "2.5.0"
version = "2.6.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
checksum = "8f232d6ef707e1956a43342693d2a31e72989554d58299d7a88738cc95b0d35c"
[[package]]
name = "memmap2"
@ -1722,15 +1729,6 @@ version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
[[package]]
name = "miniz_oxide"
version = "0.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b275950c28b37e794e8c55d88aeb5e139d0ce23fdbbeda68f8d7174abdf9e8fa"
dependencies = [
"adler",
]
[[package]]
name = "miniz_oxide"
version = "0.7.1"
@ -1764,9 +1762,9 @@ dependencies = [
[[package]]
name = "nucleo"
version = "0.2.0"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ccab936f2c8ad271bb31430944d98d358f74153566ea323265497f5639b11b6"
checksum = "ae5331f4bcce475cf28cb29c95366c3091af4b0aa7703f1a6bc858f29718fdf3"
dependencies = [
"nucleo-matcher",
"parking_lot",
@ -1827,6 +1825,12 @@ version = "1.18.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d"
[[package]]
name = "option-ext"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d"
[[package]]
name = "parking_lot"
version = "0.12.1"
@ -1970,11 +1974,22 @@ dependencies = [
"bitflags 1.3.2",
]
[[package]]
name = "redox_users"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b"
dependencies = [
"getrandom",
"redox_syscall 0.2.16",
"thiserror",
]
[[package]]
name = "regex"
version = "1.9.4"
version = "1.9.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "12de2eff854e5fa4b1295edd650e227e9d8fb0c9e90b12e7f36d6a6811791a29"
checksum = "697061221ea1b4a94a624f67d0ae2bfe4e22b8a17b6a192afb11046542cc8c47"
dependencies = [
"aho-corasick 1.0.2",
"memchr",
@ -1984,9 +1999,9 @@ dependencies = [
[[package]]
name = "regex-automata"
version = "0.3.7"
version = "0.3.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49530408a136e16e5b486e883fbb6ba058e8e4e8ae6621a77b048b314336e629"
checksum = "c2f401f4955220693b56f8ec66ee9c78abffd8d1c4f23dc41a23839eb88f0795"
dependencies = [
"aho-corasick 1.0.2",
"memchr",
@ -2037,14 +2052,14 @@ dependencies = [
[[package]]
name = "rustix"
version = "0.38.8"
version = "0.38.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "19ed4fa021d81c8392ce04db050a3da9a60299050b7ae1cf482d862b54a7218f"
checksum = "d7db8590df6dfcd144d22afd1b83b36c21a18d7cbc1dc4bb5295a8712e9eb662"
dependencies = [
"bitflags 2.4.0",
"errno",
"libc",
"linux-raw-sys 0.4.3",
"linux-raw-sys 0.4.7",
"windows-sys 0.48.0",
]
@ -2077,18 +2092,18 @@ checksum = "1792db035ce95be60c3f8853017b3999209281c24e2ba5bc8e59bf97a0c590c1"
[[package]]
name = "serde"
version = "1.0.185"
version = "1.0.188"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "be9b6f69f1dfd54c3b568ffa45c310d6973a5e5148fd40cf515acaf38cf5bc31"
checksum = "cf9e0fcba69a370eed61bcf2b728575f726b50b55cba78064753d708ddc7549e"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.185"
version = "1.0.188"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc59dfdcbad1437773485e0367fea4b090a2e0a16d9ffc46af47764536a298ec"
checksum = "4eca7ac642d82aa35b60049a6eccb4be6be75e599bd2e9adb5f875a737654af2"
dependencies = [
"proc-macro2",
"quote",
@ -2097,9 +2112,9 @@ dependencies = [
[[package]]
name = "serde_json"
version = "1.0.104"
version = "1.0.105"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "076066c5f1078eac5b722a31827a8832fe108bed65dfa75e233c89f8206e976c"
checksum = "693151e1ac27563d6dbcec9dee9fbd5da8539b20fa14ad3752b2e6d363ace360"
dependencies = [
"itoa",
"ryu",
@ -2274,7 +2289,7 @@ dependencies = [
"cfg-if",
"fastrand",
"redox_syscall 0.3.5",
"rustix 0.38.8",
"rustix 0.38.13",
"windows-sys 0.48.0",
]
@ -2309,18 +2324,18 @@ dependencies = [
[[package]]
name = "thiserror"
version = "1.0.47"
version = "1.0.48"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "97a802ec30afc17eee47b2855fc72e0c4cd62be9b4efe6591edde0ec5bd68d8f"
checksum = "9d6d7a740b8a666a7e828dd00da9c0dc290dff53154ea77ac109281de90589b7"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "1.0.47"
version = "1.0.48"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6bb623b56e39ab7dcd4b1b98bb6c8f8d907ed255b18de254088016b27a8ee19b"
checksum = "49922ecae66cc8a249b77e68d1d0623c1b2c514f0060c27cdc68bd62a1219d35"
dependencies = [
"proc-macro2",
"quote",
@ -2509,13 +2524,9 @@ checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4"
[[package]]
name = "unicode-linebreak"
version = "0.1.4"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c5faade31a542b8b35855fff6e8def199853b2da8da256da52f52f1316ee3137"
dependencies = [
"hashbrown 0.12.3",
"regex",
]
checksum = "3b09c83c3c29d37506a3e260c08c03743a6bb66a9cd432c6934ab501a190571f"
[[package]]
name = "unicode-normalization"
@ -2540,9 +2551,9 @@ checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b"
[[package]]
name = "url"
version = "2.4.0"
version = "2.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "50bff7831e19200a85b17131d085c25d7811bc4e186efdaf54bbd132994a88cb"
checksum = "143b538f18257fac9cad154828a57c6bf5157e1aa604d4816b5995bf6de87ae5"
dependencies = [
"form_urlencoded",
"idna",
@ -2628,13 +2639,14 @@ checksum = "0046fef7e28c3804e5e38bfa31ea2a0f73905319b677e57ebe37e49358989b5d"
[[package]]
name = "which"
version = "4.4.0"
version = "4.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2441c784c52b289a054b7201fc93253e288f094e2f4be9058343127c4226a269"
checksum = "8ad25fe5717e59ada8ea33511bbbf7420b11031730a24c65e82428766c307006"
dependencies = [
"dirs",
"either",
"libc",
"once_cell",
"rustix 0.38.13",
]
[[package]]

@ -164,7 +164,7 @@ code.hljs {
--searchresults-header-fg: #5f5f71;
--searchresults-border-color: #5c5c68;
--searchresults-li-bg: #242430;
--search-mark-bg: #acff5;
--search-mark-bg: #a2cff5;
}
.colibri .content .header {

@ -64,6 +64,7 @@ Its settings will be merged with the configuration directory `config.toml` and t
| `text-width` | Maximum line length. Used for the `:reflow` command and soft-wrapping if `soft-wrap.wrap-at-text-width` is set | `80` |
| `workspace-lsp-roots` | Directories relative to the workspace root that are treated as LSP roots. Should only be set in `.helix/config.toml` | `[]` |
| `default-line-ending` | The line ending to use for new documents. Can be `native`, `lf`, `crlf`, `ff`, `cr` or `nel`. `native` uses the platform's native line ending (`crlf` on Windows, otherwise `lf`). | `native` |
| `insert-final-newline` | Whether to automatically insert a trailing line-ending on write if missing | `true` |
### `[editor.statusline]` Section

@ -112,7 +112,7 @@
| pascal | ✓ | ✓ | | `pasls` |
| passwd | ✓ | | | |
| pem | ✓ | | | |
| perl | ✓ | | | `perlnavigator` |
| perl | ✓ | | | `perlnavigator` |
| php | ✓ | ✓ | ✓ | `intelephense` |
| po | ✓ | ✓ | | |
| pod | ✓ | | | |

@ -55,6 +55,7 @@
| `:lsp-restart` | Restarts the language servers used by the current doc |
| `:lsp-stop` | Stops the language servers that are used by the current doc |
| `:tree-sitter-scopes` | Display tree sitter scopes, primarily for theming and development. |
| `:tree-sitter-highlight-name` | Display name of tree-sitter highlight scope under the cursor. |
| `:debug-start`, `:dbg` | Start a debug session from a given template with given parameters. |
| `:debug-remote`, `:dbg-tcp` | Connect to a debug adapter by TCP address and start a debugging session from a given template with given parameters. |
| `:debug-eval` | Evaluate expression in current debug context. |
@ -83,3 +84,4 @@
| `:run-shell-command`, `:sh` | Run a shell command |
| `:reset-diff-change`, `:diffget`, `:diffg` | Reset the diff change at the cursor position. |
| `:clear-register` | Clear given register. If no argument is provided, clear all registers. |
| `:redraw` | Clear and re-render the whole UI |

@ -127,7 +127,7 @@ These are the available options for a language server.
| `environment` | Any environment variables that will be used when starting the language server `{ "KEY1" = "Value1", "KEY2" = "Value2" }` |
A `format` sub-table within `config` can be used to pass extra formatting options to
[Document Formatting Requests](https://github.com/microsoft/language-server-protocol/blob/gh-pages/_specifications/specification-3-17.md#document-formatting-request--leftwards_arrow_with_hook).
[Document Formatting Requests](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_formatting).
For example, with typescript:
```toml

@ -29,9 +29,15 @@ files, run
cargo xtask docgen
```
inside the project. We use [xtask][xtask] as an ad-hoc task runner and
thus do not require any dependencies other than `cargo` (You don't have
to `cargo install` anything either).
inside the project. We use [xtask][xtask] as an ad-hoc task runner.
To preview the book itself, install [mdbook][mdbook]. Then, run
```shell
mdbook serve book
```
and visit [http://localhost:3000](http://localhost:3000).
# Testing
@ -58,4 +64,5 @@ The current MSRV and future changes to the MSRV are listed in the [Firefox docum
[architecture.md]: ./architecture.md
[docs]: https://docs.helix-editor.com/
[xtask]: https://github.com/matklad/cargo-xtask
[mdbook]: https://rust-lang.github.io/mdBook/guide/installation.html
[helpers.rs]: ../helix-term/tests/test/helpers.rs

@ -121,7 +121,8 @@
then ''$RUSTFLAGS -C link-arg=-fuse-ld=lld -C target-cpu=native -Clink-arg=-Wl,--no-rosegment''
else "$RUSTFLAGS";
rustToolchain = pkgs.pkgsBuildHost.rust-bin.fromRustupToolchainFile ./rust-toolchain.toml;
craneLib = (crane.mkLib pkgs).overrideToolchain rustToolchain;
craneLibMSRV = (crane.mkLib pkgs).overrideToolchain rustToolchain;
craneLibStable = (crane.mkLib pkgs).overrideToolchain pkgs.pkgsBuildHost.rust-bin.stable.latest.default;
commonArgs =
{
inherit stdenv;
@ -133,13 +134,13 @@
doCheck = false;
meta.mainProgram = "hx";
}
// craneLib.crateNameFromCargoToml {cargoToml = ./helix-term/Cargo.toml;};
cargoArtifacts = craneLib.buildDepsOnly commonArgs;
// craneLibMSRV.crateNameFromCargoToml {cargoToml = ./helix-term/Cargo.toml;};
cargoArtifacts = craneLibMSRV.buildDepsOnly commonArgs;
in {
packages = {
helix-unwrapped = craneLib.buildPackage (commonArgs
helix-unwrapped = craneLibStable.buildPackage (commonArgs
// {
inherit cargoArtifacts;
cargoArtifacts = craneLibStable.buildDepsOnly commonArgs;
postInstall = ''
mkdir -p $out/share/applications $out/share/icons/hicolor/scalable/apps $out/share/icons/hicolor/256x256/apps
cp contrib/Helix.desktop $out/share/applications
@ -155,20 +156,20 @@
# Build the crate itself
inherit (self.packages.${system}) helix;
clippy = craneLib.cargoClippy (commonArgs
clippy = craneLibMSRV.cargoClippy (commonArgs
// {
inherit cargoArtifacts;
cargoClippyExtraArgs = "--all-targets -- --deny warnings";
});
fmt = craneLib.cargoFmt commonArgs;
fmt = craneLibMSRV.cargoFmt commonArgs;
doc = craneLib.cargoDoc (commonArgs
doc = craneLibMSRV.cargoDoc (commonArgs
// {
inherit cargoArtifacts;
});
test = craneLib.cargoTest (commonArgs
test = craneLibMSRV.cargoTest (commonArgs
// {
inherit cargoArtifacts;
});

@ -1,6 +1,6 @@
use std::ops::DerefMut;
use nucleo::pattern::{AtomKind, CaseMatching, Pattern};
use nucleo::pattern::{Atom, AtomKind, CaseMatching};
use nucleo::Config;
use parking_lot::Mutex;
@ -32,12 +32,12 @@ pub fn fuzzy_match<T: AsRef<str>>(
pattern: &str,
items: impl IntoIterator<Item = T>,
path: bool,
) -> Vec<(T, u32)> {
) -> Vec<(T, u16)> {
let mut matcher = MATCHER.lock();
matcher.config = Config::DEFAULT;
if path {
matcher.config.set_match_paths();
}
let pattern = Pattern::new(pattern, CaseMatching::Smart, AtomKind::Fuzzy);
let pattern = Atom::new(pattern, CaseMatching::Smart, AtomKind::Fuzzy, false);
pattern.match_list(items, &mut matcher)
}

@ -962,7 +962,7 @@ impl Syntax {
let res = syntax.update(source, source, &ChangeSet::new(source));
if res.is_err() {
log::error!("TS parser failed, disabeling TS for the current buffer: {res:?}");
log::error!("TS parser failed, disabling TS for the current buffer: {res:?}");
return None;
}
Some(syntax)

@ -251,6 +251,11 @@ impl Application {
}
async fn render(&mut self) {
if self.compositor.full_redraw {
self.terminal.clear().expect("Cannot clear the terminal");
self.compositor.full_redraw = false;
}
let mut cx = crate::compositor::Context {
editor: &mut self.editor,
jobs: &mut self.jobs,

@ -1253,6 +1253,65 @@ fn extend_next_long_word_end(cx: &mut Context) {
extend_word_impl(cx, movement::move_next_long_word_end)
}
/// Separate branch to find_char designed only for <ret> char.
//
// This is necessary because the one document can have different line endings inside. And we
// cannot predict what character to find when <ret> is pressed. On the current line it can be `lf`
// but on the next line it can be `crlf`. That's why [`find_char_impl`] cannot be applied here.
fn find_char_line_ending(
cx: &mut Context,
count: usize,
direction: Direction,
inclusive: bool,
extend: bool,
) {
let (view, doc) = current!(cx.editor);
let text = doc.text().slice(..);
let selection = doc.selection(view.id).clone().transform(|range| {
let cursor = range.cursor(text);
let cursor_line = range.cursor_line(text);
// Finding the line where we're going to find <ret>. Depends mostly on
// `count`, but also takes into account edge cases where we're already at the end
// of a line or the beginning of a line
let find_on_line = match direction {
Direction::Forward => {
let on_edge = line_end_char_index(&text, cursor_line) == cursor;
let line = cursor_line + count - 1 + (on_edge as usize);
if line >= text.len_lines() - 1 {
return range;
} else {
line
}
}
Direction::Backward => {
let on_edge = text.line_to_char(cursor_line) == cursor && !inclusive;
let line = cursor_line as isize - (count as isize - 1 + on_edge as isize);
if line <= 0 {
return range;
} else {
line as usize
}
}
};
let pos = match (direction, inclusive) {
(Direction::Forward, true) => line_end_char_index(&text, find_on_line),
(Direction::Forward, false) => line_end_char_index(&text, find_on_line) - 1,
(Direction::Backward, true) => line_end_char_index(&text, find_on_line - 1),
(Direction::Backward, false) => text.line_to_char(find_on_line),
};
if extend {
range.put_cursor(text, pos, true)
} else {
Range::point(range.cursor(text)).put_cursor(text, pos, true)
}
});
doc.set_selection(view.id, selection);
}
fn find_char(cx: &mut Context, direction: Direction, inclusive: bool, extend: bool) {
// TODO: count is reset to 1 before next key so we move it into the closure here.
// Would be nice to carry over.
@ -1266,13 +1325,9 @@ fn find_char(cx: &mut Context, direction: Direction, inclusive: bool, extend: bo
KeyEvent {
code: KeyCode::Enter,
..
} =>
// TODO: this isn't quite correct when CRLF is involved.
// This hack will work in most cases, since documents don't
// usually mix line endings. But we should fix it eventually
// anyway.
{
doc!(cx.editor).line_ending.as_str().chars().next().unwrap()
} => {
find_char_line_ending(cx, count, direction, inclusive, extend);
return;
}
KeyEvent {
@ -4833,17 +4888,19 @@ fn transpose_view(cx: &mut Context) {
cx.editor.transpose_view()
}
// split helper, clear it later
fn split(cx: &mut Context, action: Action) {
let (view, doc) = current!(cx.editor);
/// Open a new split in the given direction specified by the action.
///
/// Maintain the current view (both the cursor's position and view in document).
fn split(editor: &mut Editor, action: Action) {
let (view, doc) = current!(editor);
let id = doc.id();
let selection = doc.selection(view.id).clone();
let offset = view.offset;
cx.editor.switch(id, action);
editor.switch(id, action);
// match the selection in the previous view
let (view, doc) = current!(cx.editor);
let (view, doc) = current!(editor);
doc.set_selection(view.id, selection);
// match the view scroll offset (switch doesn't handle this fully
// since the selection is only matched after the split)
@ -4851,7 +4908,7 @@ fn split(cx: &mut Context, action: Action) {
}
fn hsplit(cx: &mut Context) {
split(cx, Action::HorizontalSplit);
split(cx.editor, Action::HorizontalSplit);
}
fn hsplit_new(cx: &mut Context) {
@ -4859,7 +4916,7 @@ fn hsplit_new(cx: &mut Context) {
}
fn vsplit(cx: &mut Context) {
split(cx, Action::VerticalSplit);
split(cx.editor, Action::VerticalSplit);
}
fn vsplit_new(cx: &mut Context) {

@ -6,7 +6,7 @@ use crate::job::Job;
use super::*;
use helix_core::fuzzy::fuzzy_match;
use helix_core::{encoding, shellwords::Shellwords};
use helix_core::{encoding, line_ending, shellwords::Shellwords};
use helix_view::document::DEFAULT_LANGUAGE_NAME;
use helix_view::editor::{Action, CloseError, ConfigEvent};
use serde_json::Value;
@ -330,12 +330,16 @@ fn write_impl(
path: Option<&Cow<str>>,
force: bool,
) -> anyhow::Result<()> {
let editor_auto_fmt = cx.editor.config().auto_format;
let config = cx.editor.config();
let jobs = &mut cx.jobs;
let (view, doc) = current!(cx.editor);
let path = path.map(AsRef::as_ref);
let fmt = if editor_auto_fmt {
if config.insert_final_newline {
insert_final_newline(doc, view);
}
let fmt = if config.auto_format {
doc.auto_format().map(|fmt| {
let callback = make_format_callback(
doc.id(),
@ -359,6 +363,16 @@ fn write_impl(
Ok(())
}
fn insert_final_newline(doc: &mut Document, view: &mut View) {
let text = doc.text();
if line_ending::get_line_ending(&text.slice(..)).is_none() {
let eof = Selection::point(text.len_chars());
let insert = Transaction::insert(text, &eof, doc.line_ending.as_str().into());
doc.apply(&insert, view.id);
doc.append_changes_to_history(view);
}
}
fn write(
cx: &mut compositor::Context,
args: &[Cow<str>],
@ -658,11 +672,10 @@ pub fn write_all_impl(
write_scratch: bool,
) -> anyhow::Result<()> {
let mut errors: Vec<&'static str> = Vec::new();
let auto_format = cx.editor.config().auto_format;
let config = cx.editor.config();
let jobs = &mut cx.jobs;
let current_view = view!(cx.editor);
// save all documents
let saves: Vec<_> = cx
.editor
.documents
@ -693,10 +706,21 @@ pub fn write_all_impl(
current_view.id
};
let fmt = if auto_format {
Some((doc.id(), target_view))
})
.collect();
for (doc_id, target_view) in saves {
let doc = doc_mut!(cx.editor, &doc_id);
if config.insert_final_newline {
insert_final_newline(doc, view_mut!(cx.editor, target_view));
}
let fmt = if config.auto_format {
doc.auto_format().map(|fmt| {
let callback = make_format_callback(
doc.id(),
doc_id,
doc.version(),
target_view,
fmt,
@ -709,16 +733,8 @@ pub fn write_all_impl(
};
if fmt.is_none() {
return Some(doc.id());
cx.editor.save::<PathBuf>(doc_id, None, force)?;
}
None
})
.collect();
// manually call save for the rest of docs that don't have a formatter
for id in saves {
cx.editor.save::<PathBuf>(id, None, force)?;
}
if !errors.is_empty() && !force {
@ -1527,6 +1543,84 @@ fn tree_sitter_scopes(
Ok(())
}
fn tree_sitter_highlight_name(
cx: &mut compositor::Context,
_args: &[Cow<str>],
event: PromptEvent,
) -> anyhow::Result<()> {
fn find_highlight_at_cursor(
cx: &mut compositor::Context<'_>,
) -> Option<helix_core::syntax::Highlight> {
use helix_core::syntax::HighlightEvent;
let (view, doc) = current!(cx.editor);
let syntax = doc.syntax()?;
let text = doc.text().slice(..);
let cursor = doc.selection(view.id).primary().cursor(text);
let byte = text.char_to_byte(cursor);
let node = syntax
.tree()
.root_node()
.descendant_for_byte_range(byte, byte)?;
// Query the same range as the one used in syntax highlighting.
let range = {
// Calculate viewport byte ranges:
let row = text.char_to_line(view.offset.anchor.min(text.len_chars()));
// Saturating subs to make it inclusive zero indexing.
let last_line = text.len_lines().saturating_sub(1);
let height = view.inner_area(doc).height;
let last_visible_line = (row + height as usize).saturating_sub(1).min(last_line);
let start = text.line_to_byte(row.min(last_line));
let end = text.line_to_byte(last_visible_line + 1);
start..end
};
let mut highlight = None;
for event in syntax.highlight_iter(text, Some(range), None) {
match event.unwrap() {
HighlightEvent::Source { start, end }
if start == node.start_byte() && end == node.end_byte() =>
{
return highlight;
}
HighlightEvent::HighlightStart(hl) => {
highlight = Some(hl);
}
_ => (),
}
}
None
}
if event != PromptEvent::Validate {
return Ok(());
}
let Some(highlight) = find_highlight_at_cursor(cx) else {
return Ok(());
};
let content = cx.editor.theme.scope(highlight.0).to_string();
let callback = async move {
let call: job::Callback = Callback::EditorCompositor(Box::new(
move |editor: &mut Editor, compositor: &mut Compositor| {
let content = ui::Markdown::new(content, editor.syn_loader.clone());
let popup = Popup::new("hover", content).auto_close(true);
compositor.replace_or_push("hover", popup);
},
));
Ok(call)
};
cx.jobs.callback(callback);
Ok(())
}
fn vsplit(
cx: &mut compositor::Context,
args: &[Cow<str>],
@ -1536,10 +1630,8 @@ fn vsplit(
return Ok(());
}
let id = view!(cx.editor).doc;
if args.is_empty() {
cx.editor.switch(id, Action::VerticalSplit);
split(cx.editor, Action::VerticalSplit);
} else {
for arg in args {
cx.editor
@ -1559,10 +1651,8 @@ fn hsplit(
return Ok(());
}
let id = view!(cx.editor).doc;
if args.is_empty() {
cx.editor.switch(id, Action::HorizontalSplit);
split(cx.editor, Action::HorizontalSplit);
} else {
for arg in args {
cx.editor
@ -2295,6 +2385,29 @@ fn clear_register(
Ok(())
}
fn redraw(
cx: &mut compositor::Context,
_args: &[Cow<str>],
event: PromptEvent,
) -> anyhow::Result<()> {
if event != PromptEvent::Validate {
return Ok(());
}
let callback = Box::pin(async move {
let call: job::Callback =
job::Callback::EditorCompositor(Box::new(|_editor, compositor| {
compositor.need_full_redraw();
}));
Ok(call)
});
cx.jobs.callback(callback);
Ok(())
}
pub const TYPABLE_COMMAND_LIST: &[TypableCommand] = &[
TypableCommand {
name: "quit",
@ -2684,6 +2797,13 @@ pub const TYPABLE_COMMAND_LIST: &[TypableCommand] = &[
fun: tree_sitter_scopes,
signature: CommandSignature::none(),
},
TypableCommand {
name: "tree-sitter-highlight-name",
aliases: &[],
doc: "Display name of tree-sitter highlight scope under the cursor.",
fun: tree_sitter_highlight_name,
signature: CommandSignature::none(),
},
TypableCommand {
name: "debug-start",
aliases: &["dbg"],
@ -2881,6 +3001,13 @@ pub const TYPABLE_COMMAND_LIST: &[TypableCommand] = &[
fun: clear_register,
signature: CommandSignature::none(),
},
TypableCommand {
name: "redraw",
aliases: &[],
doc: "Clear and re-render the whole UI",
fun: redraw,
signature: CommandSignature::none(),
},
];
pub static TYPABLE_COMMAND_MAP: Lazy<HashMap<&'static str, &'static TypableCommand>> =

@ -16,6 +16,7 @@ pub enum EventResult {
}
use crate::job::Jobs;
use crate::ui::picker;
use helix_view::Editor;
pub use helix_view::input::Event;
@ -79,6 +80,7 @@ pub struct Compositor {
area: Rect,
pub(crate) last_picker: Option<Box<dyn Component>>,
pub(crate) full_redraw: bool,
}
impl Compositor {
@ -87,6 +89,7 @@ impl Compositor {
layers: Vec::new(),
area,
last_picker: None,
full_redraw: false,
}
}
@ -100,6 +103,11 @@ impl Compositor {
/// Add a layer to be rendered in front of all existing layers.
pub fn push(&mut self, mut layer: Box<dyn Component>) {
// immediately clear last_picker field to avoid excessive memory
// consumption for picker with many items
if layer.id() == Some(picker::ID) {
self.last_picker = None;
}
let size = self.size();
// trigger required_size on init
layer.required_size((size.width, size.height));
@ -200,6 +208,10 @@ impl Compositor {
.find(|component| component.id() == Some(id))
.and_then(|component| component.as_any_mut().downcast_mut())
}
pub fn need_full_redraw(&mut self) {
self.full_redraw = true;
}
}
// View casting, taken straight from Cursive

@ -1073,6 +1073,7 @@ impl EditorView {
let editor = &mut cxt.editor;
if let Some((pos, view_id)) = pos_and_view(editor, row, column, true) {
let prev_view_id = view!(editor).id;
let doc = doc_mut!(editor, &view!(editor, view_id).doc);
if modifiers == KeyModifiers::ALT {
@ -1082,6 +1083,10 @@ impl EditorView {
doc.set_selection(view_id, Selection::point(pos));
}
if view_id != prev_view_id {
self.clear_completion(editor);
}
editor.focus(view_id);
editor.ensure_cursor_in_view(view_id);

@ -5,7 +5,7 @@ use crate::{
ctrl, key, shift,
};
use helix_core::fuzzy::MATCHER;
use nucleo::pattern::{AtomKind, CaseMatching, Pattern};
use nucleo::pattern::{Atom, AtomKind, CaseMatching};
use nucleo::{Config, Utf32Str};
use tui::{buffer::Buffer as Surface, widgets::Table};
@ -94,13 +94,13 @@ impl<T: Item> Menu<T> {
self.matches.clear();
let mut matcher = MATCHER.lock();
matcher.config = Config::DEFAULT;
let pattern = Pattern::new(pattern, CaseMatching::Ignore, AtomKind::Fuzzy);
let pattern = Atom::new(pattern, CaseMatching::Ignore, AtomKind::Fuzzy, false);
let mut buf = Vec::new();
let matches = self.options.iter().enumerate().filter_map(|(i, option)| {
let text = option.filter_text(&self.editor_data);
pattern
.score(Utf32Str::new(&text, &mut buf), &mut matcher)
.map(|score| (i as u32, score))
.map(|score| (i as u32, score as u32))
});
self.matches.extend(matches);
self.matches

@ -6,7 +6,7 @@ pub mod lsp;
mod markdown;
pub mod menu;
pub mod overlay;
mod picker;
pub mod picker;
pub mod popup;
mod prompt;
mod spinner;
@ -190,7 +190,7 @@ pub fn file_picker(root: PathBuf, config: &helix_view::editor::Config) -> Picker
.build()
.expect("failed to build excluded_types");
walk_builder.types(excluded_types);
let files = walk_builder.build().filter_map(|entry| {
let mut files = walk_builder.build().filter_map(|entry| {
let entry = entry.ok()?;
if !entry.file_type()?.is_file() {
return None;
@ -211,6 +211,19 @@ pub fn file_picker(root: PathBuf, config: &helix_view::editor::Config) -> Picker
})
.with_preview(|_editor, path| Some((path.clone().into(), None)));
let injector = picker.injector();
let timeout = std::time::Instant::now() + std::time::Duration::from_millis(30);
let mut hit_timeout = false;
for file in &mut files {
if injector.push(file).is_err() {
break;
}
if std::time::Instant::now() >= timeout {
hit_timeout = true;
break;
}
}
if hit_timeout {
std::thread::spawn(move || {
for file in files {
if injector.push(file).is_err() {
@ -218,6 +231,7 @@ pub fn file_picker(root: PathBuf, config: &helix_view::editor::Config) -> Picker
}
}
});
}
picker
}

@ -46,6 +46,7 @@ use helix_view::{
Document, DocumentId, Editor,
};
pub const ID: &str = "picker";
use super::{menu::Item, overlay::Overlay};
pub const MIN_AREA_WIDTH_FOR_PREVIEW: u16 = 72;
@ -111,9 +112,9 @@ impl Preview<'_, '_> {
/// Alternate text to show for the preview.
fn placeholder(&self) -> &str {
match *self {
Self::EditorDocument(_) => "<File preview>",
Self::EditorDocument(_) => "<Invalid file location>",
Self::Cached(preview) => match preview {
CachedPreview::Document(_) => "<File preview>",
CachedPreview::Document(_) => "<Invalid file location>",
CachedPreview::Binary => "<Binary file>",
CachedPreview::LargeFile => "<File too large to preview>",
CachedPreview::NotFound => "<File not found>",
@ -155,7 +156,7 @@ impl<T: Item> Clone for Injector<T> {
Injector {
dst: self.dst.clone(),
editor_data: self.editor_data.clone(),
shutown: Arc::new(AtomicBool::new(false)),
shutown: self.shutown.clone(),
}
}
}
@ -473,9 +474,13 @@ impl<T: Item + 'static> Picker<T> {
log::info!("highlighting picker item failed");
return;
};
let Some(Overlay {
content: picker, ..
}) = compositor.find::<Overlay<Self>>()
let picker = match compositor.find::<Overlay<Self>>() {
Some(Overlay { content, .. }) => Some(content),
None => compositor
.find::<Overlay<DynamicPicker<T>>>()
.map(|overlay| &mut overlay.content.file_picker),
};
let Some(picker) = picker
else {
log::info!("picker closed before syntax highlighting finished");
return;
@ -688,8 +693,14 @@ impl<T: Item + 'static> Picker<T> {
if let Some((path, range)) = self.current_file(cx.editor) {
let preview = self.get_preview(path, cx.editor);
let doc = match preview.document() {
Some(doc) => doc,
None => {
Some(doc)
if range.map_or(true, |(start, end)| {
start <= end && end <= doc.text().len_lines()
}) =>
{
doc
}
_ => {
let alt_text = preview.placeholder();
let x = inner.x + inner.width.saturating_sub(alt_text.len() as u16) / 2;
let y = inner.y + inner.height / 2;
@ -699,18 +710,30 @@ impl<T: Item + 'static> Picker<T> {
};
let mut offset = ViewPosition::default();
if let Some(range) = range {
if let Some((start_line, end_line)) = range {
let height = end_line - start_line;
let text = doc.text().slice(..);
let start = text.line_to_char(start_line);
let middle = text.line_to_char(start_line + height / 2);
if height < inner.height as usize {
let text_fmt = doc.text_format(inner.width, None);
let annotations = TextAnnotations::default();
(offset.anchor, offset.vertical_offset) = char_idx_at_visual_offset(
doc.text().slice(..),
doc.text().line_to_char(range.0),
text,
middle,
// align to middle
-(inner.height as isize / 2),
0,
&text_fmt,
&annotations,
);
if start < offset.anchor {
offset.anchor = start;
offset.vertical_offset = 0;
}
} else {
offset.anchor = start;
}
}
let mut highlights = EditorView::doc_syntax_highlights(
@ -802,11 +825,28 @@ impl<T: Item + 'static + Send + Sync> Component for Picker<T> {
_ => return EventResult::Ignored(None),
};
let close_fn =
EventResult::Consumed(Some(Box::new(|compositor: &mut Compositor, _ctx| {
let close_fn = |picker: &mut Self| {
// if the picker is very large don't store it as last_picker to avoid
// excessive memory consumption
let callback: compositor::Callback = if picker.matcher.snapshot().item_count() > 100_000
{
Box::new(|compositor: &mut Compositor, _ctx| {
// remove the layer
compositor.pop();
})
} else {
// stop streaming in new items in the background, really we should
// be restarting the stream somehow once the picker gets
// reopened instead (like for an FS crawl) that would also remove the
// need for the special case above but that is pretty tricky
picker.shutdown.store(true, atomic::Ordering::Relaxed);
Box::new(|compositor: &mut Compositor, _ctx| {
// remove the layer
compositor.last_picker = compositor.pop();
})));
})
};
EventResult::Consumed(Some(callback))
};
// So that idle timeout retriggers
ctx.editor.reset_idle_timer();
@ -830,9 +870,7 @@ impl<T: Item + 'static + Send + Sync> Component for Picker<T> {
key!(End) => {
self.to_end();
}
key!(Esc) | ctrl!('c') => {
return close_fn;
}
key!(Esc) | ctrl!('c') => return close_fn(self),
alt!(Enter) => {
if let Some(option) = self.selection() {
(self.callback_fn)(ctx, option, Action::Load);
@ -842,19 +880,19 @@ impl<T: Item + 'static + Send + Sync> Component for Picker<T> {
if let Some(option) = self.selection() {
(self.callback_fn)(ctx, option, Action::Replace);
}
return close_fn;
return close_fn(self);
}
ctrl!('s') => {
if let Some(option) = self.selection() {
(self.callback_fn)(ctx, option, Action::HorizontalSplit);
}
return close_fn;
return close_fn(self);
}
ctrl!('v') => {
if let Some(option) = self.selection() {
(self.callback_fn)(ctx, option, Action::VerticalSplit);
}
return close_fn;
return close_fn(self);
}
ctrl!('t') => {
self.toggle_preview();
@ -882,6 +920,10 @@ impl<T: Item + 'static + Send + Sync> Component for Picker<T> {
self.completion_height = height.saturating_sub(4);
Some((width, height))
}
fn id(&self) -> Option<&'static str> {
Some(ID)
}
}
impl<T: Item> Drop for Picker<T> {
fn drop(&mut self) {
@ -906,8 +948,6 @@ pub struct DynamicPicker<T: ui::menu::Item + Send + Sync> {
}
impl<T: ui::menu::Item + Send + Sync> DynamicPicker<T> {
pub const ID: &'static str = "dynamic-picker";
pub fn new(file_picker: Picker<T>, query_callback: DynQueryCallback<T>) -> Self {
Self {
file_picker,
@ -939,7 +979,7 @@ impl<T: Item + Send + Sync + 'static> Component for DynamicPicker<T> {
let callback = Callback::EditorCompositor(Box::new(move |editor, compositor| {
// Wrapping of pickers in overlay is done outside the picker code,
// so this is fragile and will break if wrapped in some other widget.
let picker = match compositor.find_id::<Overlay<DynamicPicker<T>>>(Self::ID) {
let picker = match compositor.find_id::<Overlay<DynamicPicker<T>>>(ID) {
Some(overlay) => &mut overlay.content.file_picker,
None => return,
};
@ -960,6 +1000,6 @@ impl<T: Item + Send + Sync + 'static> Component for DynamicPicker<T> {
}
fn id(&self) -> Option<&'static str> {
Some(Self::ID)
Some(ID)
}
}

@ -93,7 +93,7 @@ async fn test_buffer_close_concurrent() -> anyhow::Result<()> {
)
.await?;
helpers::assert_file_has_content(file.as_file_mut(), &RANGE.end().to_string())?;
helpers::assert_file_has_content(file.as_file_mut(), &platform_line(&RANGE.end().to_string()))?;
Ok(())
}
@ -209,7 +209,7 @@ async fn test_write_concurrent() -> anyhow::Result<()> {
let mut file_content = String::new();
file.as_file_mut().read_to_string(&mut file_content)?;
assert_eq!(RANGE.end().to_string(), file_content);
assert_eq!(platform_line(&RANGE.end().to_string()), file_content);
Ok(())
}
@ -424,13 +424,132 @@ async fn test_write_utf_bom_file() -> anyhow::Result<()> {
Ok(())
}
#[tokio::test(flavor = "multi_thread")]
async fn test_write_insert_final_newline_added_if_missing() -> anyhow::Result<()> {
let mut file = tempfile::NamedTempFile::new()?;
let mut app = helpers::AppBuilder::new()
.with_file(file.path(), None)
.with_input_text("#[h|]#ave you tried chamomile tea?")
.build()?;
test_key_sequence(&mut app, Some(":w<ret>"), None, false).await?;
helpers::assert_file_has_content(
file.as_file_mut(),
&helpers::platform_line("have you tried chamomile tea?\n"),
)?;
Ok(())
}
#[tokio::test(flavor = "multi_thread")]
async fn test_write_insert_final_newline_unchanged_if_not_missing() -> anyhow::Result<()> {
let mut file = tempfile::NamedTempFile::new()?;
let mut app = helpers::AppBuilder::new()
.with_file(file.path(), None)
.with_input_text(&helpers::platform_line("#[t|]#en minutes, please\n"))
.build()?;
test_key_sequence(&mut app, Some(":w<ret>"), None, false).await?;
helpers::assert_file_has_content(
file.as_file_mut(),
&helpers::platform_line("ten minutes, please\n"),
)?;
Ok(())
}
#[tokio::test(flavor = "multi_thread")]
async fn test_write_insert_final_newline_unchanged_if_missing_and_false() -> anyhow::Result<()> {
let mut file = tempfile::NamedTempFile::new()?;
let mut app = helpers::AppBuilder::new()
.with_config(Config {
editor: helix_view::editor::Config {
insert_final_newline: false,
..Default::default()
},
..Default::default()
})
.with_file(file.path(), None)
.with_input_text("#[t|]#he quiet rain continued through the night")
.build()?;
test_key_sequence(&mut app, Some(":w<ret>"), None, false).await?;
helpers::assert_file_has_content(
file.as_file_mut(),
"the quiet rain continued through the night",
)?;
Ok(())
}
#[tokio::test(flavor = "multi_thread")]
async fn test_write_all_insert_final_newline_add_if_missing_and_modified() -> anyhow::Result<()> {
let mut file1 = tempfile::NamedTempFile::new()?;
let mut file2 = tempfile::NamedTempFile::new()?;
let mut app = helpers::AppBuilder::new()
.with_file(file1.path(), None)
.with_input_text("#[w|]#e don't serve time travelers here")
.build()?;
test_key_sequence(
&mut app,
Some(&format!(
":o {}<ret>ia time traveler walks into a bar<esc>:wa<ret>",
file2.path().to_string_lossy()
)),
None,
false,
)
.await?;
helpers::assert_file_has_content(
file1.as_file_mut(),
&helpers::platform_line("we don't serve time travelers here\n"),
)?;
helpers::assert_file_has_content(
file2.as_file_mut(),
&helpers::platform_line("a time traveler walks into a bar\n"),
)?;
Ok(())
}
#[tokio::test(flavor = "multi_thread")]
async fn test_write_all_insert_final_newline_do_not_add_if_unmodified() -> anyhow::Result<()> {
let mut file = tempfile::NamedTempFile::new()?;
let mut app = helpers::AppBuilder::new()
.with_file(file.path(), None)
.build()?;
file.write_all(b"i lost on Jeopardy!")?;
file.rewind()?;
test_key_sequence(&mut app, Some(":wa<ret>"), None, false).await?;
helpers::assert_file_has_content(file.as_file_mut(), "i lost on Jeopardy!")?;
Ok(())
}
async fn edit_file_with_content(file_content: &[u8]) -> anyhow::Result<()> {
let mut file = tempfile::NamedTempFile::new()?;
file.as_file_mut().write_all(&file_content)?;
helpers::test_key_sequence(
&mut helpers::AppBuilder::new().build()?,
&mut helpers::AppBuilder::new()
.with_config(Config {
editor: helix_view::editor::Config {
insert_final_newline: false,
..Default::default()
},
..Default::default()
})
.build()?,
Some(&format!(":o {}<ret>:x<ret>", file.path().to_string_lossy())),
None,
true,

@ -350,7 +350,7 @@ pub fn assert_file_has_content(file: &mut File, content: &str) -> anyhow::Result
let mut file_content = String::new();
file.read_to_string(&mut file_content)?;
assert_eq!(content, file_content);
assert_eq!(file_content, content);
Ok(())
}

@ -513,3 +513,42 @@ async fn select_mode_tree_sitter_prev_function_goes_backwards_to_object() -> any
Ok(())
}
#[tokio::test(flavor = "multi_thread")]
async fn find_char_line_ending() -> anyhow::Result<()> {
test((
helpers::platform_line(indoc! {
"\
one
#[|t]#wo
three"
}),
"T<ret>gll2f<ret>",
helpers::platform_line(indoc! {
"\
one
two#[
|]#three"
}),
))
.await?;
test((
helpers::platform_line(indoc! {
"\
#[|o]#ne
two
three"
}),
"f<ret>2t<ret>ghT<ret>F<ret>",
helpers::platform_line(indoc! {
"\
one#[|
t]#wo
three"
}),
))
.await?;
Ok(())
}

@ -62,9 +62,9 @@ async fn test_split_write_quit_all() -> anyhow::Result<()> {
)
.await?;
helpers::assert_file_has_content(file1.as_file_mut(), "hello1")?;
helpers::assert_file_has_content(file2.as_file_mut(), "hello2")?;
helpers::assert_file_has_content(file3.as_file_mut(), "hello3")?;
helpers::assert_file_has_content(file1.as_file_mut(), &platform_line("hello1"))?;
helpers::assert_file_has_content(file2.as_file_mut(), &platform_line("hello2"))?;
helpers::assert_file_has_content(file3.as_file_mut(), &platform_line("hello3"))?;
Ok(())
}

@ -328,6 +328,9 @@ impl ModifierDiff {
if removed.contains(Modifier::SLOW_BLINK) || removed.contains(Modifier::RAPID_BLINK) {
queue!(w, SetAttribute(CAttribute::NoBlink))?;
}
if removed.contains(Modifier::HIDDEN) {
queue!(w, SetAttribute(CAttribute::NoHidden))?;
}
let added = self.to - self.from;
if added.contains(Modifier::REVERSED) {
@ -351,6 +354,9 @@ impl ModifierDiff {
if added.contains(Modifier::RAPID_BLINK) {
queue!(w, SetAttribute(CAttribute::RapidBlink))?;
}
if added.contains(Modifier::HIDDEN) {
queue!(w, SetAttribute(CAttribute::Hidden))?;
}
Ok(())
}

@ -73,9 +73,14 @@ pub fn get_clipboard_provider() -> Box<dyn ClipboardProvider> {
#[cfg(target_os = "macos")]
pub fn get_clipboard_provider() -> Box<dyn ClipboardProvider> {
use crate::env::binary_exists;
use crate::env::{binary_exists, env_var_is_set};
if binary_exists("pbcopy") && binary_exists("pbpaste") {
if env_var_is_set("TMUX") && binary_exists("tmux") {
command_provider! {
paste => "tmux", "save-buffer", "-";
copy => "tmux", "load-buffer", "-w", "-";
}
} else if binary_exists("pbcopy") && binary_exists("pbpaste") {
command_provider! {
paste => "pbpaste";
copy => "pbcopy";

@ -1206,7 +1206,7 @@ impl Document {
transaction.changes(),
);
if res.is_err() {
log::error!("TS parser failed, disabeling TS for the current buffer: {res:?}");
log::error!("TS parser failed, disabling TS for the current buffer: {res:?}");
self.syntax = None;
}
}

@ -244,7 +244,7 @@ pub struct Config {
/// Set a global text_width
pub text_width: usize,
/// Time in milliseconds since last keypress before idle timers trigger.
/// Used for autocompletion, set to 0 for instant. Defaults to 400ms.
/// Used for autocompletion, set to 0 for instant. Defaults to 250ms.
#[serde(
serialize_with = "serialize_duration_millis",
deserialize_with = "deserialize_duration_millis"
@ -287,6 +287,8 @@ pub struct Config {
pub workspace_lsp_roots: Vec<PathBuf>,
/// Which line ending to choose for new documents. Defaults to `native`. i.e. `crlf` on Windows, otherwise `lf`.
pub default_line_ending: LineEndingConfig,
/// Whether to automatically insert a trailing line-ending on write if missing. Defaults to `true`.
pub insert_final_newline: bool,
/// Enables smart tab
pub smart_tab: Option<SmartTabConfig>,
}
@ -817,7 +819,7 @@ impl Default for Config {
auto_completion: true,
auto_format: true,
auto_save: false,
idle_timeout: Duration::from_millis(400),
idle_timeout: Duration::from_millis(250),
preview_completion_insert: true,
completion_trigger_len: 2,
auto_info: true,
@ -842,6 +844,7 @@ impl Default for Config {
completion_replace: false,
workspace_lsp_roots: Vec::new(),
default_line_ending: LineEndingConfig::default(),
insert_final_newline: true,
smart_tab: Some(SmartTabConfig::default()),
}
}

@ -297,6 +297,11 @@ impl Theme {
self.highlights[index]
}
#[inline]
pub fn scope(&self, index: usize) -> &str {
&self.scopes[index]
}
pub fn name(&self) -> &str {
&self.name
}

@ -587,7 +587,7 @@ scope = "source.ts"
injection-regex = "(ts|typescript)"
file-types = ["ts", "mts", "cts"]
language-id = "typescript"
shebangs = []
shebangs = ["deno", "ts-node"]
roots = []
language-servers = [ "typescript-language-server" ]
indent = { tab-width = 2, unit = " " }
@ -821,6 +821,7 @@ name = "julia"
scope = "source.julia"
injection-regex = "julia"
file-types = ["jl"]
shebangs = ["julia"]
roots = ["Manifest.toml", "Project.toml"]
comment-token = "#"
language-servers = [ "julia" ]
@ -834,7 +835,7 @@ source = { git = "https://github.com/tree-sitter/tree-sitter-julia", rev = "8fb3
name = "java"
scope = "source.java"
injection-regex = "java"
file-types = ["java"]
file-types = ["java", "jav"]
roots = ["pom.xml", "build.gradle", "build.gradle.kts"]
language-servers = [ "jdtls" ]
indent = { tab-width = 2, unit = " " }
@ -874,7 +875,7 @@ name = "ocaml"
scope = "source.ocaml"
injection-regex = "ocaml"
file-types = ["ml"]
shebangs = []
shebangs = ["ocaml", "ocamlrun", "ocamlscript"]
roots = []
comment-token = "(**)"
language-servers = [ "ocamllsp" ]
@ -885,7 +886,6 @@ indent = { tab-width = 2, unit = " " }
'{' = '}'
'[' = ']'
'"' = '"'
'`' = '`'
[[grammar]]
name = "ocaml"
@ -906,7 +906,6 @@ indent = { tab-width = 2, unit = " " }
'{' = '}'
'[' = ']'
'"' = '"'
'`' = '`'
[[grammar]]
name = "ocaml-interface"
@ -1119,7 +1118,7 @@ indent = { tab-width = 2, unit = " " }
[[grammar]]
name = "perl"
source = { git = "https://github.com/tree-sitter-perl/tree-sitter-perl", rev = "ed21ecbcc128a6688770ebafd3ef68a1c9bc1ea9" }
source = { git = "https://github.com/tree-sitter-perl/tree-sitter-perl", rev = "9f3166800d40267fa68ed8273e96baf74f390517" }
[[language]]
name = "pod"
@ -1380,7 +1379,7 @@ source = { git = "https://github.com/mtoohey31/tree-sitter-gitattributes", rev =
name = "git-ignore"
scope = "source.gitignore"
roots = []
file-types = [".gitignore", ".gitignore_global"]
file-types = [".gitignore", ".gitignore_global", ".ignore", ".prettierignore", ".eslintignore", ".npmignore", "CODEOWNERS"]
injection-regex = "git-ignore"
comment-token = "#"
grammar = "gitignore"
@ -1824,6 +1823,7 @@ name = "scheme"
scope = "source.scheme"
injection-regex = "scheme"
file-types = ["ss", "scm"]
shebangs = ["scheme", "guile", "chicken"]
roots = []
comment-token = ";"
indent = { tab-width = 2, unit = " " }
@ -2069,7 +2069,7 @@ roots = ["edgedb.toml"]
[[grammar]]
name ="esdl"
source = { git = "https://github.com/greym0uth/tree-sitter-esdl", rev = "b840c8a8028127e0a7c6e6c45141adade2bd75cf" }
source = { git = "https://github.com/greym0uth/tree-sitter-esdl", rev = "df83acc8cacd0cfb139eecee0e718dc32c4f92e2" }
[[language]]
name = "pascal"
@ -2202,7 +2202,7 @@ source = { git = "https://github.com/Unoqwy/tree-sitter-kdl", rev = "e1cd292c6d1
name = "xml"
scope = "source.xml"
injection-regex = "xml"
file-types = ["xml", "mobileconfig", "plist", "xib", "storyboard", "svg", "xsd", "gml", "xaml", "gir"]
file-types = ["xml", "mobileconfig", "plist", "xib", "storyboard", "svg", "xsd", "gml", "xaml", "gir", "rss", "atom", "opml"]
indent = { tab-width = 2, unit = " " }
roots = []
@ -2665,7 +2665,7 @@ indent = { tab-width = 4, unit = " " }
[[grammar]]
name = "blueprint"
source = { git = "https://gitlab.com/gabmus/tree-sitter-blueprint", rev = "7f1a5df44861291d6951b6b2146a9fef4c226e14" }
source = { git = "https://gitlab.com/gabmus/tree-sitter-blueprint", rev = "863cea9f83ad5637300478e0559262f1e791684b" }
[[language]]
name = "forth"

@ -15,6 +15,11 @@
(decorator) @attribute
(property_definition (property_name) @variable.other.member)
(property_definition
(property_binding
"bind" @keyword
(property_name) @variable.other.member
["no-sync-create" "bidirectional" "inverted"]* @keyword))
(object) @type

@ -0,0 +1,29 @@
[
(block)
(conditional_statement)
(loop_statement)
(cstyle_for_statement)
(for_statement)
(elsif)
(array_element_expression)
(hash_element_expression)
(coderef_call_expression)
(anonymous_slice_expression)
(slice_expression)
(keyval_expression)
(anonymous_array_expression)
(anonymous_hash_expression)
(stub_expression)
(func0op_call_expression)
(func1op_call_expression)
(map_grep_expression)
(function_call_expression)
(method_call_expression)
(attribute)
] @indent
[
"}"
"]"
")"
] @outdent

@ -0,0 +1,14 @@
(subroutine_declaration_statement
body: (_) @function.inside) @function.around
(anonymous_subroutine_expression
body: (_) @function.inside) @function.around
(package_statement) @class.around
(package_statement
(block) @class.inside)
(list_expression
(_) @parameter.inside)
(comment) @comment.around
(pod) @comment.around

@ -97,3 +97,8 @@
["(" ")" "[" "]" "{" "}"] @punctuation.bracket
(quote "'") @operator
(unquote_splicing ",@") @operator
(unquote ",") @operator
(quasiquote "`") @operator

@ -1,2 +1,54 @@
((comment) @injection.content
(#set! injection.language "comment"))
; The remaining code in this file incorporates work covered by the following
; copyright and permission notice:
;
; Copyright 2023 the nvim-treesitter authors
;
; Licensed under the Apache License, Version 2.0 (the "License");
; you may not use this file except in compliance with the License.
; You may obtain a copy of the License at
;
; http://www.apache.org/licenses/LICENSE-2.0
;
; Unless required by applicable law or agreed to in writing, software
; distributed under the License is distributed on an "AS IS" BASIS,
; WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
; See the License for the specific language governing permissions and
; limitations under the License.
; Modified for Helix from https://github.com/nvim-treesitter/nvim-treesitter/blob/master/queries/yaml/injections.scm
;; Github actions ("run") / Gitlab CI ("scripts")
(block_mapping_pair
key: (flow_node) @_run (#match? @_run "^(run|script|before_script|after_script)$")
value: (flow_node
(plain_scalar
(string_scalar) @injection.content)
(#set! injection.language "bash")))
(block_mapping_pair
key: (flow_node) @_run (#match? @_run "^(run|script|before_script|after_script)$")
value: (block_node
(block_scalar) @injection.content
(#set! injection.language "bash")))
(block_mapping_pair
key: (flow_node) @_run (#match? @_run "^(run|script|before_script|after_script)$")
value: (block_node
(block_sequence
(block_sequence_item
(flow_node
(plain_scalar
(string_scalar) @injection.content))
(#set! injection.language "bash")))))
(block_mapping_pair
key: (flow_node) @_run (#match? @_run "^(run|script|before_script|after_script)$")
value: (block_node
(block_sequence
(block_sequence_item
(block_node
(block_scalar) @injection.content
(#set! injection.language "bash"))))))

@ -90,6 +90,7 @@
"ui.virtual.whitespace" = { fg = "current_line" }
"ui.virtual.wrap" = { fg = "current_line" }
"ui.virtual.ruler" = { bg = "black" }
"ui.virtual.indent-guide" = { fg = "indent" }
"ui.virtual.inlay-hint" = { fg = "cyan" }
"ui.virtual.inlay-hint.parameter" = { fg = "cyan", modifiers = ["italic", "dim"] }
"ui.virtual.inlay-hint.type" = { fg = "cyan", modifiers = ["italic", "dim"] }
@ -123,6 +124,7 @@ black = "#191A21"
grey = "#666771"
comment = "#6272A4"
current_line = "#44475a"
indent = "#56596a"
selection = "#363848"
red = "#ff5555"
orange = "#ffb86c"

@ -22,7 +22,7 @@
"constant.builtin.boolean" = "yellow"
"constant.character" = "yellow"
"constant.characted.escape" = "red_error"
"constant.character.escape" = "red_error"
"constant.numeric" = "dark_orange"
"string" = "dark_green"
"string.regexp" = "light_purple"

@ -0,0 +1,20 @@
# Material Theme <https://material-theme.com/> for Helix Editor
inherits = "material_deep_ocean"
[palette]
bg = "#212121"
text = "#b0bec5"
gray = "#616161"
error = "#ff5370"
disabled = "#474747"
accent = "#ff9800"
highlight = "#3f3f3f"
comment = "#616161"
selection = "#404040"

@ -0,0 +1,123 @@
# Material Theme <https://material-theme.com/> for Helix Editor
# Syntax Highlighting
"type" = "purple"
"constructor" = "blue"
"constant" = "yellow"
"string" = "green"
"string.regexp" = "yellow"
"string.special" = "blue"
"comment" = { fg = "comment" }
"variable" = "text"
"variable.parameter" = { fg = "orange" }
"variable.builtin" = "yellow"
"label" = "orange"
"punctuation" = "cyan"
"keyword" = "purple"
"keyword.storage" = "cyan"
"operator" = "cyan"
"function" = "blue"
"function.macro" = "cyan"
"tag" = "red"
"attribute" = "purple"
"namespace" = { fg = "yellow" }
"special" = "cyan"
"markup.heading.marker" = { fg = "cyan", modifiers = ["bold"] }
"markup.heading.1" = "cyan"
"markup.heading.2" = "red"
"markup.heading.3" = "green"
"markup.heading.4" = "yellow"
"markup.heading.5" = "blue"
"markup.heading.6" = "orange"
"markup.list" = "purple"
"markup.bold" = { modifiers = ["bold"] }
"markup.italic" = { modifiers = ["italic"] }
"markup.strikethrough" = { modifiers = ["crossed_out"] }
"markup.link.url" = { fg = "green", modifiers = ["underlined"] }
"markup.link.text" = "blue"
"markup.raw" = "text"
"diff.plus" = "green"
"diff.minus" = "red"
"diff.delta" = "blue"
# User Interface
"ui.background" = { bg = "bg", fg = "text" }
"ui.text" = { fg = "text" }
"ui.statusline" = { bg = "bg", fg = "text" }
"ui.statusline.inactive" = { bg = "bg", fg = "disabled" }
"ui.statusline.normal" = { bg = "accent", fg = "text" }
"ui.statusline.insert" = { bg = "green", fg = "text" }
"ui.statusline.select" = { bg = "purple", fg = "text" }
"ui.selection" = { bg = "selection" }
"ui.linenr" = { fg = "line-number" }
"ui.linenr.selected" = { fg = "accent" }
"ui.cursor" = { bg = "highlight", fg = "white" }
"ui.cursor.primary" = { bg = "white", fg = "gray" }
"ui.cursorline.primary" = { bg = "white" }
"ui.virtual" = { fg = "gray" }
"ui.virtual.ruler" = { bg = "highlight" }
"ui.virtual.indent-guide" = { fg = "gray" }
"ui.highlight" = { bg = "highlight" }
"ui.menu" = { bg = "highlight", fg = "text" }
"ui.help" = { bg = "highlight", fg = "text" }
"ui.popup" = { bg = "highlight", fg = "text" }
warning = "yellow"
error = "error"
info = "blue"
hint = "purple"
[palette]
bg = "#0f111a"
text = "#a6accd"
white = "#eeffff"
green = "#c3e88d"
yellow = "#ffcb6b"
blue = "#82aaff"
red = "#f07178"
purple = "#c792ea"
orange = "#f78c6c"
cyan = "#89ddff"
gray = "#717cb4"
error = "#ff5370"
disabled = "#464b5d"
accent = "#84ffff"
highlight = "#1f2233"
comment = "#464b5d"
selection = "#1f2233"
line-number = "#3b3f51"

@ -0,0 +1,21 @@
# Material Theme <https://material-theme.com/> for Helix Editor
inherits = "material_deep_ocean"
[palette]
bg = "#25363b"
text = "#b0bec5"
gray = "#546e7a"
disabled = "#415967"
accent = "#009688"
highlight = "#425b67"
comment = "#546e7a"
selection = "#395b65"
line-number = "#355058"

@ -0,0 +1,19 @@
# Material Theme <https://material-theme.com/> for Helix Editor
inherits = "material_deep_ocean"
[palette]
bg = "#292d3e"
text = "#a6accd"
disabled = "#515772"
accent = "#ab47bc"
highlight = "#444267"
comment = "#676e95"
selection = "#444267"
line-number = "#3a3f58"

@ -13,7 +13,7 @@ inherits = "monokai"
"keyword.storage.modifier" = { fg = "#fd971f", modifiers = ["italic"] }
"label" = "#e6db74"
"operator" = "keyword"
"punctuation.delimeter" = "#8f8f8f"
"punctuation.delimiter" = "#8f8f8f"
"type" = "light-blue"
"variable.builtin" = { fg = "#ae81ff", modifiers = ["bold"] }
"tag.builtin" = { fg = "#ae81ff", modifiers = ["bold"] }

@ -125,7 +125,7 @@
"keyword.control.exception" = { fg = "magenta" } # `try`, `catch`, `raise`/`throw` and related.
"keyword.operator" = { fg = "fg2", modifiers = ["bold"] } # 'or', 'and', 'in'.
"keyword.directive" = { fg = "pink-bright" } # Preprocessor directives (#if in C...).
"keyword.function" = { fg = "red" } # The keyword to define a funtion: 'def', 'fun', 'fn'.
"keyword.function" = { fg = "red" } # The keyword to define a function: 'def', 'fun', 'fn'.
"keyword.storage" = { fg = "magenta" } # Keywords describing how things are stored
"keyword.storage.type" = { fg = "magenta" } # The type of something, class, function, var, let, etc.
"keyword.storage.modifier" = { fg = "yellow" } # Storage modifiers like static, mut, const, ref, etc.
@ -183,6 +183,6 @@ bg4 = "#39506d" # Conceal, border fg
fg0 = "#d6d6d7" # Lighter fg
fg1 = "#cdcecf" # Default fg
fg2 = "#aeafb0" # Darker fg (status line)
fg3 = "#71839b" # Darker fg (line numbers, fold colums)
fg3 = "#71839b" # Darker fg (line numbers, fold columns)
sel0 = "#2b3b51" # Popup bg, visual selection bg
sel1 = "#3c5372" # Popup sel bg, search bg

@ -100,14 +100,14 @@
'variable' = { fg = "white" } # Variable names.
'variable.builtin' = { } # Language reserved variables: `this`, `self`, `super`, etc.
'variable.parameter' = { } # Funtion parameters.
'variable.parameter' = { } # Function parameters.
'variable.other.member' = { } # Fields of composite data types (e.g. structs, unions).
'variable.function' = { } # ?
'label' = { fg = "purple" } # Loop labels in rust.
'punctuation' = { fg = "yellow", modifiers = ["bold"] } # (){}[]:;,.
# 'punctuation.delimeter' = { fg = "yellow" } # Commas and colons.
# 'punctuation.delimiter' = { fg = "yellow" } # Commas and colons.
# 'punctuation.bracket' = { fg = "yellow" } # Parentheses, angle brackets, etc.
'keyword' = { fg = "pink", modifiers = ["bold"] } # Language reserved keywords.
@ -119,7 +119,7 @@
'keyword.control.exception' = {fg = "pink", modifiers = ["bold"] } # 'raise' in python.
'keyword.operator' = { } # 'or', 'and', 'in'.
'keyword.directive' = { fg = "purple" } # Preprocessor directives (#if in C).
'keyword.function' = { } # The keyword to define a funtion: 'def', 'fun', 'fn'.
'keyword.function' = { } # The keyword to define a function: 'def', 'fun', 'fn'.
'operator' = { fg = "pink", modifiers = ["bold"] } # Logical (&&, ||) and - I assume - Mathematical (+, %) operators

@ -15,14 +15,14 @@
"constructor" = "nord8"
# Diagnostics
"diagnostic" = "nord13"
"diagnostic.error" = "nord11"
"diagnostic" = { underline = { color = "nord13", style = "curl" } }
"diagnostic.error" = { underline = { color = "nord11", style = "curl" } }
"error" = "nord11"
"diagnostic.hint" = "nord10"
"diagnostic.hint" = { underline = { color = "nord10", style = "curl" } }
"hint" = "nord10"
"diagnostic.info" = "nord8"
"diagnostic.info" = { underline = { color = "nord8", style = "curl" } }
"info" = "nord8"
"diagnostic.warning" = "nord13"
"diagnostic.warning" = { underline = { color = "nord13", style = "curl" } }
"warning" = "nord13"
# Diffs
@ -100,6 +100,7 @@
"ui.popup" = { bg = "nord1" }
"ui.popup.info" = { bg = "nord1" }
"ui.help" = { bg = "nord1" }
"ui.text.focus" = { fg = "nord8", bg = "nord2" }
# Gutter
"ui.gutter" = "nord5"

@ -1,124 +1,75 @@
# Palette based on https://github.com/NLKNguyen/papercolor-theme
# Author: Soc Virnyl Estela <socvirnyl.estela@gmail.com>
"ui.linenr.selected" = { fg = "linenr_fg_selected" }
"ui.background" = {bg="background"}
"ui.text" = "foreground"
"ui.text.focus" = { fg = "selection_background", modifiers = ["bold"]}
"ui.selection" = {bg="selection_background", fg="selection_foreground"}
"ui.cursorline" = {bg="cursorline_background"}
"ui.highlight" = {bg="cursorline_background"}
"ui.statusline" = {bg="paper_bar_bg", fg="regular0"}
"ui.statusline.select" = {bg="background", fg="bright7"}
"ui.statusline.normal" = {bg="background", fg="bright3"}
"ui.statusline.inactive" = {bg="selection_foreground", fg="foreground"}
"ui.virtual.whitespace" = { fg = "regular5" }
"ui.virtual.ruler" = {bg="cursorline_background"}
"ui.cursor.match" = {bg = "regular5", fg = "regular0"}
"ui.cursor" = {bg = "regular5", fg = "background"}
"ui.window" = {bg = "#303030", fg = "bright2"}
"ui.help" = {bg = "background", fg = "bright2"}
"ui.popup" = {bg = "#303030", fg = "bright6"}
"ui.menu" = {bg = "#303030", fg = "bright6"}
"ui.menu.selected" = {bg = "#C6C6C6", fg="selection_foreground"}
inherits = "papercolor-light"
"markup.heading" = { fg = "regular4", modifiers = ["bold"] }
"markup.heading.1" = { fg = "bright2", modifiers = ["bold"] }
"markup.heading.2" = { fg = "bright5", modifiers = ["bold"] }
"markup.heading.3" = { fg = "bright3", modifiers = ["bold"] }
"markup.heading.4" = { fg = "bright5", modifiers = ["bold"] }
"markup.heading.5" = { fg = "bright5", modifiers = ["bold"] }
"markup.heading.6" = { fg = "bright5", modifiers = ["bold"] }
"markup.list" = "bright3"
"markup.bold" = { fg = "foreground", modifiers = ["bold"] }
"markup.italic" = { fg = "bright0", modifiers = ["italic"] }
"markup.strikethrough" = { modifiers = ["crossed_out"] }
"markup.link.url" = { fg = "bright6", modifiers = ["underlined"] }
"markup.link.text" = "bright2"
"markup.link.label" = { fg = "regular2", modifiers = ["bold"] }
"markup.raw" = "foreground"
"string" = "foreground"
"attribute" = "bright7"
"keyword" = { fg = "regular4", modifiers = ["bold"]}
"keyword.directive" = "regular4"
"keyword.control.conditional" = "bright3"
"keyword.function" = "regular4"
"namespace" = "bright1"
"type" = "bright2"
"type.builtin" = { fg = "foreground", modifiers = ["bold"]}
"variable" = "foreground"
"variable.builtin" = "cyan"
"variable.other.member" = "cyan"
"variable.parameter" = "foreground"
"special" = "#3E999F"
"function" = "bright6"
"constructor" = "regular4"
"function.builtin" = { fg = "foreground", modifiers = ["bold"]}
"function.macro" = { fg = "regular4", modifiers = ["bold"] }
"comment" = { fg = "#686868", modifiers = ["dim"] }
"ui.linenr" = { fg = "bright0" }
"module" = "regular4"
"constant" = "bright5"
"constant.builtin" = "bright6"
"constant.numeric" = "bright5"
"constant.character.escape" = { fg = "foreground", modifiers = ["bold"]}
"operator" = { fg = "regular4", modifiers = ["bold"]}
"label" = { fg = "selection_background", modifiers = ["bold", "italic"] }
"diff.plus" = "regular2"
"diff.delta" = "regular6"
"diff.minus" = "regular1"
[palette]
background = "#1c1c1c"
foreground = "#d0d0d0"
"warning" = "bright4"
"error" = "regular1"
"info" = "bright4"
regular0 = "#1c1c1c" # color00 "Background"
regular1 = "#af005f" # color01 "Negative"
regular2 = "#5faf00" # color02 "Positive"
regular3 = "#d7af5f" # color03 "Olive"
regular4 = "#5fafd7" # color04 "Neutral" / Aqua
regular5 = "#808080" # color05 "Comment"
regular6 = "#d7875f" # color06 "Navy"
regular7 = "#d0d0d0" # color07 "Foreground"
bright0 = "#585858" # color08 "Nontext"
bright1 = "#5faf5f" # color09 "Red"
bright2 = "#afd700" # color10 "Pink"
bright3 = "#af87d7" # color11 "Purple"
bright4 = "#ffaf00" # color12 "Accent"
bright5 = "#ff5faf" # color13 "Orange"
bright6 = "#00afaf" # color14 "Blue"
bright7 = "#5f8787" # color15 "Highlight"
"diagnostic.warning".underline = { color = "bright4", style = "curl" }
"diagnostic.error".underline = { color = "regular1", style = "curl" }
"diagnostic.info".underline = { color = "bright4", style = "curl" }
"diagnostic.hint".underline = { color = "bright4", style = "curl" }
selection_fg = "#000000"
selection_bg = "#8787af"
selection_secondary_fg = "#333333"
selection_secondary_bg = "#707097"
special = "#3e999f"
cursorline_bg = "#303030"
cursorline_secondary_bg = "#2a2a2a"
cursorcolumn_bg = "#303030"
cursorcolumn_secondary_bg = "#2a2a2a"
cursorlinenr_fg = "#ffff00"
popupmenu_fg = "#c6c6c6"
popupmenu_bg = "#303030"
linenumber_fg = "#585858"
vertsplit_fg = "#5f8787"
statusline_active_fg = "#1c1c1c"
statusline_active_bg = "#5f8787"
statusline_inactive_fg = "#bcbcbc"
statusline_inactive_bg = "#3a3a3a"
todo_fg = "#ff8700"
error_fg = "#af005f"
error_bg = "#5f0000"
matchparen_bg = "#4e4e4e"
matchparen_fg = "#c6c6c6"
wildmenu_fg = "#1c1c1c"
wildmenu_bg = "#afd700"
diffadd_fg = "#87d700"
diffadd_bg = "#005f00"
diffdelete_fg = "#af005f"
diffdelete_bg = "#5f0000"
diffchange_bg = "#005f5f"
[palette]
background="#1c1c1c"
foreground="#d0d0d0"
regular0="#1c1c1c"
regular1="#af005f"
regular2="#5faf00"
regular3="#d7af5f"
regular4="#5fafd7"
regular5="#808080"
regular6="#d7875f"
regular7="#d0d0d0"
bright0="#585858"
bright1="#5faf5f"
bright2="#afd700"
bright3="#af87d7"
bright4="#FFAF00"
bright5="#ff5faf"
bright6="#00afaf"
bright7="#5f8787"
selection_foreground="#585858"
selection_background="#8787AF"
cursorline_background="#303030"
paper_bar_bg="#5F8787"
black="#1c1c1c"
red="#af005f"
green="#5faf00"
yellow="#d7af5f"
blue="#5fafd7"
magenta="#808080"
cyan="#d7875f"
gray="#d0d0d0"
light-red="#5faf5f"
light-green="#afd700"
light-yellow="#af87d7"
light-blue="#FFAF00"
light-magenta="#ff5faf"
light-cyan="#00afaf"
light-gray="#5f8787"
white="#808080"
linenr_fg_selected="#FFFF00"
# 16 bit ANSI color names
black = "#1c1c1c"
red = "#af005f"
green = "#5faf00"
yellow = "#d7af5f"
blue = "#5fafd7"
magenta = "#808080"
cyan = "#d7875f"
white = "#d0d0d0"
light-black = "#585858"
light-red = "#5faf5f"
light-green = "#afd700"
light-yellow = "#af87d7"
light-blue = "#ffaf00"
light-magenta = "#ff5faf"
light-cyan = "#00afaf"
light-white = "#5f8787"

@ -1,31 +1,124 @@
# Palette based on https://github.com/NLKNguyen/papercolor-theme
# Author: Soc Virnyl Estela <socvirnyl.estela@gmail.com>
"ui.linenr.selected" = { fg = "linenr_fg_selected" }
"ui.background" = {bg="background"}
"ui.linenr.selected" = { fg = "cursorlinenr_fg", modifiers = ["bold"] }
"ui.linenr" = { fg = "linenumber_fg" }
"ui.background" = { bg = "background" }
"ui.text" = "foreground"
"ui.text.focus" = { fg = "selection_background", modifiers = ["bold"]}
"ui.selection" = {bg="selection_background", fg="selection_foreground"}
"ui.highlight" = {bg="cursorline_background"}
"ui.cursorline" = {bg="cursorline_background"}
"ui.statusline" = {bg="paper_bar_bg", fg="regular0"}
"ui.statusline.select" = {bg="background", fg="bright7"}
"ui.statusline.normal" = {bg="background", fg="bright3"}
"ui.statusline.inactive" = {bg="bright0", fg="foreground"}
"ui.virtual" = "indent"
"ui.text.focus" = { fg = "selection_bg", modifiers = ["bold"] }
"ui.selection" = { bg = "selection_secondary_bg", fg = "selection_secondary_fg" }
"ui.selection.primary" = { bg = "selection_bg", fg = "selection_fg" }
"ui.highlight" = { bg = "cursorline_bg" }
"ui.cursorline" = { bg = "cursorline_bg" }
"ui.cursorline.secondary" = { bg = "cursorline_secondary_bg" }
"ui.cursorcolumn" = { bg = "cursorline_bg" }
"ui.cursorcolumn.secondary" = { bg = "cursorcolumn_secondary_bg" }
"ui.statusline" = { bg = "statusline_active_bg", fg = "statusline_active_fg" }
"ui.statusline.inactive" = { bg = "statusline_inactive_bg", fg = "statusline_inactive_fg" }
"ui.statusline.normal" = { bg = "statusline_inactive_bg", fg = "bright6" }
"ui.statusline.insert" = { bg = "statusline_inactive_bg", fg = "bright4" }
"ui.statusline.select" = { bg = "statusline_inactive_bg", fg = "regular3" }
"ui.statusline.separator" = { bg = "statusline_active_bg", fg = "statusline_active_bg" }
"ui.virtual" = { fg = "cursorlinenr_fg" }
"ui.virtual.whitespace" = { fg = "regular5" }
"ui.virtual.ruler" = {bg="cursorline_background"}
"ui.cursor.match" = {bg = "regular5", fg = "regular0"}
"ui.cursor" = {bg = "regular5", fg = "background"}
"ui.window" = {bg = "#D0D0D0", fg = "bright2"}
"ui.help" = {bg = "background", fg = "bright2"}
"ui.popup" = {bg = "#D0D0D0", fg = "bright7"}
"ui.menu" = {bg = "#D0D0D0", fg = "bright7"}
"ui.menu.selected" = {bg = "selection_background", fg="selection_foreground"}
"markup.heading" = { fg = "bright7", modifiers = ["bold"] }
"ui.virtual.indent-guide" = { fg = "bright0" }
"ui.virtual.ruler" = { bg = "cursorline_secondary_bg", fg = "regular4" }
"ui.cursor.match" = { bg = "matchparen_bg", fg = "matchparen_fg" }
"ui.cursor" = { bg = "regular5", fg = "background" }
"ui.cursor.primary" = { bg = "foreground", fg = "background" }
"ui.window" = { fg = "vertsplit_fg" }
"ui.help" = { bg = "wildmenu_bg", fg = "wildmenu_fg" }
"ui.popup" = { bg = "popupmenu_bg", fg = "popupmenu_fg" }
"ui.popup.info" = { bg = "popupmenu_bg", fg = "bright7", modifiers = ["bold"] }
"ui.menu" = { bg = "popupmenu_bg", fg = "foreground" }
"ui.menu.selected" = { bg = "selection_bg", fg = "selection_fg" }
"warning" = "bright5"
"error" = { bg = "error_bg", fg = "error_fg" }
"info" = "todo_fg"
"diagnostic.warning" = { fg = "bright0", modifiers = [
"dim",
], underline = { color = "bright5", style = "curl" } }
"diagnostic.error".underline = { color = "bright1", style = "curl" }
"diagnostic.info".underline = { color = "bright4", style = "curl" }
"diagnostic.hint".underline = { color = "bright6", style = "curl" }
# Tree-sitter scopes for syntax highlighting
"attribute" = "bright4"
"type" = { fg = "bright2", modifiers = ["bold"] }
"type.builtin" = { fg = "bright2", modifiers = ["bold"] }
"type.enum" = { fg = "foreground" }
"type.enum.variant" = { fg = "foreground" }
"constructor" = "foreground"
"constant" = "bright5"
"constant.builtin" = "regular3"
"constant.builtin.boolean" = { fg = "regular2", modifiers = ["bold"] }
"constant.character.escape" = { fg = "bright3", modifiers = ["bold"] }
"constant.character" = { fg = "regular3" }
"constant.numeric" = "bright5"
"string" = "regular3"
"string.regexp" = "bright3"
"comment" = { fg = "regular5", modifiers = ["italic"] }
"comment.line" = { fg = "regular5", modifiers = ["italic"] }
"comment.block" = { fg = "regular5", modifiers = ["italic"] }
"comment.block.documentation" = { fg = "regular5", modifiers = ["bold"] }
"variable" = "foreground"
"variable.builtin" = "bright5"
"variable.other.member" = "foreground"
"variable.parameter" = "foreground"
"label" = { fg = "selection_bg", modifiers = ["bold", "italic"] }
"punctuation" = { fg = "foreground" }
"punctuation.delimiter" = { fg = "regular4", modifiers = ["bold"] }
"punctuation.bracket" = { fg = "foreground" }
"punctuation.special" = { fg = "bright1", modifiers = ["bold"] }
"keyword" = { fg = "bright2" }
"keyword.control" = "bright1"
"keyword.control.conditional" = { fg = "bright3", modifiers = ["bold"] }
"keyword.control.repeat" = { fg = "bright3", modifiers = ["bold"] }
"keyword.control.import" = { fg = "bright2" }
"keyword.control.return" = { fg = "bright2" }
"keyword.control.exception" = { fg = "bright1" }
"keyword.operator" = { fg = "regular4", modifiers = ["bold"] }
"keyword.directive" = "regular4"
"keyword.function" = "bright2"
"keyword.storage" = "bright2"
"keyword.storage.type" = { fg = "regular4", modifiers = ["bold"] }
"keyword.storage.modifier" = { fg = "regular6", modifiers = ["bold"] }
"keyword.storage.modifier.ref" = { fg = "regular4", modifiers = ["bold"] }
"keyword.special" = "bright1"
"operator" = { fg = "regular4", modifiers = ["bold"] }
"function" = { fg = "foreground" }
"function.builtin" = { fg = "bright6" }
"function.method" = { fg = "foreground" }
"function.macro" = { fg = "regular3", modifiers = ["bold"] }
"function.special" = { fg = "bright4" }
"tag" = { fg = "regular4" }
"namespace" = "bright6"
"special" = "special"
"markup.heading" = { fg = "bright4", modifiers = ["bold"] }
"markup.heading.marker" = { fg = "bright2", modifiers = ["bold"] }
"markup.heading.1" = { fg = "bright2", modifiers = ["bold"] }
"markup.heading.2" = { fg = "bright4", modifiers = ["bold"] }
"markup.heading.2" = { fg = "bright5", modifiers = ["bold"] }
"markup.heading.3" = { fg = "bright3", modifiers = ["bold"] }
"markup.heading.4" = { fg = "bright4", modifiers = ["bold"] }
"markup.heading.5" = { fg = "bright4", modifiers = ["bold"] }
@ -34,90 +127,84 @@
"markup.bold" = { fg = "foreground", modifiers = ["bold"] }
"markup.italic" = { modifiers = ["italic"] }
"markup.strikethrough" = { modifiers = ["crossed_out"] }
"markup.link.url" = { fg = "regular4", modifiers = ["underlined"] }
"markup.link.url" = { fg = "bright6", underline.style = "line" }
"markup.link.text" = "bright2"
"markup.link.label" = { fg = "regular7", modifiers = ["bold"] }
"markup.raw" = "foreground"
"string" = "foreground"
"attribute" = "bright7"
"keyword" = { fg = "regular4", modifiers = ["bold"]}
"keyword.directive" = "regular1"
"namespace" = "regular1"
"type" = "bright2"
"type.builtin" = { fg = "regular4", modifiers = ["bold"]}
"variable" = "foreground"
"variable.builtin" = "cyan"
"variable.other.member" = "regular4"
"variable.parameter" = "foreground"
"special" = "#3E999F"
"function" = "bright1"
"constructor" = "bright1"
"function.builtin" = { fg = "regular4", modifiers = ["bold"]}
"function.macro" = { fg = "regular1" }
"comment" = { fg = "bright0", modifiers = ["dim"] }
"ui.linenr" = { fg = "bright0" }
"module" = "#af0000"
"constant" = "#5f8700"
"constant.builtin" = "#5f8700"
"constant.numeric" = "#d75f00"
"constant.character.escape" = { fg = "#8700af", modifiers = ["bold"]}
"operator" = { fg = "regular4", modifiers = ["bold"]}
"markup.link.label" = { fg = "regular2", modifiers = ["bold"] }
"markup.quote" = "regular4"
# Both inline and block code
"markup.raw" = "regular3"
"label" = { fg = "selection_background", modifiers = ["bold", "italic"] }
"diff.plus" = { bg = "diffadd_bg", fg = "diffadd_fg" }
"diff.delta" = { bg = "diffchange_bg" }
"diff.delta.moved" = { modifiers = ["italic"] }
"diff.minus" = { bg = "diffdelete_bg", fg = "diffdelete_fg" }
"diff.plus" = "regular2"
"diff.delta" = "bright0"
"diff.minus" = "bright1"
"warning" = "bright4"
"error" = "regular1"
"info" = "#FFAF00"
[palette]
background = "#eeeeee"
foreground = "#444444"
regular0 = "#eeeeee" # color00 "Background"
regular1 = "#af0000" # color01 "Negative"
regular2 = "#008700" # color02 "Positive"
regular3 = "#5f8700" # color03 "Olve"
regular4 = "#0087af" # color04 "Neutral" / Aqua
regular5 = "#878787" # color05 "Comment"
regular6 = "#005f87" # color06 "Navy"
regular7 = "#444444" # color07 "Foreground"
bright0 = "#bcbcbc" # color08 "Nontext"
bright1 = "#d70000" # color09 "Red"
bright2 = "#d70087" # color10 "Pink"
bright3 = "#8700af" # color11 "Purple"
bright4 = "#d75f00" # color12 "Accent"
bright5 = "#d75f00" # color13 "Orange"
bright6 = "#005faf" # color14 "Blue"
bright7 = "#005f87" # color15 "Highlight"
"diagnostic.warning".underline = { color = "bright4", style = "curl" }
"diagnostic.error".underline = { color = "regular1", style = "curl" }
"diagnostic.info".underline = { color = "#FFAF00", style = "curl" }
"diagnostic.hint".underline = { color = "#FFAF00", style = "curl" }
selection_fg = "#eeeeee"
selection_bg = "#0087af"
selection_secondary_fg = "#d9d7d7"
selection_secondary_bg = "#2c687a"
special = "#3e999f"
cursorline_bg = "#e4e4e4"
cursorline_secondary_bg = "#eaeaea"
cursorcolumn_bg = "#e4e4e4"
cursorcolumn_secondary_bg = "#eaeaea"
cursorlinenr_fg = "#af5f00"
popupmenu_fg = "#444444"
popupmenu_bg = "#d0d0d0"
linenumber_fg = "#b2b2b2"
vertsplit_fg = "#005f87"
statusline_active_fg = "#e4e4e4"
statusline_active_bg = "#005f87"
statusline_inactive_fg = "#444444"
statusline_inactive_bg = "#d0d0d0"
todo_fg = "#00af5f"
error_fg = "#af0000"
error_bg = "#ffd7ff"
matchparen_bg = "#c6c6c6"
matchparen_fg = "#005f87"
wildmenu_fg = "#444444"
wildmenu_bg = "#ffff00"
diffadd_fg = "#008700"
diffadd_bg = "#afffaf"
diffdelete_fg = "#af0000"
diffdelete_bg = "#ffd7ff"
diffchange_bg = "#ffd787"
[palette]
background="#eeeeee"
foreground="#444444"
regular0="#eeeeee"
regular1="#af0000"
regular2="#008700"
regular3="#5f8700"
regular4="#0087af"
regular5="#878787"
regular6="#005f87"
regular7="#764e37"
bright0="#bcbcbc"
bright1="#d70000"
bright2="#d70087"
bright3="#8700af"
bright4="#d75f00"
bright5="#d75f00"
bright6="#4c7a5d"
bright7="#005faf"
selection_foreground="#eeeeee"
selection_background="#0087af"
cursorline_background="#fdfdfd"
paper_bar_bg="#005F87"
black="#eeeeee"
red="#d70000"
green="#008700"
yellow="#5f8700"
blue="#0087af"
magenta="#878787"
cyan="#005f87"
gray="#764e37"
light-red="#d70000"
light-green="#d70087"
light-yellow="#8700af"
light-blue="#d75f00"
light-magenta="#d75f00"
light-cyan="#4c7a4d"
light-gray="#005faf"
white="#444444"
linenr_fg_selected="#AF634D"
# 16 bit ANSI color names
black = "#eeeeee"
red = "#d70000"
green = "#008700"
yellow = "#5f8700"
blue = "#0087af"
magenta = "#878787"
cyan = "#005f87"
white = "#444444"
light-black = "#bcbcbc"
light-red = "#d70000"
light-green = "#d70087"
light-yellow = "#8700af"
light-blue = "#d75f00"
light-magenta = "#d75f00"
light-cyan = "#4c7a4d"
light-white = "#005faf"

@ -89,7 +89,7 @@
"comment" = { fg = "muted", modifiers = ["italic"]}
# "comment.line" = ""
# "comment.block" = ""
# "comment.block.documenation" = ""
# "comment.block.documentation" = ""
"variable" = "text"
"variable.builtin" = "love"

@ -9,7 +9,7 @@
"ui.selection" = { bg = "#304a3d" }
"ui.selection.primary" = { bg = "#2f2f2f" }
"comment" = { fg = "comment" }
"ui.virtual.inlay-hint" = { fg = "comment" }
"ui.virtual.inlay-hint" = { fg = "#9f9f9f" }
"comment.block.documentation" = { fg = "black", modifiers = ["bold"] }
"ui.statusline" = { bg = "statusbg", fg = "#ccdc90" }
"ui.statusline.inactive" = { fg = '#2e3330', bg = '#88b090' }

Loading…
Cancel
Save