Unnamed repository; edit this file 'description' to name the repository.
Diffstat (limited to 'xtask/src/main.rs')
-rw-r--r--xtask/src/main.rs267
1 files changed, 196 insertions, 71 deletions
diff --git a/xtask/src/main.rs b/xtask/src/main.rs
index 7bb7e8c9..ad120f4f 100644
--- a/xtask/src/main.rs
+++ b/xtask/src/main.rs
@@ -1,86 +1,218 @@
-mod docgen;
-mod helpers;
-mod path;
-
use std::{env, error::Error};
type DynError = Box<dyn Error>;
-pub mod tasks {
- use crate::DynError;
- use std::collections::HashSet;
+pub mod helpers {
+ use std::path::{Path, PathBuf};
+
+ use crate::path;
+ use helix_core::syntax::Configuration as LangConfig;
+ use helix_term::health::TsFeature;
+
+ /// Get the list of languages that support a particular tree-sitter
+ /// based feature.
+ pub fn ts_lang_support(feat: TsFeature) -> Vec<String> {
+ let queries_dir = path::ts_queries();
+
+ find_files(&queries_dir, feat.runtime_filename())
+ .iter()
+ .map(|f| {
+ // .../helix/runtime/queries/python/highlights.scm
+ let tail = f.strip_prefix(&queries_dir).unwrap(); // python/highlights.scm
+ let lang = tail.components().next().unwrap(); // python
+ lang.as_os_str().to_string_lossy().to_string()
+ })
+ .collect()
+ }
- pub fn docgen() -> Result<(), DynError> {
- use crate::docgen::*;
- write(TYPABLE_COMMANDS_MD_OUTPUT, &typable_commands()?);
- write(STATIC_COMMANDS_MD_OUTPUT, &static_commands()?);
- write(LANG_SUPPORT_MD_OUTPUT, &lang_features()?);
- Ok(())
+ /// Get the list of languages that have any form of tree-sitter
+ /// queries defined in the runtime directory.
+ pub fn langs_with_ts_queries() -> Vec<String> {
+ std::fs::read_dir(path::ts_queries())
+ .unwrap()
+ .filter_map(|entry| {
+ let entry = entry.ok()?;
+ entry
+ .file_type()
+ .ok()?
+ .is_dir()
+ .then(|| entry.file_name().to_string_lossy().to_string())
+ })
+ .collect()
}
- pub fn querycheck(languages: impl Iterator<Item = String>) -> Result<(), DynError> {
- use helix_core::syntax::LanguageData;
+ // naive implementation, but suffices for our needs
+ pub fn find_files(dir: &Path, filename: &str) -> Vec<PathBuf> {
+ std::fs::read_dir(dir)
+ .unwrap()
+ .filter_map(|entry| {
+ let path = entry.ok()?.path();
+ if path.is_dir() {
+ Some(find_files(&path, filename))
+ } else {
+ (path.file_name()?.to_string_lossy() == filename).then(|| vec![path])
+ }
+ })
+ .flatten()
+ .collect()
+ }
- let languages_to_check: HashSet<_> = languages.collect();
- let loader = helix_core::config::default_lang_loader();
- for (_language, lang_data) in loader.languages() {
- if !languages_to_check.is_empty()
- && !languages_to_check.contains(&lang_data.config().language_id)
- {
- continue;
- }
- let config = lang_data.config();
- let Some(syntax_config) = LanguageData::compile_syntax_config(config, &loader)? else {
- continue;
- };
- let grammar = syntax_config.grammar;
- LanguageData::compile_indent_query(grammar, config)?;
- LanguageData::compile_textobject_query(grammar, config)?;
- LanguageData::compile_tag_query(grammar, config)?;
- LanguageData::compile_rainbow_query(grammar, config)?;
- }
+ pub fn lang_config() -> LangConfig {
+ let bytes = std::fs::read(path::lang_config()).unwrap();
+ toml::from_slice(&bytes).unwrap()
+ }
+}
- println!("Query check succeeded");
+pub mod md_gen {
+ use crate::DynError;
- Ok(())
+ use crate::helpers;
+ use crate::path;
+ use helix_term::commands::TYPABLE_COMMAND_LIST;
+ use helix_term::health::TsFeature;
+ use std::fs;
+
+ pub const TYPABLE_COMMANDS_MD_OUTPUT: &str = "typable-cmd.md";
+ pub const LANG_SUPPORT_MD_OUTPUT: &str = "lang-support.md";
+
+ fn md_table_heading(cols: &[String]) -> String {
+ let mut header = String::new();
+ header += &md_table_row(cols);
+ header += &md_table_row(&vec!["---".to_string(); cols.len()]);
+ header
}
- pub fn themecheck(themes: impl Iterator<Item = String>) -> Result<(), DynError> {
- use helix_view::theme::Loader;
+ fn md_table_row(cols: &[String]) -> String {
+ format!("| {} |\n", cols.join(" | "))
+ }
- let themes_to_check: HashSet<_> = themes.collect();
+ fn md_mono(s: &str) -> String {
+ format!("`{}`", s)
+ }
- let theme_names = [
- vec!["default".to_string(), "base16_default".to_string()],
- Loader::read_names(&crate::path::themes()),
- ]
- .concat();
- let loader = Loader::new(&[crate::path::runtime()]);
- let mut errors_present = false;
+ pub fn typable_commands() -> Result<String, DynError> {
+ let mut md = String::new();
+ md.push_str(&md_table_heading(&[
+ "Name".to_owned(),
+ "Description".to_owned(),
+ ]));
- for name in theme_names {
- if !themes_to_check.is_empty() && !themes_to_check.contains(&name) {
- continue;
- }
+ let cmdify = |s: &str| format!("`:{}`", s);
- let (_, warnings) = loader.load_with_warnings(&name).unwrap();
+ for cmd in TYPABLE_COMMAND_LIST {
+ let names = std::iter::once(&cmd.name)
+ .chain(cmd.aliases.iter())
+ .map(|a| cmdify(a))
+ .collect::<Vec<_>>()
+ .join(", ");
- if !warnings.is_empty() {
- errors_present = true;
- println!("Theme '{name}' loaded with errors:");
- for warning in warnings {
- println!("\t* {}", warning);
- }
- }
+ md.push_str(&md_table_row(&[names.to_owned(), cmd.doc.to_owned()]));
}
- match errors_present {
- true => Err("Errors found when loading bundled themes".into()),
- false => {
- println!("Theme check successful!");
- Ok(())
+ Ok(md)
+ }
+
+ pub fn lang_features() -> Result<String, DynError> {
+ let mut md = String::new();
+ let ts_features = TsFeature::all();
+
+ let mut cols = vec!["Language".to_owned()];
+ cols.append(
+ &mut ts_features
+ .iter()
+ .map(|t| t.long_title().to_string())
+ .collect::<Vec<_>>(),
+ );
+ cols.push("Default LSP".to_owned());
+
+ md.push_str(&md_table_heading(&cols));
+ let config = helpers::lang_config();
+
+ let mut langs = config
+ .language
+ .iter()
+ .map(|l| l.language_id.clone())
+ .collect::<Vec<_>>();
+ langs.sort_unstable();
+
+ let mut ts_features_to_langs = Vec::new();
+ for &feat in ts_features {
+ ts_features_to_langs.push((feat, helpers::ts_lang_support(feat)));
+ }
+
+ let mut row = Vec::new();
+ for lang in langs {
+ let lc = config
+ .language
+ .iter()
+ .find(|l| l.language_id == lang)
+ .unwrap(); // lang comes from config
+ row.push(lc.language_id.clone());
+
+ for (_feat, support_list) in &ts_features_to_langs {
+ row.push(
+ if support_list.contains(&lang) {
+ "✓"
+ } else {
+ ""
+ }
+ .to_owned(),
+ );
}
+ row.push(
+ lc.language_server
+ .as_ref()
+ .map(|s| s.command.clone())
+ .map(|c| md_mono(&c))
+ .unwrap_or_default(),
+ );
+
+ md.push_str(&md_table_row(&row));
+ row.clear();
}
+
+ Ok(md)
+ }
+
+ pub fn write(filename: &str, data: &str) {
+ let error = format!("Could not write to {}", filename);
+ let path = path::book_gen().join(filename);
+ fs::write(path, data).expect(&error);
+ }
+}
+
+pub mod path {
+ use std::path::{Path, PathBuf};
+
+ pub fn project_root() -> PathBuf {
+ Path::new(env!("CARGO_MANIFEST_DIR"))
+ .parent()
+ .unwrap()
+ .to_path_buf()
+ }
+
+ pub fn book_gen() -> PathBuf {
+ project_root().join("book/src/generated/")
+ }
+
+ pub fn ts_queries() -> PathBuf {
+ project_root().join("runtime/queries")
+ }
+
+ pub fn lang_config() -> PathBuf {
+ project_root().join("languages.toml")
+ }
+}
+
+pub mod tasks {
+ use crate::md_gen;
+ use crate::DynError;
+
+ pub fn docgen() -> Result<(), DynError> {
+ use md_gen::*;
+ write(TYPABLE_COMMANDS_MD_OUTPUT, &typable_commands()?);
+ write(LANG_SUPPORT_MD_OUTPUT, &lang_features()?);
+ Ok(())
}
pub fn print_help() {
@@ -89,25 +221,18 @@ pub mod tasks {
Usage: Run with `cargo xtask <task>`, eg. `cargo xtask docgen`.
Tasks:
- docgen Generate files to be included in the mdbook output.
- query-check [languages] Check that tree-sitter queries are valid for the given
- languages, or all languages if none are specified.
- theme-check [themes] Check that the theme files in runtime/themes/ are valid for the
- given themes, or all themes if none are specified.
+ docgen: Generate files to be included in the mdbook output.
"
);
}
}
fn main() -> Result<(), DynError> {
- let mut args = env::args().skip(1);
- let task = args.next();
+ let task = env::args().nth(1);
match task {
None => tasks::print_help(),
Some(t) => match t.as_str() {
"docgen" => tasks::docgen()?,
- "query-check" => tasks::querycheck(args)?,
- "theme-check" => tasks::themecheck(args)?,
invalid => return Err(format!("Invalid task name: {}", invalid).into()),
},
};