Checkpoint

This commit is contained in:
Antonio Scandurra 2023-11-02 11:05:24 +01:00
parent 72156bf502
commit 32dded551c
6 changed files with 325 additions and 325 deletions

View file

@ -12,8 +12,8 @@ use client2::{
Client, Client,
}; };
use gpui2::{ use gpui2::{
AnyElement, AnyView, AppContext, EventEmitter, HighlightStyle, Model, Pixels, Point, Render, AnyElement, AnyView, AppContext, Entity, EntityId, EventEmitter, HighlightStyle, Model, Pixels,
SharedString, Task, View, ViewContext, WeakView, WindowContext, WindowHandle, Point, Render, SharedString, Task, View, ViewContext, WeakView, WindowContext, WindowHandle,
}; };
use parking_lot::Mutex; use parking_lot::Mutex;
use project2::{Project, ProjectEntryId, ProjectPath}; use project2::{Project, ProjectEntryId, ProjectPath};
@ -21,7 +21,6 @@ use schemars::JsonSchema;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use settings2::Settings; use settings2::Settings;
use smallvec::SmallVec; use smallvec::SmallVec;
use theme2::ThemeVariant;
use std::{ use std::{
any::{Any, TypeId}, any::{Any, TypeId},
ops::Range, ops::Range,
@ -32,6 +31,7 @@ use std::{
}, },
time::Duration, time::Duration,
}; };
use theme2::ThemeVariant;
#[derive(Deserialize)] #[derive(Deserialize)]
pub struct ItemSettings { pub struct ItemSettings {
@ -237,7 +237,7 @@ pub trait ItemHandle: 'static + Send {
fn deactivated(&self, cx: &mut WindowContext); fn deactivated(&self, cx: &mut WindowContext);
fn workspace_deactivated(&self, cx: &mut WindowContext); fn workspace_deactivated(&self, cx: &mut WindowContext);
fn navigate(&self, data: Box<dyn Any>, cx: &mut WindowContext) -> bool; 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 to_any(&self) -> AnyView;
fn is_dirty(&self, cx: &AppContext) -> bool; fn is_dirty(&self, cx: &AppContext) -> bool;
fn has_conflict(&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 { pub trait WeakItemHandle: Send + Sync {
fn id(&self) -> usize; fn id(&self) -> EntityId;
fn upgrade(&self) -> Option<Box<dyn ItemHandle>>; 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)) self.update(cx, |this, cx| this.navigate(data, cx))
} }
fn id(&self) -> usize { fn id(&self) -> EntityId {
self.id() self.entity_id()
} }
fn to_any(&self) -> AnyView { fn to_any(&self) -> AnyView {
@ -621,8 +621,8 @@ impl Clone for Box<dyn ItemHandle> {
} }
impl<T: Item> WeakItemHandle for WeakView<T> { impl<T: Item> WeakItemHandle for WeakView<T> {
fn id(&self) -> usize { fn id(&self) -> EntityId {
self.id() self.entity_id()
} }
fn upgrade(&self) -> Option<Box<dyn ItemHandle>> { 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(|| { self.read(cx).remote_id().or_else(|| {
client.peer_id().map(|creator| ViewId { client.peer_id().map(|creator| ViewId {
creator, creator,
id: self.id() as u64, id: self.id().as_u64(),
}) })
}) })
} }

View file

@ -3,26 +3,29 @@
use crate::{ use crate::{
item::{Item, ItemHandle, WeakItemHandle}, item::{Item, ItemHandle, WeakItemHandle},
toolbar::Toolbar, toolbar::Toolbar,
workspace_settings::{AutosaveSetting, WorkspaceSettings},
SplitDirection, Workspace, SplitDirection, Workspace,
}; };
use anyhow::Result; use anyhow::Result;
use collections::{HashMap, VecDeque}; use collections::{HashMap, HashSet, VecDeque};
use gpui2::{ use gpui2::{
AppContext, EventEmitter, Model, Task, View, ViewContext, VisualContext, WeakView, AppContext, AsyncWindowContext, EntityId, EventEmitter, Model, PromptLevel, Task, View,
WindowContext, ViewContext, VisualContext, WeakView, WindowContext,
}; };
use parking_lot::Mutex; use parking_lot::Mutex;
use project2::{Project, ProjectEntryId, ProjectPath}; use project2::{Project, ProjectEntryId, ProjectPath};
use serde::Deserialize; use serde::Deserialize;
use settings2::Settings;
use std::{ use std::{
any::Any, any::Any,
cmp, fmt, mem, cmp, fmt, mem,
path::PathBuf, path::{Path, PathBuf},
sync::{ sync::{
atomic::{AtomicUsize, Ordering}, atomic::{AtomicUsize, Ordering},
Arc, Arc,
}, },
}; };
use util::truncate_and_remove_front;
#[derive(PartialEq, Clone, Copy, Deserialize, Debug)] #[derive(PartialEq, Clone, Copy, Deserialize, Debug)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
@ -132,7 +135,7 @@ pub enum Event {
AddItem { item: Box<dyn ItemHandle> }, AddItem { item: Box<dyn ItemHandle> },
ActivateItem { local: bool }, ActivateItem { local: bool },
Remove, Remove,
RemoveItem { item_id: usize }, RemoveItem { item_id: EntityId },
Split(SplitDirection), Split(SplitDirection),
ChangeItemTitle, ChangeItemTitle,
Focus, Focus,
@ -167,7 +170,7 @@ impl fmt::Debug for Event {
pub struct Pane { pub struct Pane {
items: Vec<Box<dyn ItemHandle>>, items: Vec<Box<dyn ItemHandle>>,
activation_history: Vec<usize>, activation_history: Vec<EntityId>,
zoomed: bool, zoomed: bool,
active_item_index: usize, active_item_index: usize,
// last_focused_view_by_item: HashMap<usize, AnyWeakViewHandle>, // last_focused_view_by_item: HashMap<usize, AnyWeakViewHandle>,
@ -176,7 +179,7 @@ pub struct Pane {
toolbar: View<Toolbar>, toolbar: View<Toolbar>,
// tab_bar_context_menu: TabBarContextMenu, // tab_bar_context_menu: TabBarContextMenu,
// tab_context_menu: ViewHandle<ContextMenu>, // tab_context_menu: ViewHandle<ContextMenu>,
// workspace: WeakView<Workspace>, workspace: WeakView<Workspace>,
project: Model<Project>, project: Model<Project>,
has_focus: bool, has_focus: bool,
// can_drop: Rc<dyn Fn(&DragAndDrop<Workspace>, &WindowContext) -> bool>, // can_drop: Rc<dyn Fn(&DragAndDrop<Workspace>, &WindowContext) -> bool>,
@ -197,7 +200,7 @@ struct NavHistoryState {
backward_stack: VecDeque<NavigationEntry>, backward_stack: VecDeque<NavigationEntry>,
forward_stack: VecDeque<NavigationEntry>, forward_stack: VecDeque<NavigationEntry>,
closed_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>, pane: WeakView<Pane>,
next_timestamp: Arc<AtomicUsize>, next_timestamp: Arc<AtomicUsize>,
} }
@ -346,7 +349,7 @@ impl Pane {
// handle: context_menu, // handle: context_menu,
// }, // },
// tab_context_menu: cx.add_view(|cx| ContextMenu::new(pane_view_id, cx)), // tab_context_menu: cx.add_view(|cx| ContextMenu::new(pane_view_id, cx)),
// workspace, workspace,
project, project,
has_focus: false, has_focus: false,
// can_drop: Rc::new(|_, _| true), // can_drop: Rc::new(|_, _| true),
@ -748,12 +751,11 @@ impl Pane {
pub fn close_item_by_id( pub fn close_item_by_id(
&mut self, &mut self,
item_id_to_close: usize, item_id_to_close: EntityId,
save_intent: SaveIntent, save_intent: SaveIntent,
cx: &mut ViewContext<Self>, cx: &mut ViewContext<Self>,
) -> Task<Result<()>> { ) -> Task<Result<()>> {
// self.close_items(cx, save_intent, move |view_id| view_id == item_id_to_close) self.close_items(cx, save_intent, move |view_id| view_id == item_id_to_close)
todo!()
} }
// pub fn close_inactive_items( // pub fn close_inactive_items(
@ -857,142 +859,142 @@ impl Pane {
// ) // )
// } // }
// pub(super) fn file_names_for_prompt( pub(super) fn file_names_for_prompt(
// items: &mut dyn Iterator<Item = &Box<dyn ItemHandle>>, items: &mut dyn Iterator<Item = &Box<dyn ItemHandle>>,
// all_dirty_items: usize, all_dirty_items: usize,
// cx: &AppContext, cx: &AppContext,
// ) -> String { ) -> String {
// /// Quantity of item paths displayed in prompt prior to cutoff.. /// Quantity of item paths displayed in prompt prior to cutoff..
// const FILE_NAMES_CUTOFF_POINT: usize = 10; const FILE_NAMES_CUTOFF_POINT: usize = 10;
// let mut file_names: Vec<_> = items let mut file_names: Vec<_> = items
// .filter_map(|item| { .filter_map(|item| {
// item.project_path(cx).and_then(|project_path| { item.project_path(cx).and_then(|project_path| {
// project_path project_path
// .path .path
// .file_name() .file_name()
// .and_then(|name| name.to_str().map(ToOwned::to_owned)) .and_then(|name| name.to_str().map(ToOwned::to_owned))
// }) })
// }) })
// .take(FILE_NAMES_CUTOFF_POINT) .take(FILE_NAMES_CUTOFF_POINT)
// .collect(); .collect();
// let should_display_followup_text = let should_display_followup_text =
// all_dirty_items > FILE_NAMES_CUTOFF_POINT || file_names.len() != all_dirty_items; all_dirty_items > FILE_NAMES_CUTOFF_POINT || file_names.len() != all_dirty_items;
// if should_display_followup_text { if should_display_followup_text {
// let not_shown_files = all_dirty_items - file_names.len(); let not_shown_files = all_dirty_items - file_names.len();
// if not_shown_files == 1 { if not_shown_files == 1 {
// file_names.push(".. 1 file not shown".into()); file_names.push(".. 1 file not shown".into());
// } else { } else {
// file_names.push(format!(".. {} files not shown", not_shown_files).into()); file_names.push(format!(".. {} files not shown", not_shown_files).into());
// } }
// } }
// let file_names = file_names.join("\n"); let file_names = file_names.join("\n");
// format!( format!(
// "Do you want to save changes to the following {} files?\n{file_names}", "Do you want to save changes to the following {} files?\n{file_names}",
// all_dirty_items all_dirty_items
// ) )
// } }
// pub fn close_items( pub fn close_items(
// &mut self, &mut self,
// cx: &mut ViewContext<Pane>, cx: &mut ViewContext<Pane>,
// mut save_intent: SaveIntent, mut save_intent: SaveIntent,
// should_close: impl 'static + Fn(usize) -> bool, should_close: impl 'static + Fn(EntityId) -> bool,
// ) -> Task<Result<()>> { ) -> Task<Result<()>> {
// // Find the items to close. // Find the items to close.
// let mut items_to_close = Vec::new(); let mut items_to_close = Vec::new();
// let mut dirty_items = Vec::new(); let mut dirty_items = Vec::new();
// for item in &self.items { for item in &self.items {
// if should_close(item.id()) { if should_close(item.id()) {
// items_to_close.push(item.boxed_clone()); items_to_close.push(item.boxed_clone());
// if item.is_dirty(cx) { if item.is_dirty(cx) {
// dirty_items.push(item.boxed_clone()); dirty_items.push(item.boxed_clone());
// } }
// } }
// } }
// // If a buffer is open both in a singleton editor and in a multibuffer, make sure // 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 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 // to focusing the multibuffer, because this gives the user a more clear idea
// // of what content they would be saving. // of what content they would be saving.
// items_to_close.sort_by_key(|item| !item.is_singleton(cx)); items_to_close.sort_by_key(|item| !item.is_singleton(cx));
// let workspace = self.workspace.clone(); let workspace = self.workspace.clone();
// cx.spawn(|pane, mut cx| async move { cx.spawn(|pane, mut cx| async move {
// if save_intent == SaveIntent::Close && dirty_items.len() > 1 { if save_intent == SaveIntent::Close && dirty_items.len() > 1 {
// let mut answer = pane.update(&mut cx, |_, cx| { let answer = pane.update(&mut cx, |_, cx| {
// let prompt = let prompt =
// Self::file_names_for_prompt(&mut dirty_items.iter(), dirty_items.len(), cx); Self::file_names_for_prompt(&mut dirty_items.iter(), dirty_items.len(), cx);
// cx.prompt( cx.prompt(
// PromptLevel::Warning, PromptLevel::Warning,
// &prompt, &prompt,
// &["Save all", "Discard all", "Cancel"], &["Save all", "Discard all", "Cancel"],
// ) )
// })?; })?;
// match answer.next().await { match answer.await {
// Some(0) => save_intent = SaveIntent::SaveAll, Ok(0) => save_intent = SaveIntent::SaveAll,
// Some(1) => save_intent = SaveIntent::Skip, Ok(1) => save_intent = SaveIntent::Skip,
// _ => {} _ => {}
// } }
// } }
// let mut saved_project_items_ids = HashSet::default(); let mut saved_project_items_ids = HashSet::default();
// for item in items_to_close.clone() { for item in items_to_close.clone() {
// // Find the item's current index and its set of project item models. Avoid // Find the item's current index and its set of project item models. Avoid
// // storing these in advance, in case they have changed since this task // storing these in advance, in case they have changed since this task
// // was started. // was started.
// let (item_ix, mut project_item_ids) = pane.read_with(&cx, |pane, cx| { let (item_ix, mut project_item_ids) = pane.update(&mut cx, |pane, cx| {
// (pane.index_for_item(&*item), item.project_item_model_ids(cx)) (pane.index_for_item(&*item), item.project_item_model_ids(cx))
// })?; })?;
// let item_ix = if let Some(ix) = item_ix { let item_ix = if let Some(ix) = item_ix {
// ix ix
// } else { } else {
// continue; continue;
// }; };
// // Check if this view has any project items that are not open anywhere else // 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. // 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. // If there are any such project entries, prompt the user to save this item.
// let project = workspace.read_with(&cx, |workspace, cx| { let project = workspace.update(&mut cx, |workspace, cx| {
// for item in workspace.items(cx) { for item in workspace.items(cx) {
// if !items_to_close if !items_to_close
// .iter() .iter()
// .any(|item_to_close| item_to_close.id() == item.id()) .any(|item_to_close| item_to_close.id() == item.id())
// { {
// let other_project_item_ids = item.project_item_model_ids(cx); let other_project_item_ids = item.project_item_model_ids(cx);
// project_item_ids.retain(|id| !other_project_item_ids.contains(id)); project_item_ids.retain(|id| !other_project_item_ids.contains(id));
// } }
// } }
// workspace.project().clone() workspace.project().clone()
// })?; })?;
// let should_save = project_item_ids let should_save = project_item_ids
// .iter() .iter()
// .any(|id| saved_project_items_ids.insert(*id)); .any(|id| saved_project_items_ids.insert(*id));
// if should_save if should_save
// && !Self::save_item( && !Self::save_item(
// project.clone(), project.clone(),
// &pane, &pane,
// item_ix, item_ix,
// &*item, &*item,
// save_intent, save_intent,
// &mut cx, &mut cx,
// ) )
// .await? .await?
// { {
// break; break;
// } }
// // Remove the item from the pane. // Remove the item from the pane.
// pane.update(&mut cx, |pane, cx| { pane.update(&mut cx, |pane, cx| {
// if let Some(item_ix) = pane.items.iter().position(|i| i.id() == item.id()) { if let Some(item_ix) = pane.items.iter().position(|i| i.id() == item.id()) {
// pane.remove_item(item_ix, false, cx); pane.remove_item(item_ix, false, cx);
// } }
// })?; })?;
// } }
// pane.update(&mut cx, |_, cx| cx.notify())?; pane.update(&mut cx, |_, cx| cx.notify())?;
// Ok(()) Ok(())
// }) })
// } }
pub fn remove_item( pub fn remove_item(
&mut self, &mut self,
@ -1062,106 +1064,106 @@ impl Pane {
cx.notify(); cx.notify();
} }
// pub async fn save_item( pub async fn save_item(
// project: Model<Project>, project: Model<Project>,
// pane: &WeakView<Pane>, pane: &WeakView<Pane>,
// item_ix: usize, item_ix: usize,
// item: &dyn ItemHandle, item: &dyn ItemHandle,
// save_intent: SaveIntent, save_intent: SaveIntent,
// cx: &mut AsyncAppContext, cx: &mut AsyncWindowContext,
// ) -> Result<bool> { ) -> Result<bool> {
// const CONFLICT_MESSAGE: &str = const CONFLICT_MESSAGE: &str =
// "This file has changed on disk since you started editing it. Do you want to overwrite it?"; "This file has changed on disk since you started editing it. Do you want to overwrite it?";
// if save_intent == SaveIntent::Skip { if save_intent == SaveIntent::Skip {
// return Ok(true); return Ok(true);
// } }
// let (mut has_conflict, mut is_dirty, mut can_save, can_save_as) = cx.read(|cx| { let (mut has_conflict, mut is_dirty, mut can_save, can_save_as) = cx.update(|_, cx| {
// ( (
// item.has_conflict(cx), item.has_conflict(cx),
// item.is_dirty(cx), item.is_dirty(cx),
// item.can_save(cx), item.can_save(cx),
// item.is_singleton(cx), item.is_singleton(cx),
// ) )
// }); })?;
// // when saving a single buffer, we ignore whether or not it's dirty. // when saving a single buffer, we ignore whether or not it's dirty.
// if save_intent == SaveIntent::Save { if save_intent == SaveIntent::Save {
// is_dirty = true; is_dirty = true;
// } }
// if save_intent == SaveIntent::SaveAs { if save_intent == SaveIntent::SaveAs {
// is_dirty = true; is_dirty = true;
// has_conflict = false; has_conflict = false;
// can_save = false; can_save = false;
// } }
// if save_intent == SaveIntent::Overwrite { if save_intent == SaveIntent::Overwrite {
// has_conflict = false; has_conflict = false;
// } }
// if has_conflict && can_save { if has_conflict && can_save {
// let mut answer = pane.update(cx, |pane, cx| { let answer = pane.update(cx, |pane, cx| {
// pane.activate_item(item_ix, true, true, cx); pane.activate_item(item_ix, true, true, cx);
// cx.prompt( cx.prompt(
// PromptLevel::Warning, PromptLevel::Warning,
// CONFLICT_MESSAGE, CONFLICT_MESSAGE,
// &["Overwrite", "Discard", "Cancel"], &["Overwrite", "Discard", "Cancel"],
// ) )
// })?; })?;
// match answer.next().await { match answer.await {
// Some(0) => pane.update(cx, |_, cx| item.save(project, cx))?.await?, Ok(0) => pane.update(cx, |_, cx| item.save(project, cx))?.await?,
// Some(1) => pane.update(cx, |_, cx| item.reload(project, cx))?.await?, Ok(1) => pane.update(cx, |_, cx| item.reload(project, cx))?.await?,
// _ => return Ok(false), _ => return Ok(false),
// } }
// } else if is_dirty && (can_save || can_save_as) { } else if is_dirty && (can_save || can_save_as) {
// if save_intent == SaveIntent::Close { if save_intent == SaveIntent::Close {
// let will_autosave = cx.read(|cx| { let will_autosave = cx.update(|_, cx| {
// matches!( matches!(
// settings::get::<WorkspaceSettings>(cx).autosave, WorkspaceSettings::get_global(cx).autosave,
// AutosaveSetting::OnFocusChange | AutosaveSetting::OnWindowChange AutosaveSetting::OnFocusChange | AutosaveSetting::OnWindowChange
// ) && Self::can_autosave_item(&*item, cx) ) && Self::can_autosave_item(&*item, cx)
// }); })?;
// if !will_autosave { if !will_autosave {
// let mut answer = pane.update(cx, |pane, cx| { let answer = pane.update(cx, |pane, cx| {
// pane.activate_item(item_ix, true, true, cx); pane.activate_item(item_ix, true, true, cx);
// let prompt = dirty_message_for(item.project_path(cx)); let prompt = dirty_message_for(item.project_path(cx));
// cx.prompt( cx.prompt(
// PromptLevel::Warning, PromptLevel::Warning,
// &prompt, &prompt,
// &["Save", "Don't Save", "Cancel"], &["Save", "Don't Save", "Cancel"],
// ) )
// })?; })?;
// match answer.next().await { match answer.await {
// Some(0) => {} Ok(0) => {}
// Some(1) => return Ok(true), // Don't save his file Ok(1) => return Ok(true), // Don't save this file
// _ => return Ok(false), // Cancel _ => return Ok(false), // Cancel
// } }
// } }
// } }
// if can_save { if can_save {
// pane.update(cx, |_, cx| item.save(project, cx))?.await?; pane.update(cx, |_, cx| item.save(project, cx))?.await?;
// } else if can_save_as { } else if can_save_as {
// let start_abs_path = project let start_abs_path = project
// .read_with(cx, |project, cx| { .update(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_else(|| Path::new("").into()); .unwrap_or_else(|| Path::new("").into());
// let mut abs_path = cx.update(|cx| cx.prompt_for_new_path(&start_abs_path)); let 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.await.ok().flatten() {
// pane.update(cx, |_, cx| item.save_as(project, abs_path, cx))? pane.update(cx, |_, cx| item.save_as(project, abs_path, cx))?
// .await?; .await?;
// } else { } else {
// return Ok(false); return Ok(false);
// } }
// } }
// } }
// Ok(true) Ok(true)
// } }
fn can_autosave_item(item: &dyn ItemHandle, cx: &AppContext) -> bool { fn can_autosave_item(item: &dyn ItemHandle, cx: &AppContext) -> bool {
let is_deleted = item.project_entry_ids(cx).is_empty(); let is_deleted = item.project_entry_ids(cx).is_empty();
@ -2093,7 +2095,7 @@ impl NavHistory {
state.did_update(cx); 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(); let mut state = self.0.lock();
state.paths_by_item.remove(&item_id); state.paths_by_item.remove(&item_id);
state state
@ -2107,7 +2109,7 @@ impl NavHistory {
.retain(|entry| entry.item.id() != item_id); .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() 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 { fn dirty_message_for(buffer_path: Option<ProjectPath>) -> String {
// let path = buffer_path let path = buffer_path
// .as_ref() .as_ref()
// .and_then(|p| p.path.to_str()) .and_then(|p| p.path.to_str())
// .unwrap_or(&"This buffer"); .unwrap_or(&"This buffer");
// let path = truncate_and_remove_front(path, 80); let path = truncate_and_remove_front(path, 80);
// format!("{path} contains unsaved edits. Do you want to save it?") format!("{path} contains unsaved edits. Do you want to save it?")
// } }
// todo!("uncomment tests") // todo!("uncomment tests")
// #[cfg(test)] // #[cfg(test)]

View file

@ -200,7 +200,7 @@ impl<T: SearchableItem> SearchableItemHandle for View<T> {
cx: &mut WindowContext, cx: &mut WindowContext,
) -> Task<Vec<Box<dyn Any + Send>>> { ) -> Task<Vec<Box<dyn Any + Send>>> {
let matches = self.update(cx, |this, cx| this.find_matches(query, cx)); 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; let matches = matches.await;
matches matches
.into_iter() .into_iter()

View file

@ -29,12 +29,12 @@ use futures::{
}; };
use gpui2::{ use gpui2::{
div, point, size, AnyModel, AnyView, AppContext, AsyncAppContext, AsyncWindowContext, Bounds, 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, Subscription, Task, View, ViewContext, VisualContext, WeakView, WindowBounds, WindowContext,
WindowHandle, WindowOptions, WindowHandle, WindowOptions,
}; };
use item::{FollowableItem, FollowableItemHandle, Item, ItemHandle, ProjectItem}; use item::{FollowableItem, FollowableItemHandle, Item, ItemHandle, ProjectItem};
use language2::{LanguageRegistry, LocalFile}; use language2::LanguageRegistry;
use lazy_static::lazy_static; use lazy_static::lazy_static;
use node_runtime::NodeRuntime; use node_runtime::NodeRuntime;
use notifications::{simple_message_notification::MessageNotification, NotificationHandle}; 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| { |pane, workspace, id, state, cx| {
I::from_state_proto(pane, workspace, id, state, cx).map(|task| { 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<_>) }) .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), Arc::from(serialized_item_kind),
|project, workspace, workspace_id, item_id, cx| { |project, workspace, workspace_id, item_id, cx| {
let task = I::deserialize(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 workspace_store: Model<WorkspaceStore>,
pub fs: Arc<dyn fs2::Fs>, pub fs: Arc<dyn fs2::Fs>,
pub build_window_options: 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( pub initialize_workspace: fn(
WeakView<Workspace>, WeakView<Workspace>,
bool, bool,
@ -511,7 +512,7 @@ impl DelayedDebouncedEditAction {
let previous_task = self.task.take(); let previous_task = self.task.take();
self.task = Some(cx.spawn(move |workspace, mut cx| async move { 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 { if let Some(previous_task) = previous_task {
previous_task.await; previous_task.await;
} }
@ -546,7 +547,7 @@ pub struct Workspace {
bottom_dock: View<Dock>, bottom_dock: View<Dock>,
right_dock: View<Dock>, right_dock: View<Dock>,
panes: Vec<View<Pane>>, panes: Vec<View<Pane>>,
panes_by_item: HashMap<usize, WeakView<Pane>>, panes_by_item: HashMap<EntityId, WeakView<Pane>>,
active_pane: View<Pane>, active_pane: View<Pane>,
last_active_center_pane: Option<WeakView<Pane>>, last_active_center_pane: Option<WeakView<Pane>>,
// last_active_view_id: Option<proto::ViewId>, // last_active_view_id: Option<proto::ViewId>,
@ -568,9 +569,6 @@ pub struct Workspace {
pane_history_timestamp: Arc<AtomicUsize>, pane_history_timestamp: Arc<AtomicUsize>,
} }
trait AssertSend: Send {}
impl AssertSend for WindowHandle<Workspace> {}
// struct ActiveModal { // struct ActiveModal {
// view: Box<dyn ModalHandle>, // view: Box<dyn ModalHandle>,
// previously_focused_view_id: Option<usize>, // previously_focused_view_id: Option<usize>,
@ -795,7 +793,7 @@ impl Workspace {
abs_paths: Vec<PathBuf>, abs_paths: Vec<PathBuf>,
app_state: Arc<AppState>, app_state: Arc<AppState>,
_requesting_window: Option<WindowHandle<Workspace>>, _requesting_window: Option<WindowHandle<Workspace>>,
cx: &mut MainThread<AppContext>, cx: &mut AppContext,
) -> Task< ) -> Task<
anyhow::Result<( anyhow::Result<(
WindowHandle<Workspace>, WindowHandle<Workspace>,
@ -811,7 +809,7 @@ impl Workspace {
cx, 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 serialized_workspace: Option<SerializedWorkspace> = None; //persistence::DB.workspace_for_roots(&abs_paths.as_slice());
let paths_to_open = Arc::new(abs_paths); let paths_to_open = Arc::new(abs_paths);
@ -857,21 +855,25 @@ impl Workspace {
serialized_workspace serialized_workspace
.as_ref() .as_ref()
.and_then(|serialized_workspace| { .and_then(|serialized_workspace| {
let display = serialized_workspace.display?; let serialized_display = serialized_workspace.display?;
let mut bounds = serialized_workspace.bounds?; let mut bounds = serialized_workspace.bounds?;
// Stored bounds are relative to the containing display. // Stored bounds are relative to the containing display.
// So convert back to global coordinates if that screen still exists // So convert back to global coordinates if that screen still exists
if let WindowBounds::Fixed(mut window_bounds) = bounds { if let WindowBounds::Fixed(mut window_bounds) = bounds {
let screen = 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(); let screen_bounds = screen.bounds();
window_bounds.origin.x += screen_bounds.origin.x; window_bounds.origin.x += screen_bounds.origin.x;
window_bounds.origin.y += screen_bounds.origin.y; window_bounds.origin.y += screen_bounds.origin.y;
bounds = WindowBounds::Fixed(window_bounds); bounds = WindowBounds::Fixed(window_bounds);
} }
Some((bounds, display)) Some((bounds, serialized_display))
}) })
.unzip() .unzip()
}; };
@ -885,11 +887,12 @@ impl Workspace {
let workspace_id = workspace_id.clone(); let workspace_id = workspace_id.clone();
let project_handle = project_handle.clone(); let project_handle = project_handle.clone();
move |cx| { move |cx| {
cx.build_view(|cx| { cx.build_view(|cx| {
Workspace::new(workspace_id, project_handle, app_state, cx) Workspace::new(workspace_id, project_handle, app_state, cx)
}) })
}})? }
}; })?
};
// todo!() Ask how to do this // todo!() Ask how to do this
let weak_view = window.update(&mut cx, |_, cx| cx.view().downgrade())?; let weak_view = window.update(&mut cx, |_, cx| cx.view().downgrade())?;
@ -2123,7 +2126,7 @@ impl Workspace {
let (project_entry_id, project_item) = project_item.await?; let (project_entry_id, project_item) = project_item.await?;
let build_item = cx.update(|_, cx| { let build_item = cx.update(|_, cx| {
cx.default_global::<ProjectItemBuilders>() cx.default_global::<ProjectItemBuilders>()
.get(&project_item.type_id()) .get(&project_item.entity_type())
.ok_or_else(|| anyhow!("no item builder for project item")) .ok_or_else(|| anyhow!("no item builder for project item"))
.cloned() .cloned()
})??; })??;
@ -3259,7 +3262,7 @@ impl Workspace {
.filter_map(|item_handle| { .filter_map(|item_handle| {
Some(SerializedItem { Some(SerializedItem {
kind: Arc::from(item_handle.serialized_item_kind()?), 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, 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 let display_origin = cx
.update(|cx| Some(cx.displays().first()?.bounds().origin)) .update(|cx| Some(cx.displays().first()?.bounds().origin))
.ok()??; .ok()??;
@ -3583,7 +3586,7 @@ fn open_items(
_serialized_workspace: Option<SerializedWorkspace>, _serialized_workspace: Option<SerializedWorkspace>,
project_paths_to_open: Vec<(PathBuf, Option<ProjectPath>)>, project_paths_to_open: Vec<(PathBuf, Option<ProjectPath>)>,
app_state: Arc<AppState>, app_state: Arc<AppState>,
cx: &mut MainThread<ViewContext<'_, Workspace>>, cx: &mut ViewContext<Workspace>,
) -> impl Future<Output = Result<Vec<Option<Result<Box<dyn ItemHandle>>>>>> { ) -> impl Future<Output = Result<Vec<Option<Result<Box<dyn ItemHandle>>>>>> {
let mut opened_items = Vec::with_capacity(project_paths_to_open.len()); let mut opened_items = Vec::with_capacity(project_paths_to_open.len());
@ -4115,38 +4118,34 @@ impl ViewId {
// pub struct WorkspaceCreated(pub WeakView<Workspace>); // pub struct WorkspaceCreated(pub WeakView<Workspace>);
pub async fn activate_workspace_for_project( pub fn activate_workspace_for_project(
cx: &mut AsyncAppContext, cx: &mut AppContext,
predicate: impl Fn(&Project, &AppContext) -> bool + Send + 'static, predicate: impl Fn(&Project, &AppContext) -> bool + Send + 'static,
) -> Option<WindowHandle<Workspace>> { ) -> Option<WindowHandle<Workspace>> {
cx.run_on_main(move |cx| { for window in cx.windows() {
for window in cx.windows() { let Some(workspace) = window.downcast::<Workspace>() else {
let Some(workspace) = window.downcast::<Workspace>() else { continue;
continue; };
};
let predicate = workspace let predicate = workspace
.update(cx, |workspace, cx| { .update(cx, |workspace, cx| {
let project = workspace.project.read(cx); let project = workspace.project.read(cx);
if predicate(project, cx) { if predicate(project, cx) {
cx.activate_window(); cx.activate_window();
true true
} else { } else {
false false
} }
}) })
.log_err() .log_err()
.unwrap_or(false); .unwrap_or(false);
if predicate { if predicate {
return Some(workspace); return Some(workspace);
}
} }
}
None None
})
.ok()?
.await
} }
pub async fn last_opened_workspace_paths() -> Option<WorkspaceLocation> { pub async fn last_opened_workspace_paths() -> Option<WorkspaceLocation> {
@ -4349,14 +4348,12 @@ pub fn open_paths(
> { > {
let app_state = app_state.clone(); let app_state = app_state.clone();
let abs_paths = abs_paths.to_vec(); let abs_paths = abs_paths.to_vec();
cx.spawn_on_main(move |mut cx| async move { // Open paths in existing workspace if possible
// Open paths in existing workspace if possible let existing = activate_workspace_for_project(cx, {
let existing = activate_workspace_for_project(&mut cx, { let abs_paths = abs_paths.clone();
let abs_paths = abs_paths.clone(); move |project, cx| project.contains_paths(&abs_paths, cx)
move |project, cx| project.contains_paths(&abs_paths, cx) });
}) cx.spawn(move |mut cx| async move {
.await;
if let Some(existing) = existing { if let Some(existing) = existing {
// // Ok(( // // Ok((
// existing.clone(), // existing.clone(),
@ -4377,11 +4374,11 @@ pub fn open_paths(
pub fn open_new( pub fn open_new(
app_state: &Arc<AppState>, app_state: &Arc<AppState>,
cx: &mut MainThread<AppContext>, cx: &mut AppContext,
init: impl FnOnce(&mut Workspace, &mut ViewContext<Workspace>) + 'static + Send, init: impl FnOnce(&mut Workspace, &mut ViewContext<Workspace>) + 'static + Send,
) -> Task<()> { ) -> Task<()> {
let task = Workspace::new_local(Vec::new(), app_state.clone(), None, cx); 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() { if let Some((workspace, opened_paths)) = task.await.log_err() {
workspace workspace
.update(&mut cx, |workspace, cx| { .update(&mut cx, |workspace, cx| {

View file

@ -12,7 +12,7 @@ use client2::UserStore;
use db2::kvp::KEY_VALUE_STORE; use db2::kvp::KEY_VALUE_STORE;
use fs2::RealFs; use fs2::RealFs;
use futures::{channel::mpsc, SinkExt, StreamExt}; 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 isahc::{prelude::Configurable, Request};
use language2::LanguageRegistry; use language2::LanguageRegistry;
use log::LevelFilter; use log::LevelFilter;
@ -249,7 +249,7 @@ fn main() {
// .detach_and_log_err(cx) // .detach_and_log_err(cx)
} }
Ok(None) | Err(_) => cx Ok(None) | Err(_) => cx
.spawn_on_main({ .spawn({
let app_state = app_state.clone(); let app_state = app_state.clone();
|cx| async move { restore_or_create_workspace(&app_state, cx).await } |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( async fn restore_or_create_workspace(app_state: &Arc<AppState>, mut cx: AsyncAppContext) {
app_state: &Arc<AppState>,
mut cx: MainThread<AsyncAppContext>,
) {
async_maybe!({ async_maybe!({
if let Some(location) = workspace2::last_opened_workspace_paths().await { if let Some(location) = workspace2::last_opened_workspace_paths().await {
cx.update(|cx| workspace2::open_paths(location.paths().as_ref(), app_state, None, cx))? cx.update(|cx| workspace2::open_paths(location.paths().as_ref(), app_state, None, cx))?

View file

@ -6,8 +6,8 @@ mod open_listener;
pub use assets::*; pub use assets::*;
use collections::HashMap; use collections::HashMap;
use gpui2::{ use gpui2::{
point, px, AppContext, AsyncAppContext, AsyncWindowContext, MainThread, Point, Task, point, px, AppContext, AsyncAppContext, AsyncWindowContext, Point, Task, TitlebarOptions,
TitlebarOptions, WeakView, WindowBounds, WindowHandle, WindowKind, WindowOptions, WeakView, WindowBounds, WindowKind, WindowOptions,
}; };
pub use only_instance::*; pub use only_instance::*;
pub use open_listener::*; pub use open_listener::*;
@ -160,7 +160,7 @@ pub async fn handle_cli_connection(
} }
if wait { if wait {
let executor = cx.executor().clone(); let executor = cx.background_executor().clone();
let wait = async move { let wait = async move {
if paths.is_empty() { if paths.is_empty() {
let (done_tx, done_rx) = oneshot::channel(); let (done_tx, done_rx) = oneshot::channel();
@ -219,10 +219,14 @@ pub async fn handle_cli_connection(
pub fn build_window_options( pub fn build_window_options(
bounds: Option<WindowBounds>, bounds: Option<WindowBounds>,
display_uuid: Option<Uuid>, display_uuid: Option<Uuid>,
cx: &mut MainThread<AppContext>, cx: &mut AppContext,
) -> WindowOptions { ) -> WindowOptions {
let bounds = bounds.unwrap_or(WindowBounds::Maximized); 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 { WindowOptions {
bounds, bounds,