assistant2: Combine file & directory picker (#26975)
In the process of adding `@mentions` we realized that we do not want to make a distinction between Files & Directories in the UI, therefore this PR combines the File & Directory pickers into a unified version https://github.com/user-attachments/assets/f3bf189c-8b69-4f5f-90ce-0b83b12dbca3 (Ignore the `@mentions`, they are broken also on main) Release Notes: - N/A
This commit is contained in:
parent
fdcacb3849
commit
26f4b2a491
4 changed files with 95 additions and 346 deletions
|
@ -43,15 +43,6 @@ pub enum ContextKind {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ContextKind {
|
impl ContextKind {
|
||||||
pub fn label(&self) -> &'static str {
|
|
||||||
match self {
|
|
||||||
ContextKind::File => "File",
|
|
||||||
ContextKind::Directory => "Folder",
|
|
||||||
ContextKind::FetchedUrl => "Fetch",
|
|
||||||
ContextKind::Thread => "Thread",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn icon(&self) -> IconName {
|
pub fn icon(&self) -> IconName {
|
||||||
match self {
|
match self {
|
||||||
ContextKind::File => IconName::File,
|
ContextKind::File => IconName::File,
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
mod directory_context_picker;
|
|
||||||
mod fetch_context_picker;
|
mod fetch_context_picker;
|
||||||
mod file_context_picker;
|
mod file_context_picker;
|
||||||
mod thread_context_picker;
|
mod thread_context_picker;
|
||||||
|
@ -15,8 +14,6 @@ use thread_context_picker::{render_thread_context_entry, ThreadContextEntry};
|
||||||
use ui::{prelude::*, ContextMenu, ContextMenuEntry, ContextMenuItem};
|
use ui::{prelude::*, ContextMenu, ContextMenuEntry, ContextMenuItem};
|
||||||
use workspace::{notifications::NotifyResultExt, Workspace};
|
use workspace::{notifications::NotifyResultExt, Workspace};
|
||||||
|
|
||||||
use crate::context::ContextKind;
|
|
||||||
use crate::context_picker::directory_context_picker::DirectoryContextPicker;
|
|
||||||
use crate::context_picker::fetch_context_picker::FetchContextPicker;
|
use crate::context_picker::fetch_context_picker::FetchContextPicker;
|
||||||
use crate::context_picker::file_context_picker::FileContextPicker;
|
use crate::context_picker::file_context_picker::FileContextPicker;
|
||||||
use crate::context_picker::thread_context_picker::ThreadContextPicker;
|
use crate::context_picker::thread_context_picker::ThreadContextPicker;
|
||||||
|
@ -30,17 +27,41 @@ pub enum ConfirmBehavior {
|
||||||
Close,
|
Close,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
enum ContextPickerMode {
|
enum ContextPickerMode {
|
||||||
|
File,
|
||||||
|
Fetch,
|
||||||
|
Thread,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ContextPickerMode {
|
||||||
|
pub fn label(&self) -> &'static str {
|
||||||
|
match self {
|
||||||
|
Self::File => "File/Directory",
|
||||||
|
Self::Fetch => "Fetch",
|
||||||
|
Self::Thread => "Thread",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn icon(&self) -> IconName {
|
||||||
|
match self {
|
||||||
|
Self::File => IconName::File,
|
||||||
|
Self::Fetch => IconName::Globe,
|
||||||
|
Self::Thread => IconName::MessageCircle,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
enum ContextPickerState {
|
||||||
Default(Entity<ContextMenu>),
|
Default(Entity<ContextMenu>),
|
||||||
File(Entity<FileContextPicker>),
|
File(Entity<FileContextPicker>),
|
||||||
Directory(Entity<DirectoryContextPicker>),
|
|
||||||
Fetch(Entity<FetchContextPicker>),
|
Fetch(Entity<FetchContextPicker>),
|
||||||
Thread(Entity<ThreadContextPicker>),
|
Thread(Entity<ThreadContextPicker>),
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) struct ContextPicker {
|
pub(super) struct ContextPicker {
|
||||||
mode: ContextPickerMode,
|
mode: ContextPickerState,
|
||||||
workspace: WeakEntity<Workspace>,
|
workspace: WeakEntity<Workspace>,
|
||||||
editor: WeakEntity<Editor>,
|
editor: WeakEntity<Editor>,
|
||||||
context_store: WeakEntity<ContextStore>,
|
context_store: WeakEntity<ContextStore>,
|
||||||
|
@ -59,7 +80,7 @@ impl ContextPicker {
|
||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
ContextPicker {
|
ContextPicker {
|
||||||
mode: ContextPickerMode::Default(ContextMenu::build(
|
mode: ContextPickerState::Default(ContextMenu::build(
|
||||||
window,
|
window,
|
||||||
cx,
|
cx,
|
||||||
|menu, _window, _cx| menu,
|
|menu, _window, _cx| menu,
|
||||||
|
@ -73,7 +94,7 @@ impl ContextPicker {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn init(&mut self, window: &mut Window, cx: &mut Context<Self>) {
|
pub fn init(&mut self, window: &mut Window, cx: &mut Context<Self>) {
|
||||||
self.mode = ContextPickerMode::Default(self.build_menu(window, cx));
|
self.mode = ContextPickerState::Default(self.build_menu(window, cx));
|
||||||
cx.notify();
|
cx.notify();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -88,13 +109,9 @@ impl ContextPicker {
|
||||||
.enumerate()
|
.enumerate()
|
||||||
.map(|(ix, entry)| self.recent_menu_item(context_picker.clone(), ix, entry));
|
.map(|(ix, entry)| self.recent_menu_item(context_picker.clone(), ix, entry));
|
||||||
|
|
||||||
let mut context_kinds = vec![
|
let mut modes = vec![ContextPickerMode::File, ContextPickerMode::Fetch];
|
||||||
ContextKind::File,
|
|
||||||
ContextKind::Directory,
|
|
||||||
ContextKind::FetchedUrl,
|
|
||||||
];
|
|
||||||
if self.allow_threads() {
|
if self.allow_threads() {
|
||||||
context_kinds.push(ContextKind::Thread);
|
modes.push(ContextPickerMode::Thread);
|
||||||
}
|
}
|
||||||
|
|
||||||
let menu = menu
|
let menu = menu
|
||||||
|
@ -112,15 +129,15 @@ impl ContextPicker {
|
||||||
})
|
})
|
||||||
.extend(recent_entries)
|
.extend(recent_entries)
|
||||||
.when(has_recent, |menu| menu.separator())
|
.when(has_recent, |menu| menu.separator())
|
||||||
.extend(context_kinds.into_iter().map(|kind| {
|
.extend(modes.into_iter().map(|mode| {
|
||||||
let context_picker = context_picker.clone();
|
let context_picker = context_picker.clone();
|
||||||
|
|
||||||
ContextMenuEntry::new(kind.label())
|
ContextMenuEntry::new(mode.label())
|
||||||
.icon(kind.icon())
|
.icon(mode.icon())
|
||||||
.icon_size(IconSize::XSmall)
|
.icon_size(IconSize::XSmall)
|
||||||
.icon_color(Color::Muted)
|
.icon_color(Color::Muted)
|
||||||
.handler(move |window, cx| {
|
.handler(move |window, cx| {
|
||||||
context_picker.update(cx, |this, cx| this.select_kind(kind, window, cx))
|
context_picker.update(cx, |this, cx| this.select_mode(mode, window, cx))
|
||||||
})
|
})
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
@ -143,12 +160,17 @@ impl ContextPicker {
|
||||||
self.thread_store.is_some()
|
self.thread_store.is_some()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn select_kind(&mut self, kind: ContextKind, window: &mut Window, cx: &mut Context<Self>) {
|
fn select_mode(
|
||||||
|
&mut self,
|
||||||
|
mode: ContextPickerMode,
|
||||||
|
window: &mut Window,
|
||||||
|
cx: &mut Context<Self>,
|
||||||
|
) {
|
||||||
let context_picker = cx.entity().downgrade();
|
let context_picker = cx.entity().downgrade();
|
||||||
|
|
||||||
match kind {
|
match mode {
|
||||||
ContextKind::File => {
|
ContextPickerMode::File => {
|
||||||
self.mode = ContextPickerMode::File(cx.new(|cx| {
|
self.mode = ContextPickerState::File(cx.new(|cx| {
|
||||||
FileContextPicker::new(
|
FileContextPicker::new(
|
||||||
context_picker.clone(),
|
context_picker.clone(),
|
||||||
self.workspace.clone(),
|
self.workspace.clone(),
|
||||||
|
@ -160,20 +182,8 @@ impl ContextPicker {
|
||||||
)
|
)
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
ContextKind::Directory => {
|
ContextPickerMode::Fetch => {
|
||||||
self.mode = ContextPickerMode::Directory(cx.new(|cx| {
|
self.mode = ContextPickerState::Fetch(cx.new(|cx| {
|
||||||
DirectoryContextPicker::new(
|
|
||||||
context_picker.clone(),
|
|
||||||
self.workspace.clone(),
|
|
||||||
self.context_store.clone(),
|
|
||||||
self.confirm_behavior,
|
|
||||||
window,
|
|
||||||
cx,
|
|
||||||
)
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
ContextKind::FetchedUrl => {
|
|
||||||
self.mode = ContextPickerMode::Fetch(cx.new(|cx| {
|
|
||||||
FetchContextPicker::new(
|
FetchContextPicker::new(
|
||||||
context_picker.clone(),
|
context_picker.clone(),
|
||||||
self.workspace.clone(),
|
self.workspace.clone(),
|
||||||
|
@ -184,9 +194,9 @@ impl ContextPicker {
|
||||||
)
|
)
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
ContextKind::Thread => {
|
ContextPickerMode::Thread => {
|
||||||
if let Some(thread_store) = self.thread_store.as_ref() {
|
if let Some(thread_store) = self.thread_store.as_ref() {
|
||||||
self.mode = ContextPickerMode::Thread(cx.new(|cx| {
|
self.mode = ContextPickerState::Thread(cx.new(|cx| {
|
||||||
ThreadContextPicker::new(
|
ThreadContextPicker::new(
|
||||||
thread_store.clone(),
|
thread_store.clone(),
|
||||||
context_picker.clone(),
|
context_picker.clone(),
|
||||||
|
@ -224,6 +234,7 @@ impl ContextPicker {
|
||||||
ElementId::NamedInteger("ctx-recent".into(), ix),
|
ElementId::NamedInteger("ctx-recent".into(), ix),
|
||||||
&path,
|
&path,
|
||||||
&path_prefix,
|
&path_prefix,
|
||||||
|
false,
|
||||||
context_store.clone(),
|
context_store.clone(),
|
||||||
cx,
|
cx,
|
||||||
)
|
)
|
||||||
|
@ -392,11 +403,10 @@ impl EventEmitter<DismissEvent> for ContextPicker {}
|
||||||
impl Focusable for ContextPicker {
|
impl Focusable for ContextPicker {
|
||||||
fn focus_handle(&self, cx: &App) -> FocusHandle {
|
fn focus_handle(&self, cx: &App) -> FocusHandle {
|
||||||
match &self.mode {
|
match &self.mode {
|
||||||
ContextPickerMode::Default(menu) => menu.focus_handle(cx),
|
ContextPickerState::Default(menu) => menu.focus_handle(cx),
|
||||||
ContextPickerMode::File(file_picker) => file_picker.focus_handle(cx),
|
ContextPickerState::File(file_picker) => file_picker.focus_handle(cx),
|
||||||
ContextPickerMode::Directory(directory_picker) => directory_picker.focus_handle(cx),
|
ContextPickerState::Fetch(fetch_picker) => fetch_picker.focus_handle(cx),
|
||||||
ContextPickerMode::Fetch(fetch_picker) => fetch_picker.focus_handle(cx),
|
ContextPickerState::Thread(thread_picker) => thread_picker.focus_handle(cx),
|
||||||
ContextPickerMode::Thread(thread_picker) => thread_picker.focus_handle(cx),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -407,13 +417,10 @@ impl Render for ContextPicker {
|
||||||
.w(px(400.))
|
.w(px(400.))
|
||||||
.min_w(px(400.))
|
.min_w(px(400.))
|
||||||
.map(|parent| match &self.mode {
|
.map(|parent| match &self.mode {
|
||||||
ContextPickerMode::Default(menu) => parent.child(menu.clone()),
|
ContextPickerState::Default(menu) => parent.child(menu.clone()),
|
||||||
ContextPickerMode::File(file_picker) => parent.child(file_picker.clone()),
|
ContextPickerState::File(file_picker) => parent.child(file_picker.clone()),
|
||||||
ContextPickerMode::Directory(directory_picker) => {
|
ContextPickerState::Fetch(fetch_picker) => parent.child(fetch_picker.clone()),
|
||||||
parent.child(directory_picker.clone())
|
ContextPickerState::Thread(thread_picker) => parent.child(thread_picker.clone()),
|
||||||
}
|
|
||||||
ContextPickerMode::Fetch(fetch_picker) => parent.child(fetch_picker.clone()),
|
|
||||||
ContextPickerMode::Thread(thread_picker) => parent.child(thread_picker.clone()),
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,269 +0,0 @@
|
||||||
use std::path::Path;
|
|
||||||
use std::sync::atomic::AtomicBool;
|
|
||||||
use std::sync::Arc;
|
|
||||||
|
|
||||||
use fuzzy::PathMatch;
|
|
||||||
use gpui::{App, DismissEvent, Entity, FocusHandle, Focusable, Task, WeakEntity};
|
|
||||||
use picker::{Picker, PickerDelegate};
|
|
||||||
use project::{PathMatchCandidateSet, ProjectPath, WorktreeId};
|
|
||||||
use ui::{prelude::*, ListItem};
|
|
||||||
use util::ResultExt as _;
|
|
||||||
use workspace::{notifications::NotifyResultExt, Workspace};
|
|
||||||
|
|
||||||
use crate::context_picker::{ConfirmBehavior, ContextPicker};
|
|
||||||
use crate::context_store::ContextStore;
|
|
||||||
|
|
||||||
pub struct DirectoryContextPicker {
|
|
||||||
picker: Entity<Picker<DirectoryContextPickerDelegate>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl DirectoryContextPicker {
|
|
||||||
pub fn new(
|
|
||||||
context_picker: WeakEntity<ContextPicker>,
|
|
||||||
workspace: WeakEntity<Workspace>,
|
|
||||||
context_store: WeakEntity<ContextStore>,
|
|
||||||
confirm_behavior: ConfirmBehavior,
|
|
||||||
window: &mut Window,
|
|
||||||
cx: &mut Context<Self>,
|
|
||||||
) -> Self {
|
|
||||||
let delegate = DirectoryContextPickerDelegate::new(
|
|
||||||
context_picker,
|
|
||||||
workspace,
|
|
||||||
context_store,
|
|
||||||
confirm_behavior,
|
|
||||||
);
|
|
||||||
let picker = cx.new(|cx| Picker::uniform_list(delegate, window, cx));
|
|
||||||
|
|
||||||
Self { picker }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Focusable for DirectoryContextPicker {
|
|
||||||
fn focus_handle(&self, cx: &App) -> FocusHandle {
|
|
||||||
self.picker.focus_handle(cx)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Render for DirectoryContextPicker {
|
|
||||||
fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
|
|
||||||
self.picker.clone()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct DirectoryContextPickerDelegate {
|
|
||||||
context_picker: WeakEntity<ContextPicker>,
|
|
||||||
workspace: WeakEntity<Workspace>,
|
|
||||||
context_store: WeakEntity<ContextStore>,
|
|
||||||
confirm_behavior: ConfirmBehavior,
|
|
||||||
matches: Vec<PathMatch>,
|
|
||||||
selected_index: usize,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl DirectoryContextPickerDelegate {
|
|
||||||
pub fn new(
|
|
||||||
context_picker: WeakEntity<ContextPicker>,
|
|
||||||
workspace: WeakEntity<Workspace>,
|
|
||||||
context_store: WeakEntity<ContextStore>,
|
|
||||||
confirm_behavior: ConfirmBehavior,
|
|
||||||
) -> Self {
|
|
||||||
Self {
|
|
||||||
context_picker,
|
|
||||||
workspace,
|
|
||||||
context_store,
|
|
||||||
confirm_behavior,
|
|
||||||
matches: Vec::new(),
|
|
||||||
selected_index: 0,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn search(
|
|
||||||
&mut self,
|
|
||||||
query: String,
|
|
||||||
cancellation_flag: Arc<AtomicBool>,
|
|
||||||
workspace: &Entity<Workspace>,
|
|
||||||
cx: &mut Context<Picker<Self>>,
|
|
||||||
) -> Task<Vec<PathMatch>> {
|
|
||||||
if query.is_empty() {
|
|
||||||
let workspace = workspace.read(cx);
|
|
||||||
let project = workspace.project().read(cx);
|
|
||||||
let directory_matches = project.worktrees(cx).flat_map(|worktree| {
|
|
||||||
let worktree = worktree.read(cx);
|
|
||||||
let path_prefix: Arc<str> = worktree.root_name().into();
|
|
||||||
worktree.directories(false, 0).map(move |entry| PathMatch {
|
|
||||||
score: 0.,
|
|
||||||
positions: Vec::new(),
|
|
||||||
worktree_id: worktree.id().to_usize(),
|
|
||||||
path: entry.path.clone(),
|
|
||||||
path_prefix: path_prefix.clone(),
|
|
||||||
distance_to_relative_ancestor: 0,
|
|
||||||
is_dir: true,
|
|
||||||
})
|
|
||||||
});
|
|
||||||
|
|
||||||
Task::ready(directory_matches.collect())
|
|
||||||
} else {
|
|
||||||
let worktrees = workspace.read(cx).visible_worktrees(cx).collect::<Vec<_>>();
|
|
||||||
let candidate_sets = worktrees
|
|
||||||
.into_iter()
|
|
||||||
.map(|worktree| {
|
|
||||||
let worktree = worktree.read(cx);
|
|
||||||
|
|
||||||
PathMatchCandidateSet {
|
|
||||||
snapshot: worktree.snapshot(),
|
|
||||||
include_ignored: worktree
|
|
||||||
.root_entry()
|
|
||||||
.map_or(false, |entry| entry.is_ignored),
|
|
||||||
include_root_name: true,
|
|
||||||
candidates: project::Candidates::Directories,
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
|
|
||||||
let executor = cx.background_executor().clone();
|
|
||||||
cx.foreground_executor().spawn(async move {
|
|
||||||
fuzzy::match_path_sets(
|
|
||||||
candidate_sets.as_slice(),
|
|
||||||
query.as_str(),
|
|
||||||
None,
|
|
||||||
false,
|
|
||||||
100,
|
|
||||||
&cancellation_flag,
|
|
||||||
executor,
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PickerDelegate for DirectoryContextPickerDelegate {
|
|
||||||
type ListItem = ListItem;
|
|
||||||
|
|
||||||
fn match_count(&self) -> usize {
|
|
||||||
self.matches.len()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn selected_index(&self) -> usize {
|
|
||||||
self.selected_index
|
|
||||||
}
|
|
||||||
|
|
||||||
fn set_selected_index(
|
|
||||||
&mut self,
|
|
||||||
ix: usize,
|
|
||||||
_window: &mut Window,
|
|
||||||
_cx: &mut Context<Picker<Self>>,
|
|
||||||
) {
|
|
||||||
self.selected_index = ix;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn placeholder_text(&self, _window: &mut Window, _cx: &mut App) -> Arc<str> {
|
|
||||||
"Search folders…".into()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn update_matches(
|
|
||||||
&mut self,
|
|
||||||
query: String,
|
|
||||||
_window: &mut Window,
|
|
||||||
cx: &mut Context<Picker<Self>>,
|
|
||||||
) -> Task<()> {
|
|
||||||
let Some(workspace) = self.workspace.upgrade() else {
|
|
||||||
return Task::ready(());
|
|
||||||
};
|
|
||||||
|
|
||||||
let search_task = self.search(query, Arc::<AtomicBool>::default(), &workspace, cx);
|
|
||||||
|
|
||||||
cx.spawn(|this, mut cx| async move {
|
|
||||||
let mut paths = search_task.await;
|
|
||||||
let empty_path = Path::new("");
|
|
||||||
paths.retain(|path_match| path_match.path.as_ref() != empty_path);
|
|
||||||
|
|
||||||
this.update(&mut cx, |this, _cx| {
|
|
||||||
this.delegate.matches = paths;
|
|
||||||
})
|
|
||||||
.log_err();
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn confirm(&mut self, _secondary: bool, window: &mut Window, cx: &mut Context<Picker<Self>>) {
|
|
||||||
let Some(mat) = self.matches.get(self.selected_index) else {
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
|
|
||||||
let project_path = ProjectPath {
|
|
||||||
worktree_id: WorktreeId::from_usize(mat.worktree_id),
|
|
||||||
path: mat.path.clone(),
|
|
||||||
};
|
|
||||||
|
|
||||||
let Some(task) = self
|
|
||||||
.context_store
|
|
||||||
.update(cx, |context_store, cx| {
|
|
||||||
context_store.add_directory(project_path, cx)
|
|
||||||
})
|
|
||||||
.ok()
|
|
||||||
else {
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
|
|
||||||
let confirm_behavior = self.confirm_behavior;
|
|
||||||
cx.spawn_in(window, |this, mut cx| async move {
|
|
||||||
match task.await.notify_async_err(&mut cx) {
|
|
||||||
None => anyhow::Ok(()),
|
|
||||||
Some(()) => this.update_in(&mut cx, |this, window, cx| match confirm_behavior {
|
|
||||||
ConfirmBehavior::KeepOpen => {}
|
|
||||||
ConfirmBehavior::Close => this.delegate.dismissed(window, cx),
|
|
||||||
}),
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.detach_and_log_err(cx);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn dismissed(&mut self, _window: &mut Window, cx: &mut Context<Picker<Self>>) {
|
|
||||||
self.context_picker
|
|
||||||
.update(cx, |_, cx| {
|
|
||||||
cx.emit(DismissEvent);
|
|
||||||
})
|
|
||||||
.ok();
|
|
||||||
}
|
|
||||||
|
|
||||||
fn render_match(
|
|
||||||
&self,
|
|
||||||
ix: usize,
|
|
||||||
selected: bool,
|
|
||||||
_window: &mut Window,
|
|
||||||
cx: &mut Context<Picker<Self>>,
|
|
||||||
) -> Option<Self::ListItem> {
|
|
||||||
let path_match = &self.matches[ix];
|
|
||||||
let directory_name = path_match.path.to_string_lossy().to_string();
|
|
||||||
|
|
||||||
let added = self.context_store.upgrade().map_or(false, |context_store| {
|
|
||||||
context_store
|
|
||||||
.read(cx)
|
|
||||||
.includes_directory(&path_match.path)
|
|
||||||
.is_some()
|
|
||||||
});
|
|
||||||
|
|
||||||
Some(
|
|
||||||
ListItem::new(ix)
|
|
||||||
.inset(true)
|
|
||||||
.toggle_state(selected)
|
|
||||||
.start_slot(
|
|
||||||
Icon::new(IconName::Folder)
|
|
||||||
.size(IconSize::XSmall)
|
|
||||||
.color(Color::Muted),
|
|
||||||
)
|
|
||||||
.child(Label::new(directory_name))
|
|
||||||
.when(added, |el| {
|
|
||||||
el.end_slot(
|
|
||||||
h_flex()
|
|
||||||
.gap_1()
|
|
||||||
.child(
|
|
||||||
Icon::new(IconName::Check)
|
|
||||||
.size(IconSize::Small)
|
|
||||||
.color(Color::Success),
|
|
||||||
)
|
|
||||||
.child(Label::new("Added").size(LabelSize::Small)),
|
|
||||||
)
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -99,7 +99,6 @@ impl FileContextPickerDelegate {
|
||||||
query: String,
|
query: String,
|
||||||
cancellation_flag: Arc<AtomicBool>,
|
cancellation_flag: Arc<AtomicBool>,
|
||||||
workspace: &Entity<Workspace>,
|
workspace: &Entity<Workspace>,
|
||||||
|
|
||||||
cx: &mut Context<Picker<Self>>,
|
cx: &mut Context<Picker<Self>>,
|
||||||
) -> Task<Vec<PathMatch>> {
|
) -> Task<Vec<PathMatch>> {
|
||||||
if query.is_empty() {
|
if query.is_empty() {
|
||||||
|
@ -124,14 +123,14 @@ impl FileContextPickerDelegate {
|
||||||
let file_matches = project.worktrees(cx).flat_map(|worktree| {
|
let file_matches = project.worktrees(cx).flat_map(|worktree| {
|
||||||
let worktree = worktree.read(cx);
|
let worktree = worktree.read(cx);
|
||||||
let path_prefix: Arc<str> = worktree.root_name().into();
|
let path_prefix: Arc<str> = worktree.root_name().into();
|
||||||
worktree.files(false, 0).map(move |entry| PathMatch {
|
worktree.entries(false, 0).map(move |entry| PathMatch {
|
||||||
score: 0.,
|
score: 0.,
|
||||||
positions: Vec::new(),
|
positions: Vec::new(),
|
||||||
worktree_id: worktree.id().to_usize(),
|
worktree_id: worktree.id().to_usize(),
|
||||||
path: entry.path.clone(),
|
path: entry.path.clone(),
|
||||||
path_prefix: path_prefix.clone(),
|
path_prefix: path_prefix.clone(),
|
||||||
distance_to_relative_ancestor: 0,
|
distance_to_relative_ancestor: 0,
|
||||||
is_dir: false,
|
is_dir: entry.is_dir(),
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -149,7 +148,7 @@ impl FileContextPickerDelegate {
|
||||||
.root_entry()
|
.root_entry()
|
||||||
.map_or(false, |entry| entry.is_ignored),
|
.map_or(false, |entry| entry.is_ignored),
|
||||||
include_root_name: true,
|
include_root_name: true,
|
||||||
candidates: project::Candidates::Files,
|
candidates: project::Candidates::Entries,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
@ -192,7 +191,7 @@ impl PickerDelegate for FileContextPickerDelegate {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn placeholder_text(&self, _window: &mut Window, _cx: &mut App) -> Arc<str> {
|
fn placeholder_text(&self, _window: &mut Window, _cx: &mut App) -> Arc<str> {
|
||||||
"Search files…".into()
|
"Search files & directories…".into()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update_matches(
|
fn update_matches(
|
||||||
|
@ -223,13 +222,11 @@ impl PickerDelegate for FileContextPickerDelegate {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
let Some(file_name) = mat
|
let file_name = mat
|
||||||
.path
|
.path
|
||||||
.file_name()
|
.file_name()
|
||||||
.map(|os_str| os_str.to_string_lossy().into_owned())
|
.map(|os_str| os_str.to_string_lossy().into_owned())
|
||||||
else {
|
.unwrap_or(mat.path_prefix.to_string());
|
||||||
return;
|
|
||||||
};
|
|
||||||
|
|
||||||
let full_path = mat.path.display().to_string();
|
let full_path = mat.path.display().to_string();
|
||||||
|
|
||||||
|
@ -238,6 +235,8 @@ impl PickerDelegate for FileContextPickerDelegate {
|
||||||
path: mat.path.clone(),
|
path: mat.path.clone(),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let is_directory = mat.is_dir;
|
||||||
|
|
||||||
let Some(editor_entity) = self.editor.upgrade() else {
|
let Some(editor_entity) = self.editor.upgrade() else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
@ -288,8 +287,12 @@ impl PickerDelegate for FileContextPickerDelegate {
|
||||||
|
|
||||||
editor.insert("\n", window, cx); // Needed to end the fold
|
editor.insert("\n", window, cx); // Needed to end the fold
|
||||||
|
|
||||||
let file_icon = FileIcons::get_icon(&Path::new(&full_path), cx)
|
let file_icon = if is_directory {
|
||||||
.unwrap_or_else(|| SharedString::new(""));
|
FileIcons::get_folder_icon(false, cx)
|
||||||
|
} else {
|
||||||
|
FileIcons::get_icon(&Path::new(&full_path), cx)
|
||||||
|
}
|
||||||
|
.unwrap_or_else(|| SharedString::new(""));
|
||||||
|
|
||||||
let placeholder = FoldPlaceholder {
|
let placeholder = FoldPlaceholder {
|
||||||
render: render_fold_icon_button(
|
render: render_fold_icon_button(
|
||||||
|
@ -330,7 +333,11 @@ impl PickerDelegate for FileContextPickerDelegate {
|
||||||
let Some(task) = self
|
let Some(task) = self
|
||||||
.context_store
|
.context_store
|
||||||
.update(cx, |context_store, cx| {
|
.update(cx, |context_store, cx| {
|
||||||
context_store.add_file_from_path(project_path, cx)
|
if is_directory {
|
||||||
|
context_store.add_directory(project_path, cx)
|
||||||
|
} else {
|
||||||
|
context_store.add_file_from_path(project_path, cx)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.ok()
|
.ok()
|
||||||
else {
|
else {
|
||||||
|
@ -375,6 +382,7 @@ impl PickerDelegate for FileContextPickerDelegate {
|
||||||
ElementId::NamedInteger("file-ctx-picker".into(), ix),
|
ElementId::NamedInteger("file-ctx-picker".into(), ix),
|
||||||
&path_match.path,
|
&path_match.path,
|
||||||
&path_match.path_prefix,
|
&path_match.path_prefix,
|
||||||
|
path_match.is_dir,
|
||||||
self.context_store.clone(),
|
self.context_store.clone(),
|
||||||
cx,
|
cx,
|
||||||
)),
|
)),
|
||||||
|
@ -386,6 +394,7 @@ pub fn render_file_context_entry(
|
||||||
id: ElementId,
|
id: ElementId,
|
||||||
path: &Path,
|
path: &Path,
|
||||||
path_prefix: &Arc<str>,
|
path_prefix: &Arc<str>,
|
||||||
|
is_directory: bool,
|
||||||
context_store: WeakEntity<ContextStore>,
|
context_store: WeakEntity<ContextStore>,
|
||||||
cx: &App,
|
cx: &App,
|
||||||
) -> Stateful<Div> {
|
) -> Stateful<Div> {
|
||||||
|
@ -409,13 +418,24 @@ pub fn render_file_context_entry(
|
||||||
(file_name, Some(directory))
|
(file_name, Some(directory))
|
||||||
};
|
};
|
||||||
|
|
||||||
let added = context_store
|
let added = context_store.upgrade().and_then(|context_store| {
|
||||||
.upgrade()
|
if is_directory {
|
||||||
.and_then(|context_store| context_store.read(cx).will_include_file_path(path, cx));
|
context_store
|
||||||
|
.read(cx)
|
||||||
|
.includes_directory(path)
|
||||||
|
.map(FileInclusion::Direct)
|
||||||
|
} else {
|
||||||
|
context_store.read(cx).will_include_file_path(path, cx)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
let file_icon = FileIcons::get_icon(&path, cx)
|
let file_icon = if is_directory {
|
||||||
.map(Icon::from_path)
|
FileIcons::get_folder_icon(false, cx)
|
||||||
.unwrap_or_else(|| Icon::new(IconName::File));
|
} else {
|
||||||
|
FileIcons::get_icon(&path, cx)
|
||||||
|
}
|
||||||
|
.map(Icon::from_path)
|
||||||
|
.unwrap_or_else(|| Icon::new(IconName::File));
|
||||||
|
|
||||||
h_flex()
|
h_flex()
|
||||||
.id(id)
|
.id(id)
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue