diff --git a/Cargo.lock b/Cargo.lock index 966b193d91af67774ed6802d65baf558b5efae9d..8c0f51e2893f53c3e6ef78dbd5ef711f8d9dc95c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7266,6 +7266,7 @@ name = "git_ui" version = "0.1.0" dependencies = [ "agent_settings", + "alacritty_terminal", "anyhow", "askpass", "buffer_diff", diff --git a/crates/git_ui/Cargo.toml b/crates/git_ui/Cargo.toml index e06d16708697f721d9377365223dc444ba7b08ae..6927ae16a5c4aa50e5d91563dbb84b1f2e085fd0 100644 --- a/crates/git_ui/Cargo.toml +++ b/crates/git_ui/Cargo.toml @@ -17,6 +17,7 @@ test-support = ["multi_buffer/test-support", "remote_connection/test-support"] [dependencies] agent_settings.workspace = true +alacritty_terminal.workspace = true anyhow.workspace = true askpass.workspace = true buffer_diff.workspace = true diff --git a/crates/git_ui/src/git_panel.rs b/crates/git_ui/src/git_panel.rs index 0cb8ec6b78929d216b700b6e21cbf43a538c6f56..a6f1e097cfe1cc0c012ff77987011571760b3ef0 100644 --- a/crates/git_ui/src/git_panel.rs +++ b/crates/git_ui/src/git_panel.rs @@ -11,6 +11,7 @@ use crate::{ repository_selector::RepositorySelector, }; use agent_settings::AgentSettings; +use alacritty_terminal::vte::ansi; use anyhow::Context as _; use askpass::AskPassDelegate; use collections::{BTreeMap, HashMap, HashSet}; @@ -6407,7 +6408,13 @@ fn open_output( cx: &mut Context, ) { let operation = operation.into(); - let buffer = cx.new(|cx| Buffer::local(output, cx)); + + let mut handler = GitOutputHandler::default(); + let mut processor = ansi::Processor::::default(); + processor.advance(&mut handler, output.as_bytes()); + let plain_text = handler.output; + + let buffer = cx.new(|cx| Buffer::local(plain_text.as_str(), cx)); buffer.update(cx, |buffer, cx| { buffer.set_capability(language::Capability::ReadOnly, cx); }); @@ -6423,6 +6430,32 @@ fn open_output( workspace.add_item_to_center(Box::new(editor), window, cx); } +#[derive(Default)] +struct GitOutputHandler { + output: String, + line_start: usize, +} + +impl ansi::Handler for GitOutputHandler { + fn input(&mut self, c: char) { + self.output.push(c); + } + + fn linefeed(&mut self) { + self.output.push('\n'); + self.line_start = self.output.len(); + } + + fn carriage_return(&mut self) { + self.output.truncate(self.line_start); + } + + fn put_tab(&mut self, count: u16) { + self.output + .extend(std::iter::repeat_n('\t', count as usize)); + } +} + pub(crate) fn show_error_toast( workspace: Entity, action: impl Into, @@ -7863,6 +7896,25 @@ mod tests { assert_eq!(message, Some("Update tracked".to_string())); } + #[test] + fn test_git_output_handler_strips_ansi_codes() { + use alacritty_terminal::vte::ansi; + + let cases = [ + ("no escape codes here\n", "no escape codes here\n"), + ("\x1b[31mhello\x1b[0m", "hello"), + ("\x1b[1;32mfoo\x1b[0m bar", "foo bar"), + ("progress 10%\rprogress 100%\n", "progress 100%\n"), + ]; + + for (input, expected) in cases { + let mut handler = GitOutputHandler::default(); + let mut processor = ansi::Processor::::default(); + processor.advance(&mut handler, input.as_bytes()); + assert_eq!(handler.output, expected); + } + } + #[gpui::test] async fn test_dispatch_context_with_focus_states(cx: &mut TestAppContext) { init_test(cx);