Initial public release of Warp.
Repo-Sync-Origin: warpdotdev/warp-internal@12af1d983b
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
pub mod repo_data_source;
|
||||
pub mod repo_search_item;
|
||||
|
||||
pub use repo_data_source::*;
|
||||
pub use repo_search_item::*;
|
||||
@@ -0,0 +1,72 @@
|
||||
use ai::workspace::WorkspaceMetadata;
|
||||
use fuzzy_match::{match_indices_case_insensitive, FuzzyMatchResult};
|
||||
use itertools::Itertools;
|
||||
use warpui::{AppContext, Entity, SingletonEntity};
|
||||
|
||||
use super::RepoSearchItem;
|
||||
use crate::ai::persisted_workspace::PersistedWorkspace;
|
||||
use crate::search::command_palette::mixer::CommandPaletteItemAction;
|
||||
use crate::search::data_source::{Query, QueryResult};
|
||||
use crate::search::mixer::{DataSourceRunErrorWrapper, SyncDataSource};
|
||||
|
||||
const MAX_REPOS_CONSIDERED: usize = 50;
|
||||
|
||||
pub struct RepoDataSource {}
|
||||
|
||||
impl Default for RepoDataSource {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl RepoDataSource {
|
||||
pub fn new() -> Self {
|
||||
Self {}
|
||||
}
|
||||
|
||||
pub fn top_n(&self, limit: usize, app: &AppContext) -> impl Iterator<Item = RepoSearchItem> {
|
||||
PersistedWorkspace::as_ref(app)
|
||||
.workspaces()
|
||||
.filter(|cbm| cbm.path.is_dir())
|
||||
.sorted_by(WorkspaceMetadata::most_recently_navigated)
|
||||
.take(limit)
|
||||
.map(RepoSearchItem::new)
|
||||
}
|
||||
}
|
||||
|
||||
impl Entity for RepoDataSource {
|
||||
type Event = ();
|
||||
}
|
||||
|
||||
impl SyncDataSource for RepoDataSource {
|
||||
type Action = CommandPaletteItemAction;
|
||||
|
||||
fn run_query(
|
||||
&self,
|
||||
query: &Query,
|
||||
app: &AppContext,
|
||||
) -> Result<Vec<QueryResult<Self::Action>>, DataSourceRunErrorWrapper> {
|
||||
let query_str = query.text.as_str();
|
||||
|
||||
let repos = self.top_n(MAX_REPOS_CONSIDERED, app);
|
||||
|
||||
let results = repos
|
||||
.filter_map(|mut repo| {
|
||||
let match_result = if query_str.is_empty() {
|
||||
Some(FuzzyMatchResult::no_match())
|
||||
} else {
|
||||
match_indices_case_insensitive(repo.display_name.as_str(), query_str)
|
||||
};
|
||||
|
||||
// Boost repo results so they compete fairly with other sources
|
||||
match_result.map(|mut match_result| {
|
||||
match_result.score *= 4;
|
||||
repo.match_result = match_result;
|
||||
repo.into()
|
||||
})
|
||||
})
|
||||
.collect_vec();
|
||||
|
||||
Ok(results)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,141 @@
|
||||
use ai::workspace::WorkspaceMetadata;
|
||||
use fuzzy_match::FuzzyMatchResult;
|
||||
use ordered_float::OrderedFloat;
|
||||
use std::path::Path;
|
||||
use warp_core::ui::theme::Fill;
|
||||
use warpui::{
|
||||
elements::{Align, ConstrainedBox, Flex, Highlight, ParentElement, Shrinkable, Text},
|
||||
fonts::{Properties, Weight},
|
||||
AppContext, Element, SingletonEntity,
|
||||
};
|
||||
|
||||
use crate::appearance::Appearance;
|
||||
use crate::search::action::search_item::styles;
|
||||
use crate::search::command_palette::mixer::CommandPaletteItemAction;
|
||||
use crate::search::command_palette::render_util;
|
||||
use crate::search::item::SearchItem;
|
||||
use crate::search::result_renderer::ItemHighlightState;
|
||||
use crate::ui_components::icons::Icon as UiIcon;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct RepoSearchItem {
|
||||
pub display_name: String,
|
||||
pub metadata: WorkspaceMetadata,
|
||||
pub match_result: FuzzyMatchResult,
|
||||
}
|
||||
|
||||
fn repo_display_name(repo_path: &Path) -> String {
|
||||
// Try to create a relative path from the user's home directory
|
||||
dirs::home_dir()
|
||||
.and_then(|home| repo_path.strip_prefix(&home).ok())
|
||||
.map(|relative_path| format!("~/{}", relative_path.display()))
|
||||
.unwrap_or_else(|| repo_path.display().to_string())
|
||||
}
|
||||
|
||||
impl RepoSearchItem {
|
||||
pub fn new(metadata: WorkspaceMetadata) -> Self {
|
||||
RepoSearchItem {
|
||||
display_name: repo_display_name(&metadata.path),
|
||||
metadata,
|
||||
match_result: FuzzyMatchResult::no_match(),
|
||||
}
|
||||
}
|
||||
|
||||
fn render(
|
||||
&self,
|
||||
item_highlight_state: ItemHighlightState,
|
||||
appearance: &Appearance,
|
||||
) -> Box<dyn Element> {
|
||||
let label = self.render_label(item_highlight_state, appearance);
|
||||
let mut binding = Flex::row();
|
||||
|
||||
binding.add_child(Shrinkable::new(1., Align::new(label).left().finish()).finish());
|
||||
|
||||
ConstrainedBox::new(binding.finish())
|
||||
.with_height(styles::SEARCH_ITEM_HEIGHT)
|
||||
.finish()
|
||||
}
|
||||
|
||||
fn render_label(
|
||||
&self,
|
||||
item_highlight_state: ItemHighlightState,
|
||||
appearance: &Appearance,
|
||||
) -> Box<dyn Element> {
|
||||
Text::new_inline(
|
||||
repo_display_name(&self.metadata.path),
|
||||
appearance.ui_font_family(),
|
||||
appearance.monospace_font_size(),
|
||||
)
|
||||
.with_color(item_highlight_state.sub_text_fill(appearance).into_solid())
|
||||
.with_style(Properties::default().weight(Weight::Bold))
|
||||
.with_single_highlight(
|
||||
Highlight::new()
|
||||
.with_properties(Properties::default().weight(Weight::Bold))
|
||||
.with_foreground_color(
|
||||
item_highlight_state.main_text_fill(appearance).into_solid(),
|
||||
),
|
||||
self.match_result.matched_indices.clone(),
|
||||
)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl SearchItem for RepoSearchItem {
|
||||
type Action = CommandPaletteItemAction;
|
||||
|
||||
fn render_icon(
|
||||
&self,
|
||||
highlight_state: ItemHighlightState,
|
||||
appearance: &Appearance,
|
||||
) -> Box<dyn Element> {
|
||||
let icon_color: Fill = appearance.theme().terminal_colors().normal.cyan.into();
|
||||
|
||||
render_util::render_search_item_icon(
|
||||
appearance,
|
||||
UiIcon::Folder,
|
||||
icon_color.into_solid(),
|
||||
highlight_state,
|
||||
)
|
||||
}
|
||||
|
||||
fn render_item(
|
||||
&self,
|
||||
highlight_state: ItemHighlightState,
|
||||
app: &AppContext,
|
||||
) -> Box<dyn Element> {
|
||||
let appearance = Appearance::as_ref(app);
|
||||
self.render(highlight_state, appearance)
|
||||
}
|
||||
|
||||
fn render_details(&self, _ctx: &AppContext) -> Option<Box<dyn Element>> {
|
||||
None
|
||||
}
|
||||
|
||||
fn score(&self) -> OrderedFloat<f64> {
|
||||
OrderedFloat(self.match_result.score as f64)
|
||||
}
|
||||
|
||||
fn accept_result(&self) -> CommandPaletteItemAction {
|
||||
// Convert the absolute repo path into parent + basename for OpenDirectory
|
||||
let repo_path: &Path = &self.metadata.path;
|
||||
let parent = repo_path.parent().unwrap_or(Path::new("/"));
|
||||
let basename = repo_path
|
||||
.file_name()
|
||||
.map(|s| s.to_string_lossy().to_string())
|
||||
.unwrap_or_else(|| repo_path.to_string_lossy().to_string());
|
||||
|
||||
CommandPaletteItemAction::OpenDirectory {
|
||||
path: basename,
|
||||
project_directory: parent.to_string_lossy().to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
fn execute_result(&self) -> CommandPaletteItemAction {
|
||||
// For projects, execute and accept have the same behavior
|
||||
self.accept_result()
|
||||
}
|
||||
|
||||
fn accessibility_label(&self) -> String {
|
||||
format!("Repo: {}", self.metadata.path.display())
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user