feat(terminal): support styled underlines (#2730)
* feat: support styled underlines * remove deadcode * Add ansi_underlines config option * Add missing variables * Add ansi_underlines on Output and OutputBuffer * Fix tests * Add separate styled underline enum * Remove ansi_underlines from fg and bg * Remove unneeded variables * Rename ansi_underlines -> styled_underlines * Simplify CharacterStyles::new() * Move styled_underlines config description * Fix single underline and remove extra field on CharacterStyles * Read styled-underlines flag from cli opts * remove extra attribute left from merge conflict --------- Co-authored-by: Mike Lloyd <mike.lloyd03@pm.me> Co-authored-by: Mike Lloyd <49411532+mike-lloyd03@users.noreply.github.com> Co-authored-by: Aram Drevekenin <aram@poor.dev>
This commit is contained in:
parent
3942000e86
commit
7f87d93a43
18 changed files with 155 additions and 18 deletions
|
|
@ -83,6 +83,7 @@ fn serialize_chunks_with_newlines(
|
||||||
character_chunks: Vec<CharacterChunk>,
|
character_chunks: Vec<CharacterChunk>,
|
||||||
_sixel_chunks: Option<&Vec<SixelImageChunk>>, // TODO: fix this sometime
|
_sixel_chunks: Option<&Vec<SixelImageChunk>>, // TODO: fix this sometime
|
||||||
link_handler: Option<&mut Rc<RefCell<LinkHandler>>>,
|
link_handler: Option<&mut Rc<RefCell<LinkHandler>>>,
|
||||||
|
styled_underlines: bool,
|
||||||
) -> Result<String> {
|
) -> Result<String> {
|
||||||
let err_context = || "failed to serialize input chunks".to_string();
|
let err_context = || "failed to serialize input chunks".to_string();
|
||||||
|
|
||||||
|
|
@ -90,7 +91,8 @@ fn serialize_chunks_with_newlines(
|
||||||
let link_handler = link_handler.map(|l_h| l_h.borrow());
|
let link_handler = link_handler.map(|l_h| l_h.borrow());
|
||||||
for character_chunk in character_chunks {
|
for character_chunk in character_chunks {
|
||||||
let chunk_changed_colors = character_chunk.changed_colors();
|
let chunk_changed_colors = character_chunk.changed_colors();
|
||||||
let mut character_styles = CharacterStyles::new();
|
let mut character_styles =
|
||||||
|
CharacterStyles::new().enable_styled_underlines(styled_underlines);
|
||||||
vte_output.push_str("\n\r");
|
vte_output.push_str("\n\r");
|
||||||
let mut chunk_width = character_chunk.x;
|
let mut chunk_width = character_chunk.x;
|
||||||
for t_character in character_chunk.terminal_characters.iter() {
|
for t_character in character_chunk.terminal_characters.iter() {
|
||||||
|
|
@ -120,6 +122,7 @@ fn serialize_chunks(
|
||||||
sixel_chunks: Option<&Vec<SixelImageChunk>>,
|
sixel_chunks: Option<&Vec<SixelImageChunk>>,
|
||||||
link_handler: Option<&mut Rc<RefCell<LinkHandler>>>,
|
link_handler: Option<&mut Rc<RefCell<LinkHandler>>>,
|
||||||
sixel_image_store: Option<&mut SixelImageStore>,
|
sixel_image_store: Option<&mut SixelImageStore>,
|
||||||
|
styled_underlines: bool,
|
||||||
) -> Result<String> {
|
) -> Result<String> {
|
||||||
let err_context = || "failed to serialize input chunks".to_string();
|
let err_context = || "failed to serialize input chunks".to_string();
|
||||||
|
|
||||||
|
|
@ -128,7 +131,8 @@ fn serialize_chunks(
|
||||||
let link_handler = link_handler.map(|l_h| l_h.borrow());
|
let link_handler = link_handler.map(|l_h| l_h.borrow());
|
||||||
for character_chunk in character_chunks {
|
for character_chunk in character_chunks {
|
||||||
let chunk_changed_colors = character_chunk.changed_colors();
|
let chunk_changed_colors = character_chunk.changed_colors();
|
||||||
let mut character_styles = CharacterStyles::new();
|
let mut character_styles =
|
||||||
|
CharacterStyles::new().enable_styled_underlines(styled_underlines);
|
||||||
vte_goto_instruction(character_chunk.x, character_chunk.y, &mut vte_output)
|
vte_goto_instruction(character_chunk.x, character_chunk.y, &mut vte_output)
|
||||||
.with_context(err_context)?;
|
.with_context(err_context)?;
|
||||||
let mut chunk_width = character_chunk.x;
|
let mut chunk_width = character_chunk.x;
|
||||||
|
|
@ -245,16 +249,19 @@ pub struct Output {
|
||||||
sixel_image_store: Rc<RefCell<SixelImageStore>>,
|
sixel_image_store: Rc<RefCell<SixelImageStore>>,
|
||||||
character_cell_size: Rc<RefCell<Option<SizeInPixels>>>,
|
character_cell_size: Rc<RefCell<Option<SizeInPixels>>>,
|
||||||
floating_panes_stack: Option<FloatingPanesStack>,
|
floating_panes_stack: Option<FloatingPanesStack>,
|
||||||
|
styled_underlines: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Output {
|
impl Output {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
sixel_image_store: Rc<RefCell<SixelImageStore>>,
|
sixel_image_store: Rc<RefCell<SixelImageStore>>,
|
||||||
character_cell_size: Rc<RefCell<Option<SizeInPixels>>>,
|
character_cell_size: Rc<RefCell<Option<SizeInPixels>>>,
|
||||||
|
styled_underlines: bool,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Output {
|
Output {
|
||||||
sixel_image_store,
|
sixel_image_store,
|
||||||
character_cell_size,
|
character_cell_size,
|
||||||
|
styled_underlines,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -417,6 +424,7 @@ impl Output {
|
||||||
self.sixel_chunks.get(&client_id),
|
self.sixel_chunks.get(&client_id),
|
||||||
self.link_handler.as_mut(),
|
self.link_handler.as_mut(),
|
||||||
Some(&mut self.sixel_image_store.borrow_mut()),
|
Some(&mut self.sixel_image_store.borrow_mut()),
|
||||||
|
self.styled_underlines,
|
||||||
)
|
)
|
||||||
.with_context(err_context)?,
|
.with_context(err_context)?,
|
||||||
); // TODO: less allocations?
|
); // TODO: less allocations?
|
||||||
|
|
@ -869,6 +877,7 @@ impl CharacterChunk {
|
||||||
pub struct OutputBuffer {
|
pub struct OutputBuffer {
|
||||||
pub changed_lines: HashSet<usize>, // line index
|
pub changed_lines: HashSet<usize>, // line index
|
||||||
pub should_update_all_lines: bool,
|
pub should_update_all_lines: bool,
|
||||||
|
styled_underlines: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for OutputBuffer {
|
impl Default for OutputBuffer {
|
||||||
|
|
@ -876,6 +885,7 @@ impl Default for OutputBuffer {
|
||||||
OutputBuffer {
|
OutputBuffer {
|
||||||
changed_lines: HashSet::new(),
|
changed_lines: HashSet::new(),
|
||||||
should_update_all_lines: true, // first time we should do a full render
|
should_update_all_lines: true, // first time we should do a full render
|
||||||
|
styled_underlines: true,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -913,7 +923,7 @@ impl OutputBuffer {
|
||||||
let y = line_index;
|
let y = line_index;
|
||||||
chunks.push(CharacterChunk::new(terminal_characters, x, y));
|
chunks.push(CharacterChunk::new(terminal_characters, x, y));
|
||||||
}
|
}
|
||||||
serialize_chunks_with_newlines(chunks, None, None)
|
serialize_chunks_with_newlines(chunks, None, None, self.styled_underlines)
|
||||||
}
|
}
|
||||||
pub fn changed_chunks_in_viewport(
|
pub fn changed_chunks_in_viewport(
|
||||||
&self,
|
&self,
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,7 @@ pub const EMPTY_TERMINAL_CHARACTER: TerminalCharacter = TerminalCharacter {
|
||||||
pub const RESET_STYLES: CharacterStyles = CharacterStyles {
|
pub const RESET_STYLES: CharacterStyles = CharacterStyles {
|
||||||
foreground: Some(AnsiCode::Reset),
|
foreground: Some(AnsiCode::Reset),
|
||||||
background: Some(AnsiCode::Reset),
|
background: Some(AnsiCode::Reset),
|
||||||
|
underline_color: Some(AnsiCode::Reset),
|
||||||
strike: Some(AnsiCode::Reset),
|
strike: Some(AnsiCode::Reset),
|
||||||
hidden: Some(AnsiCode::Reset),
|
hidden: Some(AnsiCode::Reset),
|
||||||
reverse: Some(AnsiCode::Reset),
|
reverse: Some(AnsiCode::Reset),
|
||||||
|
|
@ -31,6 +32,7 @@ pub const RESET_STYLES: CharacterStyles = CharacterStyles {
|
||||||
dim: Some(AnsiCode::Reset),
|
dim: Some(AnsiCode::Reset),
|
||||||
italic: Some(AnsiCode::Reset),
|
italic: Some(AnsiCode::Reset),
|
||||||
link_anchor: Some(LinkAnchor::End),
|
link_anchor: Some(LinkAnchor::End),
|
||||||
|
styled_underlines_enabled: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||||
|
|
@ -40,6 +42,15 @@ pub enum AnsiCode {
|
||||||
NamedColor(NamedColor),
|
NamedColor(NamedColor),
|
||||||
RgbCode((u8, u8, u8)),
|
RgbCode((u8, u8, u8)),
|
||||||
ColorIndex(u8),
|
ColorIndex(u8),
|
||||||
|
Underline(Option<AnsiStyledUnderline>),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||||
|
pub enum AnsiStyledUnderline {
|
||||||
|
Double,
|
||||||
|
Undercurl,
|
||||||
|
Underdotted,
|
||||||
|
Underdashed,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<PaletteColor> for AnsiCode {
|
impl From<PaletteColor> for AnsiCode {
|
||||||
|
|
@ -122,6 +133,7 @@ impl NamedColor {
|
||||||
pub struct CharacterStyles {
|
pub struct CharacterStyles {
|
||||||
pub foreground: Option<AnsiCode>,
|
pub foreground: Option<AnsiCode>,
|
||||||
pub background: Option<AnsiCode>,
|
pub background: Option<AnsiCode>,
|
||||||
|
pub underline_color: Option<AnsiCode>,
|
||||||
pub strike: Option<AnsiCode>,
|
pub strike: Option<AnsiCode>,
|
||||||
pub hidden: Option<AnsiCode>,
|
pub hidden: Option<AnsiCode>,
|
||||||
pub reverse: Option<AnsiCode>,
|
pub reverse: Option<AnsiCode>,
|
||||||
|
|
@ -132,6 +144,7 @@ pub struct CharacterStyles {
|
||||||
pub dim: Option<AnsiCode>,
|
pub dim: Option<AnsiCode>,
|
||||||
pub italic: Option<AnsiCode>,
|
pub italic: Option<AnsiCode>,
|
||||||
pub link_anchor: Option<LinkAnchor>,
|
pub link_anchor: Option<LinkAnchor>,
|
||||||
|
pub styled_underlines_enabled: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CharacterStyles {
|
impl CharacterStyles {
|
||||||
|
|
@ -146,6 +159,10 @@ impl CharacterStyles {
|
||||||
self.background = background_code;
|
self.background = background_code;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
pub fn underline_color(mut self, underline_color_code: Option<AnsiCode>) -> Self {
|
||||||
|
self.underline_color = underline_color_code;
|
||||||
|
self
|
||||||
|
}
|
||||||
pub fn bold(mut self, bold_code: Option<AnsiCode>) -> Self {
|
pub fn bold(mut self, bold_code: Option<AnsiCode>) -> Self {
|
||||||
self.bold = bold_code;
|
self.bold = bold_code;
|
||||||
self
|
self
|
||||||
|
|
@ -186,9 +203,14 @@ impl CharacterStyles {
|
||||||
self.link_anchor = link_anchor;
|
self.link_anchor = link_anchor;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
pub fn enable_styled_underlines(mut self, enabled: bool) -> Self {
|
||||||
|
self.styled_underlines_enabled = enabled;
|
||||||
|
self
|
||||||
|
}
|
||||||
pub fn clear(&mut self) {
|
pub fn clear(&mut self) {
|
||||||
self.foreground = None;
|
self.foreground = None;
|
||||||
self.background = None;
|
self.background = None;
|
||||||
|
self.underline_color = None;
|
||||||
self.strike = None;
|
self.strike = None;
|
||||||
self.hidden = None;
|
self.hidden = None;
|
||||||
self.reverse = None;
|
self.reverse = None;
|
||||||
|
|
@ -215,7 +237,8 @@ impl CharacterStyles {
|
||||||
}
|
}
|
||||||
|
|
||||||
// create diff from all changed styles
|
// create diff from all changed styles
|
||||||
let mut diff = CharacterStyles::new();
|
let mut diff =
|
||||||
|
CharacterStyles::new().enable_styled_underlines(self.styled_underlines_enabled);
|
||||||
|
|
||||||
if self.foreground != new_styles.foreground {
|
if self.foreground != new_styles.foreground {
|
||||||
diff.foreground = new_styles.foreground;
|
diff.foreground = new_styles.foreground;
|
||||||
|
|
@ -223,6 +246,9 @@ impl CharacterStyles {
|
||||||
if self.background != new_styles.background {
|
if self.background != new_styles.background {
|
||||||
diff.background = new_styles.background;
|
diff.background = new_styles.background;
|
||||||
}
|
}
|
||||||
|
if self.underline_color != new_styles.underline_color {
|
||||||
|
diff.underline_color = new_styles.underline_color;
|
||||||
|
}
|
||||||
if self.strike != new_styles.strike {
|
if self.strike != new_styles.strike {
|
||||||
diff.strike = new_styles.strike;
|
diff.strike = new_styles.strike;
|
||||||
}
|
}
|
||||||
|
|
@ -274,6 +300,7 @@ impl CharacterStyles {
|
||||||
pub fn reset_all(&mut self) {
|
pub fn reset_all(&mut self) {
|
||||||
self.foreground = Some(AnsiCode::Reset);
|
self.foreground = Some(AnsiCode::Reset);
|
||||||
self.background = Some(AnsiCode::Reset);
|
self.background = Some(AnsiCode::Reset);
|
||||||
|
self.underline_color = Some(AnsiCode::Reset);
|
||||||
self.bold = Some(AnsiCode::Reset);
|
self.bold = Some(AnsiCode::Reset);
|
||||||
self.dim = Some(AnsiCode::Reset);
|
self.dim = Some(AnsiCode::Reset);
|
||||||
self.italic = Some(AnsiCode::Reset);
|
self.italic = Some(AnsiCode::Reset);
|
||||||
|
|
@ -291,7 +318,28 @@ impl CharacterStyles {
|
||||||
[1] => *self = self.bold(Some(AnsiCode::On)),
|
[1] => *self = self.bold(Some(AnsiCode::On)),
|
||||||
[2] => *self = self.dim(Some(AnsiCode::On)),
|
[2] => *self = self.dim(Some(AnsiCode::On)),
|
||||||
[3] => *self = self.italic(Some(AnsiCode::On)),
|
[3] => *self = self.italic(Some(AnsiCode::On)),
|
||||||
[4] => *self = self.underline(Some(AnsiCode::On)),
|
[4, 0] => *self = self.underline(Some(AnsiCode::Reset)),
|
||||||
|
[4, 1] => *self = self.underline(Some(AnsiCode::Underline(None))),
|
||||||
|
[4, 2] => {
|
||||||
|
*self =
|
||||||
|
self.underline(Some(AnsiCode::Underline(Some(AnsiStyledUnderline::Double))))
|
||||||
|
},
|
||||||
|
[4, 3] => {
|
||||||
|
*self = self.underline(Some(AnsiCode::Underline(Some(
|
||||||
|
AnsiStyledUnderline::Undercurl,
|
||||||
|
))))
|
||||||
|
},
|
||||||
|
[4, 4] => {
|
||||||
|
*self = self.underline(Some(AnsiCode::Underline(Some(
|
||||||
|
AnsiStyledUnderline::Underdotted,
|
||||||
|
))))
|
||||||
|
},
|
||||||
|
[4, 5] => {
|
||||||
|
*self = self.underline(Some(AnsiCode::Underline(Some(
|
||||||
|
AnsiStyledUnderline::Underdashed,
|
||||||
|
))))
|
||||||
|
},
|
||||||
|
[4] => *self = self.underline(Some(AnsiCode::Underline(None))),
|
||||||
[5] => *self = self.blink_slow(Some(AnsiCode::On)),
|
[5] => *self = self.blink_slow(Some(AnsiCode::On)),
|
||||||
[6] => *self = self.blink_fast(Some(AnsiCode::On)),
|
[6] => *self = self.blink_fast(Some(AnsiCode::On)),
|
||||||
[7] => *self = self.reverse(Some(AnsiCode::On)),
|
[7] => *self = self.reverse(Some(AnsiCode::On)),
|
||||||
|
|
@ -357,6 +405,21 @@ impl CharacterStyles {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[49] => *self = self.background(Some(AnsiCode::Reset)),
|
[49] => *self = self.background(Some(AnsiCode::Reset)),
|
||||||
|
[58] => {
|
||||||
|
let mut iter = params.map(|param| param[0]);
|
||||||
|
if let Some(ansi_code) = parse_sgr_color(&mut iter) {
|
||||||
|
*self = self.underline_color(Some(ansi_code));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[58, params @ ..] => {
|
||||||
|
let rgb_start = if params.len() > 4 { 2 } else { 1 };
|
||||||
|
let rgb_iter = params[rgb_start..].iter().copied();
|
||||||
|
let mut iter = std::iter::once(params[0]).chain(rgb_iter);
|
||||||
|
if let Some(ansi_code) = parse_sgr_color(&mut iter) {
|
||||||
|
*self = self.underline_color(Some(ansi_code));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[59] => *self = self.underline_color(Some(AnsiCode::Reset)),
|
||||||
[90] => {
|
[90] => {
|
||||||
*self = self.foreground(Some(AnsiCode::NamedColor(NamedColor::BrightBlack)))
|
*self = self.foreground(Some(AnsiCode::NamedColor(NamedColor::BrightBlack)))
|
||||||
},
|
},
|
||||||
|
|
@ -409,6 +472,7 @@ impl Display for CharacterStyles {
|
||||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||||
if self.foreground == Some(AnsiCode::Reset)
|
if self.foreground == Some(AnsiCode::Reset)
|
||||||
&& self.background == Some(AnsiCode::Reset)
|
&& self.background == Some(AnsiCode::Reset)
|
||||||
|
&& self.underline_color == Some(AnsiCode::Reset)
|
||||||
&& self.strike == Some(AnsiCode::Reset)
|
&& self.strike == Some(AnsiCode::Reset)
|
||||||
&& self.hidden == Some(AnsiCode::Reset)
|
&& self.hidden == Some(AnsiCode::Reset)
|
||||||
&& self.reverse == Some(AnsiCode::Reset)
|
&& self.reverse == Some(AnsiCode::Reset)
|
||||||
|
|
@ -456,6 +520,22 @@ impl Display for CharacterStyles {
|
||||||
_ => {},
|
_ => {},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if self.styled_underlines_enabled {
|
||||||
|
if let Some(ansi_code) = self.underline_color {
|
||||||
|
match ansi_code {
|
||||||
|
AnsiCode::RgbCode((r, g, b)) => {
|
||||||
|
write!(f, "\u{1b}[58;2;{};{};{}m", r, g, b)?;
|
||||||
|
},
|
||||||
|
AnsiCode::ColorIndex(color_index) => {
|
||||||
|
write!(f, "\u{1b}[58;5;{}m", color_index)?;
|
||||||
|
},
|
||||||
|
AnsiCode::Reset => {
|
||||||
|
write!(f, "\u{1b}[59m")?;
|
||||||
|
},
|
||||||
|
_ => {},
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
if let Some(ansi_code) = self.strike {
|
if let Some(ansi_code) = self.strike {
|
||||||
match ansi_code {
|
match ansi_code {
|
||||||
AnsiCode::On => {
|
AnsiCode::On => {
|
||||||
|
|
@ -529,15 +609,34 @@ impl Display for CharacterStyles {
|
||||||
// otherwise
|
// otherwise
|
||||||
if let Some(ansi_code) = self.underline {
|
if let Some(ansi_code) = self.underline {
|
||||||
match ansi_code {
|
match ansi_code {
|
||||||
AnsiCode::On => {
|
AnsiCode::Underline(None) => {
|
||||||
write!(f, "\u{1b}[4m")?;
|
write!(f, "\u{1b}[4m")?;
|
||||||
},
|
},
|
||||||
|
AnsiCode::Underline(Some(styled)) => {
|
||||||
|
if self.styled_underlines_enabled {
|
||||||
|
match styled {
|
||||||
|
AnsiStyledUnderline::Double => {
|
||||||
|
write!(f, "\u{1b}[4:2m")?;
|
||||||
|
},
|
||||||
|
AnsiStyledUnderline::Undercurl => {
|
||||||
|
write!(f, "\u{1b}[4:3m")?;
|
||||||
|
},
|
||||||
|
AnsiStyledUnderline::Underdotted => {
|
||||||
|
write!(f, "\u{1b}[4:4m")?;
|
||||||
|
},
|
||||||
|
AnsiStyledUnderline::Underdashed => {
|
||||||
|
write!(f, "\u{1b}[4:5m")?;
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
AnsiCode::Reset => {
|
AnsiCode::Reset => {
|
||||||
write!(f, "\u{1b}[24m")?;
|
write!(f, "\u{1b}[24m")?;
|
||||||
},
|
},
|
||||||
_ => {},
|
_ => {},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(ansi_code) = self.dim {
|
if let Some(ansi_code) = self.dim {
|
||||||
match ansi_code {
|
match ansi_code {
|
||||||
AnsiCode::On => {
|
AnsiCode::On => {
|
||||||
|
|
|
||||||
|
|
@ -566,6 +566,7 @@ pub(crate) struct Screen {
|
||||||
// its creation time
|
// its creation time
|
||||||
default_layout: Box<Layout>,
|
default_layout: Box<Layout>,
|
||||||
default_shell: Option<PathBuf>,
|
default_shell: Option<PathBuf>,
|
||||||
|
styled_underlines: bool,
|
||||||
arrow_fonts: bool,
|
arrow_fonts: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -586,6 +587,7 @@ impl Screen {
|
||||||
session_serialization: bool,
|
session_serialization: bool,
|
||||||
serialize_pane_viewport: bool,
|
serialize_pane_viewport: bool,
|
||||||
scrollback_lines_to_serialize: Option<usize>,
|
scrollback_lines_to_serialize: Option<usize>,
|
||||||
|
styled_underlines: bool,
|
||||||
arrow_fonts: bool,
|
arrow_fonts: bool,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let session_name = mode_info.session_name.clone().unwrap_or_default();
|
let session_name = mode_info.session_name.clone().unwrap_or_default();
|
||||||
|
|
@ -622,6 +624,7 @@ impl Screen {
|
||||||
session_serialization,
|
session_serialization,
|
||||||
serialize_pane_viewport,
|
serialize_pane_viewport,
|
||||||
scrollback_lines_to_serialize,
|
scrollback_lines_to_serialize,
|
||||||
|
styled_underlines,
|
||||||
arrow_fonts,
|
arrow_fonts,
|
||||||
resurrectable_sessions,
|
resurrectable_sessions,
|
||||||
}
|
}
|
||||||
|
|
@ -1032,6 +1035,7 @@ impl Screen {
|
||||||
let mut output = Output::new(
|
let mut output = Output::new(
|
||||||
self.sixel_image_store.clone(),
|
self.sixel_image_store.clone(),
|
||||||
self.character_cell_size.clone(),
|
self.character_cell_size.clone(),
|
||||||
|
self.styled_underlines,
|
||||||
);
|
);
|
||||||
let mut tabs_to_close = vec![];
|
let mut tabs_to_close = vec![];
|
||||||
for (tab_index, tab) in &mut self.tabs {
|
for (tab_index, tab) in &mut self.tabs {
|
||||||
|
|
@ -2067,6 +2071,7 @@ pub(crate) fn screen_thread_main(
|
||||||
config_options.copy_clipboard.unwrap_or_default(),
|
config_options.copy_clipboard.unwrap_or_default(),
|
||||||
config_options.copy_on_select.unwrap_or(true),
|
config_options.copy_on_select.unwrap_or(true),
|
||||||
);
|
);
|
||||||
|
let styled_underlines = config_options.styled_underlines.unwrap_or(true);
|
||||||
|
|
||||||
let thread_senders = bus.senders.clone();
|
let thread_senders = bus.senders.clone();
|
||||||
let mut screen = Screen::new(
|
let mut screen = Screen::new(
|
||||||
|
|
@ -2091,6 +2096,7 @@ pub(crate) fn screen_thread_main(
|
||||||
session_serialization,
|
session_serialization,
|
||||||
serialize_pane_viewport,
|
serialize_pane_viewport,
|
||||||
scrollback_lines_to_serialize,
|
scrollback_lines_to_serialize,
|
||||||
|
styled_underlines,
|
||||||
arrow_fonts,
|
arrow_fonts,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2049,7 +2049,7 @@ fn move_floating_pane_with_sixel_image() {
|
||||||
width: 8,
|
width: 8,
|
||||||
height: 21,
|
height: 21,
|
||||||
})));
|
})));
|
||||||
let mut output = Output::new(sixel_image_store.clone(), character_cell_size);
|
let mut output = Output::new(sixel_image_store.clone(), character_cell_size, true);
|
||||||
|
|
||||||
tab.toggle_floating_panes(Some(client_id), None).unwrap();
|
tab.toggle_floating_panes(Some(client_id), None).unwrap();
|
||||||
tab.new_pane(new_pane_id, None, None, None, Some(client_id))
|
tab.new_pane(new_pane_id, None, None, None, Some(client_id))
|
||||||
|
|
@ -2087,7 +2087,7 @@ fn floating_pane_above_sixel_image() {
|
||||||
width: 8,
|
width: 8,
|
||||||
height: 21,
|
height: 21,
|
||||||
})));
|
})));
|
||||||
let mut output = Output::new(sixel_image_store.clone(), character_cell_size);
|
let mut output = Output::new(sixel_image_store.clone(), character_cell_size, true);
|
||||||
|
|
||||||
tab.toggle_floating_panes(Some(client_id), None).unwrap();
|
tab.toggle_floating_panes(Some(client_id), None).unwrap();
|
||||||
tab.new_pane(new_pane_id, None, None, None, Some(client_id))
|
tab.new_pane(new_pane_id, None, None, None, Some(client_id))
|
||||||
|
|
|
||||||
|
|
@ -244,6 +244,7 @@ fn create_new_screen(size: Size) -> Screen {
|
||||||
let scrollback_lines_to_serialize = None;
|
let scrollback_lines_to_serialize = None;
|
||||||
|
|
||||||
let debug = false;
|
let debug = false;
|
||||||
|
let styled_underlines = true;
|
||||||
let arrow_fonts = true;
|
let arrow_fonts = true;
|
||||||
let screen = Screen::new(
|
let screen = Screen::new(
|
||||||
bus,
|
bus,
|
||||||
|
|
@ -260,6 +261,7 @@ fn create_new_screen(size: Size) -> Screen {
|
||||||
session_serialization,
|
session_serialization,
|
||||||
serialize_pane_viewport,
|
serialize_pane_viewport,
|
||||||
scrollback_lines_to_serialize,
|
scrollback_lines_to_serialize,
|
||||||
|
styled_underlines,
|
||||||
arrow_fonts,
|
arrow_fonts,
|
||||||
);
|
);
|
||||||
screen
|
screen
|
||||||
|
|
|
||||||
|
|
@ -343,3 +343,9 @@ plugins {
|
||||||
// The folder in which Zellij will look for themes
|
// The folder in which Zellij will look for themes
|
||||||
//
|
//
|
||||||
// theme_dir "/path/to/my/theme_dir"
|
// theme_dir "/path/to/my/theme_dir"
|
||||||
|
|
||||||
|
// Enable or disable the rendering of styled and colored underlines (undercurl).
|
||||||
|
// May need to be disabled for certain unsupported terminals
|
||||||
|
// Default: true
|
||||||
|
//
|
||||||
|
// styled_underlines false
|
||||||
|
|
|
||||||
|
|
@ -142,6 +142,11 @@ pub struct Options {
|
||||||
#[clap(long, value_parser)]
|
#[clap(long, value_parser)]
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub scrollback_lines_to_serialize: Option<usize>,
|
pub scrollback_lines_to_serialize: Option<usize>,
|
||||||
|
|
||||||
|
/// Whether to use ANSI styled underlines
|
||||||
|
#[clap(long, value_parser)]
|
||||||
|
#[serde(default)]
|
||||||
|
pub styled_underlines: Option<bool>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(ArgEnum, Deserialize, Serialize, Debug, Clone, Copy, PartialEq)]
|
#[derive(ArgEnum, Deserialize, Serialize, Debug, Clone, Copy, PartialEq)]
|
||||||
|
|
@ -212,6 +217,7 @@ impl Options {
|
||||||
let scrollback_lines_to_serialize = other
|
let scrollback_lines_to_serialize = other
|
||||||
.scrollback_lines_to_serialize
|
.scrollback_lines_to_serialize
|
||||||
.or(self.scrollback_lines_to_serialize);
|
.or(self.scrollback_lines_to_serialize);
|
||||||
|
let styled_underlines = other.styled_underlines.or(self.styled_underlines);
|
||||||
|
|
||||||
Options {
|
Options {
|
||||||
simplified_ui,
|
simplified_ui,
|
||||||
|
|
@ -237,6 +243,7 @@ impl Options {
|
||||||
session_serialization,
|
session_serialization,
|
||||||
serialize_pane_viewport,
|
serialize_pane_viewport,
|
||||||
scrollback_lines_to_serialize,
|
scrollback_lines_to_serialize,
|
||||||
|
styled_underlines,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -287,6 +294,7 @@ impl Options {
|
||||||
let scrollback_lines_to_serialize = other
|
let scrollback_lines_to_serialize = other
|
||||||
.scrollback_lines_to_serialize
|
.scrollback_lines_to_serialize
|
||||||
.or_else(|| self.scrollback_lines_to_serialize.clone());
|
.or_else(|| self.scrollback_lines_to_serialize.clone());
|
||||||
|
let styled_underlines = other.styled_underlines.or(self.styled_underlines);
|
||||||
|
|
||||||
Options {
|
Options {
|
||||||
simplified_ui,
|
simplified_ui,
|
||||||
|
|
@ -312,6 +320,7 @@ impl Options {
|
||||||
session_serialization,
|
session_serialization,
|
||||||
serialize_pane_viewport,
|
serialize_pane_viewport,
|
||||||
scrollback_lines_to_serialize,
|
scrollback_lines_to_serialize,
|
||||||
|
styled_underlines,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -374,6 +383,7 @@ impl From<CliOptions> for Options {
|
||||||
session_serialization: opts.session_serialization,
|
session_serialization: opts.session_serialization,
|
||||||
serialize_pane_viewport: opts.serialize_pane_viewport,
|
serialize_pane_viewport: opts.serialize_pane_viewport,
|
||||||
scrollback_lines_to_serialize: opts.scrollback_lines_to_serialize,
|
scrollback_lines_to_serialize: opts.scrollback_lines_to_serialize,
|
||||||
|
styled_underlines: opts.styled_underlines,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1438,6 +1438,9 @@ impl Options {
|
||||||
let scrollback_lines_to_serialize =
|
let scrollback_lines_to_serialize =
|
||||||
kdl_property_first_arg_as_i64_or_error!(kdl_options, "scrollback_lines_to_serialize")
|
kdl_property_first_arg_as_i64_or_error!(kdl_options, "scrollback_lines_to_serialize")
|
||||||
.map(|(v, _)| v as usize);
|
.map(|(v, _)| v as usize);
|
||||||
|
let styled_underlines =
|
||||||
|
kdl_property_first_arg_as_bool_or_error!(kdl_options, "styled_underlines")
|
||||||
|
.map(|(v, _)| v);
|
||||||
Ok(Options {
|
Ok(Options {
|
||||||
simplified_ui,
|
simplified_ui,
|
||||||
theme,
|
theme,
|
||||||
|
|
@ -1462,6 +1465,7 @@ impl Options {
|
||||||
session_serialization,
|
session_serialization,
|
||||||
serialize_pane_viewport,
|
serialize_pane_viewport,
|
||||||
scrollback_lines_to_serialize,
|
scrollback_lines_to_serialize,
|
||||||
|
styled_underlines,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
---
|
---
|
||||||
source: zellij-utils/src/setup.rs
|
source: zellij-utils/src/setup.rs
|
||||||
assertion_line: 686
|
|
||||||
expression: "format!(\"{:#?}\", options)"
|
expression: "format!(\"{:#?}\", options)"
|
||||||
---
|
---
|
||||||
Options {
|
Options {
|
||||||
|
|
@ -29,4 +28,5 @@ Options {
|
||||||
session_serialization: None,
|
session_serialization: None,
|
||||||
serialize_pane_viewport: None,
|
serialize_pane_viewport: None,
|
||||||
scrollback_lines_to_serialize: None,
|
scrollback_lines_to_serialize: None,
|
||||||
|
styled_underlines: None,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
---
|
---
|
||||||
source: zellij-utils/src/setup.rs
|
source: zellij-utils/src/setup.rs
|
||||||
assertion_line: 714
|
|
||||||
expression: "format!(\"{:#?}\", options)"
|
expression: "format!(\"{:#?}\", options)"
|
||||||
---
|
---
|
||||||
Options {
|
Options {
|
||||||
|
|
@ -29,4 +28,5 @@ Options {
|
||||||
session_serialization: None,
|
session_serialization: None,
|
||||||
serialize_pane_viewport: None,
|
serialize_pane_viewport: None,
|
||||||
scrollback_lines_to_serialize: None,
|
scrollback_lines_to_serialize: None,
|
||||||
|
styled_underlines: None,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
---
|
---
|
||||||
source: zellij-utils/src/setup.rs
|
source: zellij-utils/src/setup.rs
|
||||||
assertion_line: 673
|
|
||||||
expression: "format!(\"{:#?}\", options)"
|
expression: "format!(\"{:#?}\", options)"
|
||||||
---
|
---
|
||||||
Options {
|
Options {
|
||||||
|
|
@ -27,4 +26,5 @@ Options {
|
||||||
session_serialization: None,
|
session_serialization: None,
|
||||||
serialize_pane_viewport: None,
|
serialize_pane_viewport: None,
|
||||||
scrollback_lines_to_serialize: None,
|
scrollback_lines_to_serialize: None,
|
||||||
|
styled_underlines: None,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
---
|
---
|
||||||
source: zellij-utils/src/setup.rs
|
source: zellij-utils/src/setup.rs
|
||||||
assertion_line: 671
|
|
||||||
expression: "format!(\"{:#?}\", config)"
|
expression: "format!(\"{:#?}\", config)"
|
||||||
---
|
---
|
||||||
Config {
|
Config {
|
||||||
|
|
@ -3592,6 +3591,7 @@ Config {
|
||||||
session_serialization: None,
|
session_serialization: None,
|
||||||
serialize_pane_viewport: None,
|
serialize_pane_viewport: None,
|
||||||
scrollback_lines_to_serialize: None,
|
scrollback_lines_to_serialize: None,
|
||||||
|
styled_underlines: None,
|
||||||
},
|
},
|
||||||
themes: {},
|
themes: {},
|
||||||
plugins: {
|
plugins: {
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
---
|
---
|
||||||
source: zellij-utils/src/setup.rs
|
source: zellij-utils/src/setup.rs
|
||||||
assertion_line: 729
|
|
||||||
expression: "format!(\"{:#?}\", config)"
|
expression: "format!(\"{:#?}\", config)"
|
||||||
---
|
---
|
||||||
Config {
|
Config {
|
||||||
|
|
@ -3592,6 +3591,7 @@ Config {
|
||||||
session_serialization: None,
|
session_serialization: None,
|
||||||
serialize_pane_viewport: None,
|
serialize_pane_viewport: None,
|
||||||
scrollback_lines_to_serialize: None,
|
scrollback_lines_to_serialize: None,
|
||||||
|
styled_underlines: None,
|
||||||
},
|
},
|
||||||
themes: {},
|
themes: {},
|
||||||
plugins: {
|
plugins: {
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
---
|
---
|
||||||
source: zellij-utils/src/setup.rs
|
source: zellij-utils/src/setup.rs
|
||||||
assertion_line: 785
|
|
||||||
expression: "format!(\"{:#?}\", config)"
|
expression: "format!(\"{:#?}\", config)"
|
||||||
---
|
---
|
||||||
Config {
|
Config {
|
||||||
|
|
@ -85,6 +84,7 @@ Config {
|
||||||
session_serialization: None,
|
session_serialization: None,
|
||||||
serialize_pane_viewport: None,
|
serialize_pane_viewport: None,
|
||||||
scrollback_lines_to_serialize: None,
|
scrollback_lines_to_serialize: None,
|
||||||
|
styled_underlines: None,
|
||||||
},
|
},
|
||||||
themes: {},
|
themes: {},
|
||||||
plugins: {
|
plugins: {
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
---
|
---
|
||||||
source: zellij-utils/src/setup.rs
|
source: zellij-utils/src/setup.rs
|
||||||
assertion_line: 696
|
|
||||||
expression: "format!(\"{:#?}\", options)"
|
expression: "format!(\"{:#?}\", options)"
|
||||||
---
|
---
|
||||||
Options {
|
Options {
|
||||||
|
|
@ -29,4 +28,5 @@ Options {
|
||||||
session_serialization: None,
|
session_serialization: None,
|
||||||
serialize_pane_viewport: None,
|
serialize_pane_viewport: None,
|
||||||
scrollback_lines_to_serialize: None,
|
scrollback_lines_to_serialize: None,
|
||||||
|
styled_underlines: None,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
---
|
---
|
||||||
source: zellij-utils/src/setup.rs
|
source: zellij-utils/src/setup.rs
|
||||||
assertion_line: 757
|
|
||||||
expression: "format!(\"{:#?}\", config)"
|
expression: "format!(\"{:#?}\", config)"
|
||||||
---
|
---
|
||||||
Config {
|
Config {
|
||||||
|
|
@ -3592,6 +3591,7 @@ Config {
|
||||||
session_serialization: None,
|
session_serialization: None,
|
||||||
serialize_pane_viewport: None,
|
serialize_pane_viewport: None,
|
||||||
scrollback_lines_to_serialize: None,
|
scrollback_lines_to_serialize: None,
|
||||||
|
styled_underlines: None,
|
||||||
},
|
},
|
||||||
themes: {},
|
themes: {},
|
||||||
plugins: {
|
plugins: {
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
---
|
---
|
||||||
source: zellij-utils/src/setup.rs
|
source: zellij-utils/src/setup.rs
|
||||||
assertion_line: 771
|
|
||||||
expression: "format!(\"{:#?}\", config)"
|
expression: "format!(\"{:#?}\", config)"
|
||||||
---
|
---
|
||||||
Config {
|
Config {
|
||||||
|
|
@ -3592,6 +3591,7 @@ Config {
|
||||||
session_serialization: None,
|
session_serialization: None,
|
||||||
serialize_pane_viewport: None,
|
serialize_pane_viewport: None,
|
||||||
scrollback_lines_to_serialize: None,
|
scrollback_lines_to_serialize: None,
|
||||||
|
styled_underlines: None,
|
||||||
},
|
},
|
||||||
themes: {
|
themes: {
|
||||||
"other-theme-from-config": Theme {
|
"other-theme-from-config": Theme {
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
---
|
---
|
||||||
source: zellij-utils/src/setup.rs
|
source: zellij-utils/src/setup.rs
|
||||||
assertion_line: 743
|
|
||||||
expression: "format!(\"{:#?}\", config)"
|
expression: "format!(\"{:#?}\", config)"
|
||||||
---
|
---
|
||||||
Config {
|
Config {
|
||||||
|
|
@ -3592,6 +3591,7 @@ Config {
|
||||||
session_serialization: None,
|
session_serialization: None,
|
||||||
serialize_pane_viewport: None,
|
serialize_pane_viewport: None,
|
||||||
scrollback_lines_to_serialize: None,
|
scrollback_lines_to_serialize: None,
|
||||||
|
styled_underlines: None,
|
||||||
},
|
},
|
||||||
themes: {},
|
themes: {},
|
||||||
plugins: {
|
plugins: {
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue