@@ -1,66 +1,110 @@
-use super::{Item, ItemView};
-use crate::{status_bar::StatusItemView, Settings};
+use crate::{Editor, EditorSettings, Event};
use anyhow::Result;
use buffer::{Point, Selection, ToPoint};
-use editor::{Editor, EditorSettings, Event};
use gpui::{
- elements::*, fonts::TextStyle, AppContext, Entity, ModelHandle, RenderContext, Subscription,
- Task, View, ViewContext, ViewHandle,
+ elements::*, fonts::TextStyle, AppContext, Entity, ModelContext, ModelHandle,
+ MutableAppContext, RenderContext, Subscription, Task, View, ViewContext, ViewHandle,
+ WeakModelHandle,
};
use language::{Buffer, Diagnostic, File as _};
use postage::watch;
use project::{ProjectPath, Worktree};
use std::fmt::Write;
use std::path::Path;
+use workspace::{
+ EntryOpener, ItemHandle, ItemView, ItemViewHandle, Settings, StatusItemView, WeakItemHandle,
+};
-impl Item for Buffer {
- type View = Editor;
+pub struct BufferOpener;
+
+#[derive(Clone)]
+pub struct BufferItemHandle(pub ModelHandle<Buffer>);
+
+#[derive(Clone)]
+struct WeakBufferItemHandle(WeakModelHandle<Buffer>);
+
+impl EntryOpener for BufferOpener {
+ fn open(
+ &self,
+ worktree: &mut Worktree,
+ project_path: ProjectPath,
+ cx: &mut ModelContext<Worktree>,
+ ) -> Option<Task<Result<Box<dyn ItemHandle>>>> {
+ let buffer = worktree.open_buffer(project_path.path, cx);
+ let task = cx.spawn(|_, _| async move {
+ buffer
+ .await
+ .map(|buffer| Box::new(BufferItemHandle(buffer)) as Box<dyn ItemHandle>)
+ });
+ Some(task)
+ }
+}
- fn build_view(
- handle: ModelHandle<Self>,
+impl ItemHandle for BufferItemHandle {
+ fn add_view(
+ &self,
+ window_id: usize,
settings: watch::Receiver<Settings>,
- cx: &mut ViewContext<Self::View>,
- ) -> Self::View {
- Editor::for_buffer(
- handle,
- move |cx| {
- let settings = settings.borrow();
- let font_cache = cx.font_cache();
- let font_family_id = settings.buffer_font_family;
- let font_family_name = cx.font_cache().family_name(font_family_id).unwrap();
- let font_properties = Default::default();
- let font_id = font_cache
- .select_font(font_family_id, &font_properties)
- .unwrap();
- let font_size = settings.buffer_font_size;
-
- let mut theme = settings.theme.editor.clone();
- theme.text = TextStyle {
- color: theme.text.color,
- font_family_name,
- font_family_id,
- font_id,
- font_size,
- font_properties,
- underline: None,
- };
- EditorSettings {
- tab_size: settings.tab_size,
- style: theme,
- }
- },
- cx,
- )
+ cx: &mut MutableAppContext,
+ ) -> Box<dyn ItemViewHandle> {
+ Box::new(cx.add_view(window_id, |cx| {
+ Editor::for_buffer(
+ self.0.clone(),
+ move |cx| {
+ let settings = settings.borrow();
+ let font_cache = cx.font_cache();
+ let font_family_id = settings.buffer_font_family;
+ let font_family_name = cx.font_cache().family_name(font_family_id).unwrap();
+ let font_properties = Default::default();
+ let font_id = font_cache
+ .select_font(font_family_id, &font_properties)
+ .unwrap();
+ let font_size = settings.buffer_font_size;
+
+ let mut theme = settings.theme.editor.clone();
+ theme.text = TextStyle {
+ color: theme.text.color,
+ font_family_name,
+ font_family_id,
+ font_id,
+ font_size,
+ font_properties,
+ underline: None,
+ };
+ EditorSettings {
+ tab_size: settings.tab_size,
+ style: theme,
+ }
+ },
+ cx,
+ )
+ }))
+ }
+
+ fn boxed_clone(&self) -> Box<dyn ItemHandle> {
+ Box::new(self.clone())
}
- fn project_path(&self) -> Option<ProjectPath> {
- self.file().map(|f| ProjectPath {
+ fn downgrade(&self) -> Box<dyn workspace::WeakItemHandle> {
+ Box::new(WeakBufferItemHandle(self.0.downgrade()))
+ }
+
+ fn project_path(&self, cx: &AppContext) -> Option<ProjectPath> {
+ self.0.read(cx).file().map(|f| ProjectPath {
worktree_id: f.worktree_id(),
path: f.path().clone(),
})
}
}
+impl WeakItemHandle for WeakBufferItemHandle {
+ fn upgrade(&self, cx: &AppContext) -> Option<Box<dyn ItemHandle>> {
+ self.0
+ .upgrade(cx)
+ .map(|buffer| Box::new(BufferItemHandle(buffer)) as Box<dyn ItemHandle>)
+ }
+}
+
impl ItemView for Editor {
fn should_activate_item_on_event(event: &Event) -> bool {
matches!(event, Event::Activate)
@@ -226,7 +270,7 @@ impl View for CursorPosition {
impl StatusItemView for CursorPosition {
fn set_active_pane_item(
&mut self,
- active_pane_item: Option<&dyn crate::ItemViewHandle>,
+ active_pane_item: Option<&dyn ItemViewHandle>,
cx: &mut ViewContext<Self>,
) {
if let Some(editor) = active_pane_item.and_then(|item| item.to_any().downcast::<Editor>()) {
@@ -312,7 +356,7 @@ impl View for DiagnosticMessage {
impl StatusItemView for DiagnosticMessage {
fn set_active_pane_item(
&mut self,
- active_pane_item: Option<&dyn crate::ItemViewHandle>,
+ active_pane_item: Option<&dyn ItemViewHandle>,
cx: &mut ViewContext<Self>,
) {
if let Some(editor) = active_pane_item.and_then(|item| item.to_any().downcast::<Editor>()) {
@@ -1,4 +1,3 @@
-pub mod items;
pub mod pane;
pub mod pane_group;
pub mod settings;
@@ -12,7 +11,7 @@ use gpui::{
AnyViewHandle, AppContext, ClipboardItem, Entity, ModelContext, ModelHandle, MutableAppContext,
PromptLevel, RenderContext, Task, View, ViewContext, ViewHandle, WeakModelHandle,
};
-use language::{Buffer, LanguageRegistry};
+use language::LanguageRegistry;
use log::error;
pub use pane::*;
pub use pane_group::*;
@@ -21,6 +20,7 @@ use project::{Fs, Project, ProjectPath, Worktree};
pub use settings::Settings;
use sidebar::{Side, Sidebar, SidebarItemId, ToggleSidebarItem, ToggleSidebarItemFocus};
use status_bar::StatusBar;
+pub use status_bar::StatusItemView;
use std::{
collections::{hash_map::Entry, HashMap},
future::Future,
@@ -32,31 +32,9 @@ action!(OpenNew, WorkspaceParams);
action!(Save);
action!(DebugElements);
-struct BufferOpener;
-
-impl EntryOpener for BufferOpener {
- fn open(
- &self,
- worktree: &mut Worktree,
- project_path: ProjectPath,
- cx: &mut ModelContext<Worktree>,
- ) -> Option<Task<Result<Box<dyn ItemHandle>>>> {
- let buffer = worktree.open_buffer(project_path.path, cx);
- let task = cx.spawn(|_, _| async move {
- buffer
- .await
- .map(|buffer| Box::new(buffer) as Box<dyn ItemHandle>)
- });
- Some(task)
- }
-}
-
-pub fn init(cx: &mut MutableAppContext, entry_openers: &mut Vec<Box<dyn EntryOpener>>) {
- entry_openers.push(Box::new(BufferOpener));
-
+pub fn init(cx: &mut MutableAppContext) {
cx.add_action(Workspace::save_active_item);
cx.add_action(Workspace::debug_elements);
- cx.add_action(Workspace::open_new_file);
cx.add_action(Workspace::toggle_sidebar_item);
cx.add_action(Workspace::toggle_sidebar_item_focus);
cx.add_bindings(vec![
@@ -137,21 +115,21 @@ pub trait ItemView: View {
}
pub trait ItemHandle: Send + Sync {
- fn boxed_clone(&self) -> Box<dyn ItemHandle>;
- fn downgrade(&self) -> Box<dyn WeakItemHandle>;
-}
-
-pub trait WeakItemHandle {
fn add_view(
&self,
window_id: usize,
settings: watch::Receiver<Settings>,
cx: &mut MutableAppContext,
- ) -> Option<Box<dyn ItemViewHandle>>;
- fn alive(&self, cx: &AppContext) -> bool;
+ ) -> Box<dyn ItemViewHandle>;
+ fn boxed_clone(&self) -> Box<dyn ItemHandle>;
+ fn downgrade(&self) -> Box<dyn WeakItemHandle>;
fn project_path(&self, cx: &AppContext) -> Option<ProjectPath>;
}
+pub trait WeakItemHandle {
+ fn upgrade(&self, cx: &AppContext) -> Option<Box<dyn ItemHandle>>;
+}
+
pub trait ItemViewHandle {
fn title(&self, cx: &AppContext) -> String;
fn project_path(&self, cx: &AppContext) -> Option<ProjectPath>;
@@ -172,6 +150,15 @@ pub trait ItemViewHandle {
}
impl<T: Item> ItemHandle for ModelHandle<T> {
+ fn add_view(
+ &self,
+ window_id: usize,
+ settings: watch::Receiver<Settings>,
+ cx: &mut MutableAppContext,
+ ) -> Box<dyn ItemViewHandle> {
+ Box::new(cx.add_view(window_id, |cx| T::build_view(self.clone(), settings, cx)))
+ }
+
fn boxed_clone(&self) -> Box<dyn ItemHandle> {
Box::new(self.clone())
}
@@ -179,30 +166,38 @@ impl<T: Item> ItemHandle for ModelHandle<T> {
fn downgrade(&self) -> Box<dyn WeakItemHandle> {
Box::new(self.downgrade())
}
+
+ fn project_path(&self, cx: &AppContext) -> Option<ProjectPath> {
+ self.read(cx).project_path()
+ }
}
-impl<T: Item> WeakItemHandle for WeakModelHandle<T> {
+impl ItemHandle for Box<dyn ItemHandle> {
fn add_view(
&self,
window_id: usize,
settings: watch::Receiver<Settings>,
cx: &mut MutableAppContext,
- ) -> Option<Box<dyn ItemViewHandle>> {
- if let Some(handle) = self.upgrade(cx.as_ref()) {
- Some(Box::new(cx.add_view(window_id, |cx| {
- T::build_view(handle, settings, cx)
- })))
- } else {
- None
- }
+ ) -> Box<dyn ItemViewHandle> {
+ ItemHandle::add_view(self.as_ref(), window_id, settings, cx)
}
- fn alive(&self, cx: &AppContext) -> bool {
- self.upgrade(cx).is_some()
+ fn boxed_clone(&self) -> Box<dyn ItemHandle> {
+ self.as_ref().boxed_clone()
+ }
+
+ fn downgrade(&self) -> Box<dyn WeakItemHandle> {
+ self.as_ref().downgrade()
}
fn project_path(&self, cx: &AppContext) -> Option<ProjectPath> {
- self.upgrade(cx).and_then(|h| h.read(cx).project_path())
+ self.as_ref().project_path(cx)
+ }
+}
+
+impl<T: Item> WeakItemHandle for WeakModelHandle<T> {
+ fn upgrade(&self, cx: &AppContext) -> Option<Box<dyn ItemHandle>> {
+ WeakModelHandle::<T>::upgrade(*self, cx).map(|i| Box::new(i) as Box<dyn ItemHandle>)
}
}
@@ -589,16 +584,6 @@ impl Workspace {
}
}
- pub fn open_new_file(&mut self, _: &OpenNew, cx: &mut ViewContext<Self>) {
- let buffer = cx.add_model(|cx| Buffer::new(0, "", cx));
- let item_handle = ItemHandle::downgrade(&buffer);
- let view = item_handle
- .add_view(cx.window_id(), self.settings.clone(), cx)
- .unwrap();
- self.items.push(item_handle);
- self.active_pane().add_item_view(view, cx.as_mut());
- }
-
#[must_use]
pub fn open_entry(
&mut self,
@@ -647,7 +632,6 @@ impl Workspace {
}
let pane = pane.downgrade();
- let settings = self.settings.clone();
let mut watch = self.loading_items.get(&project_path).unwrap().clone();
Some(cx.spawn(|this, mut cx| async move {
@@ -667,12 +651,7 @@ impl Workspace {
// to the pane. If it was, we activate it, otherwise we'll store the
// item and add a new view for it.
if !this.activate_or_open_existing_entry(project_path, &pane, cx) {
- let weak_item = item.downgrade();
- let view = weak_item
- .add_view(cx.window_id(), settings, cx.as_mut())
- .unwrap();
- this.items.push(weak_item);
- pane.add_item_view(view, cx.as_mut());
+ this.add_item(item, cx);
}
}
Err(error) => {
@@ -701,16 +680,14 @@ impl Workspace {
let settings = self.settings.clone();
let mut view_for_existing_item = None;
self.items.retain(|item| {
- if item.alive(cx.as_ref()) {
+ if let Some(item) = item.upgrade(cx) {
if view_for_existing_item.is_none()
&& item
.project_path(cx)
.map_or(false, |item_project_path| item_project_path == project_path)
{
- view_for_existing_item = Some(
- item.add_view(cx.window_id(), settings.clone(), cx.as_mut())
- .unwrap(),
- );
+ view_for_existing_item =
+ Some(item.add_view(cx.window_id(), settings.clone(), cx.as_mut()));
}
true
} else {
@@ -866,6 +843,15 @@ impl Workspace {
pane
}
+ pub fn add_item<T>(&mut self, item_handle: T, cx: &mut ViewContext<Self>)
+ where
+ T: ItemHandle,
+ {
+ let view = item_handle.add_view(cx.window_id(), self.settings.clone(), cx);
+ self.items.push(item_handle.downgrade());
+ self.active_pane().add_item_view(view, cx.as_mut());
+ }
+
fn activate_pane(&mut self, pane: ViewHandle<Pane>, cx: &mut ViewContext<Self>) {
self.active_pane = pane;
self.status_bar.update(cx, |status_bar, cx| {
@@ -1121,447 +1107,447 @@ impl WorkspaceHandle for ViewHandle<Workspace> {
}
}
-#[cfg(test)]
-mod tests {
- use super::*;
- use editor::{Editor, Input};
- use serde_json::json;
- use std::collections::HashSet;
-
- #[gpui::test]
- async fn test_open_entry(mut cx: gpui::TestAppContext) {
- let params = cx.update(WorkspaceParams::test);
- params
- .fs
- .as_fake()
- .insert_tree(
- "/root",
- json!({
- "a": {
- "file1": "contents 1",
- "file2": "contents 2",
- "file3": "contents 3",
- },
- }),
- )
- .await;
-
- let (_, workspace) = cx.add_window(|cx| Workspace::new(¶ms, cx));
- workspace
- .update(&mut cx, |workspace, cx| {
- workspace.add_worktree(Path::new("/root"), cx)
- })
- .await
- .unwrap();
-
- cx.read(|cx| workspace.read(cx).worktree_scans_complete(cx))
- .await;
- let entries = cx.read(|cx| workspace.file_project_paths(cx));
- let file1 = entries[0].clone();
- let file2 = entries[1].clone();
- let file3 = entries[2].clone();
-
- // Open the first entry
- workspace
- .update(&mut cx, |w, cx| w.open_entry(file1.clone(), cx))
- .unwrap()
- .await;
- cx.read(|cx| {
- let pane = workspace.read(cx).active_pane().read(cx);
- assert_eq!(
- pane.active_item().unwrap().project_path(cx),
- Some(file1.clone())
- );
- assert_eq!(pane.items().len(), 1);
- });
-
- // Open the second entry
- workspace
- .update(&mut cx, |w, cx| w.open_entry(file2.clone(), cx))
- .unwrap()
- .await;
- cx.read(|cx| {
- let pane = workspace.read(cx).active_pane().read(cx);
- assert_eq!(
- pane.active_item().unwrap().project_path(cx),
- Some(file2.clone())
- );
- assert_eq!(pane.items().len(), 2);
- });
-
- // Open the first entry again. The existing pane item is activated.
- workspace.update(&mut cx, |w, cx| {
- assert!(w.open_entry(file1.clone(), cx).is_none())
- });
- cx.read(|cx| {
- let pane = workspace.read(cx).active_pane().read(cx);
- assert_eq!(
- pane.active_item().unwrap().project_path(cx),
- Some(file1.clone())
- );
- assert_eq!(pane.items().len(), 2);
- });
-
- // Split the pane with the first entry, then open the second entry again.
- workspace.update(&mut cx, |w, cx| {
- w.split_pane(w.active_pane().clone(), SplitDirection::Right, cx);
- assert!(w.open_entry(file2.clone(), cx).is_none());
- assert_eq!(
- w.active_pane()
- .read(cx)
- .active_item()
- .unwrap()
- .project_path(cx.as_ref()),
- Some(file2.clone())
- );
- });
-
- // Open the third entry twice concurrently. Only one pane item is added.
- let (t1, t2) = workspace.update(&mut cx, |w, cx| {
- (
- w.open_entry(file3.clone(), cx).unwrap(),
- w.open_entry(file3.clone(), cx).unwrap(),
- )
- });
- t1.await;
- t2.await;
- cx.read(|cx| {
- let pane = workspace.read(cx).active_pane().read(cx);
- assert_eq!(
- pane.active_item().unwrap().project_path(cx),
- Some(file3.clone())
- );
- let pane_entries = pane
- .items()
- .iter()
- .map(|i| i.project_path(cx).unwrap())
- .collect::<Vec<_>>();
- assert_eq!(pane_entries, &[file1, file2, file3]);
- });
- }
-
- #[gpui::test]
- async fn test_open_paths(mut cx: gpui::TestAppContext) {
- let params = cx.update(WorkspaceParams::test);
- let fs = params.fs.as_fake();
- fs.insert_dir("/dir1").await.unwrap();
- fs.insert_dir("/dir2").await.unwrap();
- fs.insert_file("/dir1/a.txt", "".into()).await.unwrap();
- fs.insert_file("/dir2/b.txt", "".into()).await.unwrap();
-
- let (_, workspace) = cx.add_window(|cx| Workspace::new(¶ms, cx));
- workspace
- .update(&mut cx, |workspace, cx| {
- workspace.add_worktree("/dir1".as_ref(), cx)
- })
- .await
- .unwrap();
- cx.read(|cx| workspace.read(cx).worktree_scans_complete(cx))
- .await;
-
- // Open a file within an existing worktree.
- cx.update(|cx| {
- workspace.update(cx, |view, cx| view.open_paths(&["/dir1/a.txt".into()], cx))
- })
- .await;
- cx.read(|cx| {
- assert_eq!(
- workspace
- .read(cx)
- .active_pane()
- .read(cx)
- .active_item()
- .unwrap()
- .title(cx),
- "a.txt"
- );
- });
-
- // Open a file outside of any existing worktree.
- cx.update(|cx| {
- workspace.update(cx, |view, cx| view.open_paths(&["/dir2/b.txt".into()], cx))
- })
- .await;
- cx.read(|cx| {
- let worktree_roots = workspace
- .read(cx)
- .worktrees(cx)
- .iter()
- .map(|w| w.read(cx).as_local().unwrap().abs_path())
- .collect::<HashSet<_>>();
- assert_eq!(
- worktree_roots,
- vec!["/dir1", "/dir2/b.txt"]
- .into_iter()
- .map(Path::new)
- .collect(),
- );
- assert_eq!(
- workspace
- .read(cx)
- .active_pane()
- .read(cx)
- .active_item()
- .unwrap()
- .title(cx),
- "b.txt"
- );
- });
- }
-
- #[gpui::test]
- async fn test_save_conflicting_item(mut cx: gpui::TestAppContext) {
- let params = cx.update(WorkspaceParams::test);
- let fs = params.fs.as_fake();
- fs.insert_tree("/root", json!({ "a.txt": "" })).await;
-
- let (window_id, workspace) = cx.add_window(|cx| Workspace::new(¶ms, cx));
- workspace
- .update(&mut cx, |workspace, cx| {
- workspace.add_worktree(Path::new("/root"), cx)
- })
- .await
- .unwrap();
-
- // Open a file within an existing worktree.
- cx.update(|cx| {
- workspace.update(cx, |view, cx| {
- view.open_paths(&[PathBuf::from("/root/a.txt")], cx)
- })
- })
- .await;
- let editor = cx.read(|cx| {
- let pane = workspace.read(cx).active_pane().read(cx);
- let item = pane.active_item().unwrap();
- item.to_any().downcast::<Editor>().unwrap()
- });
-
- cx.update(|cx| editor.update(cx, |editor, cx| editor.handle_input(&Input("x".into()), cx)));
- fs.insert_file("/root/a.txt", "changed".to_string())
- .await
- .unwrap();
- editor
- .condition(&cx, |editor, cx| editor.has_conflict(cx))
- .await;
- cx.read(|cx| assert!(editor.is_dirty(cx)));
-
- cx.update(|cx| workspace.update(cx, |w, cx| w.save_active_item(&Save, cx)));
- cx.simulate_prompt_answer(window_id, 0);
- editor
- .condition(&cx, |editor, cx| !editor.is_dirty(cx))
- .await;
- cx.read(|cx| assert!(!editor.has_conflict(cx)));
- }
-
- #[gpui::test]
- async fn test_open_and_save_new_file(mut cx: gpui::TestAppContext) {
- let params = cx.update(WorkspaceParams::test);
- params.fs.as_fake().insert_dir("/root").await.unwrap();
- let (_, workspace) = cx.add_window(|cx| Workspace::new(¶ms, cx));
- workspace
- .update(&mut cx, |workspace, cx| {
- workspace.add_worktree(Path::new("/root"), cx)
- })
- .await
- .unwrap();
- let worktree = cx.read(|cx| {
- workspace
- .read(cx)
- .worktrees(cx)
- .iter()
- .next()
- .unwrap()
- .clone()
- });
-
- // Create a new untitled buffer
- let editor = workspace.update(&mut cx, |workspace, cx| {
- workspace.open_new_file(&OpenNew(params.clone()), cx);
- workspace
- .active_item(cx)
- .unwrap()
- .to_any()
- .downcast::<Editor>()
- .unwrap()
- });
-
- editor.update(&mut cx, |editor, cx| {
- assert!(!editor.is_dirty(cx.as_ref()));
- assert_eq!(editor.title(cx.as_ref()), "untitled");
- assert!(editor.language(cx).is_none());
- editor.handle_input(&Input("hi".into()), cx);
- assert!(editor.is_dirty(cx.as_ref()));
- });
-
- // Save the buffer. This prompts for a filename.
- workspace.update(&mut cx, |workspace, cx| {
- workspace.save_active_item(&Save, cx)
- });
- cx.simulate_new_path_selection(|parent_dir| {
- assert_eq!(parent_dir, Path::new("/root"));
- Some(parent_dir.join("the-new-name.rs"))
- });
- cx.read(|cx| {
- assert!(editor.is_dirty(cx));
- assert_eq!(editor.title(cx), "untitled");
- });
-
- // When the save completes, the buffer's title is updated.
- editor
- .condition(&cx, |editor, cx| !editor.is_dirty(cx))
- .await;
- cx.read(|cx| {
- assert!(!editor.is_dirty(cx));
- assert_eq!(editor.title(cx), "the-new-name.rs");
- });
- // The language is assigned based on the path
- editor.read_with(&cx, |editor, cx| {
- assert_eq!(editor.language(cx).unwrap().name(), "Rust")
- });
-
- // Edit the file and save it again. This time, there is no filename prompt.
- editor.update(&mut cx, |editor, cx| {
- editor.handle_input(&Input(" there".into()), cx);
- assert_eq!(editor.is_dirty(cx.as_ref()), true);
- });
- workspace.update(&mut cx, |workspace, cx| {
- workspace.save_active_item(&Save, cx)
- });
- assert!(!cx.did_prompt_for_new_path());
- editor
- .condition(&cx, |editor, cx| !editor.is_dirty(cx))
- .await;
- cx.read(|cx| assert_eq!(editor.title(cx), "the-new-name.rs"));
-
- // Open the same newly-created file in another pane item. The new editor should reuse
- // the same buffer.
- workspace.update(&mut cx, |workspace, cx| {
- workspace.open_new_file(&OpenNew(params.clone()), cx);
- workspace.split_pane(workspace.active_pane().clone(), SplitDirection::Right, cx);
- assert!(workspace
- .open_entry(
- ProjectPath {
- worktree_id: worktree.id(),
- path: Path::new("the-new-name.rs").into()
- },
- cx
- )
- .is_none());
- });
- let editor2 = workspace.update(&mut cx, |workspace, cx| {
- workspace
- .active_item(cx)
- .unwrap()
- .to_any()
- .downcast::<Editor>()
- .unwrap()
- });
- cx.read(|cx| {
- assert_eq!(editor2.read(cx).buffer(), editor.read(cx).buffer());
- })
- }
-
- #[gpui::test]
- async fn test_setting_language_when_saving_as_single_file_worktree(
- mut cx: gpui::TestAppContext,
- ) {
- let params = cx.update(WorkspaceParams::test);
- params.fs.as_fake().insert_dir("/root").await.unwrap();
- let (_, workspace) = cx.add_window(|cx| Workspace::new(¶ms, cx));
-
- // Create a new untitled buffer
- let editor = workspace.update(&mut cx, |workspace, cx| {
- workspace.open_new_file(&OpenNew(params.clone()), cx);
- workspace
- .active_item(cx)
- .unwrap()
- .to_any()
- .downcast::<Editor>()
- .unwrap()
- });
-
- editor.update(&mut cx, |editor, cx| {
- assert!(editor.language(cx).is_none());
- editor.handle_input(&Input("hi".into()), cx);
- assert!(editor.is_dirty(cx.as_ref()));
- });
-
- // Save the buffer. This prompts for a filename.
- workspace.update(&mut cx, |workspace, cx| {
- workspace.save_active_item(&Save, cx)
- });
- cx.simulate_new_path_selection(|_| Some(PathBuf::from("/root/the-new-name.rs")));
-
- editor
- .condition(&cx, |editor, cx| !editor.is_dirty(cx))
- .await;
-
- // The language is assigned based on the path
- editor.read_with(&cx, |editor, cx| {
- assert_eq!(editor.language(cx).unwrap().name(), "Rust")
- });
- }
-
- #[gpui::test]
- async fn test_pane_actions(mut cx: gpui::TestAppContext) {
- cx.update(|cx| pane::init(cx));
- let params = cx.update(WorkspaceParams::test);
- params
- .fs
- .as_fake()
- .insert_tree(
- "/root",
- json!({
- "a": {
- "file1": "contents 1",
- "file2": "contents 2",
- "file3": "contents 3",
- },
- }),
- )
- .await;
-
- let (window_id, workspace) = cx.add_window(|cx| Workspace::new(¶ms, cx));
- workspace
- .update(&mut cx, |workspace, cx| {
- workspace.add_worktree(Path::new("/root"), cx)
- })
- .await
- .unwrap();
- cx.read(|cx| workspace.read(cx).worktree_scans_complete(cx))
- .await;
- let entries = cx.read(|cx| workspace.file_project_paths(cx));
- let file1 = entries[0].clone();
-
- let pane_1 = cx.read(|cx| workspace.read(cx).active_pane().clone());
-
- workspace
- .update(&mut cx, |w, cx| w.open_entry(file1.clone(), cx))
- .unwrap()
- .await;
- cx.read(|cx| {
- assert_eq!(
- pane_1.read(cx).active_item().unwrap().project_path(cx),
- Some(file1.clone())
- );
- });
-
- cx.dispatch_action(
- window_id,
- vec![pane_1.id()],
- pane::Split(SplitDirection::Right),
- );
- cx.update(|cx| {
- let pane_2 = workspace.read(cx).active_pane().clone();
- assert_ne!(pane_1, pane_2);
-
- let pane2_item = pane_2.read(cx).active_item().unwrap();
- assert_eq!(pane2_item.project_path(cx.as_ref()), Some(file1.clone()));
-
- cx.dispatch_action(window_id, vec![pane_2.id()], &CloseActiveItem);
- let workspace = workspace.read(cx);
- assert_eq!(workspace.panes.len(), 1);
- assert_eq!(workspace.active_pane(), &pane_1);
- });
- }
-}
+// #[cfg(test)]
+// mod tests {
+// use super::*;
+// use editor::{Editor, Input};
+// use serde_json::json;
+// use std::collections::HashSet;
+
+// #[gpui::test]
+// async fn test_open_entry(mut cx: gpui::TestAppContext) {
+// let params = cx.update(WorkspaceParams::test);
+// params
+// .fs
+// .as_fake()
+// .insert_tree(
+// "/root",
+// json!({
+// "a": {
+// "file1": "contents 1",
+// "file2": "contents 2",
+// "file3": "contents 3",
+// },
+// }),
+// )
+// .await;
+
+// let (_, workspace) = cx.add_window(|cx| Workspace::new(¶ms, cx));
+// workspace
+// .update(&mut cx, |workspace, cx| {
+// workspace.add_worktree(Path::new("/root"), cx)
+// })
+// .await
+// .unwrap();
+
+// cx.read(|cx| workspace.read(cx).worktree_scans_complete(cx))
+// .await;
+// let entries = cx.read(|cx| workspace.file_project_paths(cx));
+// let file1 = entries[0].clone();
+// let file2 = entries[1].clone();
+// let file3 = entries[2].clone();
+
+// // Open the first entry
+// workspace
+// .update(&mut cx, |w, cx| w.open_entry(file1.clone(), cx))
+// .unwrap()
+// .await;
+// cx.read(|cx| {
+// let pane = workspace.read(cx).active_pane().read(cx);
+// assert_eq!(
+// pane.active_item().unwrap().project_path(cx),
+// Some(file1.clone())
+// );
+// assert_eq!(pane.items().len(), 1);
+// });
+
+// // Open the second entry
+// workspace
+// .update(&mut cx, |w, cx| w.open_entry(file2.clone(), cx))
+// .unwrap()
+// .await;
+// cx.read(|cx| {
+// let pane = workspace.read(cx).active_pane().read(cx);
+// assert_eq!(
+// pane.active_item().unwrap().project_path(cx),
+// Some(file2.clone())
+// );
+// assert_eq!(pane.items().len(), 2);
+// });
+
+// // Open the first entry again. The existing pane item is activated.
+// workspace.update(&mut cx, |w, cx| {
+// assert!(w.open_entry(file1.clone(), cx).is_none())
+// });
+// cx.read(|cx| {
+// let pane = workspace.read(cx).active_pane().read(cx);
+// assert_eq!(
+// pane.active_item().unwrap().project_path(cx),
+// Some(file1.clone())
+// );
+// assert_eq!(pane.items().len(), 2);
+// });
+
+// // Split the pane with the first entry, then open the second entry again.
+// workspace.update(&mut cx, |w, cx| {
+// w.split_pane(w.active_pane().clone(), SplitDirection::Right, cx);
+// assert!(w.open_entry(file2.clone(), cx).is_none());
+// assert_eq!(
+// w.active_pane()
+// .read(cx)
+// .active_item()
+// .unwrap()
+// .project_path(cx.as_ref()),
+// Some(file2.clone())
+// );
+// });
+
+// // Open the third entry twice concurrently. Only one pane item is added.
+// let (t1, t2) = workspace.update(&mut cx, |w, cx| {
+// (
+// w.open_entry(file3.clone(), cx).unwrap(),
+// w.open_entry(file3.clone(), cx).unwrap(),
+// )
+// });
+// t1.await;
+// t2.await;
+// cx.read(|cx| {
+// let pane = workspace.read(cx).active_pane().read(cx);
+// assert_eq!(
+// pane.active_item().unwrap().project_path(cx),
+// Some(file3.clone())
+// );
+// let pane_entries = pane
+// .items()
+// .iter()
+// .map(|i| i.project_path(cx).unwrap())
+// .collect::<Vec<_>>();
+// assert_eq!(pane_entries, &[file1, file2, file3]);
+// });
+// }
+
+// #[gpui::test]
+// async fn test_open_paths(mut cx: gpui::TestAppContext) {
+// let params = cx.update(WorkspaceParams::test);
+// let fs = params.fs.as_fake();
+// fs.insert_dir("/dir1").await.unwrap();
+// fs.insert_dir("/dir2").await.unwrap();
+// fs.insert_file("/dir1/a.txt", "".into()).await.unwrap();
+// fs.insert_file("/dir2/b.txt", "".into()).await.unwrap();
+
+// let (_, workspace) = cx.add_window(|cx| Workspace::new(¶ms, cx));
+// workspace
+// .update(&mut cx, |workspace, cx| {
+// workspace.add_worktree("/dir1".as_ref(), cx)
+// })
+// .await
+// .unwrap();
+// cx.read(|cx| workspace.read(cx).worktree_scans_complete(cx))
+// .await;
+
+// // Open a file within an existing worktree.
+// cx.update(|cx| {
+// workspace.update(cx, |view, cx| view.open_paths(&["/dir1/a.txt".into()], cx))
+// })
+// .await;
+// cx.read(|cx| {
+// assert_eq!(
+// workspace
+// .read(cx)
+// .active_pane()
+// .read(cx)
+// .active_item()
+// .unwrap()
+// .title(cx),
+// "a.txt"
+// );
+// });
+
+// // Open a file outside of any existing worktree.
+// cx.update(|cx| {
+// workspace.update(cx, |view, cx| view.open_paths(&["/dir2/b.txt".into()], cx))
+// })
+// .await;
+// cx.read(|cx| {
+// let worktree_roots = workspace
+// .read(cx)
+// .worktrees(cx)
+// .iter()
+// .map(|w| w.read(cx).as_local().unwrap().abs_path())
+// .collect::<HashSet<_>>();
+// assert_eq!(
+// worktree_roots,
+// vec!["/dir1", "/dir2/b.txt"]
+// .into_iter()
+// .map(Path::new)
+// .collect(),
+// );
+// assert_eq!(
+// workspace
+// .read(cx)
+// .active_pane()
+// .read(cx)
+// .active_item()
+// .unwrap()
+// .title(cx),
+// "b.txt"
+// );
+// });
+// }
+
+// #[gpui::test]
+// async fn test_save_conflicting_item(mut cx: gpui::TestAppContext) {
+// let params = cx.update(WorkspaceParams::test);
+// let fs = params.fs.as_fake();
+// fs.insert_tree("/root", json!({ "a.txt": "" })).await;
+
+// let (window_id, workspace) = cx.add_window(|cx| Workspace::new(¶ms, cx));
+// workspace
+// .update(&mut cx, |workspace, cx| {
+// workspace.add_worktree(Path::new("/root"), cx)
+// })
+// .await
+// .unwrap();
+
+// // Open a file within an existing worktree.
+// cx.update(|cx| {
+// workspace.update(cx, |view, cx| {
+// view.open_paths(&[PathBuf::from("/root/a.txt")], cx)
+// })
+// })
+// .await;
+// let editor = cx.read(|cx| {
+// let pane = workspace.read(cx).active_pane().read(cx);
+// let item = pane.active_item().unwrap();
+// item.to_any().downcast::<Editor>().unwrap()
+// });
+
+// cx.update(|cx| editor.update(cx, |editor, cx| editor.handle_input(&Input("x".into()), cx)));
+// fs.insert_file("/root/a.txt", "changed".to_string())
+// .await
+// .unwrap();
+// editor
+// .condition(&cx, |editor, cx| editor.has_conflict(cx))
+// .await;
+// cx.read(|cx| assert!(editor.is_dirty(cx)));
+
+// cx.update(|cx| workspace.update(cx, |w, cx| w.save_active_item(&Save, cx)));
+// cx.simulate_prompt_answer(window_id, 0);
+// editor
+// .condition(&cx, |editor, cx| !editor.is_dirty(cx))
+// .await;
+// cx.read(|cx| assert!(!editor.has_conflict(cx)));
+// }
+
+// #[gpui::test]
+// async fn test_open_and_save_new_file(mut cx: gpui::TestAppContext) {
+// let params = cx.update(WorkspaceParams::test);
+// params.fs.as_fake().insert_dir("/root").await.unwrap();
+// let (_, workspace) = cx.add_window(|cx| Workspace::new(¶ms, cx));
+// workspace
+// .update(&mut cx, |workspace, cx| {
+// workspace.add_worktree(Path::new("/root"), cx)
+// })
+// .await
+// .unwrap();
+// let worktree = cx.read(|cx| {
+// workspace
+// .read(cx)
+// .worktrees(cx)
+// .iter()
+// .next()
+// .unwrap()
+// .clone()
+// });
+
+// // Create a new untitled buffer
+// let editor = workspace.update(&mut cx, |workspace, cx| {
+// workspace.open_new_file(&OpenNew(params.clone()), cx);
+// workspace
+// .active_item(cx)
+// .unwrap()
+// .to_any()
+// .downcast::<Editor>()
+// .unwrap()
+// });
+
+// editor.update(&mut cx, |editor, cx| {
+// assert!(!editor.is_dirty(cx.as_ref()));
+// assert_eq!(editor.title(cx.as_ref()), "untitled");
+// assert!(editor.language(cx).is_none());
+// editor.handle_input(&Input("hi".into()), cx);
+// assert!(editor.is_dirty(cx.as_ref()));
+// });
+
+// // Save the buffer. This prompts for a filename.
+// workspace.update(&mut cx, |workspace, cx| {
+// workspace.save_active_item(&Save, cx)
+// });
+// cx.simulate_new_path_selection(|parent_dir| {
+// assert_eq!(parent_dir, Path::new("/root"));
+// Some(parent_dir.join("the-new-name.rs"))
+// });
+// cx.read(|cx| {
+// assert!(editor.is_dirty(cx));
+// assert_eq!(editor.title(cx), "untitled");
+// });
+
+// // When the save completes, the buffer's title is updated.
+// editor
+// .condition(&cx, |editor, cx| !editor.is_dirty(cx))
+// .await;
+// cx.read(|cx| {
+// assert!(!editor.is_dirty(cx));
+// assert_eq!(editor.title(cx), "the-new-name.rs");
+// });
+// // The language is assigned based on the path
+// editor.read_with(&cx, |editor, cx| {
+// assert_eq!(editor.language(cx).unwrap().name(), "Rust")
+// });
+
+// // Edit the file and save it again. This time, there is no filename prompt.
+// editor.update(&mut cx, |editor, cx| {
+// editor.handle_input(&Input(" there".into()), cx);
+// assert_eq!(editor.is_dirty(cx.as_ref()), true);
+// });
+// workspace.update(&mut cx, |workspace, cx| {
+// workspace.save_active_item(&Save, cx)
+// });
+// assert!(!cx.did_prompt_for_new_path());
+// editor
+// .condition(&cx, |editor, cx| !editor.is_dirty(cx))
+// .await;
+// cx.read(|cx| assert_eq!(editor.title(cx), "the-new-name.rs"));
+
+// // Open the same newly-created file in another pane item. The new editor should reuse
+// // the same buffer.
+// workspace.update(&mut cx, |workspace, cx| {
+// workspace.open_new_file(&OpenNew(params.clone()), cx);
+// workspace.split_pane(workspace.active_pane().clone(), SplitDirection::Right, cx);
+// assert!(workspace
+// .open_entry(
+// ProjectPath {
+// worktree_id: worktree.id(),
+// path: Path::new("the-new-name.rs").into()
+// },
+// cx
+// )
+// .is_none());
+// });
+// let editor2 = workspace.update(&mut cx, |workspace, cx| {
+// workspace
+// .active_item(cx)
+// .unwrap()
+// .to_any()
+// .downcast::<Editor>()
+// .unwrap()
+// });
+// cx.read(|cx| {
+// assert_eq!(editor2.read(cx).buffer(), editor.read(cx).buffer());
+// })
+// }
+
+// #[gpui::test]
+// async fn test_setting_language_when_saving_as_single_file_worktree(
+// mut cx: gpui::TestAppContext,
+// ) {
+// let params = cx.update(WorkspaceParams::test);
+// params.fs.as_fake().insert_dir("/root").await.unwrap();
+// let (_, workspace) = cx.add_window(|cx| Workspace::new(¶ms, cx));
+
+// // Create a new untitled buffer
+// let editor = workspace.update(&mut cx, |workspace, cx| {
+// workspace.open_new_file(&OpenNew(params.clone()), cx);
+// workspace
+// .active_item(cx)
+// .unwrap()
+// .to_any()
+// .downcast::<Editor>()
+// .unwrap()
+// });
+
+// editor.update(&mut cx, |editor, cx| {
+// assert!(editor.language(cx).is_none());
+// editor.handle_input(&Input("hi".into()), cx);
+// assert!(editor.is_dirty(cx.as_ref()));
+// });
+
+// // Save the buffer. This prompts for a filename.
+// workspace.update(&mut cx, |workspace, cx| {
+// workspace.save_active_item(&Save, cx)
+// });
+// cx.simulate_new_path_selection(|_| Some(PathBuf::from("/root/the-new-name.rs")));
+
+// editor
+// .condition(&cx, |editor, cx| !editor.is_dirty(cx))
+// .await;
+
+// // The language is assigned based on the path
+// editor.read_with(&cx, |editor, cx| {
+// assert_eq!(editor.language(cx).unwrap().name(), "Rust")
+// });
+// }
+
+// #[gpui::test]
+// async fn test_pane_actions(mut cx: gpui::TestAppContext) {
+// cx.update(|cx| pane::init(cx));
+// let params = cx.update(WorkspaceParams::test);
+// params
+// .fs
+// .as_fake()
+// .insert_tree(
+// "/root",
+// json!({
+// "a": {
+// "file1": "contents 1",
+// "file2": "contents 2",
+// "file3": "contents 3",
+// },
+// }),
+// )
+// .await;
+
+// let (window_id, workspace) = cx.add_window(|cx| Workspace::new(¶ms, cx));
+// workspace
+// .update(&mut cx, |workspace, cx| {
+// workspace.add_worktree(Path::new("/root"), cx)
+// })
+// .await
+// .unwrap();
+// cx.read(|cx| workspace.read(cx).worktree_scans_complete(cx))
+// .await;
+// let entries = cx.read(|cx| workspace.file_project_paths(cx));
+// let file1 = entries[0].clone();
+
+// let pane_1 = cx.read(|cx| workspace.read(cx).active_pane().clone());
+
+// workspace
+// .update(&mut cx, |w, cx| w.open_entry(file1.clone(), cx))
+// .unwrap()
+// .await;
+// cx.read(|cx| {
+// assert_eq!(
+// pane_1.read(cx).active_item().unwrap().project_path(cx),
+// Some(file1.clone())
+// );
+// });
+
+// cx.dispatch_action(
+// window_id,
+// vec![pane_1.id()],
+// pane::Split(SplitDirection::Right),
+// );
+// cx.update(|cx| {
+// let pane_2 = workspace.read(cx).active_pane().clone();
+// assert_ne!(pane_1, pane_2);
+
+// let pane2_item = pane_2.read(cx).active_item().unwrap();
+// assert_eq!(pane2_item.project_path(cx.as_ref()), Some(file1.clone()));
+
+// cx.dispatch_action(window_id, vec![pane_2.id()], &CloseActiveItem);
+// let workspace = workspace.read(cx);
+// assert_eq!(workspace.panes.len(), 1);
+// assert_eq!(workspace.active_pane(), &pane_1);
+// });
+// }
+// }