Merge branch 'main' into project-panel-with-new-mouse-events
This commit is contained in:
commit
20e1044d49
46 changed files with 7318 additions and 7059 deletions
|
@ -15,7 +15,7 @@ use gpui::{
|
|||
use project::{Project, ProjectEntryId, ProjectPath};
|
||||
use serde::Deserialize;
|
||||
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;
|
||||
|
||||
actions!(
|
||||
|
@ -109,6 +109,7 @@ pub enum Event {
|
|||
ActivateItem { local: bool },
|
||||
Remove,
|
||||
Split(SplitDirection),
|
||||
ChangeItemTitle,
|
||||
}
|
||||
|
||||
pub struct Pane {
|
||||
|
@ -334,9 +335,20 @@ impl Pane {
|
|||
item.set_nav_history(pane.read(cx).nav_history.clone(), cx);
|
||||
item.added_to_pane(workspace, pane.clone(), cx);
|
||||
pane.update(cx, |pane, cx| {
|
||||
let item_idx = cmp::min(pane.active_item_index + 1, pane.items.len());
|
||||
pane.items.insert(item_idx, item);
|
||||
pane.activate_item(item_idx, activate_pane, focus_item, cx);
|
||||
// If there is already an active item, then insert the new item
|
||||
// right after it. Otherwise, adjust the `active_item_index` field
|
||||
// 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();
|
||||
});
|
||||
}
|
||||
|
@ -383,11 +395,12 @@ impl Pane {
|
|||
use NavigationMode::{GoingBack, GoingForward};
|
||||
if index < self.items.len() {
|
||||
let prev_active_item_ix = mem::replace(&mut self.active_item_index, index);
|
||||
if matches!(self.nav_history.borrow().mode, GoingBack | GoingForward)
|
||||
|| (prev_active_item_ix != self.active_item_index
|
||||
&& prev_active_item_ix < self.items.len())
|
||||
if prev_active_item_ix != self.active_item_index
|
||||
|| matches!(self.nav_history.borrow().mode, GoingBack | GoingForward)
|
||||
{
|
||||
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 {
|
||||
local: activate_pane,
|
||||
});
|
||||
|
@ -424,7 +437,7 @@ impl Pane {
|
|||
self.activate_item(index, true, true, cx);
|
||||
}
|
||||
|
||||
fn close_active_item(
|
||||
pub fn close_active_item(
|
||||
workspace: &mut Workspace,
|
||||
_: &CloseActiveItem,
|
||||
cx: &mut ViewContext<Workspace>,
|
||||
|
|
|
@ -37,6 +37,7 @@ use status_bar::StatusBar;
|
|||
pub use status_bar::StatusItemView;
|
||||
use std::{
|
||||
any::{Any, TypeId},
|
||||
borrow::Cow,
|
||||
cell::RefCell,
|
||||
fmt,
|
||||
future::Future,
|
||||
|
@ -543,7 +544,10 @@ impl<T: Item> ItemHandle for ViewHandle<T> {
|
|||
}
|
||||
|
||||
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();
|
||||
|
@ -755,6 +759,9 @@ impl Workspace {
|
|||
project::Event::CollaboratorLeft(peer_id) => {
|
||||
this.collaborator_left(*peer_id, cx);
|
||||
}
|
||||
project::Event::WorktreeRemoved(_) | project::Event::WorktreeAdded => {
|
||||
this.update_window_title(cx);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
if project.read(cx).is_read_only() {
|
||||
|
@ -766,14 +773,8 @@ impl Workspace {
|
|||
|
||||
let pane = cx.add_view(|cx| Pane::new(cx));
|
||||
let pane_id = pane.id();
|
||||
cx.observe(&pane, move |me, _, cx| {
|
||||
let active_entry = me.active_project_path(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)
|
||||
cx.subscribe(&pane, move |this, _, event, cx| {
|
||||
this.handle_pane_event(pane_id, event, cx)
|
||||
})
|
||||
.detach();
|
||||
cx.focus(&pane);
|
||||
|
@ -836,6 +837,11 @@ impl Workspace {
|
|||
_observe_current_user,
|
||||
};
|
||||
this.project_remote_id_changed(this.project.read(cx).remote_id(), cx);
|
||||
|
||||
cx.defer(|this, cx| {
|
||||
this.update_window_title(cx);
|
||||
});
|
||||
|
||||
this
|
||||
}
|
||||
|
||||
|
@ -1258,14 +1264,8 @@ impl Workspace {
|
|||
fn add_pane(&mut self, cx: &mut ViewContext<Self>) -> ViewHandle<Pane> {
|
||||
let pane = cx.add_view(|cx| Pane::new(cx));
|
||||
let pane_id = pane.id();
|
||||
cx.observe(&pane, move |me, _, cx| {
|
||||
let active_entry = me.active_project_path(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)
|
||||
cx.subscribe(&pane, move |this, _, event, cx| {
|
||||
this.handle_pane_event(pane_id, event, cx)
|
||||
})
|
||||
.detach();
|
||||
self.panes.push(pane.clone());
|
||||
|
@ -1405,6 +1405,7 @@ impl Workspace {
|
|||
self.status_bar.update(cx, |status_bar, cx| {
|
||||
status_bar.set_active_pane(&self.active_pane, cx);
|
||||
});
|
||||
self.active_item_path_changed(cx);
|
||||
cx.focus(&self.active_pane);
|
||||
cx.notify();
|
||||
}
|
||||
|
@ -1439,6 +1440,14 @@ impl Workspace {
|
|||
if *local {
|
||||
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 {
|
||||
|
@ -1471,6 +1480,8 @@ impl Workspace {
|
|||
self.unfollow(&pane, cx);
|
||||
self.last_leaders_by_pane.remove(&pane.downgrade());
|
||||
cx.notify();
|
||||
} else {
|
||||
self.active_item_path_changed(cx);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1658,15 +1669,7 @@ impl Workspace {
|
|||
|
||||
fn render_titlebar(&self, theme: &Theme, cx: &mut RenderContext<Self>) -> ElementBox {
|
||||
let mut worktree_root_names = String::new();
|
||||
{
|
||||
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(", ");
|
||||
}
|
||||
}
|
||||
}
|
||||
self.worktree_root_names(&mut worktree_root_names, cx);
|
||||
|
||||
ConstrainedBox::new(
|
||||
Container::new(
|
||||
|
@ -1702,6 +1705,50 @@ impl Workspace {
|
|||
.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> {
|
||||
let mut collaborators = self
|
||||
.project
|
||||
|
@ -2437,6 +2484,110 @@ mod tests {
|
|||
use project::{FakeFs, Project, ProjectEntryId};
|
||||
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]
|
||||
async fn test_close_window(cx: &mut TestAppContext) {
|
||||
cx.foreground().forbid_parking();
|
||||
|
@ -2476,18 +2627,6 @@ mod tests {
|
|||
cx.foreground().run_until_parked();
|
||||
assert!(!cx.has_pending_prompt(window_id));
|
||||
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]
|
||||
|
@ -2687,6 +2826,7 @@ mod tests {
|
|||
is_dirty: bool,
|
||||
has_conflict: bool,
|
||||
project_entry_ids: Vec<ProjectEntryId>,
|
||||
project_path: Option<ProjectPath>,
|
||||
is_singleton: bool,
|
||||
}
|
||||
|
||||
|
@ -2699,6 +2839,7 @@ mod tests {
|
|||
is_dirty: false,
|
||||
has_conflict: false,
|
||||
project_entry_ids: Vec::new(),
|
||||
project_path: None,
|
||||
is_singleton: true,
|
||||
}
|
||||
}
|
||||
|
@ -2724,7 +2865,7 @@ mod tests {
|
|||
}
|
||||
|
||||
fn project_path(&self, _: &AppContext) -> Option<ProjectPath> {
|
||||
None
|
||||
self.project_path.clone()
|
||||
}
|
||||
|
||||
fn project_entry_ids(&self, _: &AppContext) -> SmallVec<[ProjectEntryId; 3]> {
|
||||
|
@ -2783,5 +2924,9 @@ mod tests {
|
|||
self.reload_count += 1;
|
||||
Task::ready(Ok(()))
|
||||
}
|
||||
|
||||
fn should_update_tab_on_event(_: &Self::Event) -> bool {
|
||||
true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue