Eliminate GPUI View, ViewContext, and WindowContext types (#22632)

There's still a bit more work to do on this, but this PR is compiling
(with warnings) after eliminating the key types. When the tasks below
are complete, this will be the new narrative for GPUI:

- `Entity<T>` - This replaces `View<T>`/`Model<T>`. It represents a unit
of state, and if `T` implements `Render`, then `Entity<T>` implements
`Element`.
- `&mut App` This replaces `AppContext` and represents the app.
- `&mut Context<T>` This replaces `ModelContext` and derefs to `App`. It
is provided by the framework when updating an entity.
- `&mut Window` Broken out of `&mut WindowContext` which no longer
exists. Every method that once took `&mut WindowContext` now takes `&mut
Window, &mut App` and every method that took `&mut ViewContext<T>` now
takes `&mut Window, &mut Context<T>`

Not pictured here are the two other failed attempts. It's been quite a
month!

Tasks:

- [x] Remove `View`, `ViewContext`, `WindowContext` and thread through
`Window`
- [x] [@cole-miller @mikayla-maki] Redraw window when entities change
- [x] [@cole-miller @mikayla-maki] Get examples and Zed running
- [x] [@cole-miller @mikayla-maki] Fix Zed rendering
- [x] [@mikayla-maki] Fix todo! macros and comments
- [x] Fix a bug where the editor would not be redrawn because of view
caching
- [x] remove publicness window.notify() and replace with
`AppContext::notify`
- [x] remove `observe_new_window_models`, replace with
`observe_new_models` with an optional window
- [x] Fix a bug where the project panel would not be redrawn because of
the wrong refresh() call being used
- [x] Fix the tests
- [x] Fix warnings by eliminating `Window` params or using `_`
- [x] Fix conflicts
- [x] Simplify generic code where possible
- [x] Rename types
- [ ] Update docs

### issues post merge

- [x] Issues switching between normal and insert mode
- [x] Assistant re-rendering failure
- [x] Vim test failures
- [x] Mac build issue



Release Notes:

- N/A

---------

Co-authored-by: Antonio Scandurra <me@as-cii.com>
Co-authored-by: Cole Miller <cole@zed.dev>
Co-authored-by: Mikayla <mikayla@zed.dev>
Co-authored-by: Joseph <joseph@zed.dev>
Co-authored-by: max <max@zed.dev>
Co-authored-by: Michael Sloan <michael@zed.dev>
Co-authored-by: Mikayla Maki <mikaylamaki@Mikaylas-MacBook-Pro.local>
Co-authored-by: Mikayla <mikayla.c.maki@gmail.com>
Co-authored-by: joão <joao@zed.dev>
This commit is contained in:
Nathan Sobo 2025-01-25 20:02:45 -07:00 committed by GitHub
parent 21b4a0d50e
commit 6fca1d2b0b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
648 changed files with 36248 additions and 28208 deletions

View file

@ -14,9 +14,9 @@ use file_finder_settings::{FileFinderSettings, FileFinderWidth};
use file_icons::FileIcons;
use fuzzy::{CharBag, PathMatch, PathMatchCandidate};
use gpui::{
actions, Action, AnyElement, AppContext, DismissEvent, EventEmitter, FocusHandle,
FocusableView, KeyContext, Model, Modifiers, ModifiersChangedEvent, ParentElement, Render,
Styled, Task, View, ViewContext, VisualContext, WeakView,
actions, Action, AnyElement, App, Context, DismissEvent, Entity, EventEmitter, FocusHandle,
Focusable, KeyContext, Modifiers, ModifiersChangedEvent, ParentElement, Render, Styled, Task,
WeakEntity, Window,
};
use new_path_prompt::NewPathPrompt;
use open_path_prompt::OpenPathPrompt;
@ -45,52 +45,63 @@ use workspace::{
actions!(file_finder, [SelectPrev, ToggleMenu]);
impl ModalView for FileFinder {
fn on_before_dismiss(&mut self, cx: &mut ViewContext<Self>) -> workspace::DismissDecision {
fn on_before_dismiss(
&mut self,
window: &mut Window,
cx: &mut Context<Self>,
) -> workspace::DismissDecision {
let submenu_focused = self.picker.update(cx, |picker, cx| {
picker.delegate.popover_menu_handle.is_focused(cx)
picker.delegate.popover_menu_handle.is_focused(window, cx)
});
workspace::DismissDecision::Dismiss(!submenu_focused)
}
}
pub struct FileFinder {
picker: View<Picker<FileFinderDelegate>>,
picker: Entity<Picker<FileFinderDelegate>>,
picker_focus_handle: FocusHandle,
init_modifiers: Option<Modifiers>,
}
pub fn init_settings(cx: &mut AppContext) {
pub fn init_settings(cx: &mut App) {
FileFinderSettings::register(cx);
}
pub fn init(cx: &mut AppContext) {
pub fn init(cx: &mut App) {
init_settings(cx);
cx.observe_new_views(FileFinder::register).detach();
cx.observe_new_views(NewPathPrompt::register).detach();
cx.observe_new_views(OpenPathPrompt::register).detach();
cx.observe_new(FileFinder::register).detach();
cx.observe_new(NewPathPrompt::register).detach();
cx.observe_new(OpenPathPrompt::register).detach();
}
impl FileFinder {
fn register(workspace: &mut Workspace, _: &mut ViewContext<Workspace>) {
workspace.register_action(|workspace, action: &workspace::ToggleFileFinder, cx| {
let Some(file_finder) = workspace.active_modal::<Self>(cx) else {
Self::open(workspace, action.separate_history, cx).detach();
return;
};
fn register(
workspace: &mut Workspace,
_window: Option<&mut Window>,
_: &mut Context<Workspace>,
) {
workspace.register_action(
|workspace, action: &workspace::ToggleFileFinder, window, cx| {
let Some(file_finder) = workspace.active_modal::<Self>(cx) else {
Self::open(workspace, action.separate_history, window, cx).detach();
return;
};
file_finder.update(cx, |file_finder, cx| {
file_finder.init_modifiers = Some(cx.modifiers());
file_finder.picker.update(cx, |picker, cx| {
picker.cycle_selection(cx);
file_finder.update(cx, |file_finder, cx| {
file_finder.init_modifiers = Some(window.modifiers());
file_finder.picker.update(cx, |picker, cx| {
picker.cycle_selection(window, cx);
});
});
});
});
},
);
}
fn open(
workspace: &mut Workspace,
separate_history: bool,
cx: &mut ViewContext<Workspace>,
window: &mut Window,
cx: &mut Context<Workspace>,
) -> Task<()> {
let project = workspace.project().read(cx);
let fs = project.fs();
@ -130,33 +141,34 @@ impl FileFinder {
}
})
.collect::<Vec<_>>();
cx.spawn(move |workspace, mut cx| async move {
cx.spawn_in(window, move |workspace, mut cx| async move {
let history_items = join_all(history_items).await.into_iter().flatten();
workspace
.update(&mut cx, |workspace, cx| {
.update_in(&mut cx, |workspace, window, cx| {
let project = workspace.project().clone();
let weak_workspace = cx.view().downgrade();
workspace.toggle_modal(cx, |cx| {
let weak_workspace = cx.model().downgrade();
workspace.toggle_modal(window, cx, |window, cx| {
let delegate = FileFinderDelegate::new(
cx.view().downgrade(),
cx.model().downgrade(),
weak_workspace,
project,
currently_opened_path,
history_items.collect(),
separate_history,
window,
cx,
);
FileFinder::new(delegate, cx)
FileFinder::new(delegate, window, cx)
});
})
.ok();
})
}
fn new(delegate: FileFinderDelegate, cx: &mut ViewContext<Self>) -> Self {
let picker = cx.new_view(|cx| Picker::uniform_list(delegate, cx));
fn new(delegate: FileFinderDelegate, window: &mut Window, cx: &mut Context<Self>) -> Self {
let picker = cx.new(|cx| Picker::uniform_list(delegate, window, cx));
let picker_focus_handle = picker.focus_handle(cx);
picker.update(cx, |picker, _| {
picker.delegate.focus_handle = picker_focus_handle.clone();
@ -164,14 +176,15 @@ impl FileFinder {
Self {
picker,
picker_focus_handle,
init_modifiers: cx.modifiers().modified().then_some(cx.modifiers()),
init_modifiers: window.modifiers().modified().then_some(window.modifiers()),
}
}
fn handle_modifiers_changed(
&mut self,
event: &ModifiersChangedEvent,
cx: &mut ViewContext<Self>,
window: &mut Window,
cx: &mut Context<Self>,
) {
let Some(init_modifiers) = self.init_modifiers.take() else {
return;
@ -179,47 +192,68 @@ impl FileFinder {
if self.picker.read(cx).delegate.has_changed_selected_index {
if !event.modified() || !init_modifiers.is_subset_of(&event) {
self.init_modifiers = None;
cx.dispatch_action(menu::Confirm.boxed_clone());
window.dispatch_action(menu::Confirm.boxed_clone(), cx);
}
}
}
fn handle_select_prev(&mut self, _: &SelectPrev, cx: &mut ViewContext<Self>) {
self.init_modifiers = Some(cx.modifiers());
cx.dispatch_action(Box::new(menu::SelectPrev));
fn handle_select_prev(&mut self, _: &SelectPrev, window: &mut Window, cx: &mut Context<Self>) {
self.init_modifiers = Some(window.modifiers());
window.dispatch_action(Box::new(menu::SelectPrev), cx);
}
fn handle_toggle_menu(&mut self, _: &ToggleMenu, cx: &mut ViewContext<Self>) {
fn handle_toggle_menu(&mut self, _: &ToggleMenu, window: &mut Window, cx: &mut Context<Self>) {
self.picker.update(cx, |picker, cx| {
let menu_handle = &picker.delegate.popover_menu_handle;
if menu_handle.is_deployed() {
menu_handle.hide(cx);
} else {
menu_handle.show(cx);
menu_handle.show(window, cx);
}
});
}
fn go_to_file_split_left(&mut self, _: &pane::SplitLeft, cx: &mut ViewContext<Self>) {
self.go_to_file_split_inner(SplitDirection::Left, cx)
fn go_to_file_split_left(
&mut self,
_: &pane::SplitLeft,
window: &mut Window,
cx: &mut Context<Self>,
) {
self.go_to_file_split_inner(SplitDirection::Left, window, cx)
}
fn go_to_file_split_right(&mut self, _: &pane::SplitRight, cx: &mut ViewContext<Self>) {
self.go_to_file_split_inner(SplitDirection::Right, cx)
fn go_to_file_split_right(
&mut self,
_: &pane::SplitRight,
window: &mut Window,
cx: &mut Context<Self>,
) {
self.go_to_file_split_inner(SplitDirection::Right, window, cx)
}
fn go_to_file_split_up(&mut self, _: &pane::SplitUp, cx: &mut ViewContext<Self>) {
self.go_to_file_split_inner(SplitDirection::Up, cx)
fn go_to_file_split_up(
&mut self,
_: &pane::SplitUp,
window: &mut Window,
cx: &mut Context<Self>,
) {
self.go_to_file_split_inner(SplitDirection::Up, window, cx)
}
fn go_to_file_split_down(&mut self, _: &pane::SplitDown, cx: &mut ViewContext<Self>) {
self.go_to_file_split_inner(SplitDirection::Down, cx)
fn go_to_file_split_down(
&mut self,
_: &pane::SplitDown,
window: &mut Window,
cx: &mut Context<Self>,
) {
self.go_to_file_split_inner(SplitDirection::Down, window, cx)
}
fn go_to_file_split_inner(
&mut self,
split_direction: SplitDirection,
cx: &mut ViewContext<Self>,
window: &mut Window,
cx: &mut Context<Self>,
) {
self.picker.update(cx, |picker, cx| {
let delegate = &mut picker.delegate;
@ -239,7 +273,7 @@ impl FileFinder {
},
};
let open_task = workspace.update(cx, move |workspace, cx| {
workspace.split_path_preview(path, false, Some(split_direction), cx)
workspace.split_path_preview(path, false, Some(split_direction), window, cx)
});
open_task.detach_and_log_err(cx);
}
@ -247,11 +281,8 @@ impl FileFinder {
})
}
pub fn modal_max_width(
width_setting: Option<FileFinderWidth>,
cx: &mut ViewContext<Self>,
) -> Pixels {
let window_width = cx.viewport_size().width;
pub fn modal_max_width(width_setting: Option<FileFinderWidth>, window: &mut Window) -> Pixels {
let window_width = window.viewport_size().width;
let small_width = Pixels(545.);
match width_setting {
@ -266,18 +297,18 @@ impl FileFinder {
impl EventEmitter<DismissEvent> for FileFinder {}
impl FocusableView for FileFinder {
fn focus_handle(&self, _: &AppContext) -> FocusHandle {
impl Focusable for FileFinder {
fn focus_handle(&self, _: &App) -> FocusHandle {
self.picker_focus_handle.clone()
}
}
impl Render for FileFinder {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
let key_context = self.picker.read(cx).delegate.key_context(cx);
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
let key_context = self.picker.read(cx).delegate.key_context(window, cx);
let file_finder_settings = FileFinderSettings::get_global(cx);
let modal_max_width = Self::modal_max_width(file_finder_settings.modal_max_width, cx);
let modal_max_width = Self::modal_max_width(file_finder_settings.modal_max_width, window);
v_flex()
.key_context(key_context)
@ -294,9 +325,9 @@ impl Render for FileFinder {
}
pub struct FileFinderDelegate {
file_finder: WeakView<FileFinder>,
workspace: WeakView<Workspace>,
project: Model<Project>,
file_finder: WeakEntity<FileFinder>,
workspace: WeakEntity<Workspace>,
project: Entity<Project>,
search_count: usize,
latest_search_id: usize,
latest_search_did_cancel: bool,
@ -613,16 +644,18 @@ impl FileSearchQuery {
}
impl FileFinderDelegate {
#[allow(clippy::too_many_arguments)]
fn new(
file_finder: WeakView<FileFinder>,
workspace: WeakView<Workspace>,
project: Model<Project>,
file_finder: WeakEntity<FileFinder>,
workspace: WeakEntity<Workspace>,
project: Entity<Project>,
currently_opened_path: Option<FoundPath>,
history_items: Vec<FoundPath>,
separate_history: bool,
cx: &mut ViewContext<FileFinder>,
window: &mut Window,
cx: &mut Context<FileFinder>,
) -> Self {
Self::subscribe_to_updates(&project, cx);
Self::subscribe_to_updates(&project, window, cx);
Self {
file_finder,
workspace,
@ -644,14 +677,18 @@ impl FileFinderDelegate {
}
}
fn subscribe_to_updates(project: &Model<Project>, cx: &mut ViewContext<FileFinder>) {
cx.subscribe(project, |file_finder, _, event, cx| {
fn subscribe_to_updates(
project: &Entity<Project>,
window: &mut Window,
cx: &mut Context<FileFinder>,
) {
cx.subscribe_in(project, window, |file_finder, _, event, window, cx| {
match event {
project::Event::WorktreeUpdatedEntries(_, _)
| project::Event::WorktreeAdded(_)
| project::Event::WorktreeRemoved(_) => file_finder
.picker
.update(cx, |picker, cx| picker.refresh(cx)),
.update(cx, |picker, cx| picker.refresh(window, cx)),
_ => {}
};
})
@ -661,7 +698,8 @@ impl FileFinderDelegate {
fn spawn_search(
&mut self,
query: FileSearchQuery,
cx: &mut ViewContext<Picker<Self>>,
window: &mut Window,
cx: &mut Context<Picker<Self>>,
) -> Task<()> {
let relative_to = self
.currently_opened_path
@ -692,7 +730,7 @@ impl FileFinderDelegate {
self.cancel_flag.store(true, atomic::Ordering::Relaxed);
self.cancel_flag = Arc::new(AtomicBool::new(false));
let cancel_flag = self.cancel_flag.clone();
cx.spawn(|picker, mut cx| async move {
cx.spawn_in(window, |picker, mut cx| async move {
let matches = fuzzy::match_path_sets(
candidate_sets.as_slice(),
query.path_query(),
@ -722,7 +760,8 @@ impl FileFinderDelegate {
did_cancel: bool,
query: FileSearchQuery,
matches: impl IntoIterator<Item = ProjectPanelOrdMatch>,
cx: &mut ViewContext<Picker<Self>>,
cx: &mut Context<Picker<Self>>,
) {
if search_id >= self.latest_search_id {
self.latest_search_id = search_id;
@ -766,7 +805,7 @@ impl FileFinderDelegate {
fn labels_for_match(
&self,
path_match: &Match,
cx: &AppContext,
cx: &App,
ix: usize,
) -> (String, Vec<usize>, String, Vec<usize>) {
let (file_name, file_name_positions, full_path, full_path_positions) = match &path_match {
@ -884,9 +923,10 @@ impl FileFinderDelegate {
fn lookup_absolute_path(
&self,
query: FileSearchQuery,
cx: &mut ViewContext<Picker<Self>>,
window: &mut Window,
cx: &mut Context<Picker<Self>>,
) -> Task<()> {
cx.spawn(|picker, mut cx| async move {
cx.spawn_in(window, |picker, mut cx| async move {
let Some(project) = picker
.update(&mut cx, |picker, _| picker.delegate.project.clone())
.log_err()
@ -929,7 +969,7 @@ impl FileFinderDelegate {
}
picker
.update(&mut cx, |picker, cx| {
.update_in(&mut cx, |picker, _, cx| {
let picker_delegate = &mut picker.delegate;
let search_id = util::post_inc(&mut picker_delegate.search_count);
picker_delegate.set_search_matches(search_id, false, query, path_matches, cx);
@ -954,10 +994,10 @@ impl FileFinderDelegate {
0
}
fn key_context(&self, cx: &WindowContext) -> KeyContext {
fn key_context(&self, window: &Window, cx: &App) -> KeyContext {
let mut key_context = KeyContext::new_with_defaults();
key_context.add("FileFinder");
if self.popover_menu_handle.is_focused(cx) {
if self.popover_menu_handle.is_focused(window, cx) {
key_context.add("menu_open");
}
key_context
@ -967,7 +1007,7 @@ impl FileFinderDelegate {
impl PickerDelegate for FileFinderDelegate {
type ListItem = ListItem;
fn placeholder_text(&self, _cx: &mut WindowContext) -> Arc<str> {
fn placeholder_text(&self, _window: &mut Window, _cx: &mut App) -> Arc<str> {
"Search project files...".into()
}
@ -979,7 +1019,7 @@ impl PickerDelegate for FileFinderDelegate {
self.selected_index
}
fn set_selected_index(&mut self, ix: usize, cx: &mut ViewContext<Picker<Self>>) {
fn set_selected_index(&mut self, ix: usize, _: &mut Window, cx: &mut Context<Picker<Self>>) {
self.has_changed_selected_index = true;
self.selected_index = ix;
cx.notify();
@ -1006,7 +1046,8 @@ impl PickerDelegate for FileFinderDelegate {
fn update_matches(
&mut self,
raw_query: String,
cx: &mut ViewContext<Picker<Self>>,
window: &mut Window,
cx: &mut Context<Picker<Self>>,
) -> Task<()> {
let raw_query = raw_query.replace(' ', "");
let raw_query = raw_query.trim();
@ -1057,31 +1098,44 @@ impl PickerDelegate for FileFinderDelegate {
};
if Path::new(query.path_query()).is_absolute() {
self.lookup_absolute_path(query, cx)
self.lookup_absolute_path(query, window, cx)
} else {
self.spawn_search(query, cx)
self.spawn_search(query, window, cx)
}
}
}
fn confirm(&mut self, secondary: bool, cx: &mut ViewContext<Picker<FileFinderDelegate>>) {
fn confirm(
&mut self,
secondary: bool,
window: &mut Window,
cx: &mut Context<Picker<FileFinderDelegate>>,
) {
if let Some(m) = self.matches.get(self.selected_index()) {
if let Some(workspace) = self.workspace.upgrade() {
let open_task = workspace.update(cx, move |workspace, cx| {
let open_task = workspace.update(cx, |workspace, cx| {
let split_or_open =
|workspace: &mut Workspace,
project_path,
cx: &mut ViewContext<Workspace>| {
window: &mut Window,
cx: &mut Context<Workspace>| {
let allow_preview =
PreviewTabsSettings::get_global(cx).enable_preview_from_file_finder;
if secondary {
workspace.split_path_preview(project_path, allow_preview, None, cx)
workspace.split_path_preview(
project_path,
allow_preview,
None,
window,
cx,
)
} else {
workspace.open_path_preview(
project_path,
None,
true,
allow_preview,
window,
cx,
)
}
@ -1101,6 +1155,7 @@ impl PickerDelegate for FileFinderDelegate {
worktree_id,
path: Arc::clone(&path.project.path),
},
window,
cx,
)
} else {
@ -1110,12 +1165,14 @@ impl PickerDelegate for FileFinderDelegate {
workspace.split_abs_path(
abs_path.to_path_buf(),
false,
window,
cx,
)
} else {
workspace.open_abs_path(
abs_path.to_path_buf(),
false,
window,
cx,
)
}
@ -1126,6 +1183,7 @@ impl PickerDelegate for FileFinderDelegate {
worktree_id,
path: Arc::clone(&path.project.path),
},
window,
cx,
),
}
@ -1137,6 +1195,7 @@ impl PickerDelegate for FileFinderDelegate {
worktree_id: WorktreeId::from_usize(m.0.worktree_id),
path: m.0.path.clone(),
},
window,
cx,
),
}
@ -1155,14 +1214,18 @@ impl PickerDelegate for FileFinderDelegate {
.saturating_sub(1);
let finder = self.file_finder.clone();
cx.spawn(|_, mut cx| async move {
cx.spawn_in(window, |_, mut cx| async move {
let item = open_task.await.notify_async_err(&mut cx)?;
if let Some(row) = row {
if let Some(active_editor) = item.downcast::<Editor>() {
active_editor
.downgrade()
.update(&mut cx, |editor, cx| {
editor.go_to_singleton_buffer_point(Point::new(row, col), cx);
.update_in(&mut cx, |editor, window, cx| {
editor.go_to_singleton_buffer_point(
Point::new(row, col),
window,
cx,
);
})
.log_err();
}
@ -1176,7 +1239,7 @@ impl PickerDelegate for FileFinderDelegate {
}
}
fn dismissed(&mut self, cx: &mut ViewContext<Picker<FileFinderDelegate>>) {
fn dismissed(&mut self, _: &mut Window, cx: &mut Context<Picker<FileFinderDelegate>>) {
self.file_finder
.update(cx, |_, cx| cx.emit(DismissEvent))
.log_err();
@ -1186,7 +1249,8 @@ impl PickerDelegate for FileFinderDelegate {
&self,
ix: usize,
selected: bool,
cx: &mut ViewContext<Picker<Self>>,
_: &mut Window,
cx: &mut Context<Picker<Self>>,
) -> Option<Self::ListItem> {
let settings = FileFinderSettings::get_global(cx);
@ -1237,7 +1301,11 @@ impl PickerDelegate for FileFinderDelegate {
)
}
fn render_footer(&self, cx: &mut ViewContext<Picker<Self>>) -> Option<AnyElement> {
fn render_footer(
&self,
window: &mut Window,
cx: &mut Context<Picker<Self>>,
) -> Option<AnyElement> {
let context = self.focus_handle.clone();
Some(
h_flex()
@ -1249,8 +1317,10 @@ impl PickerDelegate for FileFinderDelegate {
.border_color(cx.theme().colors().border_variant)
.child(
Button::new("open-selection", "Open")
.key_binding(KeyBinding::for_action(&menu::Confirm, cx))
.on_click(|_, cx| cx.dispatch_action(menu::Confirm.boxed_clone())),
.key_binding(KeyBinding::for_action(&menu::Confirm, window))
.on_click(|_, window, cx| {
window.dispatch_action(menu::Confirm.boxed_clone(), cx)
}),
)
.child(
PopoverMenu::new("menu-popover")
@ -1260,13 +1330,17 @@ impl PickerDelegate for FileFinderDelegate {
.trigger(
Button::new("actions-trigger", "Split Options")
.selected_label_color(Color::Accent)
.key_binding(KeyBinding::for_action_in(&ToggleMenu, &context, cx)),
.key_binding(KeyBinding::for_action_in(
&ToggleMenu,
&context,
window,
)),
)
.menu({
move |cx| {
Some(ContextMenu::build(cx, {
move |window, cx| {
Some(ContextMenu::build(window, cx, {
let context = context.clone();
move |menu, _| {
move |menu, _, _| {
menu.context(context)
.action("Split Left", pane::SplitLeft.boxed_clone())
.action("Split Right", pane::SplitRight.boxed_clone())

View file

@ -26,7 +26,7 @@ impl Settings for FileFinderSettings {
type FileContent = FileFinderSettingsContent;
fn load(sources: SettingsSources<Self::FileContent>, _: &mut gpui::AppContext) -> Result<Self> {
fn load(sources: SettingsSources<Self::FileContent>, _: &mut gpui::App) -> Result<Self> {
sources.json_merge()
}
}

View file

@ -57,10 +57,10 @@ async fn test_matching_paths(cx: &mut TestAppContext) {
"a bandana",
] {
picker
.update(cx, |picker, cx| {
.update_in(cx, |picker, window, cx| {
picker
.delegate
.update_matches(bandana_query.to_string(), cx)
.update_matches(bandana_query.to_string(), window, cx)
})
.await;
picker.update(cx, |picker, _| {
@ -108,10 +108,10 @@ async fn test_absolute_paths(cx: &mut TestAppContext) {
let matching_abs_path = "/root/a/b/file2.txt";
picker
.update(cx, |picker, cx| {
.update_in(cx, |picker, window, cx| {
picker
.delegate
.update_matches(matching_abs_path.to_string(), cx)
.update_matches(matching_abs_path.to_string(), window, cx)
})
.await;
picker.update(cx, |picker, _| {
@ -130,10 +130,10 @@ async fn test_absolute_paths(cx: &mut TestAppContext) {
let mismatching_abs_path = "/root/a/b/file1.txt";
picker
.update(cx, |picker, cx| {
.update_in(cx, |picker, window, cx| {
picker
.delegate
.update_matches(mismatching_abs_path.to_string(), cx)
.update_matches(mismatching_abs_path.to_string(), window, cx)
})
.await;
picker.update(cx, |picker, _| {
@ -213,10 +213,10 @@ async fn test_row_column_numbers_query_inside_file(cx: &mut TestAppContext) {
assert!(file_column <= first_file_contents.len());
let query_inside_file = format!("{file_query}:{file_row}:{file_column}");
picker
.update(cx, |finder, cx| {
.update_in(cx, |finder, window, cx| {
finder
.delegate
.update_matches(query_inside_file.to_string(), cx)
.update_matches(query_inside_file.to_string(), window, cx)
})
.await;
picker.update(cx, |finder, _| {
@ -238,7 +238,7 @@ async fn test_row_column_numbers_query_inside_file(cx: &mut TestAppContext) {
cx.dispatch_action(SelectNext);
cx.dispatch_action(Confirm);
let editor = cx.update(|cx| workspace.read(cx).active_item_as::<Editor>(cx).unwrap());
let editor = cx.update(|_, cx| workspace.read(cx).active_item_as::<Editor>(cx).unwrap());
cx.executor().advance_clock(Duration::from_secs(2));
editor.update(cx, |editor, cx| {
@ -288,10 +288,10 @@ async fn test_row_column_numbers_query_outside_file(cx: &mut TestAppContext) {
assert!(file_column > first_file_contents.len());
let query_outside_file = format!("{file_query}:{file_row}:{file_column}");
picker
.update(cx, |picker, cx| {
.update_in(cx, |picker, window, cx| {
picker
.delegate
.update_matches(query_outside_file.to_string(), cx)
.update_matches(query_outside_file.to_string(), window, cx)
})
.await;
picker.update(cx, |finder, _| {
@ -313,7 +313,7 @@ async fn test_row_column_numbers_query_outside_file(cx: &mut TestAppContext) {
cx.dispatch_action(SelectNext);
cx.dispatch_action(Confirm);
let editor = cx.update(|cx| workspace.read(cx).active_item_as::<Editor>(cx).unwrap());
let editor = cx.update(|_, cx| workspace.read(cx).active_item_as::<Editor>(cx).unwrap());
cx.executor().advance_clock(Duration::from_secs(2));
editor.update(cx, |editor, cx| {
@ -359,8 +359,8 @@ async fn test_matching_cancellation(cx: &mut TestAppContext) {
let query = test_path_position("hi");
picker
.update(cx, |picker, cx| {
picker.delegate.spawn_search(query.clone(), cx)
.update_in(cx, |picker, window, cx| {
picker.delegate.spawn_search(query.clone(), window, cx)
})
.await;
@ -368,13 +368,13 @@ async fn test_matching_cancellation(cx: &mut TestAppContext) {
assert_eq!(picker.delegate.matches.len(), 5)
});
picker.update(cx, |picker, cx| {
picker.update_in(cx, |picker, window, cx| {
let matches = collect_search_matches(picker).search_matches_only();
let delegate = &mut picker.delegate;
// Simulate a search being cancelled after the time limit,
// returning only a subset of the matches that would have been found.
drop(delegate.spawn_search(query.clone(), cx));
drop(delegate.spawn_search(query.clone(), window, cx));
delegate.set_search_matches(
delegate.latest_search_id,
true, // did-cancel
@ -387,7 +387,7 @@ async fn test_matching_cancellation(cx: &mut TestAppContext) {
);
// Simulate another cancellation.
drop(delegate.spawn_search(query.clone(), cx));
drop(delegate.spawn_search(query.clone(), window, cx));
delegate.set_search_matches(
delegate.latest_search_id,
true, // did-cancel
@ -449,8 +449,10 @@ async fn test_ignored_root(cx: &mut TestAppContext) {
let (picker, _, cx) = build_find_picker(project, cx);
picker
.update(cx, |picker, cx| {
picker.delegate.spawn_search(test_path_position("hi"), cx)
.update_in(cx, |picker, window, cx| {
picker
.delegate
.spawn_search(test_path_position("hi"), window, cx)
})
.await;
picker.update(cx, |picker, _| assert_eq!(picker.delegate.matches.len(), 7));
@ -477,8 +479,10 @@ async fn test_single_file_worktrees(cx: &mut TestAppContext) {
// Even though there is only one worktree, that worktree's filename
// is included in the matching, because the worktree is a single file.
picker
.update(cx, |picker, cx| {
picker.delegate.spawn_search(test_path_position("thf"), cx)
.update_in(cx, |picker, window, cx| {
picker
.delegate
.spawn_search(test_path_position("thf"), window, cx)
})
.await;
cx.read(|cx| {
@ -498,8 +502,10 @@ async fn test_single_file_worktrees(cx: &mut TestAppContext) {
// Since the worktree root is a file, searching for its name followed by a slash does
// not match anything.
picker
.update(cx, |f, cx| {
f.delegate.spawn_search(test_path_position("thf/"), cx)
.update_in(cx, |picker, window, cx| {
picker
.delegate
.spawn_search(test_path_position("thf/"), window, cx)
})
.await;
picker.update(cx, |f, _| assert_eq!(f.delegate.matches.len(), 0));
@ -524,7 +530,7 @@ async fn test_path_distance_ordering(cx: &mut TestAppContext) {
.await;
let project = Project::test(app_state.fs.clone(), ["/root".as_ref()], cx).await;
let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
let (workspace, cx) = cx.add_window_view(|window, cx| Workspace::test_new(project, window, cx));
let worktree_id = cx.read(|cx| {
let worktrees = workspace.read(cx).worktrees(cx).collect::<Vec<_>>();
@ -540,15 +546,16 @@ async fn test_path_distance_ordering(cx: &mut TestAppContext) {
path: Arc::from(Path::new("dir2/b.txt")),
};
workspace
.update(cx, |workspace, cx| {
workspace.open_path(b_path, None, true, cx)
.update_in(cx, |workspace, window, cx| {
workspace.open_path(b_path, None, true, window, cx)
})
.await
.unwrap();
let finder = open_file_picker(&workspace, cx);
finder
.update(cx, |f, cx| {
f.delegate.spawn_search(test_path_position("a.txt"), cx)
.update_in(cx, |f, window, cx| {
f.delegate
.spawn_search(test_path_position("a.txt"), window, cx)
})
.await;
@ -580,8 +587,9 @@ async fn test_search_worktree_without_files(cx: &mut TestAppContext) {
let (picker, _workspace, cx) = build_find_picker(project, cx);
picker
.update(cx, |f, cx| {
f.delegate.spawn_search(test_path_position("dir"), cx)
.update_in(cx, |f, window, cx| {
f.delegate
.spawn_search(test_path_position("dir"), window, cx)
})
.await;
cx.read(|cx| {
@ -610,7 +618,7 @@ async fn test_query_history(cx: &mut gpui::TestAppContext) {
.await;
let project = Project::test(app_state.fs.clone(), ["/src".as_ref()], cx).await;
let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
let (workspace, cx) = cx.add_window_view(|window, cx| Workspace::test_new(project, window, cx));
let worktree_id = cx.read(|cx| {
let worktrees = workspace.read(cx).worktrees(cx).collect::<Vec<_>>();
assert_eq!(worktrees.len(), 1);
@ -623,7 +631,7 @@ async fn test_query_history(cx: &mut gpui::TestAppContext) {
//
// TODO: without closing, the opened items do not propagate their history changes for some reason
// it does work in real app though, only tests do not propagate.
workspace.update(cx, |_, cx| cx.focused());
workspace.update_in(cx, |_workspace, window, cx| window.focused(cx));
let initial_history = open_close_queried_buffer("fir", 1, "first.rs", &workspace, cx).await;
assert!(
@ -773,7 +781,7 @@ async fn test_external_files_history(cx: &mut gpui::TestAppContext) {
.detach();
cx.background_executor.run_until_parked();
let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
let (workspace, cx) = cx.add_window_view(|window, cx| Workspace::test_new(project, window, cx));
let worktree_id = cx.read(|cx| {
let worktrees = workspace.read(cx).worktrees(cx).collect::<Vec<_>>();
assert_eq!(worktrees.len(), 1,);
@ -781,8 +789,13 @@ async fn test_external_files_history(cx: &mut gpui::TestAppContext) {
WorktreeId::from_usize(worktrees[0].entity_id().as_u64() as usize)
});
workspace
.update(cx, |workspace, cx| {
workspace.open_abs_path(PathBuf::from("/external-src/test/third.rs"), false, cx)
.update_in(cx, |workspace, window, cx| {
workspace.open_abs_path(
PathBuf::from("/external-src/test/third.rs"),
false,
window,
cx,
)
})
.detach();
cx.background_executor.run_until_parked();
@ -863,7 +876,7 @@ async fn test_toggle_panel_new_selections(cx: &mut gpui::TestAppContext) {
.await;
let project = Project::test(app_state.fs.clone(), ["/src".as_ref()], cx).await;
let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
let (workspace, cx) = cx.add_window_view(|window, cx| Workspace::test_new(project, window, cx));
// generate some history to select from
open_close_queried_buffer("fir", 1, "first.rs", &workspace, cx).await;
@ -919,7 +932,7 @@ async fn test_search_preserves_history_items(cx: &mut gpui::TestAppContext) {
.await;
let project = Project::test(app_state.fs.clone(), ["/src".as_ref()], cx).await;
let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
let (workspace, cx) = cx.add_window_view(|window, cx| Workspace::test_new(project, window, cx));
let worktree_id = cx.read(|cx| {
let worktrees = workspace.read(cx).worktrees(cx).collect::<Vec<_>>();
assert_eq!(worktrees.len(), 1,);
@ -936,8 +949,10 @@ async fn test_search_preserves_history_items(cx: &mut gpui::TestAppContext) {
let finder = open_file_picker(&workspace, cx);
let first_query = "f";
finder
.update(cx, |finder, cx| {
finder.delegate.update_matches(first_query.to_string(), cx)
.update_in(cx, |finder, window, cx| {
finder
.delegate
.update_matches(first_query.to_string(), window, cx)
})
.await;
finder.update(cx, |picker, _| {
@ -958,8 +973,10 @@ async fn test_search_preserves_history_items(cx: &mut gpui::TestAppContext) {
let second_query = "fsdasdsa";
let finder = active_file_picker(&workspace, cx);
finder
.update(cx, |finder, cx| {
finder.delegate.update_matches(second_query.to_string(), cx)
.update_in(cx, |finder, window, cx| {
finder
.delegate
.update_matches(second_query.to_string(), window, cx)
})
.await;
finder.update(cx, |picker, _| {
@ -975,10 +992,10 @@ async fn test_search_preserves_history_items(cx: &mut gpui::TestAppContext) {
let finder = active_file_picker(&workspace, cx);
finder
.update(cx, |finder, cx| {
.update_in(cx, |finder, window, cx| {
finder
.delegate
.update_matches(first_query_again.to_string(), cx)
.update_matches(first_query_again.to_string(), window, cx)
})
.await;
finder.update(cx, |picker, _| {
@ -1021,7 +1038,7 @@ async fn test_search_sorts_history_items(cx: &mut gpui::TestAppContext) {
.await;
let project = Project::test(app_state.fs.clone(), ["/root".as_ref()], cx).await;
let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
let (workspace, cx) = cx.add_window_view(|window, cx| Workspace::test_new(project, window, cx));
// generate some history to select from
open_close_queried_buffer("1", 1, "1_qw", &workspace, cx).await;
open_close_queried_buffer("2", 1, "2_second", &workspace, cx).await;
@ -1032,8 +1049,10 @@ async fn test_search_sorts_history_items(cx: &mut gpui::TestAppContext) {
let finder = open_file_picker(&workspace, cx);
let query = "qw";
finder
.update(cx, |finder, cx| {
finder.delegate.update_matches(query.to_string(), cx)
.update_in(cx, |finder, window, cx| {
finder
.delegate
.update_matches(query.to_string(), window, cx)
})
.await;
finder.update(cx, |finder, _| {
@ -1070,7 +1089,7 @@ async fn test_select_current_open_file_when_no_history(cx: &mut gpui::TestAppCon
.await;
let project = Project::test(app_state.fs.clone(), ["/root".as_ref()], cx).await;
let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
let (workspace, cx) = cx.add_window_view(|window, cx| Workspace::test_new(project, window, cx));
// Open new buffer
open_queried_buffer("1", 1, "1_qw", &workspace, cx).await;
@ -1104,7 +1123,7 @@ async fn test_keep_opened_file_on_top_of_search_results_and_select_next_one(
.await;
let project = Project::test(app_state.fs.clone(), ["/src".as_ref()], cx).await;
let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
let (workspace, cx) = cx.add_window_view(|window, cx| Workspace::test_new(project, window, cx));
open_close_queried_buffer("bar", 1, "bar.rs", &workspace, cx).await;
open_close_queried_buffer("lib", 1, "lib.rs", &workspace, cx).await;
@ -1121,8 +1140,10 @@ async fn test_keep_opened_file_on_top_of_search_results_and_select_next_one(
// all files match, main.rs is still on top, but the second item is selected
picker
.update(cx, |finder, cx| {
finder.delegate.update_matches(".rs".to_string(), cx)
.update_in(cx, |finder, window, cx| {
finder
.delegate
.update_matches(".rs".to_string(), window, cx)
})
.await;
picker.update(cx, |finder, _| {
@ -1136,8 +1157,8 @@ async fn test_keep_opened_file_on_top_of_search_results_and_select_next_one(
// main.rs is not among matches, select top item
picker
.update(cx, |finder, cx| {
finder.delegate.update_matches("b".to_string(), cx)
.update_in(cx, |finder, window, cx| {
finder.delegate.update_matches("b".to_string(), window, cx)
})
.await;
picker.update(cx, |finder, _| {
@ -1148,8 +1169,8 @@ async fn test_keep_opened_file_on_top_of_search_results_and_select_next_one(
// main.rs is back, put it on top and select next item
picker
.update(cx, |finder, cx| {
finder.delegate.update_matches("m".to_string(), cx)
.update_in(cx, |finder, window, cx| {
finder.delegate.update_matches("m".to_string(), window, cx)
})
.await;
picker.update(cx, |finder, _| {
@ -1161,8 +1182,8 @@ async fn test_keep_opened_file_on_top_of_search_results_and_select_next_one(
// get back to the initial state
picker
.update(cx, |finder, cx| {
finder.delegate.update_matches("".to_string(), cx)
.update_in(cx, |finder, window, cx| {
finder.delegate.update_matches("".to_string(), window, cx)
})
.await;
picker.update(cx, |finder, _| {
@ -1195,7 +1216,7 @@ async fn test_non_separate_history_items(cx: &mut TestAppContext) {
.await;
let project = Project::test(app_state.fs.clone(), ["/src".as_ref()], cx).await;
let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
let (workspace, cx) = cx.add_window_view(|window, cx| Workspace::test_new(project, window, cx));
open_close_queried_buffer("bar", 1, "bar.rs", &workspace, cx).await;
open_close_queried_buffer("lib", 1, "lib.rs", &workspace, cx).await;
@ -1213,8 +1234,10 @@ async fn test_non_separate_history_items(cx: &mut TestAppContext) {
// all files match, main.rs is still on top, but the second item is selected
picker
.update(cx, |finder, cx| {
finder.delegate.update_matches(".rs".to_string(), cx)
.update_in(cx, |finder, window, cx| {
finder
.delegate
.update_matches(".rs".to_string(), window, cx)
})
.await;
picker.update(cx, |finder, _| {
@ -1228,8 +1251,8 @@ async fn test_non_separate_history_items(cx: &mut TestAppContext) {
// main.rs is not among matches, select top item
picker
.update(cx, |finder, cx| {
finder.delegate.update_matches("b".to_string(), cx)
.update_in(cx, |finder, window, cx| {
finder.delegate.update_matches("b".to_string(), window, cx)
})
.await;
picker.update(cx, |finder, _| {
@ -1240,8 +1263,8 @@ async fn test_non_separate_history_items(cx: &mut TestAppContext) {
// main.rs is back, put it on top and select next item
picker
.update(cx, |finder, cx| {
finder.delegate.update_matches("m".to_string(), cx)
.update_in(cx, |finder, window, cx| {
finder.delegate.update_matches("m".to_string(), window, cx)
})
.await;
picker.update(cx, |finder, _| {
@ -1253,8 +1276,8 @@ async fn test_non_separate_history_items(cx: &mut TestAppContext) {
// get back to the initial state
picker
.update(cx, |finder, cx| {
finder.delegate.update_matches("".to_string(), cx)
.update_in(cx, |finder, window, cx| {
finder.delegate.update_matches("".to_string(), window, cx)
})
.await;
picker.update(cx, |finder, _| {
@ -1285,7 +1308,7 @@ async fn test_history_items_shown_in_order_of_open(cx: &mut TestAppContext) {
.await;
let project = Project::test(app_state.fs.clone(), ["/test".as_ref()], cx).await;
let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
let (workspace, cx) = cx.add_window_view(|window, cx| Workspace::test_new(project, window, cx));
open_queried_buffer("1", 1, "1.txt", &workspace, cx).await;
open_queried_buffer("2", 1, "2.txt", &workspace, cx).await;
@ -1343,7 +1366,7 @@ async fn test_selected_history_item_stays_selected_on_worktree_updated(cx: &mut
.await;
let project = Project::test(app_state.fs.clone(), ["/test".as_ref()], cx).await;
let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
let (workspace, cx) = cx.add_window_view(|window, cx| Workspace::test_new(project, window, cx));
open_close_queried_buffer("1", 1, "1.txt", &workspace, cx).await;
open_close_queried_buffer("2", 1, "2.txt", &workspace, cx).await;
@ -1400,7 +1423,7 @@ async fn test_history_items_vs_very_good_external_match(cx: &mut gpui::TestAppCo
.await;
let project = Project::test(app_state.fs.clone(), ["/src".as_ref()], cx).await;
let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
let (workspace, cx) = cx.add_window_view(|window, cx| Workspace::test_new(project, window, cx));
// generate some history to select from
open_close_queried_buffer("fir", 1, "first.rs", &workspace, cx).await;
open_close_queried_buffer("sec", 1, "second.rs", &workspace, cx).await;
@ -1445,7 +1468,7 @@ async fn test_nonexistent_history_items_not_shown(cx: &mut gpui::TestAppContext)
.await;
let project = Project::test(app_state.fs.clone(), ["/src".as_ref()], cx).await;
let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx)); // generate some history to select from
let (workspace, cx) = cx.add_window_view(|window, cx| Workspace::test_new(project, window, cx)); // generate some history to select from
open_close_queried_buffer("fir", 1, "first.rs", &workspace, cx).await;
open_close_queried_buffer("non", 1, "nonexistent.rs", &workspace, cx).await;
open_close_queried_buffer("thi", 1, "third.rs", &workspace, cx).await;
@ -1493,7 +1516,8 @@ async fn test_search_results_refreshed_on_worktree_updates(cx: &mut gpui::TestAp
.await;
let project = Project::test(app_state.fs.clone(), ["/src".as_ref()], cx).await;
let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project.clone(), cx));
let (workspace, cx) =
cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
// Initial state
let picker = open_file_picker(&workspace, cx);
@ -1559,7 +1583,8 @@ async fn test_search_results_refreshed_on_adding_and_removing_worktrees(
.await;
let project = Project::test(app_state.fs.clone(), ["/test/project_1".as_ref()], cx).await;
let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project.clone(), cx));
let (workspace, cx) =
cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
let worktree_1_id = project.update(cx, |project, cx| {
let worktree = project.worktrees(cx).last().expect("worktree not found");
worktree.read(cx).id()
@ -1629,7 +1654,8 @@ async fn test_selected_match_stays_selected_after_matches_refreshed(cx: &mut gpu
}
let project = Project::test(app_state.fs.clone(), ["/src".as_ref()], cx).await;
let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project.clone(), cx));
let (workspace, cx) =
cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
// Initial state
let picker = open_file_picker(&workspace, cx);
@ -1685,7 +1711,8 @@ async fn test_first_match_selected_if_previous_one_is_not_in_the_match_list(
.await;
let project = Project::test(app_state.fs.clone(), ["/src".as_ref()], cx).await;
let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project.clone(), cx));
let (workspace, cx) =
cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
// Initial state
let picker = open_file_picker(&workspace, cx);
@ -1723,7 +1750,7 @@ async fn test_keeps_file_finder_open_after_modifier_keys_release(cx: &mut gpui::
.await;
let project = Project::test(app_state.fs.clone(), ["/test".as_ref()], cx).await;
let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
let (workspace, cx) = cx.add_window_view(|window, cx| Workspace::test_new(project, window, cx));
open_queried_buffer("1", 1, "1.txt", &workspace, cx).await;
@ -1751,7 +1778,7 @@ async fn test_opens_file_on_modifier_keys_release(cx: &mut gpui::TestAppContext)
.await;
let project = Project::test(app_state.fs.clone(), ["/test".as_ref()], cx).await;
let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
let (workspace, cx) = cx.add_window_view(|window, cx| Workspace::test_new(project, window, cx));
open_queried_buffer("1", 1, "1.txt", &workspace, cx).await;
open_queried_buffer("2", 1, "2.txt", &workspace, cx).await;
@ -1791,7 +1818,7 @@ async fn test_switches_between_release_norelease_modes_on_forward_nav(
.await;
let project = Project::test(app_state.fs.clone(), ["/test".as_ref()], cx).await;
let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
let (workspace, cx) = cx.add_window_view(|window, cx| Workspace::test_new(project, window, cx));
open_queried_buffer("1", 1, "1.txt", &workspace, cx).await;
open_queried_buffer("2", 1, "2.txt", &workspace, cx).await;
@ -1847,7 +1874,7 @@ async fn test_switches_between_release_norelease_modes_on_backward_nav(
.await;
let project = Project::test(app_state.fs.clone(), ["/test".as_ref()], cx).await;
let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
let (workspace, cx) = cx.add_window_view(|window, cx| Workspace::test_new(project, window, cx));
open_queried_buffer("1", 1, "1.txt", &workspace, cx).await;
open_queried_buffer("2", 1, "2.txt", &workspace, cx).await;
@ -1902,7 +1929,7 @@ async fn test_extending_modifiers_does_not_confirm_selection(cx: &mut gpui::Test
.await;
let project = Project::test(app_state.fs.clone(), ["/test".as_ref()], cx).await;
let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
let (workspace, cx) = cx.add_window_view(|window, cx| Workspace::test_new(project, window, cx));
open_queried_buffer("1", 1, "1.txt", &workspace, cx).await;
@ -1933,7 +1960,7 @@ async fn test_repeat_toggle_action(cx: &mut gpui::TestAppContext) {
.await;
let project = Project::test(app_state.fs.clone(), ["/test".as_ref()], cx).await;
let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
let (workspace, cx) = cx.add_window_view(|window, cx| Workspace::test_new(project, window, cx));
cx.dispatch_action(ToggleFileFinder::default());
let picker = active_file_picker(&workspace, cx);
@ -1956,7 +1983,7 @@ async fn open_close_queried_buffer(
input: &str,
expected_matches: usize,
expected_editor_title: &str,
workspace: &View<Workspace>,
workspace: &Entity<Workspace>,
cx: &mut gpui::VisualTestContext,
) -> Vec<FoundPath> {
let history_items = open_queried_buffer(
@ -1977,7 +2004,7 @@ async fn open_queried_buffer(
input: &str,
expected_matches: usize,
expected_editor_title: &str,
workspace: &View<Workspace>,
workspace: &Entity<Workspace>,
cx: &mut gpui::VisualTestContext,
) -> Vec<FoundPath> {
let picker = open_file_picker(&workspace, cx);
@ -2035,23 +2062,23 @@ fn test_path_position(test_str: &str) -> FileSearchQuery {
}
fn build_find_picker(
project: Model<Project>,
project: Entity<Project>,
cx: &mut TestAppContext,
) -> (
View<Picker<FileFinderDelegate>>,
View<Workspace>,
Entity<Picker<FileFinderDelegate>>,
Entity<Workspace>,
&mut VisualTestContext,
) {
let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
let (workspace, cx) = cx.add_window_view(|window, cx| Workspace::test_new(project, window, cx));
let picker = open_file_picker(&workspace, cx);
(picker, workspace, cx)
}
#[track_caller]
fn open_file_picker(
workspace: &View<Workspace>,
workspace: &Entity<Workspace>,
cx: &mut VisualTestContext,
) -> View<Picker<FileFinderDelegate>> {
) -> Entity<Picker<FileFinderDelegate>> {
cx.dispatch_action(ToggleFileFinder {
separate_history: true,
});
@ -2060,9 +2087,9 @@ fn open_file_picker(
#[track_caller]
fn active_file_picker(
workspace: &View<Workspace>,
workspace: &Entity<Workspace>,
cx: &mut VisualTestContext,
) -> View<Picker<FileFinderDelegate>> {
) -> Entity<Picker<FileFinderDelegate>> {
workspace.update(cx, |workspace, cx| {
workspace
.active_modal::<FileFinder>(cx)

View file

@ -1,6 +1,6 @@
use futures::channel::oneshot;
use fuzzy::PathMatch;
use gpui::{HighlightStyle, Model, StyledText};
use gpui::{Entity, HighlightStyle, StyledText};
use picker::{Picker, PickerDelegate};
use project::{Entry, PathMatchCandidateSet, Project, ProjectPath, WorktreeId};
use std::{
@ -11,7 +11,7 @@ use std::{
},
};
use ui::{highlight_ranges, prelude::*, LabelLike, ListItemSpacing};
use ui::{ListItem, ViewContext};
use ui::{Context, ListItem, Window};
use util::ResultExt;
use workspace::Workspace;
@ -24,7 +24,7 @@ struct Match {
}
impl Match {
fn entry<'a>(&'a self, project: &'a Project, cx: &'a WindowContext) -> Option<&'a Entry> {
fn entry<'a>(&'a self, project: &'a Project, cx: &'a App) -> Option<&'a Entry> {
if let Some(suffix) = &self.suffix {
let (worktree, path) = if let Some(path_match) = &self.path_match {
(
@ -45,7 +45,7 @@ impl Match {
}
}
fn is_dir(&self, project: &Project, cx: &WindowContext) -> bool {
fn is_dir(&self, project: &Project, cx: &App) -> bool {
self.entry(project, cx).is_some_and(|e| e.is_dir())
|| self.suffix.as_ref().is_some_and(|s| s.ends_with('/'))
}
@ -68,7 +68,7 @@ impl Match {
}
}
fn project_path(&self, project: &Project, cx: &WindowContext) -> Option<ProjectPath> {
fn project_path(&self, project: &Project, cx: &App) -> Option<ProjectPath> {
let worktree_id = if let Some(path_match) = &self.path_match {
WorktreeId::from_usize(path_match.worktree_id)
} else if let Some(worktree) = project.visible_worktrees(cx).find(|worktree| {
@ -91,7 +91,7 @@ impl Match {
})
}
fn existing_prefix(&self, project: &Project, cx: &WindowContext) -> Option<PathBuf> {
fn existing_prefix(&self, project: &Project, cx: &App) -> Option<PathBuf> {
let worktree = project.worktrees(cx).next()?.read(cx);
let mut prefix = PathBuf::new();
let parts = self.suffix.as_ref()?.split('/');
@ -105,7 +105,7 @@ impl Match {
None
}
fn styled_text(&self, project: &Project, cx: &WindowContext) -> StyledText {
fn styled_text(&self, project: &Project, window: &Window, cx: &App) -> StyledText {
let mut text = "./".to_string();
let mut highlights = Vec::new();
let mut offset = text.as_bytes().len();
@ -192,12 +192,12 @@ impl Match {
}
}
StyledText::new(text).with_highlights(&cx.text_style().clone(), highlights)
StyledText::new(text).with_highlights(&window.text_style().clone(), highlights)
}
}
pub struct NewPathDelegate {
project: Model<Project>,
project: Entity<Project>,
tx: Option<oneshot::Sender<Option<ProjectPath>>>,
selected_index: usize,
matches: Vec<Match>,
@ -207,10 +207,14 @@ pub struct NewPathDelegate {
}
impl NewPathPrompt {
pub(crate) fn register(workspace: &mut Workspace, _cx: &mut ViewContext<Workspace>) {
workspace.set_prompt_for_new_path(Box::new(|workspace, cx| {
pub(crate) fn register(
workspace: &mut Workspace,
_window: Option<&mut Window>,
_cx: &mut Context<Workspace>,
) {
workspace.set_prompt_for_new_path(Box::new(|workspace, window, cx| {
let (tx, rx) = futures::channel::oneshot::channel();
Self::prompt_for_new_path(workspace, tx, cx);
Self::prompt_for_new_path(workspace, tx, window, cx);
rx
}));
}
@ -218,10 +222,11 @@ impl NewPathPrompt {
fn prompt_for_new_path(
workspace: &mut Workspace,
tx: oneshot::Sender<Option<ProjectPath>>,
cx: &mut ViewContext<Workspace>,
window: &mut Window,
cx: &mut Context<Workspace>,
) {
let project = workspace.project().clone();
workspace.toggle_modal(cx, |cx| {
workspace.toggle_modal(window, cx, |window, cx| {
let delegate = NewPathDelegate {
project,
tx: Some(tx),
@ -232,7 +237,7 @@ impl NewPathPrompt {
should_dismiss: true,
};
Picker::uniform_list(delegate, cx).width(rems(34.))
Picker::uniform_list(delegate, window, cx).width(rems(34.))
});
}
}
@ -248,7 +253,12 @@ impl PickerDelegate for NewPathDelegate {
self.selected_index
}
fn set_selected_index(&mut self, ix: usize, cx: &mut ViewContext<picker::Picker<Self>>) {
fn set_selected_index(
&mut self,
ix: usize,
_: &mut Window,
cx: &mut Context<picker::Picker<Self>>,
) {
self.selected_index = ix;
cx.notify();
}
@ -256,7 +266,8 @@ impl PickerDelegate for NewPathDelegate {
fn update_matches(
&mut self,
query: String,
cx: &mut ViewContext<picker::Picker<Self>>,
window: &mut Window,
cx: &mut Context<picker::Picker<Self>>,
) -> gpui::Task<()> {
let query = query
.trim()
@ -301,7 +312,7 @@ impl PickerDelegate for NewPathDelegate {
let cancel_flag = self.cancel_flag.clone();
let query = query.to_string();
let prefix = dir.clone();
cx.spawn(|picker, mut cx| async move {
cx.spawn_in(window, |picker, mut cx| async move {
let matches = fuzzy::match_path_sets(
candidate_sets.as_slice(),
&dir,
@ -329,12 +340,17 @@ impl PickerDelegate for NewPathDelegate {
fn confirm_completion(
&mut self,
_: String,
cx: &mut ViewContext<Picker<Self>>,
window: &mut Window,
cx: &mut Context<Picker<Self>>,
) -> Option<String> {
self.confirm_update_query(cx)
self.confirm_update_query(window, cx)
}
fn confirm_update_query(&mut self, cx: &mut ViewContext<Picker<Self>>) -> Option<String> {
fn confirm_update_query(
&mut self,
_: &mut Window,
cx: &mut Context<Picker<Self>>,
) -> Option<String> {
let m = self.matches.get(self.selected_index)?;
if m.is_dir(self.project.read(cx), cx) {
let path = m.relative_path();
@ -345,7 +361,7 @@ impl PickerDelegate for NewPathDelegate {
}
}
fn confirm(&mut self, _: bool, cx: &mut ViewContext<picker::Picker<Self>>) {
fn confirm(&mut self, _: bool, window: &mut Window, cx: &mut Context<picker::Picker<Self>>) {
let Some(m) = self.matches.get(self.selected_index) else {
return;
};
@ -353,16 +369,16 @@ impl PickerDelegate for NewPathDelegate {
let exists = m.entry(self.project.read(cx), cx).is_some();
if exists {
self.should_dismiss = false;
let answer = cx.prompt(
let answer = window.prompt(
gpui::PromptLevel::Critical,
&format!("{} already exists. Do you want to replace it?", m.relative_path()),
Some(
"A file or folder with the same name already exists. Replacing it will overwrite its current contents.",
),
&["Replace", "Cancel"],
);
cx);
let m = m.clone();
cx.spawn(|picker, mut cx| async move {
cx.spawn_in(window, |picker, mut cx| async move {
let answer = answer.await.ok();
picker
.update(&mut cx, |picker, cx| {
@ -395,7 +411,7 @@ impl PickerDelegate for NewPathDelegate {
self.should_dismiss
}
fn dismissed(&mut self, cx: &mut ViewContext<picker::Picker<Self>>) {
fn dismissed(&mut self, _: &mut Window, cx: &mut Context<picker::Picker<Self>>) {
if let Some(tx) = self.tx.take() {
tx.send(None).ok();
}
@ -406,7 +422,8 @@ impl PickerDelegate for NewPathDelegate {
&self,
ix: usize,
selected: bool,
cx: &mut ViewContext<picker::Picker<Self>>,
window: &mut Window,
cx: &mut Context<picker::Picker<Self>>,
) -> Option<Self::ListItem> {
let m = self.matches.get(ix)?;
@ -415,15 +432,15 @@ impl PickerDelegate for NewPathDelegate {
.spacing(ListItemSpacing::Sparse)
.inset(true)
.toggle_state(selected)
.child(LabelLike::new().child(m.styled_text(self.project.read(cx), cx))),
.child(LabelLike::new().child(m.styled_text(self.project.read(cx), window, cx))),
)
}
fn no_matches_text(&self, _cx: &mut WindowContext) -> SharedString {
fn no_matches_text(&self, _window: &mut Window, _cx: &mut App) -> SharedString {
"Type a path...".into()
}
fn placeholder_text(&self, _cx: &mut WindowContext) -> Arc<str> {
fn placeholder_text(&self, _window: &mut Window, _cx: &mut App) -> Arc<str> {
Arc::from("[directory/]filename.ext")
}
}
@ -435,7 +452,7 @@ impl NewPathDelegate {
prefix: String,
suffix: Option<String>,
matches: Vec<PathMatch>,
cx: &mut ViewContext<Picker<Self>>,
cx: &mut Context<Picker<Self>>,
) {
cx.notify();
if query.is_empty() {

View file

@ -10,7 +10,7 @@ use std::{
},
};
use ui::{prelude::*, LabelLike, ListItemSpacing};
use ui::{ListItem, ViewContext};
use ui::{Context, ListItem, Window};
use util::{maybe, paths::compare_paths};
use workspace::Workspace;
@ -47,10 +47,14 @@ struct DirectoryState {
}
impl OpenPathPrompt {
pub(crate) fn register(workspace: &mut Workspace, _: &mut ViewContext<Workspace>) {
workspace.set_prompt_for_open_path(Box::new(|workspace, lister, cx| {
pub(crate) fn register(
workspace: &mut Workspace,
_window: Option<&mut Window>,
_: &mut Context<Workspace>,
) {
workspace.set_prompt_for_open_path(Box::new(|workspace, lister, window, cx| {
let (tx, rx) = futures::channel::oneshot::channel();
Self::prompt_for_open_path(workspace, lister, tx, cx);
Self::prompt_for_open_path(workspace, lister, tx, window, cx);
rx
}));
}
@ -59,14 +63,15 @@ impl OpenPathPrompt {
workspace: &mut Workspace,
lister: DirectoryLister,
tx: oneshot::Sender<Option<Vec<PathBuf>>>,
cx: &mut ViewContext<Workspace>,
window: &mut Window,
cx: &mut Context<Workspace>,
) {
workspace.toggle_modal(cx, |cx| {
workspace.toggle_modal(window, cx, |window, cx| {
let delegate = OpenPathDelegate::new(tx, lister.clone());
let picker = Picker::uniform_list(delegate, cx).width(rems(34.));
let picker = Picker::uniform_list(delegate, window, cx).width(rems(34.));
let query = lister.default_query(cx);
picker.set_query(query, cx);
picker.set_query(query, window, cx);
picker
});
}
@ -83,7 +88,7 @@ impl PickerDelegate for OpenPathDelegate {
self.selected_index
}
fn set_selected_index(&mut self, ix: usize, cx: &mut ViewContext<Picker<Self>>) {
fn set_selected_index(&mut self, ix: usize, _: &mut Window, cx: &mut Context<Picker<Self>>) {
self.selected_index = ix;
cx.notify();
}
@ -91,7 +96,8 @@ impl PickerDelegate for OpenPathDelegate {
fn update_matches(
&mut self,
query: String,
cx: &mut ViewContext<Picker<Self>>,
window: &mut Window,
cx: &mut Context<Picker<Self>>,
) -> gpui::Task<()> {
let lister = self.lister.clone();
let (mut dir, suffix) = if let Some(index) = query.rfind('/') {
@ -116,7 +122,7 @@ impl PickerDelegate for OpenPathDelegate {
self.cancel_flag = Arc::new(AtomicBool::new(false));
let cancel_flag = self.cancel_flag.clone();
cx.spawn(|this, mut cx| async move {
cx.spawn_in(window, |this, mut cx| async move {
if let Some(query) = query {
let paths = query.await;
if cancel_flag.load(atomic::Ordering::Relaxed) {
@ -223,7 +229,8 @@ impl PickerDelegate for OpenPathDelegate {
fn confirm_completion(
&mut self,
query: String,
_: &mut ViewContext<Picker<Self>>,
_window: &mut Window,
_: &mut Context<Picker<Self>>,
) -> Option<String> {
Some(
maybe!({
@ -236,7 +243,7 @@ impl PickerDelegate for OpenPathDelegate {
)
}
fn confirm(&mut self, _: bool, cx: &mut ViewContext<Picker<Self>>) {
fn confirm(&mut self, _: bool, _: &mut Window, cx: &mut Context<Picker<Self>>) {
let Some(m) = self.matches.get(self.selected_index) else {
return;
};
@ -262,7 +269,7 @@ impl PickerDelegate for OpenPathDelegate {
self.should_dismiss
}
fn dismissed(&mut self, cx: &mut ViewContext<Picker<Self>>) {
fn dismissed(&mut self, _: &mut Window, cx: &mut Context<Picker<Self>>) {
if let Some(tx) = self.tx.take() {
tx.send(None).ok();
}
@ -273,7 +280,8 @@ impl PickerDelegate for OpenPathDelegate {
&self,
ix: usize,
selected: bool,
_: &mut ViewContext<Picker<Self>>,
_window: &mut Window,
_: &mut Context<Picker<Self>>,
) -> Option<Self::ListItem> {
let m = self.matches.get(ix)?;
let directory_state = self.directory_state.as_ref()?;
@ -288,7 +296,7 @@ impl PickerDelegate for OpenPathDelegate {
)
}
fn no_matches_text(&self, _cx: &mut WindowContext) -> SharedString {
fn no_matches_text(&self, _window: &mut Window, _cx: &mut App) -> SharedString {
if let Some(error) = self.directory_state.as_ref().and_then(|s| s.error.clone()) {
error
} else {
@ -296,7 +304,7 @@ impl PickerDelegate for OpenPathDelegate {
}
}
fn placeholder_text(&self, _cx: &mut WindowContext) -> Arc<str> {
fn placeholder_text(&self, _window: &mut Window, _cx: &mut App) -> Arc<str> {
Arc::from("[directory/]filename.ext")
}
}