Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for hyperlinks and other OSC codes #43

Merged
merged 1 commit into from
Jun 2, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
176 changes: 118 additions & 58 deletions src/ansi.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
#![allow(missing_docs)]
use crate::style::{Color, Style};
use crate::style::{Color, OSControl, Style};
use crate::write::AnyWrite;
use std::fmt;

Expand All @@ -13,75 +13,95 @@ impl Style {
return Ok(());
}

// Prefix everything with reset characters if needed
if self.with_reset {
write!(f, "\x1B[0m")?
}
if self.has_sgr() {
// Prefix everything with reset characters if needed
if self.with_reset {
write!(f, "\x1B[0m")?
}

// Write the codes’ prefix, then write numbers, separated by
// semicolons, for each text style we want to apply.
write!(f, "\x1B[")?;
let mut written_anything = false;
// "Specified Graphical Rendition" prefixes
// Write the codes’ prefix, then write numbers, separated by
// semicolons, for each text style we want to apply.
write!(f, "\x1B[")?;
let mut written_anything = false;

{
let mut write_char = |c| {
if written_anything {
write!(f, ";")?;
}
written_anything = true;
#[cfg(feature = "gnu_legacy")]
write!(f, "0")?;
write!(f, "{}", c)?;
Ok(())
};

if self.is_bold {
write_char('1')?
}
if self.is_dimmed {
write_char('2')?
}
if self.is_italic {
write_char('3')?
}
if self.is_underline {
write_char('4')?
}
if self.is_blink {
write_char('5')?
}
if self.is_reverse {
write_char('7')?
}
if self.is_hidden {
write_char('8')?
}
if self.is_strikethrough {
write_char('9')?
}
}

{
let mut write_char = |c| {
// The foreground and background colors, if specified, need to be
// handled specially because the number codes are more complicated.
// (see `write_background_code` and `write_foreground_code`)
if let Some(bg) = self.background {
if written_anything {
write!(f, ";")?;
}
written_anything = true;
#[cfg(feature = "gnu_legacy")]
write!(f, "0")?;
write!(f, "{}", c)?;
Ok(())
};

if self.is_bold {
write_char('1')?
}
if self.is_dimmed {
write_char('2')?
}
if self.is_italic {
write_char('3')?
}
if self.is_underline {
write_char('4')?
}
if self.is_blink {
write_char('5')?
}
if self.is_reverse {
write_char('7')?
}
if self.is_hidden {
write_char('8')?
bg.write_background_code(f)?;
}
if self.is_strikethrough {
write_char('9')?
}
}

// The foreground and background colors, if specified, need to be
// handled specially because the number codes are more complicated.
// (see `write_background_code` and `write_foreground_code`)
if let Some(bg) = self.background {
if written_anything {
write!(f, ";")?;
if let Some(fg) = self.foreground {
if written_anything {
write!(f, ";")?;
}
fg.write_foreground_code(f)?;
}
written_anything = true;
bg.write_background_code(f)?;

// All the SGR codes end with an `m`, because reasons.
write!(f, "m")?;
}

if let Some(fg) = self.foreground {
if written_anything {
write!(f, ";")?;
// OS Control (OSC) prefixes
match self.oscontrol {
Some(OSControl::Hyperlink) => {
write!(f, "\x1B]8;;")?;
}
Some(OSControl::Title) => {
write!(f, "\x1B]2;")?;
}
Some(OSControl::Icon) => {
write!(f, "\x1B]I;")?;
}
Some(OSControl::Cwd) => {
write!(f, "\x1B]7;")?;
}
fg.write_foreground_code(f)?;
None => {}
}

// All the codes end with an `m`, because reasons.
write!(f, "m")?;

Ok(())
}

Expand All @@ -90,14 +110,28 @@ impl Style {
if self.is_plain() {
Ok(())
} else {
write!(f, "{}", RESET)
match self.oscontrol {
Some(OSControl::Hyperlink) => {
write!(f, "{}{}", HYPERLINK_RESET, RESET)
}
Some(OSControl::Title) | Some(OSControl::Icon) | Some(OSControl::Cwd) => {
write!(f, "{}", ST)
}
_ => {
write!(f, "{}", RESET)
}
}
}
}
}

/// The code to send to reset all styles and return to `Style::default()`.
pub static RESET: &str = "\x1B[0m";

// The "String Termination" code. Used for OS Control (OSC) sequences.
static ST: &str = "\x1B\\";
pub(crate) static HYPERLINK_RESET: &str = "\x1B]8;;\x1B\\";

impl Color {
fn write_foreground_code<W: AnyWrite + ?Sized>(&self, f: &mut W) -> Result<(), W::Error> {
match self {
Expand Down Expand Up @@ -362,7 +396,33 @@ impl fmt::Display for Infix {
}
Difference::Reset => {
let f: &mut dyn fmt::Write = f;
write!(f, "{}{}", RESET, self.1.prefix())

match (self.0, self.1) {
(
Style {
oscontrol: Some(OSControl::Hyperlink),
..
},
Style {
oscontrol: None, ..
},
) => {
write!(f, "{}{}", HYPERLINK_RESET, self.1.prefix())
}
(
Style {
oscontrol: Some(_), ..
},
Style {
oscontrol: None, ..
},
) => {
write!(f, "{}{}", ST, self.1.prefix())
}
(_, _) => {
write!(f, "{}{}", RESET, self.1.prefix())
}
}
}
Difference::Empty => {
Ok(()) // nothing to write
Expand Down
12 changes: 12 additions & 0 deletions src/difference.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,10 @@ impl Difference {
return Empty;
}

if first.has_sgr() && !next.has_sgr() {
return Reset;
}

// Cannot un-bold, so must Reset.
if first.is_bold && !next.is_bold {
return Reset;
Expand Down Expand Up @@ -87,6 +91,10 @@ impl Difference {
return Reset;
}

if first.oscontrol.is_some() && next.oscontrol.is_none() {
return Reset;
}

let mut extra_styles = Style::default();

if first.is_bold != next.is_bold {
Expand Down Expand Up @@ -129,6 +137,10 @@ impl Difference {
extra_styles.background = next.background;
}

if first.oscontrol != next.oscontrol {
extra_styles.oscontrol = next.oscontrol;
}

ExtraStyles(extra_styles)
}
}
Expand Down
Loading