Initial public release of Warp.
Repo-Sync-Origin: warpdotdev/warp-internal@12af1d983b
This commit is contained in:
@@ -0,0 +1,270 @@
|
||||
use crate::appearance::Appearance;
|
||||
use crate::search::ai_context_menu::styles;
|
||||
use crate::search::ai_context_menu::{mixer::AIContextMenuSearchableAction, safe_truncate};
|
||||
use crate::search::item::{IconLocation, SearchItem};
|
||||
use crate::search::result_renderer::ItemHighlightState;
|
||||
use fuzzy_match::FuzzyMatchResult;
|
||||
use ordered_float::OrderedFloat;
|
||||
use warpui::elements::{
|
||||
ConstrainedBox, Container, CrossAxisAlignment, Flex, Highlight, Icon, ParentElement, Text,
|
||||
};
|
||||
use warpui::fonts::{Properties, Weight};
|
||||
use warpui::{AppContext, Element, SingletonEntity};
|
||||
|
||||
// Import CodeSymbol from the data_source module
|
||||
use super::data_source::CodeSymbol;
|
||||
|
||||
const MAX_COMBINED_LENGTH: usize = 55;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct CodeSearchItem {
|
||||
pub code_symbol: CodeSymbol,
|
||||
pub match_result: FuzzyMatchResult,
|
||||
}
|
||||
|
||||
impl SearchItem for CodeSearchItem {
|
||||
type Action = AIContextMenuSearchableAction;
|
||||
|
||||
fn render_icon(
|
||||
&self,
|
||||
_highlight_state: ItemHighlightState,
|
||||
appearance: &Appearance,
|
||||
) -> Box<dyn Element> {
|
||||
Container::new(
|
||||
ConstrainedBox::new(
|
||||
Icon::new(
|
||||
"bundled/svg/code-01.svg",
|
||||
_highlight_state.icon_fill(appearance).into_solid(),
|
||||
)
|
||||
.finish(),
|
||||
)
|
||||
.with_width(styles::ICON_SIZE)
|
||||
.with_height(styles::ICON_SIZE)
|
||||
.finish(),
|
||||
)
|
||||
.with_margin_right(styles::MARGIN_RIGHT)
|
||||
.finish()
|
||||
}
|
||||
|
||||
fn icon_location(&self, _appearance: &Appearance) -> IconLocation {
|
||||
IconLocation::Centered
|
||||
}
|
||||
|
||||
fn render_item(
|
||||
&self,
|
||||
highlight_state: ItemHighlightState,
|
||||
app: &AppContext,
|
||||
) -> Box<dyn Element> {
|
||||
let appearance = Appearance::as_ref(app);
|
||||
|
||||
// Build the symbol name with type prefix
|
||||
let mut symbol_name = String::new();
|
||||
if let Some(symbol_type) = &self.code_symbol.symbol.type_prefix {
|
||||
symbol_name.push_str(&format!("{symbol_type} "));
|
||||
}
|
||||
symbol_name.push_str(&self.code_symbol.symbol.name);
|
||||
|
||||
// Get file path for display
|
||||
let file_path = self.code_symbol.file_path.to_string_lossy().to_string();
|
||||
let mut path_display = file_path.clone();
|
||||
|
||||
// Track truncation for highlight adjustment
|
||||
let mut symbol_truncated = false;
|
||||
|
||||
// Ensure combined length is less than MAX_COMBINED_LENGTH characters
|
||||
let combined_length = symbol_name.len() + path_display.len();
|
||||
|
||||
if combined_length > MAX_COMBINED_LENGTH {
|
||||
// If combined length is too long, prioritize showing the symbol name
|
||||
if symbol_name.len() >= MAX_COMBINED_LENGTH {
|
||||
// If symbol name itself is too long, truncate it and add ellipsis
|
||||
safe_truncate(&mut symbol_name, MAX_COMBINED_LENGTH - 3);
|
||||
symbol_name.push_str("...");
|
||||
symbol_truncated = true;
|
||||
path_display.clear();
|
||||
} else {
|
||||
// Symbol name fits, truncate path display
|
||||
let available_for_path = MAX_COMBINED_LENGTH - symbol_name.len();
|
||||
if path_display.len() > available_for_path {
|
||||
let new_path_len = available_for_path.saturating_sub(3);
|
||||
safe_truncate(&mut path_display, new_path_len);
|
||||
path_display.push_str("...");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate highlight indices, adjusting for display format
|
||||
// The fuzzy matching is done on concatenated "typeprefix" + "symbolname" (no space)
|
||||
// But display shows "typeprefix " + "symbolname" (with space)
|
||||
// So we need to adjust indices to account for the added space in display
|
||||
let symbol_highlights: Vec<usize> = if !symbol_truncated {
|
||||
self.match_result
|
||||
.matched_indices
|
||||
.iter()
|
||||
.map(|&i| {
|
||||
if let Some(symbol_type) = &self.code_symbol.symbol.type_prefix {
|
||||
// If we have a type prefix, adjust indices:
|
||||
// - Indices 0 to type_prefix.len()-1 map directly (type prefix part)
|
||||
// - Indices type_prefix.len() and beyond need +1 offset (for the added space)
|
||||
if i < symbol_type.len() {
|
||||
i // Direct mapping for type prefix
|
||||
} else {
|
||||
i + 1 // Add 1 for the space between type and name
|
||||
}
|
||||
} else {
|
||||
i // No type prefix, direct mapping
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
} else {
|
||||
// Only include highlights that fall within the truncated range
|
||||
self.match_result
|
||||
.matched_indices
|
||||
.iter()
|
||||
.filter_map(|&i| {
|
||||
let adjusted_i = if let Some(symbol_type) = &self.code_symbol.symbol.type_prefix
|
||||
{
|
||||
if i < symbol_type.len() {
|
||||
i
|
||||
} else {
|
||||
i + 1
|
||||
}
|
||||
} else {
|
||||
i
|
||||
};
|
||||
|
||||
if adjusted_i < MAX_COMBINED_LENGTH - 3 {
|
||||
Some(adjusted_i)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
};
|
||||
|
||||
// Create symbol name text with highlighting
|
||||
let mut symbol_text = Text::new(
|
||||
symbol_name,
|
||||
appearance.ui_font_family(),
|
||||
appearance.monospace_font_size() - 1.0,
|
||||
)
|
||||
.with_color(highlight_state.main_text_fill(appearance).into_solid());
|
||||
|
||||
if !symbol_highlights.is_empty() {
|
||||
symbol_text = symbol_text.with_single_highlight(
|
||||
Highlight::new().with_properties(Properties::default().weight(Weight::Bold)),
|
||||
symbol_highlights,
|
||||
);
|
||||
}
|
||||
|
||||
// Create path text with lighter color
|
||||
let path_text = if !path_display.is_empty() {
|
||||
Some(
|
||||
Text::new(
|
||||
path_display,
|
||||
appearance.ui_font_family(),
|
||||
appearance.monospace_font_size() - 2.0,
|
||||
)
|
||||
.with_color(highlight_state.sub_text_fill(appearance).into_solid())
|
||||
.finish(),
|
||||
)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
// Create row with symbol name and path on the same line
|
||||
let mut row = Flex::row()
|
||||
.with_child(symbol_text.finish())
|
||||
.with_cross_axis_alignment(CrossAxisAlignment::Center);
|
||||
|
||||
if let Some(path) = path_text {
|
||||
row.add_child(Container::new(path).with_padding_left(8.0).finish());
|
||||
}
|
||||
|
||||
row.finish()
|
||||
}
|
||||
|
||||
fn render_details(&self, ctx: &AppContext) -> Option<Box<dyn Element>> {
|
||||
let appearance = Appearance::as_ref(ctx);
|
||||
let theme = appearance.theme();
|
||||
|
||||
// Create main text: symbol type + name (e.g., "fn initialize_logger")
|
||||
let mut main_text = String::new();
|
||||
if let Some(symbol_type) = &self.code_symbol.symbol.type_prefix {
|
||||
main_text.push_str(symbol_type);
|
||||
main_text.push(' ');
|
||||
}
|
||||
main_text.push_str(&self.code_symbol.symbol.name);
|
||||
|
||||
// Create sub text: path + line number (e.g., "core/logging.rs (44)")
|
||||
let sub_text = format!(
|
||||
"{} ({})",
|
||||
self.code_symbol.file_path.to_string_lossy(),
|
||||
self.code_symbol.symbol.line_number
|
||||
);
|
||||
|
||||
// Create main text element - use slightly smaller font that scales with user settings
|
||||
let main_text_element = Text::new(
|
||||
main_text,
|
||||
appearance.monospace_font_family(), // Use monospace font for consistency
|
||||
appearance.monospace_font_size() - 1.0, // Slightly smaller than normal
|
||||
)
|
||||
.with_color(theme.active_ui_text_color().into())
|
||||
.with_style(Properties::default().weight(Weight::Medium))
|
||||
.finish();
|
||||
|
||||
// Create sub text element - even smaller for sub information
|
||||
let sub_text_element = Text::new(
|
||||
sub_text,
|
||||
appearance.monospace_font_family(), // Use monospace font for consistency
|
||||
appearance.monospace_font_size() - 3.0, // Smaller than main text
|
||||
)
|
||||
.with_color(theme.nonactive_ui_text_color().into())
|
||||
.finish();
|
||||
|
||||
// Create modal content with reduced spacing
|
||||
let content = Flex::column()
|
||||
.with_child(main_text_element)
|
||||
.with_child(
|
||||
Container::new(sub_text_element)
|
||||
.with_padding_top(2.0)
|
||||
.finish(),
|
||||
)
|
||||
.finish();
|
||||
|
||||
Some(content)
|
||||
}
|
||||
|
||||
fn score(&self) -> OrderedFloat<f64> {
|
||||
OrderedFloat(self.match_result.score as f64)
|
||||
}
|
||||
|
||||
fn accept_result(&self) -> Self::Action {
|
||||
// Format the text as "{symbol_type} {symbol_name} in {path}:{line_number}"
|
||||
let mut text = String::new();
|
||||
if let Some(symbol_type) = &self.code_symbol.symbol.type_prefix {
|
||||
text.push_str(symbol_type);
|
||||
text.push(' ');
|
||||
}
|
||||
text.push_str(&self.code_symbol.symbol.name);
|
||||
text.push_str(" in ");
|
||||
text.push_str(&self.code_symbol.file_path.to_string_lossy());
|
||||
text.push(':');
|
||||
text.push_str(&self.code_symbol.symbol.line_number.to_string());
|
||||
|
||||
AIContextMenuSearchableAction::InsertText { text }
|
||||
}
|
||||
|
||||
fn execute_result(&self) -> Self::Action {
|
||||
self.accept_result()
|
||||
}
|
||||
|
||||
fn accessibility_label(&self) -> String {
|
||||
format!(
|
||||
"Code symbol: {} in {}:{}",
|
||||
self.code_symbol.symbol.name,
|
||||
self.code_symbol.file_path.to_string_lossy(),
|
||||
self.code_symbol.symbol.line_number
|
||||
)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user