mirror of
				https://github.com/sharkdp/bat.git
				synced 2025-10-31 15:12:12 +00:00 
			
		
		
		
	Merge branch 'master' into skip-highlighting-when-no-color
This commit is contained in:
		| @@ -2,11 +2,15 @@ | ||||
|  | ||||
| ## Features | ||||
|  | ||||
| - Set terminal title to file names when Paging is not Paging::Never #2807 (@Oliver-Looney) | ||||
|  | ||||
| ## Bugfixes | ||||
|  | ||||
| - Fix long file name wrapping in header, see #2835 (@FilipRazek) | ||||
| - Fix `NO_COLOR` support, see #2767 (@acuteenvy) | ||||
| - Fix handling of inputs with OSC ANSI escape sequences, see #2541 and #2544 (@eth-p) | ||||
| - Fix handling of inputs with combined ANSI color and attribute sequences, see #2185 and #2856 (@eth-p) | ||||
| - Fix panel width when line 10000 wraps, see #2854 (@eth-p) | ||||
|  | ||||
| ## Other | ||||
|  | ||||
| @@ -23,6 +27,7 @@ | ||||
| - Update git-version dependency to use Syn v2, see #2816 (@dtolnay) | ||||
| - Update git2 dependency to v0.18.2, see #2852 (@eth-p) | ||||
| - Improve performance when color output disabled, see #2397 and #2857 (@eth-p) | ||||
| - Apply clippy fixes #2864 (@cyqsimon) | ||||
|  | ||||
| ## Syntaxes | ||||
|  | ||||
|   | ||||
| @@ -160,6 +160,9 @@ Options: | ||||
|       --acknowledgements | ||||
|           Show acknowledgements. | ||||
|  | ||||
|       --set-terminal-title | ||||
|           Sets terminal title to filenames when using a pager. | ||||
|  | ||||
|   -h, --help | ||||
|           Print help (see a summary with '-h') | ||||
|  | ||||
|   | ||||
| @@ -289,6 +289,7 @@ impl App { | ||||
|             use_custom_assets: !self.matches.get_flag("no-custom-assets"), | ||||
|             #[cfg(feature = "lessopen")] | ||||
|             use_lessopen: self.matches.get_flag("lessopen"), | ||||
|             set_terminal_title: self.matches.get_flag("set-terminal-title"), | ||||
|         }) | ||||
|     } | ||||
|  | ||||
|   | ||||
| @@ -567,6 +567,13 @@ pub fn build_app(interactive_output: bool) -> Command { | ||||
|                 .action(ArgAction::SetTrue) | ||||
|                 .hide_short_help(true) | ||||
|                 .help("Show acknowledgements."), | ||||
|         ) | ||||
|         .arg( | ||||
|             Arg::new("set-terminal-title") | ||||
|                 .long("set-terminal-title") | ||||
|                 .action(ArgAction::SetTrue) | ||||
|                 .hide_short_help(true) | ||||
|                 .help("Sets terminal title to filenames when using a pager."), | ||||
|         ); | ||||
|  | ||||
|     // Check if the current directory contains a file name cache. Otherwise, | ||||
|   | ||||
| @@ -229,9 +229,33 @@ pub fn list_themes(cfg: &Config, config_dir: &Path, cache_dir: &Path) -> Result< | ||||
|     Ok(()) | ||||
| } | ||||
|  | ||||
| fn set_terminal_title_to(new_terminal_title: String) { | ||||
|     let osc_command_for_setting_terminal_title = "\x1b]0;"; | ||||
|     let osc_end_command = "\x07"; | ||||
|     print!( | ||||
|         "{}{}{}", | ||||
|         osc_command_for_setting_terminal_title, new_terminal_title, osc_end_command | ||||
|     ); | ||||
|     io::stdout().flush().unwrap(); | ||||
| } | ||||
|  | ||||
| fn get_new_terminal_title(inputs: &Vec<Input>) -> String { | ||||
|     let mut new_terminal_title = "bat: ".to_string(); | ||||
|     for (index, input) in inputs.iter().enumerate() { | ||||
|         new_terminal_title += input.description().title(); | ||||
|         if index < inputs.len() - 1 { | ||||
|             new_terminal_title += ", "; | ||||
|         } | ||||
|     } | ||||
|     new_terminal_title | ||||
| } | ||||
|  | ||||
| fn run_controller(inputs: Vec<Input>, config: &Config, cache_dir: &Path) -> Result<bool> { | ||||
|     let assets = assets_from_cache_or_binary(config.use_custom_assets, cache_dir)?; | ||||
|     let controller = Controller::new(config, &assets); | ||||
|     if config.paging_mode != PagingMode::Never && config.set_terminal_title { | ||||
|         set_terminal_title_to(get_new_terminal_title(&inputs)); | ||||
|     } | ||||
|     controller.run(inputs, None) | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -94,6 +94,9 @@ pub struct Config<'a> { | ||||
|     // Whether or not to use $LESSOPEN if set | ||||
|     #[cfg(feature = "lessopen")] | ||||
|     pub use_lessopen: bool, | ||||
|  | ||||
|     // Weather or not to set terminal title when using a pager | ||||
|     pub set_terminal_title: bool, | ||||
| } | ||||
|  | ||||
| #[cfg(all(feature = "minimal-application", feature = "paging"))] | ||||
|   | ||||
| @@ -46,7 +46,7 @@ impl Decoration for LineNumberDecoration { | ||||
|         _printer: &InteractivePrinter, | ||||
|     ) -> DecorationText { | ||||
|         if continuation { | ||||
|             if line_number > self.cached_wrap_invalid_at { | ||||
|             if line_number >= self.cached_wrap_invalid_at { | ||||
|                 let new_width = self.cached_wrap.width + 1; | ||||
|                 return DecorationText { | ||||
|                     text: self.color.paint(" ".repeat(new_width)).to_string(), | ||||
|   | ||||
| @@ -24,9 +24,9 @@ impl AnsiStyle { | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     pub fn to_reset_sequence(&mut self) -> String { | ||||
|         match &mut self.attributes { | ||||
|             Some(a) => a.to_reset_sequence(), | ||||
|     pub fn to_reset_sequence(&self) -> String { | ||||
|         match self.attributes { | ||||
|             Some(ref a) => a.to_reset_sequence(), | ||||
|             None => String::new(), | ||||
|         } | ||||
|     } | ||||
| @@ -169,10 +169,10 @@ impl Attributes { | ||||
|         while let Some(p) = iter.next() { | ||||
|             match p { | ||||
|                 0 => self.sgr_reset(), | ||||
|                 1 => self.bold = format!("\x1B[{}m", parameters), | ||||
|                 2 => self.dim = format!("\x1B[{}m", parameters), | ||||
|                 3 => self.italic = format!("\x1B[{}m", parameters), | ||||
|                 4 => self.underline = format!("\x1B[{}m", parameters), | ||||
|                 1 => self.bold = "\x1B[1m".to_owned(), | ||||
|                 2 => self.dim = "\x1B[2m".to_owned(), | ||||
|                 3 => self.italic = "\x1B[3m".to_owned(), | ||||
|                 4 => self.underline = "\x1B[4m".to_owned(), | ||||
|                 23 => self.italic.clear(), | ||||
|                 24 => self.underline.clear(), | ||||
|                 22 => { | ||||
| @@ -183,7 +183,7 @@ impl Attributes { | ||||
|                 40..=49 => self.background = Self::parse_color(p, &mut iter), | ||||
|                 58..=59 => self.underlined = Self::parse_color(p, &mut iter), | ||||
|                 90..=97 => self.foreground = Self::parse_color(p, &mut iter), | ||||
|                 100..=107 => self.foreground = Self::parse_color(p, &mut iter), | ||||
|                 100..=107 => self.background = Self::parse_color(p, &mut iter), | ||||
|                 _ => { | ||||
|                     // Unsupported SGR sequence. | ||||
|                     // Be compatible and pretend one just wasn't was provided. | ||||
| @@ -294,12 +294,14 @@ enum EscapeSequenceOffsets { | ||||
|         start: usize, | ||||
|         end: usize, | ||||
|     }, | ||||
|     #[allow(clippy::upper_case_acronyms)] | ||||
|     NF { | ||||
|         // https://en.wikipedia.org/wiki/ANSI_escape_code#nF_Escape_sequences | ||||
|         start_sequence: usize, | ||||
|         start: usize, | ||||
|         end: usize, | ||||
|     }, | ||||
|     #[allow(clippy::upper_case_acronyms)] | ||||
|     OSC { | ||||
|         // https://en.wikipedia.org/wiki/ANSI_escape_code#OSC_(Operating_System_Command)_sequences | ||||
|         start_sequence: usize, | ||||
| @@ -307,6 +309,7 @@ enum EscapeSequenceOffsets { | ||||
|         start_terminator: usize, | ||||
|         end: usize, | ||||
|     }, | ||||
|     #[allow(clippy::upper_case_acronyms)] | ||||
|     CSI { | ||||
|         // https://en.wikipedia.org/wiki/ANSI_escape_code#CSI_(Control_Sequence_Introducer)_sequences | ||||
|         start_sequence: usize, | ||||
| @@ -340,9 +343,7 @@ impl<'a> EscapeSequenceOffsetsIterator<'a> { | ||||
|     /// Takes values from the iterator while the predicate returns true. | ||||
|     /// If the predicate returns false, that value is left. | ||||
|     fn chars_take_while(&mut self, pred: impl Fn(char) -> bool) -> Option<(usize, usize)> { | ||||
|         if self.chars.peek().is_none() { | ||||
|             return None; | ||||
|         } | ||||
|         self.chars.peek()?; | ||||
|  | ||||
|         let start = self.chars.peek().unwrap().0; | ||||
|         let mut end: usize = start; | ||||
| @@ -359,10 +360,8 @@ impl<'a> EscapeSequenceOffsetsIterator<'a> { | ||||
|     } | ||||
|  | ||||
|     fn next_text(&mut self) -> Option<EscapeSequenceOffsets> { | ||||
|         match self.chars_take_while(|c| c != '\x1B') { | ||||
|             None => None, | ||||
|             Some((start, end)) => Some(EscapeSequenceOffsets::Text { start, end }), | ||||
|         } | ||||
|         self.chars_take_while(|c| c != '\x1B') | ||||
|             .map(|(start, end)| EscapeSequenceOffsets::Text { start, end }) | ||||
|     } | ||||
|  | ||||
|     fn next_sequence(&mut self) -> Option<EscapeSequenceOffsets> { | ||||
| @@ -444,7 +443,7 @@ impl<'a> EscapeSequenceOffsetsIterator<'a> { | ||||
|         Some(EscapeSequenceOffsets::OSC { | ||||
|             start_sequence, | ||||
|             start_command: osc_open_index + osc_open_char.len_utf8(), | ||||
|             start_terminator: start_terminator, | ||||
|             start_terminator, | ||||
|             end: end_sequence, | ||||
|         }) | ||||
|     } | ||||
| @@ -502,9 +501,8 @@ impl<'a> EscapeSequenceOffsetsIterator<'a> { | ||||
|         } | ||||
|  | ||||
|         // Get the final byte. | ||||
|         match self.chars.next() { | ||||
|             Some((i, c)) => end = i + c.len_utf8(), | ||||
|             None => {} | ||||
|         if let Some((i, c)) = self.chars.next() { | ||||
|             end = i + c.len_utf8() | ||||
|         } | ||||
|  | ||||
|         Some(EscapeSequenceOffsets::NF { | ||||
| @@ -593,15 +591,18 @@ impl<'a> Iterator for EscapeSequenceIterator<'a> { | ||||
| pub enum EscapeSequence<'a> { | ||||
|     Text(&'a str), | ||||
|     Unknown(&'a str), | ||||
|     #[allow(clippy::upper_case_acronyms)] | ||||
|     NF { | ||||
|         raw_sequence: &'a str, | ||||
|         nf_sequence: &'a str, | ||||
|     }, | ||||
|     #[allow(clippy::upper_case_acronyms)] | ||||
|     OSC { | ||||
|         raw_sequence: &'a str, | ||||
|         command: &'a str, | ||||
|         terminator: &'a str, | ||||
|     }, | ||||
|     #[allow(clippy::upper_case_acronyms)] | ||||
|     CSI { | ||||
|         raw_sequence: &'a str, | ||||
|         parameters: &'a str, | ||||
| @@ -890,4 +891,37 @@ mod tests { | ||||
|         ); | ||||
|         assert_eq!(iter.next(), None); | ||||
|     } | ||||
|  | ||||
|     #[test] | ||||
|     fn test_sgr_attributes_do_not_leak_into_wrong_field() { | ||||
|         let mut attrs = crate::vscreen::Attributes::new(); | ||||
|  | ||||
|         // Bold, Dim, Italic, Underline, Foreground, Background | ||||
|         attrs.update(EscapeSequence::CSI { | ||||
|             raw_sequence: "\x1B[1;2;3;4;31;41m", | ||||
|             parameters: "1;2;3;4;31;41", | ||||
|             intermediates: "", | ||||
|             final_byte: "m", | ||||
|         }); | ||||
|  | ||||
|         assert_eq!(attrs.bold, "\x1B[1m"); | ||||
|         assert_eq!(attrs.dim, "\x1B[2m"); | ||||
|         assert_eq!(attrs.italic, "\x1B[3m"); | ||||
|         assert_eq!(attrs.underline, "\x1B[4m"); | ||||
|         assert_eq!(attrs.foreground, "\x1B[31m"); | ||||
|         assert_eq!(attrs.background, "\x1B[41m"); | ||||
|  | ||||
|         // Bold, Bright Foreground, Bright Background | ||||
|         attrs.sgr_reset(); | ||||
|         attrs.update(EscapeSequence::CSI { | ||||
|             raw_sequence: "\x1B[1;94;103m", | ||||
|             parameters: "1;94;103", | ||||
|             intermediates: "", | ||||
|             final_byte: "m", | ||||
|         }); | ||||
|  | ||||
|         assert_eq!(attrs.bold, "\x1B[1m"); | ||||
|         assert_eq!(attrs.foreground, "\x1B[94m"); | ||||
|         assert_eq!(attrs.background, "\x1B[103m"); | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -936,6 +936,18 @@ fn env_var_bat_paging() { | ||||
|     }); | ||||
| } | ||||
|  | ||||
| #[test] | ||||
| fn basic_set_terminal_title() { | ||||
|     bat() | ||||
|         .arg("--paging=always") | ||||
|         .arg("--set-terminal-title") | ||||
|         .arg("test.txt") | ||||
|         .assert() | ||||
|         .success() | ||||
|         .stdout("\u{1b}]0;bat: test.txt\x07hello world\n") | ||||
|         .stderr(""); | ||||
| } | ||||
|  | ||||
| #[test] | ||||
| fn diagnostic_sanity_check() { | ||||
|     bat() | ||||
|   | ||||
| @@ -22,7 +22,7 @@ impl BatTester { | ||||
|     pub fn test_snapshot(&self, name: &str, style: &str) { | ||||
|         let output = Command::new(&self.exe) | ||||
|             .current_dir(self.temp_dir.path()) | ||||
|             .args(&[ | ||||
|             .args([ | ||||
|                 "sample.rs", | ||||
|                 "--no-config", | ||||
|                 "--paging=never", | ||||
|   | ||||
		Reference in New Issue
	
	Block a user