aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNathan Vegdahl2021-07-17 17:49:03 +0000
committerNathan Vegdahl2021-07-17 17:49:03 +0000
commita77274e8bb33ff08f5411ec4df168b576c0c8fa5 (patch)
tree1a1c3735e1f9ea46e87f7fe60b1f81fdb2e5df5b
parentb4c59b444cc4963f95a95fe10f166e58ef857288 (diff)
parent6cba62b49917fde7c5876a1cce9d3883c6bef6c9 (diff)
Merge branch 'master' into great_line_ending_and_cursor_range_cleanup
-rw-r--r--.github/workflows/release.yml3
-rw-r--r--.gitignore1
-rw-r--r--Cargo.lock57
-rw-r--r--README.md11
-rw-r--r--book/src/keymap.md43
-rw-r--r--book/src/themes.md2
-rw-r--r--helix-core/Cargo.toml2
-rw-r--r--helix-core/src/indent.rs4
-rw-r--r--helix-core/src/syntax.rs16
-rw-r--r--helix-lsp/Cargo.toml4
-rw-r--r--helix-syntax/Cargo.toml6
-rw-r--r--helix-syntax/build.rs199
-rw-r--r--helix-syntax/src/lib.rs115
-rw-r--r--helix-term/src/application.rs28
-rw-r--r--helix-term/src/commands.rs712
-rw-r--r--helix-term/src/keymap.rs5
-rw-r--r--helix-term/src/ui/editor.rs36
-rw-r--r--helix-tui/Cargo.toml2
-rw-r--r--helix-view/src/document.rs14
-rw-r--r--helix-view/src/editor.rs20
-rw-r--r--runtime/grammars/.gitkeep0
-rw-r--r--runtime/queries/c/highlights.scm2
-rw-r--r--runtime/queries/rust/highlights.scm483
-rw-r--r--runtime/themes/dark_plus.toml80
-rw-r--r--theme.toml115
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
diff --git a/.gitignore b/.gitignore
index 1a42b440..346d0946 100644
--- a/.gitignore
+++ b/.gitignore
@@ -3,3 +3,4 @@ target
helix-term/rustfmt.toml
helix-syntax/languages/
result
+runtime/grammars
diff --git a/Cargo.lock b/Cargo.lock
index 2cd202f3..e262f081 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -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"
diff --git a/README.md b/README.md
index 56eddb31..b4f5b867 100644
--- a/README.md
+++ b/README.md
@@ -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"
diff --git a/theme.toml b/theme.toml
index 13753dc6..67b7dc57 100644
--- a/theme.toml
+++ b/theme.toml
@@ -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"