mirror of
https://github.com/zed-industries/zed.git
synced 2024-12-29 12:22:27 +03:00
Merge pull request #740 from zed-industries/prompt-only-on-last-dirty-item
Show unsaved/conflict prompt only when closing the last tab for an item
This commit is contained in:
commit
133d9f947b
@ -446,7 +446,7 @@ mod tests {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
cx.read(|cx| workspace.read(cx).worktree_scans_complete(cx))
|
cx.read(|cx| workspace.read(cx).worktree_scans_complete(cx))
|
||||||
.await;
|
.await;
|
||||||
cx.dispatch_action(window_id, vec![workspace.id()], Toggle);
|
cx.dispatch_action(window_id, Toggle);
|
||||||
|
|
||||||
let finder = cx.read(|cx| {
|
let finder = cx.read(|cx| {
|
||||||
workspace
|
workspace
|
||||||
@ -457,19 +457,16 @@ mod tests {
|
|||||||
.downcast::<FileFinder>()
|
.downcast::<FileFinder>()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
});
|
});
|
||||||
let query_buffer = cx.read(|cx| finder.read(cx).query_editor.clone());
|
cx.dispatch_action(window_id, Input("b".into()));
|
||||||
|
cx.dispatch_action(window_id, Input("n".into()));
|
||||||
let chain = vec![finder.id(), query_buffer.id()];
|
cx.dispatch_action(window_id, Input("a".into()));
|
||||||
cx.dispatch_action(window_id, chain.clone(), Input("b".into()));
|
|
||||||
cx.dispatch_action(window_id, chain.clone(), Input("n".into()));
|
|
||||||
cx.dispatch_action(window_id, chain.clone(), Input("a".into()));
|
|
||||||
finder
|
finder
|
||||||
.condition(&cx, |finder, _| finder.matches.len() == 2)
|
.condition(&cx, |finder, _| finder.matches.len() == 2)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
let active_pane = cx.read(|cx| workspace.read(cx).active_pane().clone());
|
let active_pane = cx.read(|cx| workspace.read(cx).active_pane().clone());
|
||||||
cx.dispatch_action(window_id, vec![workspace.id(), finder.id()], SelectNext);
|
cx.dispatch_action(window_id, SelectNext);
|
||||||
cx.dispatch_action(window_id, vec![workspace.id(), finder.id()], Confirm);
|
cx.dispatch_action(window_id, Confirm);
|
||||||
active_pane
|
active_pane
|
||||||
.condition(&cx, |pane, _| pane.active_item().is_some())
|
.condition(&cx, |pane, _| pane.active_item().is_some())
|
||||||
.await;
|
.await;
|
||||||
|
@ -426,15 +426,17 @@ impl TestAppContext {
|
|||||||
cx
|
cx
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn dispatch_action<A: Action>(
|
pub fn dispatch_action<A: Action>(&self, window_id: usize, action: A) {
|
||||||
&self,
|
let mut cx = self.cx.borrow_mut();
|
||||||
window_id: usize,
|
let dispatch_path = cx
|
||||||
responder_chain: Vec<usize>,
|
.presenters_and_platform_windows
|
||||||
action: A,
|
.get(&window_id)
|
||||||
) {
|
.unwrap()
|
||||||
self.cx
|
.0
|
||||||
.borrow_mut()
|
.borrow()
|
||||||
.dispatch_action_any(window_id, &responder_chain, &action);
|
.dispatch_path(cx.as_ref());
|
||||||
|
|
||||||
|
cx.dispatch_action_any(window_id, &dispatch_path, &action);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn dispatch_global_action<A: Action>(&self, action: A) {
|
pub fn dispatch_global_action<A: Action>(&self, action: A) {
|
||||||
@ -455,9 +457,9 @@ impl TestAppContext {
|
|||||||
.unwrap()
|
.unwrap()
|
||||||
.0
|
.0
|
||||||
.clone();
|
.clone();
|
||||||
let responder_chain = presenter.borrow().dispatch_path(cx.as_ref());
|
let dispatch_path = presenter.borrow().dispatch_path(cx.as_ref());
|
||||||
|
|
||||||
if !cx.dispatch_keystroke(window_id, responder_chain, &keystroke) {
|
if !cx.dispatch_keystroke(window_id, dispatch_path, &keystroke) {
|
||||||
presenter.borrow_mut().dispatch_event(
|
presenter.borrow_mut().dispatch_event(
|
||||||
Event::KeyDown {
|
Event::KeyDown {
|
||||||
keystroke,
|
keystroke,
|
||||||
@ -595,6 +597,15 @@ impl TestAppContext {
|
|||||||
pub fn leak_detector(&self) -> Arc<Mutex<LeakDetector>> {
|
pub fn leak_detector(&self) -> Arc<Mutex<LeakDetector>> {
|
||||||
self.cx.borrow().leak_detector()
|
self.cx.borrow().leak_detector()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(any(test, feature = "test-support"))]
|
||||||
|
pub fn assert_dropped(&self, handle: impl WeakHandle) {
|
||||||
|
self.cx
|
||||||
|
.borrow()
|
||||||
|
.leak_detector()
|
||||||
|
.lock()
|
||||||
|
.assert_dropped(handle.id())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AsyncAppContext {
|
impl AsyncAppContext {
|
||||||
@ -1314,10 +1325,10 @@ impl MutableAppContext {
|
|||||||
pub fn dispatch_action<A: Action>(
|
pub fn dispatch_action<A: Action>(
|
||||||
&mut self,
|
&mut self,
|
||||||
window_id: usize,
|
window_id: usize,
|
||||||
responder_chain: Vec<usize>,
|
dispatch_path: Vec<usize>,
|
||||||
action: &A,
|
action: &A,
|
||||||
) {
|
) {
|
||||||
self.dispatch_action_any(window_id, &responder_chain, action);
|
self.dispatch_action_any(window_id, &dispatch_path, action);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn dispatch_action_any(
|
pub(crate) fn dispatch_action_any(
|
||||||
@ -1403,11 +1414,11 @@ impl MutableAppContext {
|
|||||||
pub fn dispatch_keystroke(
|
pub fn dispatch_keystroke(
|
||||||
&mut self,
|
&mut self,
|
||||||
window_id: usize,
|
window_id: usize,
|
||||||
responder_chain: Vec<usize>,
|
dispatch_path: Vec<usize>,
|
||||||
keystroke: &Keystroke,
|
keystroke: &Keystroke,
|
||||||
) -> bool {
|
) -> bool {
|
||||||
let mut context_chain = Vec::new();
|
let mut context_chain = Vec::new();
|
||||||
for view_id in &responder_chain {
|
for view_id in &dispatch_path {
|
||||||
let view = self
|
let view = self
|
||||||
.cx
|
.cx
|
||||||
.views
|
.views
|
||||||
@ -1420,13 +1431,12 @@ impl MutableAppContext {
|
|||||||
for (i, cx) in context_chain.iter().enumerate().rev() {
|
for (i, cx) in context_chain.iter().enumerate().rev() {
|
||||||
match self
|
match self
|
||||||
.keystroke_matcher
|
.keystroke_matcher
|
||||||
.push_keystroke(keystroke.clone(), responder_chain[i], cx)
|
.push_keystroke(keystroke.clone(), dispatch_path[i], cx)
|
||||||
{
|
{
|
||||||
MatchResult::None => {}
|
MatchResult::None => {}
|
||||||
MatchResult::Pending => pending = true,
|
MatchResult::Pending => pending = true,
|
||||||
MatchResult::Action(action) => {
|
MatchResult::Action(action) => {
|
||||||
if self.dispatch_action_any(window_id, &responder_chain[0..=i], action.as_ref())
|
if self.dispatch_action_any(window_id, &dispatch_path[0..=i], action.as_ref()) {
|
||||||
{
|
|
||||||
self.keystroke_matcher.clear_pending();
|
self.keystroke_matcher.clear_pending();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -3301,6 +3311,10 @@ pub trait Handle<T> {
|
|||||||
Self: Sized;
|
Self: Sized;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub trait WeakHandle {
|
||||||
|
fn id(&self) -> usize;
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
|
#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
|
||||||
pub enum EntityLocation {
|
pub enum EntityLocation {
|
||||||
Model(usize),
|
Model(usize),
|
||||||
@ -3575,6 +3589,12 @@ pub struct WeakModelHandle<T> {
|
|||||||
model_type: PhantomData<T>,
|
model_type: PhantomData<T>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<T> WeakHandle for WeakModelHandle<T> {
|
||||||
|
fn id(&self) -> usize {
|
||||||
|
self.model_id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
unsafe impl<T> Send for WeakModelHandle<T> {}
|
unsafe impl<T> Send for WeakModelHandle<T> {}
|
||||||
unsafe impl<T> Sync for WeakModelHandle<T> {}
|
unsafe impl<T> Sync for WeakModelHandle<T> {}
|
||||||
|
|
||||||
@ -4144,6 +4164,12 @@ pub struct WeakViewHandle<T> {
|
|||||||
view_type: PhantomData<T>,
|
view_type: PhantomData<T>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<T> WeakHandle for WeakViewHandle<T> {
|
||||||
|
fn id(&self) -> usize {
|
||||||
|
self.view_id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl<T: View> WeakViewHandle<T> {
|
impl<T: View> WeakViewHandle<T> {
|
||||||
fn new(window_id: usize, view_id: usize) -> Self {
|
fn new(window_id: usize, view_id: usize) -> Self {
|
||||||
Self {
|
Self {
|
||||||
@ -4470,11 +4496,36 @@ impl LeakDetector {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn assert_dropped(&mut self, entity_id: usize) {
|
||||||
|
if let Some((type_name, backtraces)) = self.handle_backtraces.get_mut(&entity_id) {
|
||||||
|
for trace in backtraces.values_mut() {
|
||||||
|
if let Some(trace) = trace {
|
||||||
|
trace.resolve();
|
||||||
|
eprintln!("{:?}", crate::util::CwdBacktrace(trace));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let hint = if *LEAK_BACKTRACE {
|
||||||
|
""
|
||||||
|
} else {
|
||||||
|
" – set LEAK_BACKTRACE=1 for more information"
|
||||||
|
};
|
||||||
|
|
||||||
|
panic!(
|
||||||
|
"{} handles to {} {} still exist{}",
|
||||||
|
backtraces.len(),
|
||||||
|
type_name.unwrap_or("entity"),
|
||||||
|
entity_id,
|
||||||
|
hint
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn detect(&mut self) {
|
pub fn detect(&mut self) {
|
||||||
let mut found_leaks = false;
|
let mut found_leaks = false;
|
||||||
for (id, (type_name, backtraces)) in self.handle_backtraces.iter_mut() {
|
for (id, (type_name, backtraces)) in self.handle_backtraces.iter_mut() {
|
||||||
eprintln!(
|
eprintln!(
|
||||||
"leaked {} handles to {:?} {}",
|
"leaked {} handles to {} {}",
|
||||||
backtraces.len(),
|
backtraces.len(),
|
||||||
type_name.unwrap_or("entity"),
|
type_name.unwrap_or("entity"),
|
||||||
id
|
id
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
use super::{ItemHandle, SplitDirection};
|
use super::{ItemHandle, SplitDirection};
|
||||||
use crate::{toolbar::Toolbar, Item, Settings, WeakItemHandle, Workspace};
|
use crate::{toolbar::Toolbar, Item, Settings, WeakItemHandle, Workspace};
|
||||||
|
use anyhow::Result;
|
||||||
use collections::{HashMap, VecDeque};
|
use collections::{HashMap, VecDeque};
|
||||||
use futures::StreamExt;
|
use futures::StreamExt;
|
||||||
use gpui::{
|
use gpui::{
|
||||||
@ -8,10 +9,10 @@ use gpui::{
|
|||||||
geometry::{rect::RectF, vector::vec2f},
|
geometry::{rect::RectF, vector::vec2f},
|
||||||
keymap::Binding,
|
keymap::Binding,
|
||||||
platform::{CursorStyle, NavigationDirection},
|
platform::{CursorStyle, NavigationDirection},
|
||||||
AppContext, Entity, ModelHandle, MutableAppContext, PromptLevel, Quad, RenderContext, Task,
|
AppContext, Entity, MutableAppContext, PromptLevel, Quad, RenderContext, Task, View,
|
||||||
View, ViewContext, ViewHandle, WeakViewHandle,
|
ViewContext, ViewHandle, WeakViewHandle,
|
||||||
};
|
};
|
||||||
use project::{Project, ProjectEntryId, ProjectPath};
|
use project::{ProjectEntryId, ProjectPath};
|
||||||
use std::{any::Any, cell::RefCell, cmp, mem, path::Path, rc::Rc};
|
use std::{any::Any, cell::RefCell, cmp, mem, path::Path, rc::Rc};
|
||||||
use util::ResultExt;
|
use util::ResultExt;
|
||||||
|
|
||||||
@ -21,10 +22,16 @@ action!(ActivatePrevItem);
|
|||||||
action!(ActivateNextItem);
|
action!(ActivateNextItem);
|
||||||
action!(CloseActiveItem);
|
action!(CloseActiveItem);
|
||||||
action!(CloseInactiveItems);
|
action!(CloseInactiveItems);
|
||||||
action!(CloseItem, usize);
|
action!(CloseItem, CloseItemParams);
|
||||||
action!(GoBack, Option<WeakViewHandle<Pane>>);
|
action!(GoBack, Option<WeakViewHandle<Pane>>);
|
||||||
action!(GoForward, Option<WeakViewHandle<Pane>>);
|
action!(GoForward, Option<WeakViewHandle<Pane>>);
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct CloseItemParams {
|
||||||
|
pub item_id: usize,
|
||||||
|
pub pane: WeakViewHandle<Pane>,
|
||||||
|
}
|
||||||
|
|
||||||
const MAX_NAVIGATION_HISTORY_LEN: usize = 1024;
|
const MAX_NAVIGATION_HISTORY_LEN: usize = 1024;
|
||||||
|
|
||||||
pub fn init(cx: &mut MutableAppContext) {
|
pub fn init(cx: &mut MutableAppContext) {
|
||||||
@ -37,14 +44,11 @@ pub fn init(cx: &mut MutableAppContext) {
|
|||||||
cx.add_action(|pane: &mut Pane, _: &ActivateNextItem, cx| {
|
cx.add_action(|pane: &mut Pane, _: &ActivateNextItem, cx| {
|
||||||
pane.activate_next_item(cx);
|
pane.activate_next_item(cx);
|
||||||
});
|
});
|
||||||
cx.add_action(|pane: &mut Pane, _: &CloseActiveItem, cx| {
|
cx.add_async_action(Pane::close_active_item);
|
||||||
pane.close_active_item(cx).detach();
|
cx.add_async_action(Pane::close_inactive_items);
|
||||||
});
|
cx.add_async_action(|workspace: &mut Workspace, action: &CloseItem, cx| {
|
||||||
cx.add_action(|pane: &mut Pane, _: &CloseInactiveItems, cx| {
|
let pane = action.0.pane.upgrade(cx)?;
|
||||||
pane.close_inactive_items(cx).detach();
|
Some(Pane::close_item(workspace, pane, action.0.item_id, cx))
|
||||||
});
|
|
||||||
cx.add_action(|pane: &mut Pane, action: &CloseItem, cx| {
|
|
||||||
pane.close_item(action.0, cx).detach();
|
|
||||||
});
|
});
|
||||||
cx.add_action(|pane: &mut Pane, action: &Split, cx| {
|
cx.add_action(|pane: &mut Pane, action: &Split, cx| {
|
||||||
pane.split(action.0, cx);
|
pane.split(action.0, cx);
|
||||||
@ -98,7 +102,6 @@ pub struct Pane {
|
|||||||
active_item_index: usize,
|
active_item_index: usize,
|
||||||
nav_history: Rc<RefCell<NavHistory>>,
|
nav_history: Rc<RefCell<NavHistory>>,
|
||||||
toolbar: ViewHandle<Toolbar>,
|
toolbar: ViewHandle<Toolbar>,
|
||||||
project: ModelHandle<Project>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct ItemNavHistory {
|
pub struct ItemNavHistory {
|
||||||
@ -134,13 +137,12 @@ pub struct NavigationEntry {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Pane {
|
impl Pane {
|
||||||
pub fn new(project: ModelHandle<Project>, cx: &mut ViewContext<Self>) -> Self {
|
pub fn new(cx: &mut ViewContext<Self>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
items: Vec::new(),
|
items: Vec::new(),
|
||||||
active_item_index: 0,
|
active_item_index: 0,
|
||||||
nav_history: Default::default(),
|
nav_history: Default::default(),
|
||||||
toolbar: cx.add_view(|_| Toolbar::new()),
|
toolbar: cx.add_view(|_| Toolbar::new()),
|
||||||
project,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -410,47 +412,106 @@ impl Pane {
|
|||||||
self.activate_item(index, true, cx);
|
self.activate_item(index, true, cx);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn close_active_item(&mut self, cx: &mut ViewContext<Self>) -> Task<()> {
|
fn close_active_item(
|
||||||
if self.items.is_empty() {
|
workspace: &mut Workspace,
|
||||||
Task::ready(())
|
_: &CloseActiveItem,
|
||||||
|
cx: &mut ViewContext<Workspace>,
|
||||||
|
) -> Option<Task<Result<()>>> {
|
||||||
|
let pane_handle = workspace.active_pane().clone();
|
||||||
|
let pane = pane_handle.read(cx);
|
||||||
|
if pane.items.is_empty() {
|
||||||
|
None
|
||||||
} else {
|
} else {
|
||||||
self.close_item(self.items[self.active_item_index].id(), cx)
|
let item_id_to_close = pane.items[pane.active_item_index].id();
|
||||||
|
Some(Self::close_items(
|
||||||
|
workspace,
|
||||||
|
pane_handle,
|
||||||
|
cx,
|
||||||
|
move |item_id| item_id == item_id_to_close,
|
||||||
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn close_inactive_items(&mut self, cx: &mut ViewContext<Self>) -> Task<()> {
|
pub fn close_inactive_items(
|
||||||
if self.items.is_empty() {
|
workspace: &mut Workspace,
|
||||||
Task::ready(())
|
_: &CloseInactiveItems,
|
||||||
|
cx: &mut ViewContext<Workspace>,
|
||||||
|
) -> Option<Task<Result<()>>> {
|
||||||
|
let pane_handle = workspace.active_pane().clone();
|
||||||
|
let pane = pane_handle.read(cx);
|
||||||
|
if pane.items.is_empty() {
|
||||||
|
None
|
||||||
} else {
|
} else {
|
||||||
let active_item_id = self.items[self.active_item_index].id();
|
let active_item_id = pane.items[pane.active_item_index].id();
|
||||||
self.close_items(cx, move |id| id != active_item_id)
|
Some(Self::close_items(workspace, pane_handle, cx, move |id| {
|
||||||
|
id != active_item_id
|
||||||
|
}))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn close_item(&mut self, view_id_to_close: usize, cx: &mut ViewContext<Self>) -> Task<()> {
|
pub fn close_item(
|
||||||
self.close_items(cx, move |view_id| view_id == view_id_to_close)
|
workspace: &mut Workspace,
|
||||||
|
pane: ViewHandle<Pane>,
|
||||||
|
item_id_to_close: usize,
|
||||||
|
cx: &mut ViewContext<Workspace>,
|
||||||
|
) -> Task<Result<()>> {
|
||||||
|
Self::close_items(workspace, pane, cx, move |view_id| {
|
||||||
|
view_id == item_id_to_close
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn close_items(
|
pub fn close_items(
|
||||||
&mut self,
|
workspace: &mut Workspace,
|
||||||
cx: &mut ViewContext<Self>,
|
pane: ViewHandle<Pane>,
|
||||||
|
cx: &mut ViewContext<Workspace>,
|
||||||
should_close: impl 'static + Fn(usize) -> bool,
|
should_close: impl 'static + Fn(usize) -> bool,
|
||||||
) -> Task<()> {
|
) -> Task<Result<()>> {
|
||||||
const CONFLICT_MESSAGE: &'static str = "This file has changed on disk since you started editing it. Do you want to overwrite it?";
|
const CONFLICT_MESSAGE: &'static str = "This file has changed on disk since you started editing it. Do you want to overwrite it?";
|
||||||
const DIRTY_MESSAGE: &'static str =
|
const DIRTY_MESSAGE: &'static str =
|
||||||
"This file contains unsaved edits. Do you want to save it?";
|
"This file contains unsaved edits. Do you want to save it?";
|
||||||
|
|
||||||
let project = self.project.clone();
|
let project = workspace.project().clone();
|
||||||
cx.spawn(|this, mut cx| async move {
|
cx.spawn(|workspace, mut cx| async move {
|
||||||
while let Some(item_to_close_ix) = this.read_with(&cx, |this, _| {
|
while let Some(item_to_close_ix) = pane.read_with(&cx, |pane, _| {
|
||||||
this.items.iter().position(|item| should_close(item.id()))
|
pane.items.iter().position(|item| should_close(item.id()))
|
||||||
}) {
|
}) {
|
||||||
let item =
|
let item =
|
||||||
this.read_with(&cx, |this, _| this.items[item_to_close_ix].boxed_clone());
|
pane.read_with(&cx, |pane, _| pane.items[item_to_close_ix].boxed_clone());
|
||||||
if cx.read(|cx| item.is_dirty(cx)) {
|
|
||||||
|
let is_last_item_for_entry = workspace.read_with(&cx, |workspace, cx| {
|
||||||
|
let project_entry_id = item.project_entry_id(cx);
|
||||||
|
project_entry_id.is_none()
|
||||||
|
|| workspace
|
||||||
|
.items(cx)
|
||||||
|
.filter(|item| item.project_entry_id(cx) == project_entry_id)
|
||||||
|
.count()
|
||||||
|
== 1
|
||||||
|
});
|
||||||
|
|
||||||
|
if is_last_item_for_entry {
|
||||||
|
if cx.read(|cx| item.has_conflict(cx) && item.can_save(cx)) {
|
||||||
|
let mut answer = pane.update(&mut cx, |pane, cx| {
|
||||||
|
pane.activate_item(item_to_close_ix, true, cx);
|
||||||
|
cx.prompt(
|
||||||
|
PromptLevel::Warning,
|
||||||
|
CONFLICT_MESSAGE,
|
||||||
|
&["Overwrite", "Discard", "Cancel"],
|
||||||
|
)
|
||||||
|
});
|
||||||
|
|
||||||
|
match answer.next().await {
|
||||||
|
Some(0) => {
|
||||||
|
cx.update(|cx| item.save(project.clone(), cx)).await?;
|
||||||
|
}
|
||||||
|
Some(1) => {
|
||||||
|
cx.update(|cx| item.reload(project.clone(), cx)).await?;
|
||||||
|
}
|
||||||
|
_ => break,
|
||||||
|
}
|
||||||
|
} else if cx.read(|cx| item.is_dirty(cx)) {
|
||||||
if cx.read(|cx| item.can_save(cx)) {
|
if cx.read(|cx| item.can_save(cx)) {
|
||||||
let mut answer = this.update(&mut cx, |this, cx| {
|
let mut answer = pane.update(&mut cx, |pane, cx| {
|
||||||
this.activate_item(item_to_close_ix, true, cx);
|
pane.activate_item(item_to_close_ix, true, cx);
|
||||||
cx.prompt(
|
cx.prompt(
|
||||||
PromptLevel::Warning,
|
PromptLevel::Warning,
|
||||||
DIRTY_MESSAGE,
|
DIRTY_MESSAGE,
|
||||||
@ -460,21 +521,14 @@ impl Pane {
|
|||||||
|
|
||||||
match answer.next().await {
|
match answer.next().await {
|
||||||
Some(0) => {
|
Some(0) => {
|
||||||
if cx
|
cx.update(|cx| item.save(project.clone(), cx)).await?;
|
||||||
.update(|cx| item.save(project.clone(), cx))
|
|
||||||
.await
|
|
||||||
.log_err()
|
|
||||||
.is_none()
|
|
||||||
{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
Some(1) => {}
|
Some(1) => {}
|
||||||
_ => break,
|
_ => break,
|
||||||
}
|
}
|
||||||
} else if cx.read(|cx| item.can_save_as(cx)) {
|
} else if cx.read(|cx| item.can_save_as(cx)) {
|
||||||
let mut answer = this.update(&mut cx, |this, cx| {
|
let mut answer = pane.update(&mut cx, |pane, cx| {
|
||||||
this.activate_item(item_to_close_ix, true, cx);
|
pane.activate_item(item_to_close_ix, true, cx);
|
||||||
cx.prompt(
|
cx.prompt(
|
||||||
PromptLevel::Warning,
|
PromptLevel::Warning,
|
||||||
DIRTY_MESSAGE,
|
DIRTY_MESSAGE,
|
||||||
@ -487,21 +541,21 @@ impl Pane {
|
|||||||
let start_abs_path = project
|
let start_abs_path = project
|
||||||
.read_with(&cx, |project, cx| {
|
.read_with(&cx, |project, cx| {
|
||||||
let worktree = project.visible_worktrees(cx).next()?;
|
let worktree = project.visible_worktrees(cx).next()?;
|
||||||
Some(worktree.read(cx).as_local()?.abs_path().to_path_buf())
|
Some(
|
||||||
|
worktree
|
||||||
|
.read(cx)
|
||||||
|
.as_local()?
|
||||||
|
.abs_path()
|
||||||
|
.to_path_buf(),
|
||||||
|
)
|
||||||
})
|
})
|
||||||
.unwrap_or(Path::new("").into());
|
.unwrap_or(Path::new("").into());
|
||||||
|
|
||||||
let mut abs_path =
|
let mut abs_path =
|
||||||
cx.update(|cx| cx.prompt_for_new_path(&start_abs_path));
|
cx.update(|cx| cx.prompt_for_new_path(&start_abs_path));
|
||||||
if let Some(abs_path) = abs_path.next().await.flatten() {
|
if let Some(abs_path) = abs_path.next().await.flatten() {
|
||||||
if cx
|
cx.update(|cx| item.save_as(project.clone(), abs_path, cx))
|
||||||
.update(|cx| item.save_as(project.clone(), abs_path, cx))
|
.await?;
|
||||||
.await
|
|
||||||
.log_err()
|
|
||||||
.is_none()
|
|
||||||
{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -510,62 +564,31 @@ impl Pane {
|
|||||||
_ => break,
|
_ => break,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if cx.read(|cx| item.has_conflict(cx) && item.can_save(cx)) {
|
|
||||||
let mut answer = this.update(&mut cx, |this, cx| {
|
|
||||||
this.activate_item(item_to_close_ix, true, cx);
|
|
||||||
cx.prompt(
|
|
||||||
PromptLevel::Warning,
|
|
||||||
CONFLICT_MESSAGE,
|
|
||||||
&["Overwrite", "Discard", "Cancel"],
|
|
||||||
)
|
|
||||||
});
|
|
||||||
|
|
||||||
match answer.next().await {
|
|
||||||
Some(0) => {
|
|
||||||
if cx
|
|
||||||
.update(|cx| item.save(project.clone(), cx))
|
|
||||||
.await
|
|
||||||
.log_err()
|
|
||||||
.is_none()
|
|
||||||
{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Some(1) => {
|
|
||||||
if cx
|
|
||||||
.update(|cx| item.reload(project.clone(), cx))
|
|
||||||
.await
|
|
||||||
.log_err()
|
|
||||||
.is_none()
|
|
||||||
{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => break,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.update(&mut cx, |this, cx| {
|
pane.update(&mut cx, |pane, cx| {
|
||||||
if let Some(item_ix) = this.items.iter().position(|i| i.id() == item.id()) {
|
if let Some(item_ix) = pane.items.iter().position(|i| i.id() == item.id()) {
|
||||||
if item_ix == this.active_item_index {
|
if item_ix == pane.active_item_index {
|
||||||
if item_ix + 1 < this.items.len() {
|
if item_ix + 1 < pane.items.len() {
|
||||||
this.activate_next_item(cx);
|
pane.activate_next_item(cx);
|
||||||
} else if item_ix > 0 {
|
} else if item_ix > 0 {
|
||||||
this.activate_prev_item(cx);
|
pane.activate_prev_item(cx);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let item = this.items.remove(item_ix);
|
let item = pane.items.remove(item_ix);
|
||||||
if this.items.is_empty() {
|
if pane.items.is_empty() {
|
||||||
item.deactivated(cx);
|
item.deactivated(cx);
|
||||||
|
pane.update_toolbar(cx);
|
||||||
cx.emit(Event::Remove);
|
cx.emit(Event::Remove);
|
||||||
}
|
}
|
||||||
|
|
||||||
if item_ix < this.active_item_index {
|
if item_ix < pane.active_item_index {
|
||||||
this.active_item_index -= 1;
|
pane.active_item_index -= 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut nav_history = this.nav_history.borrow_mut();
|
let mut nav_history = pane.nav_history.borrow_mut();
|
||||||
if let Some(path) = item.project_path(cx) {
|
if let Some(path) = item.project_path(cx) {
|
||||||
nav_history.paths_by_item.insert(item.id(), path);
|
nav_history.paths_by_item.insert(item.id(), path);
|
||||||
} else {
|
} else {
|
||||||
@ -575,7 +598,8 @@ impl Pane {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
this.update(&mut cx, |_, cx| cx.notify());
|
pane.update(&mut cx, |_, cx| cx.notify());
|
||||||
|
Ok(())
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -607,6 +631,7 @@ impl Pane {
|
|||||||
let theme = cx.global::<Settings>().theme.clone();
|
let theme = cx.global::<Settings>().theme.clone();
|
||||||
|
|
||||||
enum Tabs {}
|
enum Tabs {}
|
||||||
|
let pane = cx.handle();
|
||||||
let tabs = MouseEventHandler::new::<Tabs, _, _>(0, cx, |mouse_state, cx| {
|
let tabs = MouseEventHandler::new::<Tabs, _, _>(0, cx, |mouse_state, cx| {
|
||||||
let mut row = Flex::row();
|
let mut row = Flex::row();
|
||||||
for (ix, item) in self.items.iter().enumerate() {
|
for (ix, item) in self.items.iter().enumerate() {
|
||||||
@ -698,8 +723,14 @@ impl Pane {
|
|||||||
)
|
)
|
||||||
.with_padding(Padding::uniform(4.))
|
.with_padding(Padding::uniform(4.))
|
||||||
.with_cursor_style(CursorStyle::PointingHand)
|
.with_cursor_style(CursorStyle::PointingHand)
|
||||||
.on_click(move |cx| {
|
.on_click({
|
||||||
cx.dispatch_action(CloseItem(item_id))
|
let pane = pane.clone();
|
||||||
|
move |cx| {
|
||||||
|
cx.dispatch_action(CloseItem(CloseItemParams {
|
||||||
|
item_id,
|
||||||
|
pane: pane.clone(),
|
||||||
|
}))
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.named("close-tab-icon")
|
.named("close-tab-icon")
|
||||||
} else {
|
} else {
|
||||||
@ -861,10 +892,11 @@ impl NavHistory {
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use crate::WorkspaceParams;
|
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
use gpui::TestAppContext;
|
use crate::WorkspaceParams;
|
||||||
|
use gpui::{ModelHandle, TestAppContext, ViewContext};
|
||||||
|
use project::Project;
|
||||||
|
use std::sync::atomic::AtomicUsize;
|
||||||
|
|
||||||
#[gpui::test]
|
#[gpui::test]
|
||||||
async fn test_close_items(cx: &mut TestAppContext) {
|
async fn test_close_items(cx: &mut TestAppContext) {
|
||||||
@ -874,7 +906,7 @@ mod tests {
|
|||||||
let (window_id, workspace) = cx.add_window(|cx| Workspace::new(¶ms, cx));
|
let (window_id, workspace) = cx.add_window(|cx| Workspace::new(¶ms, cx));
|
||||||
let item1 = cx.add_view(window_id, |_| {
|
let item1 = cx.add_view(window_id, |_| {
|
||||||
let mut item = TestItem::new();
|
let mut item = TestItem::new();
|
||||||
item.has_conflict = true;
|
item.is_dirty = true;
|
||||||
item
|
item
|
||||||
});
|
});
|
||||||
let item2 = cx.add_view(window_id, |_| {
|
let item2 = cx.add_view(window_id, |_| {
|
||||||
@ -885,15 +917,11 @@ mod tests {
|
|||||||
});
|
});
|
||||||
let item3 = cx.add_view(window_id, |_| {
|
let item3 = cx.add_view(window_id, |_| {
|
||||||
let mut item = TestItem::new();
|
let mut item = TestItem::new();
|
||||||
|
item.is_dirty = true;
|
||||||
item.has_conflict = true;
|
item.has_conflict = true;
|
||||||
item
|
item
|
||||||
});
|
});
|
||||||
let item4 = cx.add_view(window_id, |_| {
|
let item4 = cx.add_view(window_id, |_| {
|
||||||
let mut item = TestItem::new();
|
|
||||||
item.is_dirty = true;
|
|
||||||
item
|
|
||||||
});
|
|
||||||
let item5 = cx.add_view(window_id, |_| {
|
|
||||||
let mut item = TestItem::new();
|
let mut item = TestItem::new();
|
||||||
item.is_dirty = true;
|
item.is_dirty = true;
|
||||||
item.can_save = false;
|
item.can_save = false;
|
||||||
@ -904,26 +932,26 @@ mod tests {
|
|||||||
workspace.add_item(Box::new(item2.clone()), cx);
|
workspace.add_item(Box::new(item2.clone()), cx);
|
||||||
workspace.add_item(Box::new(item3.clone()), cx);
|
workspace.add_item(Box::new(item3.clone()), cx);
|
||||||
workspace.add_item(Box::new(item4.clone()), cx);
|
workspace.add_item(Box::new(item4.clone()), cx);
|
||||||
workspace.add_item(Box::new(item5.clone()), cx);
|
|
||||||
workspace.active_pane().clone()
|
workspace.active_pane().clone()
|
||||||
});
|
});
|
||||||
|
|
||||||
let close_items = pane.update(cx, |pane, cx| {
|
let close_items = workspace.update(cx, |workspace, cx| {
|
||||||
|
pane.update(cx, |pane, cx| {
|
||||||
pane.activate_item(1, true, cx);
|
pane.activate_item(1, true, cx);
|
||||||
assert_eq!(pane.active_item().unwrap().id(), item2.id());
|
assert_eq!(pane.active_item().unwrap().id(), item2.id());
|
||||||
|
});
|
||||||
|
|
||||||
let item1_id = item1.id();
|
let item1_id = item1.id();
|
||||||
let item3_id = item3.id();
|
let item3_id = item3.id();
|
||||||
let item4_id = item4.id();
|
let item4_id = item4.id();
|
||||||
let item5_id = item5.id();
|
Pane::close_items(workspace, pane.clone(), cx, move |id| {
|
||||||
pane.close_items(cx, move |id| {
|
[item1_id, item3_id, item4_id].contains(&id)
|
||||||
[item1_id, item3_id, item4_id, item5_id].contains(&id)
|
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
cx.foreground().run_until_parked();
|
cx.foreground().run_until_parked();
|
||||||
pane.read_with(cx, |pane, _| {
|
pane.read_with(cx, |pane, _| {
|
||||||
assert_eq!(pane.items.len(), 5);
|
assert_eq!(pane.items.len(), 4);
|
||||||
assert_eq!(pane.active_item().unwrap().id(), item1.id());
|
assert_eq!(pane.active_item().unwrap().id(), item1.id());
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -933,7 +961,7 @@ mod tests {
|
|||||||
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);
|
||||||
assert_eq!(item1.read(cx).reload_count, 0);
|
assert_eq!(item1.read(cx).reload_count, 0);
|
||||||
assert_eq!(pane.items.len(), 4);
|
assert_eq!(pane.items.len(), 3);
|
||||||
assert_eq!(pane.active_item().unwrap().id(), item3.id());
|
assert_eq!(pane.active_item().unwrap().id(), item3.id());
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -943,33 +971,67 @@ mod tests {
|
|||||||
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);
|
||||||
assert_eq!(item3.read(cx).reload_count, 1);
|
assert_eq!(item3.read(cx).reload_count, 1);
|
||||||
assert_eq!(pane.items.len(), 3);
|
assert_eq!(pane.items.len(), 2);
|
||||||
assert_eq!(pane.active_item().unwrap().id(), item4.id());
|
assert_eq!(pane.active_item().unwrap().id(), item4.id());
|
||||||
});
|
});
|
||||||
|
|
||||||
cx.simulate_prompt_answer(window_id, 0);
|
|
||||||
cx.foreground().run_until_parked();
|
|
||||||
pane.read_with(cx, |pane, cx| {
|
|
||||||
assert_eq!(item4.read(cx).save_count, 1);
|
|
||||||
assert_eq!(item4.read(cx).save_as_count, 0);
|
|
||||||
assert_eq!(item4.read(cx).reload_count, 0);
|
|
||||||
assert_eq!(pane.items.len(), 2);
|
|
||||||
assert_eq!(pane.active_item().unwrap().id(), item5.id());
|
|
||||||
});
|
|
||||||
|
|
||||||
cx.simulate_prompt_answer(window_id, 0);
|
cx.simulate_prompt_answer(window_id, 0);
|
||||||
cx.foreground().run_until_parked();
|
cx.foreground().run_until_parked();
|
||||||
cx.simulate_new_path_selection(|_| Some(Default::default()));
|
cx.simulate_new_path_selection(|_| Some(Default::default()));
|
||||||
close_items.await;
|
close_items.await.unwrap();
|
||||||
pane.read_with(cx, |pane, cx| {
|
pane.read_with(cx, |pane, cx| {
|
||||||
assert_eq!(item5.read(cx).save_count, 0);
|
assert_eq!(item4.read(cx).save_count, 0);
|
||||||
assert_eq!(item5.read(cx).save_as_count, 1);
|
assert_eq!(item4.read(cx).save_as_count, 1);
|
||||||
assert_eq!(item5.read(cx).reload_count, 0);
|
assert_eq!(item4.read(cx).reload_count, 0);
|
||||||
assert_eq!(pane.items.len(), 1);
|
assert_eq!(pane.items.len(), 1);
|
||||||
assert_eq!(pane.active_item().unwrap().id(), item2.id());
|
assert_eq!(pane.active_item().unwrap().id(), item2.id());
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[gpui::test]
|
||||||
|
async fn test_prompting_only_on_last_item_for_entry(cx: &mut TestAppContext) {
|
||||||
|
cx.foreground().forbid_parking();
|
||||||
|
|
||||||
|
let params = cx.update(WorkspaceParams::test);
|
||||||
|
let (window_id, workspace) = cx.add_window(|cx| Workspace::new(¶ms, cx));
|
||||||
|
let item = cx.add_view(window_id, |_| {
|
||||||
|
let mut item = TestItem::new();
|
||||||
|
item.is_dirty = true;
|
||||||
|
item.project_entry_id = Some(ProjectEntryId::new(&AtomicUsize::new(1)));
|
||||||
|
item
|
||||||
|
});
|
||||||
|
|
||||||
|
let (left_pane, right_pane) = workspace.update(cx, |workspace, cx| {
|
||||||
|
workspace.add_item(Box::new(item.clone()), cx);
|
||||||
|
let left_pane = workspace.active_pane().clone();
|
||||||
|
let right_pane = workspace.split_pane(left_pane.clone(), SplitDirection::Right, cx);
|
||||||
|
(left_pane, right_pane)
|
||||||
|
});
|
||||||
|
|
||||||
|
workspace
|
||||||
|
.update(cx, |workspace, cx| {
|
||||||
|
let item = right_pane.read(cx).active_item().unwrap();
|
||||||
|
Pane::close_item(workspace, right_pane.clone(), item.id(), cx)
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
workspace.read_with(cx, |workspace, _| {
|
||||||
|
assert_eq!(workspace.panes(), [left_pane.clone()]);
|
||||||
|
});
|
||||||
|
|
||||||
|
let close_item = workspace.update(cx, |workspace, cx| {
|
||||||
|
let item = left_pane.read(cx).active_item().unwrap();
|
||||||
|
Pane::close_item(workspace, left_pane.clone(), item.id(), cx)
|
||||||
|
});
|
||||||
|
cx.foreground().run_until_parked();
|
||||||
|
cx.simulate_prompt_answer(window_id, 0);
|
||||||
|
close_item.await.unwrap();
|
||||||
|
left_pane.read_with(cx, |pane, _| {
|
||||||
|
assert_eq!(pane.items.len(), 0);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
struct TestItem {
|
struct TestItem {
|
||||||
save_count: usize,
|
save_count: usize,
|
||||||
save_as_count: usize,
|
save_as_count: usize,
|
||||||
@ -977,6 +1039,7 @@ mod tests {
|
|||||||
is_dirty: bool,
|
is_dirty: bool,
|
||||||
has_conflict: bool,
|
has_conflict: bool,
|
||||||
can_save: bool,
|
can_save: bool,
|
||||||
|
project_entry_id: Option<ProjectEntryId>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TestItem {
|
impl TestItem {
|
||||||
@ -988,6 +1051,7 @@ mod tests {
|
|||||||
is_dirty: false,
|
is_dirty: false,
|
||||||
has_conflict: false,
|
has_conflict: false,
|
||||||
can_save: true,
|
can_save: true,
|
||||||
|
project_entry_id: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1016,11 +1080,18 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn project_entry_id(&self, _: &AppContext) -> Option<ProjectEntryId> {
|
fn project_entry_id(&self, _: &AppContext) -> Option<ProjectEntryId> {
|
||||||
None
|
self.project_entry_id
|
||||||
}
|
}
|
||||||
|
|
||||||
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, _: &mut ViewContext<Self>) -> Option<Self>
|
||||||
|
where
|
||||||
|
Self: Sized,
|
||||||
|
{
|
||||||
|
Some(self.clone())
|
||||||
|
}
|
||||||
|
|
||||||
fn is_dirty(&self, _: &AppContext) -> bool {
|
fn is_dirty(&self, _: &AppContext) -> bool {
|
||||||
self.is_dirty
|
self.is_dirty
|
||||||
}
|
}
|
||||||
|
@ -497,8 +497,7 @@ impl<T: Item> ItemHandle for ViewHandle<T> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if T::should_close_item_on_event(event) {
|
if T::should_close_item_on_event(event) {
|
||||||
pane.update(cx, |pane, cx| pane.close_item(item.id(), cx))
|
Pane::close_item(workspace, pane, item.id(), cx).detach_and_log_err(cx);
|
||||||
.detach();
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -738,7 +737,7 @@ impl Workspace {
|
|||||||
})
|
})
|
||||||
.detach();
|
.detach();
|
||||||
|
|
||||||
let pane = cx.add_view(|cx| Pane::new(params.project.clone(), cx));
|
let pane = cx.add_view(|cx| Pane::new(cx));
|
||||||
let pane_id = pane.id();
|
let pane_id = pane.id();
|
||||||
cx.observe(&pane, move |me, _, cx| {
|
cx.observe(&pane, move |me, _, cx| {
|
||||||
let active_entry = me.active_project_path(cx);
|
let active_entry = me.active_project_path(cx);
|
||||||
@ -1070,7 +1069,7 @@ impl Workspace {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn add_pane(&mut self, cx: &mut ViewContext<Self>) -> ViewHandle<Pane> {
|
fn add_pane(&mut self, cx: &mut ViewContext<Self>) -> ViewHandle<Pane> {
|
||||||
let pane = cx.add_view(|cx| Pane::new(self.project.clone(), cx));
|
let pane = cx.add_view(|cx| Pane::new(cx));
|
||||||
let pane_id = pane.id();
|
let pane_id = pane.id();
|
||||||
cx.observe(&pane, move |me, _, cx| {
|
cx.observe(&pane, move |me, _, cx| {
|
||||||
let active_entry = me.active_project_path(cx);
|
let active_entry = me.active_project_path(cx);
|
||||||
|
@ -563,7 +563,7 @@ mod tests {
|
|||||||
let worktree = cx.read(|cx| workspace.read(cx).worktrees(cx).next().unwrap());
|
let worktree = cx.read(|cx| workspace.read(cx).worktrees(cx).next().unwrap());
|
||||||
|
|
||||||
// Create a new untitled buffer
|
// Create a new untitled buffer
|
||||||
cx.dispatch_action(window_id, vec![workspace.id()], OpenNew(app_state.clone()));
|
cx.dispatch_action(window_id, OpenNew(app_state.clone()));
|
||||||
let editor = workspace.read_with(cx, |workspace, cx| {
|
let editor = workspace.read_with(cx, |workspace, cx| {
|
||||||
workspace
|
workspace
|
||||||
.active_item(cx)
|
.active_item(cx)
|
||||||
@ -618,7 +618,7 @@ mod tests {
|
|||||||
|
|
||||||
// Open the same newly-created file in another pane item. The new editor should reuse
|
// Open the same newly-created file in another pane item. The new editor should reuse
|
||||||
// the same buffer.
|
// the same buffer.
|
||||||
cx.dispatch_action(window_id, vec![workspace.id()], OpenNew(app_state.clone()));
|
cx.dispatch_action(window_id, OpenNew(app_state.clone()));
|
||||||
workspace
|
workspace
|
||||||
.update(cx, |workspace, cx| {
|
.update(cx, |workspace, cx| {
|
||||||
workspace.split_pane(workspace.active_pane().clone(), SplitDirection::Right, cx);
|
workspace.split_pane(workspace.active_pane().clone(), SplitDirection::Right, cx);
|
||||||
@ -655,7 +655,7 @@ mod tests {
|
|||||||
let (window_id, workspace) = cx.add_window(|cx| Workspace::new(¶ms, cx));
|
let (window_id, workspace) = cx.add_window(|cx| Workspace::new(¶ms, cx));
|
||||||
|
|
||||||
// Create a new untitled buffer
|
// Create a new untitled buffer
|
||||||
cx.dispatch_action(window_id, vec![workspace.id()], OpenNew(app_state.clone()));
|
cx.dispatch_action(window_id, OpenNew(app_state.clone()));
|
||||||
let editor = workspace.read_with(cx, |workspace, cx| {
|
let editor = workspace.read_with(cx, |workspace, cx| {
|
||||||
workspace
|
workspace
|
||||||
.active_item(cx)
|
.active_item(cx)
|
||||||
@ -725,32 +725,47 @@ mod tests {
|
|||||||
.update(cx, |w, cx| w.open_path(file1.clone(), cx))
|
.update(cx, |w, cx| w.open_path(file1.clone(), cx))
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
cx.read(|cx| {
|
|
||||||
assert_eq!(
|
let (editor_1, buffer) = pane_1.update(cx, |pane_1, cx| {
|
||||||
pane_1.read(cx).active_item().unwrap().project_path(cx),
|
let editor = pane_1.active_item().unwrap().downcast::<Editor>().unwrap();
|
||||||
Some(file1.clone())
|
assert_eq!(editor.project_path(cx), Some(file1.clone()));
|
||||||
);
|
let buffer = editor.update(cx, |editor, cx| {
|
||||||
|
editor.insert("dirt", cx);
|
||||||
|
editor.buffer().downgrade()
|
||||||
|
});
|
||||||
|
(editor.downgrade(), buffer)
|
||||||
});
|
});
|
||||||
|
|
||||||
cx.dispatch_action(
|
cx.dispatch_action(window_id, pane::Split(SplitDirection::Right));
|
||||||
window_id,
|
let editor_2 = cx.update(|cx| {
|
||||||
vec![pane_1.id()],
|
|
||||||
pane::Split(SplitDirection::Right),
|
|
||||||
);
|
|
||||||
cx.update(|cx| {
|
|
||||||
let pane_2 = workspace.read(cx).active_pane().clone();
|
let pane_2 = workspace.read(cx).active_pane().clone();
|
||||||
assert_ne!(pane_1, pane_2);
|
assert_ne!(pane_1, pane_2);
|
||||||
|
|
||||||
let pane2_item = pane_2.read(cx).active_item().unwrap();
|
let pane2_item = pane_2.read(cx).active_item().unwrap();
|
||||||
assert_eq!(pane2_item.project_path(cx.as_ref()), Some(file1.clone()));
|
assert_eq!(pane2_item.project_path(cx.as_ref()), Some(file1.clone()));
|
||||||
|
|
||||||
cx.dispatch_action(window_id, vec![pane_2.id()], &workspace::CloseActiveItem);
|
pane2_item.downcast::<Editor>().unwrap().downgrade()
|
||||||
});
|
});
|
||||||
|
cx.dispatch_action(window_id, workspace::CloseActiveItem);
|
||||||
|
|
||||||
cx.foreground().run_until_parked();
|
cx.foreground().run_until_parked();
|
||||||
workspace.read_with(cx, |workspace, _| {
|
workspace.read_with(cx, |workspace, _| {
|
||||||
assert_eq!(workspace.panes().len(), 1);
|
assert_eq!(workspace.panes().len(), 1);
|
||||||
assert_eq!(workspace.active_pane(), &pane_1);
|
assert_eq!(workspace.active_pane(), &pane_1);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
cx.dispatch_action(window_id, workspace::CloseActiveItem);
|
||||||
|
cx.foreground().run_until_parked();
|
||||||
|
cx.simulate_prompt_answer(window_id, 1);
|
||||||
|
cx.foreground().run_until_parked();
|
||||||
|
|
||||||
|
workspace.read_with(cx, |workspace, cx| {
|
||||||
|
assert!(workspace.active_item(cx).is_none());
|
||||||
|
});
|
||||||
|
|
||||||
|
cx.assert_dropped(editor_1);
|
||||||
|
cx.assert_dropped(editor_2);
|
||||||
|
cx.assert_dropped(buffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[gpui::test]
|
#[gpui::test]
|
||||||
@ -878,11 +893,10 @@ mod tests {
|
|||||||
.update(cx, |workspace, cx| {
|
.update(cx, |workspace, cx| {
|
||||||
let editor3_id = editor3.id();
|
let editor3_id = editor3.id();
|
||||||
drop(editor3);
|
drop(editor3);
|
||||||
workspace
|
Pane::close_item(workspace, workspace.active_pane().clone(), editor3_id, cx)
|
||||||
.active_pane()
|
|
||||||
.update(cx, |pane, cx| pane.close_item(editor3_id, cx))
|
|
||||||
})
|
})
|
||||||
.await;
|
.await
|
||||||
|
.unwrap();
|
||||||
workspace
|
workspace
|
||||||
.update(cx, |w, cx| Pane::go_forward(w, None, cx))
|
.update(cx, |w, cx| Pane::go_forward(w, None, cx))
|
||||||
.await;
|
.await;
|
||||||
@ -896,11 +910,10 @@ mod tests {
|
|||||||
.update(cx, |workspace, cx| {
|
.update(cx, |workspace, cx| {
|
||||||
let editor2_id = editor2.id();
|
let editor2_id = editor2.id();
|
||||||
drop(editor2);
|
drop(editor2);
|
||||||
workspace
|
Pane::close_item(workspace, workspace.active_pane().clone(), editor2_id, cx)
|
||||||
.active_pane()
|
|
||||||
.update(cx, |pane, cx| pane.close_item(editor2_id, cx))
|
|
||||||
})
|
})
|
||||||
.await;
|
.await
|
||||||
|
.unwrap();
|
||||||
app_state
|
app_state
|
||||||
.fs
|
.fs
|
||||||
.as_fake()
|
.as_fake()
|
||||||
|
Loading…
Reference in New Issue
Block a user