on master help sign in
Merge branch 'master' into great_line_ending_and_cursor_range_cleanup
a77274eNathan Vegdahl
.github/workflows/release.yml+2−1
@@ -99,6 +99,7 @@ jobs:
99 else
99 else
100 cp "target/${{ matrix.target }}/release/hx" "dist/"
100 cp "target/${{ matrix.target }}/release/hx" "dist/"
101 fi
101 fi
102+ cp -r runtime dist
102
103
103 - uses: actions/upload-artifact@v2.2.4
104 - uses: actions/upload-artifact@v2.2.4
104 with:
105 with:
@@ -148,7 +149,7 @@ jobs:
148 pkgname=helix-$TAG-$platform
149 pkgname=helix-$TAG-$platform
149 mkdir tmp/$pkgname
150 mkdir tmp/$pkgname
150 cp LICENSE README.md tmp/$pkgname
151 cp LICENSE README.md tmp/$pkgname
151− cp -r runtime tmp/$pkgname/
152+ mv bins-$platform/runtime tmp/$pkgname/
152 mv bins-$platform/hx$exe tmp/$pkgname
153 mv bins-$platform/hx$exe tmp/$pkgname
153 chmod +x tmp/$pkgname/hx$exe
154 chmod +x tmp/$pkgname/hx$exe
154
155
.gitignore+1
@@ -3,3 +3,4 @@ target
3helix-term/rustfmt.toml
3helix-term/rustfmt.toml
4helix-syntax/languages/
4helix-syntax/languages/
5result
5result
6+runtime/grammars
Cargo.lock+28−29
@@ -13,9 +13,9 @@ dependencies = [
13
13
14[[package]]
14[[package]]
15name = "anyhow"
15name = "anyhow"
16−version = "1.0.41"
16+version = "1.0.42"
17source = "registry+https://github.com/rust-lang/crates.io-index"
17source = "registry+https://github.com/rust-lang/crates.io-index"
18−checksum = "15af2628f6890fe2609a3b91bef4c83450512802e59489f9c1cb1fa5df064a61"
18+checksum = "595d3cfa7a60d4555cb5067b99f07142a08ea778de5cf993f7b75c7d8fabc486"
19
19
20[[package]]
20[[package]]
21name = "arc-swap"
21name = "arc-swap"
@@ -58,12 +58,9 @@ checksum = "df8670b8c7b9dae1793364eafadf7239c40d669904660c5960d74cfd80b46a53"
58
58
59[[package]]
59[[package]]
60name = "cc"
60name = "cc"
61−version = "1.0.68"
61+version = "1.0.69"
62source = "registry+https://github.com/rust-lang/crates.io-index"
62source = "registry+https://github.com/rust-lang/crates.io-index"
63−checksum = "4a72c244c1ff497a746a7e1fb3d14bd08420ecda70c8f25c7112f2781652d787"
63+checksum = "e70cc2f62c6ce1868963827bd677764c62d07c3d9a3e1fb1177ee1a9ab199eb2"
64−dependencies = [
65− "jobserver",
66−]
67
64
68[[package]]
65[[package]]
69name = "cfg-if"
66name = "cfg-if"
@@ -354,8 +351,9 @@ dependencies = [
354name = "helix-syntax"
351name = "helix-syntax"
355version = "0.3.0"
352version = "0.3.0"
356dependencies = [
353dependencies = [
354+ "anyhow",
357 "cc",
355 "cc",
358− "serde",
356+ "libloading",
359 "threadpool",
357 "threadpool",
360 "tree-sitter",
358 "tree-sitter",
361]
359]
@@ -475,15 +473,6 @@ version = "0.4.7"
475source = "registry+https://github.com/rust-lang/crates.io-index"
473source = "registry+https://github.com/rust-lang/crates.io-index"
476checksum = "dd25036021b0de88a0aff6b850051563c6516d0bf53f8638938edbb9de732736"
474checksum = "dd25036021b0de88a0aff6b850051563c6516d0bf53f8638938edbb9de732736"
477
475
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−
487[[package]]
476[[package]]
488name = "jsonrpc-core"
477name = "jsonrpc-core"
489version = "17.1.0"
478version = "17.1.0"
@@ -509,6 +498,16 @@ version = "0.2.97"
509source = "registry+https://github.com/rust-lang/crates.io-index"
498source = "registry+https://github.com/rust-lang/crates.io-index"
510checksum = "12b8adadd720df158f4d70dfe7ccc6adb0472d7c55ca83445f6a5ab3e36f8fb6"
499checksum = "12b8adadd720df158f4d70dfe7ccc6adb0472d7c55ca83445f6a5ab3e36f8fb6"
511
500
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+
512[[package]]
511[[package]]
513name = "lock_api"
512name = "lock_api"
514version = "0.4.4"
513version = "0.4.4"
@@ -914,9 +913,9 @@ checksum = "f173ac3d1a7e3b28003f40de0b5ce7fe2710f9b9dc3fc38664cebee46b3b6527"
914
913
915[[package]]
914[[package]]
916name = "slotmap"
915name = "slotmap"
917−version = "1.0.3"
916+version = "1.0.5"
918source = "registry+https://github.com/rust-lang/crates.io-index"
917source = "registry+https://github.com/rust-lang/crates.io-index"
919−checksum = "585cd5dffe4e9e06f6dfdf66708b70aca3f781bed561f4f667b2d9c0d4559e36"
918+checksum = "a952280edbecfb1d4bd3cf2dbc309dc6ab523e53487c438ae21a6df09fe84bc4"
920dependencies = [
919dependencies = [
921 "version_check",
920 "version_check",
922]
921]
@@ -957,18 +956,18 @@ dependencies = [
957
956
958[[package]]
957[[package]]
959name = "thiserror"
958name = "thiserror"
960−version = "1.0.25"
959+version = "1.0.26"
961source = "registry+https://github.com/rust-lang/crates.io-index"
960source = "registry+https://github.com/rust-lang/crates.io-index"
962−checksum = "fa6f76457f59514c7eeb4e59d891395fab0b2fd1d40723ae737d64153392e9c6"
961+checksum = "93119e4feac1cbe6c798c34d3a53ea0026b0b1de6a120deef895137c0529bfe2"
963dependencies = [
962dependencies = [
964 "thiserror-impl",
963 "thiserror-impl",
965]
964]
966
965
967[[package]]
966[[package]]
968name = "thiserror-impl"
967name = "thiserror-impl"
969−version = "1.0.25"
968+version = "1.0.26"
970source = "registry+https://github.com/rust-lang/crates.io-index"
969source = "registry+https://github.com/rust-lang/crates.io-index"
971−checksum = "8a36768c0fbf1bb15eca10defa29526bda730a2376c2ab4393ccfa16fb1a318d"
970+checksum = "060d69a0afe7796bf42e9e2ff91f5ee691fb15c53d38b4b62a9a53eb23164745"
972dependencies = [
971dependencies = [
973 "proc-macro2",
972 "proc-macro2",
974 "quote",
973 "quote",
@@ -1010,9 +1009,9 @@ checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c"
1010
1009
1011[[package]]
1010[[package]]
1012name = "tokio"
1011name = "tokio"
1013−version = "1.7.1"
1012+version = "1.8.1"
1014source = "registry+https://github.com/rust-lang/crates.io-index"
1013source = "registry+https://github.com/rust-lang/crates.io-index"
1015−checksum = "5fb2ed024293bb19f7a5dc54fe83bf86532a44c12a2bb8ba40d64a4509395ca2"
1014+checksum = "98c8b05dc14c75ea83d63dd391100353789f5f24b8b3866542a5e85c8be8e985"
1016dependencies = [
1015dependencies = [
1017 "autocfg",
1016 "autocfg",
1018 "bytes",
1017 "bytes",
@@ -1041,9 +1040,9 @@ dependencies = [
1041
1040
1042[[package]]
1041[[package]]
1043name = "tokio-stream"
1042name = "tokio-stream"
1044−version = "0.1.6"
1043+version = "0.1.7"
1045source = "registry+https://github.com/rust-lang/crates.io-index"
1044source = "registry+https://github.com/rust-lang/crates.io-index"
1046−checksum = "f8864d706fdb3cc0843a49647ac892720dac98a6eeb818b77190592cf4994066"
1045+checksum = "7b2f3f698253f03119ac0102beaa64f67a67e08074d03a22d18784104543727f"
1047dependencies = [
1046dependencies = [
1048 "futures-core",
1047 "futures-core",
1049 "pin-project-lite",
1048 "pin-project-lite",
@@ -1104,9 +1103,9 @@ dependencies = [
1104
1103
1105[[package]]
1104[[package]]
1106name = "unicode-segmentation"
1105name = "unicode-segmentation"
1107−version = "1.7.1"
1106+version = "1.8.0"
1108source = "registry+https://github.com/rust-lang/crates.io-index"
1107source = "registry+https://github.com/rust-lang/crates.io-index"
1109−checksum = "bb0d2e7be6ae3a5fa87eed5fb451aff96f2573d2694942e40543ae0bbe19c796"
1108+checksum = "8895849a949e7845e06bd6dc1aa51731a103c42707010a5b591c0038fb73385b"
1110
1109
1111[[package]]
1110[[package]]
1112name = "unicode-width"
1111name = "unicode-width"
README.md+7−4
@@ -41,14 +41,17 @@ cargo install --path helix-term
41
41
42This will install the `hx` binary to `$HOME/.cargo/bin`.
42This will install the `hx` binary to `$HOME/.cargo/bin`.
43
43
44−Now copy the `runtime/` directory somewhere. Helix will by default look for the runtime
44+Helix also needs it's runtime files so make sure to copy/symlink the `runtime/` directory into the
45−inside the config directory or the same directory as executable, but that can be overriden
45+config directory (for example `~/.config/helix/runtime` on Linux/macOS). This location can be overriden
46via the `HELIX_RUNTIME` environment variable.
46via the `HELIX_RUNTIME` environment variable.
47
47
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+
48−> NOTE: running via cargo doesn't require setting explicit `HELIX_RUNTIME` path, it will automatically
51+> NOTE: running via cargo also doesn't require setting explicit `HELIX_RUNTIME` path, it will automatically
49> detect the `runtime` directory in the project root.
52> detect the `runtime` directory in the project root.
50
53
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
52it with:
55it with:
53
56
54```
57```
book/src/keymap.md+23−20
@@ -41,26 +41,29 @@
41
41
42### Changes
42### Changes
43
43
44−| Key | Description |
44+| Key | Description |
45−| ----- | ----------- |
45+| ----- | ----------- |
46−| `r` | Replace with a character |
46+| `r` | Replace with a character |
47−| `R` | Replace with yanked text |
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 |
48−| `i` | Insert before selection |
51+| `i` | Insert before selection |
49−| `a` | Insert after selection (append) |
52+| `a` | Insert after selection (append) |
50−| `I` | Insert at the start of the line |
53+| `I` | Insert at the start of the line |
51−| `A` | Insert at the end of the line |
54+| `A` | Insert at the end of the line |
52−| `o` | Open new line below selection |
55+| `o` | Open new line below selection |
53−| `o` | Open new line above selection |
56+| `o` | Open new line above selection |
54−| `u` | Undo change |
57+| `u` | Undo change |
55−| `U` | Redo change |
58+| `U` | Redo change |
56−| `y` | Yank selection |
59+| `y` | Yank selection |
57−| `p` | Paste after selection |
60+| `p` | Paste after selection |
58−| `P` | Paste before selection |
61+| `P` | Paste before selection |
59−| `>` | Indent selection |
62+| `>` | Indent selection |
60−| `<` | Unindent selection |
63+| `<` | Unindent selection |
61−| `=` | Format selection |
64+| `=` | Format selection |
62−| `d` | Delete selection |
65+| `d` | Delete selection |
63−| `c` | Change selection (delete and enter insert mode) |
66+| `c` | Change selection (delete and enter insert mode) |
64
67
65### Selection manipulation
68### Selection manipulation
66
69
book/src/themes.md+2
@@ -51,6 +51,7 @@ Possible keys:
51| `attribute` | |
51| `attribute` | |
52| `keyword` | |
52| `keyword` | |
53| `keyword.directive` | Preprocessor directives (\#if in C) |
53| `keyword.directive` | Preprocessor directives (\#if in C) |
54+| `keyword.control` | Control flow |
54| `namespace` | |
55| `namespace` | |
55| `punctuation` | |
56| `punctuation` | |
56| `punctuation.delimiter` | |
57| `punctuation.delimiter` | |
@@ -61,6 +62,7 @@ Possible keys:
61| `variable.parameter` | |
62| `variable.parameter` | |
62| `type` | |
63| `type` | |
63| `type.builtin` | |
64| `type.builtin` | |
65+| `type.enum.variant` | Enum variants |
64| `constructor` | |
66| `constructor` | |
65| `function` | |
67| `function` | |
66| `function.macro` | |
68| `function.macro` | |
helix-core/Cargo.toml+1−1
@@ -19,7 +19,7 @@ helix-syntax = { version = "0.3", path = "../helix-syntax" }
19ropey = "1.3"
19ropey = "1.3"
20smallvec = "1.4"
20smallvec = "1.4"
21tendril = "0.4.2"
21tendril = "0.4.2"
22−unicode-segmentation = "1.7"
22+unicode-segmentation = "1.8"
23unicode-width = "0.1"
23unicode-width = "0.1"
24unicode-general-category = "0.4"
24unicode-general-category = "0.4"
25# slab = "0.4.2"
25# slab = "0.4.2"
helix-core/src/indent.rs+2−2
@@ -253,14 +253,14 @@ where
253
253
254 let doc = Rope::from(doc);
254 let doc = Rope::from(doc);
255 use crate::syntax::{
255 use crate::syntax::{
256− Configuration, IndentationConfiguration, Lang, LanguageConfiguration, Loader,
256+ Configuration, IndentationConfiguration, LanguageConfiguration, Loader,
257 };
257 };
258 use once_cell::sync::OnceCell;
258 use once_cell::sync::OnceCell;
259 let loader = Loader::new(Configuration {
259 let loader = Loader::new(Configuration {
260 language: vec![LanguageConfiguration {
260 language: vec![LanguageConfiguration {
261 scope: "source.rust".to_string(),
261 scope: "source.rust".to_string(),
262 file_types: vec!["rs".to_string()],
262 file_types: vec!["rs".to_string()],
263− language_id: Lang::Rust,
263+ language_id: "Rust".to_string(),
264 highlight_config: OnceCell::new(),
264 highlight_config: OnceCell::new(),
265 //
265 //
266 roots: vec![],
266 roots: vec![],
helix-core/src/syntax.rs+8−8
@@ -5,7 +5,7 @@ use crate::{
5 Rope, RopeSlice, Tendril,
5 Rope, RopeSlice, Tendril,
6};
6};
7
7
8−pub use helix_syntax::{get_language, get_language_name, Lang};
8+pub use helix_syntax::get_language;
9
9
10use arc_swap::ArcSwap;
10use arc_swap::ArcSwap;
11
11
@@ -31,7 +31,7 @@ pub struct Configuration {
31#[serde(rename_all = "kebab-case")]
31#[serde(rename_all = "kebab-case")]
32pub struct LanguageConfiguration {
32pub struct LanguageConfiguration {
33 #[serde(rename = "name")]
33 #[serde(rename = "name")]
34− pub(crate) language_id: Lang,
34+ pub(crate) language_id: String,
35 pub scope: String, // source.rust
35 pub scope: String, // source.rust
36 pub file_types: Vec<String>, // filename ends_with? <Gemfile, rb, etc>
36 pub file_types: Vec<String>, // filename ends_with? <Gemfile, rb, etc>
37 pub roots: Vec<String>, // these indicate project roots <.git, Cargo.toml>
37 pub roots: Vec<String>, // these indicate project roots <.git, Cargo.toml>
@@ -153,7 +153,7 @@ fn read_query(language: &str, filename: &str) -> String {
153
153
154impl LanguageConfiguration {
154impl LanguageConfiguration {
155 fn initialize_highlight(&self, scopes: &[String]) -> Option<Arc<HighlightConfiguration>> {
155 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();
157
157
158 let highlights_query = read_query(&language, "highlights.scm");
158 let highlights_query = read_query(&language, "highlights.scm");
159 // always highlight syntax errors
159 // always highlight syntax errors
@@ -161,17 +161,17 @@ impl LanguageConfiguration {
161
161
162 let injections_query = read_query(&language, "injections.scm");
162 let injections_query = read_query(&language, "injections.scm");
163
163
164− let locals_query = "";
164+ let locals_query = read_query(&language, "locals.scm");
165
165
166 if highlights_query.is_empty() {
166 if highlights_query.is_empty() {
167 None
167 None
168 } else {
168 } else {
169− let language = get_language(self.language_id);
169+ let language = get_language(&crate::RUNTIME_DIR, &self.language_id).ok()?;
170 let config = HighlightConfiguration::new(
170 let config = HighlightConfiguration::new(
171 language,
171 language,
172 &highlights_query,
172 &highlights_query,
173 &injections_query,
173 &injections_query,
174− locals_query,
174+ &locals_query,
175 )
175 )
176 .unwrap(); // TODO: no unwrap
176 .unwrap(); // TODO: no unwrap
177 config.configure(scopes);
177 config.configure(scopes);
@@ -198,7 +198,7 @@ impl LanguageConfiguration {
198 pub fn indent_query(&self) -> Option<&IndentQuery> {
198 pub fn indent_query(&self) -> Option<&IndentQuery> {
199 self.indent_query
199 self.indent_query
200 .get_or_init(|| {
200 .get_or_init(|| {
201− let language = get_language_name(self.language_id).to_ascii_lowercase();
201+ let language = self.language_id.to_ascii_lowercase();
202
202
203 let toml = load_runtime_file(&language, "indents.toml").ok()?;
203 let toml = load_runtime_file(&language, "indents.toml").ok()?;
204 toml::from_slice(toml.as_bytes()).ok()
204 toml::from_slice(toml.as_bytes()).ok()
@@ -1812,7 +1812,7 @@ mod test {
1812 .map(String::from)
1812 .map(String::from)
1813 .collect();
1813 .collect();
1814
1814
1815− let language = get_language(Lang::Rust);
1815+ let language = get_language(&crate::RUNTIME_DIR, "Rust").unwrap();
1816 let config = HighlightConfiguration::new(
1816 let config = HighlightConfiguration::new(
1817 language,
1817 language,
1818 &std::fs::read_to_string(
1818 &std::fs::read_to_string(
helix-lsp/Cargo.toml+2−2
@@ -23,5 +23,5 @@ lsp-types = { version = "0.89", features = ["proposed"] }
23serde = { version = "1.0", features = ["derive"] }
23serde = { version = "1.0", features = ["derive"] }
24serde_json = "1.0"
24serde_json = "1.0"
25thiserror = "1.0"
25thiserror = "1.0"
26−tokio = { version = "1.7", features = ["full"] }
26+tokio = { version = "1.8", features = ["full"] }
27−tokio-stream = "0.1.6"
27+tokio-stream = "0.1.7"
helix-syntax/Cargo.toml+4−2
@@ -12,8 +12,10 @@ include = ["src/**/*", "languages/**/*", "build.rs", "!**/docs/**/*", "!**/test/
12
12
13[dependencies]
13[dependencies]
14tree-sitter = "0.19"
14tree-sitter = "0.19"
15−serde = { version = "1.0", features = ["derive"] }
15+libloading = "0.7"
16+anyhow = "1"
16
17
17[build-dependencies]
18[build-dependencies]
18−cc = { version = "1", features = ["parallel"] }
19+cc = { version = "1" }
19threadpool = { version = "1.0" }
20threadpool = { version = "1.0" }
21+anyhow = "1"
helix-syntax/build.rs+133−66
@@ -1,79 +1,147 @@
1+use anyhow::{anyhow, Context, Result};
1use std::fs;
2use std::fs;
3+use std::time::SystemTime;
4+use std::{
2−use std::path::PathBuf;
5+ path::{Path, PathBuf},
6+ process::Command,
7+};
3
8
4use std::sync::mpsc::channel;
9use std::sync::mpsc::channel;
5
10
6−fn collect_tree_sitter_dirs(ignore: &[String]) -> Vec<String> {
11+fn collect_tree_sitter_dirs(ignore: &[String]) -> Result<Vec<String>> {
7 let mut dirs = Vec::new();
12 let mut dirs = Vec::new();
13+ let path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("languages");
14+
8− for entry in fs::read_dir("languages").unwrap().flatten() {
15+ for entry in fs::read_dir(path)? {
16+ let entry = entry?;
9 let path = entry.path();
17 let path = entry.path();
10− let dir = path.file_name().unwrap().to_str().unwrap().to_string();
18+
11− if !ignore.contains(&dir) {
19+ if !entry.file_type()?.is_dir() {
12− dirs.push(dir);
20+ continue;
13 }
21 }
14− }
15− dirs
16−}
17
22
18−fn collect_src_files(dir: &str) -> (Vec<String>, Vec<String>) {
23+ let dir = path.file_name().unwrap().to_str().unwrap().to_string();
19− eprintln!("Collect files for {}", dir);
20
24
21− let mut c_files = Vec::new();
25+ // filter ignores
22− let mut cpp_files = Vec::new();
26+ if ignore.contains(&dir) {
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− {
33 continue;
27 continue;
34 }
28 }
35− if let Some(ext) = path.extension() {
29+ dirs.push(dir)
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− }
42 }
30 }
43− (c_files, cpp_files)
44−}
45
31
46−fn build_c(files: Vec<String>, language: &str) {
32+ Ok(dirs)
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));
56}
33}
57
34
58−fn build_cpp(files: Vec<String>, language: &str) {
35+#[cfg(unix)]
59− let mut build = cc::Build::new();
36+const DYLIB_EXTENSION: &str = "so";
37+
38+#[cfg(windows)]
39+const DYLIB_EXTENSION: &str = "dll";
60
40
61− let flag = if build.get_compiler().is_like_msvc() {
41+fn build_library(src_path: &Path, language: &str) -> Result<()> {
62− "/std:c++17"
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)
63 } else {
49 } 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+ }
65 };
56 };
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);
66
60
67− for file in files {
61+ let recompile = needs_recompile(&library_path, &parser_path, &scanner_path)
68− build
62+ .with_context(|| "Failed to compare source and binary timestamps")?;
69− .file(&file)
63+
70− .include(PathBuf::from(file).parent().unwrap())
64+ if !recompile {
71− .pic(true)
65+ return Ok(());
72− .warnings(false)
66+ }
73− .cpp(true)
67+ let mut config = cc::Build::new();
74− .flag_if_supported(flag);
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);
75 }
74 }
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()?)
77}
145}
78
146
79fn build_dir(dir: &str, language: &str) {
147fn build_dir(dir: &str, language: &str) {
@@ -92,22 +160,21 @@ fn build_dir(dir: &str, language: &str) {
92 eprintln!("You can fix in using 'git submodule init && git submodule update --recursive'.");
160 eprintln!("You can fix in using 'git submodule init && git submodule update --recursive'.");
93 std::process::exit(1);
161 std::process::exit(1);
94 }
162 }
95− let (c, cpp) = collect_src_files(dir);
163+
96− if !c.is_empty() {
164+ let path = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
97− build_c(c, language);
165+ .join("languages")
98− }
166+ .join(dir)
99− if !cpp.is_empty() {
167+ .join("src");
100− build_cpp(cpp, language);
168+
101− }
169+ build_library(&path, language).unwrap();
102}
170}
103
171
104fn main() {
172fn main() {
105 let ignore = vec![
173 let ignore = vec![
106 "tree-sitter-typescript".to_string(),
174 "tree-sitter-typescript".to_string(),
107 "tree-sitter-haskell".to_string(), // aarch64 failures: https://github.com/tree-sitter/tree-sitter-haskell/issues/34
175 "tree-sitter-haskell".to_string(), // aarch64 failures: https://github.com/tree-sitter/tree-sitter-haskell/issues/34
108− ".DS_Store".to_string(),
109 ];
176 ];
110− let dirs = collect_tree_sitter_dirs(&ignore);
177+ let dirs = collect_tree_sitter_dirs(&ignore).unwrap();
111
178
112 let mut n_jobs = 0;
179 let mut n_jobs = 0;
113 let pool = threadpool::Builder::new().build(); // by going through the builder, it'll use num_cpus
180 let pool = threadpool::Builder::new().build(); // by going through the builder, it'll use num_cpus
@@ -118,7 +185,7 @@ fn main() {
118 n_jobs += 1;
185 n_jobs += 1;
119
186
120 pool.execute(move || {
187 pool.execute(move || {
121− let language = &dir[12..]; // skip tree-sitter- prefix
188+ let language = &dir.strip_prefix("tree-sitter-").unwrap();
122 build_dir(&dir, language);
189 build_dir(&dir, language);
123
190
124 // report progress
191 // 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};
2use tree_sitter::Language;
3use tree_sitter::Language;
3
4
4−#[macro_export]
5+fn replace_dashes_with_underscores(name: &str) -> String {
5−macro_rules! mk_extern {
6+ let mut result = String::with_capacity(name.len());
6− ( $( $name:ident ),* ) => {
7+ for c in name.chars() {
7− $(
8+ if c == '-' {
8− extern "C" { pub fn $name() -> Language; }
9+ result.push('_');
9− )*
10+ } else {
10− };
11+ result.push(c);
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− )*
22 }
12 }
23− };
13+ }
14+ result
24}
15}
16+#[cfg(unix)]
17+const DYLIB_EXTENSION: &str = "so";
25
18
26−#[macro_export]
19+#[cfg(windows)]
27−macro_rules! mk_get_language {
20+const DYLIB_EXTENSION: &str = "dll";
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−}
41
21
42−#[macro_export]
22+pub fn get_language(runtime_path: &std::path::Path, name: &str) -> Result<Language> {
43−macro_rules! mk_get_language_name {
23+ let name = name.to_ascii_lowercase();
44− ( $( $camel:ident ),* ) => {
24+ let mut library_path = runtime_path.join("grammars").join(&name);
45− #[must_use]
25+ // TODO: duplicated under build
46− pub const fn get_language_name(lang: Lang) -> &'static str {
26+ library_path.set_extension(DYLIB_EXTENSION);
47− match lang {
48− $(
49− Lang::$camel => stringify!($camel),
50− )*
51− }
52− }
53− };
54−}
55
27
56−#[macro_export]
28+ let library = unsafe { Library::new(&library_path) }
57−macro_rules! mk_langs {
29+ .with_context(|| format!("Error opening dynamic library {:?}", &library_path))?;
58− ( $( ($camel:ident, $name:ident) ),* ) => {
30+ let language_fn_name = format!("tree_sitter_{}", replace_dashes_with_underscores(&name));
59− mk_extern!($( $name ),*);
31+ let language = unsafe {
60− mk_enum!($( $camel ),*);
32+ let language_fn: Symbol<unsafe extern "C" fn() -> Language> = library
61− mk_get_language!($( ($camel, $name) ),*);
33+ .get(language_fn_name.as_bytes())
62− mk_get_language_name!($( $camel ),*);
34+ .with_context(|| format!("Failed to load symbol {}", language_fn_name))?;
35+ language_fn()
63 };
36 };
37+ std::mem::forget(library);
38+ Ok(language)
64}
39}
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;
9use std::{
9use std::{
10 io::{stdout, Write},
10 io::{stdout, Write},
11 sync::Arc,
11 sync::Arc,
12+ time::{Duration, Instant},
12};
13};
13
14
14use anyhow::Error;
15use anyhow::Error;
@@ -82,15 +83,18 @@ impl Application {
82 editor.new_file(Action::VerticalSplit);
83 editor.new_file(Action::VerticalSplit);
83 compositor.push(Box::new(ui::file_picker(first.clone())));
84 compositor.push(Box::new(ui::file_picker(first.clone())));
84 } else {
85 } else {
86+ let nr_of_files = args.files.len();
87+ editor.open(first.to_path_buf(), Action::VerticalSplit)?;
85 for file in args.files {
88 for file in args.files {
86 if file.is_dir() {
89 if file.is_dir() {
87 return Err(anyhow::anyhow!(
90 return Err(anyhow::anyhow!(
88 "expected a path to file, found a directory. (to open a directory pass it as first argument)"
91 "expected a path to file, found a directory. (to open a directory pass it as first argument)"
89 ));
92 ));
90 } else {
93 } else {
91− editor.open(file, Action::VerticalSplit)?;
94+ editor.open(file.to_path_buf(), Action::Load)?;
92 }
95 }
93 }
96 }
97+ editor.set_status(format!("Loaded {} files.", nr_of_files));
94 }
98 }
95 } else {
99 } else {
96 editor.new_file(Action::VerticalSplit);
100 editor.new_file(Action::VerticalSplit);
@@ -130,6 +134,8 @@ impl Application {
130
134
131 pub async fn event_loop(&mut self) {
135 pub async fn event_loop(&mut self) {
132 let mut reader = EventStream::new();
136 let mut reader = EventStream::new();
137+ let mut last_render = Instant::now();
138+ let deadline = Duration::from_secs(1) / 60;
133
139
134 self.render();
140 self.render();
135
141
@@ -139,26 +145,22 @@ impl Application {
139 break;
145 break;
140 }
146 }
141
147
142− use futures_util::{FutureExt, StreamExt};
148+ use futures_util::StreamExt;
143
149
144 tokio::select! {
150 tokio::select! {
151+ biased;
152+
145 event = reader.next() => {
153 event = reader.next() => {
146 self.handle_terminal_events(event)
154 self.handle_terminal_events(event)
147 }
155 }
148 Some((id, call)) = self.editor.language_servers.incoming.next() => {
156 Some((id, call)) = self.editor.language_servers.incoming.next() => {
149 self.handle_language_server_message(call, id).await;
157 self.handle_language_server_message(call, id).await;
150−
158+ // limit render calls for fast language server messages
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() {
159+ let last = self.editor.language_servers.incoming.is_empty();
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
160+ if last || last_render.elapsed() > deadline {
158− break;
161+ self.render();
159− }
162+ last_render = Instant::now();
160 }
163 }
161− self.render();
162 }
164 }
163 Some(callback) = self.jobs.futures.next() => {
165 Some(callback) = self.jobs.futures.next() => {
164 self.jobs.handle_callback(&mut self.editor, &mut self.compositor, callback);
166 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::{
6 object, pos_at_coords,
6 object, pos_at_coords,
7 regex::{self, Regex},
7 regex::{self, Regex},
8 register::Register,
8 register::Register,
9− search, selection, LineEnding, Position, Range, Rope, RopeGraphemes, RopeSlice, Selection,
9+ search, selection, surround, textobject, LineEnding, Position, Range, Rope, RopeGraphemes,
10− SmallVec, Tendril, Transaction,
10+ RopeSlice, Selection, SmallVec, Tendril, Transaction,
11};
11};
12
12
13use helix_view::{
13use helix_view::{
@@ -20,7 +20,7 @@ use helix_view::{
20 Document, DocumentId, Editor, ViewId,
20 Document, DocumentId, Editor, ViewId,
21};
21};
22
22
23−use anyhow::anyhow;
23+use anyhow::{anyhow, bail, Context as _};
24use helix_lsp::{
24use helix_lsp::{
25 lsp,
25 lsp,
26 util::{lsp_pos_to_pos, lsp_range_to_range, pos_to_lsp_pos, range_to_lsp_range},
26 util::{lsp_pos_to_pos, lsp_range_to_range, pos_to_lsp_pos, range_to_lsp_range},
@@ -37,6 +37,7 @@ use crate::{
37use crate::job::{self, Job, Jobs};
37use crate::job::{self, Job, Jobs};
38use futures_util::{FutureExt, TryFutureExt};
38use futures_util::{FutureExt, TryFutureExt};
39use std::collections::HashMap;
39use std::collections::HashMap;
40+use std::num::NonZeroUsize;
40use std::{fmt, future::Future};
41use std::{fmt, future::Future};
41
42
42use std::{
43use std::{
@@ -49,7 +50,7 @@ use serde::de::{self, Deserialize, Deserializer};
49
50
50pub struct Context<'a> {
51pub struct Context<'a> {
51 pub selected_register: helix_view::RegisterSelection,
52 pub selected_register: helix_view::RegisterSelection,
52− pub count: Option<std::num::NonZeroUsize>,
53+ pub count: Option<NonZeroUsize>,
53 pub editor: &'a mut Editor,
54 pub editor: &'a mut Editor,
54
55
55 pub callback: Option<crate::compositor::Callback>,
56 pub callback: Option<crate::compositor::Callback>,
@@ -75,7 +76,9 @@ impl<'a> Context<'a> {
75
76
76 #[inline]
77 #[inline]
77 pub fn on_next_key_mode(&mut self, map: HashMap<KeyEvent, fn(&mut Context)>) {
78 pub fn on_next_key_mode(&mut self, map: HashMap<KeyEvent, fn(&mut Context)>) {
79+ let count = self.count;
78 self.on_next_key(move |cx, event| {
80 self.on_next_key(move |cx, event| {
81+ cx.count = count;
79 cx.editor.autoinfo = None;
82 cx.editor.autoinfo = None;
80 if let Some(func) = map.get(&event) {
83 if let Some(func) = map.get(&event) {
81 func(cx);
84 func(cx);
@@ -180,6 +183,9 @@ impl Command {
180 extend_till_prev_char,
183 extend_till_prev_char,
181 extend_prev_char,
184 extend_prev_char,
182 replace,
185 replace,
186+ switch_case,
187+ switch_to_uppercase,
188+ switch_to_lowercase,
183 page_up,
189 page_up,
184 page_down,
190 page_down,
185 half_page_up,
191 half_page_up,
@@ -778,6 +784,57 @@ fn replace(cx: &mut Context) {
778 })
784 })
779}
785}
780
786
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+
781fn scroll(cx: &mut Context, offset: usize, direction: Direction) {
838fn scroll(cx: &mut Context, offset: usize, direction: Direction) {
782 use Direction::*;
839 use Direction::*;
783 let (view, doc) = current!(cx.editor);
840 let (view, doc) = current!(cx.editor);
@@ -1194,34 +1251,45 @@ mod cmd {
1194 pub alias: Option<&'static str>,
1251 pub alias: Option<&'static str>,
1195 pub doc: &'static str,
1252 pub doc: &'static str,
1196 // params, flags, helper, completer
1253 // params, flags, helper, completer
1197− pub fun: fn(&mut compositor::Context, &[&str], PromptEvent),
1254+ pub fun: fn(&mut compositor::Context, &[&str], PromptEvent) -> anyhow::Result<()>,
1198 pub completer: Option<Completer>,
1255 pub completer: Option<Completer>,
1199 }
1256 }
1200
1257
1258+ fn quit(
1201− fn quit(cx: &mut compositor::Context, _args: &[&str], _event: PromptEvent) {
1259+ cx: &mut compositor::Context,
1260+ _args: &[&str],
1261+ _event: PromptEvent,
1262+ ) -> anyhow::Result<()> {
1202 // last view and we have unsaved changes
1263 // last view and we have unsaved changes
1203− if cx.editor.tree.views().count() == 1 && buffers_remaining_impl(cx.editor) {
1264+ if cx.editor.tree.views().count() == 1 {
1204− return;
1265+ buffers_remaining_impl(cx.editor)?
1205 }
1266 }
1267+
1206 cx.editor
1268 cx.editor
1207 .close(view!(cx.editor).id, /* close_buffer */ false);
1269 .close(view!(cx.editor).id, /* close_buffer */ false);
1270+
1271+ Ok(())
1208 }
1272 }
1209
1273
1274+ fn force_quit(
1210− fn force_quit(cx: &mut compositor::Context, _args: &[&str], _event: PromptEvent) {
1275+ cx: &mut compositor::Context,
1276+ _args: &[&str],
1277+ _event: PromptEvent,
1278+ ) -> anyhow::Result<()> {
1211 cx.editor
1279 cx.editor
1212 .close(view!(cx.editor).id, /* close_buffer */ false);
1280 .close(view!(cx.editor).id, /* close_buffer */ false);
1281+
1282+ Ok(())
1213 }
1283 }
1214
1284
1285+ fn open(
1215− fn open(cx: &mut compositor::Context, args: &[&str], _event: PromptEvent) {
1286+ cx: &mut compositor::Context,
1216− match args.get(0) {
1287+ args: &[&str],
1217− Some(path) => {
1288+ _event: PromptEvent,
1218− // TODO: handle error
1289+ ) -> anyhow::Result<()> {
1290+ let path = args.get(0).context("wrong argument count")?;
1219− let _ = cx.editor.open(path.into(), Action::Replace);
1291+ let _ = cx.editor.open(path.into(), Action::Replace)?;
1220− }
1292+ Ok(())
1221− None => {
1222− cx.editor.set_error("wrong argument count".to_string());
1223− }
1224− };
1225 }
1293 }
1226
1294
1227 fn write_impl<P: AsRef<Path>>(
1295 fn write_impl<P: AsRef<Path>>(
@@ -1232,12 +1300,10 @@ mod cmd {
1232 let (_, doc) = current!(cx.editor);
1300 let (_, doc) = current!(cx.editor);
1233
1301
1234 if let Some(path) = path {
1302 if let Some(path) = path {
1235− if let Err(err) = doc.set_path(path.as_ref()) {
1303+ doc.set_path(path.as_ref()).context("invalid filepath")?;
1236− return Err(anyhow!("invalid filepath: {}", err));
1237− };
1238 }
1304 }
1239 if doc.path().is_none() {
1305 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");
1241 }
1307 }
1242 let fmt = doc.auto_format().map(|fmt| {
1308 let fmt = doc.auto_format().map(|fmt| {
1243 let shared = fmt.shared();
1309 let shared = fmt.shared();
@@ -1253,21 +1319,33 @@ mod cmd {
1253 Ok(tokio::spawn(doc.format_and_save(fmt)))
1319 Ok(tokio::spawn(doc.format_and_save(fmt)))
1254 }
1320 }
1255
1321
1322+ fn write(
1256− fn write(cx: &mut compositor::Context, args: &[&str], _event: PromptEvent) {
1323+ cx: &mut compositor::Context,
1324+ args: &[&str],
1325+ _event: PromptEvent,
1326+ ) -> anyhow::Result<()> {
1257− match write_impl(cx, args.first()) {
1327+ let handle = write_impl(cx, args.first())?;
1258− Err(e) => cx.editor.set_error(e.to_string()),
1259− Ok(handle) => {
1260− cx.jobs
1328+ cx.jobs
1261− .add(Job::new(handle.unwrap_or_else(|e| Err(e.into()))).wait_before_exiting());
1329+ .add(Job::new(handle.unwrap_or_else(|e| Err(e.into()))).wait_before_exiting());
1262− }
1330+
1263− };
1331+ Ok(())
1264 }
1332 }
1265
1333
1334+ fn new_file(
1266− fn new_file(cx: &mut compositor::Context, _args: &[&str], _event: PromptEvent) {
1335+ cx: &mut compositor::Context,
1336+ _args: &[&str],
1337+ _event: PromptEvent,
1338+ ) -> anyhow::Result<()> {
1267 cx.editor.new_file(Action::Replace);
1339 cx.editor.new_file(Action::Replace);
1340+
1341+ Ok(())
1268 }
1342 }
1269
1343
1344+ fn format(
1270− fn format(cx: &mut compositor::Context, _args: &[&str], _event: PromptEvent) {
1345+ cx: &mut compositor::Context,
1346+ _args: &[&str],
1347+ _event: PromptEvent,
1348+ ) -> anyhow::Result<()> {
1271 let (_, doc) = current!(cx.editor);
1349 let (_, doc) = current!(cx.editor);
1272
1350
1273 if let Some(format) = doc.format() {
1351 if let Some(format) = doc.format() {
@@ -1275,9 +1353,14 @@ mod cmd {
1275 make_format_callback(doc.id(), doc.version(), Modified::LeaveModified, format);
1353 make_format_callback(doc.id(), doc.version(), Modified::LeaveModified, format);
1276 cx.jobs.callback(callback);
1354 cx.jobs.callback(callback);
1277 }
1355 }
1278− }
1279
1356
1357+ Ok(())
1358+ }
1359+ fn set_indent_style(
1280− fn set_indent_style(cx: &mut compositor::Context, args: &[&str], _event: PromptEvent) {
1360+ cx: &mut compositor::Context,
1361+ args: &[&str],
1362+ _event: PromptEvent,
1363+ ) -> anyhow::Result<()> {
1281 use IndentStyle::*;
1364 use IndentStyle::*;
1282
1365
1283 // If no argument, report current indent style.
1366 // If no argument, report current indent style.
@@ -1289,7 +1372,7 @@ mod cmd {
1289 Spaces(n) if (2..=8).contains(&n) => format!("{} spaces", n),
1372 Spaces(n) if (2..=8).contains(&n) => format!("{} spaces", n),
1290 _ => "error".into(), // Shouldn't happen.
1373 _ => "error".into(), // Shouldn't happen.
1291 });
1374 });
1292− return;
1375+ return Ok(());
1293 }
1376 }
1294
1377
1295 // Attempt to parse argument as an indent style.
1378 // Attempt to parse argument as an indent style.
@@ -1304,18 +1387,19 @@ mod cmd {
1304 _ => None,
1387 _ => None,
1305 };
1388 };
1306
1389
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],));
1390+ let style = style.context("invalid indent style")?;
1314− }
1391+ let doc = doc_mut!(cx.editor);
1392+ doc.indent_style = style;
1393+
1394+ Ok(())
1315 }
1395 }
1316
1396
1317 /// Sets or reports the current document's line ending setting.
1397 /// Sets or reports the current document's line ending setting.
1398+ fn set_line_ending(
1318− fn set_line_ending(cx: &mut compositor::Context, args: &[&str], _event: PromptEvent) {
1399+ cx: &mut compositor::Context,
1400+ args: &[&str],
1401+ _event: PromptEvent,
1402+ ) -> anyhow::Result<()> {
1319 use LineEnding::*;
1403 use LineEnding::*;
1320
1404
1321 // If no argument, report current line ending setting.
1405 // If no argument, report current line ending setting.
@@ -1331,7 +1415,8 @@ mod cmd {
1331 // These should never be a document's default line ending.
1415 // These should never be a document's default line ending.
1332 VT | LS | PS => "error".into(),
1416 VT | LS | PS => "error".into(),
1333 });
1417 });
1418+
1334− return;
1419+ return Ok(());
1335 }
1420 }
1336
1421
1337 // Attempt to parse argument as a line ending.
1422 // Attempt to parse argument as a line ending.
@@ -1345,72 +1430,65 @@ mod cmd {
1345 _ => None,
1430 _ => None,
1346 };
1431 };
1347
1432
1348− if let Some(le) = line_ending {
1433+ let line_ending = line_ending.context("invalid line ending")?;
1349− doc_mut!(cx.editor).line_ending = le;
1434+ doc_mut!(cx.editor).line_ending = line_ending;
1350− } else {
1435+ Ok(())
1351− // Invalid argument.
1352− cx.editor
1353− .set_error(format!("invalid line ending '{}'", args[0],));
1354− }
1355 }
1436 }
1356
1437
1438+ fn earlier(
1357− fn earlier(cx: &mut compositor::Context, args: &[&str], _event: PromptEvent) {
1439+ cx: &mut compositor::Context,
1440+ args: &[&str],
1441+ _event: PromptEvent,
1442+ ) -> anyhow::Result<()> {
1443+ let uk = args
1444+ .join(" ")
1358− let uk = match args.join(" ").parse::<helix_core::history::UndoKind>() {
1445+ .parse::<helix_core::history::UndoKind>()
1359− Ok(uk) => uk,
1446+ .map_err(|s| anyhow!(s))?;
1360− Err(msg) => {
1447+
1361− cx.editor.set_error(msg);
1362− return;
1363− }
1364− };
1365 let (view, doc) = current!(cx.editor);
1448 let (view, doc) = current!(cx.editor);
1366− doc.earlier(view.id, uk)
1449+ doc.earlier(view.id, uk);
1450+
1451+ Ok(())
1367 }
1452 }
1368
1453
1454+ fn later(
1369− fn later(cx: &mut compositor::Context, args: &[&str], _event: PromptEvent) {
1455+ cx: &mut compositor::Context,
1456+ args: &[&str],
1457+ _event: PromptEvent,
1458+ ) -> anyhow::Result<()> {
1459+ let uk = args
1460+ .join(" ")
1370− let uk = match args.join(" ").parse::<helix_core::history::UndoKind>() {
1461+ .parse::<helix_core::history::UndoKind>()
1371− Ok(uk) => uk,
1462+ .map_err(|s| anyhow!(s))?;
1372− Err(msg) => {
1373− cx.editor.set_error(msg);
1374− return;
1375− }
1376− };
1377 let (view, doc) = current!(cx.editor);
1463 let (view, doc) = current!(cx.editor);
1378− doc.later(view.id, uk)
1464+ doc.later(view.id, uk);
1465+
1466+ Ok(())
1379 }
1467 }
1380
1468
1469+ fn write_quit(
1381− fn write_quit(cx: &mut compositor::Context, args: &[&str], event: PromptEvent) {
1470+ cx: &mut compositor::Context,
1471+ args: &[&str],
1472+ event: PromptEvent,
1473+ ) -> anyhow::Result<()> {
1382− match write_impl(cx, args.first()) {
1474+ let handle = write_impl(cx, args.first())?;
1383− Ok(handle) => {
1384− if let Err(e) = helix_lsp::block_on(handle) {
1475+ let _ = helix_lsp::block_on(handle)?;
1385− cx.editor.set_error(e.to_string());
1386− } else {
1387− quit(cx, &[], event);
1476+ quit(cx, &[], event)
1388− }
1389− }
1390− Err(e) => {
1391− cx.editor.set_error(e.to_string());
1392− }
1393− }
1394 }
1477 }
1395
1478
1479+ fn force_write_quit(
1396− fn force_write_quit(cx: &mut compositor::Context, args: &[&str], event: PromptEvent) {
1480+ cx: &mut compositor::Context,
1481+ args: &[&str],
1482+ event: PromptEvent,
1483+ ) -> anyhow::Result<()> {
1397− match write_impl(cx, args.first()) {
1484+ let handle = write_impl(cx, args.first())?;
1398− Ok(handle) => {
1399− if let Err(e) = helix_lsp::block_on(handle) {
1485+ let _ = helix_lsp::block_on(handle)?;
1400− cx.editor.set_error(e.to_string());
1401− } else {
1402− force_quit(cx, &[], event);
1486+ force_quit(cx, &[], event)
1403− }
1404− }
1405− Err(e) => {
1406− cx.editor.set_error(e.to_string());
1407− }
1408− }
1409 }
1487 }
1410
1488
1411− /// Returns `true` if there are modified buffers remaining and sets editor error,
1489+ /// Results an error if there are modified buffers remaining and sets editor error,
1412− /// otherwise returns `false`
1490+ /// otherwise returns `Ok(())`
1413− fn buffers_remaining_impl(editor: &mut Editor) -> bool {
1491+ fn buffers_remaining_impl(editor: &mut Editor) -> anyhow::Result<()> {
1414 let modified: Vec<_> = editor
1492 let modified: Vec<_> = editor
1415 .documents()
1493 .documents()
1416 .filter(|doc| doc.is_modified())
1494 .filter(|doc| doc.is_modified())
@@ -1421,16 +1499,13 @@ mod cmd {
1421 })
1499 })
1422 .collect();
1500 .collect();
1423 if !modified.is_empty() {
1501 if !modified.is_empty() {
1424− let err = format!(
1502+ bail!(
1425 "{} unsaved buffer(s) remaining: {:?}",
1503 "{} unsaved buffer(s) remaining: {:?}",
1426 modified.len(),
1504 modified.len(),
1427 modified
1505 modified
1428 );
1506 );
1429− editor.set_error(err);
1430− true
1431− } else {
1432− false
1433 }
1507 }
1508+ Ok(())
1434 }
1509 }
1435
1510
1436 fn write_all_impl(
1511 fn write_all_impl(
@@ -1439,7 +1514,7 @@ mod cmd {
1439 _event: PromptEvent,
1514 _event: PromptEvent,
1440 quit: bool,
1515 quit: bool,
1441 force: bool,
1516 force: bool,
1442− ) {
1517+ ) -> anyhow::Result<()> {
1443 let mut errors = String::new();
1518 let mut errors = String::new();
1444
1519
1445 // save all documents
1520 // save all documents
@@ -1452,11 +1527,10 @@ mod cmd {
1452 // TODO: handle error.
1527 // TODO: handle error.
1453 let _ = helix_lsp::block_on(tokio::spawn(doc.save()));
1528 let _ = helix_lsp::block_on(tokio::spawn(doc.save()));
1454 }
1529 }
1455− editor.set_error(errors);
1456
1530
1457 if quit {
1531 if quit {
1532+ if !force {
1458− if !force && buffers_remaining_impl(editor) {
1533+ buffers_remaining_impl(editor)?;
1459− return;
1460 }
1534 }
1461
1535
1462 // close all views
1536 // close all views
@@ -1465,23 +1539,42 @@ mod cmd {
1465 editor.close(view_id, false);
1539 editor.close(view_id, false);
1466 }
1540 }
1467 }
1541 }
1542+
1543+ bail!(errors)
1468 }
1544 }
1469
1545
1546+ fn write_all(
1470− fn write_all(cx: &mut compositor::Context, args: &[&str], event: PromptEvent) {
1547+ cx: &mut compositor::Context,
1548+ args: &[&str],
1549+ event: PromptEvent,
1550+ ) -> anyhow::Result<()> {
1471 write_all_impl(&mut cx.editor, args, event, false, false)
1551 write_all_impl(&mut cx.editor, args, event, false, false)
1472 }
1552 }
1473
1553
1554+ fn write_all_quit(
1474− fn write_all_quit(cx: &mut compositor::Context, args: &[&str], event: PromptEvent) {
1555+ cx: &mut compositor::Context,
1556+ args: &[&str],
1557+ event: PromptEvent,
1558+ ) -> anyhow::Result<()> {
1475 write_all_impl(&mut cx.editor, args, event, true, false)
1559 write_all_impl(&mut cx.editor, args, event, true, false)
1476 }
1560 }
1477
1561
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<()> {
1479 write_all_impl(&mut cx.editor, args, event, true, true)
1567 write_all_impl(&mut cx.editor, args, event, true, true)
1480 }
1568 }
1481
1569
1570+ fn quit_all_impl(
1571+ editor: &mut Editor,
1572+ _args: &[&str],
1482− fn quit_all_impl(editor: &mut Editor, _args: &[&str], _event: PromptEvent, force: bool) {
1573+ _event: PromptEvent,
1574+ force: bool,
1575+ ) -> anyhow::Result<()> {
1576+ if !force {
1483− if !force && buffers_remaining_impl(editor) {
1577+ buffers_remaining_impl(editor)?;
1484− return;
1485 }
1578 }
1486
1579
1487 // close all views
1580 // close all views
@@ -1489,57 +1582,77 @@ mod cmd {
1489 for view_id in views {
1582 for view_id in views {
1490 editor.close(view_id, false);
1583 editor.close(view_id, false);
1491 }
1584 }
1585+
1586+ Ok(())
1492 }
1587 }
1493
1588
1589+ fn quit_all(
1494− fn quit_all(cx: &mut compositor::Context, args: &[&str], event: PromptEvent) {
1590+ cx: &mut compositor::Context,
1591+ args: &[&str],
1592+ event: PromptEvent,
1593+ ) -> anyhow::Result<()> {
1495 quit_all_impl(&mut cx.editor, args, event, false)
1594 quit_all_impl(&mut cx.editor, args, event, false)
1496 }
1595 }
1497
1596
1597+ fn force_quit_all(
1498− fn force_quit_all(cx: &mut compositor::Context, args: &[&str], event: PromptEvent) {
1598+ cx: &mut compositor::Context,
1599+ args: &[&str],
1600+ event: PromptEvent,
1601+ ) -> anyhow::Result<()> {
1499 quit_all_impl(&mut cx.editor, args, event, true)
1602 quit_all_impl(&mut cx.editor, args, event, true)
1500 }
1603 }
1501
1604
1605+ fn theme(
1502− fn theme(cx: &mut compositor::Context, args: &[&str], _event: PromptEvent) {
1606+ cx: &mut compositor::Context,
1607+ args: &[&str],
1608+ _event: PromptEvent,
1609+ ) -> anyhow::Result<()> {
1503− let theme = if let Some(theme) = args.first() {
1610+ let theme = args.first().context("theme not provided")?;
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);
1611+ cx.editor.set_theme_from_name(theme)
1511 }
1612 }
1512
1613
1513 fn yank_main_selection_to_clipboard(
1614 fn yank_main_selection_to_clipboard(
1514 cx: &mut compositor::Context,
1615 cx: &mut compositor::Context,
1515 _args: &[&str],
1616 _args: &[&str],
1516 _event: PromptEvent,
1617 _event: PromptEvent,
1517− ) {
1618+ ) -> anyhow::Result<()> {
1518− yank_main_selection_to_clipboard_impl(&mut cx.editor);
1619+ yank_main_selection_to_clipboard_impl(&mut cx.editor)
1519 }
1620 }
1520
1621
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<()> {
1522 let (_, doc) = current!(cx.editor);
1627 let (_, doc) = current!(cx.editor);
1523 let separator = args
1628 let separator = args
1524 .first()
1629 .first()
1525 .copied()
1630 .copied()
1526 .unwrap_or_else(|| doc.line_ending.as_str());
1631 .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)
1528 }
1633 }
1529
1634
1530− fn paste_clipboard_after(cx: &mut compositor::Context, _args: &[&str], _event: PromptEvent) {
1635+ fn paste_clipboard_after(
1636+ cx: &mut compositor::Context,
1637+ _args: &[&str],
1638+ _event: PromptEvent,
1639+ ) -> anyhow::Result<()> {
1531− paste_clipboard_impl(&mut cx.editor, Paste::After);
1640+ paste_clipboard_impl(&mut cx.editor, Paste::After)
1532 }
1641 }
1533
1642
1534− fn paste_clipboard_before(cx: &mut compositor::Context, _args: &[&str], _event: PromptEvent) {
1643+ fn paste_clipboard_before(
1644+ cx: &mut compositor::Context,
1645+ _args: &[&str],
1646+ _event: PromptEvent,
1647+ ) -> anyhow::Result<()> {
1535− paste_clipboard_impl(&mut cx.editor, Paste::After);
1648+ paste_clipboard_impl(&mut cx.editor, Paste::After)
1536 }
1649 }
1537
1650
1538 fn replace_selections_with_clipboard(
1651 fn replace_selections_with_clipboard(
1539 cx: &mut compositor::Context,
1652 cx: &mut compositor::Context,
1540 _args: &[&str],
1653 _args: &[&str],
1541 _event: PromptEvent,
1654 _event: PromptEvent,
1542− ) {
1655+ ) -> anyhow::Result<()> {
1543 let (view, doc) = current!(cx.editor);
1656 let (view, doc) = current!(cx.editor);
1544
1657
1545 match cx.editor.clipboard_provider.get_contents() {
1658 match cx.editor.clipboard_provider.get_contents() {
@@ -1555,71 +1668,76 @@ mod cmd {
1555
1668
1556 doc.apply(&transaction, view.id);
1669 doc.apply(&transaction, view.id);
1557 doc.append_changes_to_history(view.id);
1670 doc.append_changes_to_history(view.id);
1671+ Ok(())
1558 }
1672 }
1559− Err(e) => log::error!("Couldn't get system clipboard contents: {:?}", e),
1673+ Err(e) => Err(e.context("Couldn't get system clipboard contents")),
1560 }
1674 }
1561 }
1675 }
1562
1676
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<()> {
1564 cx.editor
1682 cx.editor
1565 .set_status(cx.editor.clipboard_provider.name().into());
1683 .set_status(cx.editor.clipboard_provider.name().into());
1684+ Ok(())
1566 }
1685 }
1567
1686
1568− fn change_current_directory(cx: &mut compositor::Context, args: &[&str], _event: PromptEvent) {
1687+ fn change_current_directory(
1569− let dir = match args.first() {
1688+ cx: &mut compositor::Context,
1570− Some(dir) => dir,
1689+ args: &[&str],
1571− None => {
1690+ _event: PromptEvent,
1691+ ) -> anyhow::Result<()> {
1572− cx.editor.set_error("target directory not provided".into());
1692+ let dir = args.first().context("target directory not provided")?;
1573− return;
1574− }
1575− };
1576
1693
1577 if let Err(e) = std::env::set_current_dir(dir) {
1694 if let Err(e) = std::env::set_current_dir(dir) {
1578− cx.editor.set_error(format!(
1579− "Couldn't change the current working directory: {:?}",
1695+ bail!("Couldn't change the current working directory: {:?}", e);
1580− e
1581− ));
1582− return;
1583 }
1696 }
1584
1697
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)),
1698+ let cwd = std::env::current_dir().context("Couldn't get the new working directory")?;
1593− }
1699+ cx.editor.set_status(format!(
1700+ "Current working directory is now {}",
1701+ cwd.display()
1702+ ));
1703+ Ok(())
1594 }
1704 }
1595
1705
1596− fn show_current_directory(cx: &mut compositor::Context, _args: &[&str], _event: PromptEvent) {
1706+ fn show_current_directory(
1707+ cx: &mut compositor::Context,
1708+ _args: &[&str],
1709+ _event: PromptEvent,
1710+ ) -> anyhow::Result<()> {
1597− match std::env::current_dir() {
1711+ let cwd = std::env::current_dir().context("Couldn't get the new working directory")?;
1598− Ok(cwd) => cx
1712+ cx.editor
1599− .editor
1713+ .set_status(format!("Current working directory is {}", cwd.display()));
1600− .set_status(format!("Current working directory is {}", cwd.display())),
1714+ Ok(())
1601− Err(e) => cx
1602− .editor
1603− .set_error(format!("Couldn't get the current working directory: {}", e)),
1604− }
1605 }
1715 }
1606
1716
1607 /// Sets the [`Document`]'s encoding..
1717 /// Sets the [`Document`]'s encoding..
1718+ fn set_encoding(
1608− fn set_encoding(cx: &mut compositor::Context, args: &[&str], _: PromptEvent) {
1719+ cx: &mut compositor::Context,
1720+ args: &[&str],
1721+ _event: PromptEvent,
1722+ ) -> anyhow::Result<()> {
1609 let (_, doc) = current!(cx.editor);
1723 let (_, doc) = current!(cx.editor);
1610 if let Some(label) = args.first() {
1724 if let Some(label) = args.first() {
1611 doc.set_encoding(label)
1725 doc.set_encoding(label)
1612− .unwrap_or_else(|e| cx.editor.set_error(e.to_string()));
1613 } else {
1726 } else {
1614 let encoding = doc.encoding().name().to_string();
1727 let encoding = doc.encoding().name().to_string();
1615− cx.editor.set_status(encoding)
1728+ cx.editor.set_status(encoding);
1729+ Ok(())
1616 }
1730 }
1617 }
1731 }
1618
1732
1619 /// Reload the [`Document`] from its source file.
1733 /// Reload the [`Document`] from its source file.
1734+ fn reload(
1620− fn reload(cx: &mut compositor::Context, _args: &[&str], _: PromptEvent) {
1735+ cx: &mut compositor::Context,
1736+ _args: &[&str],
1737+ _event: PromptEvent,
1738+ ) -> anyhow::Result<()> {
1621 let (view, doc) = current!(cx.editor);
1739 let (view, doc) = current!(cx.editor);
1622− doc.reload(view.id).unwrap();
1740+ doc.reload(view.id)
1623 }
1741 }
1624
1742
1625 pub const TYPABLE_COMMAND_LIST: &[TypableCommand] = &[
1743 pub const TYPABLE_COMMAND_LIST: &[TypableCommand] = &[
@@ -1884,7 +2002,9 @@ fn command_mode(cx: &mut Context) {
1884 }
2002 }
1885
2003
1886 if let Some(cmd) = cmd::COMMANDS.get(parts[0]) {
2004 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+ }
1888 } else {
2008 } else {
1889 cx.editor
2009 cx.editor
1890 .set_error(format!("no such command: '{}'", parts[0]));
2010 .set_error(format!("no such command: '{}'", parts[0]));
@@ -2816,7 +2936,7 @@ fn yank(cx: &mut Context) {
2816 cx.editor.set_status(msg)
2936 cx.editor.set_status(msg)
2817}
2937}
2818
2938
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<()> {
2820 let (view, doc) = current!(editor);
2940 let (view, doc) = current!(editor);
2821
2941
2822 let values: Vec<String> = doc
2942 let values: Vec<String> = doc
@@ -2832,19 +2952,22 @@ fn yank_joined_to_clipboard_impl(editor: &mut Editor, separator: &str) {
2832
2952
2833 let joined = values.join(separator);
2953 let joined = values.join(separator);
2834
2954
2955+ editor
2835− if let Err(e) = editor.clipboard_provider.set_contents(joined) {
2956+ .clipboard_provider
2957+ .set_contents(joined)
2836− log::error!("Couldn't set system clipboard content: {:?}", e);
2958+ .context("Couldn't set system clipboard content")?;
2837− }
2838
2959
2839 editor.set_status(msg);
2960 editor.set_status(msg);
2961+
2962+ Ok(())
2840}
2963}
2841
2964
2842fn yank_joined_to_clipboard(cx: &mut Context) {
2965fn yank_joined_to_clipboard(cx: &mut Context) {
2843 let line_ending = current!(cx.editor).1.line_ending;
2966 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());
2845}
2968}
2846
2969
2847−fn yank_main_selection_to_clipboard_impl(editor: &mut Editor) {
2970+fn yank_main_selection_to_clipboard_impl(editor: &mut Editor) -> anyhow::Result<()> {
2848 let (view, doc) = current!(editor);
2971 let (view, doc) = current!(editor);
2849
2972
2850 let value = doc
2973 let value = doc
@@ -2853,14 +2976,15 @@ fn yank_main_selection_to_clipboard_impl(editor: &mut Editor) {
2853 .fragment(doc.text().slice(..));
2976 .fragment(doc.text().slice(..));
2854
2977
2855 if let Err(e) = editor.clipboard_provider.set_contents(value.into_owned()) {
2978 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);
2857 }
2980 }
2858
2981
2859 editor.set_status("yanked main selection to system clipboard".to_owned());
2982 editor.set_status("yanked main selection to system clipboard".to_owned());
2983+ Ok(())
2860}
2984}
2861
2985
2862fn yank_main_selection_to_clipboard(cx: &mut Context) {
2986fn 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);
2864}
2988}
2865
2989
2866#[derive(Copy, Clone)]
2990#[derive(Copy, Clone)]
@@ -2909,7 +3033,7 @@ fn paste_impl(
2909 Some(transaction)
3033 Some(transaction)
2910}
3034}
2911
3035
2912−fn paste_clipboard_impl(editor: &mut Editor, action: Paste) {
3036+fn paste_clipboard_impl(editor: &mut Editor, action: Paste) -> anyhow::Result<()> {
2913 let (view, doc) = current!(editor);
3037 let (view, doc) = current!(editor);
2914
3038
2915 match editor
3039 match editor
@@ -2920,18 +3044,19 @@ fn paste_clipboard_impl(editor: &mut Editor, action: Paste) {
2920 Ok(Some(transaction)) => {
3044 Ok(Some(transaction)) => {
2921 doc.apply(&transaction, view.id);
3045 doc.apply(&transaction, view.id);
2922 doc.append_changes_to_history(view.id);
3046 doc.append_changes_to_history(view.id);
3047+ Ok(())
2923 }
3048 }
2924− Ok(None) => {}
3049+ Ok(None) => Ok(()),
2925− Err(e) => log::error!("Couldn't get system clipboard contents: {:?}", e),
3050+ Err(e) => Err(e.context("Couldn't get system clipboard contents")),
2926 }
3051 }
2927}
3052}
2928
3053
2929fn paste_clipboard_after(cx: &mut Context) {
3054fn 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);
2931}
3056}
2932
3057
2933fn paste_clipboard_before(cx: &mut Context) {
3058fn 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);
2935}
3060}
2936
3061
2937fn replace_with_yanked(cx: &mut Context) {
3062fn replace_with_yanked(cx: &mut Context) {
@@ -2959,7 +3084,7 @@ fn replace_with_yanked(cx: &mut Context) {
2959 }
3084 }
2960}
3085}
2961
3086
2962−fn replace_selections_with_clipboard_impl(editor: &mut Editor) {
3087+fn replace_selections_with_clipboard_impl(editor: &mut Editor) -> anyhow::Result<()> {
2963 let (view, doc) = current!(editor);
3088 let (view, doc) = current!(editor);
2964
3089
2965 match editor.clipboard_provider.get_contents() {
3090 match editor.clipboard_provider.get_contents() {
@@ -2974,13 +3099,14 @@ fn replace_selections_with_clipboard_impl(editor: &mut Editor) {
2974
3099
2975 doc.apply(&transaction, view.id);
3100 doc.apply(&transaction, view.id);
2976 doc.append_changes_to_history(view.id);
3101 doc.append_changes_to_history(view.id);
3102+ Ok(())
2977 }
3103 }
2978− Err(e) => log::error!("Couldn't get system clipboard contents: {:?}", e),
3104+ Err(e) => Err(e.context("Couldn't get system clipboard contents")),
2979 }
3105 }
2980}
3106}
2981
3107
2982fn replace_selections_with_clipboard(cx: &mut Context) {
3108fn 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);
2984}
3110}
2985
3111
2986fn paste_after(cx: &mut Context) {
3112fn paste_after(cx: &mut Context) {
@@ -3405,24 +3531,6 @@ fn jump_backward(cx: &mut Context) {
3405 };
3531 };
3406}
3532}
3407
3533
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−
3426fn rotate_view(cx: &mut Context) {
3534fn rotate_view(cx: &mut Context) {
3427 cx.editor.focus_next()
3535 cx.editor.focus_next()
3428}
3536}
@@ -3468,105 +3576,46 @@ fn select_register(cx: &mut Context) {
3468 })
3576 })
3469}
3577}
3470
3578
3471−fn view_mode(cx: &mut Context) {
3579+fn align_view_top(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);
3580+ let (view, doc) = current!(cx.editor);
3581+ align_view(doc, view, Align::Top);
3582+}
3488
3583
3489− align_view(doc, view, match ch {
3584+fn align_view_center(cx: &mut Context) {
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);
3585+ let (view, doc) = current!(cx.editor);
3498− let pos = doc.selection(view.id).cursor();
3586+ align_view(doc, view, Align::Center);
3499− let pos = coords_at_pos(doc.text().slice(..), pos);
3587+}
3500
3588
3501− const OFFSET: usize = 7; // gutters
3589+fn align_view_bottom(cx: &mut Context) {
3502− view.first_col = pos.col.saturating_sub(((view.area.width as usize).saturating_sub(OFFSET)) / 2);
3590+ let (view, doc) = current!(cx.editor);
3503− },
3591+ align_view(doc, view, Align::Bottom);
3504− 'h' => (),
3505− 'j' => scroll(cx, 1, Direction::Forward),
3506− 'k' => scroll(cx, 1, Direction::Backward),
3507− 'l' => (),
3508− _ => (),
3509− }
3510− }
3511− })
3512}
3592}
3513
3593
3514−fn left_bracket_mode(cx: &mut Context) {
3594+fn align_view_middle(cx: &mut Context) {
3515− cx.on_next_key(move |cx, event| {
3595+ let (view, doc) = current!(cx.editor);
3516− if let KeyEvent {
3596+ let pos = doc.selection(view.id).cursor();
3517− code: KeyCode::Char(ch),
3597+ let pos = coords_at_pos(doc.text().slice(..), pos);
3518− ..
3598+
3519− } = event
3599+ const OFFSET: usize = 7; // gutters
3520− {
3600+ view.first_col = pos
3521− match ch {
3601+ .col
3522− 'd' => goto_prev_diag(cx),
3602+ .saturating_sub(((view.area.width as usize).saturating_sub(OFFSET)) / 2);
3523− 'D' => goto_first_diag(cx),
3524− _ => (),
3525− }
3526− }
3527− })
3528}
3603}
3529
3604
3530−fn right_bracket_mode(cx: &mut Context) {
3605+fn scroll_up(cx: &mut Context) {
3531− cx.on_next_key(move |cx, event| {
3606+ scroll(cx, cx.count(), Direction::Backward);
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− })
3544}
3607}
3545
3608
3546−use helix_core::surround;
3609+fn scroll_down(cx: &mut Context) {
3547−use helix_core::textobject;
3610+ scroll(cx, cx.count(), Direction::Forward);
3611+}
3548
3612
3549−fn match_mode(cx: &mut Context) {
3613+fn select_textobject_around(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),
3614+ select_textobject(cx, textobject::TextObject::Around);
3615+}
3616+
3617+fn select_textobject_inner(cx: &mut Context) {
3565− 'i' => select_textobject(cx, textobject::TextObject::Inside),
3618+ select_textobject(cx, textobject::TextObject::Inside);
3566− _ => (),
3567− }
3568− }
3569− })
3570}
3619}
3571
3620
3572fn select_textobject(cx: &mut Context, objtype: textobject::TextObject) {
3621fn select_textobject(cx: &mut Context, objtype: textobject::TextObject) {
@@ -3784,7 +3833,7 @@ mode_info! {
3784}
3833}
3785
3834
3786mode_info! {
3835mode_info! {
3787− /// goto mode
3836+ /// goto
3788 ///
3837 ///
3789 /// When specified with a count, it will go to that line without entering the mode.
3838 /// When specified with a count, it will go to that line without entering the mode.
3790 goto_mode, GOTO_MODE, goto_prehook,
3839 goto_mode, GOTO_MODE, goto_prehook,
@@ -3815,3 +3864,68 @@ mode_info! {
3815 /// last accessed file
3864 /// last accessed file
3816 "a" => goto_last_accessed_file,
3865 "a" => goto_last_accessed_file,
3817}
3866}
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 {
82 key!('r') => Command::replace,
82 key!('r') => Command::replace,
83 key!('R') => Command::replace_with_yanked,
83 key!('R') => Command::replace_with_yanked,
84
84
85+ key!('~') => Command::switch_case,
86+ alt!('`') => Command::switch_to_uppercase,
87+ key!('`') => Command::switch_to_lowercase,
88+
85 key!(Home) => Command::goto_line_start,
89 key!(Home) => Command::goto_line_start,
86 key!(End) => Command::goto_line_end,
90 key!(End) => Command::goto_line_end,
87
91
@@ -120,7 +124,6 @@ impl Default for Keymaps {
120 alt!(';') => Command::flip_selections,
124 alt!(';') => Command::flip_selections,
121 key!('%') => Command::select_all,
125 key!('%') => Command::select_all,
122 key!('x') => Command::extend_line,
126 key!('x') => Command::extend_line,
123− key!('x') => Command::extend_line,
124 key!('X') => Command::extend_to_line_bounds,
127 key!('X') => Command::extend_to_line_bounds,
125 // crop_to_whole_line
128 // crop_to_whole_line
126
129
helix-term/src/ui/editor.rs+33−3
@@ -64,6 +64,7 @@ impl EditorView {
64 surface: &mut Surface,
64 surface: &mut Surface,
65 theme: &Theme,
65 theme: &Theme,
66 is_focused: bool,
66 is_focused: bool,
67+ loader: &syntax::Loader,
67 ) {
68 ) {
68 let area = Rect::new(
69 let area = Rect::new(
69 view.area.x + OFFSET,
70 view.area.x + OFFSET,
@@ -72,7 +73,7 @@ impl EditorView {
72 view.area.height.saturating_sub(1),
73 view.area.height.saturating_sub(1),
73 ); // - 1 for statusline
74 ); // - 1 for statusline
74
75
75− self.render_buffer(doc, view, area, surface, theme, is_focused);
76+ self.render_buffer(doc, view, area, surface, theme, is_focused, loader);
76
77
77 // if we're not at the edge of the screen, draw a right border
78 // if we're not at the edge of the screen, draw a right border
78 if viewport.right() != view.area.right() {
79 if viewport.right() != view.area.right() {
@@ -98,6 +99,7 @@ impl EditorView {
98 self.render_statusline(doc, view, area, surface, theme, is_focused);
99 self.render_statusline(doc, view, area, surface, theme, is_focused);
99 }
100 }
100
101
102+ #[allow(clippy::too_many_arguments)]
101 pub fn render_buffer(
103 pub fn render_buffer(
102 &self,
104 &self,
103 doc: &Document,
105 doc: &Document,
@@ -106,6 +108,7 @@ impl EditorView {
106 surface: &mut Surface,
108 surface: &mut Surface,
107 theme: &Theme,
109 theme: &Theme,
108 is_focused: bool,
110 is_focused: bool,
111+ loader: &syntax::Loader,
109 ) {
112 ) {
110 let text = doc.text().slice(..);
113 let text = doc.text().slice(..);
111
114
@@ -122,8 +125,26 @@ impl EditorView {
122 // TODO: range doesn't actually restrict source, just highlight range
125 // TODO: range doesn't actually restrict source, just highlight range
123 let highlights: Vec<_> = match doc.syntax() {
126 let highlights: Vec<_> = match doc.syntax() {
124 Some(syntax) => {
127 Some(syntax) => {
128+ let scopes = theme.scopes();
125 syntax
129 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+ })
127 .collect() // TODO: we collect here to avoid holding the lock, fix later
148 .collect() // TODO: we collect here to avoid holding the lock, fix later
128 }
149 }
129 None => vec![Ok(HighlightEvent::Source {
150 None => vec![Ok(HighlightEvent::Source {
@@ -735,7 +756,16 @@ impl Component for EditorView {
735
756
736 for (view, is_focused) in cx.editor.tree.views() {
757 for (view, is_focused) in cx.editor.tree.views() {
737 let doc = cx.editor.document(view.doc).unwrap();
758 let doc = cx.editor.document(view.doc).unwrap();
759+ let loader = &cx.editor.syn_loader;
738− self.render_view(doc, view, area, surface, &cx.editor.theme, is_focused);
760+ self.render_view(
761+ doc,
762+ view,
763+ area,
764+ surface,
765+ &cx.editor.theme,
766+ is_focused,
767+ loader,
768+ );
739 }
769 }
740
770
741 if let Some(info) = std::mem::take(&mut cx.editor.autoinfo) {
771 if let Some(info) = std::mem::take(&mut cx.editor.autoinfo) {
helix-tui/Cargo.toml+1−1
@@ -18,7 +18,7 @@ default = ["crossterm"]
18[dependencies]
18[dependencies]
19bitflags = "1.0"
19bitflags = "1.0"
20cassowary = "0.3"
20cassowary = "0.3"
21−unicode-segmentation = "1.2"
21+unicode-segmentation = "1.8"
22crossterm = { version = "0.20", optional = true }
22crossterm = { version = "0.20", optional = true }
23serde = { version = "1", "optional" = true, features = ["derive"]}
23serde = { version = "1", "optional" = true, features = ["derive"]}
24helix-view = { version = "0.3", path = "../helix-view", features = ["term"] }
24helix-view = { version = "0.3", path = "../helix-view", features = ["term"] }
helix-view/src/document.rs+8−6
@@ -456,14 +456,16 @@ impl Document {
456 theme: Option<&Theme>,
456 theme: Option<&Theme>,
457 config_loader: Option<&syntax::Loader>,
457 config_loader: Option<&syntax::Loader>,
458 ) -> Result<Self, Error> {
458 ) -> Result<Self, Error> {
459− if !path.exists() {
459+ let (mut rope, encoding) = if path.exists() {
460− return Ok(Self::default());
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)
461− }
466+ };
462
467
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)?;
465 let line_ending = with_line_ending(&mut rope);
468 let line_ending = with_line_ending(&mut rope);
466−
467 let mut doc = Self::from(rope, Some(encoding));
469 let mut doc = Self::from(rope, Some(encoding));
468
470
469 // set the path and try detecting the language
471 // set the path and try detecting the language
helix-view/src/editor.rs+11−9
@@ -39,6 +39,7 @@ pub struct Editor {
39
39
40#[derive(Debug, Copy, Clone)]
40#[derive(Debug, Copy, Clone)]
41pub enum Action {
41pub enum Action {
42+ Load,
42 Replace,
43 Replace,
43 HorizontalSplit,
44 HorizontalSplit,
44 VerticalSplit,
45 VerticalSplit,
@@ -97,16 +98,14 @@ impl Editor {
97 self._refresh();
98 self._refresh();
98 }
99 }
99
100
100− pub fn set_theme_from_name(&mut self, theme: &str) {
101+ pub fn set_theme_from_name(&mut self, theme: &str) -> anyhow::Result<()> {
102+ use anyhow::Context;
103+ let theme = self
104+ .theme_loader
101− let theme = match self.theme_loader.load(theme.as_ref()) {
105+ .load(theme.as_ref())
102− Ok(theme) => theme,
103− Err(e) => {
104− log::warn!("failed setting theme `{}` - {}", theme, e);
106+ .with_context(|| format!("failed setting theme `{}`", theme))?;
105− return;
106− }
107− };
108−
109 self.set_theme(theme);
107 self.set_theme(theme);
108+ Ok(())
110 }
109 }
111
110
112 fn _refresh(&mut self) {
111 fn _refresh(&mut self) {
@@ -153,6 +152,9 @@ impl Editor {
153
152
154 return;
153 return;
155 }
154 }
155+ Action::Load => {
156+ return;
157+ }
156 Action::HorizontalSplit => {
158 Action::HorizontalSplit => {
157 let view = View::new(id);
159 let view = View::new(id);
158 let view_id = self.tree.split(view, Layout::Horizontal);
160 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 @@
54"." @punctuation.delimiter
54"." @punctuation.delimiter
55";" @punctuation.delimiter
55";" @punctuation.delimiter
56
56
57+(enumerator) @type.enum.variant
58+
57(string_literal) @string
59(string_literal) @string
58(system_lib_string) @string
60(system_lib_string) @string
59
61
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+; -------
2
7
3
8
4−; Assume all-caps names are constants
5−((identifier) @constant
6− (#match? @constant "^[A-Z][A-Z\\d_]+$'"))
7
9
8−; Assume other uppercase names are enum constructors
10+; -------
9−((identifier) @constructor
11+; Types
10− (#match? @constructor "^[A-Z]"))
12+; -------
11
13
12−; Assume that uppercase names in paths are types
14+; ---
13−(mod_item
15+; Primitives
14− name: (identifier) @namespace)
16+; ---
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))
25
17
26−((scoped_identifier
18+(escape_sequence) @escape
27− path: (identifier) @type)
19+(primitive_type) @type.builtin
28− (#match? @type "^[A-Z]"))
20+(boolean_literal) @constant.builtin
29−((scoped_identifier
21+[
30− path: (scoped_identifier
22+ (integer_literal)
31− name: (identifier) @type))
23+ (float_literal)
32− (#match? @type "^[A-Z]"))
24+] @number
25+[
26+ (char_literal)
27+ (string_literal)
28+ (raw_string_literal)
29+] @string
30+[
31+ (line_comment)
32+ (block_comment)
33+] @comment
33
34
34−; Namespaces
35+; ---
36+; Extraneous
37+; ---
35
38
36−(crate) @namespace
39+(self) @variable.builtin
37−(extern_crate_declaration
40+(enum_variant (identifier) @type.enum.variant)
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 . (_)))
46
41
47−; Function calls
42+(field_initializer
43+ (field_identifier) @property)
44+(shorthand_field_initializer) @variable
45+(shorthand_field_identifier) @variable
48
46
49−(call_expression
47+(lifetime
50− function: (identifier) @function)
48+ "'" @label
51−(call_expression
49+ (identifier) @label)
52− function: (field_expression
50+(loop_label
53− field: (field_identifier) @function.method))
54−(call_expression
55− function: (scoped_identifier
56− "::"
57− name: (identifier) @function))
51+ (identifier) @type)
58
52
59−(generic_function
53+; ---
60− function: (identifier) @function)
54+; Punctuation
61−(generic_function
55+; ---
62− function: (scoped_identifier
63− name: (identifier) @function))
64−(generic_function
65− function: (field_expression
66− field: (field_identifier) @function.method))
67
56
68−(macro_invocation
57+[
69− macro: (identifier) @function.macro
58+ "::"
70− "!" @function.macro)
59+ "."
71−(macro_invocation
60+ ";"
72− macro: (scoped_identifier
61+] @punctuation.delimiter
73− (identifier) @function.macro .))
74
62
75−; (metavariable) @variable
63+[
76−(metavariable) @function.macro
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)
77
79
78−"$" @function.macro
80+; ---
81+; Parameters
82+; ---
79
83
80−; Function definitions
84+(parameter
85+ pattern: (identifier) @variable.parameter)
86+(closure_parameters
87+ (identifier) @variable.parameter)
81
88
82−(function_item (identifier) @function)
83−(function_signature_item (identifier) @function)
84
89
85−; Other identifiers
86
90
87−(type_identifier) @type
91+; -------
88−(primitive_type) @type.builtin
92+; Keywords
89−(field_identifier) @property
93+; -------
90
94
91−(line_comment) @comment
95+(for_expression
92−(block_comment) @comment
96+ "for" @keyword.control)
97+((identifier) @keyword.control
98+ (#match? @keyword.control "^yield$"))
99+[
100+ "while"
101+ "loop"
102+ "in"
103+ "break"
104+ "continue"
93
105
94−"(" @punctuation.bracket
106+ "match"
95−")" @punctuation.bracket
107+ "if"
96−"[" @punctuation.bracket
108+ "else"
97−"]" @punctuation.bracket
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
98
146
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
149(mutable_specifier) @keyword.mut
147(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
155
148
156−(self) @variable.builtin
157
149
158−[
159−(char_literal)
160−(string_literal)
161−(raw_string_literal)
162−] @string
163
150
164−(boolean_literal) @constant.builtin
151+; -------
165−(integer_literal) @number
152+; Guess Other Types
166−(float_literal) @number
153+; -------
167
154
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+; ---
169
196
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)
170(attribute_item) @attribute
232(attribute_item) @attribute
171(inner_attribute_item) @attribute
233(inner_attribute_item) @attribute
172
234
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+
173[
254[
174−"*"
255+ "*"
175−"'"
256+ "'"
176−"->"
257+ "->"
177−"=>"
258+ "=>"
178−"<="
259+ "<="
179−"="
260+ "="
180−"=="
261+ "=="
181−"!"
262+ "!"
182−"!="
263+ "!="
183−"%"
264+ "%"
184−"%="
265+ "%="
185−"&"
266+ "&"
186−"&="
267+ "&="
187−"&&"
268+ "&&"
188−"|"
269+ "|"
189−"|="
270+ "|="
190−"||"
271+ "||"
191−"^"
272+ "^"
192−"^="
273+ "^="
193−"*"
274+ "*"
194−"*="
275+ "*="
195−"-"
276+ "-"
196−"-="
277+ "-="
197−"+"
278+ "+"
198−"+="
279+ "+="
199−"/"
280+ "/"
200−"/="
281+ "/="
201−">"
282+ ">"
202−"<"
283+ "<"
203−">="
284+ ">="
204−">>"
285+ ">>"
205−"<<"
286+ "<<"
206−">>="
287+ ">>="
207−"@"
288+ "@"
208−".."
289+ ".."
209−"..="
290+ "..="
210−"'"
291+ "'"
211] @operator
292] @operator
212
293
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+
213"?" @special
332"?" @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
1+attribute = "lilac"
2−"keyword" = "#eccdba" # almond
2+keyword = "almond"
3−"keyword.directive" = "#dbbfef" # lilac -- preprocessor comments (#if in C)
3+"keyword.directive" = "lilac" # -- preprocessor comments (#if in C)
4−"namespace" = "#dbbfef" # lilac
4+namespace = "lilac"
5−"punctuation" = "#a4a0e8" # lavender
5+punctuation = "lavender"
6−"punctuation.delimiter" = "#a4a0e8" # lavender
6+"punctuation.delimiter" = "lavender"
7−"operator" = "#dbbfef" # lilac
7+operator = "lilac"
8−"special" = "#efba5d" # honey
8+special = "honey"
9−# "property" = "#a4a0e8" # lavender
10−"property" = "#ffffff" # white
9+property = "white"
11−"variable" = "#a4a0e8" # lavender
10+variable = "lavender"
12−# "variable" = "#eccdba" # almond TODO: metavariables only
11+# variable = "almond" # TODO: metavariables only
13−"variable.parameter" = "#a4a0e8" # lavender
12+"variable.parameter" = "lavender"
13+"variable.builtin" = "mint"
14+type = "white"
14−# TODO distinguish type from type.builtin?
15+"type.builtin" = "white" # TODO: distinguish?
15−"type" = "#ffffff" # white
16−"type.builtin" = "#ffffff" # white
17−"constructor" = "#dbbfef" # lilac
16+constructor = "lilac"
18−"function" = "#ffffff" # white
17+function = "white"
19−"function.macro" = "#dbbfef" # lilac
18+"function.macro" = "lilac"
20−"function.builtin" = "#ffffff" # white
19+"function.builtin" = "white"
21−"comment" = "#697C81" # sirocco
20+comment = "sirocco"
22−"variable.builtin" = "#9ff28f" # mint
23−"constant" = "#ffffff" # white
21+constant = "white"
24−"constant.builtin" = "#ffffff" # white
22+"constant.builtin" = "white"
25−"string" = "#cccccc" # silver
23+string = "silver"
26−"number" = "#e8dca0" # chamois
24+number = "chamois"
27−"escape" = "#efba5d" # honey
25+escape = "honey"
28# used for lifetimes
26# used for lifetimes
29−"label" = "#efba5d" # honey
27+label = "honey"
30
28
31−# TODO: diferentiate number builtin
32# TODO: diferentiate doc comment
29# TODO: diferentiate doc comment
33−# TODO: variable as lilac
34−# TODO: mod/use statements as white
35−# TODO: mod stuff as chamois
36−#
37# concat (ERROR) @syntax-error and "MISSING ;" selectors for errors
30# concat (ERROR) @syntax-error and "MISSING ;" selectors for errors
38
31
39−"module" = "#ff0000"
32+module = "#ff0000"
40
33
41−"ui.background" = { bg = "#3b224c" } # midnight
34+"ui.background" = { bg = "midnight" }
42−"ui.linenr" = { fg = "#5a5977" } # comet
35+"ui.linenr" = { fg = "comet" }
43−"ui.linenr.selected" = { fg = "#dbbfef" } # lilac
36+"ui.linenr.selected" = { fg = "lilac" }
44−"ui.statusline" = { fg = "#dbbfef", bg = "#281733" } # revolver
37+"ui.statusline" = { fg = "lilac", bg = "revolver" }
45−"ui.statusline.inactive" = { fg = "#a4a0e8", bg = "#281733" } # revolver
38+"ui.statusline.inactive" = { fg = "lavender", bg = "revolver" }
46−"ui.popup" = { bg = "#281733" } # revolver
39+"ui.popup" = { bg = "revolver" }
47−"ui.window" = { fg = "#452859" } # bossa nova
40+"ui.window" = { fg = "bossanova" }
48"ui.help" = { bg = "#7958DC", fg = "#171452" }
41"ui.help" = { bg = "#7958DC", fg = "#171452" }
49
42
50−"ui.text" = { fg = "#a4a0e8" } # lavender
43+"ui.text" = { fg = "lavender" }
51−"ui.text.focus" = { fg = "#dbbfef" } # lilac
44+"ui.text.focus" = { fg = "lilac" }
52
45
53"ui.selection" = { bg = "#540099" }
46"ui.selection" = { bg = "#540099" }
54"ui.selection.primary" = { bg = "#540099" }
47"ui.selection.primary" = { bg = "#540099" }
55# TODO: namespace ui.cursor as ui.selection.cursor?
48# TODO: namespace ui.cursor as ui.selection.cursor?
56−"ui.cursor.select" = { bg = "#6F44F0" }
49+"ui.cursor.select" = { bg = "delta" }
57−"ui.cursor.insert" = { bg = "#ffffff" }
50+"ui.cursor.insert" = { bg = "white" }
58"ui.cursor.match" = { fg = "#212121", bg = "#6C6999" }
51"ui.cursor.match" = { fg = "#212121", bg = "#6C6999" }
59"ui.cursor" = { modifiers = ["reversed"] }
52"ui.cursor" = { modifiers = ["reversed"] }
60
53
61−"ui.menu.selected" = { fg = "#281733", bg = "#ffffff" } # revolver
54+"ui.menu.selected" = { fg = "revolver", bg = "white" }
62
55
63−"diagnostic" = { modifiers = ["underlined"] }
56+diagnostic = { modifiers = ["underlined"] }
64
57
65−"warning" = "#ffcd1c"
58+warning = "lightning"
66−"error" = "#f47868"
59+error = "apricot"
67−"info" = "#6F44F0"
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+
68−"hint" = "#cccccc"
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"