mirror of
https://github.com/zed-industries/zed.git
synced 2025-01-12 13:24:19 +00:00
188 lines
5.5 KiB
Rust
188 lines
5.5 KiB
Rust
|
mod highlighted_workspace_location;
|
||
|
|
||
|
use fuzzy::{StringMatch, StringMatchCandidate};
|
||
|
use gpui::{
|
||
|
actions,
|
||
|
elements::{ChildView, Flex, ParentElement},
|
||
|
AnyViewHandle, Element, ElementBox, Entity, MutableAppContext, RenderContext, Task, View,
|
||
|
ViewContext, ViewHandle,
|
||
|
};
|
||
|
use highlighted_workspace_location::HighlightedWorkspaceLocation;
|
||
|
use ordered_float::OrderedFloat;
|
||
|
use picker::{Picker, PickerDelegate};
|
||
|
use settings::Settings;
|
||
|
use workspace::{OpenPaths, Workspace, WorkspaceLocation};
|
||
|
|
||
|
const RECENT_LIMIT: usize = 100;
|
||
|
|
||
|
actions!(recent_projects, [Toggle]);
|
||
|
|
||
|
pub fn init(cx: &mut MutableAppContext) {
|
||
|
cx.add_action(RecentProjectsView::toggle);
|
||
|
Picker::<RecentProjectsView>::init(cx);
|
||
|
}
|
||
|
|
||
|
struct RecentProjectsView {
|
||
|
picker: ViewHandle<Picker<Self>>,
|
||
|
workspace_locations: Vec<WorkspaceLocation>,
|
||
|
selected_match_index: usize,
|
||
|
matches: Vec<StringMatch>,
|
||
|
}
|
||
|
|
||
|
impl RecentProjectsView {
|
||
|
fn new(cx: &mut ViewContext<Self>) -> Self {
|
||
|
let handle = cx.weak_handle();
|
||
|
let workspace_locations: Vec<WorkspaceLocation> = workspace::WORKSPACE_DB
|
||
|
.recent_workspaces(RECENT_LIMIT)
|
||
|
.unwrap_or_default()
|
||
|
.into_iter()
|
||
|
.map(|(_, location)| location)
|
||
|
.collect();
|
||
|
Self {
|
||
|
picker: cx.add_view(|cx| {
|
||
|
Picker::new("Recent Projects...", handle, cx).with_max_size(800., 1200.)
|
||
|
}),
|
||
|
workspace_locations,
|
||
|
selected_match_index: 0,
|
||
|
matches: Default::default(),
|
||
|
}
|
||
|
}
|
||
|
|
||
|
fn toggle(workspace: &mut Workspace, _: &Toggle, cx: &mut ViewContext<Workspace>) {
|
||
|
workspace.toggle_modal(cx, |_, cx| {
|
||
|
let view = cx.add_view(|cx| Self::new(cx));
|
||
|
cx.subscribe(&view, Self::on_event).detach();
|
||
|
view
|
||
|
});
|
||
|
}
|
||
|
|
||
|
fn on_event(
|
||
|
workspace: &mut Workspace,
|
||
|
_: ViewHandle<Self>,
|
||
|
event: &Event,
|
||
|
cx: &mut ViewContext<Workspace>,
|
||
|
) {
|
||
|
match event {
|
||
|
Event::Dismissed => workspace.dismiss_modal(cx),
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
pub enum Event {
|
||
|
Dismissed,
|
||
|
}
|
||
|
|
||
|
impl Entity for RecentProjectsView {
|
||
|
type Event = Event;
|
||
|
}
|
||
|
|
||
|
impl View for RecentProjectsView {
|
||
|
fn ui_name() -> &'static str {
|
||
|
"RecentProjectsView"
|
||
|
}
|
||
|
|
||
|
fn render(&mut self, cx: &mut RenderContext<Self>) -> ElementBox {
|
||
|
ChildView::new(self.picker.clone(), cx).boxed()
|
||
|
}
|
||
|
|
||
|
fn focus_in(&mut self, _: AnyViewHandle, cx: &mut ViewContext<Self>) {
|
||
|
if cx.is_self_focused() {
|
||
|
cx.focus(&self.picker);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
impl PickerDelegate for RecentProjectsView {
|
||
|
fn match_count(&self) -> usize {
|
||
|
self.matches.len()
|
||
|
}
|
||
|
|
||
|
fn selected_index(&self) -> usize {
|
||
|
self.selected_match_index
|
||
|
}
|
||
|
|
||
|
fn set_selected_index(&mut self, ix: usize, _cx: &mut ViewContext<Self>) {
|
||
|
self.selected_match_index = ix;
|
||
|
}
|
||
|
|
||
|
fn update_matches(&mut self, query: String, cx: &mut ViewContext<Self>) -> gpui::Task<()> {
|
||
|
let query = query.trim_start();
|
||
|
let smart_case = query.chars().any(|c| c.is_uppercase());
|
||
|
let candidates = self
|
||
|
.workspace_locations
|
||
|
.iter()
|
||
|
.enumerate()
|
||
|
.map(|(id, location)| {
|
||
|
let combined_string = location
|
||
|
.paths()
|
||
|
.iter()
|
||
|
.map(|path| path.to_string_lossy().to_owned())
|
||
|
.collect::<Vec<_>>()
|
||
|
.join("");
|
||
|
StringMatchCandidate::new(id, combined_string)
|
||
|
})
|
||
|
.collect::<Vec<_>>();
|
||
|
self.matches = smol::block_on(fuzzy::match_strings(
|
||
|
candidates.as_slice(),
|
||
|
query,
|
||
|
smart_case,
|
||
|
100,
|
||
|
&Default::default(),
|
||
|
cx.background().clone(),
|
||
|
));
|
||
|
self.matches.sort_unstable_by_key(|m| m.candidate_id);
|
||
|
|
||
|
self.selected_match_index = self
|
||
|
.matches
|
||
|
.iter()
|
||
|
.enumerate()
|
||
|
.max_by_key(|(_, m)| OrderedFloat(m.score))
|
||
|
.map(|(ix, _)| ix)
|
||
|
.unwrap_or(0);
|
||
|
Task::ready(())
|
||
|
}
|
||
|
|
||
|
fn confirm(&mut self, cx: &mut ViewContext<Self>) {
|
||
|
let selected_match = &self.matches[self.selected_index()];
|
||
|
let workspace_location = &self.workspace_locations[selected_match.candidate_id];
|
||
|
cx.dispatch_global_action(OpenPaths {
|
||
|
paths: workspace_location.paths().as_ref().clone(),
|
||
|
});
|
||
|
cx.emit(Event::Dismissed);
|
||
|
}
|
||
|
|
||
|
fn dismiss(&mut self, cx: &mut ViewContext<Self>) {
|
||
|
cx.emit(Event::Dismissed);
|
||
|
}
|
||
|
|
||
|
fn render_match(
|
||
|
&self,
|
||
|
ix: usize,
|
||
|
mouse_state: &mut gpui::MouseState,
|
||
|
selected: bool,
|
||
|
cx: &gpui::AppContext,
|
||
|
) -> ElementBox {
|
||
|
let settings = cx.global::<Settings>();
|
||
|
let string_match = &self.matches[ix];
|
||
|
let style = settings.theme.picker.item.style_for(mouse_state, selected);
|
||
|
|
||
|
let highlighted_location = HighlightedWorkspaceLocation::new(
|
||
|
&string_match,
|
||
|
&self.workspace_locations[string_match.candidate_id],
|
||
|
);
|
||
|
|
||
|
Flex::column()
|
||
|
.with_child(highlighted_location.names.render(style.label.clone()))
|
||
|
.with_children(
|
||
|
highlighted_location
|
||
|
.paths
|
||
|
.into_iter()
|
||
|
.map(|highlighted_path| highlighted_path.render(style.label.clone())),
|
||
|
)
|
||
|
.flex(1., false)
|
||
|
.contained()
|
||
|
.with_style(style.container)
|
||
|
.named("match")
|
||
|
}
|
||
|
}
|