From cb1bcaa9baf60a455caab4bd6694f2d5d1af8cef Mon Sep 17 00:00:00 2001 From: altsem Date: Thu, 13 Nov 2025 18:34:29 +0100 Subject: [PATCH 1/3] wip: wrap words Here's a sketch on how far i got into trying to make (at least) word-wrap a thing. Since it's just word-wrap, it won't deal with very long single- word lines still. (Should the layout module be able to split spans?). This still doesn't solve the problem if the Editor (src/screen/mod.rs) not being aware of wrapped lines. I think either we'll have to: - Feed data from the Layout back to the Editor. How tall is each Item? Is there a way to scroll the screen without recomputing from the top? Maybe keep an Item as anchor: "We are 7 lines down from Item 42" - Do wrapping separately from (or with the help of?) the Layout module. Keep each Item = 1 line. When the internal model is constructed, split long lines into multiple items. Make them behave as if 1 when selecting etc. --- src/screen/mod.rs | 23 ++--------------------- src/ui.rs | 20 ++++++++++++++++++-- src/ui/layout/mod.rs | 16 ++++++---------- 3 files changed, 26 insertions(+), 33 deletions(-) diff --git a/src/screen/mod.rs b/src/screen/mod.rs index d7ded5a596..38008d5ed3 100644 --- a/src/screen/mod.rs +++ b/src/screen/mod.rs @@ -475,27 +475,8 @@ pub(crate) fn layout_screen<'a>( let span_width = span.content.graphemes(true).count(); - if line_end + span_width >= size.width as usize { - // Truncate the span and insert an ellipsis to indicate overflow - let overflow = line_end + span_width - size.width as usize; - line_end = size.width as usize; - ui::layout_span( - layout, - ( - span.content - .graphemes(true) - .take(span_width.saturating_sub(overflow + 1)) - .collect::() - .into(), - style, - ), - ); - layout_span(layout, ("…".into(), bg)); - } else { - // Insert the span as normal - line_end += span_width; - ui::layout_span(layout, (span.content, style)); - } + line_end += span_width; + ui::layout_span(layout, (span.content, style)); }); // Add ellipsis indicator for collapsed sections diff --git a/src/ui.rs b/src/ui.rs index 6a8a5426b0..51ee8fc66a 100644 --- a/src/ui.rs +++ b/src/ui.rs @@ -127,8 +127,24 @@ pub(crate) fn layout_line<'a>(layout: &mut UiTree<'a>, line: Line<'a>) { } pub(crate) fn layout_span<'a>(layout: &mut UiTree<'a>, span: (Cow<'a, str>, Style)) { - let width = span.0.graphemes(true).count() as u16; - layout.leaf_with_size(span, [width, 1]); + match span.0 { + Cow::Borrowed(s) => { + for word in s.split_word_bounds() { + layout.leaf_with_size( + (Cow::Borrowed(word), span.1), + [word.graphemes(true).count() as u16, 1], + ); + } + } + Cow::Owned(s) => { + for word in s.split_word_bounds() { + layout.leaf_with_size( + (Cow::Owned(word.into()), span.1), + [word.graphemes(true).count() as u16, 1], + ); + } + } + } } pub(crate) fn repeat_chars(layout: &mut UiTree, count: usize, chars: &'static str, style: Style) { diff --git a/src/ui/layout/mod.rs b/src/ui/layout/mod.rs index 4438a9372f..2a08a4f128 100644 --- a/src/ui/layout/mod.rs +++ b/src/ui/layout/mod.rs @@ -242,17 +242,13 @@ impl LayoutTree { child_data.pos = Some(start + cursor); } else { // Child doesn't fit where cursor currently is - // TODO Uncomment to include wrapping (tests below) - // // Try wrapping to next line/column first - // let next_line = size * dir.axis().flip(); + let next_line = size * dir.axis().flip(); - // if (next_line + child_data.size).fits(avail_size) { - // // Fits completely on next line - // cursor = next_line; - // child_data.pos = Some(start + cursor); - // } else - - if (cursor + Vec2(1, 1)).fits(avail_size) { + if (next_line + child_data.size).fits(avail_size) { + // Fits completely on next line + cursor = next_line; + child_data.pos = Some(start + cursor); + } else if (cursor + Vec2(1, 1)).fits(avail_size) { // Can't wrap, but we can fit at least one cell where the cursor currently is child_data.pos = Some(start + cursor); child_data.size = child_data.size.min(avail_size.saturating_sub(cursor)); From 1977ed1e905179338ac5db8f377b98bc722c49a5 Mon Sep 17 00:00:00 2001 From: altsem Date: Thu, 16 Jul 2026 17:19:18 +0200 Subject: [PATCH 2/3] refactor: compute item heights with a shared `layout_item` fn --- src/items.rs | 3 +- src/screen/mod.rs | 106 ++++++++++++++++++++++++++++------------------ src/ui.rs | 2 +- src/ui/menu.rs | 2 +- 4 files changed, 67 insertions(+), 46 deletions(-) diff --git a/src/items.rs b/src/items.rs index c4dbbc82bf..09dc16428b 100644 --- a/src/items.rs +++ b/src/items.rs @@ -18,7 +18,6 @@ use std::hash::Hash; use std::hash::Hasher; use std::iter; use std::rc::Rc; -use std::sync::Arc; pub type ItemId = u64; @@ -32,7 +31,7 @@ pub(crate) struct Item { } impl Item { - pub fn to_line(&'_ self, config: Arc) -> Line<'_> { + pub fn to_line(&'_ self, config: &Config) -> Line<'_> { match self.data.clone() { ItemData::Raw(content) => Line::raw(content), ItemData::AllUnstaged(count) => Line::from(vec![ diff --git a/src/screen/mod.rs b/src/screen/mod.rs index 38008d5ed3..d6fd79bbe7 100644 --- a/src/screen/mod.rs +++ b/src/screen/mod.rs @@ -1,8 +1,8 @@ use crate::config::StyleConfig; -use crate::ui::layout::OPTS; +use crate::ui::layout::{LayoutTree, OPTS}; use crate::ui::{UiTree, layout_span}; use crate::{item_data::ItemData, ui}; -use ratatui::{layout::Size, style::Style, text::Line}; +use ratatui::{layout::Size, style::Style}; use unicode_segmentation::UnicodeSegmentation; use crate::{Res, config::Config, items::hash}; @@ -36,6 +36,7 @@ pub(crate) struct Screen { refresh_items: Box Res>>, items: Vec, line_index: Vec, + item_heights: Vec, collapsed: HashSet, } @@ -61,6 +62,7 @@ impl Screen { refresh_items, items: vec![], line_index: vec![], + item_heights: vec![], collapsed, }; @@ -75,6 +77,7 @@ impl Screen { screen.collapsed.insert(item.id); }); screen.update_line_index(); + screen.update_item_heights(); screen.cursor = screen .find_first_hunk() @@ -203,12 +206,14 @@ impl Screen { } self.update_line_index(); + self.update_item_heights(); } pub(crate) fn update(&mut self) -> Res<()> { let nav_mode = self.selected_item_nav_mode(); self.items = (self.refresh_items)()?; self.update_line_index(); + self.update_item_heights(); self.update_cursor(nav_mode); Ok(()) } @@ -267,6 +272,21 @@ impl Screen { self.clamp_scroll(); } + fn update_item_heights(&mut self) { + let mut layout = LayoutTree::new(); + + (0..self.items.len()).for_each(|item_index| { + let view = LineView { + item_index, + highlighted: false, + }; + layout_item(&mut layout, self, false, view); + }); + + layout.compute([self.size.width, self.size.height]); + self.item_heights = layout.iter().map(|item| item.size[1]).collect(); + } + fn is_cursor_off_screen(&self) -> bool { !self.line_views(self.size).any(|line| line.highlighted) } @@ -410,7 +430,7 @@ impl Screen { self.nav_filter(target_line_i, NavMode::IncludeSubLines) } - fn line_views(&'_ self, area: Size) -> impl Iterator> { + fn line_views(&'_ self, area: Size) -> impl Iterator { let scan_start = self.scroll.min(self.cursor); let scan_end = (self.scroll + area.height as usize).min(self.line_index.len()); let scan_highlight_range = scan_start..(scan_end); @@ -425,11 +445,9 @@ impl Screen { } else if highlight_depth.is_some_and(|s| s >= item.depth) { *highlight_depth = None; }; - let display = item.to_line(Arc::clone(&self.config)); Some(LineView { item_index: *item_index, - display, highlighted: highlight_depth.is_some(), }) }) @@ -437,61 +455,65 @@ impl Screen { } } -struct LineView<'a> { +struct LineView { item_index: usize, - display: Line<'a>, highlighted: bool, } const SPACES: &str = " "; -pub(crate) fn layout_screen<'a>( - layout: &mut UiTree<'a>, - size: Size, +pub(crate) fn layout_screen<'a>(layout: &mut UiTree<'a>, screen: &'a Screen, hide_cursor: bool) { + layout.vertical(None, OPTS, |layout| { + for view in screen.line_views(screen.size) { + layout_item(layout, screen, hide_cursor, view); + } + }); +} + +fn layout_item<'a>( + layout: &mut LayoutTree<(Cow<'a, str>, Style)>, screen: &'a Screen, hide_cursor: bool, + line: LineView, ) { let style = &screen.config.style; - layout.vertical(None, OPTS, |layout| { - for line in screen.line_views(size) { - layout.horizontal(None, OPTS, |layout| { - let is_line_sel = screen.line_index[screen.cursor] == line.item_index; - let area_sel = area_selection_highlight(style, &line); - let line_sel = line_selection_highlight(style, &line, is_line_sel); - let bg = area_sel.patch(line_sel); - - let mut line_end = 1; - let gutter_char = if !hide_cursor && line.highlighted { - gutter_char(style, is_line_sel, bg) - } else { - (" ".into(), Style::new()) - }; + layout.horizontal(None, OPTS, |layout| { + let is_line_sel = screen.line_index[screen.cursor] == line.item_index; + let area_sel = area_selection_highlight(style, &line); + let line_sel = line_selection_highlight(style, &line, is_line_sel); + let bg = area_sel.patch(line_sel); - layout_span(layout, gutter_char); + let mut line_end = 1; + let gutter_char = if !hide_cursor && line.highlighted { + gutter_char(style, is_line_sel, bg) + } else { + (" ".into(), Style::new()) + }; - line.display.spans.into_iter().for_each(|span| { - let style = bg.patch(line.display.style).patch(span.style); + layout_span(layout, gutter_char); - let span_width = span.content.graphemes(true).count(); + let display = screen.items[line.item_index].to_line(&screen.config); + display.spans.into_iter().for_each(|span| { + let style = bg.patch(display.style).patch(span.style); - line_end += span_width; - ui::layout_span(layout, (span.content, style)); - }); + let span_width = span.content.graphemes(true).count(); - // Add ellipsis indicator for collapsed sections - let item = &screen.items[line.item_index]; - if screen.is_collapsed(item) { - line_end += 1; - layout_span(layout, ("…".into(), bg)); - } + line_end += span_width; + ui::layout_span(layout, (span.content, style)); + }); - // Style the rest of the line's empty space - let style = if is_line_sel { line_sel } else { area_sel }; - let padding_width = (size.width as usize).saturating_sub(line_end); - ui::repeat_chars(layout, padding_width, SPACES, style); - }); + // Add ellipsis indicator for collapsed sections + let item = &screen.items[line.item_index]; + if screen.is_collapsed(item) { + line_end += 1; + layout_span(layout, ("…".into(), bg)); } + + // Style the rest of the line's empty space + let style = if is_line_sel { line_sel } else { area_sel }; + let padding_width = (screen.size.width as usize).saturating_sub(line_end); + ui::repeat_chars(layout, padding_width, SPACES, style); }); } diff --git a/src/ui.rs b/src/ui.rs index 51ee8fc66a..f9d7cea09f 100644 --- a/src/ui.rs +++ b/src/ui.rs @@ -27,7 +27,7 @@ pub(crate) fn ui(frame: &mut Frame, state: &mut State) { layout.vertical(None, OPTS, |layout| { layout.vertical(None, OPTS.grow(), |layout| { let hide_cursor = state.picker.is_some(); - screen::layout_screen(layout, size, state.screens.last().unwrap(), hide_cursor); + screen::layout_screen(layout, state.screens.last().unwrap(), hide_cursor); }); layout.vertical(None, OPTS, |layout| { diff --git a/src/ui/menu.rs b/src/ui/menu.rs index c3a7f2d091..130482f142 100644 --- a/src/ui/menu.rs +++ b/src/ui/menu.rs @@ -52,7 +52,7 @@ pub(crate) fn layout_menu<'a>(layout: &mut UiTree<'a>, state: &'a State, width: }) .partition(|(op, _binds)| matches!(op, Op::OpenMenu(_))); - let line = item.to_line(Arc::clone(&config)); + let line = item.to_line(&config); let separator_style = Style::from(&style.separator); layout.vertical(None, OPTS, |layout| { From 911a92d21edc6fda2c982d02d49a53c67c9c836d Mon Sep 17 00:00:00 2001 From: altsem Date: Thu, 16 Jul 2026 20:19:17 +0200 Subject: [PATCH 3/3] update screen to be able to navigate with multi-line items --- src/items.rs | 2 +- src/screen/mod.rs | 163 ++++++++++++++++++++++++++++++---------------- 2 files changed, 107 insertions(+), 58 deletions(-) diff --git a/src/items.rs b/src/items.rs index 09dc16428b..15860260b7 100644 --- a/src/items.rs +++ b/src/items.rs @@ -117,7 +117,7 @@ impl Item { line_i, } => { let hunk_highlights = - highlight::highlight_hunk(self.id, &config, &Rc::clone(&diff), file_i, hunk_i); + highlight::highlight_hunk(self.id, config, &Rc::clone(&diff), file_i, hunk_i); let hunk_content = &diff.hunk_content(file_i, hunk_i); let hunk_line = &hunk_content[line_range.clone()]; diff --git a/src/screen/mod.rs b/src/screen/mod.rs index d6fd79bbe7..7a8aa794d9 100644 --- a/src/screen/mod.rs +++ b/src/screen/mod.rs @@ -2,6 +2,7 @@ use crate::config::StyleConfig; use crate::ui::layout::{LayoutTree, OPTS}; use crate::ui::{UiTree, layout_span}; use crate::{item_data::ItemData, ui}; +use itertools::Itertools; use ratatui::{layout::Size, style::Style}; use unicode_segmentation::UnicodeSegmentation; @@ -9,7 +10,7 @@ use crate::{Res, config::Config, items::hash}; use super::Item; use std::borrow::Cow; -use std::collections::HashSet; +use std::collections::{BTreeMap, HashSet}; use std::sync::Arc; pub(crate) mod blame; @@ -35,7 +36,11 @@ pub(crate) struct Screen { config: Arc, refresh_items: Box Res>>, items: Vec, + /// May contain jumps where items span multiple lines line_index: Vec, + /// Maps lines to items, items spanning multiple lines appear as duplicates + unique_line_index: BTreeMap, + /// May contain jumps where items span multiple lines item_heights: Vec, collapsed: HashSet, } @@ -62,6 +67,7 @@ impl Screen { refresh_items, items: vec![], line_index: vec![], + unique_line_index: BTreeMap::new(), item_heights: vec![], collapsed, }; @@ -76,26 +82,19 @@ impl Screen { .for_each(|item| { screen.collapsed.insert(item.id); }); - screen.update_line_index(); screen.update_item_heights(); + screen.update_line_index(); screen.cursor = screen .find_first_hunk() - .or_else(|| screen.find_first_selectable()) + .or_else(|| screen.find_item_line(|item| !item.unselectable)) .unwrap_or(0); Ok(screen) } fn find_first_hunk(&mut self) -> Option { - (0..self.line_index.len()).find(|&line_i| { - !self.at_line(line_i).unselectable - && matches!(self.at_line(line_i).data, ItemData::Hunk { .. }) - }) - } - - fn find_first_selectable(&mut self) -> Option { - (0..self.line_index.len()).find(|&line_i| !self.at_line(line_i).unselectable) + self.find_item_line(|item| !item.unselectable && matches!(item.data, ItemData::Hunk { .. })) } fn at_line(&self, line_i: usize) -> &Item { @@ -109,7 +108,7 @@ impl Screen { } fn scroll_fit_start(&mut self) { - if self.items.is_empty() { + if self.line_index.is_empty() { return; } @@ -120,15 +119,18 @@ impl Screen { } fn scroll_fit_end(&mut self) { - if self.items.is_empty() { + if self.line_index.is_empty() { return; } let depth = self.get_selected_item().depth; + let current_item_i = self.line_index[self.cursor]; let last = BOTTOM_CONTEXT_LINES + (self.cursor..self.line_index.len()) - .take_while(|&line_i| line_i == self.cursor || depth < self.at_line(line_i).depth) + .take_while(|&line_i| { + self.line_index[line_i] == current_item_i || depth < self.at_line(line_i).depth + }) .last() .unwrap(); @@ -141,6 +143,7 @@ impl Screen { pub(crate) fn find_next(&mut self, nav_mode: NavMode) -> usize { (self.cursor..self.line_index.len()) .skip(1) + .filter(|line_i| self.unique_line_index.contains_key(line_i)) .find(|&line_i| self.nav_filter(line_i, nav_mode)) .unwrap_or(self.cursor) } @@ -169,8 +172,8 @@ impl Screen { fn find_previous(&mut self, nav_mode: NavMode) -> usize { (0..self.cursor) - .rev() - .find(|&line_i| self.nav_filter(line_i, nav_mode)) + .filter(|line_i| self.unique_line_index.contains_key(line_i)) + .rfind(|&line_i| self.nav_filter(line_i, nav_mode)) .unwrap_or(self.cursor) } @@ -205,15 +208,15 @@ impl Screen { } } - self.update_line_index(); self.update_item_heights(); + self.update_line_index(); } pub(crate) fn update(&mut self) -> Res<()> { let nav_mode = self.selected_item_nav_mode(); self.items = (self.refresh_items)()?; - self.update_line_index(); self.update_item_heights(); + self.update_line_index(); self.update_cursor(nav_mode); Ok(()) } @@ -249,6 +252,11 @@ impl Screen { } fn update_line_index(&mut self) { + assert_eq!( + self.items.len(), + self.item_heights.len(), + "items and item_heights should have equal len" + ); self.line_index = self .items .iter() @@ -267,28 +275,41 @@ impl Screen { Some(Some((i, next))) }) .flatten() - .map(|(i, _item)| i) + .flat_map(|(i, _item)| [i].repeat(self.item_heights[i] as usize)) + .collect(); + + self.unique_line_index = self + .line_index + .iter() + .cloned() + .enumerate() + .unique_by(|&(_, v)| v) .collect(); self.clamp_scroll(); } fn update_item_heights(&mut self) { - let mut layout = LayoutTree::new(); - - (0..self.items.len()).for_each(|item_index| { - let view = LineView { - item_index, - highlighted: false, - }; - layout_item(&mut layout, self, false, view); - }); - - layout.compute([self.size.width, self.size.height]); - self.item_heights = layout.iter().map(|item| item.size[1]).collect(); + self.item_heights = (0..self.items.len()) + .map(|item_index| { + let mut layout = LayoutTree::new(); + let view = ItemView { + item_index, + highlighted: false, + }; + layout_item(&mut layout, self, false, view); + layout.compute([self.size.width, self.size.height]); + + layout + .iter() + .map(|item| item.pos[1] + item.size[1]) + .max() + .unwrap_or(0) + }) + .collect() } fn is_cursor_off_screen(&self) -> bool { - !self.line_views(self.size).any(|line| line.highlighted) + !self.item_views(self.size).any(|item| item.highlighted) } fn move_cursor_to_screen_center(&mut self) { @@ -331,6 +352,18 @@ impl Screen { } } + // FIXME If you click a continued line, you then selected the continued line, and not the + // first one. e.g. + // + // Line 1 Item 1 + // Line 2 Item 2 + // Line 3 Item 2 <-- cursor + // + // Move up now and the cursor will move to the same item + // + // Line 1 Item 1 + // Line 2 Item 2 <-- cursor + // Line 3 Item 2 pub(crate) fn move_cursor_to_screen_line(&mut self, screen_line: usize) { if self.line_index.is_empty() { return; @@ -357,29 +390,25 @@ impl Screen { } pub(crate) fn move_cursor_to_top(&mut self) { - if self.line_index.is_empty() { + if self.unique_line_index.is_empty() { return; } - if let Some(first) = self.find_first_selectable() { + if let Some(first) = self.find_item_line(|item| !item.unselectable) { self.cursor = first; self.scroll = 0; } } pub(crate) fn move_cursor_to_bottom(&mut self) { - if self.line_index.is_empty() { + if self.unique_line_index.is_empty() { return; } - if let Some(last) = self.find_last_selectable() { + if let Some(last) = self.rfind_item_line(|item| !item.unselectable) { self.cursor = last; self.scroll_fit_end(); } } - fn find_last_selectable(&self) -> Option { - (0..self.line_index.len()).rfind(|&line_i| !self.at_line(line_i).unselectable) - } - pub(crate) fn is_collapsed(&self, item: &Item) -> bool { self.collapsed.contains(&item.id) } @@ -389,9 +418,9 @@ impl Screen { } pub(crate) fn select_matching bool>(&mut self, predicate: F) -> bool { - if let Some(line_i) = (0..self.line_index.len()).find(|&line_i| { - !self.at_line(line_i).unselectable && predicate(&self.at_line(line_i).data) - }) { + if let Some(line_i) = + self.find_item_line(|item| !item.unselectable && predicate(&item.data)) + { self.cursor = line_i; let half_screen = self.size.height as usize / 2; if self.cursor >= half_screen { @@ -406,9 +435,9 @@ impl Screen { } pub(crate) fn select_last_matching bool>(&mut self, predicate: F) -> bool { - if let Some(line_i) = (0..self.line_index.len()).rev().find(|&line_i| { - !self.at_line(line_i).unselectable && predicate(&self.at_line(line_i).data) - }) { + if let Some(line_i) = + self.rfind_item_line(|item| !item.unselectable && predicate(&item.data)) + { self.cursor = line_i; let half_screen = self.size.height as usize / 2; if self.cursor >= half_screen { @@ -422,6 +451,20 @@ impl Screen { } } + fn find_item_line bool>(&self, predicate: P) -> Option { + self.unique_line_index + .iter() + .find(|&(_, &item_i)| predicate(&self.items[item_i])) + .map(|(&line_i, _)| line_i) + } + + fn rfind_item_line bool>(&self, predicate: P) -> Option { + self.unique_line_index + .iter() + .rfind(|&(_, &item_i)| predicate(&self.items[item_i])) + .map(|(&line_i, _)| line_i) + } + pub(crate) fn is_valid_screen_line(&self, screen_line: usize) -> bool { let target_line_i = screen_line + self.scroll; if self.line_index.is_empty() || target_line_i >= self.line_index.len() { @@ -430,7 +473,7 @@ impl Screen { self.nav_filter(target_line_i, NavMode::IncludeSubLines) } - fn line_views(&'_ self, area: Size) -> impl Iterator { + fn item_views(&'_ self, area: Size) -> impl Iterator { let scan_start = self.scroll.min(self.cursor); let scan_end = (self.scroll + area.height as usize).min(self.line_index.len()); let scan_highlight_range = scan_start..(scan_end); @@ -446,16 +489,17 @@ impl Screen { *highlight_depth = None; }; - Some(LineView { + Some(ItemView { item_index: *item_index, highlighted: highlight_depth.is_some(), }) }) .skip(context_lines) + .unique_by(|item| item.item_index) } } -struct LineView { +struct ItemView { item_index: usize, highlighted: bool, } @@ -464,7 +508,7 @@ const SPACES: &str = " pub(crate) fn layout_screen<'a>(layout: &mut UiTree<'a>, screen: &'a Screen, hide_cursor: bool) { layout.vertical(None, OPTS, |layout| { - for view in screen.line_views(screen.size) { + for view in screen.item_views(screen.size) { layout_item(layout, screen, hide_cursor, view); } }); @@ -474,12 +518,17 @@ fn layout_item<'a>( layout: &mut LayoutTree<(Cow<'a, str>, Style)>, screen: &'a Screen, hide_cursor: bool, - line: LineView, + line: ItemView, ) { let style = &screen.config.style; layout.horizontal(None, OPTS, |layout| { - let is_line_sel = screen.line_index[screen.cursor] == line.item_index; + let is_line_sel = screen + .line_index + .get(screen.cursor) + .map(|&i| i == line.item_index) + .unwrap_or(false); + let area_sel = area_selection_highlight(style, &line); let line_sel = line_selection_highlight(style, &line, is_line_sel); let bg = area_sel.patch(line_sel); @@ -493,9 +542,9 @@ fn layout_item<'a>( layout_span(layout, gutter_char); - let display = screen.items[line.item_index].to_line(&screen.config); - display.spans.into_iter().for_each(|span| { - let style = bg.patch(display.style).patch(span.style); + let item_line = screen.items[line.item_index].to_line(&screen.config); + item_line.spans.into_iter().for_each(|span| { + let style = bg.patch(item_line.style).patch(span.style); let span_width = span.content.graphemes(true).count(); @@ -531,7 +580,7 @@ fn gutter_char<'a>(style: &'a StyleConfig, is_line_sel: bool, bg: Style) -> (Cow } } -fn line_selection_highlight(style: &StyleConfig, line: &LineView, selected_line: bool) -> Style { +fn line_selection_highlight(style: &StyleConfig, line: &ItemView, selected_line: bool) -> Style { if line.highlighted && selected_line { Style::from(&style.selection_line) } else { @@ -539,7 +588,7 @@ fn line_selection_highlight(style: &StyleConfig, line: &LineView, selected_line: } } -fn area_selection_highlight(style: &StyleConfig, line: &LineView) -> Style { +fn area_selection_highlight(style: &StyleConfig, line: &ItemView) -> Style { if line.highlighted { Style::from(&style.selection_area) } else {