Skip to content
Draft
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
## [Unreleased]

### Added
- SGR conceal (attribute 8, reset 28): hidden text is painted in the background color
- add `--maximized` and `--fullscreen` flags to start the window in that mode

### Changed
Expand Down
3 changes: 3 additions & 0 deletions src/renderer/draw_fns.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ pub(super) static BLANK_CELL: Cell = Cell {
overline: false,
reverse: false,
blink: false,
conceal: false,
wide: false,
wide_cont: false,
url: None,
Expand Down Expand Up @@ -234,6 +235,8 @@ pub(super) fn resolve_cell_colors(
} else {
(cell.fg, cell.bg)
};
// SGR conceal (8): paint the glyph in the background color so it is hidden.
let fg = if cell.conceal { bg } else { fg };
let fg = if cell.dim {
Color::rgb(
(fg.r as f32 * 0.5) as u8,
Expand Down
27 changes: 26 additions & 1 deletion src/renderer/text_test.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
use super::{
PaneView, Renderer, blend, cell_url_hovered, color_u32, dim_color, get_cell, mode_style,
resolve_cell_colors,
};
use crate::InputMode;
use crate::config::Config;
use crate::config::tui_config::ConfigPanel;
use crate::dpi::Physical;
use crate::terminal::Grid;
use crate::terminal::grid::{Color, CursorShape, GridColors, ShellState};
use crate::terminal::grid::{Cell, Color, CursorShape, GridColors, ShellState};
use crate::theme::default_theme;

// ── Task 20 proxy tests — pure arithmetic, no rendering ──────────────────────
Expand Down Expand Up @@ -1481,3 +1482,27 @@ fn draw_pane_sixel_transparent_pixels_preserved() {
let pane = make_pane(&grid, &m);
do_draw(&mut r, &[pane], &InputMode::Insert);
}

#[test]
fn resolve_cell_colors_conceal_paints_fg_as_bg() {
let cell = Cell {
bg: Color::rgb(10, 20, 30),
conceal: true,
..Cell::default()
};
let b = Color::BLACK;
let th = default_theme();
// Concealed glyph is painted in the background color, so it is invisible.
let (bg32, fg) = resolve_cell_colors(
&cell,
false,
false,
false,
false,
CursorShape::Block,
b,
b,
&th,
);
assert_eq!(color_u32(fg), bg32);
}
13 changes: 13 additions & 0 deletions src/terminal/grid.rs
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ pub struct Cell {
pub overline: bool,
pub reverse: bool,
pub blink: bool,
pub conceal: bool,
/// True when this is the left half of a 2-column wide character.
pub wide: bool,
/// True when this is the right (placeholder) half of a wide character.
Expand All @@ -88,6 +89,7 @@ impl Default for Cell {
overline: false,
reverse: false,
blink: false,
conceal: false,
wide: false,
wide_cont: false,
url: None,
Expand All @@ -108,6 +110,7 @@ fn cell_with_colors(c: char, fg: Color, bg: Color) -> Cell {
overline: false,
reverse: false,
blink: false,
conceal: false,
wide: false,
wide_cont: false,
url: None,
Expand All @@ -127,6 +130,7 @@ struct SavedCursor {
overline: bool,
reverse: bool,
blink: bool,
conceal: bool,
}

struct SavedScreen {
Expand All @@ -148,6 +152,7 @@ struct SavedScreen {
overline: bool,
reverse: bool,
blink: bool,
conceal: bool,
scrollback: VecDeque<Vec<Cell>>,
scrollback_wrapped: VecDeque<bool>,
row_wrapped: Vec<bool>,
Expand All @@ -173,6 +178,7 @@ pub struct Grid {
pub overline: bool,
pub reverse: bool,
pub blink: bool,
pub conceal: bool,
// DECSC/DECRC saved cursor (ESC 7 / ESC 8)
decsc: Option<SavedCursor>,
// Scrollback: lines that have scrolled off the top (oldest first)
Expand Down Expand Up @@ -268,6 +274,7 @@ impl Grid {
overline: false,
reverse: false,
blink: false,
conceal: false,
decsc: None,
scrollback: VecDeque::new(),
scrollback_wrapped: VecDeque::new(),
Expand Down Expand Up @@ -326,6 +333,7 @@ impl Grid {
overline: self.overline,
reverse: self.reverse,
blink: self.blink,
conceal: self.conceal,
scrollback: std::mem::take(&mut self.scrollback),
scrollback_wrapped: std::mem::take(&mut self.scrollback_wrapped),
row_wrapped: std::mem::replace(&mut self.row_wrapped, vec![false; self.rows]),
Expand Down Expand Up @@ -375,6 +383,7 @@ impl Grid {
self.overline = saved.overline;
self.reverse = saved.reverse;
self.blink = saved.blink;
self.conceal = saved.conceal;
self.scrollback = saved.scrollback;
self.scrollback_wrapped = saved.scrollback_wrapped;
self.row_wrapped = saved.row_wrapped;
Expand Down Expand Up @@ -638,6 +647,7 @@ impl Grid {
overline: self.overline,
reverse: self.reverse,
blink: self.blink,
conceal: self.conceal,
wide,
url: self.current_url.clone(),
..cell_with_colors(c, self.fg, self.bg)
Expand Down Expand Up @@ -974,6 +984,7 @@ impl Grid {
self.overline = false;
self.reverse = false;
self.blink = false;
self.conceal = false;
}

pub fn save_cursor(&mut self) {
Expand All @@ -990,6 +1001,7 @@ impl Grid {
overline: self.overline,
reverse: self.reverse,
blink: self.blink,
conceal: self.conceal,
});
}

Expand All @@ -1007,6 +1019,7 @@ impl Grid {
self.overline = s.overline;
self.reverse = s.reverse;
self.blink = s.blink;
self.conceal = s.conceal;
}
}

Expand Down
30 changes: 12 additions & 18 deletions src/terminal/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -66,27 +66,19 @@ struct Performer<'a> {

impl Performer<'_> {
fn handle_dec_private_modes(&mut self, action: char, p0: u16) {
// Only DECSET (h) / DECRST (l) toggle modes; the `('h' | 'l', _)` patterns
// keep other actions out of these arms, so `on` is only used when relevant.
let on = action == 'h';
match (action, p0) {
('h', 1) => self.grid.application_cursor_keys = true,
('l', 1) => self.grid.application_cursor_keys = false,
('h', 7) => self.grid.autowrap = true,
('l', 7) => self.grid.autowrap = false,
('h', 25) => self.grid.cursor_visible = true,
('l', 25) => self.grid.cursor_visible = false,
('h', 1000) => self.grid.mouse_mode = 1000,
('l', 1000) => self.grid.mouse_mode = 0,
('h', 1002) => self.grid.mouse_mode = 1002,
('l', 1002) => self.grid.mouse_mode = 0,
('h', 1003) => self.grid.mouse_mode = 1003,
('l', 1003) => self.grid.mouse_mode = 0,
('h', 1004) => self.grid.focus_report = true,
('l', 1004) => self.grid.focus_report = false,
('h', 1006) => self.grid.mouse_sgr = true,
('l', 1006) => self.grid.mouse_sgr = false,
('h' | 'l', 1) => self.grid.application_cursor_keys = on,
('h' | 'l', 7) => self.grid.autowrap = on,
('h' | 'l', 25) => self.grid.cursor_visible = on,
('h' | 'l', 1000 | 1002 | 1003) => self.grid.mouse_mode = if on { p0 } else { 0 },
('h' | 'l', 1004) => self.grid.focus_report = on,
('h' | 'l', 1006) => self.grid.mouse_sgr = on,
('h' | 'l', 2004) => self.grid.bracketed_paste = on,
('h', 1049) => self.grid.enter_alternate_screen(),
('l', 1049) => self.grid.exit_alternate_screen(),
('h', 2004) => self.grid.bracketed_paste = true,
('l', 2004) => self.grid.bracketed_paste = false,
_ => {}
}
}
Expand Down Expand Up @@ -173,6 +165,7 @@ impl Performer<'_> {
4 => self.grid.underline = true,
5 => self.grid.blink = true,
7 => self.grid.reverse = true,
8 => self.grid.conceal = true,
9 => self.grid.strikethrough = true,
22 => {
self.grid.bold = false;
Expand All @@ -182,6 +175,7 @@ impl Performer<'_> {
24 => self.grid.underline = false,
25 => self.grid.blink = false,
27 => self.grid.reverse = false,
28 => self.grid.conceal = false,
29 => self.grid.strikethrough = false,
53 => self.grid.overline = true,
55 => self.grid.overline = false,
Expand Down
18 changes: 18 additions & 0 deletions src/terminal/parser_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,24 @@ fn sgr_reset_clears_attributes() {
assert!(!p.grid.blink);
}

#[test]
fn sgr_conceal_stamped_and_reset() {
let mut p = make_parser(10, 5);
p.process(b"\x1b[8mA\x1b[28mB");
assert!(p.grid.cell(0, 0).conceal);
assert!(!p.grid.cell(1, 0).conceal);
assert!(!p.grid.conceal);
}

#[test]
fn sgr_reset_clears_conceal() {
let mut p = make_parser(10, 5);
p.process(b"\x1b[8m");
assert!(p.grid.conceal);
p.process(b"\x1b[0m");
assert!(!p.grid.conceal);
}

#[test]
fn erase_in_line_to_end() {
let mut p = make_parser(10, 5);
Expand Down