Skip to content

Commit d39a823

Browse files
committed
fix hints
1 parent 42d14a5 commit d39a823

3 files changed

Lines changed: 242 additions & 14 deletions

File tree

frontends/rioterm/src/application.rs

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -505,7 +505,6 @@ impl ApplicationHandler<EventPayload> for Application<'_> {
505505
RioEventType::Rio(RioEvent::SelectionScrollTick) => {
506506
if let Some(route) = self.router.routes.get_mut(&window_id) {
507507
route.window.screen.selection_scroll_tick();
508-
route.window.screen.render();
509508
route.request_redraw();
510509
}
511510
}
@@ -1126,7 +1125,6 @@ impl ApplicationHandler<EventPayload> for Application<'_> {
11261125

11271126
if route.window.screen.renderer.scrollbar.is_dragging() {
11281127
route.window.screen.handle_scrollbar_release();
1129-
route.window.screen.render();
11301128
route.request_redraw();
11311129
return;
11321130
}
@@ -1218,7 +1216,6 @@ impl ApplicationHandler<EventPayload> for Application<'_> {
12181216
.assistant
12191217
.hover(mx, my, win_w, scale)
12201218
{
1221-
route.window.screen.render();
12221219
route.request_redraw();
12231220
}
12241221

@@ -1250,7 +1247,6 @@ impl ApplicationHandler<EventPayload> for Application<'_> {
12501247
.command_palette
12511248
.hover(mx, my, win_w, scale)
12521249
{
1253-
route.window.screen.render();
12541250
route.request_redraw();
12551251
}
12561252
route.window.winit_window.set_cursor(CursorIcon::Default);
@@ -1270,7 +1266,25 @@ impl ApplicationHandler<EventPayload> for Application<'_> {
12701266
.search
12711267
.hover(mx, my, win_w, scale)
12721268
{
1273-
route.window.screen.render();
1269+
// The search overlay overlaps terminal cells,
1270+
// so the panel behind it must re-render with
1271+
// the new hover highlight on top. `UIDamage`
1272+
// alone isn't enough — `renderer.run`'s inner
1273+
// `(ui_terminal_damage, pty_damage)` match
1274+
// falls through to `continue` when both are
1275+
// `None`, even if `pending_update.is_dirty()`.
1276+
// Force `TerminalDamage::Full` to keep the
1277+
// panel in the render set.
1278+
route
1279+
.window
1280+
.screen
1281+
.ctx_mut()
1282+
.current_mut()
1283+
.renderable_content
1284+
.pending_update
1285+
.set_terminal_damage(
1286+
rio_backend::event::TerminalDamage::Full,
1287+
);
12741288
route.request_redraw();
12751289
}
12761290
}

frontends/rioterm/src/grid_emit.rs

Lines changed: 176 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,8 @@
2424
2525
use rio_backend::config::colors::term::TermColors;
2626
use rio_backend::crosswords::grid::row::Row;
27-
use rio_backend::crosswords::pos::{Column, Line};
27+
use rio_backend::crosswords::pos::{Column, Line, Pos};
28+
use rio_backend::crosswords::search::Match;
2829
use rio_backend::crosswords::square::{ContentTag, Square};
2930
use rio_backend::crosswords::style::{StyleFlags, StyleSet};
3031
use rio_backend::selection::SelectionRange;
@@ -92,6 +93,127 @@ fn cell_in_row_sel(row_sel: Option<RowSelection>, col: u16) -> bool {
9293
}
9394
}
9495

96+
/// Search-hint category at a cell. Matches Ghostty's `HighlightTag`
97+
/// (`ghostty/src/renderer/generic.zig:240`) — we use the same two-way
98+
/// split so `search_focused_match_background` can override the regular
99+
/// match color on the currently-focused hit.
100+
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
101+
pub enum HintTag {
102+
Match,
103+
Focused,
104+
}
105+
106+
/// Per-row hint interval, closed on both ends. Several `RowHint`s may
107+
/// exist on one row (when the row contains multiple matches).
108+
#[derive(Clone, Copy, Debug)]
109+
pub struct RowHint {
110+
pub lo: u16,
111+
pub hi: u16,
112+
pub tag: HintTag,
113+
}
114+
115+
/// Compute the hint-match intervals (if any) for visible row `y`.
116+
/// Linear-selection semantics: a match can span multiple rows; first
117+
/// / last rows clip to the match's column bounds; interior rows cover
118+
/// the full width. Mirrors `row_selection_for`.
119+
///
120+
/// `focused_match` is pushed first so it wins `cell_in_row_hints`
121+
/// iteration order when it overlaps another match — same precedence
122+
/// as Ghostty (`generic.zig:1330-1353`: "The order below matters.
123+
/// Highlights added earlier will take priority").
124+
pub fn row_hints_for(
125+
hint_matches: Option<&[Match]>,
126+
focused_match: Option<&Match>,
127+
y: usize,
128+
cols: usize,
129+
display_offset: i32,
130+
out: &mut Vec<RowHint>,
131+
) {
132+
out.clear();
133+
if cols == 0 {
134+
return;
135+
}
136+
let Some(matches) = hint_matches else {
137+
return;
138+
};
139+
let line = Line((y as i32) - display_offset);
140+
let cols_max = cols.saturating_sub(1) as u16;
141+
142+
let to_row_hint = |m: &Match, tag: HintTag| -> Option<RowHint> {
143+
let start = *m.start();
144+
let end = *m.end();
145+
if line < start.row || line > end.row {
146+
return None;
147+
}
148+
let lo = if line == start.row {
149+
start.col.0 as u16
150+
} else {
151+
0
152+
};
153+
let hi = if line == end.row {
154+
end.col.0 as u16
155+
} else {
156+
cols_max
157+
};
158+
Some(RowHint {
159+
lo: lo.min(cols_max),
160+
hi: hi.min(cols_max),
161+
tag,
162+
})
163+
};
164+
165+
let is_same_match = |a: &Match, b: &Match| -> bool {
166+
let (a_start, a_end) = (*a.start(), *a.end());
167+
let (b_start, b_end) = (*b.start(), *b.end());
168+
pos_eq(a_start, b_start) && pos_eq(a_end, b_end)
169+
};
170+
171+
if let Some(fm) = focused_match {
172+
if let Some(rh) = to_row_hint(fm, HintTag::Focused) {
173+
out.push(rh);
174+
}
175+
}
176+
for m in matches {
177+
if let Some(fm) = focused_match {
178+
if is_same_match(m, fm) {
179+
continue;
180+
}
181+
}
182+
if let Some(rh) = to_row_hint(m, HintTag::Match) {
183+
out.push(rh);
184+
}
185+
}
186+
}
187+
188+
#[inline]
189+
fn pos_eq(a: Pos, b: Pos) -> bool {
190+
a.row == b.row && a.col == b.col
191+
}
192+
193+
#[inline]
194+
fn cell_in_row_hints(row_hints: &[RowHint], col: u16) -> Option<HintTag> {
195+
for rh in row_hints {
196+
if col >= rh.lo && col <= rh.hi {
197+
return Some(rh.tag);
198+
}
199+
}
200+
None
201+
}
202+
203+
/// Foreground for a hint-matched cell. Mirrors `cell_fg_selected` but
204+
/// uses the configured `search_match_foreground` /
205+
/// `search_focused_match_foreground` from
206+
/// `colors::Colors` (`rio-backend/src/config/colors/mod.rs:287,299`).
207+
#[inline]
208+
fn cell_fg_hinted(tag: HintTag, renderer: &Renderer) -> [u8; 4] {
209+
match tag {
210+
HintTag::Focused => {
211+
normalized_to_u8(renderer.named_colors.search_focused_match_foreground)
212+
}
213+
HintTag::Match => normalized_to_u8(renderer.named_colors.search_match_foreground),
214+
}
215+
}
216+
95217
use rio_backend::sugarloaf::font::FontLibrary;
96218
use rio_backend::sugarloaf::grid::{
97219
AtlasSlot, CellBg, CellText, GlyphKey, GridRenderer, RasterizedGlyph,
@@ -425,6 +547,7 @@ pub fn build_row_bg(
425547
renderer: &Renderer,
426548
term_colors: &TermColors,
427549
row_sel: Option<RowSelection>,
550+
row_hints: &[RowHint],
428551
bg_scratch: &mut Vec<CellBg>,
429552
) {
430553
bg_scratch.clear();
@@ -434,12 +557,33 @@ pub fn build_row_bg(
434557
} else {
435558
None
436559
};
560+
// Precompute hint bg colors if the row has any hints. Both variants
561+
// are cheap enough to compute unconditionally when the row is hit.
562+
let (match_bg, focused_bg) = if !row_hints.is_empty() {
563+
(
564+
Some(normalized_to_u8(renderer.named_colors.search_match_background)),
565+
Some(normalized_to_u8(
566+
renderer.named_colors.search_focused_match_background,
567+
)),
568+
)
569+
} else {
570+
(None, None)
571+
};
437572
for x in 0..cols {
438573
let sq = row[Column(x)];
439-
let rgba = if cell_in_row_sel(row_sel, x as u16) {
440-
// Selection bg wins over the cell's own bg, matching Ghostty
441-
// `generic.zig:2817` (`.selection` branch).
574+
let col = x as u16;
575+
let rgba = if cell_in_row_sel(row_sel, col) {
576+
// Selection bg wins over hint bg and the cell's own bg,
577+
// matching Ghostty `generic.zig:2775-2800` (selection check
578+
// runs before highlight check).
442579
sel_bg.unwrap_or_else(|| cell_bg(sq, style_set, renderer, term_colors))
580+
} else if let Some(tag) = cell_in_row_hints(row_hints, col) {
581+
match tag {
582+
HintTag::Focused => focused_bg
583+
.unwrap_or_else(|| cell_bg(sq, style_set, renderer, term_colors)),
584+
HintTag::Match => match_bg
585+
.unwrap_or_else(|| cell_bg(sq, style_set, renderer, term_colors)),
586+
}
443587
} else {
444588
cell_bg(sq, style_set, renderer, term_colors)
445589
};
@@ -802,6 +946,7 @@ pub fn build_row_fg(
802946
cell_w: f32,
803947
cell_h: f32,
804948
row_sel: Option<RowSelection>,
949+
row_hints: &[RowHint],
805950
font_library: &FontLibrary,
806951
fg_scratch: &mut Vec<CellText>,
807952
) {
@@ -828,6 +973,7 @@ pub fn build_row_fg(
828973
cell_h_u32,
829974
thickness,
830975
row_sel,
976+
row_hints,
831977
fg_scratch,
832978
);
833979

@@ -1025,15 +1171,26 @@ pub fn build_row_fg(
10251171
(run_start + cell_idx_in_run as usize).min(cols.saturating_sub(1));
10261172
let src_sq = row[Column(src_col)];
10271173
let is_sel = cell_in_row_sel(row_sel, src_col as u16);
1174+
let hint_tag = if is_sel {
1175+
None
1176+
} else {
1177+
cell_in_row_hints(row_hints, src_col as u16)
1178+
};
10281179
let (atlas, color) = if is_color {
1029-
// Colour glyphs (emoji) don't take the selection-fg swap —
1030-
// matches Ghostty's behaviour for bitmap/COLR atlas entries.
1180+
// Colour glyphs (emoji) don't take the selection-fg /
1181+
// hint-fg swap — matches Ghostty's behaviour for
1182+
// bitmap/COLR atlas entries.
10311183
(CellText::ATLAS_COLOR, [255, 255, 255, 255])
10321184
} else if is_sel {
10331185
(
10341186
CellText::ATLAS_GRAYSCALE,
10351187
cell_fg_selected(src_sq, style_set, renderer, term_colors),
10361188
)
1189+
} else if let Some(tag) = hint_tag {
1190+
// Hint-fg wins over the cell's own fg, matching
1191+
// Ghostty's `.search` / `.search_selected` branches at
1192+
// `generic.zig:2829-2833` (the fg picker mirrors bg).
1193+
(CellText::ATLAS_GRAYSCALE, cell_fg_hinted(tag, renderer))
10371194
} else {
10381195
(
10391196
CellText::ATLAS_GRAYSCALE,
@@ -1070,6 +1227,7 @@ pub fn build_row_fg(
10701227
cell_h_u32,
10711228
thickness,
10721229
row_sel,
1230+
row_hints,
10731231
fg_scratch,
10741232
);
10751233
}
@@ -1087,6 +1245,7 @@ fn emit_underlines(
10871245
cell_h: u32,
10881246
thickness: u32,
10891247
row_sel: Option<RowSelection>,
1248+
row_hints: &[RowHint],
10901249
fg_scratch: &mut Vec<CellText>,
10911250
) {
10921251
for x in 0..cols {
@@ -1102,12 +1261,17 @@ fn emit_underlines(
11021261
if slot.w == 0 || slot.h == 0 {
11031262
continue;
11041263
}
1105-
let color = if cell_in_row_sel(row_sel, x as u16) {
1264+
let col = x as u16;
1265+
let color = if cell_in_row_sel(row_sel, col) {
11061266
// Inside selection: underline follows the selection fg so
11071267
// it stays visible against the selection bg. SGR 58 is
11081268
// suppressed here — a theme's selection_foreground
11091269
// overrides per-cell decoration color.
11101270
cell_fg_selected(sq, style_set, renderer, term_colors)
1271+
} else if let Some(tag) = cell_in_row_hints(row_hints, col) {
1272+
// Same reasoning as selection: underline inside a hint
1273+
// should stay legible on the hint bg.
1274+
cell_fg_hinted(tag, renderer)
11111275
} else {
11121276
decoration_color(sq, &style, style_set, renderer, term_colors)
11131277
};
@@ -1137,6 +1301,7 @@ fn emit_strikethroughs(
11371301
cell_h: u32,
11381302
thickness: u32,
11391303
row_sel: Option<RowSelection>,
1304+
row_hints: &[RowHint],
11401305
fg_scratch: &mut Vec<CellText>,
11411306
) {
11421307
for x in 0..cols {
@@ -1157,10 +1322,13 @@ fn emit_strikethroughs(
11571322
if slot.w == 0 || slot.h == 0 {
11581323
continue;
11591324
}
1325+
let col = x as u16;
11601326
// Strikethrough always uses the cell fg (there's no SGR for
11611327
// a separate strike color, matching Ghostty).
1162-
let color = if cell_in_row_sel(row_sel, x as u16) {
1328+
let color = if cell_in_row_sel(row_sel, col) {
11631329
cell_fg_selected(sq, style_set, renderer, term_colors)
1330+
} else if let Some(tag) = cell_in_row_hints(row_hints, col) {
1331+
cell_fg_hinted(tag, renderer)
11641332
} else {
11651333
cell_fg(sq, style_set, renderer, term_colors)
11661334
};

0 commit comments

Comments
 (0)