Checkpoint
This commit is contained in:
parent
72156bf502
commit
32dded551c
6 changed files with 325 additions and 325 deletions
|
@ -12,8 +12,8 @@ use client2::{
|
|||
Client,
|
||||
};
|
||||
use gpui2::{
|
||||
AnyElement, AnyView, AppContext, EventEmitter, HighlightStyle, Model, Pixels, Point, Render,
|
||||
SharedString, Task, View, ViewContext, WeakView, WindowContext, WindowHandle,
|
||||
AnyElement, AnyView, AppContext, Entity, EntityId, EventEmitter, HighlightStyle, Model, Pixels,
|
||||
Point, Render, SharedString, Task, View, ViewContext, WeakView, WindowContext, WindowHandle,
|
||||
};
|
||||
use parking_lot::Mutex;
|
||||
use project2::{Project, ProjectEntryId, ProjectPath};
|
||||
|
@ -21,7 +21,6 @@ use schemars::JsonSchema;
|
|||
use serde::{Deserialize, Serialize};
|
||||
use settings2::Settings;
|
||||
use smallvec::SmallVec;
|
||||
use theme2::ThemeVariant;
|
||||
use std::{
|
||||
any::{Any, TypeId},
|
||||
ops::Range,
|
||||
|
@ -32,6 +31,7 @@ use std::{
|
|||
},
|
||||
time::Duration,
|
||||
};
|
||||
use theme2::ThemeVariant;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct ItemSettings {
|
||||
|
@ -237,7 +237,7 @@ pub trait ItemHandle: 'static + Send {
|
|||
fn deactivated(&self, cx: &mut WindowContext);
|
||||
fn workspace_deactivated(&self, cx: &mut WindowContext);
|
||||
fn navigate(&self, data: Box<dyn Any>, cx: &mut WindowContext) -> bool;
|
||||
fn id(&self) -> usize;
|
||||
fn id(&self) -> EntityId;
|
||||
fn to_any(&self) -> AnyView;
|
||||
fn is_dirty(&self, cx: &AppContext) -> bool;
|
||||
fn has_conflict(&self, cx: &AppContext) -> bool;
|
||||
|
@ -266,7 +266,7 @@ pub trait ItemHandle: 'static + Send {
|
|||
}
|
||||
|
||||
pub trait WeakItemHandle: Send + Sync {
|
||||
fn id(&self) -> usize;
|
||||
fn id(&self) -> EntityId;
|
||||
fn upgrade(&self) -> Option<Box<dyn ItemHandle>>;
|
||||
}
|
||||
|
||||
|
@ -518,8 +518,8 @@ impl<T: Item> ItemHandle for View<T> {
|
|||
self.update(cx, |this, cx| this.navigate(data, cx))
|
||||
}
|
||||
|
||||
fn id(&self) -> usize {
|
||||
self.id()
|
||||
fn id(&self) -> EntityId {
|
||||
self.entity_id()
|
||||
}
|
||||
|
||||
fn to_any(&self) -> AnyView {
|
||||
|
@ -621,8 +621,8 @@ impl Clone for Box<dyn ItemHandle> {
|
|||
}
|
||||
|
||||
impl<T: Item> WeakItemHandle for WeakView<T> {
|
||||
fn id(&self) -> usize {
|
||||
self.id()
|
||||
fn id(&self) -> EntityId {
|
||||
self.entity_id()
|
||||
}
|
||||
|
||||
fn upgrade(&self) -> Option<Box<dyn ItemHandle>> {
|
||||
|
@ -695,7 +695,7 @@ impl<T: FollowableItem> FollowableItemHandle for View<T> {
|
|||
self.read(cx).remote_id().or_else(|| {
|
||||
client.peer_id().map(|creator| ViewId {
|
||||
creator,
|
||||
id: self.id() as u64,
|
||||
id: self.id().as_u64(),
|
||||
})
|
||||
})
|
||||
}
|
||||
|
|
|
@ -3,26 +3,29 @@
|
|||
use crate::{
|
||||
item::{Item, ItemHandle, WeakItemHandle},
|
||||
toolbar::Toolbar,
|
||||
workspace_settings::{AutosaveSetting, WorkspaceSettings},
|
||||
SplitDirection, Workspace,
|
||||
};
|
||||
use anyhow::Result;
|
||||
use collections::{HashMap, VecDeque};
|
||||
use collections::{HashMap, HashSet, VecDeque};
|
||||
use gpui2::{
|
||||
AppContext, EventEmitter, Model, Task, View, ViewContext, VisualContext, WeakView,
|
||||
WindowContext,
|
||||
AppContext, AsyncWindowContext, EntityId, EventEmitter, Model, PromptLevel, Task, View,
|
||||
ViewContext, VisualContext, WeakView, WindowContext,
|
||||
};
|
||||
use parking_lot::Mutex;
|
||||
use project2::{Project, ProjectEntryId, ProjectPath};
|
||||
use serde::Deserialize;
|
||||
use settings2::Settings;
|
||||
use std::{
|
||||
any::Any,
|
||||
cmp, fmt, mem,
|
||||
path::PathBuf,
|
||||
path::{Path, PathBuf},
|
||||
sync::{
|
||||
atomic::{AtomicUsize, Ordering},
|
||||
Arc,
|
||||
},
|
||||
};
|
||||
use util::truncate_and_remove_front;
|
||||
|
||||
#[derive(PartialEq, Clone, Copy, Deserialize, Debug)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
|
@ -132,7 +135,7 @@ pub enum Event {
|
|||
AddItem { item: Box<dyn ItemHandle> },
|
||||
ActivateItem { local: bool },
|
||||
Remove,
|
||||
RemoveItem { item_id: usize },
|
||||
RemoveItem { item_id: EntityId },
|
||||
Split(SplitDirection),
|
||||
ChangeItemTitle,
|
||||
Focus,
|
||||
|
@ -167,7 +170,7 @@ impl fmt::Debug for Event {
|
|||
|
||||
pub struct Pane {
|
||||
items: Vec<Box<dyn ItemHandle>>,
|
||||
activation_history: Vec<usize>,
|
||||
activation_history: Vec<EntityId>,
|
||||
zoomed: bool,
|
||||
active_item_index: usize,
|
||||
// last_focused_view_by_item: HashMap<usize, AnyWeakViewHandle>,
|
||||
|
@ -176,7 +179,7 @@ pub struct Pane {
|
|||
toolbar: View<Toolbar>,
|
||||
// tab_bar_context_menu: TabBarContextMenu,
|
||||
// tab_context_menu: ViewHandle<ContextMenu>,
|
||||
// workspace: WeakView<Workspace>,
|
||||
workspace: WeakView<Workspace>,
|
||||
project: Model<Project>,
|
||||
has_focus: bool,
|
||||
// can_drop: Rc<dyn Fn(&DragAndDrop<Workspace>, &WindowContext) -> bool>,
|
||||
|
@ -197,7 +200,7 @@ struct NavHistoryState {
|
|||
backward_stack: VecDeque<NavigationEntry>,
|
||||
forward_stack: VecDeque<NavigationEntry>,
|
||||
closed_stack: VecDeque<NavigationEntry>,
|
||||
paths_by_item: HashMap<usize, (ProjectPath, Option<PathBuf>)>,
|
||||
paths_by_item: HashMap<EntityId, (ProjectPath, Option<PathBuf>)>,
|
||||
pane: WeakView<Pane>,
|
||||
next_timestamp: Arc<AtomicUsize>,
|
||||
}
|
||||
|
@ -346,7 +349,7 @@ impl Pane {
|
|||
// handle: context_menu,
|
||||
// },
|
||||
// tab_context_menu: cx.add_view(|cx| ContextMenu::new(pane_view_id, cx)),
|
||||
// workspace,
|
||||
workspace,
|
||||
project,
|
||||
has_focus: false,
|
||||
// can_drop: Rc::new(|_, _| true),
|
||||
|
@ -748,12 +751,11 @@ impl Pane {
|
|||
|
||||
pub fn close_item_by_id(
|
||||
&mut self,
|
||||
item_id_to_close: usize,
|
||||
item_id_to_close: EntityId,
|
||||
save_intent: SaveIntent,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) -> Task<Result<()>> {
|
||||
// self.close_items(cx, save_intent, move |view_id| view_id == item_id_to_close)
|
||||
todo!()
|
||||
self.close_items(cx, save_intent, move |view_id| view_id == item_id_to_close)
|
||||
}
|
||||
|
||||
// pub fn close_inactive_items(
|
||||
|
@ -857,142 +859,142 @@ impl Pane {
|
|||
// )
|
||||
// }
|
||||
|
||||
// pub(super) fn file_names_for_prompt(
|
||||
// items: &mut dyn Iterator<Item = &Box<dyn ItemHandle>>,
|
||||
// all_dirty_items: usize,
|
||||
// cx: &AppContext,
|
||||
// ) -> String {
|
||||
// /// Quantity of item paths displayed in prompt prior to cutoff..
|
||||
// const FILE_NAMES_CUTOFF_POINT: usize = 10;
|
||||
// let mut file_names: Vec<_> = items
|
||||
// .filter_map(|item| {
|
||||
// item.project_path(cx).and_then(|project_path| {
|
||||
// project_path
|
||||
// .path
|
||||
// .file_name()
|
||||
// .and_then(|name| name.to_str().map(ToOwned::to_owned))
|
||||
// })
|
||||
// })
|
||||
// .take(FILE_NAMES_CUTOFF_POINT)
|
||||
// .collect();
|
||||
// let should_display_followup_text =
|
||||
// all_dirty_items > FILE_NAMES_CUTOFF_POINT || file_names.len() != all_dirty_items;
|
||||
// if should_display_followup_text {
|
||||
// let not_shown_files = all_dirty_items - file_names.len();
|
||||
// if not_shown_files == 1 {
|
||||
// file_names.push(".. 1 file not shown".into());
|
||||
// } else {
|
||||
// file_names.push(format!(".. {} files not shown", not_shown_files).into());
|
||||
// }
|
||||
// }
|
||||
// let file_names = file_names.join("\n");
|
||||
// format!(
|
||||
// "Do you want to save changes to the following {} files?\n{file_names}",
|
||||
// all_dirty_items
|
||||
// )
|
||||
// }
|
||||
pub(super) fn file_names_for_prompt(
|
||||
items: &mut dyn Iterator<Item = &Box<dyn ItemHandle>>,
|
||||
all_dirty_items: usize,
|
||||
cx: &AppContext,
|
||||
) -> String {
|
||||
/// Quantity of item paths displayed in prompt prior to cutoff..
|
||||
const FILE_NAMES_CUTOFF_POINT: usize = 10;
|
||||
let mut file_names: Vec<_> = items
|
||||
.filter_map(|item| {
|
||||
item.project_path(cx).and_then(|project_path| {
|
||||
project_path
|
||||
.path
|
||||
.file_name()
|
||||
.and_then(|name| name.to_str().map(ToOwned::to_owned))
|
||||
})
|
||||
})
|
||||
.take(FILE_NAMES_CUTOFF_POINT)
|
||||
.collect();
|
||||
let should_display_followup_text =
|
||||
all_dirty_items > FILE_NAMES_CUTOFF_POINT || file_names.len() != all_dirty_items;
|
||||
if should_display_followup_text {
|
||||
let not_shown_files = all_dirty_items - file_names.len();
|
||||
if not_shown_files == 1 {
|
||||
file_names.push(".. 1 file not shown".into());
|
||||
} else {
|
||||
file_names.push(format!(".. {} files not shown", not_shown_files).into());
|
||||
}
|
||||
}
|
||||
let file_names = file_names.join("\n");
|
||||
format!(
|
||||
"Do you want to save changes to the following {} files?\n{file_names}",
|
||||
all_dirty_items
|
||||
)
|
||||
}
|
||||
|
||||
// pub fn close_items(
|
||||
// &mut self,
|
||||
// cx: &mut ViewContext<Pane>,
|
||||
// mut save_intent: SaveIntent,
|
||||
// should_close: impl 'static + Fn(usize) -> bool,
|
||||
// ) -> Task<Result<()>> {
|
||||
// // Find the items to close.
|
||||
// let mut items_to_close = Vec::new();
|
||||
// let mut dirty_items = Vec::new();
|
||||
// for item in &self.items {
|
||||
// if should_close(item.id()) {
|
||||
// items_to_close.push(item.boxed_clone());
|
||||
// if item.is_dirty(cx) {
|
||||
// dirty_items.push(item.boxed_clone());
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
pub fn close_items(
|
||||
&mut self,
|
||||
cx: &mut ViewContext<Pane>,
|
||||
mut save_intent: SaveIntent,
|
||||
should_close: impl 'static + Fn(EntityId) -> bool,
|
||||
) -> Task<Result<()>> {
|
||||
// Find the items to close.
|
||||
let mut items_to_close = Vec::new();
|
||||
let mut dirty_items = Vec::new();
|
||||
for item in &self.items {
|
||||
if should_close(item.id()) {
|
||||
items_to_close.push(item.boxed_clone());
|
||||
if item.is_dirty(cx) {
|
||||
dirty_items.push(item.boxed_clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// // If a buffer is open both in a singleton editor and in a multibuffer, make sure
|
||||
// // to focus the singleton buffer when prompting to save that buffer, as opposed
|
||||
// // to focusing the multibuffer, because this gives the user a more clear idea
|
||||
// // of what content they would be saving.
|
||||
// items_to_close.sort_by_key(|item| !item.is_singleton(cx));
|
||||
// If a buffer is open both in a singleton editor and in a multibuffer, make sure
|
||||
// to focus the singleton buffer when prompting to save that buffer, as opposed
|
||||
// to focusing the multibuffer, because this gives the user a more clear idea
|
||||
// of what content they would be saving.
|
||||
items_to_close.sort_by_key(|item| !item.is_singleton(cx));
|
||||
|
||||
// let workspace = self.workspace.clone();
|
||||
// cx.spawn(|pane, mut cx| async move {
|
||||
// if save_intent == SaveIntent::Close && dirty_items.len() > 1 {
|
||||
// let mut answer = pane.update(&mut cx, |_, cx| {
|
||||
// let prompt =
|
||||
// Self::file_names_for_prompt(&mut dirty_items.iter(), dirty_items.len(), cx);
|
||||
// cx.prompt(
|
||||
// PromptLevel::Warning,
|
||||
// &prompt,
|
||||
// &["Save all", "Discard all", "Cancel"],
|
||||
// )
|
||||
// })?;
|
||||
// match answer.next().await {
|
||||
// Some(0) => save_intent = SaveIntent::SaveAll,
|
||||
// Some(1) => save_intent = SaveIntent::Skip,
|
||||
// _ => {}
|
||||
// }
|
||||
// }
|
||||
// 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 item models. Avoid
|
||||
// // storing these in advance, in case they have changed since this task
|
||||
// // was started.
|
||||
// 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
|
||||
// } else {
|
||||
// continue;
|
||||
// };
|
||||
let workspace = self.workspace.clone();
|
||||
cx.spawn(|pane, mut cx| async move {
|
||||
if save_intent == SaveIntent::Close && dirty_items.len() > 1 {
|
||||
let answer = pane.update(&mut cx, |_, cx| {
|
||||
let prompt =
|
||||
Self::file_names_for_prompt(&mut dirty_items.iter(), dirty_items.len(), cx);
|
||||
cx.prompt(
|
||||
PromptLevel::Warning,
|
||||
&prompt,
|
||||
&["Save all", "Discard all", "Cancel"],
|
||||
)
|
||||
})?;
|
||||
match answer.await {
|
||||
Ok(0) => save_intent = SaveIntent::SaveAll,
|
||||
Ok(1) => save_intent = SaveIntent::Skip,
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
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 item models. Avoid
|
||||
// storing these in advance, in case they have changed since this task
|
||||
// was started.
|
||||
let (item_ix, mut project_item_ids) = pane.update(&mut cx, |pane, cx| {
|
||||
(pane.index_for_item(&*item), item.project_item_model_ids(cx))
|
||||
})?;
|
||||
let item_ix = if let Some(ix) = item_ix {
|
||||
ix
|
||||
} else {
|
||||
continue;
|
||||
};
|
||||
|
||||
// // 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.
|
||||
// let project = 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));
|
||||
// }
|
||||
// }
|
||||
// workspace.project().clone()
|
||||
// })?;
|
||||
// let should_save = project_item_ids
|
||||
// .iter()
|
||||
// .any(|id| saved_project_items_ids.insert(*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.
|
||||
let project = workspace.update(&mut 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));
|
||||
}
|
||||
}
|
||||
workspace.project().clone()
|
||||
})?;
|
||||
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,
|
||||
// save_intent,
|
||||
// &mut cx,
|
||||
// )
|
||||
// .await?
|
||||
// {
|
||||
// break;
|
||||
// }
|
||||
if should_save
|
||||
&& !Self::save_item(
|
||||
project.clone(),
|
||||
&pane,
|
||||
item_ix,
|
||||
&*item,
|
||||
save_intent,
|
||||
&mut cx,
|
||||
)
|
||||
.await?
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
// // Remove the item from the pane.
|
||||
// pane.update(&mut cx, |pane, cx| {
|
||||
// if let Some(item_ix) = pane.items.iter().position(|i| i.id() == item.id()) {
|
||||
// pane.remove_item(item_ix, false, cx);
|
||||
// }
|
||||
// })?;
|
||||
// }
|
||||
// Remove the item from the pane.
|
||||
pane.update(&mut cx, |pane, cx| {
|
||||
if let Some(item_ix) = pane.items.iter().position(|i| i.id() == item.id()) {
|
||||
pane.remove_item(item_ix, false, cx);
|
||||
}
|
||||
})?;
|
||||
}
|
||||
|
||||
// pane.update(&mut cx, |_, cx| cx.notify())?;
|
||||
// Ok(())
|
||||
// })
|
||||
// }
|
||||
pane.update(&mut cx, |_, cx| cx.notify())?;
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
||||
pub fn remove_item(
|
||||
&mut self,
|
||||
|
@ -1062,106 +1064,106 @@ impl Pane {
|
|||
cx.notify();
|
||||
}
|
||||
|
||||
// pub async fn save_item(
|
||||
// project: Model<Project>,
|
||||
// pane: &WeakView<Pane>,
|
||||
// item_ix: usize,
|
||||
// item: &dyn ItemHandle,
|
||||
// save_intent: SaveIntent,
|
||||
// cx: &mut AsyncAppContext,
|
||||
// ) -> Result<bool> {
|
||||
// const CONFLICT_MESSAGE: &str =
|
||||
// "This file has changed on disk since you started editing it. Do you want to overwrite it?";
|
||||
pub async fn save_item(
|
||||
project: Model<Project>,
|
||||
pane: &WeakView<Pane>,
|
||||
item_ix: usize,
|
||||
item: &dyn ItemHandle,
|
||||
save_intent: SaveIntent,
|
||||
cx: &mut AsyncWindowContext,
|
||||
) -> Result<bool> {
|
||||
const CONFLICT_MESSAGE: &str =
|
||||
"This file has changed on disk since you started editing it. Do you want to overwrite it?";
|
||||
|
||||
// if save_intent == SaveIntent::Skip {
|
||||
// return Ok(true);
|
||||
// }
|
||||
if save_intent == SaveIntent::Skip {
|
||||
return Ok(true);
|
||||
}
|
||||
|
||||
// let (mut has_conflict, mut is_dirty, mut can_save, can_save_as) = cx.read(|cx| {
|
||||
// (
|
||||
// item.has_conflict(cx),
|
||||
// item.is_dirty(cx),
|
||||
// item.can_save(cx),
|
||||
// item.is_singleton(cx),
|
||||
// )
|
||||
// });
|
||||
let (mut has_conflict, mut is_dirty, mut can_save, can_save_as) = cx.update(|_, cx| {
|
||||
(
|
||||
item.has_conflict(cx),
|
||||
item.is_dirty(cx),
|
||||
item.can_save(cx),
|
||||
item.is_singleton(cx),
|
||||
)
|
||||
})?;
|
||||
|
||||
// // when saving a single buffer, we ignore whether or not it's dirty.
|
||||
// if save_intent == SaveIntent::Save {
|
||||
// is_dirty = true;
|
||||
// }
|
||||
// when saving a single buffer, we ignore whether or not it's dirty.
|
||||
if save_intent == SaveIntent::Save {
|
||||
is_dirty = true;
|
||||
}
|
||||
|
||||
// if save_intent == SaveIntent::SaveAs {
|
||||
// is_dirty = true;
|
||||
// has_conflict = false;
|
||||
// can_save = false;
|
||||
// }
|
||||
if save_intent == SaveIntent::SaveAs {
|
||||
is_dirty = true;
|
||||
has_conflict = false;
|
||||
can_save = false;
|
||||
}
|
||||
|
||||
// if save_intent == SaveIntent::Overwrite {
|
||||
// has_conflict = false;
|
||||
// }
|
||||
if save_intent == SaveIntent::Overwrite {
|
||||
has_conflict = false;
|
||||
}
|
||||
|
||||
// if has_conflict && can_save {
|
||||
// let mut answer = pane.update(cx, |pane, cx| {
|
||||
// pane.activate_item(item_ix, true, true, cx);
|
||||
// cx.prompt(
|
||||
// PromptLevel::Warning,
|
||||
// CONFLICT_MESSAGE,
|
||||
// &["Overwrite", "Discard", "Cancel"],
|
||||
// )
|
||||
// })?;
|
||||
// match answer.next().await {
|
||||
// Some(0) => pane.update(cx, |_, cx| item.save(project, cx))?.await?,
|
||||
// Some(1) => pane.update(cx, |_, cx| item.reload(project, cx))?.await?,
|
||||
// _ => return Ok(false),
|
||||
// }
|
||||
// } else if is_dirty && (can_save || can_save_as) {
|
||||
// if save_intent == SaveIntent::Close {
|
||||
// let will_autosave = cx.read(|cx| {
|
||||
// matches!(
|
||||
// settings::get::<WorkspaceSettings>(cx).autosave,
|
||||
// AutosaveSetting::OnFocusChange | AutosaveSetting::OnWindowChange
|
||||
// ) && Self::can_autosave_item(&*item, cx)
|
||||
// });
|
||||
// if !will_autosave {
|
||||
// let mut answer = pane.update(cx, |pane, cx| {
|
||||
// pane.activate_item(item_ix, true, true, cx);
|
||||
// let prompt = dirty_message_for(item.project_path(cx));
|
||||
// cx.prompt(
|
||||
// PromptLevel::Warning,
|
||||
// &prompt,
|
||||
// &["Save", "Don't Save", "Cancel"],
|
||||
// )
|
||||
// })?;
|
||||
// match answer.next().await {
|
||||
// Some(0) => {}
|
||||
// Some(1) => return Ok(true), // Don't save his file
|
||||
// _ => return Ok(false), // Cancel
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
if has_conflict && can_save {
|
||||
let answer = pane.update(cx, |pane, cx| {
|
||||
pane.activate_item(item_ix, true, true, cx);
|
||||
cx.prompt(
|
||||
PromptLevel::Warning,
|
||||
CONFLICT_MESSAGE,
|
||||
&["Overwrite", "Discard", "Cancel"],
|
||||
)
|
||||
})?;
|
||||
match answer.await {
|
||||
Ok(0) => pane.update(cx, |_, cx| item.save(project, cx))?.await?,
|
||||
Ok(1) => pane.update(cx, |_, cx| item.reload(project, cx))?.await?,
|
||||
_ => return Ok(false),
|
||||
}
|
||||
} else if is_dirty && (can_save || can_save_as) {
|
||||
if save_intent == SaveIntent::Close {
|
||||
let will_autosave = cx.update(|_, cx| {
|
||||
matches!(
|
||||
WorkspaceSettings::get_global(cx).autosave,
|
||||
AutosaveSetting::OnFocusChange | AutosaveSetting::OnWindowChange
|
||||
) && Self::can_autosave_item(&*item, cx)
|
||||
})?;
|
||||
if !will_autosave {
|
||||
let answer = pane.update(cx, |pane, cx| {
|
||||
pane.activate_item(item_ix, true, true, cx);
|
||||
let prompt = dirty_message_for(item.project_path(cx));
|
||||
cx.prompt(
|
||||
PromptLevel::Warning,
|
||||
&prompt,
|
||||
&["Save", "Don't Save", "Cancel"],
|
||||
)
|
||||
})?;
|
||||
match answer.await {
|
||||
Ok(0) => {}
|
||||
Ok(1) => return Ok(true), // Don't save this file
|
||||
_ => return Ok(false), // Cancel
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// if can_save {
|
||||
// pane.update(cx, |_, cx| item.save(project, cx))?.await?;
|
||||
// } else if can_save_as {
|
||||
// let start_abs_path = project
|
||||
// .read_with(cx, |project, cx| {
|
||||
// let worktree = project.visible_worktrees(cx).next()?;
|
||||
// Some(worktree.read(cx).as_local()?.abs_path().to_path_buf())
|
||||
// })
|
||||
// .unwrap_or_else(|| Path::new("").into());
|
||||
if can_save {
|
||||
pane.update(cx, |_, cx| item.save(project, cx))?.await?;
|
||||
} else if can_save_as {
|
||||
let start_abs_path = project
|
||||
.update(cx, |project, cx| {
|
||||
let worktree = project.visible_worktrees(cx).next()?;
|
||||
Some(worktree.read(cx).as_local()?.abs_path().to_path_buf())
|
||||
})?
|
||||
.unwrap_or_else(|| Path::new("").into());
|
||||
|
||||
// let mut abs_path = cx.update(|cx| cx.prompt_for_new_path(&start_abs_path));
|
||||
// if let Some(abs_path) = abs_path.next().await.flatten() {
|
||||
// pane.update(cx, |_, cx| item.save_as(project, abs_path, cx))?
|
||||
// .await?;
|
||||
// } else {
|
||||
// return Ok(false);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// Ok(true)
|
||||
// }
|
||||
let abs_path = cx.update(|_, cx| cx.prompt_for_new_path(&start_abs_path))?;
|
||||
if let Some(abs_path) = abs_path.await.ok().flatten() {
|
||||
pane.update(cx, |_, cx| item.save_as(project, abs_path, cx))?
|
||||
.await?;
|
||||
} else {
|
||||
return Ok(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
fn can_autosave_item(item: &dyn ItemHandle, cx: &AppContext) -> bool {
|
||||
let is_deleted = item.project_entry_ids(cx).is_empty();
|
||||
|
@ -2093,7 +2095,7 @@ impl NavHistory {
|
|||
state.did_update(cx);
|
||||
}
|
||||
|
||||
pub fn remove_item(&mut self, item_id: usize) {
|
||||
pub fn remove_item(&mut self, item_id: EntityId) {
|
||||
let mut state = self.0.lock();
|
||||
state.paths_by_item.remove(&item_id);
|
||||
state
|
||||
|
@ -2107,7 +2109,7 @@ impl NavHistory {
|
|||
.retain(|entry| entry.item.id() != item_id);
|
||||
}
|
||||
|
||||
pub fn path_for_item(&self, item_id: usize) -> Option<(ProjectPath, Option<PathBuf>)> {
|
||||
pub fn path_for_item(&self, item_id: EntityId) -> Option<(ProjectPath, Option<PathBuf>)> {
|
||||
self.0.lock().paths_by_item.get(&item_id).cloned()
|
||||
}
|
||||
}
|
||||
|
@ -2214,14 +2216,14 @@ impl NavHistoryState {
|
|||
// }
|
||||
// }
|
||||
|
||||
// fn dirty_message_for(buffer_path: Option<ProjectPath>) -> String {
|
||||
// let path = buffer_path
|
||||
// .as_ref()
|
||||
// .and_then(|p| p.path.to_str())
|
||||
// .unwrap_or(&"This buffer");
|
||||
// let path = truncate_and_remove_front(path, 80);
|
||||
// format!("{path} contains unsaved edits. Do you want to save it?")
|
||||
// }
|
||||
fn dirty_message_for(buffer_path: Option<ProjectPath>) -> String {
|
||||
let path = buffer_path
|
||||
.as_ref()
|
||||
.and_then(|p| p.path.to_str())
|
||||
.unwrap_or(&"This buffer");
|
||||
let path = truncate_and_remove_front(path, 80);
|
||||
format!("{path} contains unsaved edits. Do you want to save it?")
|
||||
}
|
||||
|
||||
// todo!("uncomment tests")
|
||||
// #[cfg(test)]
|
||||
|
|
|
@ -200,7 +200,7 @@ impl<T: SearchableItem> SearchableItemHandle for View<T> {
|
|||
cx: &mut WindowContext,
|
||||
) -> Task<Vec<Box<dyn Any + Send>>> {
|
||||
let matches = self.update(cx, |this, cx| this.find_matches(query, cx));
|
||||
cx.spawn_on_main(|cx| async {
|
||||
cx.spawn(|cx| async {
|
||||
let matches = matches.await;
|
||||
matches
|
||||
.into_iter()
|
||||
|
|
|
@ -29,12 +29,12 @@ use futures::{
|
|||
};
|
||||
use gpui2::{
|
||||
div, point, size, AnyModel, AnyView, AppContext, AsyncAppContext, AsyncWindowContext, Bounds,
|
||||
Context, Div, EventEmitter, GlobalPixels, MainThread, Model, ModelContext, Point, Render, Size,
|
||||
Div, EntityId, EventEmitter, GlobalPixels, Model, ModelContext, Point, Render, Size,
|
||||
Subscription, Task, View, ViewContext, VisualContext, WeakView, WindowBounds, WindowContext,
|
||||
WindowHandle, WindowOptions,
|
||||
};
|
||||
use item::{FollowableItem, FollowableItemHandle, Item, ItemHandle, ProjectItem};
|
||||
use language2::{LanguageRegistry, LocalFile};
|
||||
use language2::LanguageRegistry;
|
||||
use lazy_static::lazy_static;
|
||||
use node_runtime::NodeRuntime;
|
||||
use notifications::{simple_message_notification::MessageNotification, NotificationHandle};
|
||||
|
@ -386,7 +386,7 @@ pub fn register_followable_item<I: FollowableItem>(cx: &mut AppContext) {
|
|||
(
|
||||
|pane, workspace, id, state, cx| {
|
||||
I::from_state_proto(pane, workspace, id, state, cx).map(|task| {
|
||||
cx.executor()
|
||||
cx.foreground_executor()
|
||||
.spawn(async move { Ok(Box::new(task.await?) as Box<_>) })
|
||||
})
|
||||
},
|
||||
|
@ -412,7 +412,8 @@ pub fn register_deserializable_item<I: Item>(cx: &mut AppContext) {
|
|||
Arc::from(serialized_item_kind),
|
||||
|project, workspace, workspace_id, item_id, cx| {
|
||||
let task = I::deserialize(project, workspace, workspace_id, item_id, cx);
|
||||
cx.spawn_on_main(|_| async { Ok(Box::new(task.await?) as Box<_>) })
|
||||
cx.foreground_executor()
|
||||
.spawn(async { Ok(Box::new(task.await?) as Box<_>) })
|
||||
},
|
||||
);
|
||||
}
|
||||
|
@ -426,7 +427,7 @@ pub struct AppState {
|
|||
pub workspace_store: Model<WorkspaceStore>,
|
||||
pub fs: Arc<dyn fs2::Fs>,
|
||||
pub build_window_options:
|
||||
fn(Option<WindowBounds>, Option<Uuid>, &mut MainThread<AppContext>) -> WindowOptions,
|
||||
fn(Option<WindowBounds>, Option<Uuid>, &mut AppContext) -> WindowOptions,
|
||||
pub initialize_workspace: fn(
|
||||
WeakView<Workspace>,
|
||||
bool,
|
||||
|
@ -511,7 +512,7 @@ impl DelayedDebouncedEditAction {
|
|||
|
||||
let previous_task = self.task.take();
|
||||
self.task = Some(cx.spawn(move |workspace, mut cx| async move {
|
||||
let mut timer = cx.executor().timer(delay).fuse();
|
||||
let mut timer = cx.background_executor().timer(delay).fuse();
|
||||
if let Some(previous_task) = previous_task {
|
||||
previous_task.await;
|
||||
}
|
||||
|
@ -546,7 +547,7 @@ pub struct Workspace {
|
|||
bottom_dock: View<Dock>,
|
||||
right_dock: View<Dock>,
|
||||
panes: Vec<View<Pane>>,
|
||||
panes_by_item: HashMap<usize, WeakView<Pane>>,
|
||||
panes_by_item: HashMap<EntityId, WeakView<Pane>>,
|
||||
active_pane: View<Pane>,
|
||||
last_active_center_pane: Option<WeakView<Pane>>,
|
||||
// last_active_view_id: Option<proto::ViewId>,
|
||||
|
@ -568,9 +569,6 @@ pub struct Workspace {
|
|||
pane_history_timestamp: Arc<AtomicUsize>,
|
||||
}
|
||||
|
||||
trait AssertSend: Send {}
|
||||
impl AssertSend for WindowHandle<Workspace> {}
|
||||
|
||||
// struct ActiveModal {
|
||||
// view: Box<dyn ModalHandle>,
|
||||
// previously_focused_view_id: Option<usize>,
|
||||
|
@ -795,7 +793,7 @@ impl Workspace {
|
|||
abs_paths: Vec<PathBuf>,
|
||||
app_state: Arc<AppState>,
|
||||
_requesting_window: Option<WindowHandle<Workspace>>,
|
||||
cx: &mut MainThread<AppContext>,
|
||||
cx: &mut AppContext,
|
||||
) -> Task<
|
||||
anyhow::Result<(
|
||||
WindowHandle<Workspace>,
|
||||
|
@ -811,7 +809,7 @@ impl Workspace {
|
|||
cx,
|
||||
);
|
||||
|
||||
cx.spawn_on_main(|mut cx| async move {
|
||||
cx.spawn(|mut cx| async move {
|
||||
let serialized_workspace: Option<SerializedWorkspace> = None; //persistence::DB.workspace_for_roots(&abs_paths.as_slice());
|
||||
|
||||
let paths_to_open = Arc::new(abs_paths);
|
||||
|
@ -857,21 +855,25 @@ impl Workspace {
|
|||
serialized_workspace
|
||||
.as_ref()
|
||||
.and_then(|serialized_workspace| {
|
||||
let display = serialized_workspace.display?;
|
||||
let serialized_display = serialized_workspace.display?;
|
||||
let mut bounds = serialized_workspace.bounds?;
|
||||
|
||||
// Stored bounds are relative to the containing display.
|
||||
// So convert back to global coordinates if that screen still exists
|
||||
if let WindowBounds::Fixed(mut window_bounds) = bounds {
|
||||
let screen =
|
||||
cx.update(|cx| cx.display_for_uuid(display)).ok()??;
|
||||
cx.update(|cx|
|
||||
cx.displays()
|
||||
.into_iter()
|
||||
.find(|display| display.uuid().ok() == Some(serialized_display))
|
||||
).ok()??;
|
||||
let screen_bounds = screen.bounds();
|
||||
window_bounds.origin.x += screen_bounds.origin.x;
|
||||
window_bounds.origin.y += screen_bounds.origin.y;
|
||||
bounds = WindowBounds::Fixed(window_bounds);
|
||||
}
|
||||
|
||||
Some((bounds, display))
|
||||
Some((bounds, serialized_display))
|
||||
})
|
||||
.unzip()
|
||||
};
|
||||
|
@ -888,7 +890,8 @@ impl Workspace {
|
|||
cx.build_view(|cx| {
|
||||
Workspace::new(workspace_id, project_handle, app_state, cx)
|
||||
})
|
||||
}})?
|
||||
}
|
||||
})?
|
||||
};
|
||||
|
||||
// todo!() Ask how to do this
|
||||
|
@ -2123,7 +2126,7 @@ impl Workspace {
|
|||
let (project_entry_id, project_item) = project_item.await?;
|
||||
let build_item = cx.update(|_, cx| {
|
||||
cx.default_global::<ProjectItemBuilders>()
|
||||
.get(&project_item.type_id())
|
||||
.get(&project_item.entity_type())
|
||||
.ok_or_else(|| anyhow!("no item builder for project item"))
|
||||
.cloned()
|
||||
})??;
|
||||
|
@ -3259,7 +3262,7 @@ impl Workspace {
|
|||
.filter_map(|item_handle| {
|
||||
Some(SerializedItem {
|
||||
kind: Arc::from(item_handle.serialized_item_kind()?),
|
||||
item_id: item_handle.id(),
|
||||
item_id: item_handle.id().as_u64() as usize,
|
||||
active: Some(item_handle.id()) == active_item_id,
|
||||
})
|
||||
})
|
||||
|
@ -3565,7 +3568,7 @@ impl Workspace {
|
|||
// }
|
||||
}
|
||||
|
||||
fn window_bounds_env_override(cx: &MainThread<AsyncAppContext>) -> Option<WindowBounds> {
|
||||
fn window_bounds_env_override(cx: &AsyncAppContext) -> Option<WindowBounds> {
|
||||
let display_origin = cx
|
||||
.update(|cx| Some(cx.displays().first()?.bounds().origin))
|
||||
.ok()??;
|
||||
|
@ -3583,7 +3586,7 @@ fn open_items(
|
|||
_serialized_workspace: Option<SerializedWorkspace>,
|
||||
project_paths_to_open: Vec<(PathBuf, Option<ProjectPath>)>,
|
||||
app_state: Arc<AppState>,
|
||||
cx: &mut MainThread<ViewContext<'_, Workspace>>,
|
||||
cx: &mut ViewContext<Workspace>,
|
||||
) -> impl Future<Output = Result<Vec<Option<Result<Box<dyn ItemHandle>>>>>> {
|
||||
let mut opened_items = Vec::with_capacity(project_paths_to_open.len());
|
||||
|
||||
|
@ -4115,11 +4118,10 @@ impl ViewId {
|
|||
|
||||
// pub struct WorkspaceCreated(pub WeakView<Workspace>);
|
||||
|
||||
pub async fn activate_workspace_for_project(
|
||||
cx: &mut AsyncAppContext,
|
||||
pub fn activate_workspace_for_project(
|
||||
cx: &mut AppContext,
|
||||
predicate: impl Fn(&Project, &AppContext) -> bool + Send + 'static,
|
||||
) -> Option<WindowHandle<Workspace>> {
|
||||
cx.run_on_main(move |cx| {
|
||||
for window in cx.windows() {
|
||||
let Some(workspace) = window.downcast::<Workspace>() else {
|
||||
continue;
|
||||
|
@ -4144,9 +4146,6 @@ pub async fn activate_workspace_for_project(
|
|||
}
|
||||
|
||||
None
|
||||
})
|
||||
.ok()?
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn last_opened_workspace_paths() -> Option<WorkspaceLocation> {
|
||||
|
@ -4349,14 +4348,12 @@ pub fn open_paths(
|
|||
> {
|
||||
let app_state = app_state.clone();
|
||||
let abs_paths = abs_paths.to_vec();
|
||||
cx.spawn_on_main(move |mut cx| async move {
|
||||
// Open paths in existing workspace if possible
|
||||
let existing = activate_workspace_for_project(&mut cx, {
|
||||
let existing = activate_workspace_for_project(cx, {
|
||||
let abs_paths = abs_paths.clone();
|
||||
move |project, cx| project.contains_paths(&abs_paths, cx)
|
||||
})
|
||||
.await;
|
||||
|
||||
});
|
||||
cx.spawn(move |mut cx| async move {
|
||||
if let Some(existing) = existing {
|
||||
// // Ok((
|
||||
// existing.clone(),
|
||||
|
@ -4377,11 +4374,11 @@ pub fn open_paths(
|
|||
|
||||
pub fn open_new(
|
||||
app_state: &Arc<AppState>,
|
||||
cx: &mut MainThread<AppContext>,
|
||||
cx: &mut AppContext,
|
||||
init: impl FnOnce(&mut Workspace, &mut ViewContext<Workspace>) + 'static + Send,
|
||||
) -> Task<()> {
|
||||
let task = Workspace::new_local(Vec::new(), app_state.clone(), None, cx);
|
||||
cx.spawn_on_main(|mut cx| async move {
|
||||
cx.spawn(|mut cx| async move {
|
||||
if let Some((workspace, opened_paths)) = task.await.log_err() {
|
||||
workspace
|
||||
.update(&mut cx, |workspace, cx| {
|
||||
|
|
|
@ -12,7 +12,7 @@ use client2::UserStore;
|
|||
use db2::kvp::KEY_VALUE_STORE;
|
||||
use fs2::RealFs;
|
||||
use futures::{channel::mpsc, SinkExt, StreamExt};
|
||||
use gpui2::{Action, App, AppContext, AsyncAppContext, Context, MainThread, SemanticVersion, Task};
|
||||
use gpui2::{Action, App, AppContext, AsyncAppContext, Context, SemanticVersion, Task};
|
||||
use isahc::{prelude::Configurable, Request};
|
||||
use language2::LanguageRegistry;
|
||||
use log::LevelFilter;
|
||||
|
@ -249,7 +249,7 @@ fn main() {
|
|||
// .detach_and_log_err(cx)
|
||||
}
|
||||
Ok(None) | Err(_) => cx
|
||||
.spawn_on_main({
|
||||
.spawn({
|
||||
let app_state = app_state.clone();
|
||||
|cx| async move { restore_or_create_workspace(&app_state, cx).await }
|
||||
})
|
||||
|
@ -320,10 +320,7 @@ async fn installation_id() -> Result<String> {
|
|||
}
|
||||
}
|
||||
|
||||
async fn restore_or_create_workspace(
|
||||
app_state: &Arc<AppState>,
|
||||
mut cx: MainThread<AsyncAppContext>,
|
||||
) {
|
||||
async fn restore_or_create_workspace(app_state: &Arc<AppState>, mut cx: AsyncAppContext) {
|
||||
async_maybe!({
|
||||
if let Some(location) = workspace2::last_opened_workspace_paths().await {
|
||||
cx.update(|cx| workspace2::open_paths(location.paths().as_ref(), app_state, None, cx))?
|
||||
|
|
|
@ -6,8 +6,8 @@ mod open_listener;
|
|||
pub use assets::*;
|
||||
use collections::HashMap;
|
||||
use gpui2::{
|
||||
point, px, AppContext, AsyncAppContext, AsyncWindowContext, MainThread, Point, Task,
|
||||
TitlebarOptions, WeakView, WindowBounds, WindowHandle, WindowKind, WindowOptions,
|
||||
point, px, AppContext, AsyncAppContext, AsyncWindowContext, Point, Task, TitlebarOptions,
|
||||
WeakView, WindowBounds, WindowKind, WindowOptions,
|
||||
};
|
||||
pub use only_instance::*;
|
||||
pub use open_listener::*;
|
||||
|
@ -160,7 +160,7 @@ pub async fn handle_cli_connection(
|
|||
}
|
||||
|
||||
if wait {
|
||||
let executor = cx.executor().clone();
|
||||
let executor = cx.background_executor().clone();
|
||||
let wait = async move {
|
||||
if paths.is_empty() {
|
||||
let (done_tx, done_rx) = oneshot::channel();
|
||||
|
@ -219,10 +219,14 @@ pub async fn handle_cli_connection(
|
|||
pub fn build_window_options(
|
||||
bounds: Option<WindowBounds>,
|
||||
display_uuid: Option<Uuid>,
|
||||
cx: &mut MainThread<AppContext>,
|
||||
cx: &mut AppContext,
|
||||
) -> WindowOptions {
|
||||
let bounds = bounds.unwrap_or(WindowBounds::Maximized);
|
||||
let display = display_uuid.and_then(|uuid| cx.display_for_uuid(uuid));
|
||||
let display = display_uuid.and_then(|uuid| {
|
||||
cx.displays()
|
||||
.into_iter()
|
||||
.find(|display| display.uuid().ok() == Some(uuid))
|
||||
});
|
||||
|
||||
WindowOptions {
|
||||
bounds,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue