@@ -1,255 +1,35 @@
1+mod docgen;
2+mod helpers;
3+mod path;
4+mod querycheck;
5+mod themelint;
6+
17use std::{env, error::Error};
28
39type DynError = Box<dyn Error>;
410
5−pub mod helpers {
6− use std::path::{Path, PathBuf};
7−
8− use crate::path;
9− use helix_core::syntax::Configuration as LangConfig;
10− use helix_term::health::TsFeature;
11−
12− /// Get the list of languages that support a particular tree-sitter
13− /// based feature.
14− pub fn ts_lang_support(feat: TsFeature) -> Vec<String> {
15− let queries_dir = path::ts_queries();
16−
17− find_files(&queries_dir, feat.runtime_filename())
18− .iter()
19− .map(|f| {
20− // .../helix/runtime/queries/python/highlights.scm
21− let tail = f.strip_prefix(&queries_dir).unwrap(); // python/highlights.scm
22− let lang = tail.components().next().unwrap(); // python
23− lang.as_os_str().to_string_lossy().to_string()
24− })
25− .collect()
26− }
27−
28− /// Get the list of languages that have any form of tree-sitter
29− /// queries defined in the runtime directory.
30− pub fn langs_with_ts_queries() -> Vec<String> {
31− std::fs::read_dir(path::ts_queries())
32− .unwrap()
33− .filter_map(|entry| {
34− let entry = entry.ok()?;
35− entry
36− .file_type()
37− .ok()?
38− .is_dir()
39− .then(|| entry.file_name().to_string_lossy().to_string())
40− })
41− .collect()
42− }
43−
44− // naive implementation, but suffices for our needs
45− pub fn find_files(dir: &Path, filename: &str) -> Vec<PathBuf> {
46− std::fs::read_dir(dir)
47− .unwrap()
48− .filter_map(|entry| {
49− let path = entry.ok()?.path();
50− if path.is_dir() {
51− Some(find_files(&path, filename))
52− } else {
53− (path.file_name()?.to_string_lossy() == filename).then(|| vec![path])
54− }
55− })
56− .flatten()
57− .collect()
58− }
59−
60− pub fn lang_config() -> LangConfig {
61− let bytes = std::fs::read(path::lang_config()).unwrap();
62− toml::from_slice(&bytes).unwrap()
63− }
64−}
65−
66−pub mod md_gen {
67− use crate::DynError;
68−
69− use crate::helpers;
70− use crate::path;
71− use helix_term::commands::TYPABLE_COMMAND_LIST;
72− use helix_term::health::TsFeature;
73− use std::fs;
74−
75− pub const TYPABLE_COMMANDS_MD_OUTPUT: &str = "typable-cmd.md";
76− pub const LANG_SUPPORT_MD_OUTPUT: &str = "lang-support.md";
77−
78− fn md_table_heading(cols: &[String]) -> String {
79− let mut header = String::new();
80− header += &md_table_row(cols);
81− header += &md_table_row(&vec!["---".to_string(); cols.len()]);
82− header
83− }
84−
85− fn md_table_row(cols: &[String]) -> String {
86− format!("| {} |\n", cols.join(" | "))
87− }
88−
89− fn md_mono(s: &str) -> String {
90− format!("`{}`", s)
91− }
92−
93− pub fn typable_commands() -> Result<String, DynError> {
94− let mut md = String::new();
95− md.push_str(&md_table_heading(&[
96− "Name".to_owned(),
97− "Description".to_owned(),
98− ]));
99−
100− let cmdify = |s: &str| format!("`:{}`", s);
101−
102− for cmd in TYPABLE_COMMAND_LIST {
103− let names = std::iter::once(&cmd.name)
104− .chain(cmd.aliases.iter())
105− .map(|a| cmdify(a))
106− .collect::<Vec<_>>()
107− .join(", ");
108−
109− let doc = cmd.doc.replace('\n', "<br>");
110−
111− md.push_str(&md_table_row(&[names.to_owned(), doc.to_owned()]));
112− }
113−
114− Ok(md)
115− }
116−
117− pub fn lang_features() -> Result<String, DynError> {
118− let mut md = String::new();
119− let ts_features = TsFeature::all();
120−
121− let mut cols = vec!["Language".to_owned()];
122− cols.append(
123− &mut ts_features
124− .iter()
125− .map(|t| t.long_title().to_string())
126− .collect::<Vec<_>>(),
127− );
128− cols.push("Default LSP".to_owned());
129−
130− md.push_str(&md_table_heading(&cols));
131− let config = helpers::lang_config();
132−
133− let mut langs = config
134− .language
135− .iter()
136− .map(|l| l.language_id.clone())
137− .collect::<Vec<_>>();
138− langs.sort_unstable();
139−
140− let mut ts_features_to_langs = Vec::new();
141− for &feat in ts_features {
142− ts_features_to_langs.push((feat, helpers::ts_lang_support(feat)));
143− }
144−
145− let mut row = Vec::new();
146− for lang in langs {
147− let lc = config
148− .language
149− .iter()
150− .find(|l| l.language_id == lang)
151− .unwrap(); // lang comes from config
152− row.push(lc.language_id.clone());
153−
154− for (_feat, support_list) in &ts_features_to_langs {
155− row.push(
156− if support_list.contains(&lang) {
157− "✓"
158− } else {
159− ""
160− }
161− .to_owned(),
162− );
163− }
164− row.push(
165− lc.language_server
166− .as_ref()
167− .map(|s| s.command.clone())
168− .map(|c| md_mono(&c))
169− .unwrap_or_default(),
170− );
171−
172− md.push_str(&md_table_row(&row));
173− row.clear();
174− }
175−
176− Ok(md)
177− }
178−
179− pub fn write(filename: &str, data: &str) {
180− let error = format!("Could not write to {}", filename);
181− let path = path::book_gen().join(filename);
182− fs::write(path, data).expect(&error);
183− }
184−}
185−
186−pub mod path {
187− use std::path::{Path, PathBuf};
188−
189− pub fn project_root() -> PathBuf {
190− Path::new(env!("CARGO_MANIFEST_DIR"))
191− .parent()
192− .unwrap()
193− .to_path_buf()
194− }
195−
196− pub fn book_gen() -> PathBuf {
197− project_root().join("book/src/generated/")
198− }
199−
200− pub fn ts_queries() -> PathBuf {
201− project_root().join("runtime/queries")
202− }
203−
204− pub fn lang_config() -> PathBuf {
205− project_root().join("languages.toml")
206− }
207−}
208−
20911pub mod tasks {
210− use crate::md_gen;
12+ use crate::docgen::{lang_features, typable_commands, write};
13+ use crate::docgen::{LANG_SUPPORT_MD_OUTPUT, TYPABLE_COMMANDS_MD_OUTPUT};
14+ use crate::querycheck::query_check;
15+ use crate::themelint::{lint, lint_all};
21116 use crate::DynError;
21217
21318 pub fn docgen() -> Result<(), DynError> {
214− use md_gen::*;
21519 write(TYPABLE_COMMANDS_MD_OUTPUT, &typable_commands()?);
21620 write(LANG_SUPPORT_MD_OUTPUT, &lang_features()?);
21721 Ok(())
21822 }
21923
220− pub fn query_check() -> Result<(), String> {
221− use crate::helpers::lang_config;
222− use helix_core::{syntax::read_query, tree_sitter::Query};
223− use helix_loader::grammar::get_language;
224−
225− let query_files = [
226− "highlights.scm",
227− "locals.scm",
228− "injections.scm",
229− "textobjects.scm",
230− "indents.scm",
231− ];
232−
233− for language in lang_config().language {
234− let language_name = language.language_id.to_ascii_lowercase();
235− let grammar_name = language.grammar.unwrap_or(language.language_id);
236− for query_file in query_files {
237− let language = get_language(&grammar_name);
238− let query_text = read_query(&language_name, query_file);
239− if !query_text.is_empty() && language.is_ok() {
240− if let Err(reason) = Query::new(language.unwrap(), &query_text) {
241− return Err(format!(
242− "Failed to parse {} queries for {}: {}",
243− query_file, language_name, reason
244− ));
245− }
246− }
247− }
24+ pub fn themelint(file: Option<String>) -> Result<(), DynError> {
25+ match file {
26+ Some(file) => lint(file),
27+ None => lint_all(),
24828 }
29+ }
24930
250− println!("Query check succeeded");
251−
252− Ok(())
31+ pub fn querycheck() -> Result<(), DynError> {
32+ query_check()
25333 }
25434
25535 pub fn print_help() {
@@ -259,6 +39,7 @@ Usage: Run with `cargo xtask <task>`, eg. `cargo xtask docgen`.
25939
26040 Tasks:
26141 docgen: Generate files to be included in the mdbook output.
42+ themelint <theme>: Report errors for <theme>, or all themes if no theme is specified.
26243 query-check: Check that tree-sitter queries are valid.
26344"
26445 );
@@ -271,7 +52,8 @@ fn main() -> Result<(), DynError> {
27152 None => tasks::print_help(),
27253 Some(t) => match t.as_str() {
27354 "docgen" => tasks::docgen()?,
274− "query-check" => tasks::query_check()?,
55+ "themelint" => tasks::themelint(env::args().nth(2))?,
56+ "query-check" => tasks::querycheck()?,
27557 invalid => return Err(format!("Invalid task name: {}", invalid).into()),
27658 },
27759 };