Skip to content
Closed
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
119 changes: 119 additions & 0 deletions crates/jcode-tui/src/tui/ui.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,9 @@ use std::time::{Duration, Instant};
#[cfg(test)]
use unicode_width::UnicodeWidthStr;

const PLAIN_FONT_STYLE_ENV: &str = "JCODE_TUI_PLAIN_FONT_STYLE";
static PLAIN_FONT_STYLE_REQUESTED: OnceLock<bool> = OnceLock::new();

#[path = "ui_animations.rs"]
mod animations;
#[path = "ui_box.rs"]
Expand Down Expand Up @@ -1964,6 +1967,54 @@ pub fn draw(frame: &mut Frame, app: &dyn TuiState) {
Ok(()) => {}
Err(payload) => render_recovered_panic_frame(frame, &payload),
}
strip_font_variant_modifiers_if_requested(frame);
}
Comment thread
kupendrav marked this conversation as resolved.

fn plain_font_style_requested() -> bool {
*PLAIN_FONT_STYLE_REQUESTED.get_or_init(|| {
std::env::var_os(PLAIN_FONT_STYLE_ENV)
.and_then(|value| value.into_string().ok())
.as_deref()
.is_some_and(parse_plain_font_style_value)
})
}

fn parse_plain_font_style_value(value: &str) -> bool {
let value = value.trim();
value == "1"
|| value.eq_ignore_ascii_case("true")
|| value.eq_ignore_ascii_case("yes")
|| value.eq_ignore_ascii_case("on")
}

fn font_variant_modifiers() -> Modifier {
Modifier::BOLD | Modifier::DIM | Modifier::ITALIC | Modifier::REVERSED
}
Comment thread
kupendrav marked this conversation as resolved.

fn strip_font_variant_modifiers_if_requested(frame: &mut Frame) {
let requested = plain_font_style_requested();
let frame_area = frame.area();
let buf = frame.buffer_mut();
let area = frame_area.intersection(*buf.area());
strip_font_variant_modifiers_if(buf, area, requested);
}

fn strip_font_variant_modifiers_if(buf: &mut ratatui::buffer::Buffer, area: Rect, requested: bool) {
if requested {
strip_font_variant_modifiers(buf, area);
}
}
Comment thread
kupendrav marked this conversation as resolved.

fn strip_font_variant_modifiers(buf: &mut ratatui::buffer::Buffer, area: Rect) {
let modifiers = font_variant_modifiers();
for y in area.y..area.y.saturating_add(area.height) {
for x in area.x..area.x.saturating_add(area.width) {
let cell = &mut buf[(x, y)];
if cell.modifier.intersects(modifiers) {
cell.modifier.remove(modifiers);
}
}
}
}

fn draw_inner(frame: &mut Frame, app: &dyn TuiState) {
Expand Down Expand Up @@ -2797,6 +2848,74 @@ pub(crate) fn render_native_scrollbar(
frame.render_widget(Paragraph::new(lines), area);
}

#[cfg(test)]
mod font_style_tests {
use super::*;

fn styled_ascii_buffer() -> ratatui::buffer::Buffer {
let mut buffer = ratatui::buffer::Buffer::empty(Rect::new(0, 0, 32, 1));
let text = "browser continue Done Built";
for (x, ch) in text.chars().enumerate() {
let cell = &mut buffer[(x as u16, 0)];
cell.set_symbol(ch.encode_utf8(&mut [0; 4]));
cell.modifier
.insert(Modifier::BOLD | Modifier::DIM | Modifier::ITALIC | Modifier::REVERSED);
}
buffer
}

#[test]
fn parse_plain_font_style_value_accepts_truthy_values() {
for value in ["1", "true", "TRUE", "yes", "YES", "on", "ON", " true "] {
assert!(
parse_plain_font_style_value(value),
"{value:?} should enable plain font style"
);
}
}

#[test]
fn parse_plain_font_style_value_rejects_empty_and_falsey_values() {
for value in ["", "0", "false", "no", "off", "plain"] {
assert!(
!parse_plain_font_style_value(value),
"{value:?} should not enable plain font style"
);
}
}

#[test]
fn strip_font_variant_modifiers_if_is_noop_when_not_requested() {
let mut buffer = styled_ascii_buffer();

strip_font_variant_modifiers_if(&mut buffer, Rect::new(0, 0, 32, 1), false);

assert!(
buffer[(0, 0)].modifier.intersects(font_variant_modifiers()),
"font modifiers should remain when plain font style is disabled"
);
}

#[test]
fn strip_font_variant_modifiers_preserves_ascii_symbols() {
let mut buffer = styled_ascii_buffer();
let text = "browser continue Done Built";

strip_font_variant_modifiers(&mut buffer, Rect::new(0, 0, 32, 1));

let rendered: String = (0..text.len() as u16)
.map(|x| buffer[(x, 0)].symbol())
.collect();
assert_eq!(rendered, text);
for x in 0..text.len() as u16 {
assert!(
!buffer[(x, 0)].modifier.intersects(font_variant_modifiers()),
"cell {x} should not keep font-variant modifiers"
);
}
}
}

#[cfg(test)]
#[path = "ui_tests/mod.rs"]
mod tests;