Merge branch 'main' into panels
This commit is contained in:
commit
146809eef0
183 changed files with 10202 additions and 5720 deletions
|
@ -38,6 +38,7 @@ theme = { path = "../theme" }
|
|||
util = { path = "../util" }
|
||||
|
||||
async-recursion = "1.0.0"
|
||||
itertools = "0.10"
|
||||
bincode = "1.2.1"
|
||||
anyhow.workspace = true
|
||||
futures.workspace = true
|
||||
|
@ -45,6 +46,7 @@ lazy_static.workspace = true
|
|||
log.workspace = true
|
||||
parking_lot.workspace = true
|
||||
postage.workspace = true
|
||||
schemars.workspace = true
|
||||
serde.workspace = true
|
||||
serde_derive.workspace = true
|
||||
serde_json.workspace = true
|
||||
|
|
|
@ -6,8 +6,8 @@ use gpui::{
|
|||
WindowContext,
|
||||
};
|
||||
use serde::Deserialize;
|
||||
use settings::Settings;
|
||||
use std::rc::Rc;
|
||||
use theme::ThemeSettings;
|
||||
|
||||
pub trait Panel: View {
|
||||
fn position(&self, cx: &WindowContext) -> DockPosition;
|
||||
|
@ -379,7 +379,7 @@ impl Dock {
|
|||
|
||||
pub fn render_placeholder(&self, cx: &WindowContext) -> AnyElement<Workspace> {
|
||||
if let Some(active_entry) = self.active_entry() {
|
||||
let style = &cx.global::<Settings>().theme.workspace.dock;
|
||||
let style = &settings::get::<ThemeSettings>(cx).theme.workspace.dock;
|
||||
Empty::new()
|
||||
.into_any()
|
||||
.contained()
|
||||
|
@ -407,7 +407,7 @@ impl View for Dock {
|
|||
|
||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
|
||||
if let Some(active_entry) = self.active_entry() {
|
||||
let style = &cx.global::<Settings>().theme.workspace.dock;
|
||||
let style = &settings::get::<ThemeSettings>(cx).theme.workspace.dock;
|
||||
ChildView::new(active_entry.panel.as_any(), cx)
|
||||
.contained()
|
||||
.with_style(style.container)
|
||||
|
@ -444,7 +444,7 @@ impl View for PanelButtons {
|
|||
}
|
||||
|
||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
|
||||
let theme = &cx.global::<Settings>().theme;
|
||||
let theme = &settings::get::<ThemeSettings>(cx).theme;
|
||||
let tooltip_style = theme.tooltip.clone();
|
||||
let theme = &theme.workspace.status_bar.panel_buttons;
|
||||
let button_style = theme.button.clone();
|
||||
|
@ -578,7 +578,7 @@ impl StatusItemView for PanelButtons {
|
|||
#[cfg(test)]
|
||||
pub(crate) mod test {
|
||||
use super::*;
|
||||
use gpui::Entity;
|
||||
use gpui::{ViewContext, WindowContext};
|
||||
|
||||
pub enum TestPanelEvent {
|
||||
PositionChanged,
|
||||
|
|
|
@ -3,6 +3,7 @@ use crate::{
|
|||
FollowableItemBuilders, ItemNavHistory, Pane, ToolbarItemLocation, ViewId, Workspace,
|
||||
WorkspaceId,
|
||||
};
|
||||
use crate::{AutosaveSetting, WorkspaceSettings};
|
||||
use anyhow::Result;
|
||||
use client::{proto, Client};
|
||||
use gpui::{
|
||||
|
@ -10,7 +11,6 @@ use gpui::{
|
|||
ViewContext, ViewHandle, WeakViewHandle, WindowContext,
|
||||
};
|
||||
use project::{Project, ProjectEntryId, ProjectPath};
|
||||
use settings::{Autosave, Settings};
|
||||
use smallvec::SmallVec;
|
||||
use std::{
|
||||
any::{Any, TypeId},
|
||||
|
@ -450,8 +450,11 @@ impl<T: Item> ItemHandle for ViewHandle<T> {
|
|||
}
|
||||
|
||||
ItemEvent::Edit => {
|
||||
if let Autosave::AfterDelay { milliseconds } =
|
||||
cx.global::<Settings>().autosave
|
||||
let settings = settings::get::<WorkspaceSettings>(cx);
|
||||
let debounce_delay = settings.git.gutter_debounce;
|
||||
|
||||
if let AutosaveSetting::AfterDelay { milliseconds } =
|
||||
settings.autosave
|
||||
{
|
||||
let delay = Duration::from_millis(milliseconds);
|
||||
let item = item.clone();
|
||||
|
@ -460,9 +463,6 @@ impl<T: Item> ItemHandle for ViewHandle<T> {
|
|||
});
|
||||
}
|
||||
|
||||
let settings = cx.global::<Settings>();
|
||||
let debounce_delay = settings.git_overrides.gutter_debounce;
|
||||
|
||||
let item = item.clone();
|
||||
|
||||
if let Some(delay) = debounce_delay {
|
||||
|
@ -500,7 +500,10 @@ impl<T: Item> ItemHandle for ViewHandle<T> {
|
|||
}));
|
||||
|
||||
cx.observe_focus(self, move |workspace, item, focused, cx| {
|
||||
if !focused && cx.global::<Settings>().autosave == Autosave::OnFocusChange {
|
||||
if !focused
|
||||
&& settings::get::<WorkspaceSettings>(cx).autosave
|
||||
== AutosaveSetting::OnFocusChange
|
||||
{
|
||||
Pane::autosave_item(&item, workspace.project.clone(), cx)
|
||||
.detach_and_log_err(cx);
|
||||
}
|
||||
|
|
|
@ -149,6 +149,8 @@ impl Workspace {
|
|||
}
|
||||
|
||||
pub mod simple_message_notification {
|
||||
use super::Notification;
|
||||
use crate::Workspace;
|
||||
use gpui::{
|
||||
actions,
|
||||
elements::{Flex, MouseEventHandler, Padding, ParentElement, Svg, Text},
|
||||
|
@ -158,13 +160,8 @@ pub mod simple_message_notification {
|
|||
};
|
||||
use menu::Cancel;
|
||||
use serde::Deserialize;
|
||||
use settings::Settings;
|
||||
use std::{borrow::Cow, sync::Arc};
|
||||
|
||||
use crate::Workspace;
|
||||
|
||||
use super::Notification;
|
||||
|
||||
actions!(message_notifications, [CancelMessageNotification]);
|
||||
|
||||
#[derive(Clone, Default, Deserialize, PartialEq)]
|
||||
|
@ -240,7 +237,7 @@ pub mod simple_message_notification {
|
|||
}
|
||||
|
||||
fn render(&mut self, cx: &mut gpui::ViewContext<Self>) -> gpui::AnyElement<Self> {
|
||||
let theme = cx.global::<Settings>().theme.clone();
|
||||
let theme = theme::current(cx).clone();
|
||||
let theme = &theme.simple_message_notification;
|
||||
|
||||
enum MessageNotificationTag {}
|
||||
|
|
|
@ -2,8 +2,8 @@ mod dragged_item_receiver;
|
|||
|
||||
use super::{ItemHandle, SplitDirection};
|
||||
use crate::{
|
||||
item::WeakItemHandle, toolbar::Toolbar, Item, NewFile, NewSearch, NewTerminal, ToggleZoom,
|
||||
Workspace,
|
||||
item::WeakItemHandle, toolbar::Toolbar, AutosaveSetting, Item, NewFile, NewSearch, NewTerminal,
|
||||
ToggleZoom, Workspace, WorkspaceSettings,
|
||||
};
|
||||
use anyhow::{anyhow, Result};
|
||||
use collections::{HashMap, HashSet, VecDeque};
|
||||
|
@ -27,9 +27,18 @@ use gpui::{
|
|||
};
|
||||
use project::{Project, ProjectEntryId, ProjectPath};
|
||||
use serde::Deserialize;
|
||||
use settings::{Autosave, Settings};
|
||||
use std::{any::Any, cell::RefCell, cmp, mem, path::Path, rc::Rc};
|
||||
use theme::Theme;
|
||||
use std::{
|
||||
any::Any,
|
||||
cell::RefCell,
|
||||
cmp, mem,
|
||||
path::Path,
|
||||
rc::Rc,
|
||||
sync::{
|
||||
atomic::{AtomicUsize, Ordering},
|
||||
Arc,
|
||||
},
|
||||
};
|
||||
use theme::{Theme, ThemeSettings};
|
||||
use util::ResultExt;
|
||||
|
||||
#[derive(Clone, Deserialize, PartialEq)]
|
||||
|
@ -163,6 +172,8 @@ pub struct ItemNavHistory {
|
|||
item: Rc<dyn WeakItemHandle>,
|
||||
}
|
||||
|
||||
pub struct PaneNavHistory(Rc<RefCell<NavHistory>>);
|
||||
|
||||
struct NavHistory {
|
||||
mode: NavigationMode,
|
||||
backward_stack: VecDeque<NavigationEntry>,
|
||||
|
@ -170,6 +181,7 @@ struct NavHistory {
|
|||
closed_stack: VecDeque<NavigationEntry>,
|
||||
paths_by_item: HashMap<usize, ProjectPath>,
|
||||
pane: WeakViewHandle<Pane>,
|
||||
next_timestamp: Arc<AtomicUsize>,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
|
@ -191,6 +203,7 @@ impl Default for NavigationMode {
|
|||
pub struct NavigationEntry {
|
||||
pub item: Rc<dyn WeakItemHandle>,
|
||||
pub data: Option<Box<dyn Any>>,
|
||||
pub timestamp: usize,
|
||||
}
|
||||
|
||||
pub struct DraggedItem {
|
||||
|
@ -228,6 +241,7 @@ impl Pane {
|
|||
pub fn new(
|
||||
workspace: WeakViewHandle<Workspace>,
|
||||
background_actions: BackgroundActions,
|
||||
next_timestamp: Arc<AtomicUsize>,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) -> Self {
|
||||
let pane_view_id = cx.view_id();
|
||||
|
@ -252,6 +266,7 @@ impl Pane {
|
|||
closed_stack: Default::default(),
|
||||
paths_by_item: Default::default(),
|
||||
pane: handle.clone(),
|
||||
next_timestamp,
|
||||
})),
|
||||
toolbar: cx.add_view(|_| Toolbar::new(handle)),
|
||||
tab_bar_context_menu: TabBarContextMenu {
|
||||
|
@ -332,6 +347,10 @@ impl Pane {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn nav_history(&self) -> PaneNavHistory {
|
||||
PaneNavHistory(self.nav_history.clone())
|
||||
}
|
||||
|
||||
pub fn go_back(
|
||||
workspace: &mut Workspace,
|
||||
pane: Option<WeakViewHandle<Pane>>,
|
||||
|
@ -756,6 +775,10 @@ impl Pane {
|
|||
_: &CloseInactiveItems,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) -> Option<Task<Result<()>>> {
|
||||
if self.items.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let active_item_id = self.items[self.active_item_index].id();
|
||||
Some(self.close_items(cx, move |item_id| item_id != active_item_id))
|
||||
}
|
||||
|
@ -778,6 +801,9 @@ impl Pane {
|
|||
_: &CloseItemsToTheLeft,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) -> Option<Task<Result<()>>> {
|
||||
if self.items.is_empty() {
|
||||
return None;
|
||||
}
|
||||
let active_item_id = self.items[self.active_item_index].id();
|
||||
Some(self.close_items_to_the_left_by_id(active_item_id, cx))
|
||||
}
|
||||
|
@ -800,6 +826,9 @@ impl Pane {
|
|||
_: &CloseItemsToTheRight,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) -> Option<Task<Result<()>>> {
|
||||
if self.items.is_empty() {
|
||||
return None;
|
||||
}
|
||||
let active_item_id = self.items[self.active_item_index].id();
|
||||
Some(self.close_items_to_the_right_by_id(active_item_id, cx))
|
||||
}
|
||||
|
@ -995,8 +1024,8 @@ impl Pane {
|
|||
} else if is_dirty && (can_save || is_singleton) {
|
||||
let will_autosave = cx.read(|cx| {
|
||||
matches!(
|
||||
cx.global::<Settings>().autosave,
|
||||
Autosave::OnFocusChange | Autosave::OnWindowChange
|
||||
settings::get::<WorkspaceSettings>(cx).autosave,
|
||||
AutosaveSetting::OnFocusChange | AutosaveSetting::OnWindowChange
|
||||
) && Self::can_autosave_item(&*item, cx)
|
||||
});
|
||||
let should_save = if should_prompt_for_save && !will_autosave {
|
||||
|
@ -1220,6 +1249,25 @@ impl Pane {
|
|||
&self.toolbar
|
||||
}
|
||||
|
||||
pub fn handle_deleted_project_item(
|
||||
&mut self,
|
||||
entry_id: ProjectEntryId,
|
||||
cx: &mut ViewContext<Pane>,
|
||||
) -> Option<()> {
|
||||
let (item_index_to_delete, item_id) = self.items().enumerate().find_map(|(i, item)| {
|
||||
if item.is_singleton(cx) && item.project_entry_ids(cx).as_slice() == [entry_id] {
|
||||
Some((i, item.id()))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})?;
|
||||
|
||||
self.remove_item(item_index_to_delete, false, cx);
|
||||
self.nav_history.borrow_mut().remove_item(item_id);
|
||||
|
||||
Some(())
|
||||
}
|
||||
|
||||
fn update_toolbar(&mut self, cx: &mut ViewContext<Self>) {
|
||||
let active_item = self
|
||||
.items
|
||||
|
@ -1231,7 +1279,7 @@ impl Pane {
|
|||
}
|
||||
|
||||
fn render_tabs(&mut self, cx: &mut ViewContext<Self>) -> impl Element<Self> {
|
||||
let theme = cx.global::<Settings>().theme.clone();
|
||||
let theme = theme::current(cx).clone();
|
||||
|
||||
let pane = cx.handle().downgrade();
|
||||
let autoscroll = if mem::take(&mut self.autoscroll) {
|
||||
|
@ -1262,7 +1310,7 @@ impl Pane {
|
|||
let pane = pane.clone();
|
||||
let detail = detail.clone();
|
||||
|
||||
let theme = cx.global::<Settings>().theme.clone();
|
||||
let theme = theme::current(cx).clone();
|
||||
let mut tooltip_theme = theme.tooltip.clone();
|
||||
tooltip_theme.max_text_width = None;
|
||||
let tab_tooltip_text = item.tab_tooltip_text(cx).map(|a| a.to_string());
|
||||
|
@ -1327,7 +1375,7 @@ impl Pane {
|
|||
pane: pane.clone(),
|
||||
},
|
||||
{
|
||||
let theme = cx.global::<Settings>().theme.clone();
|
||||
let theme = theme::current(cx).clone();
|
||||
|
||||
let detail = detail.clone();
|
||||
move |dragged_item: &DraggedItem, cx: &mut ViewContext<Workspace>| {
|
||||
|
@ -1532,7 +1580,7 @@ impl Pane {
|
|||
Stack::new()
|
||||
.with_child(
|
||||
MouseEventHandler::<TabBarButton, _>::new(index, cx, |mouse_state, cx| {
|
||||
let theme = &cx.global::<Settings>().theme.workspace.tab_bar;
|
||||
let theme = &settings::get::<ThemeSettings>(cx).theme.workspace.tab_bar;
|
||||
let style = theme.pane_button.style_for(mouse_state, false);
|
||||
Svg::new(icon)
|
||||
.with_color(style.color)
|
||||
|
@ -1589,7 +1637,7 @@ impl View for Pane {
|
|||
if let Some(active_item) = self.active_item() {
|
||||
Flex::column()
|
||||
.with_child({
|
||||
let theme = cx.global::<Settings>().theme.clone();
|
||||
let theme = theme::current(cx).clone();
|
||||
|
||||
let mut stack = Stack::new();
|
||||
|
||||
|
@ -1659,7 +1707,7 @@ impl View for Pane {
|
|||
.into_any()
|
||||
} else {
|
||||
enum EmptyPane {}
|
||||
let theme = cx.global::<Settings>().theme.clone();
|
||||
let theme = theme::current(cx).clone();
|
||||
|
||||
dragged_item_receiver::<EmptyPane, _, _>(self, 0, 0, false, None, cx, |_, cx| {
|
||||
self.render_blank_pane(&theme, cx)
|
||||
|
@ -1803,6 +1851,7 @@ impl NavHistory {
|
|||
self.backward_stack.push_back(NavigationEntry {
|
||||
item,
|
||||
data: data.map(|data| Box::new(data) as Box<dyn Any>),
|
||||
timestamp: self.next_timestamp.fetch_add(1, Ordering::SeqCst),
|
||||
});
|
||||
self.forward_stack.clear();
|
||||
}
|
||||
|
@ -1813,6 +1862,7 @@ impl NavHistory {
|
|||
self.forward_stack.push_back(NavigationEntry {
|
||||
item,
|
||||
data: data.map(|data| Box::new(data) as Box<dyn Any>),
|
||||
timestamp: self.next_timestamp.fetch_add(1, Ordering::SeqCst),
|
||||
});
|
||||
}
|
||||
NavigationMode::GoingForward => {
|
||||
|
@ -1822,6 +1872,7 @@ impl NavHistory {
|
|||
self.backward_stack.push_back(NavigationEntry {
|
||||
item,
|
||||
data: data.map(|data| Box::new(data) as Box<dyn Any>),
|
||||
timestamp: self.next_timestamp.fetch_add(1, Ordering::SeqCst),
|
||||
});
|
||||
}
|
||||
NavigationMode::ClosingItem => {
|
||||
|
@ -1831,6 +1882,7 @@ impl NavHistory {
|
|||
self.closed_stack.push_back(NavigationEntry {
|
||||
item,
|
||||
data: data.map(|data| Box::new(data) as Box<dyn Any>),
|
||||
timestamp: self.next_timestamp.fetch_add(1, Ordering::SeqCst),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -1844,6 +1896,40 @@ impl NavHistory {
|
|||
});
|
||||
}
|
||||
}
|
||||
|
||||
fn remove_item(&mut self, item_id: usize) {
|
||||
self.paths_by_item.remove(&item_id);
|
||||
self.backward_stack
|
||||
.retain(|entry| entry.item.id() != item_id);
|
||||
self.forward_stack
|
||||
.retain(|entry| entry.item.id() != item_id);
|
||||
self.closed_stack.retain(|entry| entry.item.id() != item_id);
|
||||
}
|
||||
}
|
||||
|
||||
impl PaneNavHistory {
|
||||
pub fn for_each_entry(
|
||||
&self,
|
||||
cx: &AppContext,
|
||||
mut f: impl FnMut(&NavigationEntry, ProjectPath),
|
||||
) {
|
||||
let borrowed_history = self.0.borrow();
|
||||
borrowed_history
|
||||
.forward_stack
|
||||
.iter()
|
||||
.chain(borrowed_history.backward_stack.iter())
|
||||
.chain(borrowed_history.closed_stack.iter())
|
||||
.for_each(|entry| {
|
||||
if let Some(path) = borrowed_history.paths_by_item.get(&entry.item.id()) {
|
||||
f(entry, path.clone());
|
||||
} else if let Some(item) = entry.item.upgrade(cx) {
|
||||
let path = item.project_path(cx);
|
||||
if let Some(path) = path {
|
||||
f(entry, path);
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub struct PaneBackdrop<V: View> {
|
||||
|
@ -1884,7 +1970,7 @@ impl<V: View> Element<V> for PaneBackdrop<V> {
|
|||
view: &mut V,
|
||||
cx: &mut ViewContext<V>,
|
||||
) -> Self::PaintState {
|
||||
let background = cx.global::<Settings>().theme.editor.background;
|
||||
let background = theme::current(cx).editor.background;
|
||||
|
||||
let visible_bounds = bounds.intersection(visible_bounds).unwrap_or_default();
|
||||
|
||||
|
@ -1948,10 +2034,11 @@ mod tests {
|
|||
use crate::item::test::{TestItem, TestProjectItem};
|
||||
use gpui::{executor::Deterministic, TestAppContext};
|
||||
use project::FakeFs;
|
||||
use settings::SettingsStore;
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_remove_active_empty(cx: &mut TestAppContext) {
|
||||
Settings::test_async(cx);
|
||||
init_test(cx);
|
||||
let fs = FakeFs::new(cx.background());
|
||||
|
||||
let project = Project::test(fs, None, cx).await;
|
||||
|
@ -1966,7 +2053,7 @@ mod tests {
|
|||
#[gpui::test]
|
||||
async fn test_add_item_with_new_item(cx: &mut TestAppContext) {
|
||||
cx.foreground().forbid_parking();
|
||||
Settings::test_async(cx);
|
||||
init_test(cx);
|
||||
let fs = FakeFs::new(cx.background());
|
||||
|
||||
let project = Project::test(fs, None, cx).await;
|
||||
|
@ -2054,7 +2141,7 @@ mod tests {
|
|||
#[gpui::test]
|
||||
async fn test_add_item_with_existing_item(cx: &mut TestAppContext) {
|
||||
cx.foreground().forbid_parking();
|
||||
Settings::test_async(cx);
|
||||
init_test(cx);
|
||||
let fs = FakeFs::new(cx.background());
|
||||
|
||||
let project = Project::test(fs, None, cx).await;
|
||||
|
@ -2130,7 +2217,7 @@ mod tests {
|
|||
#[gpui::test]
|
||||
async fn test_add_item_with_same_project_entries(cx: &mut TestAppContext) {
|
||||
cx.foreground().forbid_parking();
|
||||
Settings::test_async(cx);
|
||||
init_test(cx);
|
||||
let fs = FakeFs::new(cx.background());
|
||||
|
||||
let project = Project::test(fs, None, cx).await;
|
||||
|
@ -2239,7 +2326,7 @@ mod tests {
|
|||
|
||||
#[gpui::test]
|
||||
async fn test_remove_item_ordering(deterministic: Arc<Deterministic>, cx: &mut TestAppContext) {
|
||||
Settings::test_async(cx);
|
||||
init_test(cx);
|
||||
let fs = FakeFs::new(cx.background());
|
||||
|
||||
let project = Project::test(fs, None, cx).await;
|
||||
|
@ -2278,7 +2365,7 @@ mod tests {
|
|||
|
||||
#[gpui::test]
|
||||
async fn test_close_inactive_items(deterministic: Arc<Deterministic>, cx: &mut TestAppContext) {
|
||||
Settings::test_async(cx);
|
||||
init_test(cx);
|
||||
let fs = FakeFs::new(cx.background());
|
||||
|
||||
let project = Project::test(fs, None, cx).await;
|
||||
|
@ -2297,7 +2384,7 @@ mod tests {
|
|||
|
||||
#[gpui::test]
|
||||
async fn test_close_clean_items(deterministic: Arc<Deterministic>, cx: &mut TestAppContext) {
|
||||
Settings::test_async(cx);
|
||||
init_test(cx);
|
||||
let fs = FakeFs::new(cx.background());
|
||||
|
||||
let project = Project::test(fs, None, cx).await;
|
||||
|
@ -2322,7 +2409,7 @@ mod tests {
|
|||
deterministic: Arc<Deterministic>,
|
||||
cx: &mut TestAppContext,
|
||||
) {
|
||||
Settings::test_async(cx);
|
||||
init_test(cx);
|
||||
let fs = FakeFs::new(cx.background());
|
||||
|
||||
let project = Project::test(fs, None, cx).await;
|
||||
|
@ -2344,7 +2431,7 @@ mod tests {
|
|||
deterministic: Arc<Deterministic>,
|
||||
cx: &mut TestAppContext,
|
||||
) {
|
||||
Settings::test_async(cx);
|
||||
init_test(cx);
|
||||
let fs = FakeFs::new(cx.background());
|
||||
|
||||
let project = Project::test(fs, None, cx).await;
|
||||
|
@ -2363,7 +2450,7 @@ mod tests {
|
|||
|
||||
#[gpui::test]
|
||||
async fn test_close_all_items(deterministic: Arc<Deterministic>, cx: &mut TestAppContext) {
|
||||
Settings::test_async(cx);
|
||||
init_test(cx);
|
||||
let fs = FakeFs::new(cx.background());
|
||||
|
||||
let project = Project::test(fs, None, cx).await;
|
||||
|
@ -2381,6 +2468,14 @@ mod tests {
|
|||
assert_item_labels(&pane, [], cx);
|
||||
}
|
||||
|
||||
fn init_test(cx: &mut TestAppContext) {
|
||||
cx.update(|cx| {
|
||||
cx.set_global(SettingsStore::test(cx));
|
||||
theme::init((), cx);
|
||||
crate::init_settings(cx);
|
||||
});
|
||||
}
|
||||
|
||||
fn add_labeled_item(
|
||||
workspace: &ViewHandle<Workspace>,
|
||||
pane: &ViewHandle<Pane>,
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
use super::DraggedItem;
|
||||
use crate::{Pane, SplitDirection, Workspace};
|
||||
use drag_and_drop::DragAndDrop;
|
||||
use gpui::{
|
||||
color::Color,
|
||||
|
@ -8,11 +10,6 @@ use gpui::{
|
|||
AppContext, Element, EventContext, MouseState, Quad, View, ViewContext, WeakViewHandle,
|
||||
};
|
||||
use project::ProjectEntryId;
|
||||
use settings::Settings;
|
||||
|
||||
use crate::{Pane, SplitDirection, Workspace};
|
||||
|
||||
use super::DraggedItem;
|
||||
|
||||
pub fn dragged_item_receiver<Tag, D, F>(
|
||||
pane: &Pane,
|
||||
|
@ -234,8 +231,5 @@ fn drop_split_direction(
|
|||
}
|
||||
|
||||
fn overlay_color(cx: &AppContext) -> Color {
|
||||
cx.global::<Settings>()
|
||||
.theme
|
||||
.workspace
|
||||
.drop_target_overlay_color
|
||||
theme::current(cx).workspace.drop_target_overlay_color
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use std::sync::Arc;
|
||||
|
||||
use crate::{AppState, FollowerStatesByLeader, Pane, Workspace};
|
||||
use crate::{AppState, FollowerStatesByLeader, Pane, Workspace, WorkspaceSettings};
|
||||
use anyhow::{anyhow, Result};
|
||||
use call::{ActiveCall, ParticipantLocation};
|
||||
use gpui::{
|
||||
|
@ -11,7 +11,6 @@ use gpui::{
|
|||
};
|
||||
use project::Project;
|
||||
use serde::Deserialize;
|
||||
use settings::Settings;
|
||||
use theme::Theme;
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
|
@ -391,7 +390,7 @@ impl PaneAxis {
|
|||
.with_children(self.members.iter().enumerate().map(|(ix, member)| {
|
||||
let mut flex = 1.0;
|
||||
if member.contains(active_pane) {
|
||||
flex = cx.global::<Settings>().active_pane_magnification;
|
||||
flex = settings::get::<WorkspaceSettings>(cx).active_pane_magnification;
|
||||
}
|
||||
|
||||
let mut member = member.render(
|
||||
|
|
|
@ -497,10 +497,8 @@ impl WorkspaceDb {
|
|||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
||||
use db::open_test_db;
|
||||
|
||||
use super::*;
|
||||
use db::open_test_db;
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_next_id_stability() {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use crate::{ItemDeserializers, Member, Pane, PaneAxis, Workspace, WorkspaceId};
|
||||
use crate::{item::ItemHandle, ItemDeserializers, Member, Pane, PaneAxis, Workspace, WorkspaceId};
|
||||
use anyhow::{anyhow, Context, Result};
|
||||
use async_recursion::async_recursion;
|
||||
use db::sqlez::{
|
||||
|
@ -150,17 +150,23 @@ impl SerializedPaneGroup {
|
|||
workspace_id: WorkspaceId,
|
||||
workspace: &WeakViewHandle<Workspace>,
|
||||
cx: &mut AsyncAppContext,
|
||||
) -> Option<(Member, Option<ViewHandle<Pane>>)> {
|
||||
) -> Option<(
|
||||
Member,
|
||||
Option<ViewHandle<Pane>>,
|
||||
Vec<Option<Box<dyn ItemHandle>>>,
|
||||
)> {
|
||||
match self {
|
||||
SerializedPaneGroup::Group { axis, children } => {
|
||||
let mut current_active_pane = None;
|
||||
let mut members = Vec::new();
|
||||
let mut items = Vec::new();
|
||||
for child in children {
|
||||
if let Some((new_member, active_pane)) = child
|
||||
if let Some((new_member, active_pane, new_items)) = child
|
||||
.deserialize(project, workspace_id, workspace, cx)
|
||||
.await
|
||||
{
|
||||
members.push(new_member);
|
||||
items.extend(new_items);
|
||||
current_active_pane = current_active_pane.or(active_pane);
|
||||
}
|
||||
}
|
||||
|
@ -170,7 +176,7 @@ impl SerializedPaneGroup {
|
|||
}
|
||||
|
||||
if members.len() == 1 {
|
||||
return Some((members.remove(0), current_active_pane));
|
||||
return Some((members.remove(0), current_active_pane, items));
|
||||
}
|
||||
|
||||
Some((
|
||||
|
@ -179,6 +185,7 @@ impl SerializedPaneGroup {
|
|||
members,
|
||||
}),
|
||||
current_active_pane,
|
||||
items,
|
||||
))
|
||||
}
|
||||
SerializedPaneGroup::Pane(serialized_pane) => {
|
||||
|
@ -186,7 +193,7 @@ impl SerializedPaneGroup {
|
|||
.update(cx, |workspace, cx| workspace.add_pane(cx).downgrade())
|
||||
.log_err()?;
|
||||
let active = serialized_pane.active;
|
||||
serialized_pane
|
||||
let new_items = serialized_pane
|
||||
.deserialize_to(project, &pane, workspace_id, workspace, cx)
|
||||
.await
|
||||
.log_err()?;
|
||||
|
@ -196,7 +203,7 @@ impl SerializedPaneGroup {
|
|||
.log_err()?
|
||||
{
|
||||
let pane = pane.upgrade(cx)?;
|
||||
Some((Member::Pane(pane.clone()), active.then(|| pane)))
|
||||
Some((Member::Pane(pane.clone()), active.then(|| pane), new_items))
|
||||
} else {
|
||||
let pane = pane.upgrade(cx)?;
|
||||
workspace
|
||||
|
@ -227,7 +234,8 @@ impl SerializedPane {
|
|||
workspace_id: WorkspaceId,
|
||||
workspace: &WeakViewHandle<Workspace>,
|
||||
cx: &mut AsyncAppContext,
|
||||
) -> Result<()> {
|
||||
) -> Result<Vec<Option<Box<dyn ItemHandle>>>> {
|
||||
let mut items = Vec::new();
|
||||
let mut active_item_index = None;
|
||||
for (index, item) in self.children.iter().enumerate() {
|
||||
let project = project.clone();
|
||||
|
@ -245,6 +253,8 @@ impl SerializedPane {
|
|||
.await
|
||||
.log_err();
|
||||
|
||||
items.push(item_handle.clone());
|
||||
|
||||
if let Some(item_handle) = item_handle {
|
||||
workspace.update(cx, |workspace, cx| {
|
||||
let pane_handle = pane_handle
|
||||
|
@ -266,7 +276,7 @@ impl SerializedPane {
|
|||
})?;
|
||||
}
|
||||
|
||||
anyhow::Ok(())
|
||||
anyhow::Ok(items)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -330,40 +340,3 @@ impl Column for SerializedItem {
|
|||
))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use db::sqlez::connection::Connection;
|
||||
|
||||
// use super::WorkspaceLocation;
|
||||
|
||||
#[test]
|
||||
fn test_workspace_round_trips() {
|
||||
let _db = Connection::open_memory(Some("workspace_id_round_trips"));
|
||||
|
||||
todo!();
|
||||
// db.exec(indoc::indoc! {"
|
||||
// CREATE TABLE workspace_id_test(
|
||||
// workspace_id INTEGER,
|
||||
// dock_anchor TEXT
|
||||
// );"})
|
||||
// .unwrap()()
|
||||
// .unwrap();
|
||||
|
||||
// let workspace_id: WorkspaceLocation = WorkspaceLocation::from(&["\test2", "\test1"]);
|
||||
|
||||
// db.exec_bound("INSERT INTO workspace_id_test(workspace_id, dock_anchor) VALUES (?,?)")
|
||||
// .unwrap()((&workspace_id, DockAnchor::Bottom))
|
||||
// .unwrap();
|
||||
|
||||
// assert_eq!(
|
||||
// db.select_row("SELECT workspace_id, dock_anchor FROM workspace_id_test LIMIT 1")
|
||||
// .unwrap()()
|
||||
// .unwrap(),
|
||||
// Some((
|
||||
// WorkspaceLocation::from(&["\test1", "\test2"]),
|
||||
// DockAnchor::Bottom
|
||||
// ))
|
||||
// );
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,7 +12,6 @@ use gpui::{
|
|||
platform::MouseButton,
|
||||
AppContext, Entity, Task, View, ViewContext,
|
||||
};
|
||||
use settings::Settings;
|
||||
use smallvec::SmallVec;
|
||||
use std::{
|
||||
borrow::Cow,
|
||||
|
@ -88,7 +87,7 @@ impl View for SharedScreen {
|
|||
}
|
||||
})
|
||||
.contained()
|
||||
.with_style(cx.global::<Settings>().theme.shared_screen)
|
||||
.with_style(theme::current(cx).shared_screen)
|
||||
})
|
||||
.on_down(MouseButton::Left, |_, _, cx| cx.focus_parent())
|
||||
.into_any()
|
||||
|
|
|
@ -11,7 +11,6 @@ use gpui::{
|
|||
AnyElement, AnyViewHandle, Entity, LayoutContext, SceneBuilder, SizeConstraint, Subscription,
|
||||
View, ViewContext, ViewHandle, WindowContext,
|
||||
};
|
||||
use settings::Settings;
|
||||
|
||||
pub trait StatusItemView: View {
|
||||
fn set_active_pane_item(
|
||||
|
@ -47,7 +46,7 @@ impl View for StatusBar {
|
|||
}
|
||||
|
||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
|
||||
let theme = &cx.global::<Settings>().theme.workspace.status_bar;
|
||||
let theme = &theme::current(cx).workspace.status_bar;
|
||||
|
||||
StatusBarElement {
|
||||
left: Flex::row()
|
||||
|
|
|
@ -3,7 +3,6 @@ use gpui::{
|
|||
elements::*, platform::CursorStyle, platform::MouseButton, Action, AnyElement, AnyViewHandle,
|
||||
AppContext, Entity, View, ViewContext, ViewHandle, WeakViewHandle, WindowContext,
|
||||
};
|
||||
use settings::Settings;
|
||||
|
||||
pub trait ToolbarItemView: View {
|
||||
fn set_active_pane_item(
|
||||
|
@ -68,7 +67,7 @@ impl View for Toolbar {
|
|||
}
|
||||
|
||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
|
||||
let theme = &cx.global::<Settings>().theme.workspace.toolbar;
|
||||
let theme = &theme::current(cx).workspace.toolbar;
|
||||
|
||||
let mut primary_left_items = Vec::new();
|
||||
let mut primary_right_items = Vec::new();
|
||||
|
@ -131,7 +130,7 @@ impl View for Toolbar {
|
|||
let height = theme.height * primary_items_row_count as f32;
|
||||
let nav_button_height = theme.height;
|
||||
let button_style = theme.nav_button;
|
||||
let tooltip_style = cx.global::<Settings>().theme.tooltip.clone();
|
||||
let tooltip_style = theme::current(cx).tooltip.clone();
|
||||
|
||||
Flex::column()
|
||||
.with_child(
|
||||
|
|
|
@ -12,6 +12,7 @@ pub mod searchable;
|
|||
pub mod shared_screen;
|
||||
mod status_bar;
|
||||
mod toolbar;
|
||||
mod workspace_settings;
|
||||
|
||||
use anyhow::{anyhow, Context, Result};
|
||||
use assets::Assets;
|
||||
|
@ -44,6 +45,7 @@ use gpui::{
|
|||
WindowContext,
|
||||
};
|
||||
use item::{FollowableItem, FollowableItemHandle, Item, ItemHandle, ProjectItem};
|
||||
use itertools::Itertools;
|
||||
use language::{LanguageRegistry, Rope};
|
||||
use std::{
|
||||
any::TypeId,
|
||||
|
@ -52,7 +54,7 @@ use std::{
|
|||
future::Future,
|
||||
path::{Path, PathBuf},
|
||||
str,
|
||||
sync::Arc,
|
||||
sync::{atomic::AtomicUsize, Arc},
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
|
@ -75,13 +77,13 @@ pub use persistence::{
|
|||
use postage::prelude::Stream;
|
||||
use project::{Project, ProjectEntryId, ProjectPath, Worktree, WorktreeId};
|
||||
use serde::Deserialize;
|
||||
use settings::{Autosave, Settings};
|
||||
use shared_screen::SharedScreen;
|
||||
use status_bar::StatusBar;
|
||||
pub use status_bar::StatusItemView;
|
||||
use theme::{Theme, ThemeRegistry};
|
||||
use theme::Theme;
|
||||
pub use toolbar::{ToolbarItemLocation, ToolbarItemView};
|
||||
use util::{paths, ResultExt};
|
||||
use util::{async_iife, paths, ResultExt};
|
||||
pub use workspace_settings::{AutosaveSetting, GitGutterSetting, WorkspaceSettings};
|
||||
|
||||
lazy_static! {
|
||||
static ref ZED_WINDOW_SIZE: Option<Vector2F> = env::var("ZED_WINDOW_SIZE")
|
||||
|
@ -184,7 +186,12 @@ pub type WorkspaceId = i64;
|
|||
|
||||
impl_actions!(workspace, [ActivatePane]);
|
||||
|
||||
pub fn init_settings(cx: &mut AppContext) {
|
||||
settings::register::<WorkspaceSettings>(cx);
|
||||
}
|
||||
|
||||
pub fn init(app_state: Arc<AppState>, cx: &mut AppContext) {
|
||||
init_settings(cx);
|
||||
pane::init(cx);
|
||||
notifications::init(cx);
|
||||
|
||||
|
@ -234,7 +241,6 @@ pub fn init(app_state: Arc<AppState>, cx: &mut AppContext) {
|
|||
},
|
||||
);
|
||||
cx.add_action(Workspace::toggle_panel);
|
||||
cx.add_action(Workspace::focus_center);
|
||||
cx.add_action(|workspace: &mut Workspace, _: &ActivatePreviousPane, cx| {
|
||||
workspace.activate_previous_pane(cx)
|
||||
});
|
||||
|
@ -270,7 +276,7 @@ pub fn init(app_state: Arc<AppState>, cx: &mut AppContext) {
|
|||
cx.add_action(
|
||||
move |_: &mut Workspace, _: &OpenSettings, cx: &mut ViewContext<Workspace>| {
|
||||
create_and_open_local_file(&paths::SETTINGS, cx, || {
|
||||
Settings::initial_user_settings_content(&Assets)
|
||||
settings::initial_user_settings_content(&Assets)
|
||||
.as_ref()
|
||||
.into()
|
||||
})
|
||||
|
@ -355,7 +361,6 @@ pub fn register_deserializable_item<I: Item>(cx: &mut AppContext) {
|
|||
|
||||
pub struct AppState {
|
||||
pub languages: Arc<LanguageRegistry>,
|
||||
pub themes: Arc<ThemeRegistry>,
|
||||
pub client: Arc<client::Client>,
|
||||
pub user_store: ModelHandle<client::UserStore>,
|
||||
pub fs: Arc<dyn fs::Fs>,
|
||||
|
@ -369,18 +374,24 @@ pub struct AppState {
|
|||
impl AppState {
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
pub fn test(cx: &mut AppContext) -> Arc<Self> {
|
||||
let settings = Settings::test(cx);
|
||||
cx.set_global(settings);
|
||||
use settings::SettingsStore;
|
||||
|
||||
if !cx.has_global::<SettingsStore>() {
|
||||
cx.set_global(SettingsStore::test(cx));
|
||||
}
|
||||
|
||||
let fs = fs::FakeFs::new(cx.background().clone());
|
||||
let languages = Arc::new(LanguageRegistry::test());
|
||||
let http_client = util::http::FakeHttpClient::with_404_response();
|
||||
let client = Client::new(http_client.clone(), cx);
|
||||
let user_store = cx.add_model(|cx| UserStore::new(client.clone(), http_client, cx));
|
||||
let themes = ThemeRegistry::new((), cx.font_cache().clone());
|
||||
|
||||
theme::init((), cx);
|
||||
client::init(&client, cx);
|
||||
crate::init_settings(cx);
|
||||
|
||||
Arc::new(Self {
|
||||
client,
|
||||
themes,
|
||||
fs,
|
||||
languages,
|
||||
user_store,
|
||||
|
@ -469,6 +480,7 @@ pub struct Workspace {
|
|||
_subscriptions: Vec<Subscription>,
|
||||
_apply_leader_updates: Task<Result<()>>,
|
||||
_observe_current_user: Task<Result<()>>,
|
||||
pane_history_timestamp: Arc<AtomicUsize>,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
|
||||
|
@ -492,7 +504,6 @@ struct FollowerState {
|
|||
|
||||
impl Workspace {
|
||||
pub fn new(
|
||||
serialized_workspace: Option<SerializedWorkspace>,
|
||||
workspace_id: WorkspaceId,
|
||||
project: ModelHandle<Project>,
|
||||
app_state: Arc<AppState>,
|
||||
|
@ -524,6 +535,14 @@ impl Workspace {
|
|||
cx.remove_window();
|
||||
}
|
||||
|
||||
project::Event::DeletedEntry(entry_id) => {
|
||||
for pane in this.panes.iter() {
|
||||
pane.update(cx, |pane, cx| {
|
||||
pane.handle_deleted_project_item(*entry_id, cx)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
_ => {}
|
||||
}
|
||||
cx.notify()
|
||||
|
@ -531,9 +550,16 @@ impl Workspace {
|
|||
.detach();
|
||||
|
||||
let weak_handle = cx.weak_handle();
|
||||
let pane_history_timestamp = Arc::new(AtomicUsize::new(0));
|
||||
|
||||
let center_pane =
|
||||
cx.add_view(|cx| Pane::new(weak_handle.clone(), app_state.background_actions, cx));
|
||||
let center_pane = cx.add_view(|cx| {
|
||||
Pane::new(
|
||||
weak_handle.clone(),
|
||||
app_state.background_actions,
|
||||
pane_history_timestamp.clone(),
|
||||
cx,
|
||||
)
|
||||
});
|
||||
cx.subscribe(¢er_pane, Self::handle_pane_event).detach();
|
||||
cx.focus(¢er_pane);
|
||||
cx.emit(Event::PaneAdded(center_pane.clone()));
|
||||
|
@ -658,16 +684,10 @@ impl Workspace {
|
|||
_apply_leader_updates,
|
||||
leader_updates_tx,
|
||||
_subscriptions: subscriptions,
|
||||
pane_history_timestamp,
|
||||
};
|
||||
this.project_remote_id_changed(project.read(cx).remote_id(), cx);
|
||||
cx.defer(|this, cx| this.update_window_title(cx));
|
||||
|
||||
if let Some(serialized_workspace) = serialized_workspace {
|
||||
cx.defer(move |_, cx| {
|
||||
Self::load_from_serialized_workspace(weak_handle, serialized_workspace, cx)
|
||||
});
|
||||
}
|
||||
|
||||
this
|
||||
}
|
||||
|
||||
|
@ -689,18 +709,15 @@ impl Workspace {
|
|||
);
|
||||
|
||||
cx.spawn(|mut cx| async move {
|
||||
let mut serialized_workspace =
|
||||
persistence::DB.workspace_for_roots(&abs_paths.as_slice());
|
||||
let serialized_workspace = persistence::DB.workspace_for_roots(&abs_paths.as_slice());
|
||||
|
||||
let paths_to_open = serialized_workspace
|
||||
.as_ref()
|
||||
.map(|workspace| workspace.location.paths())
|
||||
.unwrap_or(Arc::new(abs_paths));
|
||||
let paths_to_open = Arc::new(abs_paths);
|
||||
|
||||
// Get project paths for all of the abs_paths
|
||||
let mut worktree_roots: HashSet<Arc<Path>> = Default::default();
|
||||
let mut project_paths = Vec::new();
|
||||
for path in paths_to_open.iter() {
|
||||
let mut project_paths: Vec<(PathBuf, Option<ProjectPath>)> =
|
||||
Vec::with_capacity(paths_to_open.len());
|
||||
for path in paths_to_open.iter().cloned() {
|
||||
if let Some((worktree, project_entry)) = cx
|
||||
.update(|cx| {
|
||||
Workspace::project_path_for_path(project_handle.clone(), &path, true, cx)
|
||||
|
@ -709,9 +726,9 @@ impl Workspace {
|
|||
.log_err()
|
||||
{
|
||||
worktree_roots.insert(worktree.read_with(&mut cx, |tree, _| tree.abs_path()));
|
||||
project_paths.push(Some(project_entry));
|
||||
project_paths.push((path, Some(project_entry)));
|
||||
} else {
|
||||
project_paths.push(None);
|
||||
project_paths.push((path, None));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -731,21 +748,13 @@ impl Workspace {
|
|||
))
|
||||
});
|
||||
|
||||
let was_deserialized = serialized_workspace.is_some();
|
||||
let build_workspace = |cx: &mut ViewContext<Workspace>| {
|
||||
Workspace::new(workspace_id, project_handle.clone(), app_state.clone(), cx)
|
||||
};
|
||||
|
||||
let workspace = requesting_window_id
|
||||
.and_then(|window_id| {
|
||||
cx.update(|cx| {
|
||||
cx.replace_root_view(window_id, |cx| {
|
||||
Workspace::new(
|
||||
serialized_workspace.take(),
|
||||
workspace_id,
|
||||
project_handle.clone(),
|
||||
app_state.clone(),
|
||||
cx,
|
||||
)
|
||||
})
|
||||
})
|
||||
cx.update(|cx| cx.replace_root_view(window_id, |cx| build_workspace(cx)))
|
||||
})
|
||||
.unwrap_or_else(|| {
|
||||
let (bounds, display) = if let Some(bounds) = window_bounds_override {
|
||||
|
@ -783,22 +792,14 @@ impl Workspace {
|
|||
// Use the serialized workspace to construct the new window
|
||||
cx.add_window(
|
||||
(app_state.build_window_options)(bounds, display, cx.platform().as_ref()),
|
||||
|cx| {
|
||||
Workspace::new(
|
||||
serialized_workspace,
|
||||
workspace_id,
|
||||
project_handle.clone(),
|
||||
app_state.clone(),
|
||||
cx,
|
||||
)
|
||||
},
|
||||
|cx| build_workspace(cx),
|
||||
)
|
||||
.1
|
||||
});
|
||||
|
||||
(app_state.initialize_workspace)(
|
||||
workspace.downgrade(),
|
||||
was_deserialized,
|
||||
serialized_workspace.is_some(),
|
||||
app_state.clone(),
|
||||
cx.clone(),
|
||||
)
|
||||
|
@ -809,37 +810,14 @@ impl Workspace {
|
|||
|
||||
let workspace = workspace.downgrade();
|
||||
notify_if_database_failed(&workspace, &mut cx);
|
||||
|
||||
// Call open path for each of the project paths
|
||||
// (this will bring them to the front if they were in the serialized workspace)
|
||||
debug_assert!(paths_to_open.len() == project_paths.len());
|
||||
let tasks = paths_to_open
|
||||
.iter()
|
||||
.cloned()
|
||||
.zip(project_paths.into_iter())
|
||||
.map(|(abs_path, project_path)| {
|
||||
let workspace = workspace.clone();
|
||||
cx.spawn(|mut cx| {
|
||||
let fs = app_state.fs.clone();
|
||||
async move {
|
||||
let project_path = project_path?;
|
||||
if fs.is_file(&abs_path).await {
|
||||
Some(
|
||||
workspace
|
||||
.update(&mut cx, |workspace, cx| {
|
||||
workspace.open_path(project_path, None, true, cx)
|
||||
})
|
||||
.log_err()?
|
||||
.await,
|
||||
)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
let opened_items = futures::future::join_all(tasks.into_iter()).await;
|
||||
let opened_items = open_items(
|
||||
serialized_workspace,
|
||||
&workspace,
|
||||
project_paths,
|
||||
app_state,
|
||||
cx,
|
||||
)
|
||||
.await;
|
||||
|
||||
(workspace, opened_items)
|
||||
})
|
||||
|
@ -936,6 +914,39 @@ impl Workspace {
|
|||
&self.project
|
||||
}
|
||||
|
||||
pub fn recent_navigation_history(
|
||||
&self,
|
||||
limit: Option<usize>,
|
||||
cx: &AppContext,
|
||||
) -> Vec<ProjectPath> {
|
||||
let mut history: HashMap<ProjectPath, usize> = HashMap::default();
|
||||
for pane in &self.panes {
|
||||
let pane = pane.read(cx);
|
||||
pane.nav_history()
|
||||
.for_each_entry(cx, |entry, project_path| {
|
||||
let timestamp = entry.timestamp;
|
||||
match history.entry(project_path) {
|
||||
hash_map::Entry::Occupied(mut entry) => {
|
||||
if ×tamp > entry.get() {
|
||||
entry.insert(timestamp);
|
||||
}
|
||||
}
|
||||
hash_map::Entry::Vacant(entry) => {
|
||||
entry.insert(timestamp);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
history
|
||||
.into_iter()
|
||||
.sorted_by_key(|(_, timestamp)| *timestamp)
|
||||
.map(|(project_path, _)| project_path)
|
||||
.rev()
|
||||
.take(limit.unwrap_or(usize::MAX))
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn client(&self) -> &Client {
|
||||
&self.app_state.client
|
||||
}
|
||||
|
@ -1193,6 +1204,8 @@ impl Workspace {
|
|||
visible: bool,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) -> Task<Vec<Option<Result<Box<dyn ItemHandle>, anyhow::Error>>>> {
|
||||
log::info!("open paths {:?}", abs_paths);
|
||||
|
||||
let fs = self.app_state.fs.clone();
|
||||
|
||||
// Sort the paths to ensure we add worktrees for parents before their children.
|
||||
|
@ -1527,14 +1540,15 @@ impl Workspace {
|
|||
cx.notify();
|
||||
}
|
||||
|
||||
pub fn focus_center(&mut self, _: &menu::Cancel, cx: &mut ViewContext<Self>) {
|
||||
cx.focus_self();
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
fn add_pane(&mut self, cx: &mut ViewContext<Self>) -> ViewHandle<Pane> {
|
||||
let pane =
|
||||
cx.add_view(|cx| Pane::new(self.weak_handle(), self.app_state.background_actions, cx));
|
||||
let pane = cx.add_view(|cx| {
|
||||
Pane::new(
|
||||
self.weak_handle(),
|
||||
self.app_state.background_actions,
|
||||
self.pane_history_timestamp.clone(),
|
||||
cx,
|
||||
)
|
||||
});
|
||||
cx.subscribe(&pane, Self::handle_pane_event).detach();
|
||||
self.panes.push(pane.clone());
|
||||
cx.focus(&pane);
|
||||
|
@ -2103,7 +2117,7 @@ impl Workspace {
|
|||
enum DisconnectedOverlay {}
|
||||
Some(
|
||||
MouseEventHandler::<DisconnectedOverlay, _>::new(0, cx, |_, cx| {
|
||||
let theme = &cx.global::<Settings>().theme;
|
||||
let theme = &theme::current(cx);
|
||||
Label::new(
|
||||
"Your connection to the remote project has been lost.",
|
||||
theme.workspace.disconnected_overlay.text.clone(),
|
||||
|
@ -2474,8 +2488,8 @@ impl Workspace {
|
|||
item.workspace_deactivated(cx);
|
||||
}
|
||||
if matches!(
|
||||
cx.global::<Settings>().autosave,
|
||||
Autosave::OnWindowChange | Autosave::OnFocusChange
|
||||
settings::get::<WorkspaceSettings>(cx).autosave,
|
||||
AutosaveSetting::OnWindowChange | AutosaveSetting::OnFocusChange
|
||||
) {
|
||||
for item in pane.items() {
|
||||
Pane::autosave_item(item.as_ref(), self.project.clone(), cx)
|
||||
|
@ -2659,91 +2673,125 @@ impl Workspace {
|
|||
}
|
||||
}
|
||||
|
||||
fn load_from_serialized_workspace(
|
||||
pub(crate) fn load_workspace(
|
||||
workspace: WeakViewHandle<Workspace>,
|
||||
serialized_workspace: SerializedWorkspace,
|
||||
paths_to_open: Vec<Option<ProjectPath>>,
|
||||
cx: &mut AppContext,
|
||||
) {
|
||||
) -> Task<Vec<Option<Result<Box<dyn ItemHandle>, anyhow::Error>>>> {
|
||||
cx.spawn(|mut cx| async move {
|
||||
let (project, old_center_pane) = workspace.read_with(&cx, |workspace, _| {
|
||||
(
|
||||
workspace.project().clone(),
|
||||
workspace.last_active_center_pane.clone(),
|
||||
)
|
||||
})?;
|
||||
let result = async_iife! {{
|
||||
let (project, old_center_pane) =
|
||||
workspace.read_with(&cx, |workspace, _| {
|
||||
(
|
||||
workspace.project().clone(),
|
||||
workspace.last_active_center_pane.clone(),
|
||||
)
|
||||
})?;
|
||||
|
||||
// Traverse the splits tree and add to things
|
||||
let center_group = serialized_workspace
|
||||
.center_group
|
||||
.deserialize(&project, serialized_workspace.id, &workspace, &mut cx)
|
||||
.await;
|
||||
|
||||
// Remove old panes from workspace panes list
|
||||
workspace.update(&mut cx, |workspace, cx| {
|
||||
if let Some((center_group, active_pane)) = center_group {
|
||||
workspace.remove_panes(workspace.center.root.clone(), cx);
|
||||
|
||||
// Swap workspace center group
|
||||
workspace.center = PaneGroup::with_root(center_group);
|
||||
|
||||
// Change the focus to the workspace first so that we retrigger focus in on the pane.
|
||||
cx.focus_self();
|
||||
|
||||
if let Some(active_pane) = active_pane {
|
||||
cx.focus(&active_pane);
|
||||
} else {
|
||||
cx.focus(workspace.panes.last().unwrap());
|
||||
}
|
||||
} else {
|
||||
let old_center_handle = old_center_pane.and_then(|weak| weak.upgrade(cx));
|
||||
if let Some(old_center_handle) = old_center_handle {
|
||||
cx.focus(&old_center_handle)
|
||||
} else {
|
||||
cx.focus_self()
|
||||
}
|
||||
let mut center_items = None;
|
||||
let mut center_group = None;
|
||||
// Traverse the splits tree and add to things
|
||||
if let Some((group, active_pane, items)) = serialized_workspace
|
||||
.center_group
|
||||
.deserialize(&project, serialized_workspace.id, &workspace, &mut cx)
|
||||
.await {
|
||||
center_items = Some(items);
|
||||
center_group = Some((group, active_pane))
|
||||
}
|
||||
|
||||
let docks = serialized_workspace.docks;
|
||||
workspace.left_dock.update(cx, |dock, cx| {
|
||||
dock.set_open(docks.left.visible, cx);
|
||||
if let Some(active_panel) = docks.left.active_panel {
|
||||
if let Some(ix) = dock.panel_index_for_ui_name(&active_panel, cx) {
|
||||
dock.activate_panel(ix, cx);
|
||||
}
|
||||
}
|
||||
});
|
||||
workspace.right_dock.update(cx, |dock, cx| {
|
||||
dock.set_open(docks.right.visible, cx);
|
||||
if let Some(active_panel) = docks.right.active_panel {
|
||||
if let Some(ix) = dock.panel_index_for_ui_name(&active_panel, cx) {
|
||||
dock.activate_panel(ix, cx);
|
||||
}
|
||||
}
|
||||
});
|
||||
workspace.bottom_dock.update(cx, |dock, cx| {
|
||||
dock.set_open(docks.bottom.visible, cx);
|
||||
if let Some(active_panel) = docks.bottom.active_panel {
|
||||
if let Some(ix) = dock.panel_index_for_ui_name(&active_panel, cx) {
|
||||
dock.activate_panel(ix, cx);
|
||||
}
|
||||
}
|
||||
let resulting_list = cx.read(|cx| {
|
||||
let mut opened_items = center_items
|
||||
.unwrap_or_default()
|
||||
.into_iter()
|
||||
.filter_map(|item| {
|
||||
let item = item?;
|
||||
let project_path = item.project_path(cx)?;
|
||||
Some((project_path, item))
|
||||
})
|
||||
.collect::<HashMap<_, _>>();
|
||||
|
||||
paths_to_open
|
||||
.into_iter()
|
||||
.map(|path_to_open| {
|
||||
path_to_open.map(|path_to_open| {
|
||||
Ok(opened_items.remove(&path_to_open))
|
||||
})
|
||||
.transpose()
|
||||
.map(|item| item.flatten())
|
||||
.transpose()
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
});
|
||||
|
||||
cx.notify();
|
||||
})?;
|
||||
// Remove old panes from workspace panes list
|
||||
workspace.update(&mut cx, |workspace, cx| {
|
||||
if let Some((center_group, active_pane)) = center_group {
|
||||
workspace.remove_panes(workspace.center.root.clone(), cx);
|
||||
|
||||
// Serialize ourself to make sure our timestamps and any pane / item changes are replicated
|
||||
workspace.read_with(&cx, |workspace, cx| workspace.serialize_workspace(cx))?;
|
||||
anyhow::Ok(())
|
||||
// Swap workspace center group
|
||||
workspace.center = PaneGroup::with_root(center_group);
|
||||
|
||||
// Change the focus to the workspace first so that we retrigger focus in on the pane.
|
||||
cx.focus_self();
|
||||
|
||||
if let Some(active_pane) = active_pane {
|
||||
cx.focus(&active_pane);
|
||||
} else {
|
||||
cx.focus(workspace.panes.last().unwrap());
|
||||
}
|
||||
} else {
|
||||
let old_center_handle = old_center_pane.and_then(|weak| weak.upgrade(cx));
|
||||
if let Some(old_center_handle) = old_center_handle {
|
||||
cx.focus(&old_center_handle)
|
||||
} else {
|
||||
cx.focus_self()
|
||||
}
|
||||
}
|
||||
|
||||
let docks = serialized_workspace.docks;
|
||||
workspace.left_dock.update(cx, |dock, cx| {
|
||||
dock.set_open(docks.left.visible, cx);
|
||||
if let Some(active_panel) = docks.left.active_panel {
|
||||
if let Some(ix) = dock.panel_index_for_ui_name(&active_panel, cx) {
|
||||
dock.activate_panel(ix, cx);
|
||||
}
|
||||
}
|
||||
});
|
||||
workspace.right_dock.update(cx, |dock, cx| {
|
||||
dock.set_open(docks.right.visible, cx);
|
||||
if let Some(active_panel) = docks.right.active_panel {
|
||||
if let Some(ix) = dock.panel_index_for_ui_name(&active_panel, cx) {
|
||||
dock.activate_panel(ix, cx);
|
||||
}
|
||||
}
|
||||
});
|
||||
workspace.bottom_dock.update(cx, |dock, cx| {
|
||||
dock.set_open(docks.bottom.visible, cx);
|
||||
if let Some(active_panel) = docks.bottom.active_panel {
|
||||
if let Some(ix) = dock.panel_index_for_ui_name(&active_panel, cx) {
|
||||
dock.activate_panel(ix, cx);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
cx.notify();
|
||||
})?;
|
||||
|
||||
// Serialize ourself to make sure our timestamps and any pane / item changes are replicated
|
||||
workspace.read_with(&cx, |workspace, cx| workspace.serialize_workspace(cx))?;
|
||||
|
||||
Ok::<_, anyhow::Error>(resulting_list)
|
||||
}};
|
||||
|
||||
result.await.unwrap_or_default()
|
||||
})
|
||||
.detach_and_log_err(cx);
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
pub fn test_new(project: ModelHandle<Project>, cx: &mut ViewContext<Self>) -> Self {
|
||||
let app_state = Arc::new(AppState {
|
||||
languages: project.read(cx).languages().clone(),
|
||||
themes: ThemeRegistry::new((), cx.font_cache().clone()),
|
||||
client: project.read(cx).client(),
|
||||
user_store: project.read(cx).user_store(),
|
||||
fs: project.read(cx).fs().clone(),
|
||||
|
@ -2751,7 +2799,7 @@ impl Workspace {
|
|||
initialize_workspace: |_, _, _, _| Task::ready(Ok(())),
|
||||
background_actions: || &[],
|
||||
});
|
||||
Self::new(None, 0, project, app_state, cx)
|
||||
Self::new(0, project, app_state, cx)
|
||||
}
|
||||
|
||||
fn render_dock(&self, position: DockPosition, cx: &WindowContext) -> Option<AnyElement<Self>> {
|
||||
|
@ -2782,6 +2830,95 @@ impl Workspace {
|
|||
}
|
||||
}
|
||||
|
||||
async fn open_items(
|
||||
serialized_workspace: Option<SerializedWorkspace>,
|
||||
workspace: &WeakViewHandle<Workspace>,
|
||||
mut project_paths_to_open: Vec<(PathBuf, Option<ProjectPath>)>,
|
||||
app_state: Arc<AppState>,
|
||||
mut cx: AsyncAppContext,
|
||||
) -> Vec<Option<anyhow::Result<Box<dyn ItemHandle>>>> {
|
||||
let mut opened_items = Vec::with_capacity(project_paths_to_open.len());
|
||||
|
||||
if let Some(serialized_workspace) = serialized_workspace {
|
||||
let workspace = workspace.clone();
|
||||
let restored_items = cx
|
||||
.update(|cx| {
|
||||
Workspace::load_workspace(
|
||||
workspace,
|
||||
serialized_workspace,
|
||||
project_paths_to_open
|
||||
.iter()
|
||||
.map(|(_, project_path)| project_path)
|
||||
.cloned()
|
||||
.collect(),
|
||||
cx,
|
||||
)
|
||||
})
|
||||
.await;
|
||||
|
||||
let restored_project_paths = cx.read(|cx| {
|
||||
restored_items
|
||||
.iter()
|
||||
.filter_map(|item| item.as_ref()?.as_ref().ok()?.project_path(cx))
|
||||
.collect::<HashSet<_>>()
|
||||
});
|
||||
|
||||
opened_items = restored_items;
|
||||
project_paths_to_open
|
||||
.iter_mut()
|
||||
.for_each(|(_, project_path)| {
|
||||
if let Some(project_path_to_open) = project_path {
|
||||
if restored_project_paths.contains(project_path_to_open) {
|
||||
*project_path = None;
|
||||
}
|
||||
}
|
||||
});
|
||||
} else {
|
||||
for _ in 0..project_paths_to_open.len() {
|
||||
opened_items.push(None);
|
||||
}
|
||||
}
|
||||
assert!(opened_items.len() == project_paths_to_open.len());
|
||||
|
||||
let tasks =
|
||||
project_paths_to_open
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
.map(|(i, (abs_path, project_path))| {
|
||||
let workspace = workspace.clone();
|
||||
cx.spawn(|mut cx| {
|
||||
let fs = app_state.fs.clone();
|
||||
async move {
|
||||
let file_project_path = project_path?;
|
||||
if fs.is_file(&abs_path).await {
|
||||
Some((
|
||||
i,
|
||||
workspace
|
||||
.update(&mut cx, |workspace, cx| {
|
||||
workspace.open_path(file_project_path, None, true, cx)
|
||||
})
|
||||
.log_err()?
|
||||
.await,
|
||||
))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
for maybe_opened_path in futures::future::join_all(tasks.into_iter())
|
||||
.await
|
||||
.into_iter()
|
||||
{
|
||||
if let Some((i, path_open_result)) = maybe_opened_path {
|
||||
opened_items[i] = Some(path_open_result);
|
||||
}
|
||||
}
|
||||
|
||||
opened_items
|
||||
}
|
||||
|
||||
fn notify_if_database_failed(workspace: &WeakViewHandle<Workspace>, cx: &mut AsyncAppContext) {
|
||||
const REPORT_ISSUE_URL: &str ="https://github.com/zed-industries/community/issues/new?assignees=&labels=defect%2Ctriage&template=2_bug_report.yml";
|
||||
|
||||
|
@ -2826,7 +2963,7 @@ impl View for Workspace {
|
|||
}
|
||||
|
||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
|
||||
let theme = cx.global::<Settings>().theme.clone();
|
||||
let theme = theme::current(cx).clone();
|
||||
Stack::new()
|
||||
.with_child(
|
||||
Flex::column()
|
||||
|
@ -2992,8 +3129,6 @@ pub fn open_paths(
|
|||
Vec<Option<Result<Box<dyn ItemHandle>, anyhow::Error>>>,
|
||||
)>,
|
||||
> {
|
||||
log::info!("open paths {:?}", abs_paths);
|
||||
|
||||
let app_state = app_state.clone();
|
||||
let abs_paths = abs_paths.to_vec();
|
||||
cx.spawn(|mut cx| async move {
|
||||
|
@ -3105,7 +3240,7 @@ pub fn join_remote_project(
|
|||
|
||||
let (_, workspace) = cx.add_window(
|
||||
(app_state.build_window_options)(None, None, cx.platform().as_ref()),
|
||||
|cx| Workspace::new(None, 0, project, app_state.clone(), cx),
|
||||
|cx| Workspace::new(0, project, app_state.clone(), cx),
|
||||
);
|
||||
(app_state.initialize_workspace)(
|
||||
workspace.downgrade(),
|
||||
|
@ -3156,7 +3291,7 @@ pub fn join_remote_project(
|
|||
}
|
||||
|
||||
pub fn restart(_: &Restart, cx: &mut AppContext) {
|
||||
let should_confirm = cx.global::<Settings>().confirm_quit;
|
||||
let should_confirm = settings::get::<WorkspaceSettings>(cx).confirm_quit;
|
||||
cx.spawn(|mut cx| async move {
|
||||
let mut workspaces = cx
|
||||
.window_ids()
|
||||
|
@ -3217,23 +3352,21 @@ fn parse_pixel_position_env_var(value: &str) -> Option<Vector2F> {
|
|||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::{cell::RefCell, rc::Rc};
|
||||
|
||||
use super::*;
|
||||
use crate::{
|
||||
dock::test::{TestPanel, TestPanelEvent},
|
||||
item::test::{TestItem, TestItemEvent, TestProjectItem},
|
||||
};
|
||||
|
||||
use super::*;
|
||||
use fs::FakeFs;
|
||||
use gpui::{executor::Deterministic, TestAppContext};
|
||||
use project::{Project, ProjectEntryId};
|
||||
use serde_json::json;
|
||||
use settings::SettingsStore;
|
||||
use std::{cell::RefCell, rc::Rc};
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_tab_disambiguation(cx: &mut TestAppContext) {
|
||||
cx.foreground().forbid_parking();
|
||||
Settings::test_async(cx);
|
||||
init_test(cx);
|
||||
|
||||
let fs = FakeFs::new(cx.background());
|
||||
let project = Project::test(fs, [], cx).await;
|
||||
|
@ -3281,8 +3414,8 @@ mod tests {
|
|||
|
||||
#[gpui::test]
|
||||
async fn test_tracking_active_path(cx: &mut TestAppContext) {
|
||||
cx.foreground().forbid_parking();
|
||||
Settings::test_async(cx);
|
||||
init_test(cx);
|
||||
|
||||
let fs = FakeFs::new(cx.background());
|
||||
fs.insert_tree(
|
||||
"/root1",
|
||||
|
@ -3385,8 +3518,8 @@ mod tests {
|
|||
|
||||
#[gpui::test]
|
||||
async fn test_close_window(cx: &mut TestAppContext) {
|
||||
cx.foreground().forbid_parking();
|
||||
Settings::test_async(cx);
|
||||
init_test(cx);
|
||||
|
||||
let fs = FakeFs::new(cx.background());
|
||||
fs.insert_tree("/root", json!({ "one": "" })).await;
|
||||
|
||||
|
@ -3421,8 +3554,8 @@ mod tests {
|
|||
|
||||
#[gpui::test]
|
||||
async fn test_close_pane_items(cx: &mut TestAppContext) {
|
||||
cx.foreground().forbid_parking();
|
||||
Settings::test_async(cx);
|
||||
init_test(cx);
|
||||
|
||||
let fs = FakeFs::new(cx.background());
|
||||
|
||||
let project = Project::test(fs, None, cx).await;
|
||||
|
@ -3523,8 +3656,8 @@ mod tests {
|
|||
|
||||
#[gpui::test]
|
||||
async fn test_prompting_to_save_only_on_last_item_for_entry(cx: &mut TestAppContext) {
|
||||
cx.foreground().forbid_parking();
|
||||
Settings::test_async(cx);
|
||||
init_test(cx);
|
||||
|
||||
let fs = FakeFs::new(cx.background());
|
||||
|
||||
let project = Project::test(fs, [], cx).await;
|
||||
|
@ -3626,9 +3759,8 @@ mod tests {
|
|||
|
||||
#[gpui::test]
|
||||
async fn test_autosave(deterministic: Arc<Deterministic>, cx: &mut gpui::TestAppContext) {
|
||||
deterministic.forbid_parking();
|
||||
init_test(cx);
|
||||
|
||||
Settings::test_async(cx);
|
||||
let fs = FakeFs::new(cx.background());
|
||||
|
||||
let project = Project::test(fs, [], cx).await;
|
||||
|
@ -3645,8 +3777,10 @@ mod tests {
|
|||
|
||||
// Autosave on window change.
|
||||
item.update(cx, |item, cx| {
|
||||
cx.update_global(|settings: &mut Settings, _| {
|
||||
settings.autosave = Autosave::OnWindowChange;
|
||||
cx.update_global(|settings: &mut SettingsStore, cx| {
|
||||
settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
|
||||
settings.autosave = Some(AutosaveSetting::OnWindowChange);
|
||||
})
|
||||
});
|
||||
item.is_dirty = true;
|
||||
});
|
||||
|
@ -3659,8 +3793,10 @@ mod tests {
|
|||
// Autosave on focus change.
|
||||
item.update(cx, |item, cx| {
|
||||
cx.focus_self();
|
||||
cx.update_global(|settings: &mut Settings, _| {
|
||||
settings.autosave = Autosave::OnFocusChange;
|
||||
cx.update_global(|settings: &mut SettingsStore, cx| {
|
||||
settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
|
||||
settings.autosave = Some(AutosaveSetting::OnFocusChange);
|
||||
})
|
||||
});
|
||||
item.is_dirty = true;
|
||||
});
|
||||
|
@ -3683,8 +3819,10 @@ mod tests {
|
|||
|
||||
// Autosave after delay.
|
||||
item.update(cx, |item, cx| {
|
||||
cx.update_global(|settings: &mut Settings, _| {
|
||||
settings.autosave = Autosave::AfterDelay { milliseconds: 500 };
|
||||
cx.update_global(|settings: &mut SettingsStore, cx| {
|
||||
settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
|
||||
settings.autosave = Some(AutosaveSetting::AfterDelay { milliseconds: 500 });
|
||||
})
|
||||
});
|
||||
item.is_dirty = true;
|
||||
cx.emit(TestItemEvent::Edit);
|
||||
|
@ -3700,8 +3838,10 @@ mod tests {
|
|||
|
||||
// Autosave on focus change, ensuring closing the tab counts as such.
|
||||
item.update(cx, |item, cx| {
|
||||
cx.update_global(|settings: &mut Settings, _| {
|
||||
settings.autosave = Autosave::OnFocusChange;
|
||||
cx.update_global(|settings: &mut SettingsStore, cx| {
|
||||
settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
|
||||
settings.autosave = Some(AutosaveSetting::OnFocusChange);
|
||||
})
|
||||
});
|
||||
item.is_dirty = true;
|
||||
});
|
||||
|
@ -3735,12 +3875,9 @@ mod tests {
|
|||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_pane_navigation(
|
||||
deterministic: Arc<Deterministic>,
|
||||
cx: &mut gpui::TestAppContext,
|
||||
) {
|
||||
deterministic.forbid_parking();
|
||||
Settings::test_async(cx);
|
||||
async fn test_pane_navigation(cx: &mut gpui::TestAppContext) {
|
||||
init_test(cx);
|
||||
|
||||
let fs = FakeFs::new(cx.background());
|
||||
|
||||
let project = Project::test(fs, [], cx).await;
|
||||
|
@ -3794,9 +3931,8 @@ mod tests {
|
|||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_panels(deterministic: Arc<Deterministic>, cx: &mut gpui::TestAppContext) {
|
||||
deterministic.forbid_parking();
|
||||
Settings::test_async(cx);
|
||||
async fn test_panels(cx: &mut gpui::TestAppContext) {
|
||||
init_test(cx);
|
||||
let fs = FakeFs::new(cx.background());
|
||||
|
||||
let project = Project::test(fs, [], cx).await;
|
||||
|
@ -3942,4 +4078,14 @@ mod tests {
|
|||
assert!(!left_dock.read(cx).is_open());
|
||||
});
|
||||
}
|
||||
|
||||
pub fn init_test(cx: &mut TestAppContext) {
|
||||
cx.foreground().forbid_parking();
|
||||
cx.update(|cx| {
|
||||
cx.set_global(SettingsStore::test(cx));
|
||||
theme::init((), cx);
|
||||
language::init(cx);
|
||||
crate::init_settings(cx);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
58
crates/workspace/src/workspace_settings.rs
Normal file
58
crates/workspace/src/workspace_settings.rs
Normal file
|
@ -0,0 +1,58 @@
|
|||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use settings::Setting;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct WorkspaceSettings {
|
||||
pub active_pane_magnification: f32,
|
||||
pub confirm_quit: bool,
|
||||
pub show_call_status_icon: bool,
|
||||
pub autosave: AutosaveSetting,
|
||||
pub git: GitSettings,
|
||||
}
|
||||
|
||||
#[derive(Clone, Serialize, Deserialize, JsonSchema)]
|
||||
pub struct WorkspaceSettingsContent {
|
||||
pub active_pane_magnification: Option<f32>,
|
||||
pub confirm_quit: Option<bool>,
|
||||
pub show_call_status_icon: Option<bool>,
|
||||
pub autosave: Option<AutosaveSetting>,
|
||||
pub git: Option<GitSettings>,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum AutosaveSetting {
|
||||
Off,
|
||||
AfterDelay { milliseconds: u64 },
|
||||
OnFocusChange,
|
||||
OnWindowChange,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Default, Serialize, Deserialize, JsonSchema)]
|
||||
pub struct GitSettings {
|
||||
pub git_gutter: Option<GitGutterSetting>,
|
||||
pub gutter_debounce: Option<u64>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Default, Serialize, Deserialize, JsonSchema)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum GitGutterSetting {
|
||||
#[default]
|
||||
TrackedFiles,
|
||||
Hide,
|
||||
}
|
||||
|
||||
impl Setting for WorkspaceSettings {
|
||||
const KEY: Option<&'static str> = None;
|
||||
|
||||
type FileContent = WorkspaceSettingsContent;
|
||||
|
||||
fn load(
|
||||
default_value: &Self::FileContent,
|
||||
user_values: &[&Self::FileContent],
|
||||
_: &gpui::AppContext,
|
||||
) -> anyhow::Result<Self> {
|
||||
Self::load_via_json_merge(default_value, user_values)
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue