Initial public release of Warp.

Repo-Sync-Origin: warpdotdev/warp-internal@12af1d983b
This commit is contained in:
David Stern
2026-04-28 08:43:33 -05:00
commit 0dbd3d567a
4982 changed files with 1431549 additions and 0 deletions
@@ -0,0 +1,293 @@
//! File search row rendering components.
//!
//! This module provides UI components for rendering file and directory search results
//! in search interfaces. It handles the display of file names with their parent paths,
//! supports fuzzy match highlighting, and intelligently truncates long paths while
//! preserving important information.
//!
//! The main functionality includes:
//! - Rendering file/directory names with optional path context
//! - Highlighting fuzzy match results in both filename and path portions
//! - Smart truncation of long file paths with ellipsis
//! - Responsive layout that adapts to different highlight states
use fuzzy_match::FuzzyMatchResult;
use std::path::Path;
use warp_core::ui::theme::Fill;
use warpui::elements::{
Container, CrossAxisAlignment, Flex, Highlight, MainAxisSize, ParentElement, Shrinkable, Text,
};
use warpui::fonts::{Properties, Weight};
use warpui::text_layout::ClipConfig;
use warpui::{AppContext, Element};
use crate::appearance::Appearance;
use crate::search::ai_context_menu::safe_truncate;
use crate::search::ItemHighlightState;
use warpui::SingletonEntity;
pub const MAX_COMBINED_LENGTH: usize = 55;
pub struct FileSearchRowOptions<'a> {
pub match_result: Option<&'a FuzzyMatchResult>,
pub highlight_state: ItemHighlightState,
pub item_font_size: Option<f32>,
pub path_font_size: Option<f32>,
pub item_text_fill_override: Option<Fill>,
pub text_color_override: Option<Fill>,
pub max_combined_length: Option<usize>,
}
impl<'a> Default for FileSearchRowOptions<'a> {
fn default() -> Self {
Self {
match_result: None,
highlight_state: ItemHighlightState::Default,
item_font_size: None,
path_font_size: None,
item_text_fill_override: None,
text_color_override: None,
max_combined_length: Some(MAX_COMBINED_LENGTH),
}
}
}
/// Renders a file search result row containing a file/directory name with optional path context.
///
/// This function creates a UI element that displays a file or directory name along with its parent
/// path, with support for fuzzy match highlighting and intelligent truncation. The layout adapts
/// based on the highlight state and prioritizes showing the filename over the full path when space
/// is limited.
///
/// # Arguments
///
/// * `path` - The full path to the file or directory
/// * `options` - Rendering options (match result, highlight state, font sizes, etc.)
/// * `app` - Application context for accessing themes and fonts
///
/// # Returns
///
/// A boxed UI element representing the file search row
pub fn render_file_search_row(
path: &Path,
options: FileSearchRowOptions<'_>,
app: &AppContext,
) -> Box<dyn Element> {
let FileSearchRowOptions {
match_result,
highlight_state,
item_font_size,
path_font_size,
item_text_fill_override,
text_color_override,
max_combined_length,
} = options;
let appearance = Appearance::as_ref(app);
// Extract item name from path (file or directory name)
let max_combined_length = max_combined_length.unwrap_or(MAX_COMBINED_LENGTH);
let original_item_name = path
.file_name()
.and_then(|name| name.to_str())
.unwrap_or("")
.to_string();
let mut item_name = original_item_name.clone();
// Create path display (show it grayed out)
let original_path_display = path
.parent()
.and_then(|parent| parent.to_str())
.unwrap_or("")
.to_string();
let mut path_display = original_path_display.clone();
// Track if we truncated anything for highlight adjustment
let mut filename_truncated = false;
let mut path_truncated = false;
let mut path_truncation_offset = 0;
// Ensure combined length is less than MAX_COMBINED_LENGTH characters
if options.max_combined_length.is_some() {
let combined_length = item_name.len() + path_display.len();
if combined_length > max_combined_length {
if item_name.len() >= max_combined_length {
safe_truncate(&mut item_name, max_combined_length - 3);
item_name.push_str("...");
filename_truncated = true;
path_display.clear();
} else {
let available_for_path = max_combined_length - item_name.len();
if path_display.len() > available_for_path {
let new_path_len = available_for_path.saturating_sub(3);
path_truncation_offset = path_display.len() - new_path_len;
safe_truncate(&mut path_display, new_path_len);
path_display.push_str("...");
path_truncated = true;
}
}
}
}
// Calculate highlight indices for item name and path
let (item_name_highlights, path_highlights) = if let Some(match_result) = match_result {
let full_path_str = path.to_string_lossy();
let item_name_start_in_full_path = full_path_str.len() - original_item_name.len();
calculate_highlight_indices(
match_result,
&original_path_display,
item_name_start_in_full_path,
filename_truncated,
path_truncated,
path_truncation_offset,
max_combined_length,
)
} else {
(Vec::new(), Vec::new())
};
let item_font_size: f32 =
item_font_size.unwrap_or_else(|| appearance.monospace_font_size() - 1.0);
let path_font_size: f32 =
path_font_size.unwrap_or_else(|| appearance.monospace_font_size() - 2.0);
let base_item_fill: Fill =
item_text_fill_override.unwrap_or_else(|| highlight_state.main_text_fill(appearance));
let base_path_fill: Fill = highlight_state.sub_text_fill(appearance);
let base_item_color = base_item_fill.into_solid();
let base_path_color = base_path_fill.into_solid();
let (item_color, path_color) = if let Some(override_fill) = text_color_override {
let override_color = override_fill.into_solid();
(override_color, override_color)
} else {
(base_item_color, base_path_color)
};
// Create item name with match highlighting
let mut item_text = Text::new_inline(item_name, appearance.ui_font_family(), item_font_size)
.with_color(item_color)
.soft_wrap(false);
if !item_name_highlights.is_empty() {
item_text = item_text.with_single_highlight(
Highlight::new().with_properties(Properties::default().weight(Weight::Bold)),
item_name_highlights,
);
}
// Create path text with lighter color and highlights
let path_text = if !path_display.is_empty() {
let mut path_text =
Text::new_inline(path_display, appearance.ui_font_family(), path_font_size)
.with_color(path_color)
.with_clip(ClipConfig::start())
.soft_wrap(false);
if !path_highlights.is_empty() {
path_text = path_text.with_single_highlight(
Highlight::new().with_properties(Properties::default().weight(Weight::Bold)),
path_highlights,
);
}
Some(path_text)
} else {
None
};
// Create row with item name and path
let mut row = Flex::row()
.with_main_axis_size(MainAxisSize::Max)
.with_cross_axis_alignment(CrossAxisAlignment::Center);
row.add_child(
Shrinkable::new(
// setting this to a high value so that we don't shrink the item text until absolutely necessary
20.0,
item_text.finish(),
)
.finish(),
);
if let Some(path_text) = path_text {
row.add_child(
Shrinkable::new(
1.0,
Container::new(path_text.finish())
.with_padding_left(3.)
.finish(),
)
.finish(),
);
}
row.finish()
}
/// Calculates highlight indices for both the item name and path portions of a file search result.
///
/// This function takes fuzzy match indices from the full path and splits them appropriately
/// between the filename and directory path components. It handles truncation adjustments
/// to ensure highlights remain accurate when text is shortened with ellipsis.
///
/// # Arguments
///
/// * `match_result` - The fuzzy match result containing highlight indices for the full path
/// * `original_path` - The original (untruncated) directory path string
/// * `item_name_start_in_full_path` - Byte offset where the filename begins in the full path
/// * `item_name_truncated` - Whether the filename was truncated with ellipsis
/// * `path_truncated` - Whether the directory path was truncated with ellipsis
/// * `path_truncation_offset` - Number of characters removed from the start of the path
///
/// # Returns
///
/// A tuple containing:
/// - `Vec<usize>` - Highlight indices for the item name portion
/// - `Vec<usize>` - Highlight indices for the directory path portion
fn calculate_highlight_indices(
match_result: &FuzzyMatchResult,
original_path: &str,
item_name_start_in_full_path: usize,
item_name_truncated: bool,
path_truncated: bool,
path_truncation_offset: usize,
max_combined_length: usize,
) -> (Vec<usize>, Vec<usize>) {
let mut item_name_highlights = Vec::new();
let mut path_highlights = Vec::new();
for &index in &match_result.matched_indices {
if index >= item_name_start_in_full_path {
// This highlight is in the item name
let item_name_index = index - item_name_start_in_full_path;
// Only include if within the displayed item name range
if !item_name_truncated || item_name_index < (max_combined_length - 3) {
item_name_highlights.push(item_name_index);
}
} else {
// This highlight is in the path
let path_index = index;
// Adjust for path truncation
if path_truncated {
if path_index >= path_truncation_offset {
let adjusted_index = path_index - path_truncation_offset;
if adjusted_index < original_path.len().saturating_sub(3) {
path_highlights.push(adjusted_index);
}
}
} else {
// No truncation, use index as-is
if path_index < original_path.len() {
path_highlights.push(path_index);
}
}
}
}
(item_name_highlights, path_highlights)
}