diff --git a/helix-loader/src/grammar.rs b/helix-loader/src/grammar.rs index 231ecf348..98a93e56c 100644 --- a/helix-loader/src/grammar.rs +++ b/helix-loader/src/grammar.rs @@ -89,15 +89,102 @@ pub fn fetch_grammars() -> Result<()> { let mut grammars = get_grammar_configs()?; grammars.retain(|grammar| !matches!(grammar.source, GrammarSource::Local { .. })); - run_parallel(grammars, fetch_grammar, "fetch") + println!("Fetching {} grammars", grammars.len()); + let results = run_parallel(grammars, fetch_grammar); + + let mut errors = Vec::new(); + let mut git_updated = Vec::new(); + let mut git_up_to_date = 0; + let mut non_git = Vec::new(); + + for res in results { + match res { + Ok(FetchStatus::GitUpToDate) => git_up_to_date += 1, + Ok(FetchStatus::GitUpdated { + grammar_id, + revision, + }) => git_updated.push((grammar_id, revision)), + Ok(FetchStatus::NonGit { grammar_id }) => non_git.push(grammar_id), + Err(e) => errors.push(e), + } + } + + non_git.sort_unstable(); + git_updated.sort_unstable_by(|a, b| a.0.cmp(&b.0)); + + if git_up_to_date != 0 { + println!("{} up to date git grammars", git_up_to_date); + } + + if !non_git.is_empty() { + println!("{} non git grammars", non_git.len()); + println!("\t{:?}", non_git); + } + + if !git_updated.is_empty() { + println!("{} updated grammars", git_updated.len()); + // We checked the vec is not empty, unwrapping will not panic + let longest_id = git_updated.iter().map(|x| x.0.len()).max().unwrap(); + for (id, rev) in git_updated { + println!( + "\t{id:width$} now on {rev}", + id = id, + width = longest_id, + rev = rev + ); + } + } + + if !errors.is_empty() { + let len = errors.len(); + println!("{} grammars failed to fetch", len); + for (i, error) in errors.into_iter().enumerate() { + println!("\tFailure {}/{}: {}", i, len, error); + } + } + + Ok(()) } pub fn build_grammars(target: Option) -> Result<()> { - run_parallel( - get_grammar_configs()?, - move |grammar| build_grammar(grammar, target.as_deref()), - "build", - ) + let grammars = get_grammar_configs()?; + println!("Building {} grammars", grammars.len()); + let results = run_parallel(grammars, move |grammar| { + build_grammar(grammar, target.as_deref()) + }); + + let mut errors = Vec::new(); + let mut already_built = 0; + let mut built = Vec::new(); + + for res in results { + match res { + Ok(BuildStatus::AlreadyBuilt) => already_built += 1, + Ok(BuildStatus::Built { grammar_id }) => built.push(grammar_id), + Err(e) => errors.push(e), + } + } + + built.sort_unstable(); + + if already_built != 0 { + println!("{} grammars already built", already_built); + } + + if !built.is_empty() { + println!("{} grammars built now", built.len()); + println!("\t{:?}", built); + } + + if !errors.is_empty() { + let len = errors.len(); + println!("{} grammars failed to build", len); + for (i, error) in errors.into_iter().enumerate() { + println!("\tFailure {}/{}: {}", i, len, error); + } + } + + Ok(()) } // Returns the set of grammar configurations the user requests. @@ -126,9 +213,10 @@ fn get_grammar_configs() -> Result> { Ok(grammars) } -fn run_parallel(grammars: Vec, job: F, action: &'static str) -> Result<()> +fn run_parallel(grammars: Vec, job: F) -> Vec> where - F: Fn(GrammarConfiguration) -> Result<()> + std::marker::Send + 'static + Clone, + F: Fn(GrammarConfiguration) -> Result + Send + 'static + Clone, + Res: Send + 'static, { let pool = threadpool::Builder::new().build(); let (tx, rx) = channel(); @@ -146,14 +234,21 @@ where drop(tx); - // TODO: print all failures instead of the first one found. - rx.iter() - .find(|result| result.is_err()) - .map(|err| err.with_context(|| format!("Failed to {} some grammar(s)", action))) - .unwrap_or(Ok(())) + rx.iter().collect() +} + +enum FetchStatus { + GitUpToDate, + GitUpdated { + grammar_id: String, + revision: String, + }, + NonGit { + grammar_id: String, + }, } -fn fetch_grammar(grammar: GrammarConfiguration) -> Result<()> { +fn fetch_grammar(grammar: GrammarConfiguration) -> Result { if let GrammarSource::Git { remote, revision, .. } = grammar.source @@ -189,16 +284,18 @@ fn fetch_grammar(grammar: GrammarConfiguration) -> Result<()> { )?; git(&grammar_dir, ["checkout", &revision])?; - println!( - "Grammar '{}' checked out at '{}'.", - grammar.grammar_id, revision - ); + Ok(FetchStatus::GitUpdated { + grammar_id: grammar.grammar_id, + revision, + }) } else { - println!("Grammar '{}' is already up to date.", grammar.grammar_id); + Ok(FetchStatus::GitUpToDate) } + } else { + Ok(FetchStatus::NonGit { + grammar_id: grammar.grammar_id, + }) } - - Ok(()) } // Sets the remote for a repository to the given URL, creating the remote if @@ -245,7 +342,12 @@ where } } -fn build_grammar(grammar: GrammarConfiguration, target: Option<&str>) -> Result<()> { +enum BuildStatus { + AlreadyBuilt, + Built { grammar_id: String }, +} + +fn build_grammar(grammar: GrammarConfiguration, target: Option<&str>) -> Result { let grammar_dir = if let GrammarSource::Local { path } = &grammar.source { PathBuf::from(&path) } else { @@ -285,7 +387,7 @@ fn build_tree_sitter_library( src_path: &Path, grammar: GrammarConfiguration, target: Option<&str>, -) -> Result<()> { +) -> Result { let header_path = src_path; let parser_path = src_path.join("parser.c"); let mut scanner_path = src_path.join("scanner.c"); @@ -308,12 +410,9 @@ fn build_tree_sitter_library( .context("Failed to compare source and binary timestamps")?; if !recompile { - println!("Grammar '{}' is already built.", grammar.grammar_id); - return Ok(()); + return Ok(BuildStatus::AlreadyBuilt); } - println!("Building grammar '{}'", grammar.grammar_id); - let mut config = cc::Build::new(); config .cpp(true) @@ -381,7 +480,9 @@ fn build_tree_sitter_library( )); } - Ok(()) + Ok(BuildStatus::Built { + grammar_id: grammar.grammar_id, + }) } fn needs_recompile(