From 7e6b94231290020b55f1d08fb03ea8132781abc5 Mon Sep 17 00:00:00 2001 From: Matt Mastracci Date: Sun, 18 Feb 2024 21:51:06 -0700 Subject: [PATCH] feat(core): highlight unprintable chars in permission prompts (#22468) If we strip out unprintable chars, we don't see the full filename being requested by permission prompts. Instead, we highlight and escape them to make them visible. --- runtime/permissions/prompter.rs | 30 +++++++++++++++++++++--------- tests/integration/run_tests.rs | 20 ++++++++------------ 2 files changed, 29 insertions(+), 21 deletions(-) diff --git a/runtime/permissions/prompter.rs b/runtime/permissions/prompter.rs index f054b31f388f50..da5979f0a2f89d 100644 --- a/runtime/permissions/prompter.rs +++ b/runtime/permissions/prompter.rs @@ -11,12 +11,24 @@ use std::io::StderrLock; use std::io::StdinLock; use std::io::Write as IoWrite; -/// Helper function to strip ansi codes and ASCII control characters. -fn strip_ansi_codes_and_ascii_control(s: &str) -> std::borrow::Cow { - console_static_text::ansi::strip_ansi_codes(s) - .chars() - .filter(|c| !c.is_ascii_control()) - .collect() +/// Helper function to make control characters visible so users can see the underlying filename. +fn escape_control_characters(s: &str) -> std::borrow::Cow { + if !s.contains(|c: char| c.is_ascii_control() || c.is_control()) { + return std::borrow::Cow::Borrowed(s); + } + let mut output = String::with_capacity(s.len() * 2); + for c in s.chars() { + match c { + c if c.is_ascii_control() => output.push_str( + &colors::white_bold_on_red(c.escape_debug().to_string()).to_string(), + ), + c if c.is_control() => output.push_str( + &colors::white_bold_on_red(c.escape_debug().to_string()).to_string(), + ), + c => output.push(c), + } + } + output.into() } pub const PERMISSION_EMOJI: &str = "⚠️"; @@ -249,9 +261,9 @@ impl PermissionPrompter for TtyPrompter { return PromptResponse::Deny; // don't grant permission if this fails } - let message = strip_ansi_codes_and_ascii_control(message); - let name = strip_ansi_codes_and_ascii_control(name); - let api_name = api_name.map(strip_ansi_codes_and_ascii_control); + let message = escape_control_characters(message); + let name = escape_control_characters(name); + let api_name = api_name.map(escape_control_characters); // print to stderr so that if stdout is piped this is still displayed. let opts: String = if is_unary { diff --git a/tests/integration/run_tests.rs b/tests/integration/run_tests.rs index 02fb915b5c426a..a4bfc6345cff48 100644 --- a/tests/integration/run_tests.rs +++ b/tests/integration/run_tests.rs @@ -4725,19 +4725,19 @@ fn stdio_streams_are_locked_in_permission_prompt() { } #[test] -fn permission_prompt_strips_ansi_codes_and_control_chars() { +fn permission_prompt_escapes_ansi_codes_and_control_chars() { util::with_pty(&["repl"], |mut console| { console.write_line( r#"Deno.permissions.request({ name: "env", variable: "\rDo you like ice cream? y/n" });"# ); // will be uppercase on windows let env_name = if cfg!(windows) { - "DO YOU LIKE ICE CREAM? Y/N" + "\\rDO YOU LIKE ICE CREAM? Y/N" } else { - "Do you like ice cream? y/n" + "\\rDo you like ice cream? y/n" }; console.expect(format!( - "┌ ⚠️ Deno requests env access to \"{}\".", + "\u{250c} \u{26a0}\u{fe0f} Deno requests env access to \"{}\".", env_name )) }); @@ -4747,14 +4747,10 @@ fn permission_prompt_strips_ansi_codes_and_control_chars() { console.expect("undefined"); console.write_line_raw(r#"const unboldANSI = "\u001b[22m";"#); console.expect("undefined"); - console.write_line_raw(r#"const prompt = `┌ ⚠️ ${boldANSI}Deno requests run access to "echo"${unboldANSI}\n ├ Requested by \`Deno.Command().output()`"#); - console.expect("undefined"); - console.write_line_raw(r#"const moveANSIUp = "\u001b[1A";"#); - console.expect("undefined"); - console.write_line_raw(r#"const clearANSI = "\u001b[2K";"#); - console.expect("undefined"); - console.write_line_raw(r#"const moveANSIStart = "\u001b[1000D";"#); - console.expect("undefined"); + console.write_line_raw( + r#"new Deno.Command(`${boldANSI}cat${unboldANSI}`).spawn();"#, + ); + console.expect("\u{250c} \u{26a0}\u{fe0f} Deno requests run access to \"\\u{1b}[1mcat\\u{1b}[22m\"."); }); }