Merge branch 'main' into in-app-feedback
This commit is contained in:
commit
f2a5a4d0fd
135 changed files with 3316 additions and 2821 deletions
|
@ -2,8 +2,10 @@ use collections::HashMap;
|
|||
use gpui::{
|
||||
actions,
|
||||
elements::{ChildView, Container, Empty, MouseEventHandler, ParentElement, Side, Stack, Svg},
|
||||
geometry::vector::Vector2F,
|
||||
impl_internal_actions, Border, CursorStyle, Element, ElementBox, Entity, MouseButton,
|
||||
MutableAppContext, RenderContext, View, ViewContext, ViewHandle, WeakViewHandle,
|
||||
MutableAppContext, RenderContext, SizeConstraint, View, ViewContext, ViewHandle,
|
||||
WeakViewHandle,
|
||||
};
|
||||
use serde::Deserialize;
|
||||
use settings::{DockAnchor, Settings};
|
||||
|
@ -312,7 +314,27 @@ impl Dock {
|
|||
}
|
||||
});
|
||||
|
||||
resizable.flex(5., false).boxed()
|
||||
if anchor == DockAnchor::Right {
|
||||
resizable
|
||||
.constrained()
|
||||
.dynamically(|constraint, cx| {
|
||||
SizeConstraint::new(
|
||||
Vector2F::new(20., constraint.min.y()),
|
||||
Vector2F::new(cx.window_size.x() * 0.8, constraint.max.y()),
|
||||
)
|
||||
})
|
||||
.boxed()
|
||||
} else {
|
||||
resizable
|
||||
.constrained()
|
||||
.dynamically(|constraint, cx| {
|
||||
SizeConstraint::new(
|
||||
Vector2F::new(constraint.min.x(), 50.),
|
||||
Vector2F::new(constraint.max.x(), cx.window_size.y() * 0.8),
|
||||
)
|
||||
})
|
||||
.boxed()
|
||||
}
|
||||
}
|
||||
DockAnchor::Expanded => {
|
||||
enum ExpandedDockWash {}
|
||||
|
@ -470,7 +492,7 @@ mod tests {
|
|||
use super::*;
|
||||
use crate::{
|
||||
dock,
|
||||
item::test::TestItem,
|
||||
item::{self, test::TestItem},
|
||||
persistence::model::{
|
||||
SerializedItem, SerializedPane, SerializedPaneGroup, SerializedWorkspace,
|
||||
},
|
||||
|
@ -492,7 +514,7 @@ mod tests {
|
|||
Settings::test_async(cx);
|
||||
|
||||
cx.update(|cx| {
|
||||
register_deserializable_item::<TestItem>(cx);
|
||||
register_deserializable_item::<item::test::TestItem>(cx);
|
||||
});
|
||||
|
||||
let serialized_workspace = SerializedWorkspace {
|
||||
|
@ -508,7 +530,7 @@ mod tests {
|
|||
children: vec![SerializedItem {
|
||||
active: true,
|
||||
item_id: 0,
|
||||
kind: "test".into(),
|
||||
kind: "TestItem".into(),
|
||||
}],
|
||||
},
|
||||
left_sidebar_open: false,
|
||||
|
@ -623,6 +645,20 @@ mod tests {
|
|||
cx.assert_dock_pane_active();
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_activate_next_and_prev_pane(cx: &mut TestAppContext) {
|
||||
let mut cx = DockTestContext::new(cx).await;
|
||||
|
||||
cx.move_dock(DockAnchor::Right);
|
||||
cx.assert_dock_pane_active();
|
||||
|
||||
cx.update_workspace(|workspace, cx| workspace.activate_next_pane(cx));
|
||||
cx.assert_dock_pane_active();
|
||||
|
||||
cx.update_workspace(|workspace, cx| workspace.activate_previous_pane(cx));
|
||||
cx.assert_dock_pane_active();
|
||||
}
|
||||
|
||||
struct DockTestContext<'a> {
|
||||
pub cx: &'a mut TestAppContext,
|
||||
pub window_id: usize,
|
||||
|
|
|
@ -49,8 +49,7 @@ pub trait Item: View {
|
|||
}
|
||||
fn tab_content(&self, detail: Option<usize>, style: &theme::Tab, cx: &AppContext)
|
||||
-> ElementBox;
|
||||
fn project_path(&self, cx: &AppContext) -> Option<ProjectPath>;
|
||||
fn project_entry_ids(&self, cx: &AppContext) -> SmallVec<[ProjectEntryId; 3]>;
|
||||
fn for_each_project_item(&self, _: &AppContext, _: &mut dyn FnMut(usize, &dyn project::Item));
|
||||
fn is_singleton(&self, cx: &AppContext) -> bool;
|
||||
fn set_nav_history(&mut self, _: ItemNavHistory, _: &mut ViewContext<Self>);
|
||||
fn clone_on_split(&self, _workspace_id: WorkspaceId, _: &mut ViewContext<Self>) -> Option<Self>
|
||||
|
@ -147,6 +146,8 @@ pub trait ItemHandle: 'static + fmt::Debug {
|
|||
-> ElementBox;
|
||||
fn project_path(&self, cx: &AppContext) -> Option<ProjectPath>;
|
||||
fn project_entry_ids(&self, cx: &AppContext) -> SmallVec<[ProjectEntryId; 3]>;
|
||||
fn project_item_model_ids(&self, cx: &AppContext) -> SmallVec<[usize; 3]>;
|
||||
fn for_each_project_item(&self, _: &AppContext, _: &mut dyn FnMut(usize, &dyn project::Item));
|
||||
fn is_singleton(&self, cx: &AppContext) -> bool;
|
||||
fn boxed_clone(&self) -> Box<dyn ItemHandle>;
|
||||
fn clone_on_split(
|
||||
|
@ -240,11 +241,36 @@ impl<T: Item> ItemHandle for ViewHandle<T> {
|
|||
}
|
||||
|
||||
fn project_path(&self, cx: &AppContext) -> Option<ProjectPath> {
|
||||
self.read(cx).project_path(cx)
|
||||
let this = self.read(cx);
|
||||
let mut result = None;
|
||||
if this.is_singleton(cx) {
|
||||
this.for_each_project_item(cx, &mut |_, item| {
|
||||
result = item.project_path(cx);
|
||||
});
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
fn project_entry_ids(&self, cx: &AppContext) -> SmallVec<[ProjectEntryId; 3]> {
|
||||
self.read(cx).project_entry_ids(cx)
|
||||
let mut result = SmallVec::new();
|
||||
self.read(cx).for_each_project_item(cx, &mut |_, item| {
|
||||
if let Some(id) = item.entry_id(cx) {
|
||||
result.push(id);
|
||||
}
|
||||
});
|
||||
result
|
||||
}
|
||||
|
||||
fn project_item_model_ids(&self, cx: &AppContext) -> SmallVec<[usize; 3]> {
|
||||
let mut result = SmallVec::new();
|
||||
self.read(cx).for_each_project_item(cx, &mut |id, _| {
|
||||
result.push(id);
|
||||
});
|
||||
result
|
||||
}
|
||||
|
||||
fn for_each_project_item(&self, cx: &AppContext, f: &mut dyn FnMut(usize, &dyn project::Item)) {
|
||||
self.read(cx).for_each_project_item(cx, f)
|
||||
}
|
||||
|
||||
fn is_singleton(&self, cx: &AppContext) -> bool {
|
||||
|
@ -582,7 +608,7 @@ impl<T: Item> WeakItemHandle for WeakViewHandle<T> {
|
|||
}
|
||||
|
||||
pub trait ProjectItem: Item {
|
||||
type Item: project::Item;
|
||||
type Item: project::Item + gpui::Entity;
|
||||
|
||||
fn for_project_item(
|
||||
project: ModelHandle<Project>,
|
||||
|
@ -690,18 +716,19 @@ impl<T: FollowableItem> FollowableItemHandle for ViewHandle<T> {
|
|||
|
||||
#[cfg(test)]
|
||||
pub(crate) mod test {
|
||||
use std::{any::Any, borrow::Cow, cell::Cell};
|
||||
|
||||
use gpui::{
|
||||
elements::Empty, AppContext, Element, ElementBox, Entity, ModelHandle, RenderContext, Task,
|
||||
View, ViewContext, ViewHandle, WeakViewHandle,
|
||||
};
|
||||
use project::{Project, ProjectEntryId, ProjectPath};
|
||||
use smallvec::SmallVec;
|
||||
|
||||
use crate::{sidebar::SidebarItem, ItemId, ItemNavHistory, Pane, Workspace, WorkspaceId};
|
||||
|
||||
use super::{Item, ItemEvent};
|
||||
use crate::{sidebar::SidebarItem, ItemId, ItemNavHistory, Pane, Workspace, WorkspaceId};
|
||||
use gpui::{
|
||||
elements::Empty, AppContext, Element, ElementBox, Entity, ModelHandle, MutableAppContext,
|
||||
RenderContext, Task, View, ViewContext, ViewHandle, WeakViewHandle,
|
||||
};
|
||||
use project::{Project, ProjectEntryId, ProjectPath, WorktreeId};
|
||||
use std::{any::Any, borrow::Cow, cell::Cell, path::Path};
|
||||
|
||||
pub struct TestProjectItem {
|
||||
pub entry_id: Option<ProjectEntryId>,
|
||||
pub project_path: Option<ProjectPath>,
|
||||
}
|
||||
|
||||
pub struct TestItem {
|
||||
pub workspace_id: WorkspaceId,
|
||||
|
@ -713,13 +740,26 @@ pub(crate) mod test {
|
|||
pub is_dirty: bool,
|
||||
pub is_singleton: bool,
|
||||
pub has_conflict: bool,
|
||||
pub project_entry_ids: Vec<ProjectEntryId>,
|
||||
pub project_path: Option<ProjectPath>,
|
||||
pub project_items: Vec<ModelHandle<TestProjectItem>>,
|
||||
pub nav_history: Option<ItemNavHistory>,
|
||||
pub tab_descriptions: Option<Vec<&'static str>>,
|
||||
pub tab_detail: Cell<Option<usize>>,
|
||||
}
|
||||
|
||||
impl Entity for TestProjectItem {
|
||||
type Event = ();
|
||||
}
|
||||
|
||||
impl project::Item for TestProjectItem {
|
||||
fn entry_id(&self, _: &AppContext) -> Option<ProjectEntryId> {
|
||||
self.entry_id
|
||||
}
|
||||
|
||||
fn project_path(&self, _: &AppContext) -> Option<ProjectPath> {
|
||||
self.project_path.clone()
|
||||
}
|
||||
}
|
||||
|
||||
pub enum TestItemEvent {
|
||||
Edit,
|
||||
}
|
||||
|
@ -735,8 +775,7 @@ pub(crate) mod test {
|
|||
is_dirty: self.is_dirty,
|
||||
is_singleton: self.is_singleton,
|
||||
has_conflict: self.has_conflict,
|
||||
project_entry_ids: self.project_entry_ids.clone(),
|
||||
project_path: self.project_path.clone(),
|
||||
project_items: self.project_items.clone(),
|
||||
nav_history: None,
|
||||
tab_descriptions: None,
|
||||
tab_detail: Default::default(),
|
||||
|
@ -745,6 +784,27 @@ pub(crate) mod test {
|
|||
}
|
||||
}
|
||||
|
||||
impl TestProjectItem {
|
||||
pub fn new(id: u64, path: &str, cx: &mut MutableAppContext) -> ModelHandle<Self> {
|
||||
let entry_id = Some(ProjectEntryId::from_proto(id));
|
||||
let project_path = Some(ProjectPath {
|
||||
worktree_id: WorktreeId::from_usize(0),
|
||||
path: Path::new(path).into(),
|
||||
});
|
||||
cx.add_model(|_| Self {
|
||||
entry_id,
|
||||
project_path,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn new_untitled(cx: &mut MutableAppContext) -> ModelHandle<Self> {
|
||||
cx.add_model(|_| Self {
|
||||
project_path: None,
|
||||
entry_id: None,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl TestItem {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
|
@ -755,8 +815,7 @@ pub(crate) mod test {
|
|||
reload_count: 0,
|
||||
is_dirty: false,
|
||||
has_conflict: false,
|
||||
project_entry_ids: Vec::new(),
|
||||
project_path: None,
|
||||
project_items: Vec::new(),
|
||||
is_singleton: true,
|
||||
nav_history: None,
|
||||
tab_descriptions: None,
|
||||
|
@ -781,13 +840,19 @@ pub(crate) mod test {
|
|||
self
|
||||
}
|
||||
|
||||
pub fn with_project_entry_ids(mut self, project_entry_ids: &[u64]) -> Self {
|
||||
self.project_entry_ids.extend(
|
||||
project_entry_ids
|
||||
.iter()
|
||||
.copied()
|
||||
.map(ProjectEntryId::from_proto),
|
||||
);
|
||||
pub fn with_dirty(mut self, dirty: bool) -> Self {
|
||||
self.is_dirty = dirty;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_conflict(mut self, has_conflict: bool) -> Self {
|
||||
self.has_conflict = has_conflict;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_project_items(mut self, items: &[ModelHandle<TestProjectItem>]) -> Self {
|
||||
self.project_items.clear();
|
||||
self.project_items.extend(items.iter().cloned());
|
||||
self
|
||||
}
|
||||
|
||||
|
@ -830,12 +895,14 @@ pub(crate) mod test {
|
|||
Empty::new().boxed()
|
||||
}
|
||||
|
||||
fn project_path(&self, _: &AppContext) -> Option<ProjectPath> {
|
||||
self.project_path.clone()
|
||||
}
|
||||
|
||||
fn project_entry_ids(&self, _: &AppContext) -> SmallVec<[ProjectEntryId; 3]> {
|
||||
self.project_entry_ids.iter().copied().collect()
|
||||
fn for_each_project_item(
|
||||
&self,
|
||||
cx: &AppContext,
|
||||
f: &mut dyn FnMut(usize, &dyn project::Item),
|
||||
) {
|
||||
self.project_items
|
||||
.iter()
|
||||
.for_each(|item| f(item.id(), item.read(cx)))
|
||||
}
|
||||
|
||||
fn is_singleton(&self, _: &AppContext) -> bool {
|
||||
|
@ -879,8 +946,12 @@ pub(crate) mod test {
|
|||
self.has_conflict
|
||||
}
|
||||
|
||||
fn can_save(&self, _: &AppContext) -> bool {
|
||||
!self.project_entry_ids.is_empty()
|
||||
fn can_save(&self, cx: &AppContext) -> bool {
|
||||
!self.project_items.is_empty()
|
||||
&& self
|
||||
.project_items
|
||||
.iter()
|
||||
.all(|item| item.read(cx).entry_id.is_some())
|
||||
}
|
||||
|
||||
fn save(
|
||||
|
@ -919,7 +990,7 @@ pub(crate) mod test {
|
|||
}
|
||||
|
||||
fn serialized_item_kind() -> Option<&'static str> {
|
||||
None
|
||||
Some("TestItem")
|
||||
}
|
||||
|
||||
fn deserialize(
|
||||
|
|
|
@ -488,7 +488,7 @@ impl Pane {
|
|||
) -> Box<dyn ItemHandle> {
|
||||
let existing_item = pane.update(cx, |pane, cx| {
|
||||
for (index, item) in pane.items.iter().enumerate() {
|
||||
if item.project_path(cx).is_some()
|
||||
if item.is_singleton(cx)
|
||||
&& item.project_entry_ids(cx).as_slice() == [project_entry_id]
|
||||
{
|
||||
let item = item.boxed_clone();
|
||||
|
@ -810,13 +810,13 @@ impl Pane {
|
|||
items_to_close.sort_by_key(|item| !item.is_singleton(cx));
|
||||
|
||||
cx.spawn(|workspace, mut cx| async move {
|
||||
let mut saved_project_entry_ids = HashSet::default();
|
||||
let mut saved_project_items_ids = HashSet::default();
|
||||
for item in items_to_close.clone() {
|
||||
// Find the item's current index and its set of project entries. Avoid
|
||||
// Find the item's current index and its set of project item models. Avoid
|
||||
// storing these in advance, in case they have changed since this task
|
||||
// was started.
|
||||
let (item_ix, mut project_entry_ids) = pane.read_with(&cx, |pane, cx| {
|
||||
(pane.index_for_item(&*item), item.project_entry_ids(cx))
|
||||
let (item_ix, mut project_item_ids) = pane.read_with(&cx, |pane, cx| {
|
||||
(pane.index_for_item(&*item), item.project_item_model_ids(cx))
|
||||
});
|
||||
let item_ix = if let Some(ix) = item_ix {
|
||||
ix
|
||||
|
@ -824,30 +824,23 @@ impl Pane {
|
|||
continue;
|
||||
};
|
||||
|
||||
// If an item hasn't yet been associated with a project entry, then always
|
||||
// prompt to save it before closing it. Otherwise, check if the item has
|
||||
// any project entries that are not open anywhere else in the workspace,
|
||||
// AND that the user has not already been prompted to save. If there are
|
||||
// any such project entries, prompt the user to save this item.
|
||||
let should_save = if project_entry_ids.is_empty() {
|
||||
true
|
||||
} else {
|
||||
workspace.read_with(&cx, |workspace, cx| {
|
||||
for item in workspace.items(cx) {
|
||||
if !items_to_close
|
||||
.iter()
|
||||
.any(|item_to_close| item_to_close.id() == item.id())
|
||||
{
|
||||
let other_project_entry_ids = item.project_entry_ids(cx);
|
||||
project_entry_ids
|
||||
.retain(|id| !other_project_entry_ids.contains(id));
|
||||
}
|
||||
// Check if this view has any project items that are not open anywhere else
|
||||
// in the workspace, AND that the user has not already been prompted to save.
|
||||
// If there are any such project entries, prompt the user to save this item.
|
||||
workspace.read_with(&cx, |workspace, cx| {
|
||||
for item in workspace.items(cx) {
|
||||
if !items_to_close
|
||||
.iter()
|
||||
.any(|item_to_close| item_to_close.id() == item.id())
|
||||
{
|
||||
let other_project_item_ids = item.project_item_model_ids(cx);
|
||||
project_item_ids.retain(|id| !other_project_item_ids.contains(id));
|
||||
}
|
||||
});
|
||||
project_entry_ids
|
||||
.iter()
|
||||
.any(|id| saved_project_entry_ids.insert(*id))
|
||||
};
|
||||
}
|
||||
});
|
||||
let should_save = project_item_ids
|
||||
.iter()
|
||||
.any(|id| saved_project_items_ids.insert(*id));
|
||||
|
||||
if should_save
|
||||
&& !Self::save_item(project.clone(), &pane, item_ix, &*item, true, &mut cx)
|
||||
|
@ -1458,7 +1451,11 @@ impl View for Pane {
|
|||
0,
|
||||
self.active_item_index + 1,
|
||||
false,
|
||||
Some(100.),
|
||||
if self.docked.is_some() {
|
||||
None
|
||||
} else {
|
||||
Some(100.)
|
||||
},
|
||||
cx,
|
||||
{
|
||||
let toolbar = self.toolbar.clone();
|
||||
|
@ -1679,7 +1676,7 @@ mod tests {
|
|||
use std::sync::Arc;
|
||||
|
||||
use super::*;
|
||||
use crate::item::test::TestItem;
|
||||
use crate::item::test::{TestItem, TestProjectItem};
|
||||
use gpui::{executor::Deterministic, TestAppContext};
|
||||
use project::FakeFs;
|
||||
|
||||
|
@ -1868,7 +1865,7 @@ mod tests {
|
|||
let item = TestItem::new()
|
||||
.with_singleton(true)
|
||||
.with_label("buffer 1")
|
||||
.with_project_entry_ids(&[1]);
|
||||
.with_project_items(&[TestProjectItem::new(1, "one.txt", cx)]);
|
||||
|
||||
Pane::add_item(
|
||||
workspace,
|
||||
|
@ -1887,7 +1884,7 @@ mod tests {
|
|||
let item = TestItem::new()
|
||||
.with_singleton(true)
|
||||
.with_label("buffer 1")
|
||||
.with_project_entry_ids(&[1]);
|
||||
.with_project_items(&[TestProjectItem::new(1, "1.txt", cx)]);
|
||||
|
||||
Pane::add_item(
|
||||
workspace,
|
||||
|
@ -1906,7 +1903,7 @@ mod tests {
|
|||
let item = TestItem::new()
|
||||
.with_singleton(true)
|
||||
.with_label("buffer 2")
|
||||
.with_project_entry_ids(&[2]);
|
||||
.with_project_items(&[TestProjectItem::new(2, "2.txt", cx)]);
|
||||
|
||||
Pane::add_item(
|
||||
workspace,
|
||||
|
@ -1925,7 +1922,7 @@ mod tests {
|
|||
let item = TestItem::new()
|
||||
.with_singleton(false)
|
||||
.with_label("multibuffer 1")
|
||||
.with_project_entry_ids(&[1]);
|
||||
.with_project_items(&[TestProjectItem::new(1, "1.txt", cx)]);
|
||||
|
||||
Pane::add_item(
|
||||
workspace,
|
||||
|
@ -1944,7 +1941,7 @@ mod tests {
|
|||
let item = TestItem::new()
|
||||
.with_singleton(false)
|
||||
.with_label("multibuffer 1b")
|
||||
.with_project_entry_ids(&[1]);
|
||||
.with_project_items(&[TestProjectItem::new(1, "1.txt", cx)]);
|
||||
|
||||
Pane::add_item(
|
||||
workspace,
|
||||
|
|
|
@ -216,7 +216,9 @@ impl WorkspaceDb {
|
|||
let mut result = Vec::new();
|
||||
let mut delete_tasks = Vec::new();
|
||||
for (id, location) in self.recent_workspaces()? {
|
||||
if location.paths().iter().all(|path| path.exists()) {
|
||||
if location.paths().iter().all(|path| path.exists())
|
||||
&& location.paths().iter().any(|path| path.is_dir())
|
||||
{
|
||||
result.push((id, location));
|
||||
} else {
|
||||
delete_tasks.push(self.delete_stale_workspace(id));
|
||||
|
@ -227,14 +229,13 @@ impl WorkspaceDb {
|
|||
Ok(result)
|
||||
}
|
||||
|
||||
query! {
|
||||
pub fn last_workspace() -> Result<Option<WorkspaceLocation>> {
|
||||
SELECT workspace_location
|
||||
FROM workspaces
|
||||
WHERE workspace_location IS NOT NULL
|
||||
ORDER BY timestamp DESC
|
||||
LIMIT 1
|
||||
}
|
||||
pub async fn last_workspace(&self) -> Result<Option<WorkspaceLocation>> {
|
||||
Ok(self
|
||||
.recent_workspaces_on_disk()
|
||||
.await?
|
||||
.into_iter()
|
||||
.next()
|
||||
.map(|(_, location)| location))
|
||||
}
|
||||
|
||||
fn get_center_pane_group(&self, workspace_id: WorkspaceId) -> Result<SerializedPaneGroup> {
|
||||
|
|
|
@ -8,12 +8,11 @@ use futures::StreamExt;
|
|||
use gpui::{
|
||||
elements::*,
|
||||
geometry::{rect::RectF, vector::vec2f},
|
||||
Entity, ModelHandle, MouseButton, RenderContext, Task, View, ViewContext, ViewHandle,
|
||||
WeakViewHandle,
|
||||
AppContext, Entity, ModelHandle, MouseButton, RenderContext, Task, View, ViewContext,
|
||||
ViewHandle, WeakViewHandle,
|
||||
};
|
||||
use project::Project;
|
||||
use settings::Settings;
|
||||
use smallvec::SmallVec;
|
||||
use std::{
|
||||
path::PathBuf,
|
||||
sync::{Arc, Weak},
|
||||
|
@ -106,7 +105,7 @@ impl Item for SharedScreen {
|
|||
&self,
|
||||
_: Option<usize>,
|
||||
style: &theme::Tab,
|
||||
_: &gpui::AppContext,
|
||||
_: &AppContext,
|
||||
) -> gpui::ElementBox {
|
||||
Flex::row()
|
||||
.with_child(
|
||||
|
@ -130,15 +129,9 @@ impl Item for SharedScreen {
|
|||
.boxed()
|
||||
}
|
||||
|
||||
fn project_path(&self, _: &gpui::AppContext) -> Option<project::ProjectPath> {
|
||||
Default::default()
|
||||
}
|
||||
fn for_each_project_item(&self, _: &AppContext, _: &mut dyn FnMut(usize, &dyn project::Item)) {}
|
||||
|
||||
fn project_entry_ids(&self, _: &gpui::AppContext) -> SmallVec<[project::ProjectEntryId; 3]> {
|
||||
Default::default()
|
||||
}
|
||||
|
||||
fn is_singleton(&self, _: &gpui::AppContext) -> bool {
|
||||
fn is_singleton(&self, _: &AppContext) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
|
@ -155,7 +148,7 @@ impl Item for SharedScreen {
|
|||
Some(Self::new(&track, self.peer_id, self.user.clone(), cx))
|
||||
}
|
||||
|
||||
fn can_save(&self, _: &gpui::AppContext) -> bool {
|
||||
fn can_save(&self, _: &AppContext) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
|
|
|
@ -32,18 +32,20 @@ use futures::{
|
|||
use gpui::{
|
||||
actions,
|
||||
elements::*,
|
||||
geometry::vector::Vector2F,
|
||||
impl_actions, impl_internal_actions,
|
||||
keymap_matcher::KeymapContext,
|
||||
platform::{CursorStyle, WindowOptions},
|
||||
AnyModelHandle, AnyViewHandle, AppContext, AsyncAppContext, Entity, ModelContext, ModelHandle,
|
||||
MouseButton, MutableAppContext, PathPromptOptions, PromptLevel, RenderContext, Task, View,
|
||||
ViewContext, ViewHandle, WeakViewHandle,
|
||||
MouseButton, MutableAppContext, PathPromptOptions, PromptLevel, RenderContext, SizeConstraint,
|
||||
Task, View, ViewContext, ViewHandle, WeakViewHandle,
|
||||
};
|
||||
use item::{FollowableItem, FollowableItemHandle, Item, ItemHandle, ProjectItem};
|
||||
use language::LanguageRegistry;
|
||||
use std::{
|
||||
any::TypeId,
|
||||
borrow::Cow,
|
||||
cmp,
|
||||
future::Future,
|
||||
path::{Path, PathBuf},
|
||||
sync::Arc,
|
||||
|
@ -98,6 +100,7 @@ actions!(
|
|||
NewTerminal,
|
||||
NewSearch,
|
||||
Feedback
|
||||
ShowNotif,
|
||||
]
|
||||
);
|
||||
|
||||
|
@ -231,54 +234,8 @@ pub fn init(app_state: Arc<AppState>, cx: &mut MutableAppContext) {
|
|||
workspace.toggle_sidebar(SidebarSide::Right, cx);
|
||||
});
|
||||
cx.add_action(Workspace::activate_pane_at_index);
|
||||
cx.add_action(
|
||||
|workspace: &mut Workspace,
|
||||
SplitWithItem {
|
||||
from,
|
||||
pane_to_split,
|
||||
item_id_to_move,
|
||||
split_direction,
|
||||
}: &_,
|
||||
cx| {
|
||||
workspace.split_pane_with_item(
|
||||
from.clone(),
|
||||
pane_to_split.clone(),
|
||||
*item_id_to_move,
|
||||
*split_direction,
|
||||
cx,
|
||||
)
|
||||
},
|
||||
);
|
||||
|
||||
cx.add_async_action(
|
||||
|workspace: &mut Workspace,
|
||||
SplitWithProjectEntry {
|
||||
pane_to_split,
|
||||
split_direction,
|
||||
project_entry,
|
||||
}: &_,
|
||||
cx| {
|
||||
pane_to_split.upgrade(cx).and_then(|pane_to_split| {
|
||||
let new_pane = workspace.add_pane(cx);
|
||||
workspace
|
||||
.center
|
||||
.split(&pane_to_split, &new_pane, *split_direction)
|
||||
.unwrap();
|
||||
|
||||
workspace
|
||||
.project
|
||||
.read(cx)
|
||||
.path_for_entry(*project_entry, cx)
|
||||
.map(|path| {
|
||||
let task = workspace.open_path(path, Some(new_pane.downgrade()), true, cx);
|
||||
cx.foreground().spawn(async move {
|
||||
task.await?;
|
||||
Ok(())
|
||||
})
|
||||
})
|
||||
})
|
||||
},
|
||||
);
|
||||
cx.add_action(Workspace::split_pane_with_item);
|
||||
cx.add_action(Workspace::split_pane_with_project_entry);
|
||||
|
||||
cx.add_async_action(
|
||||
|workspace: &mut Workspace,
|
||||
|
@ -1416,29 +1373,21 @@ impl Workspace {
|
|||
}
|
||||
|
||||
pub fn activate_next_pane(&mut self, cx: &mut ViewContext<Self>) {
|
||||
let next_pane = {
|
||||
let panes = self.center.panes();
|
||||
let ix = panes
|
||||
.iter()
|
||||
.position(|pane| **pane == self.active_pane)
|
||||
.unwrap();
|
||||
let panes = self.center.panes();
|
||||
if let Some(ix) = panes.iter().position(|pane| **pane == self.active_pane) {
|
||||
let next_ix = (ix + 1) % panes.len();
|
||||
panes[next_ix].clone()
|
||||
};
|
||||
cx.focus(next_pane);
|
||||
let next_pane = panes[next_ix].clone();
|
||||
cx.focus(next_pane);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn activate_previous_pane(&mut self, cx: &mut ViewContext<Self>) {
|
||||
let prev_pane = {
|
||||
let panes = self.center.panes();
|
||||
let ix = panes
|
||||
.iter()
|
||||
.position(|pane| **pane == self.active_pane)
|
||||
.unwrap();
|
||||
let prev_ix = if ix == 0 { panes.len() - 1 } else { ix - 1 };
|
||||
panes[prev_ix].clone()
|
||||
};
|
||||
cx.focus(prev_pane);
|
||||
let panes = self.center.panes();
|
||||
if let Some(ix) = panes.iter().position(|pane| **pane == self.active_pane) {
|
||||
let prev_ix = cmp::min(ix.wrapping_sub(1), panes.len() - 1);
|
||||
let prev_pane = panes[prev_ix].clone();
|
||||
cx.focus(prev_pane);
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_pane_focused(&mut self, pane: ViewHandle<Pane>, cx: &mut ViewContext<Self>) {
|
||||
|
@ -1533,38 +1482,64 @@ impl Workspace {
|
|||
return None;
|
||||
}
|
||||
|
||||
pane.read(cx).active_item().map(|item| {
|
||||
let new_pane = self.add_pane(cx);
|
||||
if let Some(clone) = item.clone_on_split(self.database_id(), cx.as_mut()) {
|
||||
Pane::add_item(self, &new_pane, clone, true, true, None, cx);
|
||||
}
|
||||
self.center.split(&pane, &new_pane, direction).unwrap();
|
||||
cx.notify();
|
||||
new_pane
|
||||
})
|
||||
let item = pane.read(cx).active_item()?;
|
||||
let new_pane = self.add_pane(cx);
|
||||
if let Some(clone) = item.clone_on_split(self.database_id(), cx.as_mut()) {
|
||||
Pane::add_item(self, &new_pane, clone, true, true, None, cx);
|
||||
}
|
||||
self.center.split(&pane, &new_pane, direction).unwrap();
|
||||
cx.notify();
|
||||
Some(new_pane)
|
||||
}
|
||||
|
||||
pub fn split_pane_with_item(
|
||||
&mut self,
|
||||
from: WeakViewHandle<Pane>,
|
||||
pane_to_split: WeakViewHandle<Pane>,
|
||||
item_id_to_move: usize,
|
||||
split_direction: SplitDirection,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) {
|
||||
if let Some((pane_to_split, from)) = pane_to_split.upgrade(cx).zip(from.upgrade(cx)) {
|
||||
if &pane_to_split == self.dock_pane() {
|
||||
warn!("Can't split dock pane.");
|
||||
return;
|
||||
}
|
||||
|
||||
let new_pane = self.add_pane(cx);
|
||||
Pane::move_item(self, from.clone(), new_pane.clone(), item_id_to_move, 0, cx);
|
||||
self.center
|
||||
.split(&pane_to_split, &new_pane, split_direction)
|
||||
.unwrap();
|
||||
cx.notify();
|
||||
pub fn split_pane_with_item(&mut self, action: &SplitWithItem, cx: &mut ViewContext<Self>) {
|
||||
let Some(pane_to_split) = action.pane_to_split.upgrade(cx) else { return; };
|
||||
let Some(from) = action.from.upgrade(cx) else { return; };
|
||||
if &pane_to_split == self.dock_pane() {
|
||||
warn!("Can't split dock pane.");
|
||||
return;
|
||||
}
|
||||
|
||||
let new_pane = self.add_pane(cx);
|
||||
Pane::move_item(
|
||||
self,
|
||||
from.clone(),
|
||||
new_pane.clone(),
|
||||
action.item_id_to_move,
|
||||
0,
|
||||
cx,
|
||||
);
|
||||
self.center
|
||||
.split(&pane_to_split, &new_pane, action.split_direction)
|
||||
.unwrap();
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
pub fn split_pane_with_project_entry(
|
||||
&mut self,
|
||||
action: &SplitWithProjectEntry,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) -> Option<Task<Result<()>>> {
|
||||
let pane_to_split = action.pane_to_split.upgrade(cx)?;
|
||||
if &pane_to_split == self.dock_pane() {
|
||||
warn!("Can't split dock pane.");
|
||||
return None;
|
||||
}
|
||||
|
||||
let new_pane = self.add_pane(cx);
|
||||
self.center
|
||||
.split(&pane_to_split, &new_pane, action.split_direction)
|
||||
.unwrap();
|
||||
|
||||
let path = self
|
||||
.project
|
||||
.read(cx)
|
||||
.path_for_entry(action.project_entry, cx)?;
|
||||
let task = self.open_path(path, Some(new_pane.downgrade()), true, cx);
|
||||
Some(cx.foreground().spawn(async move {
|
||||
task.await?;
|
||||
Ok(())
|
||||
}))
|
||||
}
|
||||
|
||||
fn remove_pane(&mut self, pane: ViewHandle<Pane>, cx: &mut ViewContext<Self>) {
|
||||
|
@ -1650,6 +1625,7 @@ impl Workspace {
|
|||
project_id,
|
||||
leader_id: Some(leader_id),
|
||||
});
|
||||
|
||||
Some(cx.spawn_weak(|this, mut cx| async move {
|
||||
let response = request.await?;
|
||||
if let Some(this) = this.upgrade(&cx) {
|
||||
|
@ -1746,6 +1722,10 @@ impl Workspace {
|
|||
self.follower_states_by_leader.contains_key(&peer_id)
|
||||
}
|
||||
|
||||
pub fn is_followed(&self, peer_id: PeerId) -> bool {
|
||||
self.leader_state.followers.contains(&peer_id)
|
||||
}
|
||||
|
||||
fn render_titlebar(&self, theme: &Theme, cx: &mut RenderContext<Self>) -> ElementBox {
|
||||
let project = &self.project.read(cx);
|
||||
let mut worktree_root_names = String::new();
|
||||
|
@ -1923,6 +1903,9 @@ impl Workspace {
|
|||
.to_proto(),
|
||||
)
|
||||
});
|
||||
|
||||
cx.notify();
|
||||
|
||||
Ok(proto::FollowResponse {
|
||||
active_view_id,
|
||||
views: this
|
||||
|
@ -1955,10 +1938,11 @@ impl Workspace {
|
|||
_: Arc<Client>,
|
||||
mut cx: AsyncAppContext,
|
||||
) -> Result<()> {
|
||||
this.update(&mut cx, |this, _| {
|
||||
this.update(&mut cx, |this, cx| {
|
||||
this.leader_state
|
||||
.followers
|
||||
.remove(&envelope.original_sender_id()?);
|
||||
cx.notify();
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
@ -2499,7 +2483,16 @@ impl View for Workspace {
|
|||
if self.left_sidebar.read(cx).active_item().is_some() {
|
||||
Some(
|
||||
ChildView::new(&self.left_sidebar, cx)
|
||||
.flex(0.8, false)
|
||||
.constrained()
|
||||
.dynamically(|constraint, cx| {
|
||||
SizeConstraint::new(
|
||||
Vector2F::new(20., constraint.min.y()),
|
||||
Vector2F::new(
|
||||
cx.window_size.x() * 0.8,
|
||||
constraint.max.y(),
|
||||
),
|
||||
)
|
||||
})
|
||||
.boxed(),
|
||||
)
|
||||
} else {
|
||||
|
@ -2536,7 +2529,16 @@ impl View for Workspace {
|
|||
if self.right_sidebar.read(cx).active_item().is_some() {
|
||||
Some(
|
||||
ChildView::new(&self.right_sidebar, cx)
|
||||
.flex(0.8, false)
|
||||
.constrained()
|
||||
.dynamically(|constraint, cx| {
|
||||
SizeConstraint::new(
|
||||
Vector2F::new(20., constraint.min.y()),
|
||||
Vector2F::new(
|
||||
cx.window_size.x() * 0.8,
|
||||
constraint.max.y(),
|
||||
),
|
||||
)
|
||||
})
|
||||
.boxed(),
|
||||
)
|
||||
} else {
|
||||
|
@ -2681,8 +2683,8 @@ pub fn activate_workspace_for_project(
|
|||
None
|
||||
}
|
||||
|
||||
pub fn last_opened_workspace_paths() -> Option<WorkspaceLocation> {
|
||||
DB.last_workspace().log_err().flatten()
|
||||
pub async fn last_opened_workspace_paths() -> Option<WorkspaceLocation> {
|
||||
DB.last_workspace().await.log_err().flatten()
|
||||
}
|
||||
|
||||
#[allow(clippy::type_complexity)]
|
||||
|
@ -2755,7 +2757,7 @@ pub fn open_new(app_state: &Arc<AppState>, cx: &mut MutableAppContext) -> Task<(
|
|||
mod tests {
|
||||
use std::{cell::RefCell, rc::Rc};
|
||||
|
||||
use crate::item::test::{TestItem, TestItemEvent};
|
||||
use crate::item::test::{TestItem, TestItemEvent, TestProjectItem};
|
||||
|
||||
use super::*;
|
||||
use fs::FakeFs;
|
||||
|
@ -2862,15 +2864,11 @@ mod tests {
|
|||
project.worktrees(cx).next().unwrap().read(cx).id()
|
||||
});
|
||||
|
||||
let item1 = cx.add_view(&workspace, |_| {
|
||||
let mut item = TestItem::new();
|
||||
item.project_path = Some((worktree_id, "one.txt").into());
|
||||
item
|
||||
let item1 = cx.add_view(&workspace, |cx| {
|
||||
TestItem::new().with_project_items(&[TestProjectItem::new(1, "one.txt", cx)])
|
||||
});
|
||||
let item2 = cx.add_view(&workspace, |_| {
|
||||
let mut item = TestItem::new();
|
||||
item.project_path = Some((worktree_id, "two.txt").into());
|
||||
item
|
||||
let item2 = cx.add_view(&workspace, |cx| {
|
||||
TestItem::new().with_project_items(&[TestProjectItem::new(2, "two.txt", cx)])
|
||||
});
|
||||
|
||||
// Add an item to an empty pane
|
||||
|
@ -2971,16 +2969,11 @@ mod tests {
|
|||
|
||||
// When there are dirty untitled items, prompt to save each one. If the user
|
||||
// cancels any prompt, then abort.
|
||||
let item2 = cx.add_view(&workspace, |_| {
|
||||
let mut item = TestItem::new();
|
||||
item.is_dirty = true;
|
||||
item
|
||||
});
|
||||
let item3 = cx.add_view(&workspace, |_| {
|
||||
let mut item = TestItem::new();
|
||||
item.is_dirty = true;
|
||||
item.project_entry_ids = vec![ProjectEntryId::from_proto(1)];
|
||||
item
|
||||
let item2 = cx.add_view(&workspace, |_| TestItem::new().with_dirty(true));
|
||||
let item3 = cx.add_view(&workspace, |cx| {
|
||||
TestItem::new()
|
||||
.with_dirty(true)
|
||||
.with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
|
||||
});
|
||||
workspace.update(cx, |w, cx| {
|
||||
w.add_item(Box::new(item2.clone()), cx);
|
||||
|
@ -3005,30 +2998,27 @@ mod tests {
|
|||
Workspace::new(Default::default(), 0, project, default_item_factory, cx)
|
||||
});
|
||||
|
||||
let item1 = cx.add_view(&workspace, |_| {
|
||||
let mut item = TestItem::new();
|
||||
item.is_dirty = true;
|
||||
item.project_entry_ids = vec![ProjectEntryId::from_proto(1)];
|
||||
item
|
||||
let item1 = cx.add_view(&workspace, |cx| {
|
||||
TestItem::new()
|
||||
.with_dirty(true)
|
||||
.with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
|
||||
});
|
||||
let item2 = cx.add_view(&workspace, |_| {
|
||||
let mut item = TestItem::new();
|
||||
item.is_dirty = true;
|
||||
item.has_conflict = true;
|
||||
item.project_entry_ids = vec![ProjectEntryId::from_proto(2)];
|
||||
item
|
||||
let item2 = cx.add_view(&workspace, |cx| {
|
||||
TestItem::new()
|
||||
.with_dirty(true)
|
||||
.with_conflict(true)
|
||||
.with_project_items(&[TestProjectItem::new(2, "2.txt", cx)])
|
||||
});
|
||||
let item3 = cx.add_view(&workspace, |_| {
|
||||
let mut item = TestItem::new();
|
||||
item.is_dirty = true;
|
||||
item.has_conflict = true;
|
||||
item.project_entry_ids = vec![ProjectEntryId::from_proto(3)];
|
||||
item
|
||||
let item3 = cx.add_view(&workspace, |cx| {
|
||||
TestItem::new()
|
||||
.with_dirty(true)
|
||||
.with_conflict(true)
|
||||
.with_project_items(&[TestProjectItem::new(3, "3.txt", cx)])
|
||||
});
|
||||
let item4 = cx.add_view(&workspace, |_| {
|
||||
let mut item = TestItem::new();
|
||||
item.is_dirty = true;
|
||||
item
|
||||
let item4 = cx.add_view(&workspace, |cx| {
|
||||
TestItem::new()
|
||||
.with_dirty(true)
|
||||
.with_project_items(&[TestProjectItem::new_untitled(cx)])
|
||||
});
|
||||
let pane = workspace.update(cx, |workspace, cx| {
|
||||
workspace.add_item(Box::new(item1.clone()), cx);
|
||||
|
@ -3051,15 +3041,20 @@ mod tests {
|
|||
[item1_id, item3_id, item4_id].contains(&id)
|
||||
})
|
||||
});
|
||||
|
||||
cx.foreground().run_until_parked();
|
||||
|
||||
// There's a prompt to save item 1.
|
||||
pane.read_with(cx, |pane, _| {
|
||||
assert_eq!(pane.items_len(), 4);
|
||||
assert_eq!(pane.active_item().unwrap().id(), item1.id());
|
||||
});
|
||||
assert!(cx.has_pending_prompt(window_id));
|
||||
|
||||
// Confirm saving item 1.
|
||||
cx.simulate_prompt_answer(window_id, 0);
|
||||
cx.foreground().run_until_parked();
|
||||
|
||||
// Item 1 is saved. There's a prompt to save item 3.
|
||||
pane.read_with(cx, |pane, cx| {
|
||||
assert_eq!(item1.read(cx).save_count, 1);
|
||||
assert_eq!(item1.read(cx).save_as_count, 0);
|
||||
|
@ -3067,9 +3062,13 @@ mod tests {
|
|||
assert_eq!(pane.items_len(), 3);
|
||||
assert_eq!(pane.active_item().unwrap().id(), item3.id());
|
||||
});
|
||||
assert!(cx.has_pending_prompt(window_id));
|
||||
|
||||
// Cancel saving item 3.
|
||||
cx.simulate_prompt_answer(window_id, 1);
|
||||
cx.foreground().run_until_parked();
|
||||
|
||||
// Item 3 is reloaded. There's a prompt to save item 4.
|
||||
pane.read_with(cx, |pane, cx| {
|
||||
assert_eq!(item3.read(cx).save_count, 0);
|
||||
assert_eq!(item3.read(cx).save_as_count, 0);
|
||||
|
@ -3077,11 +3076,17 @@ mod tests {
|
|||
assert_eq!(pane.items_len(), 2);
|
||||
assert_eq!(pane.active_item().unwrap().id(), item4.id());
|
||||
});
|
||||
assert!(cx.has_pending_prompt(window_id));
|
||||
|
||||
// Confirm saving item 4.
|
||||
cx.simulate_prompt_answer(window_id, 0);
|
||||
cx.foreground().run_until_parked();
|
||||
|
||||
// There's a prompt for a path for item 4.
|
||||
cx.simulate_new_path_selection(|_| Some(Default::default()));
|
||||
close_items.await.unwrap();
|
||||
|
||||
// The requested items are closed.
|
||||
pane.read_with(cx, |pane, cx| {
|
||||
assert_eq!(item4.read(cx).save_count, 0);
|
||||
assert_eq!(item4.read(cx).save_as_count, 1);
|
||||
|
@ -3106,29 +3111,35 @@ mod tests {
|
|||
// workspace items with multiple project entries.
|
||||
let single_entry_items = (0..=4)
|
||||
.map(|project_entry_id| {
|
||||
let mut item = TestItem::new();
|
||||
item.is_dirty = true;
|
||||
item.project_entry_ids = vec![ProjectEntryId::from_proto(project_entry_id)];
|
||||
item.is_singleton = true;
|
||||
item
|
||||
cx.add_view(&workspace, |cx| {
|
||||
TestItem::new()
|
||||
.with_dirty(true)
|
||||
.with_project_items(&[TestProjectItem::new(
|
||||
project_entry_id,
|
||||
&format!("{project_entry_id}.txt"),
|
||||
cx,
|
||||
)])
|
||||
})
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
let item_2_3 = {
|
||||
let mut item = TestItem::new();
|
||||
item.is_dirty = true;
|
||||
item.is_singleton = false;
|
||||
item.project_entry_ids =
|
||||
vec![ProjectEntryId::from_proto(2), ProjectEntryId::from_proto(3)];
|
||||
item
|
||||
};
|
||||
let item_3_4 = {
|
||||
let mut item = TestItem::new();
|
||||
item.is_dirty = true;
|
||||
item.is_singleton = false;
|
||||
item.project_entry_ids =
|
||||
vec![ProjectEntryId::from_proto(3), ProjectEntryId::from_proto(4)];
|
||||
item
|
||||
};
|
||||
let item_2_3 = cx.add_view(&workspace, |cx| {
|
||||
TestItem::new()
|
||||
.with_dirty(true)
|
||||
.with_singleton(false)
|
||||
.with_project_items(&[
|
||||
single_entry_items[2].read(cx).project_items[0].clone(),
|
||||
single_entry_items[3].read(cx).project_items[0].clone(),
|
||||
])
|
||||
});
|
||||
let item_3_4 = cx.add_view(&workspace, |cx| {
|
||||
TestItem::new()
|
||||
.with_dirty(true)
|
||||
.with_singleton(false)
|
||||
.with_project_items(&[
|
||||
single_entry_items[3].read(cx).project_items[0].clone(),
|
||||
single_entry_items[4].read(cx).project_items[0].clone(),
|
||||
])
|
||||
});
|
||||
|
||||
// Create two panes that contain the following project entries:
|
||||
// left pane:
|
||||
|
@ -3139,9 +3150,9 @@ mod tests {
|
|||
// multi-entry items: (3, 4)
|
||||
let left_pane = workspace.update(cx, |workspace, cx| {
|
||||
let left_pane = workspace.active_pane().clone();
|
||||
workspace.add_item(Box::new(cx.add_view(|_| item_2_3.clone())), cx);
|
||||
for item in &single_entry_items {
|
||||
workspace.add_item(Box::new(cx.add_view(|_| item.clone())), cx);
|
||||
workspace.add_item(Box::new(item_2_3.clone()), cx);
|
||||
for item in single_entry_items {
|
||||
workspace.add_item(Box::new(item), cx);
|
||||
}
|
||||
left_pane.update(cx, |pane, cx| {
|
||||
pane.activate_item(2, true, true, cx);
|
||||
|
@ -3156,7 +3167,7 @@ mod tests {
|
|||
|
||||
//Need to cause an effect flush in order to respect new focus
|
||||
workspace.update(cx, |workspace, cx| {
|
||||
workspace.add_item(Box::new(cx.add_view(|_| item_3_4.clone())), cx);
|
||||
workspace.add_item(Box::new(item_3_4.clone()), cx);
|
||||
cx.focus(left_pane.clone());
|
||||
});
|
||||
|
||||
|
@ -3205,10 +3216,8 @@ mod tests {
|
|||
Workspace::new(Default::default(), 0, project, default_item_factory, cx)
|
||||
});
|
||||
|
||||
let item = cx.add_view(&workspace, |_| {
|
||||
let mut item = TestItem::new();
|
||||
item.project_entry_ids = vec![ProjectEntryId::from_proto(1)];
|
||||
item
|
||||
let item = cx.add_view(&workspace, |cx| {
|
||||
TestItem::new().with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
|
||||
});
|
||||
let item_id = item.id();
|
||||
workspace.update(cx, |workspace, cx| {
|
||||
|
@ -3293,7 +3302,9 @@ mod tests {
|
|||
workspace.add_item(Box::new(item.clone()), cx);
|
||||
});
|
||||
item.update(cx, |item, cx| {
|
||||
item.project_entry_ids = Default::default();
|
||||
item.project_items[0].update(cx, |item, _| {
|
||||
item.entry_id = None;
|
||||
});
|
||||
item.is_dirty = true;
|
||||
cx.blur();
|
||||
});
|
||||
|
@ -3324,10 +3335,8 @@ mod tests {
|
|||
Workspace::new(Default::default(), 0, project, default_item_factory, cx)
|
||||
});
|
||||
|
||||
let item = cx.add_view(&workspace, |_| {
|
||||
let mut item = TestItem::new();
|
||||
item.project_entry_ids = vec![ProjectEntryId::from_proto(1)];
|
||||
item
|
||||
let item = cx.add_view(&workspace, |cx| {
|
||||
TestItem::new().with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
|
||||
});
|
||||
let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
|
||||
let toolbar = pane.read_with(cx, |pane, _| pane.toolbar().clone());
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue