From d74959d3ade0f4d7ee9665940e785311705796b3 Mon Sep 17 00:00:00 2001 From: Nirmal Patel Date: Mon, 28 Mar 2022 20:10:38 -0400 Subject: [PATCH] Handle BrokenPipe when piping hx --health through head Functions in health.rs now return std:io::Result<()>. All instances of println! and eprintln! have been replaced with writeln!. This allows detection of errors while printing messages and errors. BrokenPipe errors are ignored. All other errors are returned. Fixes #1841 Signed-off-by: Nirmal Patel --- helix-term/src/health.rs | 112 +++++++++++++++++++++++++++------------ helix-term/src/main.rs | 21 ++++++-- 2 files changed, 94 insertions(+), 39 deletions(-) diff --git a/helix-term/src/health.rs b/helix-term/src/health.rs index f13d35f09c277..9508578e5d6b0 100644 --- a/helix-term/src/health.rs +++ b/helix-term/src/health.rs @@ -1,6 +1,7 @@ use crossterm::style::{Color, Print, Stylize}; use helix_core::config::{default_syntax_loader, user_syntax_loader}; use helix_loader::grammar::load_runtime_file; +use std::io::Write; #[derive(Copy, Clone)] pub enum TsFeature { @@ -40,43 +41,59 @@ impl TsFeature { } /// Display general diagnostics. -pub fn general() { +pub fn general() -> std::io::Result<()> { + let stdout = std::io::stdout(); + let mut stdout = stdout.lock(); + let config_file = helix_loader::config_file(); let lang_file = helix_loader::lang_config_file(); let log_file = helix_loader::log_file(); let rt_dir = helix_loader::runtime_dir(); if config_file.exists() { - println!("Config file: {}", config_file.display()); + writeln!(stdout, "Config file: {}", config_file.display())?; } else { - println!("Config file: default") + writeln!(stdout, "Config file: default")?; } if lang_file.exists() { - println!("Language file: {}", lang_file.display()); + writeln!(stdout, "Language file: {}", lang_file.display())?; } else { - println!("Language file: default") + writeln!(stdout, "Language file: default")?; } - println!("Log file: {}", log_file.display()); - println!("Runtime directory: {}", rt_dir.display()); + writeln!(stdout, "Log file: {}", log_file.display())?; + writeln!(stdout, "Runtime directory: {}", rt_dir.display())?; if let Ok(path) = std::fs::read_link(&rt_dir) { let msg = format!("Runtime directory is symlinked to {}", path.display()); - println!("{}", msg.yellow()); + writeln!(stdout, "{}", msg.yellow())?; } if !rt_dir.exists() { - println!("{}", "Runtime directory does not exist.".red()); + writeln!(stdout, "{}", "Runtime directory does not exist.".red())?; } if rt_dir.read_dir().ok().map(|it| it.count()) == Some(0) { - println!("{}", "Runtime directory is empty.".red()); + writeln!(stdout, "{}", "Runtime directory is empty.".red())?; } + + Ok(()) } -pub fn languages_all() { - let mut syn_loader_conf = user_syntax_loader().unwrap_or_else(|err| { - eprintln!("{}: {}", "Error parsing user language config".red(), err); - eprintln!("{}", "Using default language config".yellow()); - default_syntax_loader() - }); +pub fn languages_all() -> std::io::Result<()> { + let stdout = std::io::stdout(); + let mut stdout = stdout.lock(); + + let mut syn_loader_conf = match user_syntax_loader() { + Ok(conf) => { + conf + }, + Err(err) => { + let stderr = std::io::stderr(); + let mut stderr = stderr.lock(); + + writeln!(stderr, "{}: {}", "Error parsing user language config".red(), err)?; + writeln!(stderr, "{}", "Using default language config".yellow())?; + default_syntax_loader() + }, + }; let mut headings = vec!["Language", "LSP", "DAP"]; @@ -105,7 +122,7 @@ pub fn languages_all() { for heading in headings { column(heading, Color::White); } - println!(); + writeln!(stdout)?; syn_loader_conf .language @@ -138,18 +155,31 @@ pub fn languages_all() { } } - println!(); + writeln!(stdout)?; } + + Ok(()) } /// Display diagnostics pertaining to a particular language (LSP, /// highlight queries, etc). -pub fn language(lang_str: String) { - let syn_loader_conf = user_syntax_loader().unwrap_or_else(|err| { - eprintln!("{}: {}", "Error parsing user language config".red(), err); - eprintln!("{}", "Using default language config".yellow()); - default_syntax_loader() - }); +pub fn language(lang_str: String) -> std::io::Result<()> { + let stdout = std::io::stdout(); + let mut stdout = stdout.lock(); + + let syn_loader_conf = match user_syntax_loader() { + Ok(conf) => { + conf + }, + Err(err) => { + let stderr = std::io::stderr(); + let mut stderr = stderr.lock(); + + writeln!(stderr, "{}: {}", "Error parsing user language config".red(), err)?; + writeln!(stderr, "{}", "Using default language config".yellow())?; + default_syntax_loader() + }, + }; let lang = match syn_loader_conf .language @@ -159,7 +189,7 @@ pub fn language(lang_str: String) { Some(l) => l, None => { let msg = format!("Language '{lang_str}' not found"); - println!("{}", msg.red()); + writeln!(stdout, "{}", msg.clone().red())?; let suggestions: Vec<&str> = syn_loader_conf .language .iter() @@ -168,9 +198,9 @@ pub fn language(lang_str: String) { .collect(); if !suggestions.is_empty() { let suggestions = suggestions.join(", "); - println!("Did you mean one of these: {} ?", suggestions.yellow()); + writeln!(stdout, "Did you mean one of these: {} ?", suggestions.yellow())?; } - return; + return Err(std::io::Error::new(std::io::ErrorKind::Other, msg)); } }; @@ -179,41 +209,53 @@ pub fn language(lang_str: String) { lang.language_server .as_ref() .map(|lsp| lsp.command.to_string()), - ); + )?; probe_protocol( "debug adapter", lang.debugger.as_ref().map(|dap| dap.command.to_string()), - ); + )?; for ts_feat in TsFeature::all() { - probe_treesitter_feature(&lang_str, *ts_feat) + probe_treesitter_feature(&lang_str, *ts_feat)? } + + Ok(()) } /// Display diagnostics about LSP and DAP. -fn probe_protocol(protocol_name: &str, server_cmd: Option) { +fn probe_protocol(protocol_name: &str, server_cmd: Option) -> std::io::Result<()> { + let stdout = std::io::stdout(); + let mut stdout = stdout.lock(); + let cmd_name = match server_cmd { Some(ref cmd) => cmd.as_str().green(), None => "None".yellow(), }; - println!("Configured {}: {}", protocol_name, cmd_name); + writeln!(stdout, "Configured {}: {}", protocol_name, cmd_name)?; if let Some(cmd) = server_cmd { let path = match which::which(&cmd) { Ok(path) => path.display().to_string().green(), Err(_) => "Not found in $PATH".to_string().red(), }; - println!("Binary for {}: {}", protocol_name, path); + writeln!(stdout, "Binary for {}: {}", protocol_name, path)?; } + + Ok(()) } /// Display diagnostics about a feature that requires tree-sitter /// query files (highlights, textobjects, etc). -fn probe_treesitter_feature(lang: &str, feature: TsFeature) { +fn probe_treesitter_feature(lang: &str, feature: TsFeature) -> std::io::Result<()> { + let stdout = std::io::stdout(); + let mut stdout = stdout.lock(); + let found = match load_runtime_file(lang, feature.runtime_filename()).is_ok() { true => "Found".green(), false => "Not found".red(), }; - println!("{} queries: {}", feature.short_title(), found); + writeln!(stdout, "{} queries: {}", feature.short_title(), found)?; + + Ok(()) } diff --git a/helix-term/src/main.rs b/helix-term/src/main.rs index 0385d92c0dd57..91846e679ae58 100644 --- a/helix-term/src/main.rs +++ b/helix-term/src/main.rs @@ -3,6 +3,7 @@ use helix_term::application::Application; use helix_term::args::Args; use helix_term::config::{Config, ConfigLoadError}; use std::path::PathBuf; +use std::io::Write; fn setup_logging(logpath: PathBuf, verbosity: u64) -> Result<()> { let mut base_config = fern::Dispatch::new(); @@ -89,14 +90,26 @@ FLAGS: if args.health { if let Some(lang) = args.health_arg { - match lang.as_str() { + let io_result = match lang.as_str() { "all" => helix_term::health::languages_all(), _ => helix_term::health::language(lang), + }; + + if let Err(err) = io_result { + if err.kind() != std::io::ErrorKind::BrokenPipe { + return Err(err.into()); + } } } else { - helix_term::health::general(); - println!(); - helix_term::health::languages_all(); + let io_result = helix_term::health::general() + .and_then(|_| writeln!(std::io::stdout().lock())) + .and_then(|_| helix_term::health::languages_all()); + + if let Err(err) = io_result { + if err.kind() != std::io::ErrorKind::BrokenPipe { + return Err(err.into()); + } + } } std::process::exit(0); }