Merge branch 'master' into great_line_ending_and_cursor_range_cleanup

pull/376/head
Nathan Vegdahl 3 years ago
commit a77274e8bb

@ -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

1
.gitignore vendored

@ -3,3 +3,4 @@ target
helix-term/rustfmt.toml
helix-syntax/languages/
result
runtime/grammars

57
Cargo.lock generated

@ -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",
]
@ -475,15 +473,6 @@ version = "0.4.7"
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"
@ -509,6 +498,16 @@ version = "0.2.97"
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"
@ -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:
```

@ -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

@ -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` | |

@ -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"

@ -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![],

@ -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(

@ -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"

@ -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"

@ -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

@ -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)
);

@ -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);

File diff suppressed because it is too large Load Diff

@ -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

@ -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) {

@ -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"] }

@ -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

@ -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);

@ -54,6 +54,8 @@
"." @punctuation.delimiter
";" @punctuation.delimiter
(enumerator) @type.enum.variant
(string_literal) @string
(system_lib_string) @string

@ -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

@ -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"

Loading…
Cancel
Save