use std::path::Path;use std::rc::Rc; use collections::HashMap;use file_icons::FileIcons;use git::status::FileStatus;use gpui::{ Action, AnyElement, App, AvailableSpace, Bounds, ClickEvent, ClipboardItem, ContentMask, CursorStyle, DefiniteLength, Entity, Focusable as _, Hitbox, HitboxBehavior, Hsla, IntoElement, Length, Modifiers, MouseButton, MouseDownEvent, MouseMoveEvent, ParentElement, Pixels, ShapedLine, SharedString, Styled, TextAlign, Window, div, fill, linear_color_stop, linear_gradient, point, px, size,};use language::language_settings::ShowWhitespaceSetting;use multi_buffer::{Anchor, ExcerptBoundaryInfo};use project::Entry;use settings::{RelativeLineNumbers, Settings};use smallvec::SmallVec;use sum_tree::Bias;use text::BufferId;use theme::ActiveTheme;use ui::{ ButtonLike, ContextMenu, Indicator, KeyBinding, Tooltip, prelude::*, right_click_menu, text_for_keystroke,};use util::ResultExt;use workspace::{ItemHandle, ItemSettings, OpenInTerminal, OpenTerminal, RevealInProjectPanel}; use super::{ BlockLayout, EditorElement, EditorLayout, LineWithInvisibles, layout_line, render_breadcrumb_text,};use crate::{ BUFFER_HEADER_PADDING, DisplayRow, Editor, EditorSettings, EditorSnapshot, FILE_HEADER_HEIGHT, GutterDimensions, JumpData, MULTI_BUFFER_EXCERPT_HEADER_HEIGHT, OpenExcerpts, Point, RowExt, SelectionEffects, StickyHeaderExcerpt, ToPoint, ToggleFold, ToggleFoldAll, display_map::ToDisplayPoint, scroll::{Autoscroll, ScrollOffset, ScrollPixelOffset},}; pub(crate) struct StickyHeader { sticky_row: DisplayRow, pub(crate) start_point: Point, pub(crate) offset: ScrollOffset,} pub(super) struct StickyHeaders { pub(super) lines: Vec<StickyHeaderLine>, gutter_background: Hsla, content_background: Hsla, gutter_right_padding: Pixels,} pub(super) struct StickyHeaderLine { row: DisplayRow, pub(super) offset: Pixels, line: Rc<LineWithInvisibles>, line_number: Option<ShapedLine>, elements: SmallVec<[AnyElement; 1]>, available_text_width: Pixels, hitbox: Hitbox,} impl EditorElement { pub(crate) fn sticky_headers(editor: &Editor, snapshot: &EditorSnapshot) -> Vec<StickyHeader> { let scroll_top = snapshot.scroll_position().y; let mut end_rows = Vec::<DisplayRow>::new(); let mut rows = Vec::<StickyHeader>::new(); for item in editor.sticky_headers.iter().flatten() { let start_point = item .source_range_for_text .start .to_point(snapshot.buffer_snapshot()); let end_point = item.range.end.to_point(snapshot.buffer_snapshot()); let sticky_row = snapshot .display_snapshot .point_to_display_point(start_point, Bias::Left) .row(); if rows .last() .is_some_and(|last| last.sticky_row == sticky_row) { continue; } let end_row = snapshot .display_snapshot .point_to_display_point(end_point, Bias::Left) .row(); let max_sticky_row = end_row.previous_row(); if max_sticky_row <= sticky_row { continue; } while end_rows .last() .is_some_and(|&last_end| last_end <= sticky_row) { end_rows.pop(); } let depth = end_rows.len(); let adjusted_scroll_top = scroll_top + depth as f64; if sticky_row.as_f64() >= adjusted_scroll_top || end_row.as_f64() <= adjusted_scroll_top { continue; } let max_scroll_offset = max_sticky_row.as_f64() - scroll_top; let offset = (depth as f64).min(max_scroll_offset); end_rows.push(end_row); rows.push(StickyHeader { sticky_row, start_point, offset, }); } rows } pub(super) fn should_show_buffer_headers(&self) -> bool { self.split_side.is_none() } pub(super) fn layout_sticky_buffer_header( &self, StickyHeaderExcerpt { excerpt }: StickyHeaderExcerpt<'_>, scroll_position: gpui::Point<ScrollOffset>, line_height: Pixels, right_margin: Pixels, snapshot: &EditorSnapshot, hitbox: &Hitbox, selected_buffer_ids: &Vec<BufferId>, blocks: &[BlockLayout], latest_selection_anchors: &HashMap<BufferId, Anchor>, window: &mut Window, cx: &mut App, ) -> AnyElement { let jump_data = header_jump_data( snapshot, DisplayRow(scroll_position.y as u32), FILE_HEADER_HEIGHT + MULTI_BUFFER_EXCERPT_HEADER_HEIGHT, excerpt, latest_selection_anchors, ); let editor_bg_color = cx.theme().colors().editor_background; let selected = selected_buffer_ids.contains(&excerpt.buffer_id()); let available_width = hitbox.bounds.size.width - right_margin; let mut header = v_flex() .w_full() .relative() .child( div() .w(available_width) .h(FILE_HEADER_HEIGHT as f32 * line_height) .bg(linear_gradient( 0., linear_color_stop(editor_bg_color.opacity(0.), 0.), linear_color_stop(editor_bg_color, 0.6), )) .absolute() .top_0(), ) .child( render_buffer_header( &self.editor, excerpt, false, selected, true, jump_data, window, cx, ) .into_any_element(), ) .into_any_element(); let mut origin = hitbox.origin; // Move floating header up to avoid colliding with the next buffer header. for block in blocks.iter() { if !block.is_buffer_header { continue; } let Some(display_row) = block.row.filter(|row| row.0 > scroll_position.y as u32) else { continue; }; let max_row = display_row.0.saturating_sub(FILE_HEADER_HEIGHT); let offset = scroll_position.y - max_row as f64; if offset > 0.0 { origin.y -= Pixels::from(offset * ScrollPixelOffset::from(line_height)); } break; } let size = size( AvailableSpace::Definite(available_width), AvailableSpace::MinContent, ); header.prepaint_as_root(origin, size, window, cx); header } pub(super) fn layout_sticky_headers( &self, snapshot: &EditorSnapshot, editor_width: Pixels, is_row_soft_wrapped: impl Copy + Fn(usize) -> bool, line_height: Pixels, scroll_pixel_position: gpui::Point<ScrollPixelOffset>, content_origin: gpui::Point<Pixels>, gutter_dimensions: &GutterDimensions, gutter_hitbox: &Hitbox, text_hitbox: &Hitbox, relative_line_numbers: RelativeLineNumbers, relative_to: Option<DisplayRow>, window: &mut Window, cx: &mut App, ) -> Option<StickyHeaders> { let show_line_numbers = snapshot .show_line_numbers .unwrap_or_else(|| EditorSettings::get_global(cx).gutter.line_numbers); let rows = Self::sticky_headers(self.editor.read(cx), snapshot); let mut lines = Vec::<StickyHeaderLine>::new(); for StickyHeader { sticky_row, start_point, offset, } in rows.into_iter().rev() { let line = layout_line( sticky_row, snapshot, &self.style, editor_width, is_row_soft_wrapped, window, cx, ); let line_number = show_line_numbers.then(|| { let start_display_row = start_point.to_display_point(snapshot).row(); let relative_number = relative_to .filter(|_| relative_line_numbers != RelativeLineNumbers::Disabled) .map(|base| { snapshot.relative_line_delta( base, start_display_row, relative_line_numbers == RelativeLineNumbers::Wrapped, ) }); let number = relative_number .filter(|&delta| delta != 0) .map(|delta| delta.unsigned_abs() as u32) .unwrap_or(start_point.row + 1); let color = cx.theme().colors().editor_line_number; self.shape_line_number(SharedString::from(number.to_string()), color, window) }); lines.push(StickyHeaderLine::new( sticky_row, line_height * offset as f32, line, line_number, line_height, scroll_pixel_position, content_origin, gutter_hitbox, text_hitbox, window, cx, )); } lines.reverse(); if lines.is_empty() { return None; } Some(StickyHeaders { lines, gutter_background: cx.theme().colors().editor_gutter_background, content_background: self.style.background, gutter_right_padding: gutter_dimensions.right_padding, }) } pub(super) fn paint_sticky_headers( &mut self, layout: &mut EditorLayout, window: &mut Window, cx: &mut App, ) { let Some(mut sticky_headers) = layout.sticky_headers.take() else { return; }; let Some(last_line_offset) = sticky_headers.lines.last().map(|line| line.offset) else { layout.sticky_headers = Some(sticky_headers); return; }; let whitespace_setting = self .editor .read(cx) .buffer .read(cx) .language_settings(cx) .show_whitespaces; sticky_headers.paint(layout, whitespace_setting, window, cx); let sticky_header_hitboxes: Vec<Hitbox> = sticky_headers .lines .iter() .map(|line| line.hitbox.clone()) .collect(); let hovered_hitbox = sticky_header_hitboxes .iter() .find_map(|hitbox| hitbox.is_hovered(window).then_some(hitbox.id)); window.on_mouse_event(move |_: &MouseMoveEvent, phase, window, _cx| { if !phase.bubble() { return; } let current_hover = sticky_header_hitboxes .iter() .find_map(|hitbox| hitbox.is_hovered(window).then_some(hitbox.id)); if hovered_hitbox != current_hover { window.refresh(); } }); let position_map = layout.position_map.clone(); for (line_index, line) in sticky_headers.lines.iter().enumerate() { let editor = self.editor.clone(); let hitbox = line.hitbox.clone(); let row = line.row; let line_layout = line.line.clone(); let position_map = position_map.clone(); window.on_mouse_event(move |event: &MouseDownEvent, phase, window, cx| { if !phase.bubble() { return; } if event.button == MouseButton::Left && hitbox.is_hovered(window) { let point_for_position = position_map.point_for_position_on_line(event.position, row, &line_layout); editor.update(cx, |editor, cx| { let snapshot = editor.snapshot(window, cx); let anchor = snapshot .display_snapshot .display_point_to_anchor(point_for_position.nearest_valid, Bias::Left); editor.change_selections( SelectionEffects::scroll(Autoscroll::top_relative( line_index as ScrollOffset, )), window, cx, |selections| { selections.clear_disjoint(); selections.set_pending_anchor_range( anchor..anchor, crate::SelectMode::Character, ); }, ); cx.stop_propagation(); }); } }); } let text_bounds = layout.position_map.text_hitbox.bounds; let border_top = text_bounds.top() + last_line_offset + layout.position_map.line_height; let separator_height = px(1.); let border_bounds = window.pixel_snap_bounds(Bounds::from_corners( point(layout.gutter_hitbox.bounds.left(), border_top), point(text_bounds.right(), border_top + separator_height), )); window.paint_quad(fill(border_bounds, cx.theme().colors().border_variant)); layout.sticky_headers = Some(sticky_headers); }} impl StickyHeaders { fn paint( &mut self, layout: &mut EditorLayout, whitespace_setting: ShowWhitespaceSetting, window: &mut Window, cx: &mut App, ) { let line_height = layout.position_map.line_height; for line in self.lines.iter_mut().rev() { window.paint_layer( Bounds::new( layout.gutter_hitbox.origin + point(Pixels::ZERO, line.offset), size(line.hitbox.size.width, line_height), ), |window| { let gutter_bounds = Bounds::new( layout.gutter_hitbox.origin + point(Pixels::ZERO, line.offset), size(layout.gutter_hitbox.size.width, line_height), ); window.paint_quad(fill(gutter_bounds, self.gutter_background)); let text_bounds = Bounds::new( layout.position_map.text_hitbox.origin + point(Pixels::ZERO, line.offset), size(line.available_text_width, line_height), ); window.paint_quad(fill(text_bounds, self.content_background)); if line.hitbox.is_hovered(window) { let hover_overlay = cx.theme().colors().panel_overlay_hover; window.paint_quad(fill(gutter_bounds, hover_overlay)); window.paint_quad(fill(text_bounds, hover_overlay)); } line.paint( layout, self.gutter_right_padding, line.available_text_width, layout.content_origin, line_height, whitespace_setting, window, cx, ); }, ); window.set_cursor_style(CursorStyle::IBeam, &line.hitbox); } }} impl StickyHeaderLine { fn new( row: DisplayRow, offset: Pixels, mut line: LineWithInvisibles, line_number: Option<ShapedLine>, line_height: Pixels, scroll_pixel_position: gpui::Point<ScrollPixelOffset>, content_origin: gpui::Point<Pixels>, gutter_hitbox: &Hitbox, text_hitbox: &Hitbox, window: &mut Window, cx: &mut App, ) -> Self { let mut elements = SmallVec::<[AnyElement; 1]>::new(); line.prepaint_with_custom_offset( line_height, scroll_pixel_position, content_origin, offset, &mut elements, window, cx, ); let hitbox_bounds = Bounds::new( gutter_hitbox.origin + point(Pixels::ZERO, offset), size(text_hitbox.right() - gutter_hitbox.left(), line_height), ); let available_text_width = (hitbox_bounds.size.width - gutter_hitbox.size.width).max(Pixels::ZERO); Self { row, offset, line: Rc::new(line), line_number, elements, available_text_width, hitbox: window.insert_hitbox(hitbox_bounds, HitboxBehavior::BlockMouseExceptScroll), } }Lines 1–500 of 1055. Load more
use std::path::Path; use std::rc::Rc; use collections::HashMap; use file_icons::FileIcons; use git::status::FileStatus; use gpui::{ Action, AnyElement, App, AvailableSpace, Bounds, ClickEvent, ClipboardItem, ContentMask, CursorStyle, DefiniteLength, Entity, Focusable as _, Hitbox, HitboxBehavior, Hsla, IntoElement, Length, Modifiers, MouseButton, MouseDownEvent, MouseMoveEvent, ParentElement, Pixels, ShapedLine, SharedString, Styled, TextAlign, Window, div, fill, linear_color_stop, linear_gradient, point, px, size, }; use language::language_settings::ShowWhitespaceSetting; use multi_buffer::{Anchor, ExcerptBoundaryInfo}; use project::Entry; use settings::{RelativeLineNumbers, Settings}; use smallvec::SmallVec; use sum_tree::Bias; use text::BufferId; use theme::ActiveTheme; use ui::{ ButtonLike, ContextMenu, Indicator, KeyBinding, Tooltip, prelude::*, right_click_menu, text_for_keystroke, }; use util::ResultExt; use workspace::{ItemHandle, ItemSettings, OpenInTerminal, OpenTerminal, RevealInProjectPanel}; use super::{ BlockLayout, EditorElement, EditorLayout, LineWithInvisibles, layout_line, render_breadcrumb_text, }; use crate::{ BUFFER_HEADER_PADDING, DisplayRow, Editor, EditorSettings, EditorSnapshot, FILE_HEADER_HEIGHT, GutterDimensions, JumpData, MULTI_BUFFER_EXCERPT_HEADER_HEIGHT, OpenExcerpts, Point, RowExt, SelectionEffects, StickyHeaderExcerpt, ToPoint, ToggleFold, ToggleFoldAll, display_map::ToDisplayPoint, scroll::{Autoscroll, ScrollOffset, ScrollPixelOffset}, }; pub(crate) struct StickyHeader { sticky_row: DisplayRow, pub(crate) start_point: Point, pub(crate) offset: ScrollOffset, } pub(super) struct StickyHeaders { pub(super) lines: Vec<StickyHeaderLine>, gutter_background: Hsla, content_background: Hsla, gutter_right_padding: Pixels, } pub(super) struct StickyHeaderLine { row: DisplayRow, pub(super) offset: Pixels, line: Rc<LineWithInvisibles>, line_number: Option<ShapedLine>, elements: SmallVec<[AnyElement; 1]>, available_text_width: Pixels, hitbox: Hitbox, } impl EditorElement { pub(crate) fn sticky_headers(editor: &Editor, snapshot: &EditorSnapshot) -> Vec<StickyHeader> { let scroll_top = snapshot.scroll_position().y; let mut end_rows = Vec::<DisplayRow>::new(); let mut rows = Vec::<StickyHeader>::new(); for item in editor.sticky_headers.iter().flatten() { let start_point = item .source_range_for_text .start .to_point(snapshot.buffer_snapshot()); let end_point = item.range.end.to_point(snapshot.buffer_snapshot()); let sticky_row = snapshot .display_snapshot .point_to_display_point(start_point, Bias::Left) .row(); if rows .last() .is_some_and(|last| last.sticky_row == sticky_row) { continue; } let end_row = snapshot .display_snapshot .point_to_display_point(end_point, Bias::Left) .row(); let max_sticky_row = end_row.previous_row(); if max_sticky_row <= sticky_row { continue; } while end_rows .last() .is_some_and(|&last_end| last_end <= sticky_row) { end_rows.pop(); } let depth = end_rows.len(); let adjusted_scroll_top = scroll_top + depth as f64; if sticky_row.as_f64() >= adjusted_scroll_top || end_row.as_f64() <= adjusted_scroll_top { continue; } let max_scroll_offset = max_sticky_row.as_f64() - scroll_top; let offset = (depth as f64).min(max_scroll_offset); end_rows.push(end_row); rows.push(StickyHeader { sticky_row, start_point, offset, }); } rows } pub(super) fn should_show_buffer_headers(&self) -> bool { self.split_side.is_none() } pub(super) fn layout_sticky_buffer_header( &self, StickyHeaderExcerpt { excerpt }: StickyHeaderExcerpt<'_>, scroll_position: gpui::Point<ScrollOffset>, line_height: Pixels, right_margin: Pixels, snapshot: &EditorSnapshot, hitbox: &Hitbox, selected_buffer_ids: &Vec<BufferId>, blocks: &[BlockLayout], latest_selection_anchors: &HashMap<BufferId, Anchor>, window: &mut Window, cx: &mut App, ) -> AnyElement { let jump_data = header_jump_data( snapshot, DisplayRow(scroll_position.y as u32), FILE_HEADER_HEIGHT + MULTI_BUFFER_EXCERPT_HEADER_HEIGHT, excerpt, latest_selection_anchors, ); let editor_bg_color = cx.theme().colors().editor_background; let selected = selected_buffer_ids.contains(&excerpt.buffer_id()); let available_width = hitbox.bounds.size.width - right_margin; let mut header = v_flex() .w_full() .relative() .child( div() .w(available_width) .h(FILE_HEADER_HEIGHT as f32 * line_height) .bg(linear_gradient( 0., linear_color_stop(editor_bg_color.opacity(0.), 0.), linear_color_stop(editor_bg_color, 0.6), )) .absolute() .top_0(), ) .child( render_buffer_header( &self.editor, excerpt, false, selected, true, jump_data, window, cx, ) .into_any_element(), ) .into_any_element(); let mut origin = hitbox.origin; // Move floating header up to avoid colliding with the next buffer header. for block in blocks.iter() { if !block.is_buffer_header { continue; } let Some(display_row) = block.row.filter(|row| row.0 > scroll_position.y as u32) else { continue; }; let max_row = display_row.0.saturating_sub(FILE_HEADER_HEIGHT); let offset = scroll_position.y - max_row as f64; if offset > 0.0 { origin.y -= Pixels::from(offset * ScrollPixelOffset::from(line_height)); } break; } let size = size( AvailableSpace::Definite(available_width), AvailableSpace::MinContent, ); header.prepaint_as_root(origin, size, window, cx); header } pub(super) fn layout_sticky_headers( &self, snapshot: &EditorSnapshot, editor_width: Pixels, is_row_soft_wrapped: impl Copy + Fn(usize) -> bool, line_height: Pixels, scroll_pixel_position: gpui::Point<ScrollPixelOffset>, content_origin: gpui::Point<Pixels>, gutter_dimensions: &GutterDimensions, gutter_hitbox: &Hitbox, text_hitbox: &Hitbox, relative_line_numbers: RelativeLineNumbers, relative_to: Option<DisplayRow>, window: &mut Window, cx: &mut App, ) -> Option<StickyHeaders> { let show_line_numbers = snapshot .show_line_numbers .unwrap_or_else(|| EditorSettings::get_global(cx).gutter.line_numbers); let rows = Self::sticky_headers(self.editor.read(cx), snapshot); let mut lines = Vec::<StickyHeaderLine>::new(); for StickyHeader { sticky_row, start_point, offset, } in rows.into_iter().rev() { let line = layout_line( sticky_row, snapshot, &self.style, editor_width, is_row_soft_wrapped, window, cx, ); let line_number = show_line_numbers.then(|| { let start_display_row = start_point.to_display_point(snapshot).row(); let relative_number = relative_to .filter(|_| relative_line_numbers != RelativeLineNumbers::Disabled) .map(|base| { snapshot.relative_line_delta( base, start_display_row, relative_line_numbers == RelativeLineNumbers::Wrapped, ) }); let number = relative_number .filter(|&delta| delta != 0) .map(|delta| delta.unsigned_abs() as u32) .unwrap_or(start_point.row + 1); let color = cx.theme().colors().editor_line_number; self.shape_line_number(SharedString::from(number.to_string()), color, window) }); lines.push(StickyHeaderLine::new( sticky_row, line_height * offset as f32, line, line_number, line_height, scroll_pixel_position, content_origin, gutter_hitbox, text_hitbox, window, cx, )); } lines.reverse(); if lines.is_empty() { return None; } Some(StickyHeaders { lines, gutter_background: cx.theme().colors().editor_gutter_background, content_background: self.style.background, gutter_right_padding: gutter_dimensions.right_padding, }) } pub(super) fn paint_sticky_headers( &mut self, layout: &mut EditorLayout, window: &mut Window, cx: &mut App, ) { let Some(mut sticky_headers) = layout.sticky_headers.take() else { return; }; let Some(last_line_offset) = sticky_headers.lines.last().map(|line| line.offset) else { layout.sticky_headers = Some(sticky_headers); return; }; let whitespace_setting = self .editor .read(cx) .buffer .read(cx) .language_settings(cx) .show_whitespaces; sticky_headers.paint(layout, whitespace_setting, window, cx); let sticky_header_hitboxes: Vec<Hitbox> = sticky_headers .lines .iter() .map(|line| line.hitbox.clone()) .collect(); let hovered_hitbox = sticky_header_hitboxes .iter() .find_map(|hitbox| hitbox.is_hovered(window).then_some(hitbox.id)); window.on_mouse_event(move |_: &MouseMoveEvent, phase, window, _cx| { if !phase.bubble() { return; } let current_hover = sticky_header_hitboxes .iter() .find_map(|hitbox| hitbox.is_hovered(window).then_some(hitbox.id)); if hovered_hitbox != current_hover { window.refresh(); } }); let position_map = layout.position_map.clone(); for (line_index, line) in sticky_headers.lines.iter().enumerate() { let editor = self.editor.clone(); let hitbox = line.hitbox.clone(); let row = line.row; let line_layout = line.line.clone(); let position_map = position_map.clone(); window.on_mouse_event(move |event: &MouseDownEvent, phase, window, cx| { if !phase.bubble() { return; } if event.button == MouseButton::Left && hitbox.is_hovered(window) { let point_for_position = position_map.point_for_position_on_line(event.position, row, &line_layout); editor.update(cx, |editor, cx| { let snapshot = editor.snapshot(window, cx); let anchor = snapshot .display_snapshot .display_point_to_anchor(point_for_position.nearest_valid, Bias::Left); editor.change_selections( SelectionEffects::scroll(Autoscroll::top_relative( line_index as ScrollOffset, )), window, cx, |selections| { selections.clear_disjoint(); selections.set_pending_anchor_range( anchor..anchor, crate::SelectMode::Character, ); }, ); cx.stop_propagation(); }); } }); } let text_bounds = layout.position_map.text_hitbox.bounds; let border_top = text_bounds.top() + last_line_offset + layout.position_map.line_height; let separator_height = px(1.); let border_bounds = window.pixel_snap_bounds(Bounds::from_corners( point(layout.gutter_hitbox.bounds.left(), border_top), point(text_bounds.right(), border_top + separator_height), )); window.paint_quad(fill(border_bounds, cx.theme().colors().border_variant)); layout.sticky_headers = Some(sticky_headers); } } impl StickyHeaders { fn paint( &mut self, layout: &mut EditorLayout, whitespace_setting: ShowWhitespaceSetting, window: &mut Window, cx: &mut App, ) { let line_height = layout.position_map.line_height; for line in self.lines.iter_mut().rev() { window.paint_layer( Bounds::new( layout.gutter_hitbox.origin + point(Pixels::ZERO, line.offset), size(line.hitbox.size.width, line_height), ), |window| { let gutter_bounds = Bounds::new( layout.gutter_hitbox.origin + point(Pixels::ZERO, line.offset), size(layout.gutter_hitbox.size.width, line_height), ); window.paint_quad(fill(gutter_bounds, self.gutter_background)); let text_bounds = Bounds::new( layout.position_map.text_hitbox.origin + point(Pixels::ZERO, line.offset), size(line.available_text_width, line_height), ); window.paint_quad(fill(text_bounds, self.content_background)); if line.hitbox.is_hovered(window) { let hover_overlay = cx.theme().colors().panel_overlay_hover; window.paint_quad(fill(gutter_bounds, hover_overlay)); window.paint_quad(fill(text_bounds, hover_overlay)); } line.paint( layout, self.gutter_right_padding, line.available_text_width, layout.content_origin, line_height, whitespace_setting, window, cx, ); }, ); window.set_cursor_style(CursorStyle::IBeam, &line.hitbox); } } } impl StickyHeaderLine { fn new( row: DisplayRow, offset: Pixels, mut line: LineWithInvisibles, line_number: Option<ShapedLine>, line_height: Pixels, scroll_pixel_position: gpui::Point<ScrollPixelOffset>, content_origin: gpui::Point<Pixels>, gutter_hitbox: &Hitbox, text_hitbox: &Hitbox, window: &mut Window, cx: &mut App, ) -> Self { let mut elements = SmallVec::<[AnyElement; 1]>::new(); line.prepaint_with_custom_offset( line_height, scroll_pixel_position, content_origin, offset, &mut elements, window, cx, ); let hitbox_bounds = Bounds::new( gutter_hitbox.origin + point(Pixels::ZERO, offset), size(text_hitbox.right() - gutter_hitbox.left(), line_height), ); let available_text_width = (hitbox_bounds.size.width - gutter_hitbox.size.width).max(Pixels::ZERO); Self { row, offset, line: Rc::new(line), line_number, elements, available_text_width, hitbox: window.insert_hitbox(hitbox_bounds, HitboxBehavior::BlockMouseExceptScroll), } }