diff options
-rw-r--r-- | .github/workflows/release.yml | 3 | ||||
-rw-r--r-- | .gitignore | 1 | ||||
-rw-r--r-- | Cargo.lock | 57 | ||||
-rw-r--r-- | README.md | 11 | ||||
-rw-r--r-- | book/src/keymap.md | 43 | ||||
-rw-r--r-- | book/src/themes.md | 2 | ||||
-rw-r--r-- | helix-core/Cargo.toml | 2 | ||||
-rw-r--r-- | helix-core/src/indent.rs | 4 | ||||
-rw-r--r-- | helix-core/src/syntax.rs | 16 | ||||
-rw-r--r-- | helix-lsp/Cargo.toml | 4 | ||||
-rw-r--r-- | helix-syntax/Cargo.toml | 6 | ||||
-rw-r--r-- | helix-syntax/build.rs | 199 | ||||
-rw-r--r-- | helix-syntax/src/lib.rs | 115 | ||||
-rw-r--r-- | helix-term/src/application.rs | 28 | ||||
-rw-r--r-- | helix-term/src/commands.rs | 712 | ||||
-rw-r--r-- | helix-term/src/keymap.rs | 5 | ||||
-rw-r--r-- | helix-term/src/ui/editor.rs | 36 | ||||
-rw-r--r-- | helix-tui/Cargo.toml | 2 | ||||
-rw-r--r-- | helix-view/src/document.rs | 14 | ||||
-rw-r--r-- | helix-view/src/editor.rs | 20 | ||||
-rw-r--r-- | runtime/grammars/.gitkeep | 0 | ||||
-rw-r--r-- | runtime/queries/c/highlights.scm | 2 | ||||
-rw-r--r-- | runtime/queries/rust/highlights.scm | 483 | ||||
-rw-r--r-- | runtime/themes/dark_plus.toml | 80 | ||||
-rw-r--r-- | theme.toml | 115 |
25 files changed, 1177 insertions, 783 deletions
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 467ccbf4..5ce9ed62 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -99,6 +99,7 @@ jobs: else cp "target/${{ matrix.target }}/release/hx" "dist/" fi + cp -r runtime dist - uses: actions/upload-artifact@v2.2.4 with: @@ -148,7 +149,7 @@ jobs: pkgname=helix-$TAG-$platform mkdir tmp/$pkgname cp LICENSE README.md tmp/$pkgname - cp -r runtime tmp/$pkgname/ + mv bins-$platform/runtime tmp/$pkgname/ mv bins-$platform/hx$exe tmp/$pkgname chmod +x tmp/$pkgname/hx$exe @@ -3,3 +3,4 @@ target helix-term/rustfmt.toml helix-syntax/languages/ result +runtime/grammars @@ -13,9 +13,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.41" +version = "1.0.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15af2628f6890fe2609a3b91bef4c83450512802e59489f9c1cb1fa5df064a61" +checksum = "595d3cfa7a60d4555cb5067b99f07142a08ea778de5cf993f7b75c7d8fabc486" [[package]] name = "arc-swap" @@ -58,12 +58,9 @@ checksum = "df8670b8c7b9dae1793364eafadf7239c40d669904660c5960d74cfd80b46a53" [[package]] name = "cc" -version = "1.0.68" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a72c244c1ff497a746a7e1fb3d14bd08420ecda70c8f25c7112f2781652d787" -dependencies = [ - "jobserver", -] +checksum = "e70cc2f62c6ce1868963827bd677764c62d07c3d9a3e1fb1177ee1a9ab199eb2" [[package]] name = "cfg-if" @@ -354,8 +351,9 @@ dependencies = [ name = "helix-syntax" version = "0.3.0" dependencies = [ + "anyhow", "cc", - "serde", + "libloading", "threadpool", "tree-sitter", ] @@ -476,15 +474,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd25036021b0de88a0aff6b850051563c6516d0bf53f8638938edbb9de732736" [[package]] -name = "jobserver" -version = "0.1.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "972f5ae5d1cb9c6ae417789196c803205313edde988685da5e3aae0827b9e7fd" -dependencies = [ - "libc", -] - -[[package]] name = "jsonrpc-core" version = "17.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -510,6 +499,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "12b8adadd720df158f4d70dfe7ccc6adb0472d7c55ca83445f6a5ab3e36f8fb6" [[package]] +name = "libloading" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f84d96438c15fcd6c3f244c8fce01d1e2b9c6b5623e9c711dc9286d8fc92d6a" +dependencies = [ + "cfg-if 1.0.0", + "winapi", +] + +[[package]] name = "lock_api" version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -914,9 +913,9 @@ checksum = "f173ac3d1a7e3b28003f40de0b5ce7fe2710f9b9dc3fc38664cebee46b3b6527" [[package]] name = "slotmap" -version = "1.0.3" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "585cd5dffe4e9e06f6dfdf66708b70aca3f781bed561f4f667b2d9c0d4559e36" +checksum = "a952280edbecfb1d4bd3cf2dbc309dc6ab523e53487c438ae21a6df09fe84bc4" dependencies = [ "version_check", ] @@ -957,18 +956,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.25" +version = "1.0.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa6f76457f59514c7eeb4e59d891395fab0b2fd1d40723ae737d64153392e9c6" +checksum = "93119e4feac1cbe6c798c34d3a53ea0026b0b1de6a120deef895137c0529bfe2" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.25" +version = "1.0.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a36768c0fbf1bb15eca10defa29526bda730a2376c2ab4393ccfa16fb1a318d" +checksum = "060d69a0afe7796bf42e9e2ff91f5ee691fb15c53d38b4b62a9a53eb23164745" dependencies = [ "proc-macro2", "quote", @@ -1010,9 +1009,9 @@ checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" [[package]] name = "tokio" -version = "1.7.1" +version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fb2ed024293bb19f7a5dc54fe83bf86532a44c12a2bb8ba40d64a4509395ca2" +checksum = "98c8b05dc14c75ea83d63dd391100353789f5f24b8b3866542a5e85c8be8e985" dependencies = [ "autocfg", "bytes", @@ -1041,9 +1040,9 @@ dependencies = [ [[package]] name = "tokio-stream" -version = "0.1.6" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8864d706fdb3cc0843a49647ac892720dac98a6eeb818b77190592cf4994066" +checksum = "7b2f3f698253f03119ac0102beaa64f67a67e08074d03a22d18784104543727f" dependencies = [ "futures-core", "pin-project-lite", @@ -1104,9 +1103,9 @@ dependencies = [ [[package]] name = "unicode-segmentation" -version = "1.7.1" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb0d2e7be6ae3a5fa87eed5fb451aff96f2573d2694942e40543ae0bbe19c796" +checksum = "8895849a949e7845e06bd6dc1aa51731a103c42707010a5b591c0038fb73385b" [[package]] name = "unicode-width" @@ -41,14 +41,17 @@ cargo install --path helix-term This will install the `hx` binary to `$HOME/.cargo/bin`. -Now copy the `runtime/` directory somewhere. Helix will by default look for the runtime -inside the config directory or the same directory as executable, but that can be overriden +Helix also needs it's runtime files so make sure to copy/symlink the `runtime/` directory into the +config directory (for example `~/.config/helix/runtime` on Linux/macOS). This location can be overriden via the `HELIX_RUNTIME` environment variable. -> NOTE: running via cargo doesn't require setting explicit `HELIX_RUNTIME` path, it will automatically +Packages already solve this for you by wrapping the `hx` binary with a wrapper +that sets the variable to the install dir. + +> NOTE: running via cargo also doesn't require setting explicit `HELIX_RUNTIME` path, it will automatically > detect the `runtime` directory in the project root. -If you want to embed the `runtime/` directory into the Helix binary you can build +Alternatively, if you want to embed the `runtime/` directory into the Helix binary you can build it with: ``` diff --git a/book/src/keymap.md b/book/src/keymap.md index c0c455d3..5d6e5795 100644 --- a/book/src/keymap.md +++ b/book/src/keymap.md @@ -41,26 +41,29 @@ ### Changes -| Key | Description | -| ----- | ----------- | -| `r` | Replace with a character | -| `R` | Replace with yanked text | -| `i` | Insert before selection | -| `a` | Insert after selection (append) | -| `I` | Insert at the start of the line | -| `A` | Insert at the end of the line | -| `o` | Open new line below selection | -| `o` | Open new line above selection | -| `u` | Undo change | -| `U` | Redo change | -| `y` | Yank selection | -| `p` | Paste after selection | -| `P` | Paste before selection | -| `>` | Indent selection | -| `<` | Unindent selection | -| `=` | Format selection | -| `d` | Delete selection | -| `c` | Change selection (delete and enter insert mode) | +| Key | Description | +| ----- | ----------- | +| `r` | Replace with a character | +| `R` | Replace with yanked text | +| `~` | Switch case of the selected text | +| `\`` | Set the selected text to upper case | +| `Alt-\`` | Set the selected text to lower case | +| `i` | Insert before selection | +| `a` | Insert after selection (append) | +| `I` | Insert at the start of the line | +| `A` | Insert at the end of the line | +| `o` | Open new line below selection | +| `o` | Open new line above selection | +| `u` | Undo change | +| `U` | Redo change | +| `y` | Yank selection | +| `p` | Paste after selection | +| `P` | Paste before selection | +| `>` | Indent selection | +| `<` | Unindent selection | +| `=` | Format selection | +| `d` | Delete selection | +| `c` | Change selection (delete and enter insert mode) | ### Selection manipulation diff --git a/book/src/themes.md b/book/src/themes.md index e5c461fd..f17195aa 100644 --- a/book/src/themes.md +++ b/book/src/themes.md @@ -51,6 +51,7 @@ Possible keys: | `attribute` | | | `keyword` | | | `keyword.directive` | Preprocessor directives (\#if in C) | +| `keyword.control` | Control flow | | `namespace` | | | `punctuation` | | | `punctuation.delimiter` | | @@ -61,6 +62,7 @@ Possible keys: | `variable.parameter` | | | `type` | | | `type.builtin` | | +| `type.enum.variant` | Enum variants | | `constructor` | | | `function` | | | `function.macro` | | diff --git a/helix-core/Cargo.toml b/helix-core/Cargo.toml index 80d559a9..634c4d9f 100644 --- a/helix-core/Cargo.toml +++ b/helix-core/Cargo.toml @@ -19,7 +19,7 @@ helix-syntax = { version = "0.3", path = "../helix-syntax" } ropey = "1.3" smallvec = "1.4" tendril = "0.4.2" -unicode-segmentation = "1.7" +unicode-segmentation = "1.8" unicode-width = "0.1" unicode-general-category = "0.4" # slab = "0.4.2" diff --git a/helix-core/src/indent.rs b/helix-core/src/indent.rs index 81bdffc0..1b36db7b 100644 --- a/helix-core/src/indent.rs +++ b/helix-core/src/indent.rs @@ -253,14 +253,14 @@ where let doc = Rope::from(doc); use crate::syntax::{ - Configuration, IndentationConfiguration, Lang, LanguageConfiguration, Loader, + Configuration, IndentationConfiguration, LanguageConfiguration, Loader, }; use once_cell::sync::OnceCell; let loader = Loader::new(Configuration { language: vec![LanguageConfiguration { scope: "source.rust".to_string(), file_types: vec!["rs".to_string()], - language_id: Lang::Rust, + language_id: "Rust".to_string(), highlight_config: OnceCell::new(), // roots: vec![], diff --git a/helix-core/src/syntax.rs b/helix-core/src/syntax.rs index 84a5f9bd..d9bfc16f 100644 --- a/helix-core/src/syntax.rs +++ b/helix-core/src/syntax.rs @@ -5,7 +5,7 @@ use crate::{ Rope, RopeSlice, Tendril, }; -pub use helix_syntax::{get_language, get_language_name, Lang}; +pub use helix_syntax::get_language; use arc_swap::ArcSwap; @@ -31,7 +31,7 @@ pub struct Configuration { #[serde(rename_all = "kebab-case")] pub struct LanguageConfiguration { #[serde(rename = "name")] - pub(crate) language_id: Lang, + pub(crate) language_id: String, pub scope: String, // source.rust pub file_types: Vec<String>, // filename ends_with? <Gemfile, rb, etc> pub roots: Vec<String>, // these indicate project roots <.git, Cargo.toml> @@ -153,7 +153,7 @@ fn read_query(language: &str, filename: &str) -> String { impl LanguageConfiguration { fn initialize_highlight(&self, scopes: &[String]) -> Option<Arc<HighlightConfiguration>> { - let language = get_language_name(self.language_id).to_ascii_lowercase(); + let language = self.language_id.to_ascii_lowercase(); let highlights_query = read_query(&language, "highlights.scm"); // always highlight syntax errors @@ -161,17 +161,17 @@ impl LanguageConfiguration { let injections_query = read_query(&language, "injections.scm"); - let locals_query = ""; + let locals_query = read_query(&language, "locals.scm"); if highlights_query.is_empty() { None } else { - let language = get_language(self.language_id); + let language = get_language(&crate::RUNTIME_DIR, &self.language_id).ok()?; let config = HighlightConfiguration::new( language, &highlights_query, &injections_query, - locals_query, + &locals_query, ) .unwrap(); // TODO: no unwrap config.configure(scopes); @@ -198,7 +198,7 @@ impl LanguageConfiguration { pub fn indent_query(&self) -> Option<&IndentQuery> { self.indent_query .get_or_init(|| { - let language = get_language_name(self.language_id).to_ascii_lowercase(); + let language = self.language_id.to_ascii_lowercase(); let toml = load_runtime_file(&language, "indents.toml").ok()?; toml::from_slice(toml.as_bytes()).ok() @@ -1812,7 +1812,7 @@ mod test { .map(String::from) .collect(); - let language = get_language(Lang::Rust); + let language = get_language(&crate::RUNTIME_DIR, "Rust").unwrap(); let config = HighlightConfiguration::new( language, &std::fs::read_to_string( diff --git a/helix-lsp/Cargo.toml b/helix-lsp/Cargo.toml index 8d43af69..be099821 100644 --- a/helix-lsp/Cargo.toml +++ b/helix-lsp/Cargo.toml @@ -23,5 +23,5 @@ lsp-types = { version = "0.89", features = ["proposed"] } serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" thiserror = "1.0" -tokio = { version = "1.7", features = ["full"] } -tokio-stream = "0.1.6" +tokio = { version = "1.8", features = ["full"] } +tokio-stream = "0.1.7" diff --git a/helix-syntax/Cargo.toml b/helix-syntax/Cargo.toml index 140e3d24..7ad24488 100644 --- a/helix-syntax/Cargo.toml +++ b/helix-syntax/Cargo.toml @@ -12,8 +12,10 @@ include = ["src/**/*", "languages/**/*", "build.rs", "!**/docs/**/*", "!**/test/ [dependencies] tree-sitter = "0.19" -serde = { version = "1.0", features = ["derive"] } +libloading = "0.7" +anyhow = "1" [build-dependencies] -cc = { version = "1", features = ["parallel"] } +cc = { version = "1" } threadpool = { version = "1.0" } +anyhow = "1" diff --git a/helix-syntax/build.rs b/helix-syntax/build.rs index 847f8a67..02c4bc0a 100644 --- a/helix-syntax/build.rs +++ b/helix-syntax/build.rs @@ -1,79 +1,147 @@ +use anyhow::{anyhow, Context, Result}; use std::fs; -use std::path::PathBuf; +use std::time::SystemTime; +use std::{ + path::{Path, PathBuf}, + process::Command, +}; use std::sync::mpsc::channel; -fn collect_tree_sitter_dirs(ignore: &[String]) -> Vec<String> { +fn collect_tree_sitter_dirs(ignore: &[String]) -> Result<Vec<String>> { let mut dirs = Vec::new(); - for entry in fs::read_dir("languages").unwrap().flatten() { + let path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("languages"); + + for entry in fs::read_dir(path)? { + let entry = entry?; let path = entry.path(); - let dir = path.file_name().unwrap().to_str().unwrap().to_string(); - if !ignore.contains(&dir) { - dirs.push(dir); + + if !entry.file_type()?.is_dir() { + continue; } - } - dirs -} -fn collect_src_files(dir: &str) -> (Vec<String>, Vec<String>) { - eprintln!("Collect files for {}", dir); + let dir = path.file_name().unwrap().to_str().unwrap().to_string(); - let mut c_files = Vec::new(); - let mut cpp_files = Vec::new(); - let path = PathBuf::from("languages").join(&dir).join("src"); - for entry in fs::read_dir(path).unwrap().flatten() { - let path = entry.path(); - if path - .file_stem() - .unwrap() - .to_str() - .unwrap() - .starts_with("binding") - { + // filter ignores + if ignore.contains(&dir) { continue; } - if let Some(ext) = path.extension() { - if ext == "c" { - c_files.push(path.to_str().unwrap().to_string()); - } else if ext == "cc" || ext == "cpp" || ext == "cxx" { - cpp_files.push(path.to_str().unwrap().to_string()); - } - } + dirs.push(dir) } - (c_files, cpp_files) -} -fn build_c(files: Vec<String>, language: &str) { - let mut build = cc::Build::new(); - for file in files { - build - .file(&file) - .include(PathBuf::from(file).parent().unwrap()) - .pic(true) - .warnings(false); - } - build.compile(&format!("tree-sitter-{}-c", language)); + Ok(dirs) } -fn build_cpp(files: Vec<String>, language: &str) { - let mut build = cc::Build::new(); +#[cfg(unix)] +const DYLIB_EXTENSION: &str = "so"; + +#[cfg(windows)] +const DYLIB_EXTENSION: &str = "dll"; - let flag = if build.get_compiler().is_like_msvc() { - "/std:c++17" +fn build_library(src_path: &Path, language: &str) -> Result<()> { + let header_path = src_path; + // let grammar_path = src_path.join("grammar.json"); + let parser_path = src_path.join("parser.c"); + let mut scanner_path = src_path.join("scanner.c"); + + let scanner_path = if scanner_path.exists() { + Some(scanner_path) } else { - "-std=c++14" + scanner_path.set_extension("cc"); + if scanner_path.exists() { + Some(scanner_path) + } else { + None + } }; + let parser_lib_path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../runtime/grammars"); + let mut library_path = parser_lib_path.join(language); + library_path.set_extension(DYLIB_EXTENSION); - for file in files { - build - .file(&file) - .include(PathBuf::from(file).parent().unwrap()) - .pic(true) - .warnings(false) - .cpp(true) - .flag_if_supported(flag); + let recompile = needs_recompile(&library_path, &parser_path, &scanner_path) + .with_context(|| "Failed to compare source and binary timestamps")?; + + if !recompile { + return Ok(()); + } + let mut config = cc::Build::new(); + config.cpp(true).opt_level(2).cargo_metadata(false); + let compiler = config.get_compiler(); + let mut command = Command::new(compiler.path()); + command.current_dir(src_path); + for (key, value) in compiler.env() { + command.env(key, value); } - build.compile(&format!("tree-sitter-{}-cpp", language)); + + if cfg!(windows) { + command + .args(&["/nologo", "/LD", "/I"]) + .arg(header_path) + .arg("/Od"); + if let Some(scanner_path) = scanner_path.as_ref() { + command.arg(scanner_path); + } + + command + .arg(parser_path) + .arg("/link") + .arg(format!("/out:{}", library_path.to_str().unwrap())); + } else { + command + .arg("-shared") + .arg("-fPIC") + .arg("-fno-exceptions") + .arg("-g") + .arg("-I") + .arg(header_path) + .arg("-o") + .arg(&library_path) + .arg("-O2"); + if let Some(scanner_path) = scanner_path.as_ref() { + if scanner_path.extension() == Some("c".as_ref()) { + command.arg("-xc").arg("-std=c99").arg(scanner_path); + } else { + command.arg(scanner_path); + } + } + command.arg("-xc").arg(parser_path); + } + + let output = command + .output() + .with_context(|| "Failed to execute C compiler")?; + if !output.status.success() { + return Err(anyhow!( + "Parser compilation failed.\nStdout: {}\nStderr: {}", + String::from_utf8_lossy(&output.stdout), + String::from_utf8_lossy(&output.stderr) + )); + } + + Ok(()) +} +fn needs_recompile( + lib_path: &Path, + parser_c_path: &Path, + scanner_path: &Option<PathBuf>, +) -> Result<bool> { + if !lib_path.exists() { + return Ok(true); + } + let lib_mtime = mtime(lib_path)?; + if mtime(parser_c_path)? > lib_mtime { + return Ok(true); + } + if let Some(scanner_path) = scanner_path { + if mtime(scanner_path)? > lib_mtime { + return Ok(true); + } + } + Ok(false) +} + +fn mtime(path: &Path) -> Result<SystemTime> { + Ok(fs::metadata(path)?.modified()?) } fn build_dir(dir: &str, language: &str) { @@ -92,22 +160,21 @@ fn build_dir(dir: &str, language: &str) { eprintln!("You can fix in using 'git submodule init && git submodule update --recursive'."); std::process::exit(1); } - let (c, cpp) = collect_src_files(dir); - if !c.is_empty() { - build_c(c, language); - } - if !cpp.is_empty() { - build_cpp(cpp, language); - } + + let path = PathBuf::from(env!("CARGO_MANIFEST_DIR")) + .join("languages") + .join(dir) + .join("src"); + + build_library(&path, language).unwrap(); } fn main() { let ignore = vec![ "tree-sitter-typescript".to_string(), "tree-sitter-haskell".to_string(), // aarch64 failures: https://github.com/tree-sitter/tree-sitter-haskell/issues/34 - ".DS_Store".to_string(), ]; - let dirs = collect_tree_sitter_dirs(&ignore); + let dirs = collect_tree_sitter_dirs(&ignore).unwrap(); let mut n_jobs = 0; let pool = threadpool::Builder::new().build(); // by going through the builder, it'll use num_cpus @@ -118,7 +185,7 @@ fn main() { n_jobs += 1; pool.execute(move || { - let language = &dir[12..]; // skip tree-sitter- prefix + let language = &dir.strip_prefix("tree-sitter-").unwrap(); build_dir(&dir, language); // report progress diff --git a/helix-syntax/src/lib.rs b/helix-syntax/src/lib.rs index 5e3bb3ea..b6c0ecf3 100644 --- a/helix-syntax/src/lib.rs +++ b/helix-syntax/src/lib.rs @@ -1,94 +1,39 @@ -use serde::{Deserialize, Serialize}; +use anyhow::{Context, Result}; +use libloading::{Library, Symbol}; use tree_sitter::Language; -#[macro_export] -macro_rules! mk_extern { - ( $( $name:ident ),* ) => { - $( - extern "C" { pub fn $name() -> Language; } - )* - }; -} - -#[macro_export] -macro_rules! mk_enum { - ( $( $camel:ident ),* ) => { - #[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)] - #[serde(rename_all = "lowercase")] - pub enum Lang { - $( - $camel, - )* +fn replace_dashes_with_underscores(name: &str) -> String { + let mut result = String::with_capacity(name.len()); + for c in name.chars() { + if c == '-' { + result.push('_'); + } else { + result.push(c); } - }; + } + result } +#[cfg(unix)] +const DYLIB_EXTENSION: &str = "so"; -#[macro_export] -macro_rules! mk_get_language { - ( $( ($camel:ident, $name:ident) ),* ) => { - #[must_use] - pub fn get_language(lang: Lang) -> Language { - unsafe { - match lang { - $( - Lang::$camel => $name(), - )* - } - } - } - }; -} +#[cfg(windows)] +const DYLIB_EXTENSION: &str = "dll"; -#[macro_export] -macro_rules! mk_get_language_name { - ( $( $camel:ident ),* ) => { - #[must_use] - pub const fn get_language_name(lang: Lang) -> &'static str { - match lang { - $( - Lang::$camel => stringify!($camel), - )* - } - } - }; -} +pub fn get_language(runtime_path: &std::path::Path, name: &str) -> Result<Language> { + let name = name.to_ascii_lowercase(); + let mut library_path = runtime_path.join("grammars").join(&name); + // TODO: duplicated under build + library_path.set_extension(DYLIB_EXTENSION); -#[macro_export] -macro_rules! mk_langs { - ( $( ($camel:ident, $name:ident) ),* ) => { - mk_extern!($( $name ),*); - mk_enum!($( $camel ),*); - mk_get_language!($( ($camel, $name) ),*); - mk_get_language_name!($( $camel ),*); + let library = unsafe { Library::new(&library_path) } + .with_context(|| format!("Error opening dynamic library {:?}", &library_path))?; + let language_fn_name = format!("tree_sitter_{}", replace_dashes_with_underscores(&name)); + let language = unsafe { + let language_fn: Symbol<unsafe extern "C" fn() -> Language> = library + .get(language_fn_name.as_bytes()) + .with_context(|| format!("Failed to load symbol {}", language_fn_name))?; + language_fn() }; + std::mem::forget(library); + Ok(language) } - -mk_langs!( - // 1) Name for enum - // 2) tree-sitter function to call to get a Language - (Agda, tree_sitter_agda), - (Bash, tree_sitter_bash), - (Cpp, tree_sitter_cpp), - (CSharp, tree_sitter_c_sharp), - (Css, tree_sitter_css), - (C, tree_sitter_c), - (Elixir, tree_sitter_elixir), - (Go, tree_sitter_go), - // (Haskell, tree_sitter_haskell), - (Html, tree_sitter_html), - (Javascript, tree_sitter_javascript), - (Java, tree_sitter_java), - (Json, tree_sitter_json), - (Julia, tree_sitter_julia), - (Latex, tree_sitter_latex), - (Nix, tree_sitter_nix), - (Php, tree_sitter_php), - (Python, tree_sitter_python), - (Ruby, tree_sitter_ruby), - (Rust, tree_sitter_rust), - (Scala, tree_sitter_scala), - (Swift, tree_sitter_swift), - (Toml, tree_sitter_toml), - (Tsx, tree_sitter_tsx), - (Typescript, tree_sitter_typescript) -); diff --git a/helix-term/src/application.rs b/helix-term/src/application.rs index 17ba2652..c55d4c98 100644 --- a/helix-term/src/application.rs +++ b/helix-term/src/application.rs @@ -9,6 +9,7 @@ use log::error; use std::{ io::{stdout, Write}, sync::Arc, + time::{Duration, Instant}, }; use anyhow::Error; @@ -82,15 +83,18 @@ impl Application { editor.new_file(Action::VerticalSplit); compositor.push(Box::new(ui::file_picker(first.clone()))); } else { + let nr_of_files = args.files.len(); + editor.open(first.to_path_buf(), Action::VerticalSplit)?; for file in args.files { if file.is_dir() { return Err(anyhow::anyhow!( "expected a path to file, found a directory. (to open a directory pass it as first argument)" )); } else { - editor.open(file, Action::VerticalSplit)?; + editor.open(file.to_path_buf(), Action::Load)?; } } + editor.set_status(format!("Loaded {} files.", nr_of_files)); } } else { editor.new_file(Action::VerticalSplit); @@ -130,6 +134,8 @@ impl Application { pub async fn event_loop(&mut self) { let mut reader = EventStream::new(); + let mut last_render = Instant::now(); + let deadline = Duration::from_secs(1) / 60; self.render(); @@ -139,26 +145,22 @@ impl Application { break; } - use futures_util::{FutureExt, StreamExt}; + use futures_util::StreamExt; tokio::select! { + biased; + event = reader.next() => { self.handle_terminal_events(event) } Some((id, call)) = self.editor.language_servers.incoming.next() => { self.handle_language_server_message(call, id).await; - - // eagerly process any other available notifications/calls - let now = std::time::Instant::now(); - let deadline = std::time::Duration::from_millis(10); - while let Some(Some((id, call))) = self.editor.language_servers.incoming.next().now_or_never() { - self.handle_language_server_message(call, id).await; - - if now.elapsed() > deadline { // use a deadline so we don't block too long - break; - } + // limit render calls for fast language server messages + let last = self.editor.language_servers.incoming.is_empty(); + if last || last_render.elapsed() > deadline { + self.render(); + last_render = Instant::now(); } - self.render(); } Some(callback) = self.jobs.futures.next() => { self.jobs.handle_callback(&mut self.editor, &mut self.compositor, callback); diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index 51e633f6..74b54db7 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -6,8 +6,8 @@ use helix_core::{ object, pos_at_coords, regex::{self, Regex}, register::Register, - search, selection, LineEnding, Position, Range, Rope, RopeGraphemes, RopeSlice, Selection, - SmallVec, Tendril, Transaction, + search, selection, surround, textobject, LineEnding, Position, Range, Rope, RopeGraphemes, + RopeSlice, Selection, SmallVec, Tendril, Transaction, }; use helix_view::{ @@ -20,7 +20,7 @@ use helix_view::{ Document, DocumentId, Editor, ViewId, }; -use anyhow::anyhow; +use anyhow::{anyhow, bail, Context as _}; use helix_lsp::{ lsp, util::{lsp_pos_to_pos, lsp_range_to_range, pos_to_lsp_pos, range_to_lsp_range}, @@ -37,6 +37,7 @@ use crate::{ use crate::job::{self, Job, Jobs}; use futures_util::{FutureExt, TryFutureExt}; use std::collections::HashMap; +use std::num::NonZeroUsize; use std::{fmt, future::Future}; use std::{ @@ -49,7 +50,7 @@ use serde::de::{self, Deserialize, Deserializer}; pub struct Context<'a> { pub selected_register: helix_view::RegisterSelection, - pub count: Option<std::num::NonZeroUsize>, + pub count: Option<NonZeroUsize>, pub editor: &'a mut Editor, pub callback: Option<crate::compositor::Callback>, @@ -75,7 +76,9 @@ impl<'a> Context<'a> { #[inline] pub fn on_next_key_mode(&mut self, map: HashMap<KeyEvent, fn(&mut Context)>) { + let count = self.count; self.on_next_key(move |cx, event| { + cx.count = count; cx.editor.autoinfo = None; if let Some(func) = map.get(&event) { func(cx); @@ -180,6 +183,9 @@ impl Command { extend_till_prev_char, extend_prev_char, replace, + switch_case, + switch_to_uppercase, + switch_to_lowercase, page_up, page_down, half_page_up, @@ -778,6 +784,57 @@ fn replace(cx: &mut Context) { }) } +fn switch_case(cx: &mut Context) { + let (view, doc) = current!(cx.editor); + let transaction = + Transaction::change_by_selection(doc.text(), doc.selection(view.id), |range| { + let text: Tendril = range + .fragment(doc.text().slice(..)) + .chars() + .flat_map(|ch| { + if ch.is_lowercase() { + ch.to_uppercase().collect() + } else if ch.is_uppercase() { + ch.to_lowercase().collect() + } else { + vec![ch] + } + }) + .collect(); + + (range.from(), range.to() + 1, Some(text)) + }); + + doc.apply(&transaction, view.id); + doc.append_changes_to_history(view.id); +} + +fn switch_to_uppercase(cx: &mut Context) { + let (view, doc) = current!(cx.editor); + let transaction = + Transaction::change_by_selection(doc.text(), doc.selection(view.id), |range| { + let text: Tendril = range.fragment(doc.text().slice(..)).to_uppercase().into(); + + (range.from(), range.to() + 1, Some(text)) + }); + + doc.apply(&transaction, view.id); + doc.append_changes_to_history(view.id); +} + +fn switch_to_lowercase(cx: &mut Context) { + let (view, doc) = current!(cx.editor); + let transaction = + Transaction::change_by_selection(doc.text(), doc.selection(view.id), |range| { + let text: Tendril = range.fragment(doc.text().slice(..)).to_lowercase().into(); + + (range.from(), range.to() + 1, Some(text)) + }); + + doc.apply(&transaction, view.id); + doc.append_changes_to_history(view.id); +} + fn scroll(cx: &mut Context, offset: usize, direction: Direction) { use Direction::*; let (view, doc) = current!(cx.editor); @@ -1194,34 +1251,45 @@ mod cmd { pub alias: Option<&'static str>, pub doc: &'static str, // params, flags, helper, completer - pub fun: fn(&mut compositor::Context, &[&str], PromptEvent), + pub fun: fn(&mut compositor::Context, &[&str], PromptEvent) -> anyhow::Result<()>, pub completer: Option<Completer>, } - fn quit(cx: &mut compositor::Context, _args: &[&str], _event: PromptEvent) { + fn quit( + cx: &mut compositor::Context, + _args: &[&str], + _event: PromptEvent, + ) -> anyhow::Result<()> { // last view and we have unsaved changes - if cx.editor.tree.views().count() == 1 && buffers_remaining_impl(cx.editor) { - return; + if cx.editor.tree.views().count() == 1 { + buffers_remaining_impl(cx.editor)? } + cx.editor .close(view!(cx.editor).id, /* close_buffer */ false); + + Ok(()) } - fn force_quit(cx: &mut compositor::Context, _args: &[&str], _event: PromptEvent) { + fn force_quit( + cx: &mut compositor::Context, + _args: &[&str], + _event: PromptEvent, + ) -> anyhow::Result<()> { cx.editor .close(view!(cx.editor).id, /* close_buffer */ false); + + Ok(()) } - fn open(cx: &mut compositor::Context, args: &[&str], _event: PromptEvent) { - match args.get(0) { - Some(path) => { - // TODO: handle error - let _ = cx.editor.open(path.into(), Action::Replace); - } - None => { - cx.editor.set_error("wrong argument count".to_string()); - } - }; + fn open( + cx: &mut compositor::Context, + args: &[&str], + _event: PromptEvent, + ) -> anyhow::Result<()> { + let path = args.get(0).context("wrong argument count")?; + let _ = cx.editor.open(path.into(), Action::Replace)?; + Ok(()) } fn write_impl<P: AsRef<Path>>( @@ -1232,12 +1300,10 @@ mod cmd { let (_, doc) = current!(cx.editor); if let Some(path) = path { - if let Err(err) = doc.set_path(path.as_ref()) { - return Err(anyhow!("invalid filepath: {}", err)); - }; + doc.set_path(path.as_ref()).context("invalid filepath")?; } if doc.path().is_none() { - return Err(anyhow!("cannot write a buffer without a filename")); + bail!("cannot write a buffer without a filename"); } let fmt = doc.auto_format().map(|fmt| { let shared = fmt.shared(); @@ -1253,21 +1319,33 @@ mod cmd { Ok(tokio::spawn(doc.format_and_save(fmt))) } - fn write(cx: &mut compositor::Context, args: &[&str], _event: PromptEvent) { - match write_impl(cx, args.first()) { - Err(e) => cx.editor.set_error(e.to_string()), - Ok(handle) => { - cx.jobs - .add(Job::new(handle.unwrap_or_else(|e| Err(e.into()))).wait_before_exiting()); - } - }; + fn write( + cx: &mut compositor::Context, + args: &[&str], + _event: PromptEvent, + ) -> anyhow::Result<()> { + let handle = write_impl(cx, args.first())?; + cx.jobs + .add(Job::new(handle.unwrap_or_else(|e| Err(e.into()))).wait_before_exiting()); + + Ok(()) } - fn new_file(cx: &mut compositor::Context, _args: &[&str], _event: PromptEvent) { + fn new_file( + cx: &mut compositor::Context, + _args: &[&str], + _event: PromptEvent, + ) -> anyhow::Result<()> { cx.editor.new_file(Action::Replace); + + Ok(()) } - fn format(cx: &mut compositor::Context, _args: &[&str], _event: PromptEvent) { + fn format( + cx: &mut compositor::Context, + _args: &[&str], + _event: PromptEvent, + ) -> anyhow::Result<()> { let (_, doc) = current!(cx.editor); if let Some(format) = doc.format() { @@ -1275,9 +1353,14 @@ mod cmd { make_format_callback(doc.id(), doc.version(), Modified::LeaveModified, format); cx.jobs.callback(callback); } - } - fn set_indent_style(cx: &mut compositor::Context, args: &[&str], _event: PromptEvent) { + Ok(()) + } + fn set_indent_style( + cx: &mut compositor::Context, + args: &[&str], + _event: PromptEvent, + ) -> anyhow::Result<()> { use IndentStyle::*; // If no argument, report current indent style. @@ -1289,7 +1372,7 @@ mod cmd { Spaces(n) if (2..=8).contains(&n) => format!("{} spaces", n), _ => "error".into(), // Shouldn't happen. }); - return; + return Ok(()); } // Attempt to parse argument as an indent style. @@ -1304,18 +1387,19 @@ mod cmd { _ => None, }; - if let Some(s) = style { - let doc = doc_mut!(cx.editor); - doc.indent_style = s; - } else { - // Invalid argument. - cx.editor - .set_error(format!("invalid indent style '{}'", args[0],)); - } + let style = style.context("invalid indent style")?; + let doc = doc_mut!(cx.editor); + doc.indent_style = style; + + Ok(()) } /// Sets or reports the current document's line ending setting. - fn set_line_ending(cx: &mut compositor::Context, args: &[&str], _event: PromptEvent) { + fn set_line_ending( + cx: &mut compositor::Context, + args: &[&str], + _event: PromptEvent, + ) -> anyhow::Result<()> { use LineEnding::*; // If no argument, report current line ending setting. @@ -1331,7 +1415,8 @@ mod cmd { // These should never be a document's default line ending. VT | LS | PS => "error".into(), }); - return; + + return Ok(()); } // Attempt to parse argument as a line ending. @@ -1345,72 +1430,65 @@ mod cmd { _ => None, }; - if let Some(le) = line_ending { - doc_mut!(cx.editor).line_ending = le; - } else { - // Invalid argument. - cx.editor - .set_error(format!("invalid line ending '{}'", args[0],)); - } + let line_ending = line_ending.context("invalid line ending")?; + doc_mut!(cx.editor).line_ending = line_ending; + Ok(()) } - fn earlier(cx: &mut compositor::Context, args: &[&str], _event: PromptEvent) { - let uk = match args.join(" ").parse::<helix_core::history::UndoKind>() { - Ok(uk) => uk, - Err(msg) => { - cx.editor.set_error(msg); - return; - } - }; + fn earlier( + cx: &mut compositor::Context, + args: &[&str], + _event: PromptEvent, + ) -> anyhow::Result<()> { + let uk = args + .join(" ") + .parse::<helix_core::history::UndoKind>() + .map_err(|s| anyhow!(s))?; + let (view, doc) = current!(cx.editor); - doc.earlier(view.id, uk) + doc.earlier(view.id, uk); + + Ok(()) } - fn later(cx: &mut compositor::Context, args: &[&str], _event: PromptEvent) { - let uk = match args.join(" ").parse::<helix_core::history::UndoKind>() { - Ok(uk) => uk, - Err(msg) => { - cx.editor.set_error(msg); - return; - } - }; + fn later( + cx: &mut compositor::Context, + args: &[&str], + _event: PromptEvent, + ) -> anyhow::Result<()> { + let uk = args + .join(" ") + .parse::<helix_core::history::UndoKind>() + .map_err(|s| anyhow!(s))?; let (view, doc) = current!(cx.editor); - doc.later(view.id, uk) + doc.later(view.id, uk); + + Ok(()) } - fn write_quit(cx: &mut compositor::Context, args: &[&str], event: PromptEvent) { - match write_impl(cx, args.first()) { - Ok(handle) => { - if let Err(e) = helix_lsp::block_on(handle) { - cx.editor.set_error(e.to_string()); - } else { - quit(cx, &[], event); - } - } - Err(e) => { - cx.editor.set_error(e.to_string()); - } - } + fn write_quit( + cx: &mut compositor::Context, + args: &[&str], + event: PromptEvent, + ) -> anyhow::Result<()> { + let handle = write_impl(cx, args.first())?; + let _ = helix_lsp::block_on(handle)?; + quit(cx, &[], event) } - fn force_write_quit(cx: &mut compositor::Context, args: &[&str], event: PromptEvent) { - match write_impl(cx, args.first()) { - Ok(handle) => { - if let Err(e) = helix_lsp::block_on(handle) { - cx.editor.set_error(e.to_string()); - } else { - force_quit(cx, &[], event); - } - } - Err(e) => { - cx.editor.set_error(e.to_string()); - } - } + fn force_write_quit( + cx: &mut compositor::Context, + args: &[&str], + event: PromptEvent, + ) -> anyhow::Result<()> { + let handle = write_impl(cx, args.first())?; + let _ = helix_lsp::block_on(handle)?; + force_quit(cx, &[], event) } - /// Returns `true` if there are modified buffers remaining and sets editor error, - /// otherwise returns `false` - fn buffers_remaining_impl(editor: &mut Editor) -> bool { + /// Results an error if there are modified buffers remaining and sets editor error, + /// otherwise returns `Ok(())` + fn buffers_remaining_impl(editor: &mut Editor) -> anyhow::Result<()> { let modified: Vec<_> = editor .documents() .filter(|doc| doc.is_modified()) @@ -1421,16 +1499,13 @@ mod cmd { }) .collect(); if !modified.is_empty() { - let err = format!( + bail!( "{} unsaved buffer(s) remaining: {:?}", modified.len(), modified ); - editor.set_error(err); - true - } else { - false } + Ok(()) } fn write_all_impl( @@ -1439,7 +1514,7 @@ mod cmd { _event: PromptEvent, quit: bool, force: bool, - ) { + ) -> anyhow::Result<()> { let mut errors = String::new(); // save all documents @@ -1452,11 +1527,10 @@ mod cmd { // TODO: handle error. let _ = helix_lsp::block_on(tokio::spawn(doc.save())); } - editor.set_error(errors); if quit { - if !force && buffers_remaining_impl(editor) { - return; + if !force { + buffers_remaining_impl(editor)?; } // close all views @@ -1465,23 +1539,42 @@ mod cmd { editor.close(view_id, false); } } + + bail!(errors) } - fn write_all(cx: &mut compositor::Context, args: &[&str], event: PromptEvent) { + fn write_all( + cx: &mut compositor::Context, + args: &[&str], + event: PromptEvent, + ) -> anyhow::Result<()> { write_all_impl(&mut cx.editor, args, event, false, false) } - fn write_all_quit(cx: &mut compositor::Context, args: &[&str], event: PromptEvent) { + fn write_all_quit( + cx: &mut compositor::Context, + args: &[&str], + event: PromptEvent, + ) -> anyhow::Result<()> { write_all_impl(&mut cx.editor, args, event, true, false) } - fn force_write_all_quit(cx: &mut compositor::Context, args: &[&str], event: PromptEvent) { + fn force_write_all_quit( + cx: &mut compositor::Context, + args: &[&str], + event: PromptEvent, + ) -> anyhow::Result<()> { write_all_impl(&mut cx.editor, args, event, true, true) } - fn quit_all_impl(editor: &mut Editor, _args: &[&str], _event: PromptEvent, force: bool) { - if !force && buffers_remaining_impl(editor) { - return; + fn quit_all_impl( + editor: &mut Editor, + _args: &[&str], + _event: PromptEvent, + force: bool, + ) -> anyhow::Result<()> { + if !force { + buffers_remaining_impl(editor)?; } // close all views @@ -1489,57 +1582,77 @@ mod cmd { for view_id in views { editor.close(view_id, false); } + + Ok(()) } - fn quit_all(cx: &mut compositor::Context, args: &[&str], event: PromptEvent) { + fn quit_all( + cx: &mut compositor::Context, + args: &[&str], + event: PromptEvent, + ) -> anyhow::Result<()> { quit_all_impl(&mut cx.editor, args, event, false) } - fn force_quit_all(cx: &mut compositor::Context, args: &[&str], event: PromptEvent) { + fn force_quit_all( + cx: &mut compositor::Context, + args: &[&str], + event: PromptEvent, + ) -> anyhow::Result<()> { quit_all_impl(&mut cx.editor, args, event, true) } - fn theme(cx: &mut compositor::Context, args: &[&str], _event: PromptEvent) { - let theme = if let Some(theme) = args.first() { - theme - } else { - cx.editor.set_error("theme name not provided".into()); - return; - }; - - cx.editor.set_theme_from_name(theme); + fn theme( + cx: &mut compositor::Context, + args: &[&str], + _event: PromptEvent, + ) -> anyhow::Result<()> { + let theme = args.first().context("theme not provided")?; + cx.editor.set_theme_from_name(theme) } fn yank_main_selection_to_clipboard( cx: &mut compositor::Context, _args: &[&str], _event: PromptEvent, - ) { - yank_main_selection_to_clipboard_impl(&mut cx.editor); + ) -> anyhow::Result<()> { + yank_main_selection_to_clipboard_impl(&mut cx.editor) } - fn yank_joined_to_clipboard(cx: &mut compositor::Context, args: &[&str], _event: PromptEvent) { + fn yank_joined_to_clipboard( + cx: &mut compositor::Context, + args: &[&str], + _event: PromptEvent, + ) -> anyhow::Result<()> { let (_, doc) = current!(cx.editor); let separator = args .first() .copied() .unwrap_or_else(|| doc.line_ending.as_str()); - yank_joined_to_clipboard_impl(&mut cx.editor, separator); + yank_joined_to_clipboard_impl(&mut cx.editor, separator) } - fn paste_clipboard_after(cx: &mut compositor::Context, _args: &[&str], _event: PromptEvent) { - paste_clipboard_impl(&mut cx.editor, Paste::After); + fn paste_clipboard_after( + cx: &mut compositor::Context, + _args: &[&str], + _event: PromptEvent, + ) -> anyhow::Result<()> { + paste_clipboard_impl(&mut cx.editor, Paste::After) } - fn paste_clipboard_before(cx: &mut compositor::Context, _args: &[&str], _event: PromptEvent) { - paste_clipboard_impl(&mut cx.editor, Paste::After); + fn paste_clipboard_before( + cx: &mut compositor::Context, + _args: &[&str], + _event: PromptEvent, + ) -> anyhow::Result<()> { + paste_clipboard_impl(&mut cx.editor, Paste::After) } fn replace_selections_with_clipboard( cx: &mut compositor::Context, _args: &[&str], _event: PromptEvent, - ) { + ) -> anyhow::Result<()> { let (view, doc) = current!(cx.editor); match cx.editor.clipboard_provider.get_contents() { @@ -1555,71 +1668,76 @@ mod cmd { doc.apply(&transaction, view.id); doc.append_changes_to_history(view.id); + Ok(()) } - Err(e) => log::error!("Couldn't get system clipboard contents: {:?}", e), + Err(e) => Err(e.context("Couldn't get system clipboard contents")), } } - fn show_clipboard_provider(cx: &mut compositor::Context, _args: &[&str], _event: PromptEvent) { + fn show_clipboard_provider( + cx: &mut compositor::Context, + _args: &[&str], + _event: PromptEvent, + ) -> anyhow::Result<()> { cx.editor .set_status(cx.editor.clipboard_provider.name().into()); + Ok(()) } - fn change_current_directory(cx: &mut compositor::Context, args: &[&str], _event: PromptEvent) { - let dir = match args.first() { - Some(dir) => dir, - None => { - cx.editor.set_error("target directory not provided".into()); - return; - } - }; + fn change_current_directory( + cx: &mut compositor::Context, + args: &[&str], + _event: PromptEvent, + ) -> anyhow::Result<()> { + let dir = args.first().context("target directory not provided")?; if let Err(e) = std::env::set_current_dir(dir) { - cx.editor.set_error(format!( - "Couldn't change the current working directory: {:?}", - e - )); - return; + bail!("Couldn't change the current working directory: {:?}", e); } - match std::env::current_dir() { - Ok(cwd) => cx.editor.set_status(format!( - "Current working directory is now {}", - cwd.display() - )), - Err(e) => cx - .editor - .set_error(format!("Couldn't get the new working directory: {}", e)), - } + let cwd = std::env::current_dir().context("Couldn't get the new working directory")?; + cx.editor.set_status(format!( + "Current working directory is now {}", + cwd.display() + )); + Ok(()) } - fn show_current_directory(cx: &mut compositor::Context, _args: &[&str], _event: PromptEvent) { - match std::env::current_dir() { - Ok(cwd) => cx - .editor - .set_status(format!("Current working directory is {}", cwd.display())), - Err(e) => cx - .editor - .set_error(format!("Couldn't get the current working directory: {}", e)), - } + fn show_current_directory( + cx: &mut compositor::Context, + _args: &[&str], + _event: PromptEvent, + ) -> anyhow::Result<()> { + let cwd = std::env::current_dir().context("Couldn't get the new working directory")?; + cx.editor + .set_status(format!("Current working directory is {}", cwd.display())); + Ok(()) } /// Sets the [`Document`]'s encoding.. - fn set_encoding(cx: &mut compositor::Context, args: &[&str], _: PromptEvent) { + fn set_encoding( + cx: &mut compositor::Context, + args: &[&str], + _event: PromptEvent, + ) -> anyhow::Result<()> { let (_, doc) = current!(cx.editor); if let Some(label) = args.first() { doc.set_encoding(label) - .unwrap_or_else(|e| cx.editor.set_error(e.to_string())); } else { let encoding = doc.encoding().name().to_string(); - cx.editor.set_status(encoding) + cx.editor.set_status(encoding); + Ok(()) } } /// Reload the [`Document`] from its source file. - fn reload(cx: &mut compositor::Context, _args: &[&str], _: PromptEvent) { + fn reload( + cx: &mut compositor::Context, + _args: &[&str], + _event: PromptEvent, + ) -> anyhow::Result<()> { let (view, doc) = current!(cx.editor); - doc.reload(view.id).unwrap(); + doc.reload(view.id) } pub const TYPABLE_COMMAND_LIST: &[TypableCommand] = &[ @@ -1884,7 +2002,9 @@ fn command_mode(cx: &mut Context) { } if let Some(cmd) = cmd::COMMANDS.get(parts[0]) { - (cmd.fun)(cx, &parts[1..], event); + if let Err(e) = (cmd.fun)(cx, &parts[1..], event) { + cx.editor.set_error(format!("{}", e)); + } } else { cx.editor .set_error(format!("no such command: '{}'", parts[0])); @@ -2816,7 +2936,7 @@ fn yank(cx: &mut Context) { cx.editor.set_status(msg) } -fn yank_joined_to_clipboard_impl(editor: &mut Editor, separator: &str) { +fn yank_joined_to_clipboard_impl(editor: &mut Editor, separator: &str) -> anyhow::Result<()> { let (view, doc) = current!(editor); let values: Vec<String> = doc @@ -2832,19 +2952,22 @@ fn yank_joined_to_clipboard_impl(editor: &mut Editor, separator: &str) { let joined = values.join(separator); - if let Err(e) = editor.clipboard_provider.set_contents(joined) { - log::error!("Couldn't set system clipboard content: {:?}", e); - } + editor + .clipboard_provider + .set_contents(joined) + .context("Couldn't set system clipboard content")?; editor.set_status(msg); + + Ok(()) } fn yank_joined_to_clipboard(cx: &mut Context) { let line_ending = current!(cx.editor).1.line_ending; - yank_joined_to_clipboard_impl(&mut cx.editor, line_ending.as_str()); + let _ = yank_joined_to_clipboard_impl(&mut cx.editor, line_ending.as_str()); } -fn yank_main_selection_to_clipboard_impl(editor: &mut Editor) { +fn yank_main_selection_to_clipboard_impl(editor: &mut Editor) -> anyhow::Result<()> { let (view, doc) = current!(editor); let value = doc @@ -2853,14 +2976,15 @@ fn yank_main_selection_to_clipboard_impl(editor: &mut Editor) { .fragment(doc.text().slice(..)); if let Err(e) = editor.clipboard_provider.set_contents(value.into_owned()) { - log::error!("Couldn't set system clipboard content: {:?}", e); + bail!("Couldn't set system clipboard content: {:?}", e); } editor.set_status("yanked main selection to system clipboard".to_owned()); + Ok(()) } fn yank_main_selection_to_clipboard(cx: &mut Context) { - yank_main_selection_to_clipboard_impl(&mut cx.editor); + let _ = yank_main_selection_to_clipboard_impl(&mut cx.editor); } #[derive(Copy, Clone)] @@ -2909,7 +3033,7 @@ fn paste_impl( Some(transaction) } -fn paste_clipboard_impl(editor: &mut Editor, action: Paste) { +fn paste_clipboard_impl(editor: &mut Editor, action: Paste) -> anyhow::Result<()> { let (view, doc) = current!(editor); match editor @@ -2920,18 +3044,19 @@ fn paste_clipboard_impl(editor: &mut Editor, action: Paste) { Ok(Some(transaction)) => { doc.apply(&transaction, view.id); doc.append_changes_to_history(view.id); + Ok(()) } - Ok(None) => {} - Err(e) => log::error!("Couldn't get system clipboard contents: {:?}", e), + Ok(None) => Ok(()), + Err(e) => Err(e.context("Couldn't get system clipboard contents")), } } fn paste_clipboard_after(cx: &mut Context) { - paste_clipboard_impl(&mut cx.editor, Paste::After); + let _ = paste_clipboard_impl(&mut cx.editor, Paste::After); } fn paste_clipboard_before(cx: &mut Context) { - paste_clipboard_impl(&mut cx.editor, Paste::Before); + let _ = paste_clipboard_impl(&mut cx.editor, Paste::Before); } fn replace_with_yanked(cx: &mut Context) { @@ -2959,7 +3084,7 @@ fn replace_with_yanked(cx: &mut Context) { } } -fn replace_selections_with_clipboard_impl(editor: &mut Editor) { +fn replace_selections_with_clipboard_impl(editor: &mut Editor) -> anyhow::Result<()> { let (view, doc) = current!(editor); match editor.clipboard_provider.get_contents() { @@ -2974,13 +3099,14 @@ fn replace_selections_with_clipboard_impl(editor: &mut Editor) { doc.apply(&transaction, view.id); doc.append_changes_to_history(view.id); + Ok(()) } - Err(e) => log::error!("Couldn't get system clipboard contents: {:?}", e), + Err(e) => Err(e.context("Couldn't get system clipboard contents")), } } fn replace_selections_with_clipboard(cx: &mut Context) { - replace_selections_with_clipboard_impl(&mut cx.editor); + let _ = replace_selections_with_clipboard_impl(&mut cx.editor); } fn paste_after(cx: &mut Context) { @@ -3405,24 +3531,6 @@ fn jump_backward(cx: &mut Context) { }; } -fn window_mode(cx: &mut Context) { - cx.on_next_key(move |cx, event| { - if let KeyEvent { - code: KeyCode::Char(ch), - .. - } = event - { - match ch { - 'w' => rotate_view(cx), - 'h' => hsplit(cx), - 'v' => vsplit(cx), - 'q' => wclose(cx), - _ => {} - } - } - }) -} - fn rotate_view(cx: &mut Context) { cx.editor.focus_next() } @@ -3468,105 +3576,46 @@ fn select_register(cx: &mut Context) { }) } -fn view_mode(cx: &mut Context) { - cx.on_next_key(move |cx, event| { - if let KeyEvent { - code: KeyCode::Char(ch), - .. - } = event - { - // if lock, call cx again - // TODO: temporarily show VIE in the mode list - match ch { - // center - 'z' | 'c' - // top - | 't' - // bottom - | 'b' => { - let (view, doc) = current!(cx.editor); +fn align_view_top(cx: &mut Context) { + let (view, doc) = current!(cx.editor); + align_view(doc, view, Align::Top); +} - align_view(doc, view, match ch { - 'z' | 'c' => Align::Center, - 't' => Align::Top, - 'b' => Align::Bottom, - _ => unreachable!() - }); - } - 'm' => { - let (view, doc) = current!(cx.editor); - let pos = doc.selection(view.id).cursor(); - let pos = coords_at_pos(doc.text().slice(..), pos); +fn align_view_center(cx: &mut Context) { + let (view, doc) = current!(cx.editor); + align_view(doc, view, Align::Center); +} - const OFFSET: usize = 7; // gutters - view.first_col = pos.col.saturating_sub(((view.area.width as usize).saturating_sub(OFFSET)) / 2); - }, - 'h' => (), - 'j' => scroll(cx, 1, Direction::Forward), - 'k' => scroll(cx, 1, Direction::Backward), - 'l' => (), - _ => (), - } - } - }) +fn align_view_bottom(cx: &mut Context) { + let (view, doc) = current!(cx.editor); + align_view(doc, view, Align::Bottom); } -fn left_bracket_mode(cx: &mut Context) { - cx.on_next_key(move |cx, event| { - if let KeyEvent { - code: KeyCode::Char(ch), - .. - } = event - { - match ch { - 'd' => goto_prev_diag(cx), - 'D' => goto_first_diag(cx), - _ => (), - } - } - }) +fn align_view_middle(cx: &mut Context) { + let (view, doc) = current!(cx.editor); + let pos = doc.selection(view.id).cursor(); + let pos = coords_at_pos(doc.text().slice(..), pos); + + const OFFSET: usize = 7; // gutters + view.first_col = pos + .col + .saturating_sub(((view.area.width as usize).saturating_sub(OFFSET)) / 2); } -fn right_bracket_mode(cx: &mut Context) { - cx.on_next_key(move |cx, event| { - if let KeyEvent { - code: KeyCode::Char(ch), - .. - } = event - { - match ch { - 'd' => goto_next_diag(cx), - 'D' => goto_last_diag(cx), - _ => (), - } - } - }) +fn scroll_up(cx: &mut Context) { + scroll(cx, cx.count(), Direction::Backward); } -use helix_core::surround; -use helix_core::textobject; +fn scroll_down(cx: &mut Context) { + scroll(cx, cx.count(), Direction::Forward); +} -fn match_mode(cx: &mut Context) { - let count = cx.count; - cx.on_next_key(move |cx, event| { - if let KeyEvent { - code: KeyCode::Char(ch), - .. - } = event - { - // FIXME: count gets reset because of cx.on_next_key() - cx.count = count; - match ch { - 'm' => match_brackets(cx), - 's' => surround_add(cx), - 'r' => surround_replace(cx), - 'd' => surround_delete(cx), - 'a' => select_textobject(cx, textobject::TextObject::Around), - 'i' => select_textobject(cx, textobject::TextObject::Inside), - _ => (), - } - } - }) +fn select_textobject_around(cx: &mut Context) { + select_textobject(cx, textobject::TextObject::Around); +} + +fn select_textobject_inner(cx: &mut Context) { + select_textobject(cx, textobject::TextObject::Inside); } fn select_textobject(cx: &mut Context, objtype: textobject::TextObject) { @@ -3784,7 +3833,7 @@ mode_info! { } mode_info! { - /// goto mode + /// goto /// /// When specified with a count, it will go to that line without entering the mode. goto_mode, GOTO_MODE, goto_prehook, @@ -3815,3 +3864,68 @@ mode_info! { /// last accessed file "a" => goto_last_accessed_file, } + +mode_info! { + /// window + window_mode, WINDOW_MODE, + /// rotate + "w" | "C-w" => rotate_view, + /// horizontal split + "h" => hsplit, + /// vertical split + "v" => vsplit, + /// close + "q" => wclose, +} + +mode_info! { + /// match + match_mode, MATCH_MODE, + /// matching character + "m" => match_brackets, + /// surround add + "s" => surround_add, + /// surround replace + "r" => surround_replace, + /// surround delete + "d" => surround_delete, + /// around object + "a" => select_textobject_around, + /// inside object + "i" => select_textobject_inner, +} + +mode_info! { + /// select to previous + left_bracket_mode, LEFT_BRACKET_MODE, + /// previous diagnostic + "d" => goto_prev_diag, + /// diagnostic (first) + "D" => goto_first_diag, +} + +mode_info! { + /// select to next + right_bracket_mode, RIGHT_BRACKET_MODE, + /// diagnostic + "d" => goto_next_diag, + /// diagnostic (last) + "D" => goto_last_diag, +} + +mode_info! { + /// view + view_mode, VIEW_MODE, + /// align view top + "t" => align_view_top, + /// align view center + "z" | "c" => align_view_center, + /// align view bottom + "b" => align_view_bottom, + /// align view middle + "m" => align_view_middle, + /// scroll up + "k" => scroll_up, + /// scroll down + "j" => scroll_down, +} diff --git a/helix-term/src/keymap.rs b/helix-term/src/keymap.rs index d815e006..32994c37 100644 --- a/helix-term/src/keymap.rs +++ b/helix-term/src/keymap.rs @@ -82,6 +82,10 @@ impl Default for Keymaps { key!('r') => Command::replace, key!('R') => Command::replace_with_yanked, + key!('~') => Command::switch_case, + alt!('`') => Command::switch_to_uppercase, + key!('`') => Command::switch_to_lowercase, + key!(Home) => Command::goto_line_start, key!(End) => Command::goto_line_end, @@ -120,7 +124,6 @@ impl Default for Keymaps { alt!(';') => Command::flip_selections, key!('%') => Command::select_all, key!('x') => Command::extend_line, - key!('x') => Command::extend_line, key!('X') => Command::extend_to_line_bounds, // crop_to_whole_line diff --git a/helix-term/src/ui/editor.rs b/helix-term/src/ui/editor.rs index d374d9b6..40b57b85 100644 --- a/helix-term/src/ui/editor.rs +++ b/helix-term/src/ui/editor.rs @@ -64,6 +64,7 @@ impl EditorView { surface: &mut Surface, theme: &Theme, is_focused: bool, + loader: &syntax::Loader, ) { let area = Rect::new( view.area.x + OFFSET, @@ -72,7 +73,7 @@ impl EditorView { view.area.height.saturating_sub(1), ); // - 1 for statusline - self.render_buffer(doc, view, area, surface, theme, is_focused); + self.render_buffer(doc, view, area, surface, theme, is_focused, loader); // if we're not at the edge of the screen, draw a right border if viewport.right() != view.area.right() { @@ -98,6 +99,7 @@ impl EditorView { self.render_statusline(doc, view, area, surface, theme, is_focused); } + #[allow(clippy::too_many_arguments)] pub fn render_buffer( &self, doc: &Document, @@ -106,6 +108,7 @@ impl EditorView { surface: &mut Surface, theme: &Theme, is_focused: bool, + loader: &syntax::Loader, ) { let text = doc.text().slice(..); @@ -122,8 +125,26 @@ impl EditorView { // TODO: range doesn't actually restrict source, just highlight range let highlights: Vec<_> = match doc.syntax() { Some(syntax) => { + let scopes = theme.scopes(); syntax - .highlight_iter(text.slice(..), Some(range), None, |_| None) + .highlight_iter(text.slice(..), Some(range), None, |language| { + loader + .language_config_for_scope(&format!("source.{}", language)) + .and_then(|language_config| { + let config = language_config.highlight_config(scopes)?; + let config_ref = config.as_ref(); + // SAFETY: the referenced `HighlightConfiguration` behind + // the `Arc` is guaranteed to remain valid throughout the + // duration of the highlight. + let config_ref = unsafe { + std::mem::transmute::< + _, + &'static syntax::HighlightConfiguration, + >(config_ref) + }; + Some(config_ref) + }) + }) .collect() // TODO: we collect here to avoid holding the lock, fix later } None => vec![Ok(HighlightEvent::Source { @@ -735,7 +756,16 @@ impl Component for EditorView { for (view, is_focused) in cx.editor.tree.views() { let doc = cx.editor.document(view.doc).unwrap(); - self.render_view(doc, view, area, surface, &cx.editor.theme, is_focused); + let loader = &cx.editor.syn_loader; + self.render_view( + doc, + view, + area, + surface, + &cx.editor.theme, + is_focused, + loader, + ); } if let Some(info) = std::mem::take(&mut cx.editor.autoinfo) { diff --git a/helix-tui/Cargo.toml b/helix-tui/Cargo.toml index 7f98144c..33a9427a 100644 --- a/helix-tui/Cargo.toml +++ b/helix-tui/Cargo.toml @@ -18,7 +18,7 @@ default = ["crossterm"] [dependencies] bitflags = "1.0" cassowary = "0.3" -unicode-segmentation = "1.2" +unicode-segmentation = "1.8" crossterm = { version = "0.20", optional = true } serde = { version = "1", "optional" = true, features = ["derive"]} helix-view = { version = "0.3", path = "../helix-view", features = ["term"] } diff --git a/helix-view/src/document.rs b/helix-view/src/document.rs index b917b902..a04af94d 100644 --- a/helix-view/src/document.rs +++ b/helix-view/src/document.rs @@ -456,14 +456,16 @@ impl Document { theme: Option<&Theme>, config_loader: Option<&syntax::Loader>, ) -> Result<Self, Error> { - if !path.exists() { - return Ok(Self::default()); - } + let (mut rope, encoding) = if path.exists() { + let mut file = + std::fs::File::open(&path).context(format!("unable to open {:?}", path))?; + from_reader(&mut file, encoding)? + } else { + let encoding = encoding.unwrap_or(encoding_rs::UTF_8); + (Rope::from(DEFAULT_LINE_ENDING.as_str()), encoding) + }; - let mut file = std::fs::File::open(&path).context(format!("unable to open {:?}", path))?; - let (mut rope, encoding) = from_reader(&mut file, encoding)?; let line_ending = with_line_ending(&mut rope); - let mut doc = Self::from(rope, Some(encoding)); // set the path and try detecting the language diff --git a/helix-view/src/editor.rs b/helix-view/src/editor.rs index 4f01cce4..cd9d0a92 100644 --- a/helix-view/src/editor.rs +++ b/helix-view/src/editor.rs @@ -39,6 +39,7 @@ pub struct Editor { #[derive(Debug, Copy, Clone)] pub enum Action { + Load, Replace, HorizontalSplit, VerticalSplit, @@ -97,16 +98,14 @@ impl Editor { self._refresh(); } - pub fn set_theme_from_name(&mut self, theme: &str) { - let theme = match self.theme_loader.load(theme.as_ref()) { - Ok(theme) => theme, - Err(e) => { - log::warn!("failed setting theme `{}` - {}", theme, e); - return; - } - }; - + pub fn set_theme_from_name(&mut self, theme: &str) -> anyhow::Result<()> { + use anyhow::Context; + let theme = self + .theme_loader + .load(theme.as_ref()) + .with_context(|| format!("failed setting theme `{}`", theme))?; self.set_theme(theme); + Ok(()) } fn _refresh(&mut self) { @@ -153,6 +152,9 @@ impl Editor { return; } + Action::Load => { + return; + } Action::HorizontalSplit => { let view = View::new(id); let view_id = self.tree.split(view, Layout::Horizontal); diff --git a/runtime/grammars/.gitkeep b/runtime/grammars/.gitkeep new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/runtime/grammars/.gitkeep diff --git a/runtime/queries/c/highlights.scm b/runtime/queries/c/highlights.scm index 36fe47d9..258e07e7 100644 --- a/runtime/queries/c/highlights.scm +++ b/runtime/queries/c/highlights.scm @@ -54,6 +54,8 @@ "." @punctuation.delimiter ";" @punctuation.delimiter +(enumerator) @type.enum.variant + (string_literal) @string (system_lib_string) @string diff --git a/runtime/queries/rust/highlights.scm b/runtime/queries/rust/highlights.scm index b72216f6..5e469e67 100644 --- a/runtime/queries/rust/highlights.scm +++ b/runtime/queries/rust/highlights.scm @@ -1,213 +1,336 @@ -; Identifier conventions +; ------- +; Tree-Sitter doesn't allow overrides in regards to captures, +; though it is possible to affect the child node of a captured +; node. Thus, the approach here is to flip the order so that +; overrides are unnecessary. +; ------- -; Assume all-caps names are constants -((identifier) @constant - (#match? @constant "^[A-Z][A-Z\\d_]+$'")) -; Assume other uppercase names are enum constructors -((identifier) @constructor - (#match? @constructor "^[A-Z]")) +; ------- +; Types +; ------- -; Assume that uppercase names in paths are types -(mod_item - name: (identifier) @namespace) -(scoped_identifier - path: (identifier) @namespace) -(scoped_identifier - (scoped_identifier - name: (identifier) @namespace)) -(scoped_type_identifier - path: (identifier) @namespace) -(scoped_type_identifier - (scoped_identifier - name: (identifier) @namespace)) +; --- +; Primitives +; --- -((scoped_identifier - path: (identifier) @type) - (#match? @type "^[A-Z]")) -((scoped_identifier - path: (scoped_identifier - name: (identifier) @type)) - (#match? @type "^[A-Z]")) +(escape_sequence) @escape +(primitive_type) @type.builtin +(boolean_literal) @constant.builtin +[ + (integer_literal) + (float_literal) +] @number +[ + (char_literal) + (string_literal) + (raw_string_literal) +] @string +[ + (line_comment) + (block_comment) +] @comment -; Namespaces +; --- +; Extraneous +; --- -(crate) @namespace -(extern_crate_declaration - (crate) - name: (identifier) @namespace) -(scoped_use_list - path: (identifier) @namespace) -(scoped_use_list - path: (scoped_identifier - (identifier) @namespace)) -(use_list (scoped_identifier (identifier) @namespace . (_))) +(self) @variable.builtin +(enum_variant (identifier) @type.enum.variant) -; Function calls +(field_initializer + (field_identifier) @property) +(shorthand_field_initializer) @variable +(shorthand_field_identifier) @variable -(call_expression - function: (identifier) @function) -(call_expression - function: (field_expression - field: (field_identifier) @function.method)) -(call_expression - function: (scoped_identifier - "::" - name: (identifier) @function)) +(lifetime + "'" @label + (identifier) @label) +(loop_label + (identifier) @type) -(generic_function - function: (identifier) @function) -(generic_function - function: (scoped_identifier - name: (identifier) @function)) -(generic_function - function: (field_expression - field: (field_identifier) @function.method)) +; --- +; Punctuation +; --- -(macro_invocation - macro: (identifier) @function.macro - "!" @function.macro) -(macro_invocation - macro: (scoped_identifier - (identifier) @function.macro .)) +[ + "::" + "." + ";" +] @punctuation.delimiter -; (metavariable) @variable -(metavariable) @function.macro +[ + "(" + ")" + "[" + "]" +] @punctuation.bracket +(type_arguments + [ + "<" + ">" + ] @punctuation.bracket) +(type_parameters + [ + "<" + ">" + ] @punctuation.bracket) -"$" @function.macro +; --- +; Parameters +; --- -; Function definitions +(parameter + pattern: (identifier) @variable.parameter) +(closure_parameters + (identifier) @variable.parameter) -(function_item (identifier) @function) -(function_signature_item (identifier) @function) -; Other identifiers -(type_identifier) @type -(primitive_type) @type.builtin -(field_identifier) @property +; ------- +; Keywords +; ------- -(line_comment) @comment -(block_comment) @comment +(for_expression + "for" @keyword.control) +((identifier) @keyword.control + (#match? @keyword.control "^yield$")) +[ + "while" + "loop" + "in" + "break" + "continue" -"(" @punctuation.bracket -")" @punctuation.bracket -"[" @punctuation.bracket -"]" @punctuation.bracket + "match" + "if" + "else" + "return" + + "await" +] @keyword.control + +[ + (crate) + (super) + "as" + "use" + "pub" + "mod" + "extern" + + "fn" + "struct" + "enum" + "impl" + "where" + "trait" + "for" + + "type" + "union" + "unsafe" + "default" + "macro_rules!" + + "let" + "ref" + "move" + + "dyn" + "static" + "const" + "async" +] @keyword -(type_arguments - "<" @punctuation.bracket - ">" @punctuation.bracket) -(type_parameters - "<" @punctuation.bracket - ">" @punctuation.bracket) - -"::" @punctuation.delimiter -"." @punctuation.delimiter -";" @punctuation.delimiter - -(parameter (identifier) @variable.parameter) -(closure_parameters (_) @variable.parameter) - -(lifetime (identifier) @label) - -"async" @keyword -"break" @keyword -"const" @keyword -"continue" @keyword -(crate) @keyword -"default" @keyword -"dyn" @keyword -"else" @keyword -"enum" @keyword -"extern" @keyword -"fn" @keyword -"for" @keyword -"if" @keyword -"impl" @keyword -"in" @keyword -"let" @keyword -"let" @keyword -"loop" @keyword -"macro_rules!" @keyword -"match" @keyword -"mod" @keyword -"move" @keyword -"pub" @keyword -"ref" @keyword -"return" @keyword -"static" @keyword -"struct" @keyword -"trait" @keyword -"type" @keyword -"union" @keyword -"unsafe" @keyword -"use" @keyword -"where" @keyword -"while" @keyword (mutable_specifier) @keyword.mut -(use_list (self) @keyword) -(scoped_use_list (self) @keyword) -(scoped_identifier (self) @keyword) -(super) @keyword -"as" @keyword -(self) @variable.builtin -[ -(char_literal) -(string_literal) -(raw_string_literal) -] @string -(boolean_literal) @constant.builtin -(integer_literal) @number -(float_literal) @number +; ------- +; Guess Other Types +; ------- -(escape_sequence) @escape +((identifier) @constant + (#match? @constant "^[A-Z][A-Z\\d_]+$")) + +; --- +; PascalCase identifiers in call_expressions (e.g. `Ok()`) +; are assumed to be enum constructors. +; --- + +(call_expression + function: [ + ((identifier) @type.variant + (#match? @type.variant "^[A-Z]")) + (scoped_identifier + name: ((identifier) @type.variant + (#match? @type.variant "^[A-Z]"))) + ]) + +; --- +; Assume that types in match arms are enums and not +; tuple structs. Same for `if let` expressions. +; --- + +(match_pattern + (scoped_identifier + name: (identifier) @constructor)) +(tuple_struct_pattern + type: [ + ((identifier) @constructor) + (scoped_identifier + name: (identifier) @constructor) + ]) +(struct_pattern + type: [ + ((type_identifier) @constructor) + (scoped_type_identifier + name: (type_identifier) @constructor) + ]) + +; --- +; Other PascalCase identifiers are assumed to be structs. +; --- +((identifier) @type + (#match? @type "^[A-Z]")) + + + +; ------- +; Functions +; ------- + +(call_expression + function: [ + ((identifier) @function) + (scoped_identifier + name: (identifier) @function) + (field_expression + field: (field_identifier) @function) + ]) +(generic_function + function: [ + ((identifier) @function) + (scoped_identifier + name: (identifier) @function) + (field_expression + field: (field_identifier) @function.method) + ]) + +(function_item + name: (identifier) @function) + +; --- +; Macros +; --- + +(meta_item + (identifier) @attribute) (attribute_item) @attribute (inner_attribute_item) @attribute +(macro_definition + name: (identifier) @function.macro) +(macro_invocation + macro: [ + ((identifier) @function.macro) + (scoped_identifier + name: (identifier) @function.macro) + ] + "!" @function.macro) + +(metavariable) @variable.parameter +(fragment_specifier) @variable.parameter + + + +; ------- +; Operators +; ------- + [ -"*" -"'" -"->" -"=>" -"<=" -"=" -"==" -"!" -"!=" -"%" -"%=" -"&" -"&=" -"&&" -"|" -"|=" -"||" -"^" -"^=" -"*" -"*=" -"-" -"-=" -"+" -"+=" -"/" -"/=" -">" -"<" -">=" -">>" -"<<" -">>=" -"@" -".." -"..=" -"'" + "*" + "'" + "->" + "=>" + "<=" + "=" + "==" + "!" + "!=" + "%" + "%=" + "&" + "&=" + "&&" + "|" + "|=" + "||" + "^" + "^=" + "*" + "*=" + "-" + "-=" + "+" + "+=" + "/" + "/=" + ">" + "<" + ">=" + ">>" + "<<" + ">>=" + "@" + ".." + "..=" + "'" ] @operator + + +; ------- +; Paths +; ------- + +(use_declaration + argument: (identifier) @namespace) +(use_wildcard + (identifier) @namespace) +(extern_crate_declaration + name: (identifier) @namespace) +(mod_item + name: (identifier) @namespace) +(scoped_use_list + path: (identifier)? @namespace) +(use_list + (identifier) @namespace) +(use_as_clause + path: (identifier)? @namespace + alias: (identifier) @namespace) + +; --- +; Remaining Paths +; --- + +(scoped_identifier + path: (identifier)? @namespace + name: (identifier) @namespace) +(scoped_type_identifier + path: (identifier) @namespace) + + + +; ------- +; Remaining Identifiers +; ------- + "?" @special + +(type_identifier) @type +(identifier) @variable +(field_identifier) @variable diff --git a/runtime/themes/dark_plus.toml b/runtime/themes/dark_plus.toml new file mode 100644 index 00000000..82cc6289 --- /dev/null +++ b/runtime/themes/dark_plus.toml @@ -0,0 +1,80 @@ +# Author: Shafkath Shuhan <shafkathshuhannyc@gmail.com> + +"namespace" = { fg = "type" } +"module" = { fg = "type" } +"type" = { fg = "type" } +"type.builtin" = { fg = "type" } + +"keyword" = { fg = "keyword" } +"keyword.directive" = { fg = "keyword" } +"function.macro" = { fg = "keyword" } +"variable.builtin" = { fg = "keyword" } +"label" = { fg = "keyword" } +"constant.builtin" = { fg = "keyword" } + +"punctuation" = { fg = "text" } +"punctuation.delimiter" = { fg = "text" } + +"keyword.control" = { fg = "special" } +"special" = { fg = "text" } +"operator" = { fg = "text" } + +"variable" = { fg = "variable" } +"variable.parameter" = { fg = "variable" } +"property" = { fg = "variable" } + +"attribute" = { fg = "fn_declaration" } +"function" = { fg = "fn_declaration" } +"function.builtin" = { fg = "fn_declaration" } + +"comment" = { fg = "#6A9955" } + +"constant" = { fg = "constant" } +"type.enum.variant" = { fg = "constant" } +"constructor" = { fg = "constant" } + +"string" = { fg = "#ce9178" } +"number" = { fg = "#b5cea8" } +"escape" = { fg = "#d7ba7d" } + +"ui.background" = { fg = "#d4d4d4", bg = "#1e1e1e" } + +"ui.help" = { bg = "widget" } +"ui.popup" = { bg = "widget" } +"ui.window" = { bg = "widget" } +"ui.menu.selected" = { bg = "widget" } + +"ui.cursor" = { fg = "cursor", modifiers = ["reversed"] } +"ui.cursor.primary" = { fg = "cursor", modifiers = ["reversed"] } +"ui.cursor.match" = { fg = "cursor", modifiers = ['underlined'] } + +"ui.selection" = { bg = "#3a3d41" } +"ui.selection.primary" = { bg = "#add6ff26" } + +"ui.linenr" = { fg = "#858585" } +"ui.linenr.selected" = { fg = "#c6c6c6" } + +"ui.statusline" = { fg = "#ffffff", bg = "#007acc" } +"ui.statusline.inactive" = { fg = "#ffffff", bg = "#007acc" } + +"ui.text" = { fg = "text", bg = "background" } +"ui.text.focus" = { fg = "#ffffff" } + +"warning" = { fg = "#cca700" } +"error" = { fg = "#f48771" } +"info" = { fg = "#75beff" } +"hint" = { fg = "#eeeeeeb3" } + +[palette] +type = "#4EC9B0" +keyword = "#569CD6" +regex = "#CE9178" +special = "#C586C0" +variable = "#9CDCFE" +fn_declaration = "#DCDCAA" +constant = "#4FC1FF" + +background = "#1e1e1e" +text = "#d4d4d4" +cursor = "#a6a6a6" +widget = "#252526" @@ -1,68 +1,81 @@ -"attribute" = "#dbbfef" # lilac -"keyword" = "#eccdba" # almond -"keyword.directive" = "#dbbfef" # lilac -- preprocessor comments (#if in C) -"namespace" = "#dbbfef" # lilac -"punctuation" = "#a4a0e8" # lavender -"punctuation.delimiter" = "#a4a0e8" # lavender -"operator" = "#dbbfef" # lilac -"special" = "#efba5d" # honey -# "property" = "#a4a0e8" # lavender -"property" = "#ffffff" # white -"variable" = "#a4a0e8" # lavender -# "variable" = "#eccdba" # almond TODO: metavariables only -"variable.parameter" = "#a4a0e8" # lavender -# TODO distinguish type from type.builtin? -"type" = "#ffffff" # white -"type.builtin" = "#ffffff" # white -"constructor" = "#dbbfef" # lilac -"function" = "#ffffff" # white -"function.macro" = "#dbbfef" # lilac -"function.builtin" = "#ffffff" # white -"comment" = "#697C81" # sirocco -"variable.builtin" = "#9ff28f" # mint -"constant" = "#ffffff" # white -"constant.builtin" = "#ffffff" # white -"string" = "#cccccc" # silver -"number" = "#e8dca0" # chamois -"escape" = "#efba5d" # honey +attribute = "lilac" +keyword = "almond" +"keyword.directive" = "lilac" # -- preprocessor comments (#if in C) +namespace = "lilac" +punctuation = "lavender" +"punctuation.delimiter" = "lavender" +operator = "lilac" +special = "honey" +property = "white" +variable = "lavender" +# variable = "almond" # TODO: metavariables only +"variable.parameter" = "lavender" +"variable.builtin" = "mint" +type = "white" +"type.builtin" = "white" # TODO: distinguish? +constructor = "lilac" +function = "white" +"function.macro" = "lilac" +"function.builtin" = "white" +comment = "sirocco" +constant = "white" +"constant.builtin" = "white" +string = "silver" +number = "chamois" +escape = "honey" # used for lifetimes -"label" = "#efba5d" # honey +label = "honey" -# TODO: diferentiate number builtin # TODO: diferentiate doc comment -# TODO: variable as lilac -# TODO: mod/use statements as white -# TODO: mod stuff as chamois -# # concat (ERROR) @syntax-error and "MISSING ;" selectors for errors -"module" = "#ff0000" +module = "#ff0000" -"ui.background" = { bg = "#3b224c" } # midnight -"ui.linenr" = { fg = "#5a5977" } # comet -"ui.linenr.selected" = { fg = "#dbbfef" } # lilac -"ui.statusline" = { fg = "#dbbfef", bg = "#281733" } # revolver -"ui.statusline.inactive" = { fg = "#a4a0e8", bg = "#281733" } # revolver -"ui.popup" = { bg = "#281733" } # revolver -"ui.window" = { fg = "#452859" } # bossa nova +"ui.background" = { bg = "midnight" } +"ui.linenr" = { fg = "comet" } +"ui.linenr.selected" = { fg = "lilac" } +"ui.statusline" = { fg = "lilac", bg = "revolver" } +"ui.statusline.inactive" = { fg = "lavender", bg = "revolver" } +"ui.popup" = { bg = "revolver" } +"ui.window" = { fg = "bossanova" } "ui.help" = { bg = "#7958DC", fg = "#171452" } -"ui.text" = { fg = "#a4a0e8" } # lavender -"ui.text.focus" = { fg = "#dbbfef" } # lilac +"ui.text" = { fg = "lavender" } +"ui.text.focus" = { fg = "lilac" } "ui.selection" = { bg = "#540099" } "ui.selection.primary" = { bg = "#540099" } # TODO: namespace ui.cursor as ui.selection.cursor? -"ui.cursor.select" = { bg = "#6F44F0" } -"ui.cursor.insert" = { bg = "#ffffff" } +"ui.cursor.select" = { bg = "delta" } +"ui.cursor.insert" = { bg = "white" } "ui.cursor.match" = { fg = "#212121", bg = "#6C6999" } "ui.cursor" = { modifiers = ["reversed"] } -"ui.menu.selected" = { fg = "#281733", bg = "#ffffff" } # revolver +"ui.menu.selected" = { fg = "revolver", bg = "white" } -"diagnostic" = { modifiers = ["underlined"] } +diagnostic = { modifiers = ["underlined"] } -"warning" = "#ffcd1c" -"error" = "#f47868" -"info" = "#6F44F0" -"hint" = "#cccccc" +warning = "lightning" +error = "apricot" +info = "delta" +hint = "silver" + +[palette] +white = "#ffffff" +lilac = "#dbbfef" +lavender = "#a4a0e8" +comet = "#5a5977" +bossanova = "#452859" +midnight = "#3b224c" +revolver = "#281733" + +silver = "#cccccc" +sirocco = "#697C81" +mint = "#9ff28f" +almond = "#eccdba" +chamois = "#E8DCA0" +honey = "#efba5d" + +apricot = "#f47868" +lightning = "#ffcd1c" +delta = "#6F44F0" |