on master help sign in
Handle BrokenPipe when piping hx --health through head (#1876)

Co-authored-by: Gokul Soumya <gokulps15@gmail.com>

commit8702aaaefcc3a069947b4e52a9c1f57868f32b5c authorNirmal Patel <npate012@gmail.com> dateWed 30 Mar 2022, 01:09 (5 years ago) parent7cd6050
2 files changed+106−43
helix-term/src/health.rs+100−35 helix-term/src/main.rs+6−8
helix-term/src/health.rs+100−35
@@ -1,6 +1,7 @@
11use crossterm::style::{Color, Print, Stylize};
22use helix_core::config::{default_syntax_loader, user_syntax_loader};
33use helix_loader::grammar::load_runtime_file;
4+use std::io::Write;
45
56#[derive(Copy, Clone)]
67pub enum TsFeature {
@@ -40,43 +41,62 @@ impl TsFeature {
4041}
4142
4243/// Display general diagnostics.
43−pub fn general() {
44+pub fn general() -> std::io::Result<()> {
45+ let stdout = std::io::stdout();
46+ let mut stdout = stdout.lock();
47+
4448 let config_file = helix_loader::config_file();
4549 let lang_file = helix_loader::lang_config_file();
4650 let log_file = helix_loader::log_file();
4751 let rt_dir = helix_loader::runtime_dir();
4852
4953 if config_file.exists() {
50− println!("Config file: {}", config_file.display());
54+ writeln!(stdout, "Config file: {}", config_file.display())?;
5155 } else {
52− println!("Config file: default")
56+ writeln!(stdout, "Config file: default")?;
5357 }
5458 if lang_file.exists() {
55− println!("Language file: {}", lang_file.display());
59+ writeln!(stdout, "Language file: {}", lang_file.display())?;
5660 } else {
57− println!("Language file: default")
61+ writeln!(stdout, "Language file: default")?;
5862 }
59− println!("Log file: {}", log_file.display());
60− println!("Runtime directory: {}", rt_dir.display());
63+ writeln!(stdout, "Log file: {}", log_file.display())?;
64+ writeln!(stdout, "Runtime directory: {}", rt_dir.display())?;
6165
6266 if let Ok(path) = std::fs::read_link(&rt_dir) {
6367 let msg = format!("Runtime directory is symlinked to {}", path.display());
64− println!("{}", msg.yellow());
68+ writeln!(stdout, "{}", msg.yellow())?;
6569 }
6670 if !rt_dir.exists() {
67− println!("{}", "Runtime directory does not exist.".red());
71+ writeln!(stdout, "{}", "Runtime directory does not exist.".red())?;
6872 }
6973 if rt_dir.read_dir().ok().map(|it| it.count()) == Some(0) {
70− println!("{}", "Runtime directory is empty.".red());
74+ writeln!(stdout, "{}", "Runtime directory is empty.".red())?;
7175 }
76+
77+ Ok(())
7278}
7379
74−pub fn languages_all() {
75− let mut syn_loader_conf = user_syntax_loader().unwrap_or_else(|err| {
76− eprintln!("{}: {}", "Error parsing user language config".red(), err);
77− eprintln!("{}", "Using default language config".yellow());
78− default_syntax_loader()
79− });
80+pub fn languages_all() -> std::io::Result<()> {
81+ let stdout = std::io::stdout();
82+ let mut stdout = stdout.lock();
83+
84+ let mut syn_loader_conf = match user_syntax_loader() {
85+ Ok(conf) => conf,
86+ Err(err) => {
87+ let stderr = std::io::stderr();
88+ let mut stderr = stderr.lock();
89+
90+ writeln!(
91+ stderr,
92+ "{}: {}",
93+ "Error parsing user language config".red(),
94+ err
95+ )?;
96+ writeln!(stderr, "{}", "Using default language config".yellow())?;
97+ default_syntax_loader()
98+ }
99+ };
80100
81101 let mut headings = vec!["Language", "LSP", "DAP"];
82102
@@ -106,7 +126,7 @@ pub fn languages_all() {
106126 for heading in headings {
107127 column(heading, Color::White);
108128 }
109− println!();
129+ writeln!(stdout)?;
110130
111131 syn_loader_conf
112132 .language
@@ -139,18 +159,34 @@ pub fn languages_all() {
139159 }
140160 }
141161
142− println!();
162+ writeln!(stdout)?;
143163 }
164+
165+ Ok(())
144166}
145167
146168/// Display diagnostics pertaining to a particular language (LSP,
147169/// highlight queries, etc).
148−pub fn language(lang_str: String) {
149− let syn_loader_conf = user_syntax_loader().unwrap_or_else(|err| {
150− eprintln!("{}: {}", "Error parsing user language config".red(), err);
151− eprintln!("{}", "Using default language config".yellow());
152− default_syntax_loader()
153− });
170+pub fn language(lang_str: String) -> std::io::Result<()> {
171+ let stdout = std::io::stdout();
172+ let mut stdout = stdout.lock();
173+
174+ let syn_loader_conf = match user_syntax_loader() {
175+ Ok(conf) => conf,
176+ Err(err) => {
177+ let stderr = std::io::stderr();
178+ let mut stderr = stderr.lock();
179+
180+ writeln!(
181+ stderr,
182+ "{}: {}",
183+ "Error parsing user language config".red(),
184+ err
185+ )?;
186+ writeln!(stderr, "{}", "Using default language config".yellow())?;
187+ default_syntax_loader()
188+ }
189+ };
154190
155191 let lang = match syn_loader_conf
156192 .language
@@ -160,7 +196,7 @@ pub fn language(lang_str: String) {
160196 Some(l) => l,
161197 None => {
162198 let msg = format!("Language '{}' not found", lang_str);
163− println!("{}", msg.red());
199+ writeln!(stdout, "{}", msg.red())?;
164200 let suggestions: Vec<&str> = syn_loader_conf
165201 .language
166202 .iter()
@@ -169,9 +205,13 @@ pub fn language(lang_str: String) {
169205 .collect();
170206 if !suggestions.is_empty() {
171207 let suggestions = suggestions.join(", ");
172− println!("Did you mean one of these: {} ?", suggestions.yellow());
208+ writeln!(
209+ stdout,
210+ "Did you mean one of these: {} ?",
211+ suggestions.yellow()
212+ )?;
173213 }
174− return;
214+ return Ok(());
175215 }
176216 };
177217
@@ -180,41 +220,66 @@ pub fn language(lang_str: String) {
180220 lang.language_server
181221 .as_ref()
182222 .map(|lsp| lsp.command.to_string()),
183− );
223+ )?;
184224
185225 probe_protocol(
186226 "debug adapter",
187227 lang.debugger.as_ref().map(|dap| dap.command.to_string()),
188− );
228+ )?;
189229
190230 for ts_feat in TsFeature::all() {
191− probe_treesitter_feature(&lang_str, *ts_feat)
231+ probe_treesitter_feature(&lang_str, *ts_feat)?
192232 }
233+
234+ Ok(())
193235}
194236
195237/// Display diagnostics about LSP and DAP.
196−fn probe_protocol(protocol_name: &str, server_cmd: Option<String>) {
238+fn probe_protocol(protocol_name: &str, server_cmd: Option<String>) -> std::io::Result<()> {
239+ let stdout = std::io::stdout();
240+ let mut stdout = stdout.lock();
241+
197242 let cmd_name = match server_cmd {
198243 Some(ref cmd) => cmd.as_str().green(),
199244 None => "None".yellow(),
200245 };
201− println!("Configured {}: {}", protocol_name, cmd_name);
246+ writeln!(stdout, "Configured {}: {}", protocol_name, cmd_name)?;
202247
203248 if let Some(cmd) = server_cmd {
204249 let path = match which::which(&cmd) {
205250 Ok(path) => path.display().to_string().green(),
206251 Err(_) => "Not found in $PATH".to_string().red(),
207252 };
208− println!("Binary for {}: {}", protocol_name, path);
253+ writeln!(stdout, "Binary for {}: {}", protocol_name, path)?;
209254 }
255+
256+ Ok(())
210257}
211258
212259/// Display diagnostics about a feature that requires tree-sitter
213260/// query files (highlights, textobjects, etc).
214−fn probe_treesitter_feature(lang: &str, feature: TsFeature) {
261+fn probe_treesitter_feature(lang: &str, feature: TsFeature) -> std::io::Result<()> {
262+ let stdout = std::io::stdout();
263+ let mut stdout = stdout.lock();
264+
215265 let found = match load_runtime_file(lang, feature.runtime_filename()).is_ok() {
216266 true => "Found".green(),
217267 false => "Not found".red(),
218268 };
219− println!("{} queries: {}", feature.short_title(), found);
269+ writeln!(stdout, "{} queries: {}", feature.short_title(), found)?;
270+
271+ Ok(())
272+}
273+
274+pub fn print_health(health_arg: Option<String>) -> std::io::Result<()> {
275+ match health_arg.as_deref() {
276+ Some("all") => languages_all()?,
277+ Some(lang) => language(lang.to_string())?,
278+ None => {
279+ general()?;
280+ writeln!(std::io::stdout().lock())?;
281+ languages_all()?;
282+ }
283+ }
284+ Ok(())
220285}
helix-term/src/main.rs+6−8
@@ -88,16 +88,14 @@ FLAGS:
8888 }
8989
9090 if args.health {
91− if let Some(lang) = args.health_arg {
92− match lang.as_str() {
93− "all" => helix_term::health::languages_all(),
94− _ => helix_term::health::language(lang),
91+ if let Err(err) = helix_term::health::print_health(args.health_arg) {
92+ // Piping to for example `head -10` requires special handling:
93+ // https://stackoverflow.com/a/65760807/7115678
94+ if err.kind() != std::io::ErrorKind::BrokenPipe {
95+ return Err(err.into());
9596 }
96− } else {
97− helix_term::health::general();
98− println!();
99− helix_term::health::languages_all();
10097 }
98+
10199 std::process::exit(0);
102100 }
103101