on master help sign in
migrate grammar fetching/building code into helix-loader crate
4fc991fMichael Davis
Cargo.lock+17−2
@@ -358,12 +358,11 @@ dependencies = [
358name = "helix-core"
358name = "helix-core"
359version = "0.6.0"
359version = "0.6.0"
360dependencies = [
360dependencies = [
361− "anyhow",
362 "arc-swap",
361 "arc-swap",
363 "chrono",
362 "chrono",
364 "encoding_rs",
363 "encoding_rs",
365 "etcetera",
364 "etcetera",
366− "libloading",
365+ "helix-loader",
367 "log",
366 "log",
368 "once_cell",
367 "once_cell",
369 "quickcheck",
368 "quickcheck",
@@ -397,6 +396,21 @@ dependencies = [
397 "which",
396 "which",
398]
397]
399
398
399+[[package]]
400+name = "helix-loader"
401+version = "0.6.0"
402+dependencies = [
403+ "anyhow",
404+ "cc",
405+ "etcetera",
406+ "libloading",
407+ "once_cell",
408+ "serde",
409+ "threadpool",
410+ "toml",
411+ "tree-sitter",
412+]
413+
400[[package]]
414[[package]]
401name = "helix-lsp"
415name = "helix-lsp"
402version = "0.6.0"
416version = "0.6.0"
@@ -432,6 +446,7 @@ dependencies = [
432 "grep-searcher",
446 "grep-searcher",
433 "helix-core",
447 "helix-core",
434 "helix-dap",
448 "helix-dap",
449+ "helix-loader",
435 "helix-lsp",
450 "helix-lsp",
436 "helix-tui",
451 "helix-tui",
437 "helix-view",
452 "helix-view",
Cargo.toml+1
@@ -6,6 +6,7 @@ members = [
6 "helix-tui",
6 "helix-tui",
7 "helix-lsp",
7 "helix-lsp",
8 "helix-dap",
8 "helix-dap",
9+ "helix-loader",
9 "xtask",
10 "xtask",
10]
11]
11
12
book/src/guides/adding_languages.md+5−5
@@ -51,16 +51,16 @@ Grammar configuration takes these keys:
51| Key | Description |
51| Key | Description |
52| --- | ----------- |
52| --- | ----------- |
53| `name` | The name of the tree-sitter grammar |
53| `name` | The name of the tree-sitter grammar |
54−| `path` | A path within the grammar directory which should be built. Some grammar repositories host multiple grammars (for example `tree-sitter-typescript` and `tree-sitter-ocaml`) in subdirectories. This key is used to point `hx --build-grammars` to the correct path for compilation. When ommitted, the root of the grammar directory is used |
55| `source` | The method of fetching the grammar - a table with a schema defined below |
54| `source` | The method of fetching the grammar - a table with a schema defined below |
56
55
57Where `source` is a table with either these keys when using a grammar from a
56Where `source` is a table with either these keys when using a grammar from a
58git repository:
57git repository:
59
58
60−| Key | Description |
59+| Key | Description |
61−| --- | ----------- |
60+| --- | ----------- |
62−| `git` | A git remote URL from which the grammar should be cloned |
61+| `git` | A git remote URL from which the grammar should be cloned |
63−| `rev` | The revision (commit hash or tag) which should be fetched |
62+| `rev` | The revision (commit hash or tag) which should be fetched |
63+| `subpath` | A path within the grammar directory which should be built. Some grammar repositories host multiple grammars (for example `tree-sitter-typescript` and `tree-sitter-ocaml`) in subdirectories. This key is used to point `hx --build-grammars` to the correct path for compilation. When omitted, the root of repository is used |
64
64
65Or a `path` key with an absolute path to a locally available grammar directory.
65Or a `path` key with an absolute path to a locally available grammar directory.
66
66
docs/architecture.md+9−7
@@ -1,11 +1,13 @@
1
1
2−| Crate | Description |
2+| Crate | Description |
3−| ----------- | ----------- |
3+| ----------- | ----------- |
4−| helix-core | Core editing primitives, functional. |
4+| helix-core | Core editing primitives, functional. |
5−| helix-lsp | Language server client |
5+| helix-lsp | Language server client |
6+| helix-dap | Debug Adapter Protocol (DAP) client |
7+| helix-loader | Functions for building, fetching, and loading external resources |
6−| helix-view | UI abstractions for use in backends, imperative shell. |
8+| helix-view | UI abstractions for use in backends, imperative shell. |
7−| helix-term | Terminal UI |
9+| helix-term | Terminal UI |
8−| helix-tui | TUI primitives, forked from tui-rs, inspired by Cursive |
10+| helix-tui | TUI primitives, forked from tui-rs, inspired by Cursive |
9
11
10
12
11This document contains a high-level overview of Helix internals.
13This document contains a high-level overview of Helix internals.
helix-core/Cargo.toml+3−3
@@ -13,6 +13,8 @@ include = ["src/**/*", "README.md"]
13[features]
13[features]
14
14
15[dependencies]
15[dependencies]
16+helix-loader = { version = "0.6", path = "../helix-loader" }
17+
16ropey = "1.3"
18ropey = "1.3"
17smallvec = "1.8"
19smallvec = "1.8"
18smartstring = "1.0.0"
20smartstring = "1.0.0"
@@ -33,13 +35,11 @@ toml = "0.5"
33
35
34similar = "2.1"
36similar = "2.1"
35
37
36−etcetera = "0.3"
37encoding_rs = "0.8"
38encoding_rs = "0.8"
38
39
39chrono = { version = "0.4", default-features = false, features = ["alloc", "std"] }
40chrono = { version = "0.4", default-features = false, features = ["alloc", "std"] }
40
41
41−libloading = "0.7"
42+etcetera = "0.3"
42−anyhow = "1"
43
43
44[dev-dependencies]
44[dev-dependencies]
45quickcheck = { version = "1", default-features = false }
45quickcheck = { version = "1", default-features = false }
helix-core/src/config.rs+2−25
@@ -1,33 +1,10 @@
1−use crate::merge_toml_values;
2−
3−/// Default bultin-in languages.toml.
4−pub fn default_lang_config() -> toml::Value {
5− toml::from_slice(include_bytes!("../../languages.toml"))
6− .expect("Could not parse bultin-in languages.toml to valid toml")
7−}
8−
9−/// User configured languages.toml file, merged with the default config.
10−pub fn user_lang_config() -> Result<toml::Value, toml::de::Error> {
11− let def_lang_conf = default_lang_config();
12− let data = std::fs::read(crate::config_dir().join("languages.toml"));
13− let user_lang_conf = match data {
14− Ok(raw) => {
15− let value = toml::from_slice(&raw)?;
16− merge_toml_values(def_lang_conf, value)
17− }
18− Err(_) => def_lang_conf,
19− };
20−
21− Ok(user_lang_conf)
22−}
23−
24/// Syntax configuration loader based on built-in languages.toml.
1/// Syntax configuration loader based on built-in languages.toml.
25pub fn default_syntax_loader() -> crate::syntax::Configuration {
2pub fn default_syntax_loader() -> crate::syntax::Configuration {
26− default_lang_config()
3+ helix_loader::default_lang_config()
27 .try_into()
4 .try_into()
28 .expect("Could not serialize built-in languages.toml")
5 .expect("Could not serialize built-in languages.toml")
29}
6}
30/// Syntax configuration loader based on user configured languages.toml.
7/// Syntax configuration loader based on user configured languages.toml.
31pub fn user_syntax_loader() -> Result<crate::syntax::Configuration, toml::de::Error> {
8pub fn user_syntax_loader() -> Result<crate::syntax::Configuration, toml::de::Error> {
32− user_lang_config()?.try_into()
9+ helix_loader::user_lang_config()?.try_into()
33}
10}
helix-core/src/indent.rs−2
@@ -444,8 +444,6 @@ where
444 debugger: None,
444 debugger: None,
445 auto_pairs: None,
445 auto_pairs: None,
446 }],
446 }],
447− grammar: vec![],
448− grammar_selection: None,
449 });
447 });
450
448
451 // set runtime path so we can find the queries
449 // set runtime path so we can find the queries
helix-core/src/lib.rs−141
@@ -33,9 +33,6 @@ pub mod unicode {
33 pub use unicode_width as width;
33 pub use unicode_width as width;
34}
34}
35
35
36−static RUNTIME_DIR: once_cell::sync::Lazy<std::path::PathBuf> =
37− once_cell::sync::Lazy::new(runtime_dir);
38−
39pub fn find_first_non_whitespace_char(line: RopeSlice) -> Option<usize> {
36pub fn find_first_non_whitespace_char(line: RopeSlice) -> Option<usize> {
40 line.chars().position(|ch| !ch.is_whitespace())
37 line.chars().position(|ch| !ch.is_whitespace())
41}
38}
@@ -85,144 +82,6 @@ pub fn find_root(root: Option<&str>, root_markers: &[String]) -> Option<std::pat
85 }
82 }
86}
83}
87
84
88−pub fn runtime_dir() -> std::path::PathBuf {
89− if let Ok(dir) = std::env::var("HELIX_RUNTIME") {
90− return dir.into();
91− }
92−
93− const RT_DIR: &str = "runtime";
94− let conf_dir = config_dir().join(RT_DIR);
95− if conf_dir.exists() {
96− return conf_dir;
97− }
98−
99− if let Ok(dir) = std::env::var("CARGO_MANIFEST_DIR") {
100− // this is the directory of the crate being run by cargo, we need the workspace path so we take the parent
101− return std::path::PathBuf::from(dir).parent().unwrap().join(RT_DIR);
102− }
103−
104− // fallback to location of the executable being run
105− std::env::current_exe()
106− .ok()
107− .and_then(|path| path.parent().map(|path| path.to_path_buf().join(RT_DIR)))
108− .unwrap()
109−}
110−
111−pub fn config_dir() -> std::path::PathBuf {
112− // TODO: allow env var override
113− let strategy = choose_base_strategy().expect("Unable to find the config directory!");
114− let mut path = strategy.config_dir();
115− path.push("helix");
116− path
117−}
118−
119−pub fn cache_dir() -> std::path::PathBuf {
120− // TODO: allow env var override
121− let strategy = choose_base_strategy().expect("Unable to find the config directory!");
122− let mut path = strategy.cache_dir();
123− path.push("helix");
124− path
125−}
126−
127−pub fn config_file() -> std::path::PathBuf {
128− config_dir().join("config.toml")
129−}
130−
131−pub fn lang_config_file() -> std::path::PathBuf {
132− config_dir().join("languages.toml")
133−}
134−
135−pub fn log_file() -> std::path::PathBuf {
136− cache_dir().join("helix.log")
137−}
138−
139−// right overrides left
140−pub fn merge_toml_values(left: toml::Value, right: toml::Value) -> toml::Value {
141− use toml::Value;
142−
143− fn get_name(v: &Value) -> Option<&str> {
144− v.get("name").and_then(Value::as_str)
145− }
146−
147− match (left, right) {
148− (Value::Array(mut left_items), Value::Array(right_items)) => {
149− left_items.reserve(right_items.len());
150− for rvalue in right_items {
151− let lvalue = get_name(&rvalue)
152− .and_then(|rname| left_items.iter().position(|v| get_name(v) == Some(rname)))
153− .map(|lpos| left_items.remove(lpos));
154− let mvalue = match lvalue {
155− Some(lvalue) => merge_toml_values(lvalue, rvalue),
156− None => rvalue,
157− };
158− left_items.push(mvalue);
159− }
160− Value::Array(left_items)
161− }
162− (Value::Table(mut left_map), Value::Table(right_map)) => {
163− for (rname, rvalue) in right_map {
164− match left_map.remove(&rname) {
165− Some(lvalue) => {
166− let merged_value = merge_toml_values(lvalue, rvalue);
167− left_map.insert(rname, merged_value);
168− }
169− None => {
170− left_map.insert(rname, rvalue);
171− }
172− }
173− }
174− Value::Table(left_map)
175− }
176− // Catch everything else we didn't handle, and use the right value
177− (_, value) => value,
178− }
179−}
180−
181−#[cfg(test)]
182−mod merge_toml_tests {
183− use super::merge_toml_values;
184−
185− #[test]
186− fn language_tomls() {
187− use toml::Value;
188−
189− const USER: &str = "
190− [[language]]
191− name = \"nix\"
192− test = \"bbb\"
193− indent = { tab-width = 4, unit = \" \", test = \"aaa\" }
194− ";
195−
196− let base: Value = toml::from_slice(include_bytes!("../../languages.toml"))
197− .expect("Couldn't parse built-in languages config");
198− let user: Value = toml::from_str(USER).unwrap();
199−
200− let merged = merge_toml_values(base, user);
201− let languages = merged.get("language").unwrap().as_array().unwrap();
202− let nix = languages
203− .iter()
204− .find(|v| v.get("name").unwrap().as_str().unwrap() == "nix")
205− .unwrap();
206− let nix_indent = nix.get("indent").unwrap();
207−
208− // We changed tab-width and unit in indent so check them if they are the new values
209− assert_eq!(
210− nix_indent.get("tab-width").unwrap().as_integer().unwrap(),
211− 4
212− );
213− assert_eq!(nix_indent.get("unit").unwrap().as_str().unwrap(), " ");
214− // We added a new keys, so check them
215− assert_eq!(nix.get("test").unwrap().as_str().unwrap(), "bbb");
216− assert_eq!(nix_indent.get("test").unwrap().as_str().unwrap(), "aaa");
217− // We didn't change comment-token so it should be same
218− assert_eq!(nix.get("comment-token").unwrap().as_str().unwrap(), "#");
219− }
220−}
221−
222−pub use etcetera::home_dir;
223−
224−use etcetera::base_strategy::{choose_base_strategy, BaseStrategy};
225−
226pub use ropey::{Rope, RopeBuilder, RopeSlice};
85pub use ropey::{Rope, RopeBuilder, RopeSlice};
227
86
228// pub use tendril::StrTendril as Tendril;
87// pub use tendril::StrTendril as Tendril;
helix-core/src/path.rs+3−2
@@ -1,9 +1,10 @@
1+use etcetera::home_dir;
1use std::path::{Component, Path, PathBuf};
2use std::path::{Component, Path, PathBuf};
2
3
3/// Replaces users home directory from `path` with tilde `~` if the directory
4/// Replaces users home directory from `path` with tilde `~` if the directory
4/// is available, otherwise returns the path unchanged.
5/// is available, otherwise returns the path unchanged.
5pub fn fold_home_dir(path: &Path) -> PathBuf {
6pub fn fold_home_dir(path: &Path) -> PathBuf {
6− if let Ok(home) = super::home_dir() {
7+ if let Ok(home) = home_dir() {
7 if path.starts_with(&home) {
8 if path.starts_with(&home) {
8 // it's ok to unwrap, the path starts with home dir
9 // it's ok to unwrap, the path starts with home dir
9 return PathBuf::from("~").join(path.strip_prefix(&home).unwrap());
10 return PathBuf::from("~").join(path.strip_prefix(&home).unwrap());
@@ -20,7 +21,7 @@ pub fn expand_tilde(path: &Path) -> PathBuf {
20 let mut components = path.components().peekable();
21 let mut components = path.components().peekable();
21 if let Some(Component::Normal(c)) = components.peek() {
22 if let Some(Component::Normal(c)) = components.peek() {
22 if c == &"~" {
23 if c == &"~" {
23− if let Ok(home) = super::home_dir() {
24+ if let Ok(home) = home_dir() {
24 // it's ok to unwrap, the path starts with `~`
25 // it's ok to unwrap, the path starts with `~`
25 return home.join(path.strip_prefix("~").unwrap());
26 return home.join(path.strip_prefix("~").unwrap());
26 }
27 }
helix-core/src/syntax.rs+6−85
@@ -7,10 +7,6 @@ use crate::{
7 Rope, RopeSlice, Tendril,
7 Rope, RopeSlice, Tendril,
8};
8};
9
9
10−use anyhow::{Context, Result};
11−use libloading::{Library, Symbol};
12−use tree_sitter::Language;
13−
14use arc_swap::{ArcSwap, Guard};
10use arc_swap::{ArcSwap, Guard};
15use slotmap::{DefaultKey as LayerId, HopSlotMap};
11use slotmap::{DefaultKey as LayerId, HopSlotMap};
16
12
@@ -27,33 +23,7 @@ use std::{
27use once_cell::sync::{Lazy, OnceCell};
23use once_cell::sync::{Lazy, OnceCell};
28use serde::{Deserialize, Serialize};
24use serde::{Deserialize, Serialize};
29
25
30−#[cfg(unix)]
26+use helix_loader::grammar::{get_language, load_runtime_file};
31−pub const DYLIB_EXTENSION: &str = "so";
32−
33−#[cfg(windows)]
34−pub const DYLIB_EXTENSION: &str = "dll";
35−
36−fn replace_dashes_with_underscores(name: &str) -> String {
37− name.replace('-', "_")
38−}
39−
40−pub fn get_language(runtime_path: &std::path::Path, name: &str) -> Result<Language> {
41− let name = name.to_ascii_lowercase();
42− let mut library_path = runtime_path.join("grammars").join(&name);
43− library_path.set_extension(DYLIB_EXTENSION);
44−
45− let library = unsafe { Library::new(&library_path) }
46− .with_context(|| format!("Error opening dynamic library {:?}", &library_path))?;
47− let language_fn_name = format!("tree_sitter_{}", replace_dashes_with_underscores(&name));
48− let language = unsafe {
49− let language_fn: Symbol<unsafe extern "C" fn() -> Language> = library
50− .get(language_fn_name.as_bytes())
51− .with_context(|| format!("Failed to load symbol {}", language_fn_name))?;
52− language_fn()
53− };
54− std::mem::forget(library);
55− Ok(language)
56−}
57
27
58fn deserialize_regex<'de, D>(deserializer: D) -> Result<Option<Regex>, D::Error>
28fn deserialize_regex<'de, D>(deserializer: D) -> Result<Option<Regex>, D::Error>
59where
29where
@@ -81,19 +51,8 @@ where
81}
51}
82
52
83#[derive(Debug, Serialize, Deserialize)]
53#[derive(Debug, Serialize, Deserialize)]
84−#[serde(rename_all = "kebab-case", deny_unknown_fields)]
85pub struct Configuration {
54pub struct Configuration {
86− #[serde(rename = "use-grammars")]
87− pub grammar_selection: Option<GrammarSelection>,
88 pub language: Vec<LanguageConfiguration>,
55 pub language: Vec<LanguageConfiguration>,
89− pub grammar: Vec<GrammarConfiguration>,
90−}
91−
92−#[derive(Debug, Serialize, Deserialize)]
93−#[serde(rename_all = "lowercase", untagged)]
94−pub enum GrammarSelection {
95− Only(HashSet<String>),
96− Except(HashSet<String>),
97}
56}
98
57
99// largely based on tree-sitter/cli/src/loader.rs
58// largely based on tree-sitter/cli/src/loader.rs
@@ -279,29 +238,6 @@ pub struct IndentQuery {
279 pub outdent: HashSet<String>,
238 pub outdent: HashSet<String>,
280}
239}
281
240
282−#[derive(Debug, Serialize, Deserialize)]
283−pub struct GrammarConfiguration {
284− #[serde(rename = "name")]
285− pub grammar_id: String, // c-sharp, rust
286− pub source: GrammarSource,
287− pub path: Option<String>,
288−}
289−
290−#[derive(Debug, Serialize, Deserialize)]
291−#[serde(rename_all = "lowercase")]
292−#[serde(untagged)]
293−pub enum GrammarSource {
294− Local {
295− path: String,
296− },
297− Git {
298− #[serde(rename = "git")]
299− remote: String,
300− #[serde(rename = "rev")]
301− revision: String,
302− },
303−}
304−
305#[derive(Debug)]
241#[derive(Debug)]
306pub struct TextObjectQuery {
242pub struct TextObjectQuery {
307 pub query: Query,
243 pub query: Query,
@@ -398,14 +334,6 @@ impl TextObjectQuery {
398 }
334 }
399}
335}
400
336
401−pub fn load_runtime_file(language: &str, filename: &str) -> Result<String, std::io::Error> {
402− let path = crate::RUNTIME_DIR
403− .join("queries")
404− .join(language)
405− .join(filename);
406− std::fs::read_to_string(&path)
407−}
408−
409fn read_query(language: &str, filename: &str) -> String {
337fn read_query(language: &str, filename: &str) -> String {
410 static INHERITS_REGEX: Lazy<Regex> =
338 static INHERITS_REGEX: Lazy<Regex> =
411 Lazy::new(|| Regex::new(r";+\s*inherits\s*:?\s*([a-z_,()]+)\s*").unwrap());
339 Lazy::new(|| Regex::new(r";+\s*inherits\s*:?\s*([a-z_,()]+)\s*").unwrap());
@@ -451,12 +379,9 @@ impl LanguageConfiguration {
451 if highlights_query.is_empty() {
379 if highlights_query.is_empty() {
452 None
380 None
453 } else {
381 } else {
454− let language = get_language(
455− &crate::RUNTIME_DIR,
456− self.grammar.as_deref().unwrap_or(&self.language_id),
382+ let language = get_language(self.grammar.as_deref().unwrap_or(&self.language_id))
457− )
458− .map_err(|e| log::info!("{}", e))
383+ .map_err(|e| log::info!("{}", e))
459− .ok()?;
384+ .ok()?;
460 let config = HighlightConfiguration::new(
385 let config = HighlightConfiguration::new(
461 language,
386 language,
462 &highlights_query,
387 &highlights_query,
@@ -2116,13 +2041,9 @@ mod test {
2116 .map(String::from)
2041 .map(String::from)
2117 .collect();
2042 .collect();
2118
2043
2119− let loader = Loader::new(Configuration {
2044+ let loader = Loader::new(Configuration { language: vec![] });
2120− language: vec![],
2121− grammar: vec![],
2122− grammar_selection: None,
2123− });
2124
2045
2125− let language = get_language(&crate::RUNTIME_DIR, "Rust").unwrap();
2046+ let language = get_language("Rust").unwrap();
2126 let config = HighlightConfiguration::new(
2047 let config = HighlightConfiguration::new(
2127 language,
2048 language,
2128 &std::fs::read_to_string("../runtime/grammars/sources/rust/queries/highlights.scm")
2049 &std::fs::read_to_string("../runtime/grammars/sources/rust/queries/highlights.scm")
helix-loader/Cargo.tomlnew file+23
@@ -0,0 +1,23 @@
1+[package]
2+name = "helix-loader"
3+version = "0.6.0"
4+description = "A post-modern text editor."
5+authors = ["Blaž Hrastnik <blaz@mxxn.io>"]
6+edition = "2021"
7+license = "MPL-2.0"
8+categories = ["editor"]
9+repository = "https://github.com/helix-editor/helix"
10+homepage = "https://helix-editor.com"
11+
12+[dependencies]
13+anyhow = "1"
14+serde = { version = "1.0", features = ["derive"] }
15+toml = "0.5"
16+etcetera = "0.3"
17+tree-sitter = "0.20"
18+libloading = "0.7"
19+once_cell = "1.9"
20+
21+# cloning/compiling tree-sitter grammars
22+cc = { version = "1" }
23+threadpool = { version = "1.0" }
helix-loader/build.rsnew file+6
@@ -0,0 +1,6 @@
1+fn main() {
2+ println!(
3+ "cargo:rustc-env=BUILD_TARGET={}",
4+ std::env::var("TARGET").unwrap()
5+ );
6+}
helix-term/src/grammars.rs → helix-loader/src/grammar.rsrenamed+161−69
@@ -1,63 +1,155 @@
1use anyhow::{anyhow, Context, Result};
1use anyhow::{anyhow, Context, Result};
2+use libloading::{Library, Symbol};
3+use serde::{Deserialize, Serialize};
2use std::fs;
4use std::fs;
3use std::time::SystemTime;
5use std::time::SystemTime;
4use std::{
6use std::{
7+ collections::HashSet,
5 path::{Path, PathBuf},
8 path::{Path, PathBuf},
6 process::Command,
9 process::Command,
7 sync::mpsc::channel,
10 sync::mpsc::channel,
8};
11};
12+use tree_sitter::Language;
9
13
10−use helix_core::syntax::{GrammarConfiguration, GrammarSelection, GrammarSource, DYLIB_EXTENSION};
14+#[cfg(unix)]
15+const DYLIB_EXTENSION: &str = "so";
16+
17+#[cfg(windows)]
18+const DYLIB_EXTENSION: &str = "dll";
19+
20+#[derive(Debug, Serialize, Deserialize)]
21+struct Configuration {
22+ #[serde(rename = "use-grammars")]
23+ pub grammar_selection: Option<GrammarSelection>,
24+ pub grammar: Vec<GrammarConfiguration>,
25+}
26+
27+#[derive(Debug, Serialize, Deserialize)]
28+#[serde(rename_all = "lowercase", untagged)]
29+pub enum GrammarSelection {
30+ Only(HashSet<String>),
31+ Except(HashSet<String>),
32+}
33+
34+#[derive(Debug, Serialize, Deserialize)]
35+#[serde(deny_unknown_fields)]
36+pub struct GrammarConfiguration {
37+ #[serde(rename = "name")]
38+ pub grammar_id: String,
39+ pub source: GrammarSource,
40+}
41+
42+#[derive(Debug, Serialize, Deserialize)]
43+#[serde(rename_all = "lowercase", untagged)]
44+pub enum GrammarSource {
45+ Local {
46+ path: String,
47+ },
48+ Git {
49+ #[serde(rename = "git")]
50+ remote: String,
51+ #[serde(rename = "rev")]
52+ revision: String,
53+ subpath: Option<String>,
54+ },
55+}
11
56
12const BUILD_TARGET: &str = env!("BUILD_TARGET");
57const BUILD_TARGET: &str = env!("BUILD_TARGET");
13const REMOTE_NAME: &str = "origin";
58const REMOTE_NAME: &str = "origin";
14
59
60+pub fn get_language(name: &str) -> Result<Language> {
61+ let name = name.to_ascii_lowercase();
62+ let mut library_path = crate::runtime_dir().join("grammars").join(&name);
63+ library_path.set_extension(DYLIB_EXTENSION);
64+
65+ let library = unsafe { Library::new(&library_path) }
66+ .with_context(|| format!("Error opening dynamic library {library_path:?}"))?;
67+ let language_fn_name = format!("tree_sitter_{}", name.replace('-', "_"));
68+ let language = unsafe {
69+ let language_fn: Symbol<unsafe extern "C" fn() -> Language> = library
70+ .get(language_fn_name.as_bytes())
71+ .with_context(|| format!("Failed to load symbol {language_fn_name}"))?;
72+ language_fn()
73+ };
74+ std::mem::forget(library);
75+ Ok(language)
76+}
77+
15pub fn fetch_grammars() -> Result<()> {
78pub fn fetch_grammars() -> Result<()> {
16− run_parallel(get_grammar_configs(), fetch_grammar, "fetch")
79+ run_parallel(get_grammar_configs()?, fetch_grammar, "fetch")
17}
80}
18
81
19pub fn build_grammars() -> Result<()> {
82pub fn build_grammars() -> Result<()> {
20− run_parallel(get_grammar_configs(), build_grammar, "build")
83+ run_parallel(get_grammar_configs()?, build_grammar, "build")
84+}
85+
86+// Returns the set of grammar configurations the user requests.
87+// Grammars are configured in the default and user `languages.toml` and are
88+// merged. The `grammar_selection` key of the config is then used to filter
89+// down all grammars into a subset of the user's choosing.
90+fn get_grammar_configs() -> Result<Vec<GrammarConfiguration>> {
91+ let config: Configuration = crate::user_lang_config()
92+ .context("Could not parse languages.toml")?
93+ .try_into()?;
94+
95+ let grammars = match config.grammar_selection {
96+ Some(GrammarSelection::Only(selections)) => config
97+ .grammar
98+ .into_iter()
99+ .filter(|grammar| selections.contains(&grammar.grammar_id))
100+ .collect(),
101+ Some(GrammarSelection::Except(rejections)) => config
102+ .grammar
103+ .into_iter()
104+ .filter(|grammar| !rejections.contains(&grammar.grammar_id))
105+ .collect(),
106+ None => config.grammar,
107+ };
108+
109+ Ok(grammars)
21}
110}
22
111
23fn run_parallel<F>(grammars: Vec<GrammarConfiguration>, job: F, action: &'static str) -> Result<()>
112fn run_parallel<F>(grammars: Vec<GrammarConfiguration>, job: F, action: &'static str) -> Result<()>
24where
113where
25 F: Fn(GrammarConfiguration) -> Result<()> + std::marker::Send + 'static + Copy,
114 F: Fn(GrammarConfiguration) -> Result<()> + std::marker::Send + 'static + Copy,
26{
115{
27− let mut n_jobs = 0;
28 let pool = threadpool::Builder::new().build();
116 let pool = threadpool::Builder::new().build();
29 let (tx, rx) = channel();
117 let (tx, rx) = channel();
30
118
31 for grammar in grammars {
119 for grammar in grammars {
32 let tx = tx.clone();
120 let tx = tx.clone();
33− n_jobs += 1;
34
121
35 pool.execute(move || {
122 pool.execute(move || {
36− let grammar_id = grammar.grammar_id.clone();
37− job(grammar).unwrap_or_else(|err| {
38− eprintln!("Failed to {} grammar '{}'\n{}", action, grammar_id, err)
39− });
40−
41− // report progress
42− tx.send(1).unwrap();
123+ tx.send(job(grammar)).unwrap();
43 });
124 });
44 }
125 }
45 pool.join();
126 pool.join();
46
127
47− if rx.try_iter().sum::<usize>() == n_jobs {
128+ // TODO: print all failures instead of the first one found.
129+ if let Some(failure) = rx.try_iter().find_map(|result| result.err()) {
130+ Err(anyhow!(
131+ "Failed to {} some grammar(s).\n{}",
132+ action,
133+ failure
48− Ok(())
134+ ))
49 } else {
135 } else {
50− Err(anyhow!("Failed to {} some grammar(s).", action))
136+ Ok(())
51 }
137 }
52}
138}
53
139
54fn fetch_grammar(grammar: GrammarConfiguration) -> Result<()> {
140fn fetch_grammar(grammar: GrammarConfiguration) -> Result<()> {
55− if let GrammarSource::Git { remote, revision } = grammar.source {
141+ if let GrammarSource::Git {
142+ remote, revision, ..
143+ } = grammar.source
144+ {
56− let grammar_dir = helix_core::runtime_dir()
145+ let grammar_dir = crate::runtime_dir()
57 .join("grammars/sources")
146 .join("grammars/sources")
58− .join(grammar.grammar_id.clone());
147+ .join(&grammar.grammar_id);
59
148
149+ fs::create_dir_all(&grammar_dir).context(format!(
60− fs::create_dir_all(grammar_dir.clone()).expect("Could not create grammar directory");
150+ "Could not create grammar directory {:?}",
151+ grammar_dir
152+ ))?;
61
153
62 // create the grammar dir contains a git directory
154 // create the grammar dir contains a git directory
63 if !grammar_dir.join(".git").is_dir() {
155 if !grammar_dir.join(".git").is_dir() {
@@ -65,12 +157,12 @@ fn fetch_grammar(grammar: GrammarConfiguration) -> Result<()> {
65 }
157 }
66
158
67 // ensure the remote matches the configured remote
159 // ensure the remote matches the configured remote
68− if get_remote_url(&grammar_dir).map_or(true, |s| s.trim_end() != remote) {
160+ if get_remote_url(&grammar_dir).map_or(true, |s| s != remote) {
69 set_remote(&grammar_dir, &remote)?;
161 set_remote(&grammar_dir, &remote)?;
70 }
162 }
71
163
72 // ensure the revision matches the configured revision
164 // ensure the revision matches the configured revision
73− if get_revision(&grammar_dir).map_or(true, |s| s.trim_end() != revision) {
165+ if get_revision(&grammar_dir).map_or(true, |s| s != revision) {
74 // Fetch the exact revision from the remote.
166 // Fetch the exact revision from the remote.
75 // Supported by server-side git since v2.5.0 (July 2015),
167 // Supported by server-side git since v2.5.0 (July 2015),
76 // enabled by default on major git hosts.
168 // enabled by default on major git hosts.
@@ -94,33 +186,38 @@ fn fetch_grammar(grammar: GrammarConfiguration) -> Result<()> {
94
186
95// Sets the remote for a repository to the given URL, creating the remote if
187// Sets the remote for a repository to the given URL, creating the remote if
96// it does not yet exist.
188// it does not yet exist.
97−fn set_remote(repository: &Path, remote_url: &str) -> Result<String> {
189+fn set_remote(repository_dir: &Path, remote_url: &str) -> Result<String> {
190+ git(
191+ repository_dir,
98− git(repository, ["remote", "set-url", REMOTE_NAME, remote_url])
192+ ["remote", "set-url", REMOTE_NAME, remote_url],
193+ )
99− .or_else(|_| git(repository, ["remote", "add", REMOTE_NAME, remote_url]))
194+ .or_else(|_| git(repository_dir, ["remote", "add", REMOTE_NAME, remote_url]))
100}
195}
101
196
102−fn get_remote_url(repository: &Path) -> Option<String> {
197+fn get_remote_url(repository_dir: &Path) -> Option<String> {
103− git(repository, ["remote", "get-url", REMOTE_NAME]).ok()
198+ git(repository_dir, ["remote", "get-url", REMOTE_NAME]).ok()
104}
199}
105
200
106−fn get_revision(repository: &Path) -> Option<String> {
201+fn get_revision(repository_dir: &Path) -> Option<String> {
107− git(repository, ["rev-parse", "HEAD"]).ok()
202+ git(repository_dir, ["rev-parse", "HEAD"]).ok()
108}
203}
109
204
110// A wrapper around 'git' commands which returns stdout in success and a
205// A wrapper around 'git' commands which returns stdout in success and a
111// helpful error message showing the command, stdout, and stderr in error.
206// helpful error message showing the command, stdout, and stderr in error.
112−fn git<I, S>(repository: &Path, args: I) -> Result<String>
207+fn git<I, S>(repository_dir: &Path, args: I) -> Result<String>
113where
208where
114 I: IntoIterator<Item = S>,
209 I: IntoIterator<Item = S>,
115 S: AsRef<std::ffi::OsStr>,
210 S: AsRef<std::ffi::OsStr>,
116{
211{
117 let output = Command::new("git")
212 let output = Command::new("git")
118 .args(args)
213 .args(args)
119− .current_dir(repository)
214+ .current_dir(repository_dir)
120 .output()?;
215 .output()?;
121
216
122 if output.status.success() {
217 if output.status.success() {
123− Ok(String::from_utf8_lossy(&output.stdout).into_owned())
218+ Ok(String::from_utf8_lossy(&output.stdout)
219+ .trim_end()
220+ .to_owned())
124 } else {
221 } else {
125 // TODO: figure out how to display the git command using `args`
222 // TODO: figure out how to display the git command using `args`
126 Err(anyhow!(
223 Err(anyhow!(
@@ -132,52 +229,37 @@ where
132}
229}
133
230
134fn build_grammar(grammar: GrammarConfiguration) -> Result<()> {
231fn build_grammar(grammar: GrammarConfiguration) -> Result<()> {
232+ println!("{:#?}", grammar);
135− let grammar_dir = if let GrammarSource::Local { ref path } = grammar.source {
233+ let grammar_dir = if let GrammarSource::Local { path } = &grammar.source {
136− PathBuf::from(path)
234+ PathBuf::from(&path)
137 } else {
235 } else {
138− helix_core::runtime_dir()
236+ crate::runtime_dir()
139 .join("grammars/sources")
237 .join("grammars/sources")
140− .join(grammar.grammar_id.clone())
238+ .join(&grammar.grammar_id)
141 };
239 };
142
240
143− grammar_dir.read_dir().with_context(|| {
241+ let grammar_dir_entries = grammar_dir.read_dir().with_context(|| {
144− format!(
145− "The directory {:?} is empty, you probably need to use 'hx --fetch-grammars'?",
242+ format!("Failed to read directory {grammar_dir:?}. Did you use 'hx --fetch-grammars'?")
146− grammar_dir
147− )
148 })?;
243 })?;
149
244
245+ if grammar_dir_entries.count() == 0 {
246+ return Err(anyhow!(
247+ "Directory {grammar_dir:?} is empty. Did you use 'hx --fetch-grammars'?"
248+ ));
249+ };
250+
150− let path = match grammar.path {
251+ let path = match &grammar.source {
252+ GrammarSource::Git {
253+ subpath: Some(subpath),
254+ ..
151− Some(ref subpath) => grammar_dir.join(subpath),
255+ } => grammar_dir.join(subpath),
152− None => grammar_dir,
256+ _ => grammar_dir,
153 }
257 }
154 .join("src");
258 .join("src");
155
259
156 build_tree_sitter_library(&path, grammar)
260 build_tree_sitter_library(&path, grammar)
157}
261}
158
262
159−// Returns the set of grammar configurations the user requests.
160−// Grammars are configured in the default and user `languages.toml` and are
161−// merged. The `grammar_selection` key of the config is then used to filter
162−// down all grammars into a subset of the user's choosing.
163−fn get_grammar_configs() -> Vec<GrammarConfiguration> {
164− let config = helix_core::config::user_syntax_loader().expect("Could not parse languages.toml");
165−
166− match config.grammar_selection {
167− Some(GrammarSelection::Only(selections)) => config
168− .grammar
169− .into_iter()
170− .filter(|grammar| selections.contains(&grammar.grammar_id))
171− .collect(),
172− Some(GrammarSelection::Except(rejections)) => config
173− .grammar
174− .into_iter()
175− .filter(|grammar| !rejections.contains(&grammar.grammar_id))
176− .collect(),
177− None => config.grammar,
178− }
179−}
180−
181fn build_tree_sitter_library(src_path: &Path, grammar: GrammarConfiguration) -> Result<()> {
263fn build_tree_sitter_library(src_path: &Path, grammar: GrammarConfiguration) -> Result<()> {
182 let header_path = src_path;
264 let header_path = src_path;
183 let parser_path = src_path.join("parser.c");
265 let parser_path = src_path.join("parser.c");
@@ -193,8 +275,8 @@ fn build_tree_sitter_library(src_path: &Path, grammar: GrammarConfiguration) ->
193 None
275 None
194 }
276 }
195 };
277 };
196− let parser_lib_path = helix_core::runtime_dir().join("../runtime/grammars");
278+ let parser_lib_path = crate::runtime_dir().join("grammars");
197− let mut library_path = parser_lib_path.join(grammar.grammar_id.clone());
279+ let mut library_path = parser_lib_path.join(&grammar.grammar_id);
198 library_path.set_extension(DYLIB_EXTENSION);
280 library_path.set_extension(DYLIB_EXTENSION);
199
281
200 let recompile = needs_recompile(&library_path, &parser_path, &scanner_path)
282 let recompile = needs_recompile(&library_path, &parser_path, &scanner_path)
@@ -210,7 +292,7 @@ fn build_tree_sitter_library(src_path: &Path, grammar: GrammarConfiguration) ->
210 let mut config = cc::Build::new();
292 let mut config = cc::Build::new();
211 config
293 config
212 .cpp(true)
294 .cpp(true)
213− .opt_level(2)
295+ .opt_level(3)
214 .cargo_metadata(false)
296 .cargo_metadata(false)
215 .host(BUILD_TARGET)
297 .host(BUILD_TARGET)
216 .target(BUILD_TARGET);
298 .target(BUILD_TARGET);
@@ -245,7 +327,7 @@ fn build_tree_sitter_library(src_path: &Path, grammar: GrammarConfiguration) ->
245 .arg(header_path)
327 .arg(header_path)
246 .arg("-o")
328 .arg("-o")
247 .arg(&library_path)
329 .arg(&library_path)
248− .arg("-O2");
330+ .arg("-O3");
249 if let Some(scanner_path) = scanner_path.as_ref() {
331 if let Some(scanner_path) = scanner_path.as_ref() {
250 if scanner_path.extension() == Some("c".as_ref()) {
332 if scanner_path.extension() == Some("c".as_ref()) {
251 command.arg("-xc").arg("-std=c99").arg(scanner_path);
333 command.arg("-xc").arg("-std=c99").arg(scanner_path);
@@ -294,3 +376,13 @@ fn needs_recompile(
294fn mtime(path: &Path) -> Result<SystemTime> {
376fn mtime(path: &Path) -> Result<SystemTime> {
295 Ok(fs::metadata(path)?.modified()?)
377 Ok(fs::metadata(path)?.modified()?)
296}
378}
379+
380+/// Gives the contents of a file from a language's `runtime/queries/<lang>`
381+/// directory
382+pub fn load_runtime_file(language: &str, filename: &str) -> Result<String, std::io::Error> {
383+ let path = crate::RUNTIME_DIR
384+ .join("queries")
385+ .join(language)
386+ .join(filename);
387+ std::fs::read_to_string(&path)
388+}
helix-loader/src/lib.rsnew file+161
@@ -0,0 +1,161 @@
1+pub mod grammar;
2+
3+use etcetera::base_strategy::{choose_base_strategy, BaseStrategy};
4+
5+pub static RUNTIME_DIR: once_cell::sync::Lazy<std::path::PathBuf> =
6+ once_cell::sync::Lazy::new(runtime_dir);
7+
8+pub fn runtime_dir() -> std::path::PathBuf {
9+ if let Ok(dir) = std::env::var("HELIX_RUNTIME") {
10+ return dir.into();
11+ }
12+
13+ const RT_DIR: &str = "runtime";
14+ let conf_dir = config_dir().join(RT_DIR);
15+ if conf_dir.exists() {
16+ return conf_dir;
17+ }
18+
19+ if let Ok(dir) = std::env::var("CARGO_MANIFEST_DIR") {
20+ // this is the directory of the crate being run by cargo, we need the workspace path so we take the parent
21+ return std::path::PathBuf::from(dir).parent().unwrap().join(RT_DIR);
22+ }
23+
24+ // fallback to location of the executable being run
25+ std::env::current_exe()
26+ .ok()
27+ .and_then(|path| path.parent().map(|path| path.to_path_buf().join(RT_DIR)))
28+ .unwrap()
29+}
30+
31+pub fn config_dir() -> std::path::PathBuf {
32+ // TODO: allow env var override
33+ let strategy = choose_base_strategy().expect("Unable to find the config directory!");
34+ let mut path = strategy.config_dir();
35+ path.push("helix");
36+ path
37+}
38+
39+pub fn cache_dir() -> std::path::PathBuf {
40+ // TODO: allow env var override
41+ let strategy = choose_base_strategy().expect("Unable to find the config directory!");
42+ let mut path = strategy.cache_dir();
43+ path.push("helix");
44+ path
45+}
46+
47+pub fn config_file() -> std::path::PathBuf {
48+ config_dir().join("config.toml")
49+}
50+
51+pub fn lang_config_file() -> std::path::PathBuf {
52+ config_dir().join("languages.toml")
53+}
54+
55+pub fn log_file() -> std::path::PathBuf {
56+ cache_dir().join("helix.log")
57+}
58+
59+/// Default bultin-in languages.toml.
60+pub fn default_lang_config() -> toml::Value {
61+ toml::from_slice(include_bytes!("../../languages.toml"))
62+ .expect("Could not parse bultin-in languages.toml to valid toml")
63+}
64+
65+/// User configured languages.toml file, merged with the default config.
66+pub fn user_lang_config() -> Result<toml::Value, toml::de::Error> {
67+ let def_lang_conf = default_lang_config();
68+ let data = std::fs::read(crate::config_dir().join("languages.toml"));
69+ let user_lang_conf = match data {
70+ Ok(raw) => {
71+ let value = toml::from_slice(&raw)?;
72+ merge_toml_values(def_lang_conf, value)
73+ }
74+ Err(_) => def_lang_conf,
75+ };
76+
77+ Ok(user_lang_conf)
78+}
79+
80+// right overrides left
81+pub fn merge_toml_values(left: toml::Value, right: toml::Value) -> toml::Value {
82+ use toml::Value;
83+
84+ fn get_name(v: &Value) -> Option<&str> {
85+ v.get("name").and_then(Value::as_str)
86+ }
87+
88+ match (left, right) {
89+ (Value::Array(mut left_items), Value::Array(right_items)) => {
90+ left_items.reserve(right_items.len());
91+ for rvalue in right_items {
92+ let lvalue = get_name(&rvalue)
93+ .and_then(|rname| left_items.iter().position(|v| get_name(v) == Some(rname)))
94+ .map(|lpos| left_items.remove(lpos));
95+ let mvalue = match lvalue {
96+ Some(lvalue) => merge_toml_values(lvalue, rvalue),
97+ None => rvalue,
98+ };
99+ left_items.push(mvalue);
100+ }
101+ Value::Array(left_items)
102+ }
103+ (Value::Table(mut left_map), Value::Table(right_map)) => {
104+ for (rname, rvalue) in right_map {
105+ match left_map.remove(&rname) {
106+ Some(lvalue) => {
107+ let merged_value = merge_toml_values(lvalue, rvalue);
108+ left_map.insert(rname, merged_value);
109+ }
110+ None => {
111+ left_map.insert(rname, rvalue);
112+ }
113+ }
114+ }
115+ Value::Table(left_map)
116+ }
117+ // Catch everything else we didn't handle, and use the right value
118+ (_, value) => value,
119+ }
120+}
121+
122+#[cfg(test)]
123+mod merge_toml_tests {
124+ use super::merge_toml_values;
125+
126+ #[test]
127+ fn language_tomls() {
128+ use toml::Value;
129+
130+ const USER: &str = "
131+ [[language]]
132+ name = \"nix\"
133+ test = \"bbb\"
134+ indent = { tab-width = 4, unit = \" \", test = \"aaa\" }
135+ ";
136+
137+ let base: Value = toml::from_slice(include_bytes!("../../languages.toml"))
138+ .expect("Couldn't parse built-in languages config");
139+ let user: Value = toml::from_str(USER).unwrap();
140+
141+ let merged = merge_toml_values(base, user);
142+ let languages = merged.get("language").unwrap().as_array().unwrap();
143+ let nix = languages
144+ .iter()
145+ .find(|v| v.get("name").unwrap().as_str().unwrap() == "nix")
146+ .unwrap();
147+ let nix_indent = nix.get("indent").unwrap();
148+
149+ // We changed tab-width and unit in indent so check them if they are the new values
150+ assert_eq!(
151+ nix_indent.get("tab-width").unwrap().as_integer().unwrap(),
152+ 4
153+ );
154+ assert_eq!(nix_indent.get("unit").unwrap().as_str().unwrap(), " ");
155+ // We added a new keys, so check them
156+ assert_eq!(nix.get("test").unwrap().as_str().unwrap(), "bbb");
157+ assert_eq!(nix_indent.get("test").unwrap().as_str().unwrap(), "aaa");
158+ // We didn't change comment-token so it should be same
159+ assert_eq!(nix.get("comment-token").unwrap().as_str().unwrap(), "#");
160+ }
161+}
helix-term/Cargo.toml+1
@@ -26,6 +26,7 @@ helix-core = { version = "0.6", path = "../helix-core" }
26helix-view = { version = "0.6", path = "../helix-view" }
26helix-view = { version = "0.6", path = "../helix-view" }
27helix-lsp = { version = "0.6", path = "../helix-lsp" }
27helix-lsp = { version = "0.6", path = "../helix-lsp" }
28helix-dap = { version = "0.6", path = "../helix-dap" }
28helix-dap = { version = "0.6", path = "../helix-dap" }
29+helix-loader = { version = "0.6", path = "../helix-loader" }
29
30
30anyhow = "1"
31anyhow = "1"
31once_cell = "1.10"
32once_cell = "1.10"
helix-term/build.rs−5
@@ -14,10 +14,5 @@ fn main() {
14 None => env!("CARGO_PKG_VERSION").into(),
14 None => env!("CARGO_PKG_VERSION").into(),
15 };
15 };
16
16
17− println!(
18− "cargo:rustc-env=BUILD_TARGET={}",
19− std::env::var("TARGET").unwrap()
20− );
21−
22 println!("cargo:rustc-env=VERSION_AND_GIT_HASH={}", version);
17 println!("cargo:rustc-env=VERSION_AND_GIT_HASH={}", version);
23}
18}
helix-term/src/application.rs+3−3
@@ -61,10 +61,10 @@ impl Application {
61 let mut compositor = Compositor::new()?;
61 let mut compositor = Compositor::new()?;
62 let size = compositor.size();
62 let size = compositor.size();
63
63
64− let conf_dir = helix_core::config_dir();
64+ let conf_dir = helix_loader::config_dir();
65
65
66 let theme_loader =
66 let theme_loader =
67− std::sync::Arc::new(theme::Loader::new(&conf_dir, &helix_core::runtime_dir()));
67+ std::sync::Arc::new(theme::Loader::new(&conf_dir, &helix_loader::runtime_dir()));
68
68
69 let true_color = config.editor.true_color || crate::true_color();
69 let true_color = config.editor.true_color || crate::true_color();
70 let theme = config
70 let theme = config
@@ -109,7 +109,7 @@ impl Application {
109 compositor.push(editor_view);
109 compositor.push(editor_view);
110
110
111 if args.load_tutor {
111 if args.load_tutor {
112− let path = helix_core::runtime_dir().join("tutor.txt");
112+ let path = helix_loader::runtime_dir().join("tutor.txt");
113 editor.open(path, Action::VerticalSplit)?;
113 editor.open(path, Action::VerticalSplit)?;
114 // Unset path to prevent accidentally saving to the original tutor file.
114 // Unset path to prevent accidentally saving to the original tutor file.
115 doc_mut!(editor).set_path(None)?;
115 doc_mut!(editor).set_path(None)?;
helix-term/src/commands/typed.rs+1−1
@@ -828,7 +828,7 @@ fn tutor(
828 _args: &[Cow<str>],
828 _args: &[Cow<str>],
829 _event: PromptEvent,
829 _event: PromptEvent,
830) -> anyhow::Result<()> {
830) -> anyhow::Result<()> {
831− let path = helix_core::runtime_dir().join("tutor.txt");
831+ let path = helix_loader::runtime_dir().join("tutor.txt");
832 cx.editor.open(path, Action::Replace)?;
832 cx.editor.open(path, Action::Replace)?;
833 // Unset path to prevent accidentally saving to the original tutor file.
833 // Unset path to prevent accidentally saving to the original tutor file.
834 doc_mut!(cx.editor).set_path(None)?;
834 doc_mut!(cx.editor).set_path(None)?;
helix-term/src/health.rs+6−8
@@ -1,8 +1,6 @@
1use crossterm::style::{Color, Print, Stylize};
1use crossterm::style::{Color, Print, Stylize};
2−use helix_core::{
3− config::{default_syntax_loader, user_syntax_loader},
2+use helix_core::config::{default_syntax_loader, user_syntax_loader};
4− syntax::load_runtime_file,
3+use helix_loader::grammar::load_runtime_file;
5−};
6
4
7#[derive(Copy, Clone)]
5#[derive(Copy, Clone)]
8pub enum TsFeature {
6pub enum TsFeature {
@@ -43,10 +41,10 @@ impl TsFeature {
43
41
44/// Display general diagnostics.
42/// Display general diagnostics.
45pub fn general() {
43pub fn general() {
46− let config_file = helix_core::config_file();
44+ let config_file = helix_loader::config_file();
47− let lang_file = helix_core::lang_config_file();
45+ let lang_file = helix_loader::lang_config_file();
48− let log_file = helix_core::log_file();
46+ let log_file = helix_loader::log_file();
49− let rt_dir = helix_core::runtime_dir();
47+ let rt_dir = helix_loader::runtime_dir();
50
48
51 if config_file.exists() {
49 if config_file.exists() {
52 println!("Config file: {}", config_file.display());
50 println!("Config file: {}", config_file.display());
helix-term/src/lib.rs−1
@@ -7,7 +7,6 @@ pub mod commands;
7pub mod compositor;
7pub mod compositor;
8pub mod config;
8pub mod config;
9pub mod health;
9pub mod health;
10−pub mod grammars;
11pub mod job;
10pub mod job;
12pub mod keymap;
11pub mod keymap;
13pub mod ui;
12pub mod ui;
helix-term/src/main.rs+5−5
@@ -40,7 +40,7 @@ fn main() -> Result<()> {
40
40
41#[tokio::main]
41#[tokio::main]
42async fn main_impl() -> Result<i32> {
42async fn main_impl() -> Result<i32> {
43− let logpath = helix_core::log_file();
43+ let logpath = helix_loader::log_file();
44 let parent = logpath.parent().unwrap();
44 let parent = logpath.parent().unwrap();
45 if !parent.exists() {
45 if !parent.exists() {
46 std::fs::create_dir_all(parent).ok();
46 std::fs::create_dir_all(parent).ok();
@@ -105,21 +105,21 @@ FLAGS:
105 }
105 }
106
106
107 if args.fetch_grammars {
107 if args.fetch_grammars {
108− helix_term::grammars::fetch_grammars()?;
108+ helix_loader::grammar::fetch_grammars()?;
109 return Ok(0);
109 return Ok(0);
110 }
110 }
111
111
112 if args.build_grammars {
112 if args.build_grammars {
113− helix_term::grammars::build_grammars()?;
113+ helix_loader::grammar::build_grammars()?;
114 return Ok(0);
114 return Ok(0);
115 }
115 }
116
116
117− let conf_dir = helix_core::config_dir();
117+ let conf_dir = helix_loader::config_dir();
118 if !conf_dir.exists() {
118 if !conf_dir.exists() {
119 std::fs::create_dir_all(&conf_dir).ok();
119 std::fs::create_dir_all(&conf_dir).ok();
120 }
120 }
121
121
122− let config = match std::fs::read_to_string(helix_core::config_file()) {
122+ let config = match std::fs::read_to_string(helix_loader::config_file()) {
123 Ok(config) => toml::from_str(&config)
123 Ok(config) => toml::from_str(&config)
124 .map(merge_keys)
124 .map(merge_keys)
125 .unwrap_or_else(|err| {
125 .unwrap_or_else(|err| {
helix-term/src/ui/mod.rs+2−2
@@ -218,9 +218,9 @@ pub mod completers {
218 }
218 }
219
219
220 pub fn theme(_editor: &Editor, input: &str) -> Vec<Completion> {
220 pub fn theme(_editor: &Editor, input: &str) -> Vec<Completion> {
221− let mut names = theme::Loader::read_names(&helix_core::runtime_dir().join("themes"));
221+ let mut names = theme::Loader::read_names(&helix_loader::runtime_dir().join("themes"));
222 names.extend(theme::Loader::read_names(
222 names.extend(theme::Loader::read_names(
223− &helix_core::config_dir().join("themes"),
223+ &helix_loader::config_dir().join("themes"),
224 ));
224 ));
225 names.push("default".into());
225 names.push("default".into());
226 names.push("base16_default".into());
226 names.push("base16_default".into());
languages.toml+4−8
@@ -302,8 +302,7 @@ indent = { tab-width = 2, unit = " " }
302
302
303[[grammar]]
303[[grammar]]
304name = "typescript"
304name = "typescript"
305−source = { git = "https://github.com/tree-sitter/tree-sitter-typescript", rev = "3e897ea5925f037cfae2e551f8e6b12eec2a201a" }
305+source = { git = "https://github.com/tree-sitter/tree-sitter-typescript", rev = "3e897ea5925f037cfae2e551f8e6b12eec2a201a", subpath = "typescript" }
306−path = "typescript"
307
306
308[[language]]
307[[language]]
309name = "tsx"
308name = "tsx"
@@ -317,8 +316,7 @@ indent = { tab-width = 2, unit = " " }
317
316
318[[grammar]]
317[[grammar]]
319name = "tsx"
318name = "tsx"
320−source = { git = "https://github.com/tree-sitter/tree-sitter-typescript", rev = "3e897ea5925f037cfae2e551f8e6b12eec2a201a" }
319+source = { git = "https://github.com/tree-sitter/tree-sitter-typescript", rev = "3e897ea5925f037cfae2e551f8e6b12eec2a201a", subpath = "tsx" }
321−path = "tsx"
322
320
323[[language]]
321[[language]]
324name = "css"
322name = "css"
@@ -522,8 +520,7 @@ indent = { tab-width = 2, unit = " " }
522
520
523[[grammar]]
521[[grammar]]
524name = "ocaml"
522name = "ocaml"
525−source = { git = "https://github.com/tree-sitter/tree-sitter-ocaml", rev = "23d419ba45789c5a47d31448061557716b02750a" }
523+source = { git = "https://github.com/tree-sitter/tree-sitter-ocaml", rev = "23d419ba45789c5a47d31448061557716b02750a", subpath = "ocaml" }
526−path = "ocaml"
527
524
528[[language]]
525[[language]]
529name = "ocaml-interface"
526name = "ocaml-interface"
@@ -536,8 +533,7 @@ indent = { tab-width = 2, unit = " "}
536
533
537[[grammar]]
534[[grammar]]
538name = "ocaml-interface"
535name = "ocaml-interface"
539−source = { git = "https://github.com/tree-sitter/tree-sitter-ocaml", rev = "23d419ba45789c5a47d31448061557716b02750a" }
536+source = { git = "https://github.com/tree-sitter/tree-sitter-ocaml", rev = "23d419ba45789c5a47d31448061557716b02750a", subpath = "interface" }
540−path = "interface"
541
537
542[[language]]
538[[language]]
543name = "lua"
539name = "lua"