Unnamed repository; edit this file 'description' to name the repository.
Diffstat (limited to 'xtask/src/main.rs')
| -rw-r--r-- | xtask/src/main.rs | 267 |
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()), }, }; |