mirror of
https://github.com/zed-industries/zed.git
synced 2024-09-20 10:57:22 +03:00
Merge pull request #1073 from zed-industries/window-menu
Add a Window application menu
This commit is contained in:
commit
b2adff63e7
@ -2234,7 +2234,6 @@ mod tests {
|
|||||||
.read_with(cx_a, |project, _| project.next_remote_id())
|
.read_with(cx_a, |project, _| project.next_remote_id())
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
let project_a_events = Rc::new(RefCell::new(Vec::new()));
|
|
||||||
let user_b = client_a
|
let user_b = client_a
|
||||||
.user_store
|
.user_store
|
||||||
.update(cx_a, |store, cx| {
|
.update(cx_a, |store, cx| {
|
||||||
@ -2242,15 +2241,6 @@ mod tests {
|
|||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
project_a.update(cx_a, {
|
|
||||||
let project_a_events = project_a_events.clone();
|
|
||||||
move |_, cx| {
|
|
||||||
cx.subscribe(&cx.handle(), move |_, _, event, _| {
|
|
||||||
project_a_events.borrow_mut().push(event.clone());
|
|
||||||
})
|
|
||||||
.detach();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
let (worktree_a, _) = project_a
|
let (worktree_a, _) = project_a
|
||||||
.update(cx_a, |p, cx| {
|
.update(cx_a, |p, cx| {
|
||||||
@ -2262,6 +2252,17 @@ mod tests {
|
|||||||
.read_with(cx_a, |tree, _| tree.as_local().unwrap().scan_complete())
|
.read_with(cx_a, |tree, _| tree.as_local().unwrap().scan_complete())
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
|
let project_a_events = Rc::new(RefCell::new(Vec::new()));
|
||||||
|
project_a.update(cx_a, {
|
||||||
|
let project_a_events = project_a_events.clone();
|
||||||
|
move |_, cx| {
|
||||||
|
cx.subscribe(&cx.handle(), move |_, _, event, _| {
|
||||||
|
project_a_events.borrow_mut().push(event.clone());
|
||||||
|
})
|
||||||
|
.detach();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// Request to join that project as client B
|
// Request to join that project as client B
|
||||||
let project_b = cx_b.spawn(|mut cx| {
|
let project_b = cx_b.spawn(|mut cx| {
|
||||||
let client = client_b.client.clone();
|
let client = client_b.client.clone();
|
||||||
@ -5855,6 +5856,9 @@ mod tests {
|
|||||||
.update(cx_a, |workspace, cx| {
|
.update(cx_a, |workspace, cx| {
|
||||||
workspace.split_pane(workspace.active_pane().clone(), SplitDirection::Right, cx);
|
workspace.split_pane(workspace.active_pane().clone(), SplitDirection::Right, cx);
|
||||||
assert_ne!(*workspace.active_pane(), pane_a1);
|
assert_ne!(*workspace.active_pane(), pane_a1);
|
||||||
|
});
|
||||||
|
workspace_a
|
||||||
|
.update(cx_a, |workspace, cx| {
|
||||||
let leader_id = *project_a.read(cx).collaborators().keys().next().unwrap();
|
let leader_id = *project_a.read(cx).collaborators().keys().next().unwrap();
|
||||||
workspace
|
workspace
|
||||||
.toggle_follow(&workspace::ToggleFollow(leader_id), cx)
|
.toggle_follow(&workspace::ToggleFollow(leader_id), cx)
|
||||||
@ -5866,6 +5870,9 @@ mod tests {
|
|||||||
.update(cx_b, |workspace, cx| {
|
.update(cx_b, |workspace, cx| {
|
||||||
workspace.split_pane(workspace.active_pane().clone(), SplitDirection::Right, cx);
|
workspace.split_pane(workspace.active_pane().clone(), SplitDirection::Right, cx);
|
||||||
assert_ne!(*workspace.active_pane(), pane_b1);
|
assert_ne!(*workspace.active_pane(), pane_b1);
|
||||||
|
});
|
||||||
|
workspace_b
|
||||||
|
.update(cx_b, |workspace, cx| {
|
||||||
let leader_id = *project_b.read(cx).collaborators().keys().next().unwrap();
|
let leader_id = *project_b.read(cx).collaborators().keys().next().unwrap();
|
||||||
workspace
|
workspace
|
||||||
.toggle_follow(&workspace::ToggleFollow(leader_id), cx)
|
.toggle_follow(&workspace::ToggleFollow(leader_id), cx)
|
||||||
|
@ -542,12 +542,23 @@ impl TestAppContext {
|
|||||||
!prompts.is_empty()
|
!prompts.is_empty()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(any(test, feature = "test-support"))]
|
pub fn current_window_title(&self, window_id: usize) -> Option<String> {
|
||||||
|
let mut state = self.cx.borrow_mut();
|
||||||
|
let (_, window) = state
|
||||||
|
.presenters_and_platform_windows
|
||||||
|
.get_mut(&window_id)
|
||||||
|
.unwrap();
|
||||||
|
let test_window = window
|
||||||
|
.as_any_mut()
|
||||||
|
.downcast_mut::<platform::test::Window>()
|
||||||
|
.unwrap();
|
||||||
|
test_window.title.clone()
|
||||||
|
}
|
||||||
|
|
||||||
pub fn leak_detector(&self) -> Arc<Mutex<LeakDetector>> {
|
pub fn leak_detector(&self) -> Arc<Mutex<LeakDetector>> {
|
||||||
self.cx.borrow().leak_detector()
|
self.cx.borrow().leak_detector()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(any(test, feature = "test-support"))]
|
|
||||||
pub fn assert_dropped(&self, handle: impl WeakHandle) {
|
pub fn assert_dropped(&self, handle: impl WeakHandle) {
|
||||||
self.cx
|
self.cx
|
||||||
.borrow()
|
.borrow()
|
||||||
@ -3265,6 +3276,13 @@ impl<'a, T: View> ViewContext<'a, T> {
|
|||||||
self.app.focus(self.window_id, None);
|
self.app.focus(self.window_id, None);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn set_window_title(&mut self, title: &str) {
|
||||||
|
let window_id = self.window_id();
|
||||||
|
if let Some((_, window)) = self.presenters_and_platform_windows.get_mut(&window_id) {
|
||||||
|
window.set_title(title);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn add_model<S, F>(&mut self, build_model: F) -> ModelHandle<S>
|
pub fn add_model<S, F>(&mut self, build_model: F) -> ModelHandle<S>
|
||||||
where
|
where
|
||||||
S: Entity,
|
S: Entity,
|
||||||
|
@ -96,6 +96,7 @@ pub trait Window: WindowContext {
|
|||||||
fn on_close(&mut self, callback: Box<dyn FnOnce()>);
|
fn on_close(&mut self, callback: Box<dyn FnOnce()>);
|
||||||
fn prompt(&self, level: PromptLevel, msg: &str, answers: &[&str]) -> oneshot::Receiver<usize>;
|
fn prompt(&self, level: PromptLevel, msg: &str, answers: &[&str]) -> oneshot::Receiver<usize>;
|
||||||
fn activate(&self);
|
fn activate(&self);
|
||||||
|
fn set_title(&mut self, title: &str);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait WindowContext {
|
pub trait WindowContext {
|
||||||
|
@ -202,6 +202,11 @@ impl MacForegroundPlatform {
|
|||||||
|
|
||||||
menu_bar_item.setSubmenu_(menu);
|
menu_bar_item.setSubmenu_(menu);
|
||||||
menu_bar.addItem_(menu_bar_item);
|
menu_bar.addItem_(menu_bar_item);
|
||||||
|
|
||||||
|
if menu_name == "Window" {
|
||||||
|
let app: id = msg_send![APP_CLASS, sharedApplication];
|
||||||
|
app.setWindowsMenu_(menu);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
menu_bar
|
menu_bar
|
||||||
|
@ -386,8 +386,15 @@ impl platform::Window for Window {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn activate(&self) {
|
fn activate(&self) {
|
||||||
|
unsafe { msg_send![self.0.borrow().native_window, makeKeyAndOrderFront: nil] }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_title(&mut self, title: &str) {
|
||||||
unsafe {
|
unsafe {
|
||||||
let _: () = msg_send![self.0.borrow().native_window, makeKeyAndOrderFront: nil];
|
let app = NSApplication::sharedApplication(nil);
|
||||||
|
let window = self.0.borrow().native_window;
|
||||||
|
let title = ns_string(title);
|
||||||
|
msg_send![app, changeWindowsItem:window title:title filename:false]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -37,6 +37,7 @@ pub struct Window {
|
|||||||
event_handlers: Vec<Box<dyn FnMut(super::Event)>>,
|
event_handlers: Vec<Box<dyn FnMut(super::Event)>>,
|
||||||
resize_handlers: Vec<Box<dyn FnMut()>>,
|
resize_handlers: Vec<Box<dyn FnMut()>>,
|
||||||
close_handlers: Vec<Box<dyn FnOnce()>>,
|
close_handlers: Vec<Box<dyn FnOnce()>>,
|
||||||
|
pub(crate) title: Option<String>,
|
||||||
pub(crate) pending_prompts: RefCell<VecDeque<oneshot::Sender<usize>>>,
|
pub(crate) pending_prompts: RefCell<VecDeque<oneshot::Sender<usize>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -189,9 +190,14 @@ impl Window {
|
|||||||
close_handlers: Vec::new(),
|
close_handlers: Vec::new(),
|
||||||
scale_factor: 1.0,
|
scale_factor: 1.0,
|
||||||
current_scene: None,
|
current_scene: None,
|
||||||
|
title: None,
|
||||||
pending_prompts: Default::default(),
|
pending_prompts: Default::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn title(&self) -> Option<String> {
|
||||||
|
self.title.clone()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl super::Dispatcher for Dispatcher {
|
impl super::Dispatcher for Dispatcher {
|
||||||
@ -248,6 +254,10 @@ impl super::Window for Window {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn activate(&self) {}
|
fn activate(&self) {}
|
||||||
|
|
||||||
|
fn set_title(&mut self, title: &str) {
|
||||||
|
self.title = Some(title.to_string())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn platform() -> Platform {
|
pub fn platform() -> Platform {
|
||||||
|
@ -139,6 +139,7 @@ pub struct Collaborator {
|
|||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
pub enum Event {
|
pub enum Event {
|
||||||
ActiveEntryChanged(Option<ProjectEntryId>),
|
ActiveEntryChanged(Option<ProjectEntryId>),
|
||||||
|
WorktreeAdded,
|
||||||
WorktreeRemoved(WorktreeId),
|
WorktreeRemoved(WorktreeId),
|
||||||
DiskBasedDiagnosticsStarted,
|
DiskBasedDiagnosticsStarted,
|
||||||
DiskBasedDiagnosticsUpdated,
|
DiskBasedDiagnosticsUpdated,
|
||||||
@ -3602,11 +3603,19 @@ impl Project {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn remove_worktree(&mut self, id: WorktreeId, cx: &mut ModelContext<Self>) {
|
pub fn remove_worktree(&mut self, id_to_remove: WorktreeId, cx: &mut ModelContext<Self>) {
|
||||||
self.worktrees.retain(|worktree| {
|
self.worktrees.retain(|worktree| {
|
||||||
worktree
|
if let Some(worktree) = worktree.upgrade(cx) {
|
||||||
.upgrade(cx)
|
let id = worktree.read(cx).id();
|
||||||
.map_or(false, |w| w.read(cx).id() != id)
|
if id == id_to_remove {
|
||||||
|
cx.emit(Event::WorktreeRemoved(id));
|
||||||
|
false
|
||||||
|
} else {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
});
|
});
|
||||||
cx.notify();
|
cx.notify();
|
||||||
}
|
}
|
||||||
@ -3637,6 +3646,7 @@ impl Project {
|
|||||||
self.worktrees
|
self.worktrees
|
||||||
.push(WorktreeHandle::Weak(worktree.downgrade()));
|
.push(WorktreeHandle::Weak(worktree.downgrade()));
|
||||||
}
|
}
|
||||||
|
cx.emit(Event::WorktreeAdded);
|
||||||
cx.notify();
|
cx.notify();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -15,7 +15,7 @@ use gpui::{
|
|||||||
use project::{Project, ProjectEntryId, ProjectPath};
|
use project::{Project, ProjectEntryId, ProjectPath};
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use settings::Settings;
|
use settings::Settings;
|
||||||
use std::{any::Any, cell::RefCell, cmp, mem, path::Path, rc::Rc};
|
use std::{any::Any, cell::RefCell, mem, path::Path, rc::Rc};
|
||||||
use util::ResultExt;
|
use util::ResultExt;
|
||||||
|
|
||||||
actions!(
|
actions!(
|
||||||
@ -109,6 +109,7 @@ pub enum Event {
|
|||||||
ActivateItem { local: bool },
|
ActivateItem { local: bool },
|
||||||
Remove,
|
Remove,
|
||||||
Split(SplitDirection),
|
Split(SplitDirection),
|
||||||
|
ChangeItemTitle,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Pane {
|
pub struct Pane {
|
||||||
@ -334,9 +335,20 @@ impl Pane {
|
|||||||
item.set_nav_history(pane.read(cx).nav_history.clone(), cx);
|
item.set_nav_history(pane.read(cx).nav_history.clone(), cx);
|
||||||
item.added_to_pane(workspace, pane.clone(), cx);
|
item.added_to_pane(workspace, pane.clone(), cx);
|
||||||
pane.update(cx, |pane, cx| {
|
pane.update(cx, |pane, cx| {
|
||||||
let item_idx = cmp::min(pane.active_item_index + 1, pane.items.len());
|
// If there is already an active item, then insert the new item
|
||||||
pane.items.insert(item_idx, item);
|
// right after it. Otherwise, adjust the `active_item_index` field
|
||||||
pane.activate_item(item_idx, activate_pane, focus_item, cx);
|
// before activating the new item, so that in the `activate_item`
|
||||||
|
// method, we can detect that the active item is changing.
|
||||||
|
let item_ix;
|
||||||
|
if pane.active_item_index < pane.items.len() {
|
||||||
|
item_ix = pane.active_item_index + 1
|
||||||
|
} else {
|
||||||
|
item_ix = pane.items.len();
|
||||||
|
pane.active_item_index = usize::MAX;
|
||||||
|
};
|
||||||
|
|
||||||
|
pane.items.insert(item_ix, item);
|
||||||
|
pane.activate_item(item_ix, activate_pane, focus_item, cx);
|
||||||
cx.notify();
|
cx.notify();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -383,11 +395,12 @@ impl Pane {
|
|||||||
use NavigationMode::{GoingBack, GoingForward};
|
use NavigationMode::{GoingBack, GoingForward};
|
||||||
if index < self.items.len() {
|
if index < self.items.len() {
|
||||||
let prev_active_item_ix = mem::replace(&mut self.active_item_index, index);
|
let prev_active_item_ix = mem::replace(&mut self.active_item_index, index);
|
||||||
if matches!(self.nav_history.borrow().mode, GoingBack | GoingForward)
|
if prev_active_item_ix != self.active_item_index
|
||||||
|| (prev_active_item_ix != self.active_item_index
|
|| matches!(self.nav_history.borrow().mode, GoingBack | GoingForward)
|
||||||
&& prev_active_item_ix < self.items.len())
|
|
||||||
{
|
{
|
||||||
self.items[prev_active_item_ix].deactivated(cx);
|
if let Some(prev_item) = self.items.get(prev_active_item_ix) {
|
||||||
|
prev_item.deactivated(cx);
|
||||||
|
}
|
||||||
cx.emit(Event::ActivateItem {
|
cx.emit(Event::ActivateItem {
|
||||||
local: activate_pane,
|
local: activate_pane,
|
||||||
});
|
});
|
||||||
@ -424,7 +437,7 @@ impl Pane {
|
|||||||
self.activate_item(index, true, true, cx);
|
self.activate_item(index, true, true, cx);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn close_active_item(
|
pub fn close_active_item(
|
||||||
workspace: &mut Workspace,
|
workspace: &mut Workspace,
|
||||||
_: &CloseActiveItem,
|
_: &CloseActiveItem,
|
||||||
cx: &mut ViewContext<Workspace>,
|
cx: &mut ViewContext<Workspace>,
|
||||||
|
@ -38,6 +38,7 @@ use status_bar::StatusBar;
|
|||||||
pub use status_bar::StatusItemView;
|
pub use status_bar::StatusItemView;
|
||||||
use std::{
|
use std::{
|
||||||
any::{Any, TypeId},
|
any::{Any, TypeId},
|
||||||
|
borrow::Cow,
|
||||||
cell::RefCell,
|
cell::RefCell,
|
||||||
fmt,
|
fmt,
|
||||||
future::Future,
|
future::Future,
|
||||||
@ -532,7 +533,10 @@ impl<T: Item> ItemHandle for ViewHandle<T> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if T::should_update_tab_on_event(event) {
|
if T::should_update_tab_on_event(event) {
|
||||||
pane.update(cx, |_, cx| cx.notify());
|
pane.update(cx, |_, cx| {
|
||||||
|
cx.emit(pane::Event::ChangeItemTitle);
|
||||||
|
cx.notify();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.detach();
|
.detach();
|
||||||
@ -744,6 +748,9 @@ impl Workspace {
|
|||||||
project::Event::CollaboratorLeft(peer_id) => {
|
project::Event::CollaboratorLeft(peer_id) => {
|
||||||
this.collaborator_left(*peer_id, cx);
|
this.collaborator_left(*peer_id, cx);
|
||||||
}
|
}
|
||||||
|
project::Event::WorktreeRemoved(_) | project::Event::WorktreeAdded => {
|
||||||
|
this.update_window_title(cx);
|
||||||
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
if project.read(cx).is_read_only() {
|
if project.read(cx).is_read_only() {
|
||||||
@ -755,14 +762,8 @@ impl Workspace {
|
|||||||
|
|
||||||
let pane = cx.add_view(|cx| Pane::new(cx));
|
let pane = cx.add_view(|cx| Pane::new(cx));
|
||||||
let pane_id = pane.id();
|
let pane_id = pane.id();
|
||||||
cx.observe(&pane, move |me, _, cx| {
|
cx.subscribe(&pane, move |this, _, event, cx| {
|
||||||
let active_entry = me.active_project_path(cx);
|
this.handle_pane_event(pane_id, event, cx)
|
||||||
me.project
|
|
||||||
.update(cx, |project, cx| project.set_active_path(active_entry, cx));
|
|
||||||
})
|
|
||||||
.detach();
|
|
||||||
cx.subscribe(&pane, move |me, _, event, cx| {
|
|
||||||
me.handle_pane_event(pane_id, event, cx)
|
|
||||||
})
|
})
|
||||||
.detach();
|
.detach();
|
||||||
cx.focus(&pane);
|
cx.focus(&pane);
|
||||||
@ -825,6 +826,11 @@ impl Workspace {
|
|||||||
_observe_current_user,
|
_observe_current_user,
|
||||||
};
|
};
|
||||||
this.project_remote_id_changed(this.project.read(cx).remote_id(), cx);
|
this.project_remote_id_changed(this.project.read(cx).remote_id(), cx);
|
||||||
|
|
||||||
|
cx.defer(|this, cx| {
|
||||||
|
this.update_window_title(cx);
|
||||||
|
});
|
||||||
|
|
||||||
this
|
this
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1238,14 +1244,8 @@ impl Workspace {
|
|||||||
fn add_pane(&mut self, cx: &mut ViewContext<Self>) -> ViewHandle<Pane> {
|
fn add_pane(&mut self, cx: &mut ViewContext<Self>) -> ViewHandle<Pane> {
|
||||||
let pane = cx.add_view(|cx| Pane::new(cx));
|
let pane = cx.add_view(|cx| Pane::new(cx));
|
||||||
let pane_id = pane.id();
|
let pane_id = pane.id();
|
||||||
cx.observe(&pane, move |me, _, cx| {
|
cx.subscribe(&pane, move |this, _, event, cx| {
|
||||||
let active_entry = me.active_project_path(cx);
|
this.handle_pane_event(pane_id, event, cx)
|
||||||
me.project
|
|
||||||
.update(cx, |project, cx| project.set_active_path(active_entry, cx));
|
|
||||||
})
|
|
||||||
.detach();
|
|
||||||
cx.subscribe(&pane, move |me, _, event, cx| {
|
|
||||||
me.handle_pane_event(pane_id, event, cx)
|
|
||||||
})
|
})
|
||||||
.detach();
|
.detach();
|
||||||
self.panes.push(pane.clone());
|
self.panes.push(pane.clone());
|
||||||
@ -1385,6 +1385,7 @@ impl Workspace {
|
|||||||
self.status_bar.update(cx, |status_bar, cx| {
|
self.status_bar.update(cx, |status_bar, cx| {
|
||||||
status_bar.set_active_pane(&self.active_pane, cx);
|
status_bar.set_active_pane(&self.active_pane, cx);
|
||||||
});
|
});
|
||||||
|
self.active_item_path_changed(cx);
|
||||||
cx.focus(&self.active_pane);
|
cx.focus(&self.active_pane);
|
||||||
cx.notify();
|
cx.notify();
|
||||||
}
|
}
|
||||||
@ -1419,6 +1420,14 @@ impl Workspace {
|
|||||||
if *local {
|
if *local {
|
||||||
self.unfollow(&pane, cx);
|
self.unfollow(&pane, cx);
|
||||||
}
|
}
|
||||||
|
if pane == self.active_pane {
|
||||||
|
self.active_item_path_changed(cx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pane::Event::ChangeItemTitle => {
|
||||||
|
if pane == self.active_pane {
|
||||||
|
self.active_item_path_changed(cx);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -1451,6 +1460,8 @@ impl Workspace {
|
|||||||
self.unfollow(&pane, cx);
|
self.unfollow(&pane, cx);
|
||||||
self.last_leaders_by_pane.remove(&pane.downgrade());
|
self.last_leaders_by_pane.remove(&pane.downgrade());
|
||||||
cx.notify();
|
cx.notify();
|
||||||
|
} else {
|
||||||
|
self.active_item_path_changed(cx);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1638,15 +1649,7 @@ impl Workspace {
|
|||||||
|
|
||||||
fn render_titlebar(&self, theme: &Theme, cx: &mut RenderContext<Self>) -> ElementBox {
|
fn render_titlebar(&self, theme: &Theme, cx: &mut RenderContext<Self>) -> ElementBox {
|
||||||
let mut worktree_root_names = String::new();
|
let mut worktree_root_names = String::new();
|
||||||
{
|
self.worktree_root_names(&mut worktree_root_names, cx);
|
||||||
let mut worktrees = self.project.read(cx).visible_worktrees(cx).peekable();
|
|
||||||
while let Some(worktree) = worktrees.next() {
|
|
||||||
worktree_root_names.push_str(worktree.read(cx).root_name());
|
|
||||||
if worktrees.peek().is_some() {
|
|
||||||
worktree_root_names.push_str(", ");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ConstrainedBox::new(
|
ConstrainedBox::new(
|
||||||
Container::new(
|
Container::new(
|
||||||
@ -1682,6 +1685,50 @@ impl Workspace {
|
|||||||
.named("titlebar")
|
.named("titlebar")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn active_item_path_changed(&mut self, cx: &mut ViewContext<Self>) {
|
||||||
|
let active_entry = self.active_project_path(cx);
|
||||||
|
self.project
|
||||||
|
.update(cx, |project, cx| project.set_active_path(active_entry, cx));
|
||||||
|
self.update_window_title(cx);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_window_title(&mut self, cx: &mut ViewContext<Self>) {
|
||||||
|
let mut title = String::new();
|
||||||
|
if let Some(path) = self.active_item(cx).and_then(|item| item.project_path(cx)) {
|
||||||
|
let filename = path
|
||||||
|
.path
|
||||||
|
.file_name()
|
||||||
|
.map(|s| s.to_string_lossy())
|
||||||
|
.or_else(|| {
|
||||||
|
Some(Cow::Borrowed(
|
||||||
|
self.project()
|
||||||
|
.read(cx)
|
||||||
|
.worktree_for_id(path.worktree_id, cx)?
|
||||||
|
.read(cx)
|
||||||
|
.root_name(),
|
||||||
|
))
|
||||||
|
});
|
||||||
|
if let Some(filename) = filename {
|
||||||
|
title.push_str(filename.as_ref());
|
||||||
|
title.push_str(" — ");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.worktree_root_names(&mut title, cx);
|
||||||
|
if title.is_empty() {
|
||||||
|
title = "empty project".to_string();
|
||||||
|
}
|
||||||
|
cx.set_window_title(&title);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn worktree_root_names(&self, string: &mut String, cx: &mut MutableAppContext) {
|
||||||
|
for (i, worktree) in self.project.read(cx).visible_worktrees(cx).enumerate() {
|
||||||
|
if i != 0 {
|
||||||
|
string.push_str(", ");
|
||||||
|
}
|
||||||
|
string.push_str(worktree.read(cx).root_name());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn render_collaborators(&self, theme: &Theme, cx: &mut RenderContext<Self>) -> Vec<ElementBox> {
|
fn render_collaborators(&self, theme: &Theme, cx: &mut RenderContext<Self>) -> Vec<ElementBox> {
|
||||||
let mut collaborators = self
|
let mut collaborators = self
|
||||||
.project
|
.project
|
||||||
@ -2417,6 +2464,110 @@ mod tests {
|
|||||||
use project::{FakeFs, Project, ProjectEntryId};
|
use project::{FakeFs, Project, ProjectEntryId};
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
|
|
||||||
|
#[gpui::test]
|
||||||
|
async fn test_tracking_active_path(cx: &mut TestAppContext) {
|
||||||
|
cx.foreground().forbid_parking();
|
||||||
|
Settings::test_async(cx);
|
||||||
|
let fs = FakeFs::new(cx.background());
|
||||||
|
fs.insert_tree(
|
||||||
|
"/root1",
|
||||||
|
json!({
|
||||||
|
"one.txt": "",
|
||||||
|
"two.txt": "",
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
fs.insert_tree(
|
||||||
|
"/root2",
|
||||||
|
json!({
|
||||||
|
"three.txt": "",
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
let project = Project::test(fs, ["root1".as_ref()], cx).await;
|
||||||
|
let (window_id, workspace) = cx.add_window(|cx| Workspace::new(project.clone(), cx));
|
||||||
|
let worktree_id = project.read_with(cx, |project, cx| {
|
||||||
|
project.worktrees(cx).next().unwrap().read(cx).id()
|
||||||
|
});
|
||||||
|
|
||||||
|
let item1 = cx.add_view(window_id, |_| {
|
||||||
|
let mut item = TestItem::new();
|
||||||
|
item.project_path = Some((worktree_id, "one.txt").into());
|
||||||
|
item
|
||||||
|
});
|
||||||
|
let item2 = cx.add_view(window_id, |_| {
|
||||||
|
let mut item = TestItem::new();
|
||||||
|
item.project_path = Some((worktree_id, "two.txt").into());
|
||||||
|
item
|
||||||
|
});
|
||||||
|
|
||||||
|
// Add an item to an empty pane
|
||||||
|
workspace.update(cx, |workspace, cx| workspace.add_item(Box::new(item1), cx));
|
||||||
|
project.read_with(cx, |project, cx| {
|
||||||
|
assert_eq!(
|
||||||
|
project.active_entry(),
|
||||||
|
project.entry_for_path(&(worktree_id, "one.txt").into(), cx)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
assert_eq!(
|
||||||
|
cx.current_window_title(window_id).as_deref(),
|
||||||
|
Some("one.txt — root1")
|
||||||
|
);
|
||||||
|
|
||||||
|
// Add a second item to a non-empty pane
|
||||||
|
workspace.update(cx, |workspace, cx| workspace.add_item(Box::new(item2), cx));
|
||||||
|
assert_eq!(
|
||||||
|
cx.current_window_title(window_id).as_deref(),
|
||||||
|
Some("two.txt — root1")
|
||||||
|
);
|
||||||
|
project.read_with(cx, |project, cx| {
|
||||||
|
assert_eq!(
|
||||||
|
project.active_entry(),
|
||||||
|
project.entry_for_path(&(worktree_id, "two.txt").into(), cx)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Close the active item
|
||||||
|
workspace
|
||||||
|
.update(cx, |workspace, cx| {
|
||||||
|
Pane::close_active_item(workspace, &Default::default(), cx).unwrap()
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
cx.current_window_title(window_id).as_deref(),
|
||||||
|
Some("one.txt — root1")
|
||||||
|
);
|
||||||
|
project.read_with(cx, |project, cx| {
|
||||||
|
assert_eq!(
|
||||||
|
project.active_entry(),
|
||||||
|
project.entry_for_path(&(worktree_id, "one.txt").into(), cx)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Add a project folder
|
||||||
|
project
|
||||||
|
.update(cx, |project, cx| {
|
||||||
|
project.find_or_create_local_worktree("/root2", true, cx)
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
cx.current_window_title(window_id).as_deref(),
|
||||||
|
Some("one.txt — root1, root2")
|
||||||
|
);
|
||||||
|
|
||||||
|
// Remove a project folder
|
||||||
|
project.update(cx, |project, cx| {
|
||||||
|
project.remove_worktree(worktree_id, cx);
|
||||||
|
});
|
||||||
|
assert_eq!(
|
||||||
|
cx.current_window_title(window_id).as_deref(),
|
||||||
|
Some("one.txt — root2")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[gpui::test]
|
#[gpui::test]
|
||||||
async fn test_close_window(cx: &mut TestAppContext) {
|
async fn test_close_window(cx: &mut TestAppContext) {
|
||||||
cx.foreground().forbid_parking();
|
cx.foreground().forbid_parking();
|
||||||
@ -2456,18 +2607,6 @@ mod tests {
|
|||||||
cx.foreground().run_until_parked();
|
cx.foreground().run_until_parked();
|
||||||
assert!(!cx.has_pending_prompt(window_id));
|
assert!(!cx.has_pending_prompt(window_id));
|
||||||
assert_eq!(task.await.unwrap(), false);
|
assert_eq!(task.await.unwrap(), false);
|
||||||
|
|
||||||
// If there are multiple dirty items representing the same project entry.
|
|
||||||
workspace.update(cx, |w, cx| {
|
|
||||||
w.add_item(Box::new(item2.clone()), cx);
|
|
||||||
w.add_item(Box::new(item3.clone()), cx);
|
|
||||||
});
|
|
||||||
let task = workspace.update(cx, |w, cx| w.prepare_to_close(cx));
|
|
||||||
cx.foreground().run_until_parked();
|
|
||||||
cx.simulate_prompt_answer(window_id, 2 /* cancel */);
|
|
||||||
cx.foreground().run_until_parked();
|
|
||||||
assert!(!cx.has_pending_prompt(window_id));
|
|
||||||
assert_eq!(task.await.unwrap(), false);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[gpui::test]
|
#[gpui::test]
|
||||||
@ -2667,6 +2806,7 @@ mod tests {
|
|||||||
is_dirty: bool,
|
is_dirty: bool,
|
||||||
has_conflict: bool,
|
has_conflict: bool,
|
||||||
project_entry_ids: Vec<ProjectEntryId>,
|
project_entry_ids: Vec<ProjectEntryId>,
|
||||||
|
project_path: Option<ProjectPath>,
|
||||||
is_singleton: bool,
|
is_singleton: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2679,6 +2819,7 @@ mod tests {
|
|||||||
is_dirty: false,
|
is_dirty: false,
|
||||||
has_conflict: false,
|
has_conflict: false,
|
||||||
project_entry_ids: Vec::new(),
|
project_entry_ids: Vec::new(),
|
||||||
|
project_path: None,
|
||||||
is_singleton: true,
|
is_singleton: true,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -2704,7 +2845,7 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn project_path(&self, _: &AppContext) -> Option<ProjectPath> {
|
fn project_path(&self, _: &AppContext) -> Option<ProjectPath> {
|
||||||
None
|
self.project_path.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn project_entry_ids(&self, _: &AppContext) -> SmallVec<[ProjectEntryId; 3]> {
|
fn project_entry_ids(&self, _: &AppContext) -> SmallVec<[ProjectEntryId; 3]> {
|
||||||
@ -2763,5 +2904,9 @@ mod tests {
|
|||||||
self.reload_count += 1;
|
self.reload_count += 1;
|
||||||
Task::ready(Ok(()))
|
Task::ready(Ok(()))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn should_update_tab_on_event(_: &Self::Event) -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -229,6 +229,10 @@ pub fn menus() -> Vec<Menu<'static>> {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
Menu {
|
||||||
|
name: "Window",
|
||||||
|
items: vec![MenuItem::Separator],
|
||||||
|
},
|
||||||
Menu {
|
Menu {
|
||||||
name: "Help",
|
name: "Help",
|
||||||
items: vec![MenuItem::Action {
|
items: vec![MenuItem::Action {
|
||||||
|
Loading…
Reference in New Issue
Block a user