Merge branch 'main' into optimize-large-multi-buffers
This commit is contained in:
commit
a58b39f884
19 changed files with 312 additions and 243 deletions
4
Cargo.lock
generated
4
Cargo.lock
generated
|
@ -1132,7 +1132,7 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "collab"
|
name = "collab"
|
||||||
version = "0.5.2"
|
version = "0.5.3"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"async-tungstenite",
|
"async-tungstenite",
|
||||||
|
@ -8187,7 +8187,7 @@ checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zed"
|
name = "zed"
|
||||||
version = "0.70.0"
|
version = "0.71.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"activity_indicator",
|
"activity_indicator",
|
||||||
"anyhow",
|
"anyhow",
|
||||||
|
|
|
@ -3,7 +3,7 @@ authors = ["Nathan Sobo <nathan@zed.dev>"]
|
||||||
default-run = "collab"
|
default-run = "collab"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
name = "collab"
|
name = "collab"
|
||||||
version = "0.5.2"
|
version = "0.5.3"
|
||||||
|
|
||||||
[[bin]]
|
[[bin]]
|
||||||
name = "collab"
|
name = "collab"
|
||||||
|
|
|
@ -32,7 +32,9 @@ use std::{
|
||||||
sync::Arc,
|
sync::Arc,
|
||||||
};
|
};
|
||||||
use unindent::Unindent as _;
|
use unindent::Unindent as _;
|
||||||
use workspace::{item::Item, shared_screen::SharedScreen, SplitDirection, ToggleFollow, Workspace};
|
use workspace::{
|
||||||
|
item::ItemHandle as _, shared_screen::SharedScreen, SplitDirection, ToggleFollow, Workspace,
|
||||||
|
};
|
||||||
|
|
||||||
#[ctor::ctor]
|
#[ctor::ctor]
|
||||||
fn init_logger() {
|
fn init_logger() {
|
||||||
|
@ -5602,7 +5604,7 @@ async fn test_following(
|
||||||
});
|
});
|
||||||
assert!(cx_b.read(|cx| editor_b2.is_focused(cx)));
|
assert!(cx_b.read(|cx| editor_b2.is_focused(cx)));
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
editor_b2.read_with(cx_b, |editor, cx| editor.project_path(cx)),
|
cx_b.read(|cx| editor_b2.project_path(cx)),
|
||||||
Some((worktree_id, "2.txt").into())
|
Some((worktree_id, "2.txt").into())
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
|
|
@ -21,7 +21,6 @@ use language::{
|
||||||
use project::{DiagnosticSummary, Project, ProjectPath};
|
use project::{DiagnosticSummary, Project, ProjectPath};
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
use settings::Settings;
|
use settings::Settings;
|
||||||
use smallvec::SmallVec;
|
|
||||||
use std::{
|
use std::{
|
||||||
any::{Any, TypeId},
|
any::{Any, TypeId},
|
||||||
cmp::Ordering,
|
cmp::Ordering,
|
||||||
|
@ -521,12 +520,8 @@ impl Item for ProjectDiagnosticsEditor {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn project_path(&self, _: &AppContext) -> Option<project::ProjectPath> {
|
fn for_each_project_item(&self, cx: &AppContext, f: &mut dyn FnMut(usize, &dyn project::Item)) {
|
||||||
None
|
self.editor.for_each_project_item(cx, f)
|
||||||
}
|
|
||||||
|
|
||||||
fn project_entry_ids(&self, cx: &AppContext) -> SmallVec<[project::ProjectEntryId; 3]> {
|
|
||||||
self.editor.project_entry_ids(cx)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_singleton(&self, _: &AppContext) -> bool {
|
fn is_singleton(&self, _: &AppContext) -> bool {
|
||||||
|
|
|
@ -8,19 +8,17 @@ use anyhow::{anyhow, Context, Result};
|
||||||
use collections::HashSet;
|
use collections::HashSet;
|
||||||
use futures::future::try_join_all;
|
use futures::future::try_join_all;
|
||||||
use futures::FutureExt;
|
use futures::FutureExt;
|
||||||
|
|
||||||
use gpui::{
|
use gpui::{
|
||||||
elements::*, geometry::vector::vec2f, AppContext, Entity, ModelHandle, MutableAppContext,
|
elements::*, geometry::vector::vec2f, AppContext, Entity, ModelHandle, MutableAppContext,
|
||||||
RenderContext, Subscription, Task, View, ViewContext, ViewHandle, WeakViewHandle,
|
RenderContext, Subscription, Task, View, ViewContext, ViewHandle, WeakViewHandle,
|
||||||
};
|
};
|
||||||
use language::{
|
use language::{
|
||||||
proto::serialize_anchor as serialize_text_anchor, Bias, Buffer, File as _, OffsetRangeExt,
|
proto::serialize_anchor as serialize_text_anchor, Bias, Buffer, OffsetRangeExt, Point,
|
||||||
Point, SelectionGoal,
|
SelectionGoal,
|
||||||
};
|
};
|
||||||
use project::{File, FormatTrigger, Project, ProjectEntryId, ProjectPath};
|
use project::{FormatTrigger, Item as _, Project, ProjectPath};
|
||||||
use rpc::proto::{self, update_view};
|
use rpc::proto::{self, update_view};
|
||||||
use settings::Settings;
|
use settings::Settings;
|
||||||
use smallvec::SmallVec;
|
|
||||||
use std::{
|
use std::{
|
||||||
borrow::Cow,
|
borrow::Cow,
|
||||||
cmp::{self, Ordering},
|
cmp::{self, Ordering},
|
||||||
|
@ -557,22 +555,10 @@ impl Item for Editor {
|
||||||
.boxed()
|
.boxed()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn project_path(&self, cx: &AppContext) -> Option<ProjectPath> {
|
fn for_each_project_item(&self, cx: &AppContext, f: &mut dyn FnMut(usize, &dyn project::Item)) {
|
||||||
let buffer = self.buffer.read(cx).as_singleton()?;
|
|
||||||
let file = buffer.read(cx).file();
|
|
||||||
File::from_dyn(file).map(|file| ProjectPath {
|
|
||||||
worktree_id: file.worktree_id(cx),
|
|
||||||
path: file.path().clone(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn project_entry_ids(&self, cx: &AppContext) -> SmallVec<[ProjectEntryId; 3]> {
|
|
||||||
self.buffer
|
self.buffer
|
||||||
.read(cx)
|
.read(cx)
|
||||||
.files(cx)
|
.for_each_buffer(|buffer| f(buffer.id(), buffer.read(cx)));
|
||||||
.into_iter()
|
|
||||||
.filter_map(|file| File::from_dyn(Some(file))?.project_entry_id(cx))
|
|
||||||
.collect()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_singleton(&self, cx: &AppContext) -> bool {
|
fn is_singleton(&self, cx: &AppContext) -> bool {
|
||||||
|
@ -609,7 +595,12 @@ impl Item for Editor {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn can_save(&self, cx: &AppContext) -> bool {
|
fn can_save(&self, cx: &AppContext) -> bool {
|
||||||
!self.buffer().read(cx).is_singleton() || self.project_path(cx).is_some()
|
let buffer = &self.buffer().read(cx);
|
||||||
|
if let Some(buffer) = buffer.as_singleton() {
|
||||||
|
buffer.read(cx).project_path(cx).is_some()
|
||||||
|
} else {
|
||||||
|
true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn save(
|
fn save(
|
||||||
|
|
|
@ -10,11 +10,10 @@ use gpui::{AppContext, Entity, ModelContext, ModelHandle, Task};
|
||||||
pub use language::Completion;
|
pub use language::Completion;
|
||||||
use language::{
|
use language::{
|
||||||
char_kind, AutoindentMode, Buffer, BufferChunks, BufferSnapshot, CharKind, Chunk, CursorShape,
|
char_kind, AutoindentMode, Buffer, BufferChunks, BufferSnapshot, CharKind, Chunk, CursorShape,
|
||||||
DiagnosticEntry, File, IndentSize, Language, OffsetRangeExt, OffsetUtf16, Outline, OutlineItem,
|
DiagnosticEntry, IndentSize, Language, OffsetRangeExt, OffsetUtf16, Outline, OutlineItem,
|
||||||
Point, PointUtf16, Selection, TextDimension, ToOffset as _, ToOffsetUtf16 as _, ToPoint as _,
|
Point, PointUtf16, Selection, TextDimension, ToOffset as _, ToOffsetUtf16 as _, ToPoint as _,
|
||||||
ToPointUtf16 as _, TransactionId, Unclipped,
|
ToPointUtf16 as _, TransactionId, Unclipped,
|
||||||
};
|
};
|
||||||
use smallvec::SmallVec;
|
|
||||||
use std::{
|
use std::{
|
||||||
borrow::Cow,
|
borrow::Cow,
|
||||||
cell::{Ref, RefCell},
|
cell::{Ref, RefCell},
|
||||||
|
@ -1338,12 +1337,11 @@ impl MultiBuffer {
|
||||||
.and_then(|(buffer, offset)| buffer.read(cx).language_at(offset))
|
.and_then(|(buffer, offset)| buffer.read(cx).language_at(offset))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn files<'a>(&'a self, cx: &'a AppContext) -> SmallVec<[&'a Arc<dyn File>; 2]> {
|
pub fn for_each_buffer(&self, mut f: impl FnMut(&ModelHandle<Buffer>)) {
|
||||||
let buffers = self.buffers.borrow();
|
self.buffers
|
||||||
buffers
|
.borrow()
|
||||||
.values()
|
.values()
|
||||||
.filter_map(|buffer| buffer.buffer.read(cx).file())
|
.for_each(|state| f(&state.buffer))
|
||||||
.collect()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn title<'a>(&'a self, cx: &'a AppContext) -> Cow<'a, str> {
|
pub fn title<'a>(&'a self, cx: &'a AppContext) -> Cow<'a, str> {
|
||||||
|
|
|
@ -2169,8 +2169,6 @@ impl BufferSnapshot {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO - move later, after processing captures
|
|
||||||
|
|
||||||
let mut text = String::new();
|
let mut text = String::new();
|
||||||
let mut name_ranges = Vec::new();
|
let mut name_ranges = Vec::new();
|
||||||
let mut highlight_ranges = Vec::new();
|
let mut highlight_ranges = Vec::new();
|
||||||
|
@ -2184,7 +2182,13 @@ impl BufferSnapshot {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
let range = capture.node.start_byte()..capture.node.end_byte();
|
let mut range = capture.node.start_byte()..capture.node.end_byte();
|
||||||
|
let start = capture.node.start_position();
|
||||||
|
if capture.node.end_position().row > start.row {
|
||||||
|
range.end =
|
||||||
|
range.start + self.line_len(start.row as u32) as usize - start.column;
|
||||||
|
}
|
||||||
|
|
||||||
if !text.is_empty() {
|
if !text.is_empty() {
|
||||||
text.push(' ');
|
text.push(' ');
|
||||||
}
|
}
|
||||||
|
|
|
@ -455,6 +455,32 @@ async fn test_outline(cx: &mut gpui::TestAppContext) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[gpui::test]
|
||||||
|
async fn test_outline_nodes_with_newlines(cx: &mut gpui::TestAppContext) {
|
||||||
|
let text = r#"
|
||||||
|
impl A for B<
|
||||||
|
C
|
||||||
|
> {
|
||||||
|
};
|
||||||
|
"#
|
||||||
|
.unindent();
|
||||||
|
|
||||||
|
let buffer =
|
||||||
|
cx.add_model(|cx| Buffer::new(0, text, cx).with_language(Arc::new(rust_lang()), cx));
|
||||||
|
let outline = buffer
|
||||||
|
.read_with(cx, |buffer, _| buffer.snapshot().outline(None))
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
outline
|
||||||
|
.items
|
||||||
|
.iter()
|
||||||
|
.map(|item| (item.text.as_str(), item.depth))
|
||||||
|
.collect::<Vec<_>>(),
|
||||||
|
&[("impl A for B<", 0)]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[gpui::test]
|
#[gpui::test]
|
||||||
async fn test_symbols_containing(cx: &mut gpui::TestAppContext) {
|
async fn test_symbols_containing(cx: &mut gpui::TestAppContext) {
|
||||||
let text = r#"
|
let text = r#"
|
||||||
|
|
|
@ -67,8 +67,9 @@ use util::{debug_panic, defer, post_inc, ResultExt, TryFutureExt as _};
|
||||||
pub use fs::*;
|
pub use fs::*;
|
||||||
pub use worktree::*;
|
pub use worktree::*;
|
||||||
|
|
||||||
pub trait Item: Entity {
|
pub trait Item {
|
||||||
fn entry_id(&self, cx: &AppContext) -> Option<ProjectEntryId>;
|
fn entry_id(&self, cx: &AppContext) -> Option<ProjectEntryId>;
|
||||||
|
fn project_path(&self, cx: &AppContext) -> Option<ProjectPath>;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Language server state is stored across 3 collections:
|
// Language server state is stored across 3 collections:
|
||||||
|
@ -6405,4 +6406,11 @@ impl Item for Buffer {
|
||||||
fn entry_id(&self, cx: &AppContext) -> Option<ProjectEntryId> {
|
fn entry_id(&self, cx: &AppContext) -> Option<ProjectEntryId> {
|
||||||
File::from_dyn(self.file()).and_then(|file| file.project_entry_id(cx))
|
File::from_dyn(self.file()).and_then(|file| file.project_entry_id(cx))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn project_path(&self, cx: &AppContext) -> Option<ProjectPath> {
|
||||||
|
File::from_dyn(self.file()).map(|file| ProjectPath {
|
||||||
|
worktree_id: file.worktree_id(cx),
|
||||||
|
path: file.path().clone(),
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -763,7 +763,6 @@ impl ProjectPanel {
|
||||||
ix += 1;
|
ix += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
self.clipboard_entry.take();
|
|
||||||
if clipboard_entry.is_cut() {
|
if clipboard_entry.is_cut() {
|
||||||
if let Some(task) = self.project.update(cx, |project, cx| {
|
if let Some(task) = self.project.update(cx, |project, cx| {
|
||||||
project.rename_entry(clipboard_entry.entry_id(), new_path, cx)
|
project.rename_entry(clipboard_entry.entry_id(), new_path, cx)
|
||||||
|
@ -1176,13 +1175,15 @@ impl ProjectPanel {
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
.on_click(MouseButton::Left, move |e, cx| {
|
.on_click(MouseButton::Left, move |e, cx| {
|
||||||
if kind == EntryKind::Dir {
|
if !show_editor {
|
||||||
cx.dispatch_action(ToggleExpanded(entry_id))
|
if kind == EntryKind::Dir {
|
||||||
} else {
|
cx.dispatch_action(ToggleExpanded(entry_id))
|
||||||
cx.dispatch_action(Open {
|
} else {
|
||||||
entry_id,
|
cx.dispatch_action(Open {
|
||||||
change_focus: e.click_count > 1,
|
entry_id,
|
||||||
})
|
change_focus: e.click_count > 1,
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.on_down(MouseButton::Right, move |e, cx| {
|
.on_down(MouseButton::Right, move |e, cx| {
|
||||||
|
|
|
@ -16,7 +16,6 @@ use gpui::{
|
||||||
use menu::Confirm;
|
use menu::Confirm;
|
||||||
use project::{search::SearchQuery, Project};
|
use project::{search::SearchQuery, Project};
|
||||||
use settings::Settings;
|
use settings::Settings;
|
||||||
use smallvec::SmallVec;
|
|
||||||
use std::{
|
use std::{
|
||||||
any::{Any, TypeId},
|
any::{Any, TypeId},
|
||||||
mem,
|
mem,
|
||||||
|
@ -275,12 +274,8 @@ impl Item for ProjectSearchView {
|
||||||
.boxed()
|
.boxed()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn project_path(&self, _: &gpui::AppContext) -> Option<project::ProjectPath> {
|
fn for_each_project_item(&self, cx: &AppContext, f: &mut dyn FnMut(usize, &dyn project::Item)) {
|
||||||
None
|
self.results_editor.for_each_project_item(cx, f)
|
||||||
}
|
|
||||||
|
|
||||||
fn project_entry_ids(&self, cx: &AppContext) -> SmallVec<[project::ProjectEntryId; 3]> {
|
|
||||||
self.results_editor.project_entry_ids(cx)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_singleton(&self, _: &AppContext) -> bool {
|
fn is_singleton(&self, _: &AppContext) -> bool {
|
||||||
|
|
|
@ -18,10 +18,9 @@ use gpui::{
|
||||||
AnyViewHandle, AppContext, Element, ElementBox, Entity, ModelHandle, MutableAppContext, Task,
|
AnyViewHandle, AppContext, Element, ElementBox, Entity, ModelHandle, MutableAppContext, Task,
|
||||||
View, ViewContext, ViewHandle, WeakViewHandle,
|
View, ViewContext, ViewHandle, WeakViewHandle,
|
||||||
};
|
};
|
||||||
use project::{LocalWorktree, Project, ProjectPath};
|
use project::{LocalWorktree, Project};
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use settings::{Settings, TerminalBlink, WorkingDirectory};
|
use settings::{Settings, TerminalBlink, WorkingDirectory};
|
||||||
use smallvec::SmallVec;
|
|
||||||
use smol::Timer;
|
use smol::Timer;
|
||||||
use terminal::{
|
use terminal::{
|
||||||
alacritty_terminal::{
|
alacritty_terminal::{
|
||||||
|
@ -616,13 +615,7 @@ impl Item for TerminalView {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
fn project_path(&self, _cx: &gpui::AppContext) -> Option<ProjectPath> {
|
fn for_each_project_item(&self, _: &AppContext, _: &mut dyn FnMut(usize, &dyn project::Item)) {}
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
fn project_entry_ids(&self, _cx: &gpui::AppContext) -> SmallVec<[project::ProjectEntryId; 3]> {
|
|
||||||
SmallVec::new()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn is_singleton(&self, _cx: &gpui::AppContext) -> bool {
|
fn is_singleton(&self, _cx: &gpui::AppContext) -> bool {
|
||||||
false
|
false
|
||||||
|
|
|
@ -6,12 +6,11 @@ use gpui::{
|
||||||
Padding, ParentElement,
|
Padding, ParentElement,
|
||||||
},
|
},
|
||||||
fonts::TextStyle,
|
fonts::TextStyle,
|
||||||
Border, Element, Entity, ModelHandle, MutableAppContext, Quad, RenderContext, Task, View,
|
AppContext, Border, Element, Entity, ModelHandle, MutableAppContext, Quad, RenderContext, Task,
|
||||||
ViewContext, ViewHandle, WeakViewHandle,
|
View, ViewContext, ViewHandle, WeakViewHandle,
|
||||||
};
|
};
|
||||||
use project::{Project, ProjectEntryId, ProjectPath};
|
use project::Project;
|
||||||
use settings::Settings;
|
use settings::Settings;
|
||||||
use smallvec::SmallVec;
|
|
||||||
use theme::{ColorScheme, Layer, Style, StyleSet};
|
use theme::{ColorScheme, Layer, Style, StyleSet};
|
||||||
use workspace::{
|
use workspace::{
|
||||||
item::{Item, ItemEvent},
|
item::{Item, ItemEvent},
|
||||||
|
@ -306,7 +305,7 @@ impl Item for ThemeTestbench {
|
||||||
&self,
|
&self,
|
||||||
_: Option<usize>,
|
_: Option<usize>,
|
||||||
style: &theme::Tab,
|
style: &theme::Tab,
|
||||||
_: &gpui::AppContext,
|
_: &AppContext,
|
||||||
) -> gpui::ElementBox {
|
) -> gpui::ElementBox {
|
||||||
Label::new("Theme Testbench".into(), style.label.clone())
|
Label::new("Theme Testbench".into(), style.label.clone())
|
||||||
.aligned()
|
.aligned()
|
||||||
|
@ -314,21 +313,15 @@ impl Item for ThemeTestbench {
|
||||||
.boxed()
|
.boxed()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn project_path(&self, _: &gpui::AppContext) -> Option<ProjectPath> {
|
fn for_each_project_item(&self, _: &AppContext, _: &mut dyn FnMut(usize, &dyn project::Item)) {}
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
fn project_entry_ids(&self, _: &gpui::AppContext) -> SmallVec<[ProjectEntryId; 3]> {
|
fn is_singleton(&self, _: &AppContext) -> bool {
|
||||||
SmallVec::new()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn is_singleton(&self, _: &gpui::AppContext) -> bool {
|
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_nav_history(&mut self, _: workspace::ItemNavHistory, _: &mut ViewContext<Self>) {}
|
fn set_nav_history(&mut self, _: workspace::ItemNavHistory, _: &mut ViewContext<Self>) {}
|
||||||
|
|
||||||
fn can_save(&self, _: &gpui::AppContext) -> bool {
|
fn can_save(&self, _: &AppContext) -> bool {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -49,8 +49,7 @@ pub trait Item: View {
|
||||||
}
|
}
|
||||||
fn tab_content(&self, detail: Option<usize>, style: &theme::Tab, cx: &AppContext)
|
fn tab_content(&self, detail: Option<usize>, style: &theme::Tab, cx: &AppContext)
|
||||||
-> ElementBox;
|
-> ElementBox;
|
||||||
fn project_path(&self, cx: &AppContext) -> Option<ProjectPath>;
|
fn for_each_project_item(&self, _: &AppContext, _: &mut dyn FnMut(usize, &dyn project::Item));
|
||||||
fn project_entry_ids(&self, cx: &AppContext) -> SmallVec<[ProjectEntryId; 3]>;
|
|
||||||
fn is_singleton(&self, cx: &AppContext) -> bool;
|
fn is_singleton(&self, cx: &AppContext) -> bool;
|
||||||
fn set_nav_history(&mut self, _: ItemNavHistory, _: &mut ViewContext<Self>);
|
fn set_nav_history(&mut self, _: ItemNavHistory, _: &mut ViewContext<Self>);
|
||||||
fn clone_on_split(&self, _workspace_id: WorkspaceId, _: &mut ViewContext<Self>) -> Option<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;
|
-> ElementBox;
|
||||||
fn project_path(&self, cx: &AppContext) -> Option<ProjectPath>;
|
fn project_path(&self, cx: &AppContext) -> Option<ProjectPath>;
|
||||||
fn project_entry_ids(&self, cx: &AppContext) -> SmallVec<[ProjectEntryId; 3]>;
|
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 is_singleton(&self, cx: &AppContext) -> bool;
|
||||||
fn boxed_clone(&self) -> Box<dyn ItemHandle>;
|
fn boxed_clone(&self) -> Box<dyn ItemHandle>;
|
||||||
fn clone_on_split(
|
fn clone_on_split(
|
||||||
|
@ -240,11 +241,36 @@ impl<T: Item> ItemHandle for ViewHandle<T> {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn project_path(&self, cx: &AppContext) -> Option<ProjectPath> {
|
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]> {
|
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 {
|
fn is_singleton(&self, cx: &AppContext) -> bool {
|
||||||
|
@ -582,7 +608,7 @@ impl<T: Item> WeakItemHandle for WeakViewHandle<T> {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait ProjectItem: Item {
|
pub trait ProjectItem: Item {
|
||||||
type Item: project::Item;
|
type Item: project::Item + gpui::Entity;
|
||||||
|
|
||||||
fn for_project_item(
|
fn for_project_item(
|
||||||
project: ModelHandle<Project>,
|
project: ModelHandle<Project>,
|
||||||
|
@ -690,18 +716,19 @@ impl<T: FollowableItem> FollowableItemHandle for ViewHandle<T> {
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
pub(crate) mod 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 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 struct TestItem {
|
||||||
pub workspace_id: WorkspaceId,
|
pub workspace_id: WorkspaceId,
|
||||||
|
@ -713,13 +740,26 @@ pub(crate) mod test {
|
||||||
pub is_dirty: bool,
|
pub is_dirty: bool,
|
||||||
pub is_singleton: bool,
|
pub is_singleton: bool,
|
||||||
pub has_conflict: bool,
|
pub has_conflict: bool,
|
||||||
pub project_entry_ids: Vec<ProjectEntryId>,
|
pub project_items: Vec<ModelHandle<TestProjectItem>>,
|
||||||
pub project_path: Option<ProjectPath>,
|
|
||||||
pub nav_history: Option<ItemNavHistory>,
|
pub nav_history: Option<ItemNavHistory>,
|
||||||
pub tab_descriptions: Option<Vec<&'static str>>,
|
pub tab_descriptions: Option<Vec<&'static str>>,
|
||||||
pub tab_detail: Cell<Option<usize>>,
|
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 {
|
pub enum TestItemEvent {
|
||||||
Edit,
|
Edit,
|
||||||
}
|
}
|
||||||
|
@ -735,8 +775,7 @@ pub(crate) mod test {
|
||||||
is_dirty: self.is_dirty,
|
is_dirty: self.is_dirty,
|
||||||
is_singleton: self.is_singleton,
|
is_singleton: self.is_singleton,
|
||||||
has_conflict: self.has_conflict,
|
has_conflict: self.has_conflict,
|
||||||
project_entry_ids: self.project_entry_ids.clone(),
|
project_items: self.project_items.clone(),
|
||||||
project_path: self.project_path.clone(),
|
|
||||||
nav_history: None,
|
nav_history: None,
|
||||||
tab_descriptions: None,
|
tab_descriptions: None,
|
||||||
tab_detail: Default::default(),
|
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 {
|
impl TestItem {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
|
@ -755,8 +815,7 @@ pub(crate) mod test {
|
||||||
reload_count: 0,
|
reload_count: 0,
|
||||||
is_dirty: false,
|
is_dirty: false,
|
||||||
has_conflict: false,
|
has_conflict: false,
|
||||||
project_entry_ids: Vec::new(),
|
project_items: Vec::new(),
|
||||||
project_path: None,
|
|
||||||
is_singleton: true,
|
is_singleton: true,
|
||||||
nav_history: None,
|
nav_history: None,
|
||||||
tab_descriptions: None,
|
tab_descriptions: None,
|
||||||
|
@ -781,13 +840,19 @@ pub(crate) mod test {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn with_project_entry_ids(mut self, project_entry_ids: &[u64]) -> Self {
|
pub fn with_dirty(mut self, dirty: bool) -> Self {
|
||||||
self.project_entry_ids.extend(
|
self.is_dirty = dirty;
|
||||||
project_entry_ids
|
self
|
||||||
.iter()
|
}
|
||||||
.copied()
|
|
||||||
.map(ProjectEntryId::from_proto),
|
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
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -830,12 +895,14 @@ pub(crate) mod test {
|
||||||
Empty::new().boxed()
|
Empty::new().boxed()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn project_path(&self, _: &AppContext) -> Option<ProjectPath> {
|
fn for_each_project_item(
|
||||||
self.project_path.clone()
|
&self,
|
||||||
}
|
cx: &AppContext,
|
||||||
|
f: &mut dyn FnMut(usize, &dyn project::Item),
|
||||||
fn project_entry_ids(&self, _: &AppContext) -> SmallVec<[ProjectEntryId; 3]> {
|
) {
|
||||||
self.project_entry_ids.iter().copied().collect()
|
self.project_items
|
||||||
|
.iter()
|
||||||
|
.for_each(|item| f(item.id(), item.read(cx)))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_singleton(&self, _: &AppContext) -> bool {
|
fn is_singleton(&self, _: &AppContext) -> bool {
|
||||||
|
@ -879,8 +946,12 @@ pub(crate) mod test {
|
||||||
self.has_conflict
|
self.has_conflict
|
||||||
}
|
}
|
||||||
|
|
||||||
fn can_save(&self, _: &AppContext) -> bool {
|
fn can_save(&self, cx: &AppContext) -> bool {
|
||||||
!self.project_entry_ids.is_empty()
|
!self.project_items.is_empty()
|
||||||
|
&& self
|
||||||
|
.project_items
|
||||||
|
.iter()
|
||||||
|
.all(|item| item.read(cx).entry_id.is_some())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn save(
|
fn save(
|
||||||
|
|
|
@ -482,7 +482,7 @@ impl Pane {
|
||||||
) -> Box<dyn ItemHandle> {
|
) -> Box<dyn ItemHandle> {
|
||||||
let existing_item = pane.update(cx, |pane, cx| {
|
let existing_item = pane.update(cx, |pane, cx| {
|
||||||
for (index, item) in pane.items.iter().enumerate() {
|
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]
|
&& item.project_entry_ids(cx).as_slice() == [project_entry_id]
|
||||||
{
|
{
|
||||||
let item = item.boxed_clone();
|
let item = item.boxed_clone();
|
||||||
|
@ -804,13 +804,13 @@ impl Pane {
|
||||||
items_to_close.sort_by_key(|item| !item.is_singleton(cx));
|
items_to_close.sort_by_key(|item| !item.is_singleton(cx));
|
||||||
|
|
||||||
cx.spawn(|workspace, mut cx| async move {
|
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() {
|
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
|
// storing these in advance, in case they have changed since this task
|
||||||
// was started.
|
// was started.
|
||||||
let (item_ix, mut project_entry_ids) = pane.read_with(&cx, |pane, cx| {
|
let (item_ix, mut project_item_ids) = pane.read_with(&cx, |pane, cx| {
|
||||||
(pane.index_for_item(&*item), item.project_entry_ids(cx))
|
(pane.index_for_item(&*item), item.project_item_model_ids(cx))
|
||||||
});
|
});
|
||||||
let item_ix = if let Some(ix) = item_ix {
|
let item_ix = if let Some(ix) = item_ix {
|
||||||
ix
|
ix
|
||||||
|
@ -818,30 +818,23 @@ impl Pane {
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
|
|
||||||
// If an item hasn't yet been associated with a project entry, then always
|
// Check if this view has any project items that are not open anywhere else
|
||||||
// prompt to save it before closing it. Otherwise, check if the item has
|
// in the workspace, AND that the user has not already been prompted to save.
|
||||||
// any project entries that are not open anywhere else in the workspace,
|
// If there are any such project entries, prompt the user to save this item.
|
||||||
// AND that the user has not already been prompted to save. If there are
|
workspace.read_with(&cx, |workspace, cx| {
|
||||||
// any such project entries, prompt the user to save this item.
|
for item in workspace.items(cx) {
|
||||||
let should_save = if project_entry_ids.is_empty() {
|
if !items_to_close
|
||||||
true
|
.iter()
|
||||||
} else {
|
.any(|item_to_close| item_to_close.id() == item.id())
|
||||||
workspace.read_with(&cx, |workspace, cx| {
|
{
|
||||||
for item in workspace.items(cx) {
|
let other_project_item_ids = item.project_item_model_ids(cx);
|
||||||
if !items_to_close
|
project_item_ids.retain(|id| !other_project_item_ids.contains(id));
|
||||||
.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));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
project_entry_ids
|
});
|
||||||
.iter()
|
let should_save = project_item_ids
|
||||||
.any(|id| saved_project_entry_ids.insert(*id))
|
.iter()
|
||||||
};
|
.any(|id| saved_project_items_ids.insert(*id));
|
||||||
|
|
||||||
if should_save
|
if should_save
|
||||||
&& !Self::save_item(project.clone(), &pane, item_ix, &*item, true, &mut cx)
|
&& !Self::save_item(project.clone(), &pane, item_ix, &*item, true, &mut cx)
|
||||||
|
@ -1677,7 +1670,7 @@ mod tests {
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::item::test::TestItem;
|
use crate::item::test::{TestItem, TestProjectItem};
|
||||||
use gpui::{executor::Deterministic, TestAppContext};
|
use gpui::{executor::Deterministic, TestAppContext};
|
||||||
use project::FakeFs;
|
use project::FakeFs;
|
||||||
|
|
||||||
|
@ -1866,7 +1859,7 @@ mod tests {
|
||||||
let item = TestItem::new()
|
let item = TestItem::new()
|
||||||
.with_singleton(true)
|
.with_singleton(true)
|
||||||
.with_label("buffer 1")
|
.with_label("buffer 1")
|
||||||
.with_project_entry_ids(&[1]);
|
.with_project_items(&[TestProjectItem::new(1, "one.txt", cx)]);
|
||||||
|
|
||||||
Pane::add_item(
|
Pane::add_item(
|
||||||
workspace,
|
workspace,
|
||||||
|
@ -1885,7 +1878,7 @@ mod tests {
|
||||||
let item = TestItem::new()
|
let item = TestItem::new()
|
||||||
.with_singleton(true)
|
.with_singleton(true)
|
||||||
.with_label("buffer 1")
|
.with_label("buffer 1")
|
||||||
.with_project_entry_ids(&[1]);
|
.with_project_items(&[TestProjectItem::new(1, "1.txt", cx)]);
|
||||||
|
|
||||||
Pane::add_item(
|
Pane::add_item(
|
||||||
workspace,
|
workspace,
|
||||||
|
@ -1904,7 +1897,7 @@ mod tests {
|
||||||
let item = TestItem::new()
|
let item = TestItem::new()
|
||||||
.with_singleton(true)
|
.with_singleton(true)
|
||||||
.with_label("buffer 2")
|
.with_label("buffer 2")
|
||||||
.with_project_entry_ids(&[2]);
|
.with_project_items(&[TestProjectItem::new(2, "2.txt", cx)]);
|
||||||
|
|
||||||
Pane::add_item(
|
Pane::add_item(
|
||||||
workspace,
|
workspace,
|
||||||
|
@ -1923,7 +1916,7 @@ mod tests {
|
||||||
let item = TestItem::new()
|
let item = TestItem::new()
|
||||||
.with_singleton(false)
|
.with_singleton(false)
|
||||||
.with_label("multibuffer 1")
|
.with_label("multibuffer 1")
|
||||||
.with_project_entry_ids(&[1]);
|
.with_project_items(&[TestProjectItem::new(1, "1.txt", cx)]);
|
||||||
|
|
||||||
Pane::add_item(
|
Pane::add_item(
|
||||||
workspace,
|
workspace,
|
||||||
|
@ -1942,7 +1935,7 @@ mod tests {
|
||||||
let item = TestItem::new()
|
let item = TestItem::new()
|
||||||
.with_singleton(false)
|
.with_singleton(false)
|
||||||
.with_label("multibuffer 1b")
|
.with_label("multibuffer 1b")
|
||||||
.with_project_entry_ids(&[1]);
|
.with_project_items(&[TestProjectItem::new(1, "1.txt", cx)]);
|
||||||
|
|
||||||
Pane::add_item(
|
Pane::add_item(
|
||||||
workspace,
|
workspace,
|
||||||
|
|
|
@ -8,12 +8,11 @@ use futures::StreamExt;
|
||||||
use gpui::{
|
use gpui::{
|
||||||
elements::*,
|
elements::*,
|
||||||
geometry::{rect::RectF, vector::vec2f},
|
geometry::{rect::RectF, vector::vec2f},
|
||||||
Entity, ModelHandle, MouseButton, RenderContext, Task, View, ViewContext, ViewHandle,
|
AppContext, Entity, ModelHandle, MouseButton, RenderContext, Task, View, ViewContext,
|
||||||
WeakViewHandle,
|
ViewHandle, WeakViewHandle,
|
||||||
};
|
};
|
||||||
use project::Project;
|
use project::Project;
|
||||||
use settings::Settings;
|
use settings::Settings;
|
||||||
use smallvec::SmallVec;
|
|
||||||
use std::{
|
use std::{
|
||||||
path::PathBuf,
|
path::PathBuf,
|
||||||
sync::{Arc, Weak},
|
sync::{Arc, Weak},
|
||||||
|
@ -106,7 +105,7 @@ impl Item for SharedScreen {
|
||||||
&self,
|
&self,
|
||||||
_: Option<usize>,
|
_: Option<usize>,
|
||||||
style: &theme::Tab,
|
style: &theme::Tab,
|
||||||
_: &gpui::AppContext,
|
_: &AppContext,
|
||||||
) -> gpui::ElementBox {
|
) -> gpui::ElementBox {
|
||||||
Flex::row()
|
Flex::row()
|
||||||
.with_child(
|
.with_child(
|
||||||
|
@ -130,15 +129,9 @@ impl Item for SharedScreen {
|
||||||
.boxed()
|
.boxed()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn project_path(&self, _: &gpui::AppContext) -> Option<project::ProjectPath> {
|
fn for_each_project_item(&self, _: &AppContext, _: &mut dyn FnMut(usize, &dyn project::Item)) {}
|
||||||
Default::default()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn project_entry_ids(&self, _: &gpui::AppContext) -> SmallVec<[project::ProjectEntryId; 3]> {
|
fn is_singleton(&self, _: &AppContext) -> bool {
|
||||||
Default::default()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn is_singleton(&self, _: &gpui::AppContext) -> bool {
|
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -155,7 +148,7 @@ impl Item for SharedScreen {
|
||||||
Some(Self::new(&track, self.peer_id, self.user.clone(), cx))
|
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
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2746,7 +2746,7 @@ pub fn open_new(app_state: &Arc<AppState>, cx: &mut MutableAppContext) -> Task<(
|
||||||
mod tests {
|
mod tests {
|
||||||
use std::{cell::RefCell, rc::Rc};
|
use std::{cell::RefCell, rc::Rc};
|
||||||
|
|
||||||
use crate::item::test::{TestItem, TestItemEvent};
|
use crate::item::test::{TestItem, TestItemEvent, TestProjectItem};
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
use fs::FakeFs;
|
use fs::FakeFs;
|
||||||
|
@ -2853,15 +2853,11 @@ mod tests {
|
||||||
project.worktrees(cx).next().unwrap().read(cx).id()
|
project.worktrees(cx).next().unwrap().read(cx).id()
|
||||||
});
|
});
|
||||||
|
|
||||||
let item1 = cx.add_view(&workspace, |_| {
|
let item1 = cx.add_view(&workspace, |cx| {
|
||||||
let mut item = TestItem::new();
|
TestItem::new().with_project_items(&[TestProjectItem::new(1, "one.txt", cx)])
|
||||||
item.project_path = Some((worktree_id, "one.txt").into());
|
|
||||||
item
|
|
||||||
});
|
});
|
||||||
let item2 = cx.add_view(&workspace, |_| {
|
let item2 = cx.add_view(&workspace, |cx| {
|
||||||
let mut item = TestItem::new();
|
TestItem::new().with_project_items(&[TestProjectItem::new(2, "two.txt", cx)])
|
||||||
item.project_path = Some((worktree_id, "two.txt").into());
|
|
||||||
item
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Add an item to an empty pane
|
// Add an item to an empty pane
|
||||||
|
@ -2962,16 +2958,11 @@ mod tests {
|
||||||
|
|
||||||
// When there are dirty untitled items, prompt to save each one. If the user
|
// When there are dirty untitled items, prompt to save each one. If the user
|
||||||
// cancels any prompt, then abort.
|
// cancels any prompt, then abort.
|
||||||
let item2 = cx.add_view(&workspace, |_| {
|
let item2 = cx.add_view(&workspace, |_| TestItem::new().with_dirty(true));
|
||||||
let mut item = TestItem::new();
|
let item3 = cx.add_view(&workspace, |cx| {
|
||||||
item.is_dirty = true;
|
TestItem::new()
|
||||||
item
|
.with_dirty(true)
|
||||||
});
|
.with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
|
||||||
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
|
|
||||||
});
|
});
|
||||||
workspace.update(cx, |w, cx| {
|
workspace.update(cx, |w, cx| {
|
||||||
w.add_item(Box::new(item2.clone()), cx);
|
w.add_item(Box::new(item2.clone()), cx);
|
||||||
|
@ -2996,30 +2987,27 @@ mod tests {
|
||||||
Workspace::new(Default::default(), 0, project, default_item_factory, cx)
|
Workspace::new(Default::default(), 0, project, default_item_factory, cx)
|
||||||
});
|
});
|
||||||
|
|
||||||
let item1 = cx.add_view(&workspace, |_| {
|
let item1 = cx.add_view(&workspace, |cx| {
|
||||||
let mut item = TestItem::new();
|
TestItem::new()
|
||||||
item.is_dirty = true;
|
.with_dirty(true)
|
||||||
item.project_entry_ids = vec![ProjectEntryId::from_proto(1)];
|
.with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
|
||||||
item
|
|
||||||
});
|
});
|
||||||
let item2 = cx.add_view(&workspace, |_| {
|
let item2 = cx.add_view(&workspace, |cx| {
|
||||||
let mut item = TestItem::new();
|
TestItem::new()
|
||||||
item.is_dirty = true;
|
.with_dirty(true)
|
||||||
item.has_conflict = true;
|
.with_conflict(true)
|
||||||
item.project_entry_ids = vec![ProjectEntryId::from_proto(2)];
|
.with_project_items(&[TestProjectItem::new(2, "2.txt", cx)])
|
||||||
item
|
|
||||||
});
|
});
|
||||||
let item3 = cx.add_view(&workspace, |_| {
|
let item3 = cx.add_view(&workspace, |cx| {
|
||||||
let mut item = TestItem::new();
|
TestItem::new()
|
||||||
item.is_dirty = true;
|
.with_dirty(true)
|
||||||
item.has_conflict = true;
|
.with_conflict(true)
|
||||||
item.project_entry_ids = vec![ProjectEntryId::from_proto(3)];
|
.with_project_items(&[TestProjectItem::new(3, "3.txt", cx)])
|
||||||
item
|
|
||||||
});
|
});
|
||||||
let item4 = cx.add_view(&workspace, |_| {
|
let item4 = cx.add_view(&workspace, |cx| {
|
||||||
let mut item = TestItem::new();
|
TestItem::new()
|
||||||
item.is_dirty = true;
|
.with_dirty(true)
|
||||||
item
|
.with_project_items(&[TestProjectItem::new_untitled(cx)])
|
||||||
});
|
});
|
||||||
let pane = workspace.update(cx, |workspace, cx| {
|
let pane = workspace.update(cx, |workspace, cx| {
|
||||||
workspace.add_item(Box::new(item1.clone()), cx);
|
workspace.add_item(Box::new(item1.clone()), cx);
|
||||||
|
@ -3042,15 +3030,20 @@ mod tests {
|
||||||
[item1_id, item3_id, item4_id].contains(&id)
|
[item1_id, item3_id, item4_id].contains(&id)
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
cx.foreground().run_until_parked();
|
cx.foreground().run_until_parked();
|
||||||
|
|
||||||
|
// There's a prompt to save item 1.
|
||||||
pane.read_with(cx, |pane, _| {
|
pane.read_with(cx, |pane, _| {
|
||||||
assert_eq!(pane.items_len(), 4);
|
assert_eq!(pane.items_len(), 4);
|
||||||
assert_eq!(pane.active_item().unwrap().id(), item1.id());
|
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.simulate_prompt_answer(window_id, 0);
|
||||||
cx.foreground().run_until_parked();
|
cx.foreground().run_until_parked();
|
||||||
|
|
||||||
|
// Item 1 is saved. There's a prompt to save item 3.
|
||||||
pane.read_with(cx, |pane, cx| {
|
pane.read_with(cx, |pane, cx| {
|
||||||
assert_eq!(item1.read(cx).save_count, 1);
|
assert_eq!(item1.read(cx).save_count, 1);
|
||||||
assert_eq!(item1.read(cx).save_as_count, 0);
|
assert_eq!(item1.read(cx).save_as_count, 0);
|
||||||
|
@ -3058,9 +3051,13 @@ mod tests {
|
||||||
assert_eq!(pane.items_len(), 3);
|
assert_eq!(pane.items_len(), 3);
|
||||||
assert_eq!(pane.active_item().unwrap().id(), item3.id());
|
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.simulate_prompt_answer(window_id, 1);
|
||||||
cx.foreground().run_until_parked();
|
cx.foreground().run_until_parked();
|
||||||
|
|
||||||
|
// Item 3 is reloaded. There's a prompt to save item 4.
|
||||||
pane.read_with(cx, |pane, cx| {
|
pane.read_with(cx, |pane, cx| {
|
||||||
assert_eq!(item3.read(cx).save_count, 0);
|
assert_eq!(item3.read(cx).save_count, 0);
|
||||||
assert_eq!(item3.read(cx).save_as_count, 0);
|
assert_eq!(item3.read(cx).save_as_count, 0);
|
||||||
|
@ -3068,11 +3065,17 @@ mod tests {
|
||||||
assert_eq!(pane.items_len(), 2);
|
assert_eq!(pane.items_len(), 2);
|
||||||
assert_eq!(pane.active_item().unwrap().id(), item4.id());
|
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.simulate_prompt_answer(window_id, 0);
|
||||||
cx.foreground().run_until_parked();
|
cx.foreground().run_until_parked();
|
||||||
|
|
||||||
|
// There's a prompt for a path for item 4.
|
||||||
cx.simulate_new_path_selection(|_| Some(Default::default()));
|
cx.simulate_new_path_selection(|_| Some(Default::default()));
|
||||||
close_items.await.unwrap();
|
close_items.await.unwrap();
|
||||||
|
|
||||||
|
// The requested items are closed.
|
||||||
pane.read_with(cx, |pane, cx| {
|
pane.read_with(cx, |pane, cx| {
|
||||||
assert_eq!(item4.read(cx).save_count, 0);
|
assert_eq!(item4.read(cx).save_count, 0);
|
||||||
assert_eq!(item4.read(cx).save_as_count, 1);
|
assert_eq!(item4.read(cx).save_as_count, 1);
|
||||||
|
@ -3097,29 +3100,35 @@ mod tests {
|
||||||
// workspace items with multiple project entries.
|
// workspace items with multiple project entries.
|
||||||
let single_entry_items = (0..=4)
|
let single_entry_items = (0..=4)
|
||||||
.map(|project_entry_id| {
|
.map(|project_entry_id| {
|
||||||
let mut item = TestItem::new();
|
cx.add_view(&workspace, |cx| {
|
||||||
item.is_dirty = true;
|
TestItem::new()
|
||||||
item.project_entry_ids = vec![ProjectEntryId::from_proto(project_entry_id)];
|
.with_dirty(true)
|
||||||
item.is_singleton = true;
|
.with_project_items(&[TestProjectItem::new(
|
||||||
item
|
project_entry_id,
|
||||||
|
&format!("{project_entry_id}.txt"),
|
||||||
|
cx,
|
||||||
|
)])
|
||||||
|
})
|
||||||
})
|
})
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
let item_2_3 = {
|
let item_2_3 = cx.add_view(&workspace, |cx| {
|
||||||
let mut item = TestItem::new();
|
TestItem::new()
|
||||||
item.is_dirty = true;
|
.with_dirty(true)
|
||||||
item.is_singleton = false;
|
.with_singleton(false)
|
||||||
item.project_entry_ids =
|
.with_project_items(&[
|
||||||
vec![ProjectEntryId::from_proto(2), ProjectEntryId::from_proto(3)];
|
single_entry_items[2].read(cx).project_items[0].clone(),
|
||||||
item
|
single_entry_items[3].read(cx).project_items[0].clone(),
|
||||||
};
|
])
|
||||||
let item_3_4 = {
|
});
|
||||||
let mut item = TestItem::new();
|
let item_3_4 = cx.add_view(&workspace, |cx| {
|
||||||
item.is_dirty = true;
|
TestItem::new()
|
||||||
item.is_singleton = false;
|
.with_dirty(true)
|
||||||
item.project_entry_ids =
|
.with_singleton(false)
|
||||||
vec![ProjectEntryId::from_proto(3), ProjectEntryId::from_proto(4)];
|
.with_project_items(&[
|
||||||
item
|
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:
|
// Create two panes that contain the following project entries:
|
||||||
// left pane:
|
// left pane:
|
||||||
|
@ -3130,9 +3139,9 @@ mod tests {
|
||||||
// multi-entry items: (3, 4)
|
// multi-entry items: (3, 4)
|
||||||
let left_pane = workspace.update(cx, |workspace, cx| {
|
let left_pane = workspace.update(cx, |workspace, cx| {
|
||||||
let left_pane = workspace.active_pane().clone();
|
let left_pane = workspace.active_pane().clone();
|
||||||
workspace.add_item(Box::new(cx.add_view(|_| item_2_3.clone())), cx);
|
workspace.add_item(Box::new(item_2_3.clone()), cx);
|
||||||
for item in &single_entry_items {
|
for item in single_entry_items {
|
||||||
workspace.add_item(Box::new(cx.add_view(|_| item.clone())), cx);
|
workspace.add_item(Box::new(item), cx);
|
||||||
}
|
}
|
||||||
left_pane.update(cx, |pane, cx| {
|
left_pane.update(cx, |pane, cx| {
|
||||||
pane.activate_item(2, true, true, cx);
|
pane.activate_item(2, true, true, cx);
|
||||||
|
@ -3147,7 +3156,7 @@ mod tests {
|
||||||
|
|
||||||
//Need to cause an effect flush in order to respect new focus
|
//Need to cause an effect flush in order to respect new focus
|
||||||
workspace.update(cx, |workspace, cx| {
|
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());
|
cx.focus(left_pane.clone());
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -3196,10 +3205,8 @@ mod tests {
|
||||||
Workspace::new(Default::default(), 0, project, default_item_factory, cx)
|
Workspace::new(Default::default(), 0, project, default_item_factory, cx)
|
||||||
});
|
});
|
||||||
|
|
||||||
let item = cx.add_view(&workspace, |_| {
|
let item = cx.add_view(&workspace, |cx| {
|
||||||
let mut item = TestItem::new();
|
TestItem::new().with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
|
||||||
item.project_entry_ids = vec![ProjectEntryId::from_proto(1)];
|
|
||||||
item
|
|
||||||
});
|
});
|
||||||
let item_id = item.id();
|
let item_id = item.id();
|
||||||
workspace.update(cx, |workspace, cx| {
|
workspace.update(cx, |workspace, cx| {
|
||||||
|
@ -3284,7 +3291,9 @@ mod tests {
|
||||||
workspace.add_item(Box::new(item.clone()), cx);
|
workspace.add_item(Box::new(item.clone()), cx);
|
||||||
});
|
});
|
||||||
item.update(cx, |item, 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;
|
item.is_dirty = true;
|
||||||
cx.blur();
|
cx.blur();
|
||||||
});
|
});
|
||||||
|
@ -3315,10 +3324,8 @@ mod tests {
|
||||||
Workspace::new(Default::default(), 0, project, default_item_factory, cx)
|
Workspace::new(Default::default(), 0, project, default_item_factory, cx)
|
||||||
});
|
});
|
||||||
|
|
||||||
let item = cx.add_view(&workspace, |_| {
|
let item = cx.add_view(&workspace, |cx| {
|
||||||
let mut item = TestItem::new();
|
TestItem::new().with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
|
||||||
item.project_entry_ids = vec![ProjectEntryId::from_proto(1)];
|
|
||||||
item
|
|
||||||
});
|
});
|
||||||
let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
|
let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
|
||||||
let toolbar = pane.read_with(cx, |pane, _| pane.toolbar().clone());
|
let toolbar = pane.read_with(cx, |pane, _| pane.toolbar().clone());
|
||||||
|
|
|
@ -3,7 +3,7 @@ authors = ["Nathan Sobo <nathansobo@gmail.com>"]
|
||||||
description = "The fast, collaborative code editor."
|
description = "The fast, collaborative code editor."
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
name = "zed"
|
name = "zed"
|
||||||
version = "0.70.0"
|
version = "0.71.0"
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
name = "zed"
|
name = "zed"
|
||||||
|
|
|
@ -40,5 +40,4 @@
|
||||||
")" @context)) @item
|
")" @context)) @item
|
||||||
|
|
||||||
(field_declaration
|
(field_declaration
|
||||||
name: (_) @name
|
name: (_) @name) @item
|
||||||
type: (_) @context) @item
|
|
Loading…
Add table
Add a link
Reference in a new issue