diff --git a/Cargo.lock b/Cargo.lock index 7be9d45293..06222d02a6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1132,7 +1132,7 @@ dependencies = [ [[package]] name = "collab" -version = "0.5.2" +version = "0.5.3" dependencies = [ "anyhow", "async-tungstenite", @@ -8187,7 +8187,7 @@ checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec" [[package]] name = "zed" -version = "0.70.0" +version = "0.71.0" dependencies = [ "activity_indicator", "anyhow", diff --git a/crates/collab/Cargo.toml b/crates/collab/Cargo.toml index 9c3f0f61c4..8c21af7273 100644 --- a/crates/collab/Cargo.toml +++ b/crates/collab/Cargo.toml @@ -3,7 +3,7 @@ authors = ["Nathan Sobo "] default-run = "collab" edition = "2021" name = "collab" -version = "0.5.2" +version = "0.5.3" [[bin]] name = "collab" diff --git a/crates/collab/src/tests/integration_tests.rs b/crates/collab/src/tests/integration_tests.rs index 729da6d109..3f2a777f87 100644 --- a/crates/collab/src/tests/integration_tests.rs +++ b/crates/collab/src/tests/integration_tests.rs @@ -32,7 +32,9 @@ use std::{ sync::Arc, }; 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] fn init_logger() { @@ -5602,7 +5604,7 @@ async fn test_following( }); assert!(cx_b.read(|cx| editor_b2.is_focused(cx))); 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()) ); assert_eq!( diff --git a/crates/diagnostics/src/diagnostics.rs b/crates/diagnostics/src/diagnostics.rs index e98aa645d5..d3078bce81 100644 --- a/crates/diagnostics/src/diagnostics.rs +++ b/crates/diagnostics/src/diagnostics.rs @@ -21,7 +21,6 @@ use language::{ use project::{DiagnosticSummary, Project, ProjectPath}; use serde_json::json; use settings::Settings; -use smallvec::SmallVec; use std::{ any::{Any, TypeId}, cmp::Ordering, @@ -521,12 +520,8 @@ impl Item for ProjectDiagnosticsEditor { ) } - fn project_path(&self, _: &AppContext) -> Option { - None - } - - fn project_entry_ids(&self, cx: &AppContext) -> SmallVec<[project::ProjectEntryId; 3]> { - self.editor.project_entry_ids(cx) + fn for_each_project_item(&self, cx: &AppContext, f: &mut dyn FnMut(usize, &dyn project::Item)) { + self.editor.for_each_project_item(cx, f) } fn is_singleton(&self, _: &AppContext) -> bool { diff --git a/crates/editor/src/items.rs b/crates/editor/src/items.rs index cce6d338f0..7e7f44e514 100644 --- a/crates/editor/src/items.rs +++ b/crates/editor/src/items.rs @@ -8,19 +8,17 @@ use anyhow::{anyhow, Context, Result}; use collections::HashSet; use futures::future::try_join_all; use futures::FutureExt; - use gpui::{ elements::*, geometry::vector::vec2f, AppContext, Entity, ModelHandle, MutableAppContext, RenderContext, Subscription, Task, View, ViewContext, ViewHandle, WeakViewHandle, }; use language::{ - proto::serialize_anchor as serialize_text_anchor, Bias, Buffer, File as _, OffsetRangeExt, - Point, SelectionGoal, + proto::serialize_anchor as serialize_text_anchor, Bias, Buffer, OffsetRangeExt, Point, + SelectionGoal, }; -use project::{File, FormatTrigger, Project, ProjectEntryId, ProjectPath}; +use project::{FormatTrigger, Item as _, Project, ProjectPath}; use rpc::proto::{self, update_view}; use settings::Settings; -use smallvec::SmallVec; use std::{ borrow::Cow, cmp::{self, Ordering}, @@ -557,22 +555,10 @@ impl Item for Editor { .boxed() } - fn project_path(&self, cx: &AppContext) -> Option { - 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]> { + fn for_each_project_item(&self, cx: &AppContext, f: &mut dyn FnMut(usize, &dyn project::Item)) { self.buffer .read(cx) - .files(cx) - .into_iter() - .filter_map(|file| File::from_dyn(Some(file))?.project_entry_id(cx)) - .collect() + .for_each_buffer(|buffer| f(buffer.id(), buffer.read(cx))); } fn is_singleton(&self, cx: &AppContext) -> bool { @@ -609,7 +595,12 @@ impl Item for Editor { } 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( diff --git a/crates/editor/src/multi_buffer.rs b/crates/editor/src/multi_buffer.rs index 7812c34781..6b5262c16a 100644 --- a/crates/editor/src/multi_buffer.rs +++ b/crates/editor/src/multi_buffer.rs @@ -10,11 +10,10 @@ use gpui::{AppContext, Entity, ModelContext, ModelHandle, Task}; pub use language::Completion; use language::{ 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 _, ToPointUtf16 as _, TransactionId, Unclipped, }; -use smallvec::SmallVec; use std::{ borrow::Cow, cell::{Ref, RefCell}, @@ -1338,12 +1337,11 @@ impl MultiBuffer { .and_then(|(buffer, offset)| buffer.read(cx).language_at(offset)) } - pub fn files<'a>(&'a self, cx: &'a AppContext) -> SmallVec<[&'a Arc; 2]> { - let buffers = self.buffers.borrow(); - buffers + pub fn for_each_buffer(&self, mut f: impl FnMut(&ModelHandle)) { + self.buffers + .borrow() .values() - .filter_map(|buffer| buffer.buffer.read(cx).file()) - .collect() + .for_each(|state| f(&state.buffer)) } pub fn title<'a>(&'a self, cx: &'a AppContext) -> Cow<'a, str> { diff --git a/crates/language/src/buffer.rs b/crates/language/src/buffer.rs index 972a33c0b7..ad3bdb6821 100644 --- a/crates/language/src/buffer.rs +++ b/crates/language/src/buffer.rs @@ -2169,8 +2169,6 @@ impl BufferSnapshot { continue; } - // TODO - move later, after processing captures - let mut text = String::new(); let mut name_ranges = Vec::new(); let mut highlight_ranges = Vec::new(); @@ -2184,7 +2182,13 @@ impl BufferSnapshot { 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() { text.push(' '); } diff --git a/crates/language/src/buffer_tests.rs b/crates/language/src/buffer_tests.rs index b7f35e9ad5..5aa9152205 100644 --- a/crates/language/src/buffer_tests.rs +++ b/crates/language/src/buffer_tests.rs @@ -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::>(), + &[("impl A for B<", 0)] + ); +} + #[gpui::test] async fn test_symbols_containing(cx: &mut gpui::TestAppContext) { let text = r#" diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index ce7296994a..995a6514c5 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -67,8 +67,9 @@ use util::{debug_panic, defer, post_inc, ResultExt, TryFutureExt as _}; pub use fs::*; pub use worktree::*; -pub trait Item: Entity { +pub trait Item { fn entry_id(&self, cx: &AppContext) -> Option; + fn project_path(&self, cx: &AppContext) -> Option; } // Language server state is stored across 3 collections: @@ -6405,4 +6406,11 @@ impl Item for Buffer { fn entry_id(&self, cx: &AppContext) -> Option { File::from_dyn(self.file()).and_then(|file| file.project_entry_id(cx)) } + + fn project_path(&self, cx: &AppContext) -> Option { + File::from_dyn(self.file()).map(|file| ProjectPath { + worktree_id: file.worktree_id(cx), + path: file.path().clone(), + }) + } } diff --git a/crates/project_panel/src/project_panel.rs b/crates/project_panel/src/project_panel.rs index 0042695950..e59353aae4 100644 --- a/crates/project_panel/src/project_panel.rs +++ b/crates/project_panel/src/project_panel.rs @@ -763,7 +763,6 @@ impl ProjectPanel { ix += 1; } - self.clipboard_entry.take(); if clipboard_entry.is_cut() { if let Some(task) = self.project.update(cx, |project, cx| { project.rename_entry(clipboard_entry.entry_id(), new_path, cx) @@ -1176,13 +1175,15 @@ impl ProjectPanel { ) }) .on_click(MouseButton::Left, move |e, cx| { - if kind == EntryKind::Dir { - cx.dispatch_action(ToggleExpanded(entry_id)) - } else { - cx.dispatch_action(Open { - entry_id, - change_focus: e.click_count > 1, - }) + if !show_editor { + if kind == EntryKind::Dir { + cx.dispatch_action(ToggleExpanded(entry_id)) + } else { + cx.dispatch_action(Open { + entry_id, + change_focus: e.click_count > 1, + }) + } } }) .on_down(MouseButton::Right, move |e, cx| { diff --git a/crates/search/src/project_search.rs b/crates/search/src/project_search.rs index 68c2f33a75..a315cf0c5f 100644 --- a/crates/search/src/project_search.rs +++ b/crates/search/src/project_search.rs @@ -16,7 +16,6 @@ use gpui::{ use menu::Confirm; use project::{search::SearchQuery, Project}; use settings::Settings; -use smallvec::SmallVec; use std::{ any::{Any, TypeId}, mem, @@ -275,12 +274,8 @@ impl Item for ProjectSearchView { .boxed() } - fn project_path(&self, _: &gpui::AppContext) -> Option { - None - } - - fn project_entry_ids(&self, cx: &AppContext) -> SmallVec<[project::ProjectEntryId; 3]> { - self.results_editor.project_entry_ids(cx) + fn for_each_project_item(&self, cx: &AppContext, f: &mut dyn FnMut(usize, &dyn project::Item)) { + self.results_editor.for_each_project_item(cx, f) } fn is_singleton(&self, _: &AppContext) -> bool { diff --git a/crates/terminal_view/src/terminal_view.rs b/crates/terminal_view/src/terminal_view.rs index e02a9758dd..847dfc5ee5 100644 --- a/crates/terminal_view/src/terminal_view.rs +++ b/crates/terminal_view/src/terminal_view.rs @@ -18,10 +18,9 @@ use gpui::{ AnyViewHandle, AppContext, Element, ElementBox, Entity, ModelHandle, MutableAppContext, Task, View, ViewContext, ViewHandle, WeakViewHandle, }; -use project::{LocalWorktree, Project, ProjectPath}; +use project::{LocalWorktree, Project}; use serde::Deserialize; use settings::{Settings, TerminalBlink, WorkingDirectory}; -use smallvec::SmallVec; use smol::Timer; use terminal::{ alacritty_terminal::{ @@ -616,13 +615,7 @@ impl Item for TerminalView { None } - fn project_path(&self, _cx: &gpui::AppContext) -> Option { - None - } - - fn project_entry_ids(&self, _cx: &gpui::AppContext) -> SmallVec<[project::ProjectEntryId; 3]> { - SmallVec::new() - } + fn for_each_project_item(&self, _: &AppContext, _: &mut dyn FnMut(usize, &dyn project::Item)) {} fn is_singleton(&self, _cx: &gpui::AppContext) -> bool { false diff --git a/crates/theme_testbench/src/theme_testbench.rs b/crates/theme_testbench/src/theme_testbench.rs index cf9f03de45..3cda5d3e51 100644 --- a/crates/theme_testbench/src/theme_testbench.rs +++ b/crates/theme_testbench/src/theme_testbench.rs @@ -6,12 +6,11 @@ use gpui::{ Padding, ParentElement, }, fonts::TextStyle, - Border, Element, Entity, ModelHandle, MutableAppContext, Quad, RenderContext, Task, View, - ViewContext, ViewHandle, WeakViewHandle, + AppContext, Border, Element, Entity, ModelHandle, MutableAppContext, Quad, RenderContext, Task, + View, ViewContext, ViewHandle, WeakViewHandle, }; -use project::{Project, ProjectEntryId, ProjectPath}; +use project::Project; use settings::Settings; -use smallvec::SmallVec; use theme::{ColorScheme, Layer, Style, StyleSet}; use workspace::{ item::{Item, ItemEvent}, @@ -306,7 +305,7 @@ impl Item for ThemeTestbench { &self, _: Option, style: &theme::Tab, - _: &gpui::AppContext, + _: &AppContext, ) -> gpui::ElementBox { Label::new("Theme Testbench".into(), style.label.clone()) .aligned() @@ -314,21 +313,15 @@ impl Item for ThemeTestbench { .boxed() } - fn project_path(&self, _: &gpui::AppContext) -> Option { - None - } + fn for_each_project_item(&self, _: &AppContext, _: &mut dyn FnMut(usize, &dyn project::Item)) {} - fn project_entry_ids(&self, _: &gpui::AppContext) -> SmallVec<[ProjectEntryId; 3]> { - SmallVec::new() - } - - fn is_singleton(&self, _: &gpui::AppContext) -> bool { + fn is_singleton(&self, _: &AppContext) -> bool { false } fn set_nav_history(&mut self, _: workspace::ItemNavHistory, _: &mut ViewContext) {} - fn can_save(&self, _: &gpui::AppContext) -> bool { + fn can_save(&self, _: &AppContext) -> bool { false } diff --git a/crates/workspace/src/item.rs b/crates/workspace/src/item.rs index 19385038e4..b1888bb243 100644 --- a/crates/workspace/src/item.rs +++ b/crates/workspace/src/item.rs @@ -49,8 +49,7 @@ pub trait Item: View { } fn tab_content(&self, detail: Option, style: &theme::Tab, cx: &AppContext) -> ElementBox; - fn project_path(&self, cx: &AppContext) -> Option; - 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); fn clone_on_split(&self, _workspace_id: WorkspaceId, _: &mut ViewContext) -> Option @@ -147,6 +146,8 @@ pub trait ItemHandle: 'static + fmt::Debug { -> ElementBox; fn project_path(&self, cx: &AppContext) -> Option; 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; fn clone_on_split( @@ -240,11 +241,36 @@ impl ItemHandle for ViewHandle { } fn project_path(&self, cx: &AppContext) -> Option { - 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 WeakItemHandle for WeakViewHandle { } pub trait ProjectItem: Item { - type Item: project::Item; + type Item: project::Item + gpui::Entity; fn for_project_item( project: ModelHandle, @@ -690,18 +716,19 @@ impl FollowableItemHandle for ViewHandle { #[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, + pub project_path: Option, + } 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, - pub project_path: Option, + pub project_items: Vec>, pub nav_history: Option, pub tab_descriptions: Option>, pub tab_detail: Cell>, } + impl Entity for TestProjectItem { + type Event = (); + } + + impl project::Item for TestProjectItem { + fn entry_id(&self, _: &AppContext) -> Option { + self.entry_id + } + + fn project_path(&self, _: &AppContext) -> Option { + 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 { + 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 { + 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]) -> 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 { - 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( diff --git a/crates/workspace/src/pane.rs b/crates/workspace/src/pane.rs index bd355b8b3e..8d0d731551 100644 --- a/crates/workspace/src/pane.rs +++ b/crates/workspace/src/pane.rs @@ -482,7 +482,7 @@ impl Pane { ) -> Box { 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(); @@ -804,13 +804,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 @@ -818,30 +818,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) @@ -1677,7 +1670,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; @@ -1866,7 +1859,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, @@ -1885,7 +1878,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, @@ -1904,7 +1897,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, @@ -1923,7 +1916,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, @@ -1942,7 +1935,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, diff --git a/crates/workspace/src/shared_screen.rs b/crates/workspace/src/shared_screen.rs index d292ece3d5..b76535f6ed 100644 --- a/crates/workspace/src/shared_screen.rs +++ b/crates/workspace/src/shared_screen.rs @@ -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, 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 { - 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 } diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index de5b50b468..5851734609 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -2746,7 +2746,7 @@ pub fn open_new(app_state: &Arc, 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; @@ -2853,15 +2853,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 @@ -2962,16 +2958,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); @@ -2996,30 +2987,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); @@ -3042,15 +3030,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); @@ -3058,9 +3051,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); @@ -3068,11 +3065,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); @@ -3097,29 +3100,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::>(); - 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: @@ -3130,9 +3139,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); @@ -3147,7 +3156,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()); }); @@ -3196,10 +3205,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| { @@ -3284,7 +3291,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(); }); @@ -3315,10 +3324,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()); diff --git a/crates/zed/Cargo.toml b/crates/zed/Cargo.toml index 38fa2ad4be..63719b7003 100644 --- a/crates/zed/Cargo.toml +++ b/crates/zed/Cargo.toml @@ -3,7 +3,7 @@ authors = ["Nathan Sobo "] description = "The fast, collaborative code editor." edition = "2021" name = "zed" -version = "0.70.0" +version = "0.71.0" [lib] name = "zed" diff --git a/crates/zed/src/languages/go/outline.scm b/crates/zed/src/languages/go/outline.scm index 849d2bdebf..2ff7ef25a0 100644 --- a/crates/zed/src/languages/go/outline.scm +++ b/crates/zed/src/languages/go/outline.scm @@ -40,5 +40,4 @@ ")" @context)) @item (field_declaration - name: (_) @name - type: (_) @context) @item \ No newline at end of file + name: (_) @name) @item \ No newline at end of file