//! Find References UI component for displaying LSP textDocument/references results.//!//! This module provides a hover card that shows all references to a symbol//! as a flat list with file info, line numbers, and syntax-highlighted code snippets. use std::collections::HashMap;use std::path::PathBuf; use lsp::ReferenceLocation;use pathfinder_geometry::vector::Vector2F;use string_offset::CharOffset;use warp_core::ui::appearance::Appearance;use warp_core::ui::icons::Icon as WarpIcon;use warp_core::ui::theme::color::internal_colors;use warp_editor::content::buffer::InitialBufferState;use warp_editor::render::element::VerticalExpansionBehavior;use warp_files::FileModel;use warpui::elements::{ Border, ChildAnchor, ChildView, ClippedScrollStateHandle, ClippedScrollable, ConstrainedBox, Container, CornerRadius, CrossAxisAlignment, Fill, Flex, Hoverable, MouseStateHandle, OffsetPositioning, ParentAnchor, ParentElement, ParentOffsetBounds, Radius, ScrollbarWidth, Shrinkable, Stack, Text,};use warpui::keymap::FixedBinding;use warpui::platform::Cursor;use warpui::prelude::Align;use warpui::ui_components::components::UiComponent;use warpui::{ AppContext, Element, Entity, SingletonEntity, TypedActionView, View, ViewContext, ViewHandle,}; use super::editor::view::{CodeEditorRenderOptions, CodeEditorView};use super::global_buffer_model::GlobalBufferModel;use crate::editor::InteractionState;use crate::search::result_renderer::ItemHighlightState; /// Maximum height for the find references card.pub const FIND_REFERENCES_CARD_MAX_HEIGHT: f32 = 300.; const HAS_REFERENCES: &str = "HasReferences"; pub fn init(app: &mut AppContext) { use warpui::keymap::macros::*; app.register_fixed_bindings([ FixedBinding::new( "escape", FindReferencesViewAction::Close, id!(FindReferencesView::ui_name()), ), FixedBinding::new( "enter", FindReferencesViewAction::SelectReference, id!(FindReferencesView::ui_name()) & id!(HAS_REFERENCES), ), FixedBinding::new( "numpadenter", FindReferencesViewAction::SelectReference, id!(FindReferencesView::ui_name()) & id!(HAS_REFERENCES), ), FixedBinding::new( "up", FindReferencesViewAction::ArrowUp, id!(FindReferencesView::ui_name()) & id!(HAS_REFERENCES), ), FixedBinding::new( "down", FindReferencesViewAction::ArrowDown, id!(FindReferencesView::ui_name()) & id!(HAS_REFERENCES), ), ]);} /// Actions that FindReferencesView can handle.#[derive(Clone, Debug)]pub enum FindReferencesViewAction { /// Navigate to the reference at the given index. GotoReference(usize), /// Close the references card. Close, /// Move selection up (arrow up). ArrowUp, /// Move selection down (arrow down). ArrowDown, /// Select the currently highlighted reference (enter). SelectReference,} /// Events emitted by FindReferencesView.pub enum FindReferencesViewEvent { /// User requested navigation to a reference. GotoReference(usize), /// User requested to close the card. CloseRequested,} /// View for displaying find references results with async line loading.pub struct FindReferencesView { /// Flat list of all references with their UI state. references: Vec<ReferenceEntryWithUi>, /// The offset where references were requested (used for positioning). request_offset: CharOffset, /// Scroll state for the card content. scroll_state: ClippedScrollStateHandle, back_mouse_state: MouseStateHandle, /// The index of the currently selected reference for keyboard navigation. selected_reference_index: usize,} /// A reference entry bundled with its UI state.pub struct ReferenceEntryWithUi { /// The reference data. pub entry: FlatReferenceEntry, /// Mouse state for the entry row hover. pub entry_mouse_state: MouseStateHandle, /// Mouse state for the file name tooltip. pub file_name_mouse_state: MouseStateHandle, /// Editor view for syntax highlighting. pub editor_view: ViewHandle<CodeEditorView>,} impl ReferenceEntryWithUi { /// Updates the line content and refreshes the editor view. /// Trims whitespace from the start of the line and adjusts the column accordingly. pub fn update_line_content(&mut self, line: &str, ctx: &mut ViewContext<FindReferencesView>) { let trimmed = line.trim_start(); let trim_offset = line.len() - trimmed.len(); self.entry.line_content = Some(trimmed.to_string()); self.entry.column = self.entry.column.saturating_sub(trim_offset); // Update the editor view with the loaded content let content = trimmed.to_string(); let file_path = self.entry.file_path.clone(); self.editor_view.update(ctx, |view, ctx| { view.set_language_with_local_path(&file_path, ctx); let state = InitialBufferState::plain_text(&content); view.reset(state, ctx); }); }} impl FindReferencesView { /// Creates a new FindReferencesView with async line loading. /// Lines are loaded asynchronously from GlobalBufferModel (fast) or disk (slow). /// /// Converts raw ReferenceLocation to FlatReferenceEntry and loads line content. pub fn new( raw_references: Vec<ReferenceLocation>, workspace_root: Option<PathBuf>, request_offset: CharOffset, ctx: &mut ViewContext<Self>, ) -> Self { // Convert ReferenceLocation to ReferenceEntryWithUi (without line content - will load async) let mut references: Vec<ReferenceEntryWithUi> = raw_references .into_iter() .map(|r| { let file_path = r.file_path.clone(); // Get just the file name (leaf) for display let file_name = file_path .file_name() .map(|n| n.to_string_lossy().to_string()) .unwrap_or_else(|| file_path.to_string_lossy().to_string()); // Guard against empty file names from malformed LSP URIs let file_name = if file_name.is_empty() { "[unknown]".to_string() } else { file_name }; // Get relative path from workspace root for tooltip let display_path = workspace_root .as_ref() .and_then(|root| file_path.strip_prefix(root).ok()) .map(|p| p.to_string_lossy().to_string()) .unwrap_or_else(|| file_path.to_string_lossy().to_string()); let line_number = r.range.start.line + 1; // Convert 0-based to 1-based let entry = FlatReferenceEntry { file_path, file_name, display_path, line_number, column: r.range.start.column, line_content: None, // Will be loaded async }; // Create editor view for this reference let editor_view = Self::create_editor_view_for_reference(&entry, ctx); ReferenceEntryWithUi { entry, entry_mouse_state: MouseStateHandle::default(), file_name_mouse_state: MouseStateHandle::default(), editor_view, } }) .collect(); // Batch references by file for efficient loading let mut file_to_refs: HashMap<PathBuf, Vec<usize>> = HashMap::new(); for (idx, reference) in references.iter().enumerate() { if reference.entry.line_content.is_none() { file_to_refs .entry(reference.entry.file_path.clone()) .or_default() .push(idx); } } // Try to load lines from GlobalBufferModel first (fast, in-memory) let global_buffer = GlobalBufferModel::handle(ctx); for (file_path, ref_indices) in file_to_refs.iter() { // Collect all line numbers we need from this file (0-based) let line_numbers: Vec<usize> = ref_indices .iter() .map(|&idx| references[idx].entry.line_number - 1) .collect(); if line_numbers.is_empty() { continue; } if let Some(lines) = global_buffer.update(ctx, |model, ctx| { model.get_lines_for_file(file_path, line_numbers.clone(), ctx) }) { // Build a map from line number to content for quick lookup let line_map: HashMap<usize, &String> = lines.iter().map(|(ln, content)| (*ln, content)).collect(); // Successfully loaded from buffer - update references for &ref_idx in ref_indices { let reference = &mut references[ref_idx]; let line_num = reference.entry.line_number - 1; // 0-based if let Some(line) = line_map.get(&line_num) { reference.update_line_content(line, ctx); } } } } // For any references still without line_content, spawn async file reads for (file_path, ref_indices) in file_to_refs { // Check if any refs for this file still need loading let needs_loading: Vec<usize> = ref_indices .iter() .copied() .filter(|&idx| references[idx].entry.line_content.is_none()) .collect(); if needs_loading.is_empty() { continue; } // Collect line numbers for this file (0-based) let line_numbers: Vec<usize> = needs_loading .iter() .map(|&idx| references[idx].entry.line_number - 1) .collect(); let file_path_clone = file_path.clone(); ctx.spawn( async move { FileModel::read_lines_async(&file_path_clone, line_numbers).await }, move |me, result, ctx| { if let Ok(lines) = result { // Build a map from line number to content for quick lookup let line_map: HashMap<usize, &String> = lines.iter().map(|(ln, content)| (*ln, content)).collect(); for &ref_idx in &needs_loading { if ref_idx >= me.references.len() { continue; } let reference = &mut me.references[ref_idx]; let line_num = reference.entry.line_number - 1; // 0-based if let Some(line) = line_map.get(&line_num) { reference.update_line_content(line, ctx); } } ctx.notify(); // Trigger re-render } }, ); } Self { references, request_offset, scroll_state: ClippedScrollStateHandle::default(), back_mouse_state: MouseStateHandle::default(), selected_reference_index: 0, } } /// Returns the request offset for positioning the card. pub fn request_offset(&self) -> CharOffset { self.request_offset } /// Gets a reference by index. pub fn get_reference(&self, index: usize) -> Option<&FlatReferenceEntry> { self.references.get(index).map(|r| &r.entry) } /// Creates a read-only code editor view for a single reference line. fn create_editor_view_for_reference( reference: &FlatReferenceEntry, ctx: &mut ViewContext<Self>, ) -> ViewHandle<CodeEditorView> { let view = ctx.add_typed_action_view(|ctx| { let mut editor_view = CodeEditorView::new( None, None, CodeEditorRenderOptions::new(VerticalExpansionBehavior::InfiniteHeight), ctx, ) .with_can_show_diff_ui(false) .with_show_line_numbers(false) .with_horizontal_scrollbar_appearance( warpui::elements::new_scrollable::ScrollableAppearance::new( warpui::elements::ScrollbarWidth::None, false, ), ); editor_view.set_vertical_scrollbar_appearance( warpui::elements::new_scrollable::ScrollableAppearance::new( warpui::elements::ScrollbarWidth::None, false, ), ); editor_view }); // Initialize the editor with the reference line content (or empty if still loading) let content = reference.line_content.clone().unwrap_or_default(); let file_path = reference.file_path.clone(); view.update(ctx, |view, ctx| { view.set_show_current_line_highlights(false, ctx); view.set_interaction_state(InteractionState::Disabled, ctx); // Set up syntax highlighting based on file extension view.set_language_with_local_path(&file_path, ctx); // Reset with the reference line content let state = InitialBufferState::plain_text(&content); view.reset(state, ctx); }); view }} impl Entity for FindReferencesView { type Event = FindReferencesViewEvent;} impl TypedActionView for FindReferencesView { type Action = FindReferencesViewAction; fn handle_action(&mut self, action: &Self::Action, ctx: &mut ViewContext<Self>) { match action { FindReferencesViewAction::GotoReference(index) => { ctx.emit(FindReferencesViewEvent::GotoReference(*index)); } FindReferencesViewAction::Close => { ctx.emit(FindReferencesViewEvent::CloseRequested); } FindReferencesViewAction::ArrowUp => { if !self.references.is_empty() { self.selected_reference_index = (self.selected_reference_index + self.references.len() - 1) % self.references.len(); ctx.notify(); } } FindReferencesViewAction::ArrowDown => { if !self.references.is_empty() { self.selected_reference_index = (self.selected_reference_index + 1) % self.references.len(); ctx.notify(); } } FindReferencesViewAction::SelectReference => { if self.selected_reference_index < self.references.len() { ctx.emit(FindReferencesViewEvent::GotoReference( self.selected_reference_index, )); } } } }} impl View for FindReferencesView { fn ui_name() -> &'static str { "FindReferencesView" } fn keymap_context(&self, _app: &AppContext) -> warpui::keymap::Context { let mut context = Self::default_keymap_context(); if !self.references.is_empty() { context.set.insert(HAS_REFERENCES); } context } fn render(&self, app: &AppContext) -> Box<dyn Element> { if self.references.is_empty() { return warpui::elements::Empty::new().finish(); } let appearance = Appearance::handle(app).as_ref(app); let theme = appearance.theme(); // Header with back arrow and "Showing X references" let header = render_header(self.back_mouse_state.clone(), self.references.len(), app); // Content: flat list of reference entries let mut content_column = Flex::column().with_cross_axis_alignment(CrossAxisAlignment::Stretch); for (index, reference) in self.references.iter().enumerate() { let is_selected = index == self.selected_reference_index; let entry = render_reference_entry( &reference.entry, reference.entry_mouse_state.clone(), reference.file_name_mouse_state.clone(), &reference.editor_view, index, is_selected, app, ); content_column.add_child(entry); } // Make content scrollable let scrollable_content = ClippedScrollable::vertical( self.scroll_state.clone(), content_column.finish(), ScrollbarWidth::None, theme.disabled_ui_text_color().into(), theme.active_ui_text_color().into(), Fill::None, ) .finish(); // Combine header and content let card_content = Flex::column() .with_cross_axis_alignment(CrossAxisAlignment::Stretch) .with_child(header) .with_child(Shrinkable::new(1., scrollable_content).finish()) .finish(); ConstrainedBox::new( Container::new(card_content) .with_background_color(internal_colors::neutral_1(theme)) .with_corner_radius(CornerRadius::with_all(Radius::Pixels(8.))) .with_border(Border::all(1.).with_border_fill(internal_colors::neutral_4(theme))) .finish(), ) .with_max_height(FIND_REFERENCES_CARD_MAX_HEIGHT) .finish() }} /// A single reference entry with file information for flat list display.#[derive(Debug, Clone)]pub struct FlatReferenceEntry { /// The absolute file path. pub file_path: PathBuf, /// The display file name (just the file name, not full path). pub file_name: String, /// The relative path from workspace root (for tooltip display). pub display_path: String, /// 1-based line number where the reference appears. pub line_number: usize, /// 0-based column number where the reference starts (adjusted for trimmed whitespace). pub column: usize, /// The content of the line containing the reference (trimmed). /// None indicates the line is still loading from disk. pub line_content: Option<String>,} /// Renders the card header with back arrow and "Showing X references" text.fn render_header( back_mouse_state: MouseStateHandle, total_refs: usize, app: &AppContext,) -> Box<dyn Element> { let appearance = Appearance::handle(app).as_ref(app); let theme = appearance.theme(); // "Showing X references" title let title_text = if total_refs == 1 { "Showing 1 reference".to_string() } else {Lines 1–500 of 696. Load more
//! Find References UI component for displaying LSP textDocument/references results. //! //! This module provides a hover card that shows all references to a symbol //! as a flat list with file info, line numbers, and syntax-highlighted code snippets. use std::collections::HashMap; use std::path::PathBuf; use lsp::ReferenceLocation; use pathfinder_geometry::vector::Vector2F; use string_offset::CharOffset; use warp_core::ui::appearance::Appearance; use warp_core::ui::icons::Icon as WarpIcon; use warp_core::ui::theme::color::internal_colors; use warp_editor::content::buffer::InitialBufferState; use warp_editor::render::element::VerticalExpansionBehavior; use warp_files::FileModel; use warpui::elements::{ Border, ChildAnchor, ChildView, ClippedScrollStateHandle, ClippedScrollable, ConstrainedBox, Container, CornerRadius, CrossAxisAlignment, Fill, Flex, Hoverable, MouseStateHandle, OffsetPositioning, ParentAnchor, ParentElement, ParentOffsetBounds, Radius, ScrollbarWidth, Shrinkable, Stack, Text, }; use warpui::keymap::FixedBinding; use warpui::platform::Cursor; use warpui::prelude::Align; use warpui::ui_components::components::UiComponent; use warpui::{ AppContext, Element, Entity, SingletonEntity, TypedActionView, View, ViewContext, ViewHandle, }; use super::editor::view::{CodeEditorRenderOptions, CodeEditorView}; use super::global_buffer_model::GlobalBufferModel; use crate::editor::InteractionState; use crate::search::result_renderer::ItemHighlightState; /// Maximum height for the find references card. pub const FIND_REFERENCES_CARD_MAX_HEIGHT: f32 = 300.; const HAS_REFERENCES: &str = "HasReferences"; pub fn init(app: &mut AppContext) { use warpui::keymap::macros::*; app.register_fixed_bindings([ FixedBinding::new( "escape", FindReferencesViewAction::Close, id!(FindReferencesView::ui_name()), ), FixedBinding::new( "enter", FindReferencesViewAction::SelectReference, id!(FindReferencesView::ui_name()) & id!(HAS_REFERENCES), ), FixedBinding::new( "numpadenter", FindReferencesViewAction::SelectReference, id!(FindReferencesView::ui_name()) & id!(HAS_REFERENCES), ), FixedBinding::new( "up", FindReferencesViewAction::ArrowUp, id!(FindReferencesView::ui_name()) & id!(HAS_REFERENCES), ), FixedBinding::new( "down", FindReferencesViewAction::ArrowDown, id!(FindReferencesView::ui_name()) & id!(HAS_REFERENCES), ), ]); } /// Actions that FindReferencesView can handle. #[derive(Clone, Debug)] pub enum FindReferencesViewAction { /// Navigate to the reference at the given index. GotoReference(usize), /// Close the references card. Close, /// Move selection up (arrow up). ArrowUp, /// Move selection down (arrow down). ArrowDown, /// Select the currently highlighted reference (enter). SelectReference, } /// Events emitted by FindReferencesView. pub enum FindReferencesViewEvent { /// User requested navigation to a reference. GotoReference(usize), /// User requested to close the card. CloseRequested, } /// View for displaying find references results with async line loading. pub struct FindReferencesView { /// Flat list of all references with their UI state. references: Vec<ReferenceEntryWithUi>, /// The offset where references were requested (used for positioning). request_offset: CharOffset, /// Scroll state for the card content. scroll_state: ClippedScrollStateHandle, back_mouse_state: MouseStateHandle, /// The index of the currently selected reference for keyboard navigation. selected_reference_index: usize, } /// A reference entry bundled with its UI state. pub struct ReferenceEntryWithUi { /// The reference data. pub entry: FlatReferenceEntry, /// Mouse state for the entry row hover. pub entry_mouse_state: MouseStateHandle, /// Mouse state for the file name tooltip. pub file_name_mouse_state: MouseStateHandle, /// Editor view for syntax highlighting. pub editor_view: ViewHandle<CodeEditorView>, } impl ReferenceEntryWithUi { /// Updates the line content and refreshes the editor view. /// Trims whitespace from the start of the line and adjusts the column accordingly. pub fn update_line_content(&mut self, line: &str, ctx: &mut ViewContext<FindReferencesView>) { let trimmed = line.trim_start(); let trim_offset = line.len() - trimmed.len(); self.entry.line_content = Some(trimmed.to_string()); self.entry.column = self.entry.column.saturating_sub(trim_offset); // Update the editor view with the loaded content let content = trimmed.to_string(); let file_path = self.entry.file_path.clone(); self.editor_view.update(ctx, |view, ctx| { view.set_language_with_local_path(&file_path, ctx); let state = InitialBufferState::plain_text(&content); view.reset(state, ctx); }); } } impl FindReferencesView { /// Creates a new FindReferencesView with async line loading. /// Lines are loaded asynchronously from GlobalBufferModel (fast) or disk (slow). /// /// Converts raw ReferenceLocation to FlatReferenceEntry and loads line content. pub fn new( raw_references: Vec<ReferenceLocation>, workspace_root: Option<PathBuf>, request_offset: CharOffset, ctx: &mut ViewContext<Self>, ) -> Self { // Convert ReferenceLocation to ReferenceEntryWithUi (without line content - will load async) let mut references: Vec<ReferenceEntryWithUi> = raw_references .into_iter() .map(|r| { let file_path = r.file_path.clone(); // Get just the file name (leaf) for display let file_name = file_path .file_name() .map(|n| n.to_string_lossy().to_string()) .unwrap_or_else(|| file_path.to_string_lossy().to_string()); // Guard against empty file names from malformed LSP URIs let file_name = if file_name.is_empty() { "[unknown]".to_string() } else { file_name }; // Get relative path from workspace root for tooltip let display_path = workspace_root .as_ref() .and_then(|root| file_path.strip_prefix(root).ok()) .map(|p| p.to_string_lossy().to_string()) .unwrap_or_else(|| file_path.to_string_lossy().to_string()); let line_number = r.range.start.line + 1; // Convert 0-based to 1-based let entry = FlatReferenceEntry { file_path, file_name, display_path, line_number, column: r.range.start.column, line_content: None, // Will be loaded async }; // Create editor view for this reference let editor_view = Self::create_editor_view_for_reference(&entry, ctx); ReferenceEntryWithUi { entry, entry_mouse_state: MouseStateHandle::default(), file_name_mouse_state: MouseStateHandle::default(), editor_view, } }) .collect(); // Batch references by file for efficient loading let mut file_to_refs: HashMap<PathBuf, Vec<usize>> = HashMap::new(); for (idx, reference) in references.iter().enumerate() { if reference.entry.line_content.is_none() { file_to_refs .entry(reference.entry.file_path.clone()) .or_default() .push(idx); } } // Try to load lines from GlobalBufferModel first (fast, in-memory) let global_buffer = GlobalBufferModel::handle(ctx); for (file_path, ref_indices) in file_to_refs.iter() { // Collect all line numbers we need from this file (0-based) let line_numbers: Vec<usize> = ref_indices .iter() .map(|&idx| references[idx].entry.line_number - 1) .collect(); if line_numbers.is_empty() { continue; } if let Some(lines) = global_buffer.update(ctx, |model, ctx| { model.get_lines_for_file(file_path, line_numbers.clone(), ctx) }) { // Build a map from line number to content for quick lookup let line_map: HashMap<usize, &String> = lines.iter().map(|(ln, content)| (*ln, content)).collect(); // Successfully loaded from buffer - update references for &ref_idx in ref_indices { let reference = &mut references[ref_idx]; let line_num = reference.entry.line_number - 1; // 0-based if let Some(line) = line_map.get(&line_num) { reference.update_line_content(line, ctx); } } } } // For any references still without line_content, spawn async file reads for (file_path, ref_indices) in file_to_refs { // Check if any refs for this file still need loading let needs_loading: Vec<usize> = ref_indices .iter() .copied() .filter(|&idx| references[idx].entry.line_content.is_none()) .collect(); if needs_loading.is_empty() { continue; } // Collect line numbers for this file (0-based) let line_numbers: Vec<usize> = needs_loading .iter() .map(|&idx| references[idx].entry.line_number - 1) .collect(); let file_path_clone = file_path.clone(); ctx.spawn( async move { FileModel::read_lines_async(&file_path_clone, line_numbers).await }, move |me, result, ctx| { if let Ok(lines) = result { // Build a map from line number to content for quick lookup let line_map: HashMap<usize, &String> = lines.iter().map(|(ln, content)| (*ln, content)).collect(); for &ref_idx in &needs_loading { if ref_idx >= me.references.len() { continue; } let reference = &mut me.references[ref_idx]; let line_num = reference.entry.line_number - 1; // 0-based if let Some(line) = line_map.get(&line_num) { reference.update_line_content(line, ctx); } } ctx.notify(); // Trigger re-render } }, ); } Self { references, request_offset, scroll_state: ClippedScrollStateHandle::default(), back_mouse_state: MouseStateHandle::default(), selected_reference_index: 0, } } /// Returns the request offset for positioning the card. pub fn request_offset(&self) -> CharOffset { self.request_offset } /// Gets a reference by index. pub fn get_reference(&self, index: usize) -> Option<&FlatReferenceEntry> { self.references.get(index).map(|r| &r.entry) } /// Creates a read-only code editor view for a single reference line. fn create_editor_view_for_reference( reference: &FlatReferenceEntry, ctx: &mut ViewContext<Self>, ) -> ViewHandle<CodeEditorView> { let view = ctx.add_typed_action_view(|ctx| { let mut editor_view = CodeEditorView::new( None, None, CodeEditorRenderOptions::new(VerticalExpansionBehavior::InfiniteHeight), ctx, ) .with_can_show_diff_ui(false) .with_show_line_numbers(false) .with_horizontal_scrollbar_appearance( warpui::elements::new_scrollable::ScrollableAppearance::new( warpui::elements::ScrollbarWidth::None, false, ), ); editor_view.set_vertical_scrollbar_appearance( warpui::elements::new_scrollable::ScrollableAppearance::new( warpui::elements::ScrollbarWidth::None, false, ), ); editor_view }); // Initialize the editor with the reference line content (or empty if still loading) let content = reference.line_content.clone().unwrap_or_default(); let file_path = reference.file_path.clone(); view.update(ctx, |view, ctx| { view.set_show_current_line_highlights(false, ctx); view.set_interaction_state(InteractionState::Disabled, ctx); // Set up syntax highlighting based on file extension view.set_language_with_local_path(&file_path, ctx); // Reset with the reference line content let state = InitialBufferState::plain_text(&content); view.reset(state, ctx); }); view } } impl Entity for FindReferencesView { type Event = FindReferencesViewEvent; } impl TypedActionView for FindReferencesView { type Action = FindReferencesViewAction; fn handle_action(&mut self, action: &Self::Action, ctx: &mut ViewContext<Self>) { match action { FindReferencesViewAction::GotoReference(index) => { ctx.emit(FindReferencesViewEvent::GotoReference(*index)); } FindReferencesViewAction::Close => { ctx.emit(FindReferencesViewEvent::CloseRequested); } FindReferencesViewAction::ArrowUp => { if !self.references.is_empty() { self.selected_reference_index = (self.selected_reference_index + self.references.len() - 1) % self.references.len(); ctx.notify(); } } FindReferencesViewAction::ArrowDown => { if !self.references.is_empty() { self.selected_reference_index = (self.selected_reference_index + 1) % self.references.len(); ctx.notify(); } } FindReferencesViewAction::SelectReference => { if self.selected_reference_index < self.references.len() { ctx.emit(FindReferencesViewEvent::GotoReference( self.selected_reference_index, )); } } } } } impl View for FindReferencesView { fn ui_name() -> &'static str { "FindReferencesView" } fn keymap_context(&self, _app: &AppContext) -> warpui::keymap::Context { let mut context = Self::default_keymap_context(); if !self.references.is_empty() { context.set.insert(HAS_REFERENCES); } context } fn render(&self, app: &AppContext) -> Box<dyn Element> { if self.references.is_empty() { return warpui::elements::Empty::new().finish(); } let appearance = Appearance::handle(app).as_ref(app); let theme = appearance.theme(); // Header with back arrow and "Showing X references" let header = render_header(self.back_mouse_state.clone(), self.references.len(), app); // Content: flat list of reference entries let mut content_column = Flex::column().with_cross_axis_alignment(CrossAxisAlignment::Stretch); for (index, reference) in self.references.iter().enumerate() { let is_selected = index == self.selected_reference_index; let entry = render_reference_entry( &reference.entry, reference.entry_mouse_state.clone(), reference.file_name_mouse_state.clone(), &reference.editor_view, index, is_selected, app, ); content_column.add_child(entry); } // Make content scrollable let scrollable_content = ClippedScrollable::vertical( self.scroll_state.clone(), content_column.finish(), ScrollbarWidth::None, theme.disabled_ui_text_color().into(), theme.active_ui_text_color().into(), Fill::None, ) .finish(); // Combine header and content let card_content = Flex::column() .with_cross_axis_alignment(CrossAxisAlignment::Stretch) .with_child(header) .with_child(Shrinkable::new(1., scrollable_content).finish()) .finish(); ConstrainedBox::new( Container::new(card_content) .with_background_color(internal_colors::neutral_1(theme)) .with_corner_radius(CornerRadius::with_all(Radius::Pixels(8.))) .with_border(Border::all(1.).with_border_fill(internal_colors::neutral_4(theme))) .finish(), ) .with_max_height(FIND_REFERENCES_CARD_MAX_HEIGHT) .finish() } } /// A single reference entry with file information for flat list display. #[derive(Debug, Clone)] pub struct FlatReferenceEntry { /// The absolute file path. pub file_path: PathBuf, /// The display file name (just the file name, not full path). pub file_name: String, /// The relative path from workspace root (for tooltip display). pub display_path: String, /// 1-based line number where the reference appears. pub line_number: usize, /// 0-based column number where the reference starts (adjusted for trimmed whitespace). pub column: usize, /// The content of the line containing the reference (trimmed). /// None indicates the line is still loading from disk. pub line_content: Option<String>, } /// Renders the card header with back arrow and "Showing X references" text. fn render_header( back_mouse_state: MouseStateHandle, total_refs: usize, app: &AppContext, ) -> Box<dyn Element> { let appearance = Appearance::handle(app).as_ref(app); let theme = appearance.theme(); // "Showing X references" title let title_text = if total_refs == 1 { "Showing 1 reference".to_string() } else {