on master help sign in
Merge branch 'master' into great_line_ending_and_cursor_range_cleanup
commita77274e8bb33ff08f5411ec4df168b576c0c8fa5 authorNathan Vegdahl <cessen@cessen.com> dateSat 17 Jul 2021, 10:49 (5 years ago) parentb4c59b46cba62b
25 files changed+1177−783
.github/workflows/release.yml+2−1 .gitignore+1 Cargo.lock+28−29 README.md+7−4 book/src/keymap.md+23−20 book/src/themes.md+2 helix-core/Cargo.toml+1−1 helix-core/src/indent.rs+2−2 helix-core/src/syntax.rs+8−8 helix-lsp/Cargo.toml+2−2 helix-syntax/Cargo.toml+4−2 helix-syntax/build.rs+133−66 helix-syntax/src/lib.rs+30−85 helix-term/src/application.rs+15−13 helix-term/src/commands.rs+413−299 helix-term/src/keymap.rs+4−1 helix-term/src/ui/editor.rs+33−3 helix-tui/Cargo.toml+1−1 helix-view/src/document.rs+8−6 helix-view/src/editor.rs+11−9 runtime/grammars/.gitkeep runtime/queries/c/highlights.scm+2 runtime/queries/rust/highlights.scm+303−180 runtime/themes/dark_plus.toml+80 theme.toml+64−51
.github/workflows/release.yml+2−1
@@ -99,6 +99,7 @@ jobs:
9999 else
100100 cp "target/${{ matrix.target }}/release/hx" "dist/"
101101 fi
102+ cp -r runtime dist
102103
103104 - uses: actions/upload-artifact@v2.2.4
104105 with:
@@ -148,7 +149,7 @@ jobs:
148149 pkgname=helix-$TAG-$platform
149150 mkdir tmp/$pkgname
150151 cp LICENSE README.md tmp/$pkgname
151− cp -r runtime tmp/$pkgname/
152+ mv bins-$platform/runtime tmp/$pkgname/
152153 mv bins-$platform/hx$exe tmp/$pkgname
153154 chmod +x tmp/$pkgname/hx$exe
154155
.gitignore+1
@@ -3,3 +3,4 @@ target
33helix-term/rustfmt.toml
44helix-syntax/languages/
55result
6+runtime/grammars
Cargo.lock+28−29
@@ -13,9 +13,9 @@ dependencies = [
1313
1414[[package]]
1515name = "anyhow"
16−version = "1.0.41"
16+version = "1.0.42"
1717source = "registry+https://github.com/rust-lang/crates.io-index"
18−checksum = "15af2628f6890fe2609a3b91bef4c83450512802e59489f9c1cb1fa5df064a61"
18+checksum = "595d3cfa7a60d4555cb5067b99f07142a08ea778de5cf993f7b75c7d8fabc486"
1919
2020[[package]]
2121name = "arc-swap"
@@ -58,12 +58,9 @@ checksum = "df8670b8c7b9dae1793364eafadf7239c40d669904660c5960d74cfd80b46a53"
5858
5959[[package]]
6060name = "cc"
61−version = "1.0.68"
61+version = "1.0.69"
6262source = "registry+https://github.com/rust-lang/crates.io-index"
63−checksum = "4a72c244c1ff497a746a7e1fb3d14bd08420ecda70c8f25c7112f2781652d787"
64−dependencies = [
65− "jobserver",
66−]
63+checksum = "e70cc2f62c6ce1868963827bd677764c62d07c3d9a3e1fb1177ee1a9ab199eb2"
6764
6865[[package]]
6966name = "cfg-if"
@@ -354,8 +351,9 @@ dependencies = [
354351name = "helix-syntax"
355352version = "0.3.0"
356353dependencies = [
354+ "anyhow",
357355 "cc",
358− "serde",
356+ "libloading",
359357 "threadpool",
360358 "tree-sitter",
361359]
@@ -475,15 +473,6 @@ version = "0.4.7"
475473source = "registry+https://github.com/rust-lang/crates.io-index"
476474checksum = "dd25036021b0de88a0aff6b850051563c6516d0bf53f8638938edbb9de732736"
477475
478−[[package]]
479−name = "jobserver"
480−version = "0.1.22"
481−source = "registry+https://github.com/rust-lang/crates.io-index"
482−checksum = "972f5ae5d1cb9c6ae417789196c803205313edde988685da5e3aae0827b9e7fd"
483−dependencies = [
484− "libc",
485−]
486−
487476[[package]]
488477name = "jsonrpc-core"
489478version = "17.1.0"
@@ -509,6 +498,16 @@ version = "0.2.97"
509498source = "registry+https://github.com/rust-lang/crates.io-index"
510499checksum = "12b8adadd720df158f4d70dfe7ccc6adb0472d7c55ca83445f6a5ab3e36f8fb6"
511500
501+[[package]]
502+name = "libloading"
503+version = "0.7.0"
504+source = "registry+https://github.com/rust-lang/crates.io-index"
505+checksum = "6f84d96438c15fcd6c3f244c8fce01d1e2b9c6b5623e9c711dc9286d8fc92d6a"
506+dependencies = [
507+ "cfg-if 1.0.0",
508+ "winapi",
509+]
510+
512511[[package]]
513512name = "lock_api"
514513version = "0.4.4"
@@ -914,9 +913,9 @@ checksum = "f173ac3d1a7e3b28003f40de0b5ce7fe2710f9b9dc3fc38664cebee46b3b6527"
914913
915914[[package]]
916915name = "slotmap"
917−version = "1.0.3"
916+version = "1.0.5"
918917source = "registry+https://github.com/rust-lang/crates.io-index"
919−checksum = "585cd5dffe4e9e06f6dfdf66708b70aca3f781bed561f4f667b2d9c0d4559e36"
918+checksum = "a952280edbecfb1d4bd3cf2dbc309dc6ab523e53487c438ae21a6df09fe84bc4"
920919dependencies = [
921920 "version_check",
922921]
@@ -957,18 +956,18 @@ dependencies = [
957956
958957[[package]]
959958name = "thiserror"
960−version = "1.0.25"
959+version = "1.0.26"
961960source = "registry+https://github.com/rust-lang/crates.io-index"
962−checksum = "fa6f76457f59514c7eeb4e59d891395fab0b2fd1d40723ae737d64153392e9c6"
961+checksum = "93119e4feac1cbe6c798c34d3a53ea0026b0b1de6a120deef895137c0529bfe2"
963962dependencies = [
964963 "thiserror-impl",
965964]
966965
967966[[package]]
968967name = "thiserror-impl"
969−version = "1.0.25"
968+version = "1.0.26"
970969source = "registry+https://github.com/rust-lang/crates.io-index"
971−checksum = "8a36768c0fbf1bb15eca10defa29526bda730a2376c2ab4393ccfa16fb1a318d"
970+checksum = "060d69a0afe7796bf42e9e2ff91f5ee691fb15c53d38b4b62a9a53eb23164745"
972971dependencies = [
973972 "proc-macro2",
974973 "quote",
@@ -1010,9 +1009,9 @@ checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c"
10101009
10111010[[package]]
10121011name = "tokio"
1013−version = "1.7.1"
1012+version = "1.8.1"
10141013source = "registry+https://github.com/rust-lang/crates.io-index"
1015−checksum = "5fb2ed024293bb19f7a5dc54fe83bf86532a44c12a2bb8ba40d64a4509395ca2"
1014+checksum = "98c8b05dc14c75ea83d63dd391100353789f5f24b8b3866542a5e85c8be8e985"
10161015dependencies = [
10171016 "autocfg",
10181017 "bytes",
@@ -1041,9 +1040,9 @@ dependencies = [
10411040
10421041[[package]]
10431042name = "tokio-stream"
1044−version = "0.1.6"
1043+version = "0.1.7"
10451044source = "registry+https://github.com/rust-lang/crates.io-index"
1046−checksum = "f8864d706fdb3cc0843a49647ac892720dac98a6eeb818b77190592cf4994066"
1045+checksum = "7b2f3f698253f03119ac0102beaa64f67a67e08074d03a22d18784104543727f"
10471046dependencies = [
10481047 "futures-core",
10491048 "pin-project-lite",
@@ -1104,9 +1103,9 @@ dependencies = [
11041103
11051104[[package]]
11061105name = "unicode-segmentation"
1107−version = "1.7.1"
1106+version = "1.8.0"
11081107source = "registry+https://github.com/rust-lang/crates.io-index"
1109−checksum = "bb0d2e7be6ae3a5fa87eed5fb451aff96f2573d2694942e40543ae0bbe19c796"
1108+checksum = "8895849a949e7845e06bd6dc1aa51731a103c42707010a5b591c0038fb73385b"
11101109
11111110[[package]]
11121111name = "unicode-width"
README.md+7−4
@@ -41,14 +41,17 @@ cargo install --path helix-term
4141
4242This will install the `hx` binary to `$HOME/.cargo/bin`.
4343
44−Now copy the `runtime/` directory somewhere. Helix will by default look for the runtime
45−inside the config directory or the same directory as executable, but that can be overriden
44+Helix also needs it's runtime files so make sure to copy/symlink the `runtime/` directory into the
45+config directory (for example `~/.config/helix/runtime` on Linux/macOS). This location can be overriden
4646via the `HELIX_RUNTIME` environment variable.
4747
48−> NOTE: running via cargo doesn't require setting explicit `HELIX_RUNTIME` path, it will automatically
48+Packages already solve this for you by wrapping the `hx` binary with a wrapper
49+that sets the variable to the install dir.
50+
51+> NOTE: running via cargo also doesn't require setting explicit `HELIX_RUNTIME` path, it will automatically
4952> detect the `runtime` directory in the project root.
5053
51−If you want to embed the `runtime/` directory into the Helix binary you can build
54+Alternatively, if you want to embed the `runtime/` directory into the Helix binary you can build
5255it with:
5356
5457```
book/src/keymap.md+23−20
@@ -41,26 +41,29 @@
4141
4242### Changes
4343
44−| Key | Description |
45−| ----- | ----------- |
46−| `r` | Replace with a character |
47−| `R` | Replace with yanked text |
48−| `i` | Insert before selection |
49−| `a` | Insert after selection (append) |
50−| `I` | Insert at the start of the line |
51−| `A` | Insert at the end of the line |
52−| `o` | Open new line below selection |
53−| `o` | Open new line above selection |
54−| `u` | Undo change |
55−| `U` | Redo change |
56−| `y` | Yank selection |
57−| `p` | Paste after selection |
58−| `P` | Paste before selection |
59−| `>` | Indent selection |
60−| `<` | Unindent selection |
61−| `=` | Format selection |
62−| `d` | Delete selection |
63−| `c` | Change selection (delete and enter insert mode) |
44+| Key | Description |
45+| ----- | ----------- |
46+| `r` | Replace with a character |
47+| `R` | Replace with yanked text |
48+| `~` | Switch case of the selected text |
49+| `\`` | Set the selected text to upper case |
50+| `Alt-\`` | Set the selected text to lower case |
51+| `i` | Insert before selection |
52+| `a` | Insert after selection (append) |
53+| `I` | Insert at the start of the line |
54+| `A` | Insert at the end of the line |
55+| `o` | Open new line below selection |
56+| `o` | Open new line above selection |
57+| `u` | Undo change |
58+| `U` | Redo change |
59+| `y` | Yank selection |
60+| `p` | Paste after selection |
61+| `P` | Paste before selection |
62+| `>` | Indent selection |
63+| `<` | Unindent selection |
64+| `=` | Format selection |
65+| `d` | Delete selection |
66+| `c` | Change selection (delete and enter insert mode) |
6467
6568### Selection manipulation
6669
book/src/themes.md+2
@@ -51,6 +51,7 @@ Possible keys:
5151| `attribute` | |
5252| `keyword` | |
5353| `keyword.directive` | Preprocessor directives (\#if in C) |
54+| `keyword.control` | Control flow |
5455| `namespace` | |
5556| `punctuation` | |
5657| `punctuation.delimiter` | |
@@ -61,6 +62,7 @@ Possible keys:
6162| `variable.parameter` | |
6263| `type` | |
6364| `type.builtin` | |
65+| `type.enum.variant` | Enum variants |
6466| `constructor` | |
6567| `function` | |
6668| `function.macro` | |
helix-core/Cargo.toml+1−1
@@ -19,7 +19,7 @@ helix-syntax = { version = "0.3", path = "../helix-syntax" }
1919ropey = "1.3"
2020smallvec = "1.4"
2121tendril = "0.4.2"
22−unicode-segmentation = "1.7"
22+unicode-segmentation = "1.8"
2323unicode-width = "0.1"
2424unicode-general-category = "0.4"
2525# slab = "0.4.2"
helix-core/src/indent.rs+2−2
@@ -253,14 +253,14 @@ where
253253
254254 let doc = Rope::from(doc);
255255 use crate::syntax::{
256− Configuration, IndentationConfiguration, Lang, LanguageConfiguration, Loader,
256+ Configuration, IndentationConfiguration, LanguageConfiguration, Loader,
257257 };
258258 use once_cell::sync::OnceCell;
259259 let loader = Loader::new(Configuration {
260260 language: vec![LanguageConfiguration {
261261 scope: "source.rust".to_string(),
262262 file_types: vec!["rs".to_string()],
263− language_id: Lang::Rust,
263+ language_id: "Rust".to_string(),
264264 highlight_config: OnceCell::new(),
265265 //
266266 roots: vec![],
helix-core/src/syntax.rs+8−8
@@ -5,7 +5,7 @@ use crate::{
55 Rope, RopeSlice, Tendril,
66};
77
8−pub use helix_syntax::{get_language, get_language_name, Lang};
8+pub use helix_syntax::get_language;
99
1010use arc_swap::ArcSwap;
1111
@@ -31,7 +31,7 @@ pub struct Configuration {
3131#[serde(rename_all = "kebab-case")]
3232pub struct LanguageConfiguration {
3333 #[serde(rename = "name")]
34− pub(crate) language_id: Lang,
34+ pub(crate) language_id: String,
3535 pub scope: String, // source.rust
3636 pub file_types: Vec<String>, // filename ends_with? <Gemfile, rb, etc>
3737 pub roots: Vec<String>, // these indicate project roots <.git, Cargo.toml>
@@ -153,7 +153,7 @@ fn read_query(language: &str, filename: &str) -> String {
153153
154154impl LanguageConfiguration {
155155 fn initialize_highlight(&self, scopes: &[String]) -> Option<Arc<HighlightConfiguration>> {
156− let language = get_language_name(self.language_id).to_ascii_lowercase();
156+ let language = self.language_id.to_ascii_lowercase();
157157
158158 let highlights_query = read_query(&language, "highlights.scm");
159159 // always highlight syntax errors
@@ -161,17 +161,17 @@ impl LanguageConfiguration {
161161
162162 let injections_query = read_query(&language, "injections.scm");
163163
164− let locals_query = "";
164+ let locals_query = read_query(&language, "locals.scm");
165165
166166 if highlights_query.is_empty() {
167167 None
168168 } else {
169− let language = get_language(self.language_id);
169+ let language = get_language(&crate::RUNTIME_DIR, &self.language_id).ok()?;
170170 let config = HighlightConfiguration::new(
171171 language,
172172 &highlights_query,
173173 &injections_query,
174− locals_query,
174+ &locals_query,
175175 )
176176 .unwrap(); // TODO: no unwrap
177177 config.configure(scopes);
@@ -198,7 +198,7 @@ impl LanguageConfiguration {
198198 pub fn indent_query(&self) -> Option<&IndentQuery> {
199199 self.indent_query
200200 .get_or_init(|| {
201− let language = get_language_name(self.language_id).to_ascii_lowercase();
201+ let language = self.language_id.to_ascii_lowercase();
202202
203203 let toml = load_runtime_file(&language, "indents.toml").ok()?;
204204 toml::from_slice(toml.as_bytes()).ok()
@@ -1812,7 +1812,7 @@ mod test {
18121812 .map(String::from)
18131813 .collect();
18141814
1815− let language = get_language(Lang::Rust);
1815+ let language = get_language(&crate::RUNTIME_DIR, "Rust").unwrap();
18161816 let config = HighlightConfiguration::new(
18171817 language,
18181818 &std::fs::read_to_string(
helix-lsp/Cargo.toml+2−2
@@ -23,5 +23,5 @@ lsp-types = { version = "0.89", features = ["proposed"] }
2323serde = { version = "1.0", features = ["derive"] }
2424serde_json = "1.0"
2525thiserror = "1.0"
26−tokio = { version = "1.7", features = ["full"] }
27−tokio-stream = "0.1.6"
26+tokio = { version = "1.8", features = ["full"] }
27+tokio-stream = "0.1.7"
helix-syntax/Cargo.toml+4−2
@@ -12,8 +12,10 @@ include = ["src/**/*", "languages/**/*", "build.rs", "!**/docs/**/*", "!**/test/
1212
1313[dependencies]
1414tree-sitter = "0.19"
15−serde = { version = "1.0", features = ["derive"] }
15+libloading = "0.7"
16+anyhow = "1"
1617
1718[build-dependencies]
18−cc = { version = "1", features = ["parallel"] }
19+cc = { version = "1" }
1920threadpool = { version = "1.0" }
21+anyhow = "1"
helix-syntax/build.rs+133−66
@@ -1,79 +1,147 @@
1+use anyhow::{anyhow, Context, Result};
12use std::fs;
2−use std::path::PathBuf;
3+use std::time::SystemTime;
4+use std::{
5+ path::{Path, PathBuf},
6+ process::Command,
7+};
38
49use std::sync::mpsc::channel;
510
6−fn collect_tree_sitter_dirs(ignore: &[String]) -> Vec<String> {
11+fn collect_tree_sitter_dirs(ignore: &[String]) -> Result<Vec<String>> {
712 let mut dirs = Vec::new();
8− for entry in fs::read_dir("languages").unwrap().flatten() {
13+ let path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("languages");
14+
15+ for entry in fs::read_dir(path)? {
16+ let entry = entry?;
917 let path = entry.path();
10− let dir = path.file_name().unwrap().to_str().unwrap().to_string();
11− if !ignore.contains(&dir) {
12− dirs.push(dir);
18+
19+ if !entry.file_type()?.is_dir() {
20+ continue;
1321 }
14− }
15− dirs
16−}
1722
18−fn collect_src_files(dir: &str) -> (Vec<String>, Vec<String>) {
19− eprintln!("Collect files for {}", dir);
23+ let dir = path.file_name().unwrap().to_str().unwrap().to_string();
2024
21− let mut c_files = Vec::new();
22− let mut cpp_files = Vec::new();
23− let path = PathBuf::from("languages").join(&dir).join("src");
24− for entry in fs::read_dir(path).unwrap().flatten() {
25− let path = entry.path();
26− if path
27− .file_stem()
28− .unwrap()
29− .to_str()
30− .unwrap()
31− .starts_with("binding")
32− {
25+ // filter ignores
26+ if ignore.contains(&dir) {
3327 continue;
3428 }
35− if let Some(ext) = path.extension() {
36− if ext == "c" {
37− c_files.push(path.to_str().unwrap().to_string());
38− } else if ext == "cc" || ext == "cpp" || ext == "cxx" {
39− cpp_files.push(path.to_str().unwrap().to_string());
40− }
41− }
29+ dirs.push(dir)
4230 }
43− (c_files, cpp_files)
44−}
4531
46−fn build_c(files: Vec<String>, language: &str) {
47− let mut build = cc::Build::new();
48− for file in files {
49− build
50− .file(&file)
51− .include(PathBuf::from(file).parent().unwrap())
52− .pic(true)
53− .warnings(false);
54− }
55− build.compile(&format!("tree-sitter-{}-c", language));
32+ Ok(dirs)
5633}
5734
58−fn build_cpp(files: Vec<String>, language: &str) {
59− let mut build = cc::Build::new();
35+#[cfg(unix)]
36+const DYLIB_EXTENSION: &str = "so";
37+
38+#[cfg(windows)]
39+const DYLIB_EXTENSION: &str = "dll";
6040
61− let flag = if build.get_compiler().is_like_msvc() {
62− "/std:c++17"
41+fn build_library(src_path: &Path, language: &str) -> Result<()> {
42+ let header_path = src_path;
43+ // let grammar_path = src_path.join("grammar.json");
44+ let parser_path = src_path.join("parser.c");
45+ let mut scanner_path = src_path.join("scanner.c");
46+
47+ let scanner_path = if scanner_path.exists() {
48+ Some(scanner_path)
6349 } else {
64− "-std=c++14"
50+ scanner_path.set_extension("cc");
51+ if scanner_path.exists() {
52+ Some(scanner_path)
53+ } else {
54+ None
55+ }
6556 };
57+ let parser_lib_path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../runtime/grammars");
58+ let mut library_path = parser_lib_path.join(language);
59+ library_path.set_extension(DYLIB_EXTENSION);
6660
67− for file in files {
68− build
69− .file(&file)
70− .include(PathBuf::from(file).parent().unwrap())
71− .pic(true)
72− .warnings(false)
73− .cpp(true)
74− .flag_if_supported(flag);
61+ let recompile = needs_recompile(&library_path, &parser_path, &scanner_path)
62+ .with_context(|| "Failed to compare source and binary timestamps")?;
63+
64+ if !recompile {
65+ return Ok(());
66+ }
67+ let mut config = cc::Build::new();
68+ config.cpp(true).opt_level(2).cargo_metadata(false);
69+ let compiler = config.get_compiler();
70+ let mut command = Command::new(compiler.path());
71+ command.current_dir(src_path);
72+ for (key, value) in compiler.env() {
73+ command.env(key, value);
7574 }
76− build.compile(&format!("tree-sitter-{}-cpp", language));
75+
76+ if cfg!(windows) {
77+ command
78+ .args(&["/nologo", "/LD", "/I"])
79+ .arg(header_path)
80+ .arg("/Od");
81+ if let Some(scanner_path) = scanner_path.as_ref() {
82+ command.arg(scanner_path);
83+ }
84+
85+ command
86+ .arg(parser_path)
87+ .arg("/link")
88+ .arg(format!("/out:{}", library_path.to_str().unwrap()));
89+ } else {
90+ command
91+ .arg("-shared")
92+ .arg("-fPIC")
93+ .arg("-fno-exceptions")
94+ .arg("-g")
95+ .arg("-I")
96+ .arg(header_path)
97+ .arg("-o")
98+ .arg(&library_path)
99+ .arg("-O2");
100+ if let Some(scanner_path) = scanner_path.as_ref() {
101+ if scanner_path.extension() == Some("c".as_ref()) {
102+ command.arg("-xc").arg("-std=c99").arg(scanner_path);
103+ } else {
104+ command.arg(scanner_path);
105+ }
106+ }
107+ command.arg("-xc").arg(parser_path);
108+ }
109+
110+ let output = command
111+ .output()
112+ .with_context(|| "Failed to execute C compiler")?;
113+ if !output.status.success() {
114+ return Err(anyhow!(
115+ "Parser compilation failed.\nStdout: {}\nStderr: {}",
116+ String::from_utf8_lossy(&output.stdout),
117+ String::from_utf8_lossy(&output.stderr)
118+ ));
119+ }
120+
121+ Ok(())
122+}
123+fn needs_recompile(
124+ lib_path: &Path,
125+ parser_c_path: &Path,
126+ scanner_path: &Option<PathBuf>,
127+) -> Result<bool> {
128+ if !lib_path.exists() {
129+ return Ok(true);
130+ }
131+ let lib_mtime = mtime(lib_path)?;
132+ if mtime(parser_c_path)? > lib_mtime {
133+ return Ok(true);
134+ }
135+ if let Some(scanner_path) = scanner_path {
136+ if mtime(scanner_path)? > lib_mtime {
137+ return Ok(true);
138+ }
139+ }
140+ Ok(false)
141+}
142+
143+fn mtime(path: &Path) -> Result<SystemTime> {
144+ Ok(fs::metadata(path)?.modified()?)
77145}
78146
79147fn build_dir(dir: &str, language: &str) {
@@ -92,22 +160,21 @@ fn build_dir(dir: &str, language: &str) {
92160 eprintln!("You can fix in using 'git submodule init && git submodule update --recursive'.");
93161 std::process::exit(1);
94162 }
95− let (c, cpp) = collect_src_files(dir);
96− if !c.is_empty() {
97− build_c(c, language);
98− }
99− if !cpp.is_empty() {
100− build_cpp(cpp, language);
101− }
163+
164+ let path = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
165+ .join("languages")
166+ .join(dir)
167+ .join("src");
168+
169+ build_library(&path, language).unwrap();
102170}
103171
104172fn main() {
105173 let ignore = vec![
106174 "tree-sitter-typescript".to_string(),
107175 "tree-sitter-haskell".to_string(), // aarch64 failures: https://github.com/tree-sitter/tree-sitter-haskell/issues/34
108− ".DS_Store".to_string(),
109176 ];
110− let dirs = collect_tree_sitter_dirs(&ignore);
177+ let dirs = collect_tree_sitter_dirs(&ignore).unwrap();
111178
112179 let mut n_jobs = 0;
113180 let pool = threadpool::Builder::new().build(); // by going through the builder, it'll use num_cpus
@@ -118,7 +185,7 @@ fn main() {
118185 n_jobs += 1;
119186
120187 pool.execute(move || {
121− let language = &dir[12..]; // skip tree-sitter- prefix
188+ let language = &dir.strip_prefix("tree-sitter-").unwrap();
122189 build_dir(&dir, language);
123190
124191 // report progress
helix-syntax/src/lib.rs+30−85
@@ -1,94 +1,39 @@
1−use serde::{Deserialize, Serialize};
1+use anyhow::{Context, Result};
2+use libloading::{Library, Symbol};
23use tree_sitter::Language;
34
4−#[macro_export]
5−macro_rules! mk_extern {
6− ( $( $name:ident ),* ) => {
7− $(
8− extern "C" { pub fn $name() -> Language; }
9− )*
10− };
11−}
12−
13−#[macro_export]
14−macro_rules! mk_enum {
15− ( $( $camel:ident ),* ) => {
16− #[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
17− #[serde(rename_all = "lowercase")]
18− pub enum Lang {
19− $(
20− $camel,
21− )*
5+fn replace_dashes_with_underscores(name: &str) -> String {
6+ let mut result = String::with_capacity(name.len());
7+ for c in name.chars() {
8+ if c == '-' {
9+ result.push('_');
10+ } else {
11+ result.push(c);
2212 }
23− };
13+ }
14+ result
2415}
16+#[cfg(unix)]
17+const DYLIB_EXTENSION: &str = "so";
2518
26−#[macro_export]
27−macro_rules! mk_get_language {
28− ( $( ($camel:ident, $name:ident) ),* ) => {
29− #[must_use]
30− pub fn get_language(lang: Lang) -> Language {
31− unsafe {
32− match lang {
33− $(
34− Lang::$camel => $name(),
35− )*
36− }
37− }
38− }
39− };
40−}
19+#[cfg(windows)]
20+const DYLIB_EXTENSION: &str = "dll";
4121
42−#[macro_export]
43−macro_rules! mk_get_language_name {
44− ( $( $camel:ident ),* ) => {
45− #[must_use]
46− pub const fn get_language_name(lang: Lang) -> &'static str {
47− match lang {
48− $(
49− Lang::$camel => stringify!($camel),
50− )*
51− }
52− }
53− };
54−}
22+pub fn get_language(runtime_path: &std::path::Path, name: &str) -> Result<Language> {
23+ let name = name.to_ascii_lowercase();
24+ let mut library_path = runtime_path.join("grammars").join(&name);
25+ // TODO: duplicated under build
26+ library_path.set_extension(DYLIB_EXTENSION);
5527
56−#[macro_export]
57−macro_rules! mk_langs {
58− ( $( ($camel:ident, $name:ident) ),* ) => {
59− mk_extern!($( $name ),*);
60− mk_enum!($( $camel ),*);
61− mk_get_language!($( ($camel, $name) ),*);
62− mk_get_language_name!($( $camel ),*);
28+ let library = unsafe { Library::new(&library_path) }
29+ .with_context(|| format!("Error opening dynamic library {:?}", &library_path))?;
30+ let language_fn_name = format!("tree_sitter_{}", replace_dashes_with_underscores(&name));
31+ let language = unsafe {
32+ let language_fn: Symbol<unsafe extern "C" fn() -> Language> = library
33+ .get(language_fn_name.as_bytes())
34+ .with_context(|| format!("Failed to load symbol {}", language_fn_name))?;
35+ language_fn()
6336 };
37+ std::mem::forget(library);
38+ Ok(language)
6439}
65−
66−mk_langs!(
67− // 1) Name for enum
68− // 2) tree-sitter function to call to get a Language
69− (Agda, tree_sitter_agda),
70− (Bash, tree_sitter_bash),
71− (Cpp, tree_sitter_cpp),
72− (CSharp, tree_sitter_c_sharp),
73− (Css, tree_sitter_css),
74− (C, tree_sitter_c),
75− (Elixir, tree_sitter_elixir),
76− (Go, tree_sitter_go),
77− // (Haskell, tree_sitter_haskell),
78− (Html, tree_sitter_html),
79− (Javascript, tree_sitter_javascript),
80− (Java, tree_sitter_java),
81− (Json, tree_sitter_json),
82− (Julia, tree_sitter_julia),
83− (Latex, tree_sitter_latex),
84− (Nix, tree_sitter_nix),
85− (Php, tree_sitter_php),
86− (Python, tree_sitter_python),
87− (Ruby, tree_sitter_ruby),
88− (Rust, tree_sitter_rust),
89− (Scala, tree_sitter_scala),
90− (Swift, tree_sitter_swift),
91− (Toml, tree_sitter_toml),
92− (Tsx, tree_sitter_tsx),
93− (Typescript, tree_sitter_typescript)
94−);
helix-term/src/application.rs+15−13
@@ -9,6 +9,7 @@ use log::error;
99use std::{
1010 io::{stdout, Write},
1111 sync::Arc,
12+ time::{Duration, Instant},
1213};
1314
1415use anyhow::Error;
@@ -82,15 +83,18 @@ impl Application {
8283 editor.new_file(Action::VerticalSplit);
8384 compositor.push(Box::new(ui::file_picker(first.clone())));
8485 } else {
86+ let nr_of_files = args.files.len();
87+ editor.open(first.to_path_buf(), Action::VerticalSplit)?;
8588 for file in args.files {
8689 if file.is_dir() {
8790 return Err(anyhow::anyhow!(
8891 "expected a path to file, found a directory. (to open a directory pass it as first argument)"
8992 ));
9093 } else {
91− editor.open(file, Action::VerticalSplit)?;
94+ editor.open(file.to_path_buf(), Action::Load)?;
9295 }
9396 }
97+ editor.set_status(format!("Loaded {} files.", nr_of_files));
9498 }
9599 } else {
96100 editor.new_file(Action::VerticalSplit);
@@ -130,6 +134,8 @@ impl Application {
130134
131135 pub async fn event_loop(&mut self) {
132136 let mut reader = EventStream::new();
137+ let mut last_render = Instant::now();
138+ let deadline = Duration::from_secs(1) / 60;
133139
134140 self.render();
135141
@@ -139,26 +145,22 @@ impl Application {
139145 break;
140146 }
141147
142− use futures_util::{FutureExt, StreamExt};
148+ use futures_util::StreamExt;
143149
144150 tokio::select! {
151+ biased;
152+
145153 event = reader.next() => {
146154 self.handle_terminal_events(event)
147155 }
148156 Some((id, call)) = self.editor.language_servers.incoming.next() => {
149157 self.handle_language_server_message(call, id).await;
150−
151− // eagerly process any other available notifications/calls
152− let now = std::time::Instant::now();
153− let deadline = std::time::Duration::from_millis(10);
154− while let Some(Some((id, call))) = self.editor.language_servers.incoming.next().now_or_never() {
155− self.handle_language_server_message(call, id).await;
156−
157− if now.elapsed() > deadline { // use a deadline so we don't block too long
158− break;
159− }
158+ // limit render calls for fast language server messages
159+ let last = self.editor.language_servers.incoming.is_empty();
160+ if last || last_render.elapsed() > deadline {
161+ self.render();
162+ last_render = Instant::now();
160163 }
161− self.render();
162164 }
163165 Some(callback) = self.jobs.futures.next() => {
164166 self.jobs.handle_callback(&mut self.editor, &mut self.compositor, callback);
helix-term/src/commands.rs+413−299
@@ -6,8 +6,8 @@ use helix_core::{
66 object, pos_at_coords,
77 regex::{self, Regex},
88 register::Register,
9− search, selection, LineEnding, Position, Range, Rope, RopeGraphemes, RopeSlice, Selection,
10− SmallVec, Tendril, Transaction,
9+ search, selection, surround, textobject, LineEnding, Position, Range, Rope, RopeGraphemes,
10+ RopeSlice, Selection, SmallVec, Tendril, Transaction,
1111};
1212
1313use helix_view::{
@@ -20,7 +20,7 @@ use helix_view::{
2020 Document, DocumentId, Editor, ViewId,
2121};
2222
23−use anyhow::anyhow;
23+use anyhow::{anyhow, bail, Context as _};
2424use helix_lsp::{
2525 lsp,
2626 util::{lsp_pos_to_pos, lsp_range_to_range, pos_to_lsp_pos, range_to_lsp_range},
@@ -37,6 +37,7 @@ use crate::{
3737use crate::job::{self, Job, Jobs};
3838use futures_util::{FutureExt, TryFutureExt};
3939use std::collections::HashMap;
40+use std::num::NonZeroUsize;
4041use std::{fmt, future::Future};
4142
4243use std::{
@@ -49,7 +50,7 @@ use serde::de::{self, Deserialize, Deserializer};
4950
5051pub struct Context<'a> {
5152 pub selected_register: helix_view::RegisterSelection,
52− pub count: Option<std::num::NonZeroUsize>,
53+ pub count: Option<NonZeroUsize>,
5354 pub editor: &'a mut Editor,
5455
5556 pub callback: Option<crate::compositor::Callback>,
@@ -75,7 +76,9 @@ impl<'a> Context<'a> {
7576
7677 #[inline]
7778 pub fn on_next_key_mode(&mut self, map: HashMap<KeyEvent, fn(&mut Context)>) {
79+ let count = self.count;
7880 self.on_next_key(move |cx, event| {
81+ cx.count = count;
7982 cx.editor.autoinfo = None;
8083 if let Some(func) = map.get(&event) {
8184 func(cx);
@@ -180,6 +183,9 @@ impl Command {
180183 extend_till_prev_char,
181184 extend_prev_char,
182185 replace,
186+ switch_case,
187+ switch_to_uppercase,
188+ switch_to_lowercase,
183189 page_up,
184190 page_down,
185191 half_page_up,
@@ -778,6 +784,57 @@ fn replace(cx: &mut Context) {
778784 })
779785}
780786
787+fn switch_case(cx: &mut Context) {
788+ let (view, doc) = current!(cx.editor);
789+ let transaction =
790+ Transaction::change_by_selection(doc.text(), doc.selection(view.id), |range| {
791+ let text: Tendril = range
792+ .fragment(doc.text().slice(..))
793+ .chars()
794+ .flat_map(|ch| {
795+ if ch.is_lowercase() {
796+ ch.to_uppercase().collect()
797+ } else if ch.is_uppercase() {
798+ ch.to_lowercase().collect()
799+ } else {
800+ vec![ch]
801+ }
802+ })
803+ .collect();
804+
805+ (range.from(), range.to() + 1, Some(text))
806+ });
807+
808+ doc.apply(&transaction, view.id);
809+ doc.append_changes_to_history(view.id);
810+}
811+
812+fn switch_to_uppercase(cx: &mut Context) {
813+ let (view, doc) = current!(cx.editor);
814+ let transaction =
815+ Transaction::change_by_selection(doc.text(), doc.selection(view.id), |range| {
816+ let text: Tendril = range.fragment(doc.text().slice(..)).to_uppercase().into();
817+
818+ (range.from(), range.to() + 1, Some(text))
819+ });
820+
821+ doc.apply(&transaction, view.id);
822+ doc.append_changes_to_history(view.id);
823+}
824+
825+fn switch_to_lowercase(cx: &mut Context) {
826+ let (view, doc) = current!(cx.editor);
827+ let transaction =
828+ Transaction::change_by_selection(doc.text(), doc.selection(view.id), |range| {
829+ let text: Tendril = range.fragment(doc.text().slice(..)).to_lowercase().into();
830+
831+ (range.from(), range.to() + 1, Some(text))
832+ });
833+
834+ doc.apply(&transaction, view.id);
835+ doc.append_changes_to_history(view.id);
836+}
837+
781838fn scroll(cx: &mut Context, offset: usize, direction: Direction) {
782839 use Direction::*;
783840 let (view, doc) = current!(cx.editor);
@@ -1194,34 +1251,45 @@ mod cmd {
11941251 pub alias: Option<&'static str>,
11951252 pub doc: &'static str,
11961253 // params, flags, helper, completer
1197− pub fun: fn(&mut compositor::Context, &[&str], PromptEvent),
1254+ pub fun: fn(&mut compositor::Context, &[&str], PromptEvent) -> anyhow::Result<()>,
11981255 pub completer: Option<Completer>,
11991256 }
12001257
1201− fn quit(cx: &mut compositor::Context, _args: &[&str], _event: PromptEvent) {
1258+ fn quit(
1259+ cx: &mut compositor::Context,
1260+ _args: &[&str],
1261+ _event: PromptEvent,
1262+ ) -> anyhow::Result<()> {
12021263 // last view and we have unsaved changes
1203− if cx.editor.tree.views().count() == 1 && buffers_remaining_impl(cx.editor) {
1204− return;
1264+ if cx.editor.tree.views().count() == 1 {
1265+ buffers_remaining_impl(cx.editor)?
12051266 }
1267+
12061268 cx.editor
12071269 .close(view!(cx.editor).id, /* close_buffer */ false);
1270+
1271+ Ok(())
12081272 }
12091273
1210− fn force_quit(cx: &mut compositor::Context, _args: &[&str], _event: PromptEvent) {
1274+ fn force_quit(
1275+ cx: &mut compositor::Context,
1276+ _args: &[&str],
1277+ _event: PromptEvent,
1278+ ) -> anyhow::Result<()> {
12111279 cx.editor
12121280 .close(view!(cx.editor).id, /* close_buffer */ false);
1281+
1282+ Ok(())
12131283 }
12141284
1215− fn open(cx: &mut compositor::Context, args: &[&str], _event: PromptEvent) {
1216− match args.get(0) {
1217− Some(path) => {
1218− // TODO: handle error
1219− let _ = cx.editor.open(path.into(), Action::Replace);
1220− }
1221− None => {
1222− cx.editor.set_error("wrong argument count".to_string());
1223− }
1224− };
1285+ fn open(
1286+ cx: &mut compositor::Context,
1287+ args: &[&str],
1288+ _event: PromptEvent,
1289+ ) -> anyhow::Result<()> {
1290+ let path = args.get(0).context("wrong argument count")?;
1291+ let _ = cx.editor.open(path.into(), Action::Replace)?;
1292+ Ok(())
12251293 }
12261294
12271295 fn write_impl<P: AsRef<Path>>(
@@ -1232,12 +1300,10 @@ mod cmd {
12321300 let (_, doc) = current!(cx.editor);
12331301
12341302 if let Some(path) = path {
1235− if let Err(err) = doc.set_path(path.as_ref()) {
1236− return Err(anyhow!("invalid filepath: {}", err));
1237− };
1303+ doc.set_path(path.as_ref()).context("invalid filepath")?;
12381304 }
12391305 if doc.path().is_none() {
1240− return Err(anyhow!("cannot write a buffer without a filename"));
1306+ bail!("cannot write a buffer without a filename");
12411307 }
12421308 let fmt = doc.auto_format().map(|fmt| {
12431309 let shared = fmt.shared();
@@ -1253,21 +1319,33 @@ mod cmd {
12531319 Ok(tokio::spawn(doc.format_and_save(fmt)))
12541320 }
12551321
1256− fn write(cx: &mut compositor::Context, args: &[&str], _event: PromptEvent) {
1257− match write_impl(cx, args.first()) {
1258− Err(e) => cx.editor.set_error(e.to_string()),
1259− Ok(handle) => {
1260− cx.jobs
1261− .add(Job::new(handle.unwrap_or_else(|e| Err(e.into()))).wait_before_exiting());
1262− }
1263− };
1322+ fn write(
1323+ cx: &mut compositor::Context,
1324+ args: &[&str],
1325+ _event: PromptEvent,
1326+ ) -> anyhow::Result<()> {
1327+ let handle = write_impl(cx, args.first())?;
1328+ cx.jobs
1329+ .add(Job::new(handle.unwrap_or_else(|e| Err(e.into()))).wait_before_exiting());
1330+
1331+ Ok(())
12641332 }
12651333
1266− fn new_file(cx: &mut compositor::Context, _args: &[&str], _event: PromptEvent) {
1334+ fn new_file(
1335+ cx: &mut compositor::Context,
1336+ _args: &[&str],
1337+ _event: PromptEvent,
1338+ ) -> anyhow::Result<()> {
12671339 cx.editor.new_file(Action::Replace);
1340+
1341+ Ok(())
12681342 }
12691343
1270− fn format(cx: &mut compositor::Context, _args: &[&str], _event: PromptEvent) {
1344+ fn format(
1345+ cx: &mut compositor::Context,
1346+ _args: &[&str],
1347+ _event: PromptEvent,
1348+ ) -> anyhow::Result<()> {
12711349 let (_, doc) = current!(cx.editor);
12721350
12731351 if let Some(format) = doc.format() {
@@ -1275,9 +1353,14 @@ mod cmd {
12751353 make_format_callback(doc.id(), doc.version(), Modified::LeaveModified, format);
12761354 cx.jobs.callback(callback);
12771355 }
1278− }
12791356
1280− fn set_indent_style(cx: &mut compositor::Context, args: &[&str], _event: PromptEvent) {
1357+ Ok(())
1358+ }
1359+ fn set_indent_style(
1360+ cx: &mut compositor::Context,
1361+ args: &[&str],
1362+ _event: PromptEvent,
1363+ ) -> anyhow::Result<()> {
12811364 use IndentStyle::*;
12821365
12831366 // If no argument, report current indent style.
@@ -1289,7 +1372,7 @@ mod cmd {
12891372 Spaces(n) if (2..=8).contains(&n) => format!("{} spaces", n),
12901373 _ => "error".into(), // Shouldn't happen.
12911374 });
1292− return;
1375+ return Ok(());
12931376 }
12941377
12951378 // Attempt to parse argument as an indent style.
@@ -1304,18 +1387,19 @@ mod cmd {
13041387 _ => None,
13051388 };
13061389
1307− if let Some(s) = style {
1308− let doc = doc_mut!(cx.editor);
1309− doc.indent_style = s;
1310− } else {
1311− // Invalid argument.
1312− cx.editor
1313− .set_error(format!("invalid indent style '{}'", args[0],));
1314− }
1390+ let style = style.context("invalid indent style")?;
1391+ let doc = doc_mut!(cx.editor);
1392+ doc.indent_style = style;
1393+
1394+ Ok(())
13151395 }
13161396
13171397 /// Sets or reports the current document's line ending setting.
1318− fn set_line_ending(cx: &mut compositor::Context, args: &[&str], _event: PromptEvent) {
1398+ fn set_line_ending(
1399+ cx: &mut compositor::Context,
1400+ args: &[&str],
1401+ _event: PromptEvent,
1402+ ) -> anyhow::Result<()> {
13191403 use LineEnding::*;
13201404
13211405 // If no argument, report current line ending setting.
@@ -1331,7 +1415,8 @@ mod cmd {
13311415 // These should never be a document's default line ending.
13321416 VT | LS | PS => "error".into(),
13331417 });
1334− return;
1418+
1419+ return Ok(());
13351420 }
13361421
13371422 // Attempt to parse argument as a line ending.
@@ -1345,72 +1430,65 @@ mod cmd {
13451430 _ => None,
13461431 };
13471432
1348− if let Some(le) = line_ending {
1349− doc_mut!(cx.editor).line_ending = le;
1350− } else {
1351− // Invalid argument.
1352− cx.editor
1353− .set_error(format!("invalid line ending '{}'", args[0],));
1354− }
1433+ let line_ending = line_ending.context("invalid line ending")?;
1434+ doc_mut!(cx.editor).line_ending = line_ending;
1435+ Ok(())
13551436 }
13561437
1357− fn earlier(cx: &mut compositor::Context, args: &[&str], _event: PromptEvent) {
1358− let uk = match args.join(" ").parse::<helix_core::history::UndoKind>() {
1359− Ok(uk) => uk,
1360− Err(msg) => {
1361− cx.editor.set_error(msg);
1362− return;
1363− }
1364− };
1438+ fn earlier(
1439+ cx: &mut compositor::Context,
1440+ args: &[&str],
1441+ _event: PromptEvent,
1442+ ) -> anyhow::Result<()> {
1443+ let uk = args
1444+ .join(" ")
1445+ .parse::<helix_core::history::UndoKind>()
1446+ .map_err(|s| anyhow!(s))?;
1447+
13651448 let (view, doc) = current!(cx.editor);
1366− doc.earlier(view.id, uk)
1449+ doc.earlier(view.id, uk);
1450+
1451+ Ok(())
13671452 }
13681453
1369− fn later(cx: &mut compositor::Context, args: &[&str], _event: PromptEvent) {
1370− let uk = match args.join(" ").parse::<helix_core::history::UndoKind>() {
1371− Ok(uk) => uk,
1372− Err(msg) => {
1373− cx.editor.set_error(msg);
1374− return;
1375− }
1376− };
1454+ fn later(
1455+ cx: &mut compositor::Context,
1456+ args: &[&str],
1457+ _event: PromptEvent,
1458+ ) -> anyhow::Result<()> {
1459+ let uk = args
1460+ .join(" ")
1461+ .parse::<helix_core::history::UndoKind>()
1462+ .map_err(|s| anyhow!(s))?;
13771463 let (view, doc) = current!(cx.editor);
1378− doc.later(view.id, uk)
1464+ doc.later(view.id, uk);
1465+
1466+ Ok(())
13791467 }
13801468
1381− fn write_quit(cx: &mut compositor::Context, args: &[&str], event: PromptEvent) {
1382− match write_impl(cx, args.first()) {
1383− Ok(handle) => {
1384− if let Err(e) = helix_lsp::block_on(handle) {
1385− cx.editor.set_error(e.to_string());
1386− } else {
1387− quit(cx, &[], event);
1388− }
1389− }
1390− Err(e) => {
1391− cx.editor.set_error(e.to_string());
1392− }
1393− }
1469+ fn write_quit(
1470+ cx: &mut compositor::Context,
1471+ args: &[&str],
1472+ event: PromptEvent,
1473+ ) -> anyhow::Result<()> {
1474+ let handle = write_impl(cx, args.first())?;
1475+ let _ = helix_lsp::block_on(handle)?;
1476+ quit(cx, &[], event)
13941477 }
13951478
1396− fn force_write_quit(cx: &mut compositor::Context, args: &[&str], event: PromptEvent) {
1397− match write_impl(cx, args.first()) {
1398− Ok(handle) => {
1399− if let Err(e) = helix_lsp::block_on(handle) {
1400− cx.editor.set_error(e.to_string());
1401− } else {
1402− force_quit(cx, &[], event);
1403− }
1404− }
1405− Err(e) => {
1406− cx.editor.set_error(e.to_string());
1407− }
1408− }
1479+ fn force_write_quit(
1480+ cx: &mut compositor::Context,
1481+ args: &[&str],
1482+ event: PromptEvent,
1483+ ) -> anyhow::Result<()> {
1484+ let handle = write_impl(cx, args.first())?;
1485+ let _ = helix_lsp::block_on(handle)?;
1486+ force_quit(cx, &[], event)
14091487 }
14101488
1411− /// Returns `true` if there are modified buffers remaining and sets editor error,
1412− /// otherwise returns `false`
1413− fn buffers_remaining_impl(editor: &mut Editor) -> bool {
1489+ /// Results an error if there are modified buffers remaining and sets editor error,
1490+ /// otherwise returns `Ok(())`
1491+ fn buffers_remaining_impl(editor: &mut Editor) -> anyhow::Result<()> {
14141492 let modified: Vec<_> = editor
14151493 .documents()
14161494 .filter(|doc| doc.is_modified())
@@ -1421,16 +1499,13 @@ mod cmd {
14211499 })
14221500 .collect();
14231501 if !modified.is_empty() {
1424− let err = format!(
1502+ bail!(
14251503 "{} unsaved buffer(s) remaining: {:?}",
14261504 modified.len(),
14271505 modified
14281506 );
1429− editor.set_error(err);
1430− true
1431− } else {
1432− false
14331507 }
1508+ Ok(())
14341509 }
14351510
14361511 fn write_all_impl(
@@ -1439,7 +1514,7 @@ mod cmd {
14391514 _event: PromptEvent,
14401515 quit: bool,
14411516 force: bool,
1442− ) {
1517+ ) -> anyhow::Result<()> {
14431518 let mut errors = String::new();
14441519
14451520 // save all documents
@@ -1452,11 +1527,10 @@ mod cmd {
14521527 // TODO: handle error.
14531528 let _ = helix_lsp::block_on(tokio::spawn(doc.save()));
14541529 }
1455− editor.set_error(errors);
14561530
14571531 if quit {
1458− if !force && buffers_remaining_impl(editor) {
1459− return;
1532+ if !force {
1533+ buffers_remaining_impl(editor)?;
14601534 }
14611535
14621536 // close all views
@@ -1465,23 +1539,42 @@ mod cmd {
14651539 editor.close(view_id, false);
14661540 }
14671541 }
1542+
1543+ bail!(errors)
14681544 }
14691545
1470− fn write_all(cx: &mut compositor::Context, args: &[&str], event: PromptEvent) {
1546+ fn write_all(
1547+ cx: &mut compositor::Context,
1548+ args: &[&str],
1549+ event: PromptEvent,
1550+ ) -> anyhow::Result<()> {
14711551 write_all_impl(&mut cx.editor, args, event, false, false)
14721552 }
14731553
1474− fn write_all_quit(cx: &mut compositor::Context, args: &[&str], event: PromptEvent) {
1554+ fn write_all_quit(
1555+ cx: &mut compositor::Context,
1556+ args: &[&str],
1557+ event: PromptEvent,
1558+ ) -> anyhow::Result<()> {
14751559 write_all_impl(&mut cx.editor, args, event, true, false)
14761560 }
14771561
1478− fn force_write_all_quit(cx: &mut compositor::Context, args: &[&str], event: PromptEvent) {
1562+ fn force_write_all_quit(
1563+ cx: &mut compositor::Context,
1564+ args: &[&str],
1565+ event: PromptEvent,
1566+ ) -> anyhow::Result<()> {
14791567 write_all_impl(&mut cx.editor, args, event, true, true)
14801568 }
14811569
1482− fn quit_all_impl(editor: &mut Editor, _args: &[&str], _event: PromptEvent, force: bool) {
1483− if !force && buffers_remaining_impl(editor) {
1484− return;
1570+ fn quit_all_impl(
1571+ editor: &mut Editor,
1572+ _args: &[&str],
1573+ _event: PromptEvent,
1574+ force: bool,
1575+ ) -> anyhow::Result<()> {
1576+ if !force {
1577+ buffers_remaining_impl(editor)?;
14851578 }
14861579
14871580 // close all views
@@ -1489,57 +1582,77 @@ mod cmd {
14891582 for view_id in views {
14901583 editor.close(view_id, false);
14911584 }
1585+
1586+ Ok(())
14921587 }
14931588
1494− fn quit_all(cx: &mut compositor::Context, args: &[&str], event: PromptEvent) {
1589+ fn quit_all(
1590+ cx: &mut compositor::Context,
1591+ args: &[&str],
1592+ event: PromptEvent,
1593+ ) -> anyhow::Result<()> {
14951594 quit_all_impl(&mut cx.editor, args, event, false)
14961595 }
14971596
1498− fn force_quit_all(cx: &mut compositor::Context, args: &[&str], event: PromptEvent) {
1597+ fn force_quit_all(
1598+ cx: &mut compositor::Context,
1599+ args: &[&str],
1600+ event: PromptEvent,
1601+ ) -> anyhow::Result<()> {
14991602 quit_all_impl(&mut cx.editor, args, event, true)
15001603 }
15011604
1502− fn theme(cx: &mut compositor::Context, args: &[&str], _event: PromptEvent) {
1503− let theme = if let Some(theme) = args.first() {
1504− theme
1505− } else {
1506− cx.editor.set_error("theme name not provided".into());
1507− return;
1508− };
1509−
1510− cx.editor.set_theme_from_name(theme);
1605+ fn theme(
1606+ cx: &mut compositor::Context,
1607+ args: &[&str],
1608+ _event: PromptEvent,
1609+ ) -> anyhow::Result<()> {
1610+ let theme = args.first().context("theme not provided")?;
1611+ cx.editor.set_theme_from_name(theme)
15111612 }
15121613
15131614 fn yank_main_selection_to_clipboard(
15141615 cx: &mut compositor::Context,
15151616 _args: &[&str],
15161617 _event: PromptEvent,
1517− ) {
1518− yank_main_selection_to_clipboard_impl(&mut cx.editor);
1618+ ) -> anyhow::Result<()> {
1619+ yank_main_selection_to_clipboard_impl(&mut cx.editor)
15191620 }
15201621
1521− fn yank_joined_to_clipboard(cx: &mut compositor::Context, args: &[&str], _event: PromptEvent) {
1622+ fn yank_joined_to_clipboard(
1623+ cx: &mut compositor::Context,
1624+ args: &[&str],
1625+ _event: PromptEvent,
1626+ ) -> anyhow::Result<()> {
15221627 let (_, doc) = current!(cx.editor);
15231628 let separator = args
15241629 .first()
15251630 .copied()
15261631 .unwrap_or_else(|| doc.line_ending.as_str());
1527− yank_joined_to_clipboard_impl(&mut cx.editor, separator);
1632+ yank_joined_to_clipboard_impl(&mut cx.editor, separator)
15281633 }
15291634
1530− fn paste_clipboard_after(cx: &mut compositor::Context, _args: &[&str], _event: PromptEvent) {
1531− paste_clipboard_impl(&mut cx.editor, Paste::After);
1635+ fn paste_clipboard_after(
1636+ cx: &mut compositor::Context,
1637+ _args: &[&str],
1638+ _event: PromptEvent,
1639+ ) -> anyhow::Result<()> {
1640+ paste_clipboard_impl(&mut cx.editor, Paste::After)
15321641 }
15331642
1534− fn paste_clipboard_before(cx: &mut compositor::Context, _args: &[&str], _event: PromptEvent) {
1535− paste_clipboard_impl(&mut cx.editor, Paste::After);
1643+ fn paste_clipboard_before(
1644+ cx: &mut compositor::Context,
1645+ _args: &[&str],
1646+ _event: PromptEvent,
1647+ ) -> anyhow::Result<()> {
1648+ paste_clipboard_impl(&mut cx.editor, Paste::After)
15361649 }
15371650
15381651 fn replace_selections_with_clipboard(
15391652 cx: &mut compositor::Context,
15401653 _args: &[&str],
15411654 _event: PromptEvent,
1542− ) {
1655+ ) -> anyhow::Result<()> {
15431656 let (view, doc) = current!(cx.editor);
15441657
15451658 match cx.editor.clipboard_provider.get_contents() {
@@ -1555,71 +1668,76 @@ mod cmd {
15551668
15561669 doc.apply(&transaction, view.id);
15571670 doc.append_changes_to_history(view.id);
1671+ Ok(())
15581672 }
1559− Err(e) => log::error!("Couldn't get system clipboard contents: {:?}", e),
1673+ Err(e) => Err(e.context("Couldn't get system clipboard contents")),
15601674 }
15611675 }
15621676
1563− fn show_clipboard_provider(cx: &mut compositor::Context, _args: &[&str], _event: PromptEvent) {
1677+ fn show_clipboard_provider(
1678+ cx: &mut compositor::Context,
1679+ _args: &[&str],
1680+ _event: PromptEvent,
1681+ ) -> anyhow::Result<()> {
15641682 cx.editor
15651683 .set_status(cx.editor.clipboard_provider.name().into());
1684+ Ok(())
15661685 }
15671686
1568− fn change_current_directory(cx: &mut compositor::Context, args: &[&str], _event: PromptEvent) {
1569− let dir = match args.first() {
1570− Some(dir) => dir,
1571− None => {
1572− cx.editor.set_error("target directory not provided".into());
1573− return;
1574− }
1575− };
1687+ fn change_current_directory(
1688+ cx: &mut compositor::Context,
1689+ args: &[&str],
1690+ _event: PromptEvent,
1691+ ) -> anyhow::Result<()> {
1692+ let dir = args.first().context("target directory not provided")?;
15761693
15771694 if let Err(e) = std::env::set_current_dir(dir) {
1578− cx.editor.set_error(format!(
1579− "Couldn't change the current working directory: {:?}",
1580− e
1581− ));
1582− return;
1695+ bail!("Couldn't change the current working directory: {:?}", e);
15831696 }
15841697
1585− match std::env::current_dir() {
1586− Ok(cwd) => cx.editor.set_status(format!(
1587− "Current working directory is now {}",
1588− cwd.display()
1589− )),
1590− Err(e) => cx
1591− .editor
1592− .set_error(format!("Couldn't get the new working directory: {}", e)),
1593− }
1698+ let cwd = std::env::current_dir().context("Couldn't get the new working directory")?;
1699+ cx.editor.set_status(format!(
1700+ "Current working directory is now {}",
1701+ cwd.display()
1702+ ));
1703+ Ok(())
15941704 }
15951705
1596− fn show_current_directory(cx: &mut compositor::Context, _args: &[&str], _event: PromptEvent) {
1597− match std::env::current_dir() {
1598− Ok(cwd) => cx
1599− .editor
1600− .set_status(format!("Current working directory is {}", cwd.display())),
1601− Err(e) => cx
1602− .editor
1603− .set_error(format!("Couldn't get the current working directory: {}", e)),
1604− }
1706+ fn show_current_directory(
1707+ cx: &mut compositor::Context,
1708+ _args: &[&str],
1709+ _event: PromptEvent,
1710+ ) -> anyhow::Result<()> {
1711+ let cwd = std::env::current_dir().context("Couldn't get the new working directory")?;
1712+ cx.editor
1713+ .set_status(format!("Current working directory is {}", cwd.display()));
1714+ Ok(())
16051715 }
16061716
16071717 /// Sets the [`Document`]'s encoding..
1608− fn set_encoding(cx: &mut compositor::Context, args: &[&str], _: PromptEvent) {
1718+ fn set_encoding(
1719+ cx: &mut compositor::Context,
1720+ args: &[&str],
1721+ _event: PromptEvent,
1722+ ) -> anyhow::Result<()> {
16091723 let (_, doc) = current!(cx.editor);
16101724 if let Some(label) = args.first() {
16111725 doc.set_encoding(label)
1612− .unwrap_or_else(|e| cx.editor.set_error(e.to_string()));
16131726 } else {
16141727 let encoding = doc.encoding().name().to_string();
1615− cx.editor.set_status(encoding)
1728+ cx.editor.set_status(encoding);
1729+ Ok(())
16161730 }
16171731 }
16181732
16191733 /// Reload the [`Document`] from its source file.
1620− fn reload(cx: &mut compositor::Context, _args: &[&str], _: PromptEvent) {
1734+ fn reload(
1735+ cx: &mut compositor::Context,
1736+ _args: &[&str],
1737+ _event: PromptEvent,
1738+ ) -> anyhow::Result<()> {
16211739 let (view, doc) = current!(cx.editor);
1622− doc.reload(view.id).unwrap();
1740+ doc.reload(view.id)
16231741 }
16241742
16251743 pub const TYPABLE_COMMAND_LIST: &[TypableCommand] = &[
@@ -1884,7 +2002,9 @@ fn command_mode(cx: &mut Context) {
18842002 }
18852003
18862004 if let Some(cmd) = cmd::COMMANDS.get(parts[0]) {
1887− (cmd.fun)(cx, &parts[1..], event);
2005+ if let Err(e) = (cmd.fun)(cx, &parts[1..], event) {
2006+ cx.editor.set_error(format!("{}", e));
2007+ }
18882008 } else {
18892009 cx.editor
18902010 .set_error(format!("no such command: '{}'", parts[0]));
@@ -2816,7 +2936,7 @@ fn yank(cx: &mut Context) {
28162936 cx.editor.set_status(msg)
28172937}
28182938
2819−fn yank_joined_to_clipboard_impl(editor: &mut Editor, separator: &str) {
2939+fn yank_joined_to_clipboard_impl(editor: &mut Editor, separator: &str) -> anyhow::Result<()> {
28202940 let (view, doc) = current!(editor);
28212941
28222942 let values: Vec<String> = doc
@@ -2832,19 +2952,22 @@ fn yank_joined_to_clipboard_impl(editor: &mut Editor, separator: &str) {
28322952
28332953 let joined = values.join(separator);
28342954
2835− if let Err(e) = editor.clipboard_provider.set_contents(joined) {
2836− log::error!("Couldn't set system clipboard content: {:?}", e);
2837− }
2955+ editor
2956+ .clipboard_provider
2957+ .set_contents(joined)
2958+ .context("Couldn't set system clipboard content")?;
28382959
28392960 editor.set_status(msg);
2961+
2962+ Ok(())
28402963}
28412964
28422965fn yank_joined_to_clipboard(cx: &mut Context) {
28432966 let line_ending = current!(cx.editor).1.line_ending;
2844− yank_joined_to_clipboard_impl(&mut cx.editor, line_ending.as_str());
2967+ let _ = yank_joined_to_clipboard_impl(&mut cx.editor, line_ending.as_str());
28452968}
28462969
2847−fn yank_main_selection_to_clipboard_impl(editor: &mut Editor) {
2970+fn yank_main_selection_to_clipboard_impl(editor: &mut Editor) -> anyhow::Result<()> {
28482971 let (view, doc) = current!(editor);
28492972
28502973 let value = doc
@@ -2853,14 +2976,15 @@ fn yank_main_selection_to_clipboard_impl(editor: &mut Editor) {
28532976 .fragment(doc.text().slice(..));
28542977
28552978 if let Err(e) = editor.clipboard_provider.set_contents(value.into_owned()) {
2856− log::error!("Couldn't set system clipboard content: {:?}", e);
2979+ bail!("Couldn't set system clipboard content: {:?}", e);
28572980 }
28582981
28592982 editor.set_status("yanked main selection to system clipboard".to_owned());
2983+ Ok(())
28602984}
28612985
28622986fn yank_main_selection_to_clipboard(cx: &mut Context) {
2863− yank_main_selection_to_clipboard_impl(&mut cx.editor);
2987+ let _ = yank_main_selection_to_clipboard_impl(&mut cx.editor);
28642988}
28652989
28662990#[derive(Copy, Clone)]
@@ -2909,7 +3033,7 @@ fn paste_impl(
29093033 Some(transaction)
29103034}
29113035
2912−fn paste_clipboard_impl(editor: &mut Editor, action: Paste) {
3036+fn paste_clipboard_impl(editor: &mut Editor, action: Paste) -> anyhow::Result<()> {
29133037 let (view, doc) = current!(editor);
29143038
29153039 match editor
@@ -2920,18 +3044,19 @@ fn paste_clipboard_impl(editor: &mut Editor, action: Paste) {
29203044 Ok(Some(transaction)) => {
29213045 doc.apply(&transaction, view.id);
29223046 doc.append_changes_to_history(view.id);
3047+ Ok(())
29233048 }
2924− Ok(None) => {}
2925− Err(e) => log::error!("Couldn't get system clipboard contents: {:?}", e),
3049+ Ok(None) => Ok(()),
3050+ Err(e) => Err(e.context("Couldn't get system clipboard contents")),
29263051 }
29273052}
29283053
29293054fn paste_clipboard_after(cx: &mut Context) {
2930− paste_clipboard_impl(&mut cx.editor, Paste::After);
3055+ let _ = paste_clipboard_impl(&mut cx.editor, Paste::After);
29313056}
29323057
29333058fn paste_clipboard_before(cx: &mut Context) {
2934− paste_clipboard_impl(&mut cx.editor, Paste::Before);
3059+ let _ = paste_clipboard_impl(&mut cx.editor, Paste::Before);
29353060}
29363061
29373062fn replace_with_yanked(cx: &mut Context) {
@@ -2959,7 +3084,7 @@ fn replace_with_yanked(cx: &mut Context) {
29593084 }
29603085}
29613086
2962−fn replace_selections_with_clipboard_impl(editor: &mut Editor) {
3087+fn replace_selections_with_clipboard_impl(editor: &mut Editor) -> anyhow::Result<()> {
29633088 let (view, doc) = current!(editor);
29643089
29653090 match editor.clipboard_provider.get_contents() {
@@ -2974,13 +3099,14 @@ fn replace_selections_with_clipboard_impl(editor: &mut Editor) {
29743099
29753100 doc.apply(&transaction, view.id);
29763101 doc.append_changes_to_history(view.id);
3102+ Ok(())
29773103 }
2978− Err(e) => log::error!("Couldn't get system clipboard contents: {:?}", e),
3104+ Err(e) => Err(e.context("Couldn't get system clipboard contents")),
29793105 }
29803106}
29813107
29823108fn replace_selections_with_clipboard(cx: &mut Context) {
2983− replace_selections_with_clipboard_impl(&mut cx.editor);
3109+ let _ = replace_selections_with_clipboard_impl(&mut cx.editor);
29843110}
29853111
29863112fn paste_after(cx: &mut Context) {
@@ -3405,24 +3531,6 @@ fn jump_backward(cx: &mut Context) {
34053531 };
34063532}
34073533
3408−fn window_mode(cx: &mut Context) {
3409− cx.on_next_key(move |cx, event| {
3410− if let KeyEvent {
3411− code: KeyCode::Char(ch),
3412− ..
3413− } = event
3414− {
3415− match ch {
3416− 'w' => rotate_view(cx),
3417− 'h' => hsplit(cx),
3418− 'v' => vsplit(cx),
3419− 'q' => wclose(cx),
3420− _ => {}
3421− }
3422− }
3423− })
3424−}
3425−
34263534fn rotate_view(cx: &mut Context) {
34273535 cx.editor.focus_next()
34283536}
@@ -3468,105 +3576,46 @@ fn select_register(cx: &mut Context) {
34683576 })
34693577}
34703578
3471−fn view_mode(cx: &mut Context) {
3472− cx.on_next_key(move |cx, event| {
3473− if let KeyEvent {
3474− code: KeyCode::Char(ch),
3475− ..
3476− } = event
3477− {
3478− // if lock, call cx again
3479− // TODO: temporarily show VIE in the mode list
3480− match ch {
3481− // center
3482− 'z' | 'c'
3483− // top
3484− | 't'
3485− // bottom
3486− | 'b' => {
3487− let (view, doc) = current!(cx.editor);
3579+fn align_view_top(cx: &mut Context) {
3580+ let (view, doc) = current!(cx.editor);
3581+ align_view(doc, view, Align::Top);
3582+}
34883583
3489− align_view(doc, view, match ch {
3490− 'z' | 'c' => Align::Center,
3491− 't' => Align::Top,
3492− 'b' => Align::Bottom,
3493− _ => unreachable!()
3494− });
3495− }
3496− 'm' => {
3497− let (view, doc) = current!(cx.editor);
3498− let pos = doc.selection(view.id).cursor();
3499− let pos = coords_at_pos(doc.text().slice(..), pos);
3584+fn align_view_center(cx: &mut Context) {
3585+ let (view, doc) = current!(cx.editor);
3586+ align_view(doc, view, Align::Center);
3587+}
35003588
3501− const OFFSET: usize = 7; // gutters
3502− view.first_col = pos.col.saturating_sub(((view.area.width as usize).saturating_sub(OFFSET)) / 2);
3503− },
3504− 'h' => (),
3505− 'j' => scroll(cx, 1, Direction::Forward),
3506− 'k' => scroll(cx, 1, Direction::Backward),
3507− 'l' => (),
3508− _ => (),
3509− }
3510− }
3511− })
3589+fn align_view_bottom(cx: &mut Context) {
3590+ let (view, doc) = current!(cx.editor);
3591+ align_view(doc, view, Align::Bottom);
35123592}
35133593
3514−fn left_bracket_mode(cx: &mut Context) {
3515− cx.on_next_key(move |cx, event| {
3516− if let KeyEvent {
3517− code: KeyCode::Char(ch),
3518− ..
3519− } = event
3520− {
3521− match ch {
3522− 'd' => goto_prev_diag(cx),
3523− 'D' => goto_first_diag(cx),
3524− _ => (),
3525− }
3526− }
3527− })
3594+fn align_view_middle(cx: &mut Context) {
3595+ let (view, doc) = current!(cx.editor);
3596+ let pos = doc.selection(view.id).cursor();
3597+ let pos = coords_at_pos(doc.text().slice(..), pos);
3598+
3599+ const OFFSET: usize = 7; // gutters
3600+ view.first_col = pos
3601+ .col
3602+ .saturating_sub(((view.area.width as usize).saturating_sub(OFFSET)) / 2);
35283603}
35293604
3530−fn right_bracket_mode(cx: &mut Context) {
3531− cx.on_next_key(move |cx, event| {
3532− if let KeyEvent {
3533− code: KeyCode::Char(ch),
3534− ..
3535− } = event
3536− {
3537− match ch {
3538− 'd' => goto_next_diag(cx),
3539− 'D' => goto_last_diag(cx),
3540− _ => (),
3541− }
3542− }
3543− })
3605+fn scroll_up(cx: &mut Context) {
3606+ scroll(cx, cx.count(), Direction::Backward);
35443607}
35453608
3546−use helix_core::surround;
3547−use helix_core::textobject;
3609+fn scroll_down(cx: &mut Context) {
3610+ scroll(cx, cx.count(), Direction::Forward);
3611+}
35483612
3549−fn match_mode(cx: &mut Context) {
3550− let count = cx.count;
3551− cx.on_next_key(move |cx, event| {
3552− if let KeyEvent {
3553− code: KeyCode::Char(ch),
3554− ..
3555− } = event
3556− {
3557− // FIXME: count gets reset because of cx.on_next_key()
3558− cx.count = count;
3559− match ch {
3560− 'm' => match_brackets(cx),
3561− 's' => surround_add(cx),
3562− 'r' => surround_replace(cx),
3563− 'd' => surround_delete(cx),
3564− 'a' => select_textobject(cx, textobject::TextObject::Around),
3565− 'i' => select_textobject(cx, textobject::TextObject::Inside),
3566− _ => (),
3567− }
3568− }
3569− })
3613+fn select_textobject_around(cx: &mut Context) {
3614+ select_textobject(cx, textobject::TextObject::Around);
3615+}
3616+
3617+fn select_textobject_inner(cx: &mut Context) {
3618+ select_textobject(cx, textobject::TextObject::Inside);
35703619}
35713620
35723621fn select_textobject(cx: &mut Context, objtype: textobject::TextObject) {
@@ -3784,7 +3833,7 @@ mode_info! {
37843833}
37853834
37863835mode_info! {
3787− /// goto mode
3836+ /// goto
37883837 ///
37893838 /// When specified with a count, it will go to that line without entering the mode.
37903839 goto_mode, GOTO_MODE, goto_prehook,
@@ -3815,3 +3864,68 @@ mode_info! {
38153864 /// last accessed file
38163865 "a" => goto_last_accessed_file,
38173866}
3867+
3868+mode_info! {
3869+ /// window
3870+ window_mode, WINDOW_MODE,
3871+ /// rotate
3872+ "w" | "C-w" => rotate_view,
3873+ /// horizontal split
3874+ "h" => hsplit,
3875+ /// vertical split
3876+ "v" => vsplit,
3877+ /// close
3878+ "q" => wclose,
3879+}
3880+
3881+mode_info! {
3882+ /// match
3883+ match_mode, MATCH_MODE,
3884+ /// matching character
3885+ "m" => match_brackets,
3886+ /// surround add
3887+ "s" => surround_add,
3888+ /// surround replace
3889+ "r" => surround_replace,
3890+ /// surround delete
3891+ "d" => surround_delete,
3892+ /// around object
3893+ "a" => select_textobject_around,
3894+ /// inside object
3895+ "i" => select_textobject_inner,
3896+}
3897+
3898+mode_info! {
3899+ /// select to previous
3900+ left_bracket_mode, LEFT_BRACKET_MODE,
3901+ /// previous diagnostic
3902+ "d" => goto_prev_diag,
3903+ /// diagnostic (first)
3904+ "D" => goto_first_diag,
3905+}
3906+
3907+mode_info! {
3908+ /// select to next
3909+ right_bracket_mode, RIGHT_BRACKET_MODE,
3910+ /// diagnostic
3911+ "d" => goto_next_diag,
3912+ /// diagnostic (last)
3913+ "D" => goto_last_diag,
3914+}
3915+
3916+mode_info! {
3917+ /// view
3918+ view_mode, VIEW_MODE,
3919+ /// align view top
3920+ "t" => align_view_top,
3921+ /// align view center
3922+ "z" | "c" => align_view_center,
3923+ /// align view bottom
3924+ "b" => align_view_bottom,
3925+ /// align view middle
3926+ "m" => align_view_middle,
3927+ /// scroll up
3928+ "k" => scroll_up,
3929+ /// scroll down
3930+ "j" => scroll_down,
3931+}
helix-term/src/keymap.rs+4−1
@@ -82,6 +82,10 @@ impl Default for Keymaps {
8282 key!('r') => Command::replace,
8383 key!('R') => Command::replace_with_yanked,
8484
85+ key!('~') => Command::switch_case,
86+ alt!('`') => Command::switch_to_uppercase,
87+ key!('`') => Command::switch_to_lowercase,
88+
8589 key!(Home) => Command::goto_line_start,
8690 key!(End) => Command::goto_line_end,
8791
@@ -120,7 +124,6 @@ impl Default for Keymaps {
120124 alt!(';') => Command::flip_selections,
121125 key!('%') => Command::select_all,
122126 key!('x') => Command::extend_line,
123− key!('x') => Command::extend_line,
124127 key!('X') => Command::extend_to_line_bounds,
125128 // crop_to_whole_line
126129
helix-term/src/ui/editor.rs+33−3
@@ -64,6 +64,7 @@ impl EditorView {
6464 surface: &mut Surface,
6565 theme: &Theme,
6666 is_focused: bool,
67+ loader: &syntax::Loader,
6768 ) {
6869 let area = Rect::new(
6970 view.area.x + OFFSET,
@@ -72,7 +73,7 @@ impl EditorView {
7273 view.area.height.saturating_sub(1),
7374 ); // - 1 for statusline
7475
75− self.render_buffer(doc, view, area, surface, theme, is_focused);
76+ self.render_buffer(doc, view, area, surface, theme, is_focused, loader);
7677
7778 // if we're not at the edge of the screen, draw a right border
7879 if viewport.right() != view.area.right() {
@@ -98,6 +99,7 @@ impl EditorView {
9899 self.render_statusline(doc, view, area, surface, theme, is_focused);
99100 }
100101
102+ #[allow(clippy::too_many_arguments)]
101103 pub fn render_buffer(
102104 &self,
103105 doc: &Document,
@@ -106,6 +108,7 @@ impl EditorView {
106108 surface: &mut Surface,
107109 theme: &Theme,
108110 is_focused: bool,
111+ loader: &syntax::Loader,
109112 ) {
110113 let text = doc.text().slice(..);
111114
@@ -122,8 +125,26 @@ impl EditorView {
122125 // TODO: range doesn't actually restrict source, just highlight range
123126 let highlights: Vec<_> = match doc.syntax() {
124127 Some(syntax) => {
128+ let scopes = theme.scopes();
125129 syntax
126− .highlight_iter(text.slice(..), Some(range), None, |_| None)
130+ .highlight_iter(text.slice(..), Some(range), None, |language| {
131+ loader
132+ .language_config_for_scope(&format!("source.{}", language))
133+ .and_then(|language_config| {
134+ let config = language_config.highlight_config(scopes)?;
135+ let config_ref = config.as_ref();
136+ // SAFETY: the referenced `HighlightConfiguration` behind
137+ // the `Arc` is guaranteed to remain valid throughout the
138+ // duration of the highlight.
139+ let config_ref = unsafe {
140+ std::mem::transmute::<
141+ _,
142+ &'static syntax::HighlightConfiguration,
143+ >(config_ref)
144+ };
145+ Some(config_ref)
146+ })
147+ })
127148 .collect() // TODO: we collect here to avoid holding the lock, fix later
128149 }
129150 None => vec![Ok(HighlightEvent::Source {
@@ -735,7 +756,16 @@ impl Component for EditorView {
735756
736757 for (view, is_focused) in cx.editor.tree.views() {
737758 let doc = cx.editor.document(view.doc).unwrap();
738− self.render_view(doc, view, area, surface, &cx.editor.theme, is_focused);
759+ let loader = &cx.editor.syn_loader;
760+ self.render_view(
761+ doc,
762+ view,
763+ area,
764+ surface,
765+ &cx.editor.theme,
766+ is_focused,
767+ loader,
768+ );
739769 }
740770
741771 if let Some(info) = std::mem::take(&mut cx.editor.autoinfo) {
helix-tui/Cargo.toml+1−1
@@ -18,7 +18,7 @@ default = ["crossterm"]
1818[dependencies]
1919bitflags = "1.0"
2020cassowary = "0.3"
21−unicode-segmentation = "1.2"
21+unicode-segmentation = "1.8"
2222crossterm = { version = "0.20", optional = true }
2323serde = { version = "1", "optional" = true, features = ["derive"]}
2424helix-view = { version = "0.3", path = "../helix-view", features = ["term"] }
helix-view/src/document.rs+8−6
@@ -456,14 +456,16 @@ impl Document {
456456 theme: Option<&Theme>,
457457 config_loader: Option<&syntax::Loader>,
458458 ) -> Result<Self, Error> {
459− if !path.exists() {
460− return Ok(Self::default());
461− }
459+ let (mut rope, encoding) = if path.exists() {
460+ let mut file =
461+ std::fs::File::open(&path).context(format!("unable to open {:?}", path))?;
462+ from_reader(&mut file, encoding)?
463+ } else {
464+ let encoding = encoding.unwrap_or(encoding_rs::UTF_8);
465+ (Rope::from(DEFAULT_LINE_ENDING.as_str()), encoding)
466+ };
462467
463− let mut file = std::fs::File::open(&path).context(format!("unable to open {:?}", path))?;
464− let (mut rope, encoding) = from_reader(&mut file, encoding)?;
465468 let line_ending = with_line_ending(&mut rope);
466−
467469 let mut doc = Self::from(rope, Some(encoding));
468470
469471 // set the path and try detecting the language
helix-view/src/editor.rs+11−9
@@ -39,6 +39,7 @@ pub struct Editor {
3939
4040#[derive(Debug, Copy, Clone)]
4141pub enum Action {
42+ Load,
4243 Replace,
4344 HorizontalSplit,
4445 VerticalSplit,
@@ -97,16 +98,14 @@ impl Editor {
9798 self._refresh();
9899 }
99100
100− pub fn set_theme_from_name(&mut self, theme: &str) {
101− let theme = match self.theme_loader.load(theme.as_ref()) {
102− Ok(theme) => theme,
103− Err(e) => {
104− log::warn!("failed setting theme `{}` - {}", theme, e);
105− return;
106− }
107− };
108−
101+ pub fn set_theme_from_name(&mut self, theme: &str) -> anyhow::Result<()> {
102+ use anyhow::Context;
103+ let theme = self
104+ .theme_loader
105+ .load(theme.as_ref())
106+ .with_context(|| format!("failed setting theme `{}`", theme))?;
109107 self.set_theme(theme);
108+ Ok(())
110109 }
111110
112111 fn _refresh(&mut self) {
@@ -153,6 +152,9 @@ impl Editor {
153152
154153 return;
155154 }
155+ Action::Load => {
156+ return;
157+ }
156158 Action::HorizontalSplit => {
157159 let view = View::new(id);
158160 let view_id = self.tree.split(view, Layout::Horizontal);
runtime/grammars/.gitkeepnew file

No changes to the content.

runtime/queries/c/highlights.scm+2
@@ -54,6 +54,8 @@
5454"." @punctuation.delimiter
5555";" @punctuation.delimiter
5656
57+(enumerator) @type.enum.variant
58+
5759(string_literal) @string
5860(system_lib_string) @string
5961
runtime/queries/rust/highlights.scm+303−180
@@ -1,213 +1,336 @@
1−; Identifier conventions
1+; -------
2+; Tree-Sitter doesn't allow overrides in regards to captures,
3+; though it is possible to affect the child node of a captured
4+; node. Thus, the approach here is to flip the order so that
5+; overrides are unnecessary.
6+; -------
27
38
4−; Assume all-caps names are constants
5−((identifier) @constant
6− (#match? @constant "^[A-Z][A-Z\\d_]+$'"))
79
8−; Assume other uppercase names are enum constructors
9−((identifier) @constructor
10− (#match? @constructor "^[A-Z]"))
10+; -------
11+; Types
12+; -------
1113
12−; Assume that uppercase names in paths are types
13−(mod_item
14− name: (identifier) @namespace)
15−(scoped_identifier
16− path: (identifier) @namespace)
17−(scoped_identifier
18− (scoped_identifier
19− name: (identifier) @namespace))
20−(scoped_type_identifier
21− path: (identifier) @namespace)
22−(scoped_type_identifier
23− (scoped_identifier
24− name: (identifier) @namespace))
14+; ---
15+; Primitives
16+; ---
2517
26−((scoped_identifier
27− path: (identifier) @type)
28− (#match? @type "^[A-Z]"))
29−((scoped_identifier
30− path: (scoped_identifier
31− name: (identifier) @type))
32− (#match? @type "^[A-Z]"))
18+(escape_sequence) @escape
19+(primitive_type) @type.builtin
20+(boolean_literal) @constant.builtin
21+[
22+ (integer_literal)
23+ (float_literal)
24+] @number
25+[
26+ (char_literal)
27+ (string_literal)
28+ (raw_string_literal)
29+] @string
30+[
31+ (line_comment)
32+ (block_comment)
33+] @comment
3334
34−; Namespaces
35+; ---
36+; Extraneous
37+; ---
3538
36−(crate) @namespace
37−(extern_crate_declaration
38− (crate)
39− name: (identifier) @namespace)
40−(scoped_use_list
41− path: (identifier) @namespace)
42−(scoped_use_list
43− path: (scoped_identifier
44− (identifier) @namespace))
45−(use_list (scoped_identifier (identifier) @namespace . (_)))
39+(self) @variable.builtin
40+(enum_variant (identifier) @type.enum.variant)
4641
47−; Function calls
42+(field_initializer
43+ (field_identifier) @property)
44+(shorthand_field_initializer) @variable
45+(shorthand_field_identifier) @variable
4846
49−(call_expression
50− function: (identifier) @function)
51−(call_expression
52− function: (field_expression
53− field: (field_identifier) @function.method))
54−(call_expression
55− function: (scoped_identifier
56− "::"
57− name: (identifier) @function))
47+(lifetime
48+ "'" @label
49+ (identifier) @label)
50+(loop_label
51+ (identifier) @type)
5852
59−(generic_function
60− function: (identifier) @function)
61−(generic_function
62− function: (scoped_identifier
63− name: (identifier) @function))
64−(generic_function
65− function: (field_expression
66− field: (field_identifier) @function.method))
53+; ---
54+; Punctuation
55+; ---
6756
68−(macro_invocation
69− macro: (identifier) @function.macro
70− "!" @function.macro)
71−(macro_invocation
72− macro: (scoped_identifier
73− (identifier) @function.macro .))
57+[
58+ "::"
59+ "."
60+ ";"
61+] @punctuation.delimiter
7462
75−; (metavariable) @variable
76−(metavariable) @function.macro
63+[
64+ "("
65+ ")"
66+ "["
67+ "]"
68+] @punctuation.bracket
69+(type_arguments
70+ [
71+ "<"
72+ ">"
73+ ] @punctuation.bracket)
74+(type_parameters
75+ [
76+ "<"
77+ ">"
78+ ] @punctuation.bracket)
7779
78−"$" @function.macro
80+; ---
81+; Parameters
82+; ---
7983
80−; Function definitions
84+(parameter
85+ pattern: (identifier) @variable.parameter)
86+(closure_parameters
87+ (identifier) @variable.parameter)
8188
82−(function_item (identifier) @function)
83−(function_signature_item (identifier) @function)
8489
85−; Other identifiers
8690
87−(type_identifier) @type
88−(primitive_type) @type.builtin
89−(field_identifier) @property
91+; -------
92+; Keywords
93+; -------
9094
91−(line_comment) @comment
92−(block_comment) @comment
95+(for_expression
96+ "for" @keyword.control)
97+((identifier) @keyword.control
98+ (#match? @keyword.control "^yield$"))
99+[
100+ "while"
101+ "loop"
102+ "in"
103+ "break"
104+ "continue"
93105
94−"(" @punctuation.bracket
95−")" @punctuation.bracket
96−"[" @punctuation.bracket
97−"]" @punctuation.bracket
106+ "match"
107+ "if"
108+ "else"
109+ "return"
110+
111+ "await"
112+] @keyword.control
113+
114+[
115+ (crate)
116+ (super)
117+ "as"
118+ "use"
119+ "pub"
120+ "mod"
121+ "extern"
122+
123+ "fn"
124+ "struct"
125+ "enum"
126+ "impl"
127+ "where"
128+ "trait"
129+ "for"
130+
131+ "type"
132+ "union"
133+ "unsafe"
134+ "default"
135+ "macro_rules!"
136+
137+ "let"
138+ "ref"
139+ "move"
140+
141+ "dyn"
142+ "static"
143+ "const"
144+ "async"
145+] @keyword
98146
99−(type_arguments
100− "<" @punctuation.bracket
101− ">" @punctuation.bracket)
102−(type_parameters
103− "<" @punctuation.bracket
104− ">" @punctuation.bracket)
105−
106−"::" @punctuation.delimiter
107−"." @punctuation.delimiter
108−";" @punctuation.delimiter
109−
110−(parameter (identifier) @variable.parameter)
111−(closure_parameters (_) @variable.parameter)
112−
113−(lifetime (identifier) @label)
114−
115−"async" @keyword
116−"break" @keyword
117−"const" @keyword
118−"continue" @keyword
119−(crate) @keyword
120−"default" @keyword
121−"dyn" @keyword
122−"else" @keyword
123−"enum" @keyword
124−"extern" @keyword
125−"fn" @keyword
126−"for" @keyword
127−"if" @keyword
128−"impl" @keyword
129−"in" @keyword
130−"let" @keyword
131−"let" @keyword
132−"loop" @keyword
133−"macro_rules!" @keyword
134−"match" @keyword
135−"mod" @keyword
136−"move" @keyword
137−"pub" @keyword
138−"ref" @keyword
139−"return" @keyword
140−"static" @keyword
141−"struct" @keyword
142−"trait" @keyword
143−"type" @keyword
144−"union" @keyword
145−"unsafe" @keyword
146−"use" @keyword
147−"where" @keyword
148−"while" @keyword
149147(mutable_specifier) @keyword.mut
150−(use_list (self) @keyword)
151−(scoped_use_list (self) @keyword)
152−(scoped_identifier (self) @keyword)
153−(super) @keyword
154−"as" @keyword
155148
156−(self) @variable.builtin
157149
158−[
159−(char_literal)
160−(string_literal)
161−(raw_string_literal)
162−] @string
163150
164−(boolean_literal) @constant.builtin
165−(integer_literal) @number
166−(float_literal) @number
151+; -------
152+; Guess Other Types
153+; -------
167154
168−(escape_sequence) @escape
155+((identifier) @constant
156+ (#match? @constant "^[A-Z][A-Z\\d_]+$"))
157+
158+; ---
159+; PascalCase identifiers in call_expressions (e.g. `Ok()`)
160+; are assumed to be enum constructors.
161+; ---
162+
163+(call_expression
164+ function: [
165+ ((identifier) @type.variant
166+ (#match? @type.variant "^[A-Z]"))
167+ (scoped_identifier
168+ name: ((identifier) @type.variant
169+ (#match? @type.variant "^[A-Z]")))
170+ ])
171+
172+; ---
173+; Assume that types in match arms are enums and not
174+; tuple structs. Same for `if let` expressions.
175+; ---
176+
177+(match_pattern
178+ (scoped_identifier
179+ name: (identifier) @constructor))
180+(tuple_struct_pattern
181+ type: [
182+ ((identifier) @constructor)
183+ (scoped_identifier
184+ name: (identifier) @constructor)
185+ ])
186+(struct_pattern
187+ type: [
188+ ((type_identifier) @constructor)
189+ (scoped_type_identifier
190+ name: (type_identifier) @constructor)
191+ ])
192+
193+; ---
194+; Other PascalCase identifiers are assumed to be structs.
195+; ---
169196
197+((identifier) @type
198+ (#match? @type "^[A-Z]"))
199+
200+
201+
202+; -------
203+; Functions
204+; -------
205+
206+(call_expression
207+ function: [
208+ ((identifier) @function)
209+ (scoped_identifier
210+ name: (identifier) @function)
211+ (field_expression
212+ field: (field_identifier) @function)
213+ ])
214+(generic_function
215+ function: [
216+ ((identifier) @function)
217+ (scoped_identifier
218+ name: (identifier) @function)
219+ (field_expression
220+ field: (field_identifier) @function.method)
221+ ])
222+
223+(function_item
224+ name: (identifier) @function)
225+
226+; ---
227+; Macros
228+; ---
229+
230+(meta_item
231+ (identifier) @attribute)
170232(attribute_item) @attribute
171233(inner_attribute_item) @attribute
172234
235+(macro_definition
236+ name: (identifier) @function.macro)
237+(macro_invocation
238+ macro: [
239+ ((identifier) @function.macro)
240+ (scoped_identifier
241+ name: (identifier) @function.macro)
242+ ]
243+ "!" @function.macro)
244+
245+(metavariable) @variable.parameter
246+(fragment_specifier) @variable.parameter
247+
248+
249+
250+; -------
251+; Operators
252+; -------
253+
173254[
174−"*"
175−"'"
176−"->"
177−"=>"
178−"<="
179−"="
180−"=="
181−"!"
182−"!="
183−"%"
184−"%="
185−"&"
186−"&="
187−"&&"
188−"|"
189−"|="
190−"||"
191−"^"
192−"^="
193−"*"
194−"*="
195−"-"
196−"-="
197−"+"
198−"+="
199−"/"
200−"/="
201−">"
202−"<"
203−">="
204−">>"
205−"<<"
206−">>="
207−"@"
208−".."
209−"..="
210−"'"
255+ "*"
256+ "'"
257+ "->"
258+ "=>"
259+ "<="
260+ "="
261+ "=="
262+ "!"
263+ "!="
264+ "%"
265+ "%="
266+ "&"
267+ "&="
268+ "&&"
269+ "|"
270+ "|="
271+ "||"
272+ "^"
273+ "^="
274+ "*"
275+ "*="
276+ "-"
277+ "-="
278+ "+"
279+ "+="
280+ "/"
281+ "/="
282+ ">"
283+ "<"
284+ ">="
285+ ">>"
286+ "<<"
287+ ">>="
288+ "@"
289+ ".."
290+ "..="
291+ "'"
211292] @operator
212293
294+
295+
296+; -------
297+; Paths
298+; -------
299+
300+(use_declaration
301+ argument: (identifier) @namespace)
302+(use_wildcard
303+ (identifier) @namespace)
304+(extern_crate_declaration
305+ name: (identifier) @namespace)
306+(mod_item
307+ name: (identifier) @namespace)
308+(scoped_use_list
309+ path: (identifier)? @namespace)
310+(use_list
311+ (identifier) @namespace)
312+(use_as_clause
313+ path: (identifier)? @namespace
314+ alias: (identifier) @namespace)
315+
316+; ---
317+; Remaining Paths
318+; ---
319+
320+(scoped_identifier
321+ path: (identifier)? @namespace
322+ name: (identifier) @namespace)
323+(scoped_type_identifier
324+ path: (identifier) @namespace)
325+
326+
327+
328+; -------
329+; Remaining Identifiers
330+; -------
331+
213332"?" @special
333+
334+(type_identifier) @type
335+(identifier) @variable
336+(field_identifier) @variable
runtime/themes/dark_plus.tomlnew file+80
@@ -0,0 +1,80 @@
1+# Author: Shafkath Shuhan <shafkathshuhannyc@gmail.com>
2+
3+"namespace" = { fg = "type" }
4+"module" = { fg = "type" }
5+"type" = { fg = "type" }
6+"type.builtin" = { fg = "type" }
7+
8+"keyword" = { fg = "keyword" }
9+"keyword.directive" = { fg = "keyword" }
10+"function.macro" = { fg = "keyword" }
11+"variable.builtin" = { fg = "keyword" }
12+"label" = { fg = "keyword" }
13+"constant.builtin" = { fg = "keyword" }
14+
15+"punctuation" = { fg = "text" }
16+"punctuation.delimiter" = { fg = "text" }
17+
18+"keyword.control" = { fg = "special" }
19+"special" = { fg = "text" }
20+"operator" = { fg = "text" }
21+
22+"variable" = { fg = "variable" }
23+"variable.parameter" = { fg = "variable" }
24+"property" = { fg = "variable" }
25+
26+"attribute" = { fg = "fn_declaration" }
27+"function" = { fg = "fn_declaration" }
28+"function.builtin" = { fg = "fn_declaration" }
29+
30+"comment" = { fg = "#6A9955" }
31+
32+"constant" = { fg = "constant" }
33+"type.enum.variant" = { fg = "constant" }
34+"constructor" = { fg = "constant" }
35+
36+"string" = { fg = "#ce9178" }
37+"number" = { fg = "#b5cea8" }
38+"escape" = { fg = "#d7ba7d" }
39+
40+"ui.background" = { fg = "#d4d4d4", bg = "#1e1e1e" }
41+
42+"ui.help" = { bg = "widget" }
43+"ui.popup" = { bg = "widget" }
44+"ui.window" = { bg = "widget" }
45+"ui.menu.selected" = { bg = "widget" }
46+
47+"ui.cursor" = { fg = "cursor", modifiers = ["reversed"] }
48+"ui.cursor.primary" = { fg = "cursor", modifiers = ["reversed"] }
49+"ui.cursor.match" = { fg = "cursor", modifiers = ['underlined'] }
50+
51+"ui.selection" = { bg = "#3a3d41" }
52+"ui.selection.primary" = { bg = "#add6ff26" }
53+
54+"ui.linenr" = { fg = "#858585" }
55+"ui.linenr.selected" = { fg = "#c6c6c6" }
56+
57+"ui.statusline" = { fg = "#ffffff", bg = "#007acc" }
58+"ui.statusline.inactive" = { fg = "#ffffff", bg = "#007acc" }
59+
60+"ui.text" = { fg = "text", bg = "background" }
61+"ui.text.focus" = { fg = "#ffffff" }
62+
63+"warning" = { fg = "#cca700" }
64+"error" = { fg = "#f48771" }
65+"info" = { fg = "#75beff" }
66+"hint" = { fg = "#eeeeeeb3" }
67+
68+[palette]
69+type = "#4EC9B0"
70+keyword = "#569CD6"
71+regex = "#CE9178"
72+special = "#C586C0"
73+variable = "#9CDCFE"
74+fn_declaration = "#DCDCAA"
75+constant = "#4FC1FF"
76+
77+background = "#1e1e1e"
78+text = "#d4d4d4"
79+cursor = "#a6a6a6"
80+widget = "#252526"
theme.toml+64−51
@@ -1,68 +1,81 @@
1−"attribute" = "#dbbfef" # lilac
2−"keyword" = "#eccdba" # almond
3−"keyword.directive" = "#dbbfef" # lilac -- preprocessor comments (#if in C)
4−"namespace" = "#dbbfef" # lilac
5−"punctuation" = "#a4a0e8" # lavender
6−"punctuation.delimiter" = "#a4a0e8" # lavender
7−"operator" = "#dbbfef" # lilac
8−"special" = "#efba5d" # honey
9−# "property" = "#a4a0e8" # lavender
10−"property" = "#ffffff" # white
11−"variable" = "#a4a0e8" # lavender
12−# "variable" = "#eccdba" # almond TODO: metavariables only
13−"variable.parameter" = "#a4a0e8" # lavender
14−# TODO distinguish type from type.builtin?
15−"type" = "#ffffff" # white
16−"type.builtin" = "#ffffff" # white
17−"constructor" = "#dbbfef" # lilac
18−"function" = "#ffffff" # white
19−"function.macro" = "#dbbfef" # lilac
20−"function.builtin" = "#ffffff" # white
21−"comment" = "#697C81" # sirocco
22−"variable.builtin" = "#9ff28f" # mint
23−"constant" = "#ffffff" # white
24−"constant.builtin" = "#ffffff" # white
25−"string" = "#cccccc" # silver
26−"number" = "#e8dca0" # chamois
27−"escape" = "#efba5d" # honey
1+attribute = "lilac"
2+keyword = "almond"
3+"keyword.directive" = "lilac" # -- preprocessor comments (#if in C)
4+namespace = "lilac"
5+punctuation = "lavender"
6+"punctuation.delimiter" = "lavender"
7+operator = "lilac"
8+special = "honey"
9+property = "white"
10+variable = "lavender"
11+# variable = "almond" # TODO: metavariables only
12+"variable.parameter" = "lavender"
13+"variable.builtin" = "mint"
14+type = "white"
15+"type.builtin" = "white" # TODO: distinguish?
16+constructor = "lilac"
17+function = "white"
18+"function.macro" = "lilac"
19+"function.builtin" = "white"
20+comment = "sirocco"
21+constant = "white"
22+"constant.builtin" = "white"
23+string = "silver"
24+number = "chamois"
25+escape = "honey"
2826# used for lifetimes
29−"label" = "#efba5d" # honey
27+label = "honey"
3028
31−# TODO: diferentiate number builtin
3229# TODO: diferentiate doc comment
33−# TODO: variable as lilac
34−# TODO: mod/use statements as white
35−# TODO: mod stuff as chamois
36−#
3730# concat (ERROR) @syntax-error and "MISSING ;" selectors for errors
3831
39−"module" = "#ff0000"
32+module = "#ff0000"
4033
41−"ui.background" = { bg = "#3b224c" } # midnight
42−"ui.linenr" = { fg = "#5a5977" } # comet
43−"ui.linenr.selected" = { fg = "#dbbfef" } # lilac
44−"ui.statusline" = { fg = "#dbbfef", bg = "#281733" } # revolver
45−"ui.statusline.inactive" = { fg = "#a4a0e8", bg = "#281733" } # revolver
46−"ui.popup" = { bg = "#281733" } # revolver
47−"ui.window" = { fg = "#452859" } # bossa nova
34+"ui.background" = { bg = "midnight" }
35+"ui.linenr" = { fg = "comet" }
36+"ui.linenr.selected" = { fg = "lilac" }
37+"ui.statusline" = { fg = "lilac", bg = "revolver" }
38+"ui.statusline.inactive" = { fg = "lavender", bg = "revolver" }
39+"ui.popup" = { bg = "revolver" }
40+"ui.window" = { fg = "bossanova" }
4841"ui.help" = { bg = "#7958DC", fg = "#171452" }
4942
50−"ui.text" = { fg = "#a4a0e8" } # lavender
51−"ui.text.focus" = { fg = "#dbbfef" } # lilac
43+"ui.text" = { fg = "lavender" }
44+"ui.text.focus" = { fg = "lilac" }
5245
5346"ui.selection" = { bg = "#540099" }
5447"ui.selection.primary" = { bg = "#540099" }
5548# TODO: namespace ui.cursor as ui.selection.cursor?
56−"ui.cursor.select" = { bg = "#6F44F0" }
57−"ui.cursor.insert" = { bg = "#ffffff" }
49+"ui.cursor.select" = { bg = "delta" }
50+"ui.cursor.insert" = { bg = "white" }
5851"ui.cursor.match" = { fg = "#212121", bg = "#6C6999" }
5952"ui.cursor" = { modifiers = ["reversed"] }
6053
61−"ui.menu.selected" = { fg = "#281733", bg = "#ffffff" } # revolver
54+"ui.menu.selected" = { fg = "revolver", bg = "white" }
6255
63−"diagnostic" = { modifiers = ["underlined"] }
56+diagnostic = { modifiers = ["underlined"] }
6457
65−"warning" = "#ffcd1c"
66−"error" = "#f47868"
67−"info" = "#6F44F0"
68−"hint" = "#cccccc"
58+warning = "lightning"
59+error = "apricot"
60+info = "delta"
61+hint = "silver"
62+
63+[palette]
64+white = "#ffffff"
65+lilac = "#dbbfef"
66+lavender = "#a4a0e8"
67+comet = "#5a5977"
68+bossanova = "#452859"
69+midnight = "#3b224c"
70+revolver = "#281733"
71+
72+silver = "#cccccc"
73+sirocco = "#697C81"
74+mint = "#9ff28f"
75+almond = "#eccdba"
76+chamois = "#E8DCA0"
77+honey = "#efba5d"
78+
79+apricot = "#f47868"
80+lightning = "#ffcd1c"
81+delta = "#6F44F0"