Merge branch 'modified-status-in-tabs'

This commit is contained in:
Max Brunsfeld 2021-04-07 15:43:13 -07:00
commit 57a3207897
13 changed files with 320 additions and 70 deletions

1
Cargo.lock generated
View file

@ -2241,6 +2241,7 @@ dependencies = [
"crossbeam-channel 0.5.0", "crossbeam-channel 0.5.0",
"dirs", "dirs",
"easy-parallel", "easy-parallel",
"futures-core",
"gpui", "gpui",
"ignore", "ignore",
"lazy_static", "lazy_static",

View file

@ -31,7 +31,7 @@ impl gpui::View for TextView {
"View" "View"
} }
fn render<'a>(&self, app: &gpui::AppContext) -> gpui::ElementBox { fn render<'a>(&self, _: &gpui::AppContext) -> gpui::ElementBox {
TextElement.boxed() TextElement.boxed()
} }
} }

View file

@ -0,0 +1,73 @@
use super::Element;
use crate::PaintContext;
use pathfinder_geometry::{
rect::RectF,
vector::{vec2f, Vector2F},
};
pub struct Canvas<F>(F)
where
F: FnMut(RectF, &mut PaintContext);
impl<F> Canvas<F>
where
F: FnMut(RectF, &mut PaintContext),
{
pub fn new(f: F) -> Self {
Self(f)
}
}
impl<F> Element for Canvas<F>
where
F: FnMut(RectF, &mut PaintContext),
{
type LayoutState = ();
type PaintState = ();
fn layout(
&mut self,
constraint: crate::SizeConstraint,
_: &mut crate::LayoutContext,
) -> (Vector2F, Self::LayoutState) {
let x = if constraint.max.x().is_finite() {
constraint.max.x()
} else {
constraint.min.x()
};
let y = if constraint.max.y().is_finite() {
constraint.max.y()
} else {
constraint.min.y()
};
(vec2f(x, y), ())
}
fn paint(
&mut self,
bounds: RectF,
_: &mut Self::LayoutState,
ctx: &mut PaintContext,
) -> Self::PaintState {
self.0(bounds, ctx)
}
fn after_layout(
&mut self,
_: Vector2F,
_: &mut Self::LayoutState,
_: &mut crate::AfterLayoutContext,
) {
}
fn dispatch_event(
&mut self,
_: &crate::Event,
_: RectF,
_: &mut Self::LayoutState,
_: &mut Self::PaintState,
_: &mut crate::EventContext,
) -> bool {
false
}
}

View file

@ -36,6 +36,11 @@ impl Container {
self self
} }
pub fn with_margin_left(mut self, margin: f32) -> Self {
self.margin.left = margin;
self
}
pub fn with_uniform_padding(mut self, padding: f32) -> Self { pub fn with_uniform_padding(mut self, padding: f32) -> Self {
self.padding = Padding { self.padding = Padding {
top: padding, top: padding,

View file

@ -1,4 +1,5 @@
mod align; mod align;
mod canvas;
mod constrained_box; mod constrained_box;
mod container; mod container;
mod empty; mod empty;
@ -13,6 +14,7 @@ mod uniform_list;
pub use crate::presenter::ChildView; pub use crate::presenter::ChildView;
pub use align::*; pub use align::*;
pub use canvas::*;
pub use constrained_box::*; pub use constrained_box::*;
pub use container::*; pub use container::*;
pub use empty::*; pub use empty::*;

View file

@ -230,12 +230,6 @@ impl Window {
Ok(window) Ok(window)
} }
} }
pub fn zoom(&self) {
unsafe {
self.0.as_ref().borrow().native_window.performZoom_(nil);
}
}
} }
impl Drop for Window { impl Drop for Window {

View file

@ -20,6 +20,7 @@ dirs = "3.0"
easy-parallel = "3.1.0" easy-parallel = "3.1.0"
gpui = {path = "../gpui"} gpui = {path = "../gpui"}
ignore = {git = "https://github.com/zed-industries/ripgrep", rev = "1d152118f35b3e3590216709b86277062d79b8a0"} ignore = {git = "https://github.com/zed-industries/ripgrep", rev = "1d152118f35b3e3590216709b86277062d79b8a0"}
futures-core = "0.3"
lazy_static = "1.4.0" lazy_static = "1.4.0"
libc = "0.2" libc = "0.2"
log = "0.4" log = "0.4"

View file

@ -3,6 +3,7 @@ mod point;
mod text; mod text;
pub use anchor::*; pub use anchor::*;
use futures_core::future::LocalBoxFuture;
pub use point::*; pub use point::*;
pub use text::*; pub use text::*;
@ -14,7 +15,7 @@ use crate::{
worktree::FileHandle, worktree::FileHandle,
}; };
use anyhow::{anyhow, Result}; use anyhow::{anyhow, Result};
use gpui::{AppContext, Entity, ModelContext, Task}; use gpui::{AppContext, Entity, ModelContext};
use lazy_static::lazy_static; use lazy_static::lazy_static;
use rand::prelude::*; use rand::prelude::*;
use std::{ use std::{
@ -36,6 +37,7 @@ pub struct Buffer {
fragments: SumTree<Fragment>, fragments: SumTree<Fragment>,
insertion_splits: HashMap<time::Local, SumTree<InsertionSplit>>, insertion_splits: HashMap<time::Local, SumTree<InsertionSplit>>,
pub version: time::Global, pub version: time::Global,
saved_version: time::Global,
last_edit: time::Local, last_edit: time::Local,
selections: HashMap<SelectionSetId, Vec<Selection>>, selections: HashMap<SelectionSetId, Vec<Selection>>,
pub selections_last_update: SelectionsVersion, pub selections_last_update: SelectionsVersion,
@ -216,6 +218,7 @@ impl Buffer {
fragments, fragments,
insertion_splits, insertion_splits,
version: time::Global::new(), version: time::Global::new(),
saved_version: time::Global::new(),
last_edit: time::Local::default(), last_edit: time::Local::default(),
selections: HashMap::default(), selections: HashMap::default(),
selections_last_update: 0, selections_last_update: 0,
@ -241,17 +244,34 @@ impl Buffer {
} }
} }
pub fn save(&self, ctx: &mut ModelContext<Self>) -> Option<Task<Result<()>>> { pub fn save(&mut self, ctx: &mut ModelContext<Self>) -> LocalBoxFuture<'static, Result<()>> {
if let Some(file) = &self.file { if let Some(file) = &self.file {
let snapshot = self.snapshot(); let snapshot = self.snapshot();
Some(file.save(snapshot, ctx.app())) let version = self.version.clone();
let save_task = file.save(snapshot, ctx.app());
let task = ctx.spawn(save_task, |me, save_result, ctx| {
if save_result.is_ok() {
me.did_save(version, ctx);
}
save_result
});
Box::pin(task)
} else { } else {
None Box::pin(async { Ok(()) })
} }
} }
pub fn is_modified(&self) -> bool { fn did_save(&mut self, version: time::Global, ctx: &mut ModelContext<Buffer>) {
self.version != time::Global::new() self.saved_version = version;
ctx.emit(Event::Saved);
}
pub fn is_dirty(&self) -> bool {
self.version > self.saved_version
}
pub fn version(&self) -> time::Global {
self.version.clone()
} }
pub fn text_summary(&self) -> TextSummary { pub fn text_summary(&self) -> TextSummary {
@ -398,6 +418,7 @@ impl Buffer {
None None
}; };
let was_dirty = self.is_dirty();
let old_version = self.version.clone(); let old_version = self.version.clone();
let old_ranges = old_ranges let old_ranges = old_ranges
.into_iter() .into_iter()
@ -416,7 +437,7 @@ impl Buffer {
ctx.notify(); ctx.notify();
let changes = self.edits_since(old_version).collect::<Vec<_>>(); let changes = self.edits_since(old_version).collect::<Vec<_>>();
if !changes.is_empty() { if !changes.is_empty() {
ctx.emit(Event::Edited(changes)) self.did_edit(changes, was_dirty, ctx);
} }
} }
@ -434,6 +455,13 @@ impl Buffer {
Ok(ops) Ok(ops)
} }
fn did_edit(&self, changes: Vec<Edit>, was_dirty: bool, ctx: &mut ModelContext<Self>) {
ctx.emit(Event::Edited(changes));
if !was_dirty {
ctx.emit(Event::Dirtied);
}
}
pub fn simulate_typing<T: Rng>(&mut self, rng: &mut T) { pub fn simulate_typing<T: Rng>(&mut self, rng: &mut T) {
let end = rng.gen_range(0..self.len() + 1); let end = rng.gen_range(0..self.len() + 1);
let start = rng.gen_range(0..end + 1); let start = rng.gen_range(0..end + 1);
@ -619,6 +647,7 @@ impl Buffer {
ops: I, ops: I,
ctx: Option<&mut ModelContext<Self>>, ctx: Option<&mut ModelContext<Self>>,
) -> Result<()> { ) -> Result<()> {
let was_dirty = self.is_dirty();
let old_version = self.version.clone(); let old_version = self.version.clone();
let mut deferred_ops = Vec::new(); let mut deferred_ops = Vec::new();
@ -637,7 +666,7 @@ impl Buffer {
ctx.notify(); ctx.notify();
let changes = self.edits_since(old_version).collect::<Vec<_>>(); let changes = self.edits_since(old_version).collect::<Vec<_>>();
if !changes.is_empty() { if !changes.is_empty() {
ctx.emit(Event::Edited(changes)); self.did_edit(changes, was_dirty, ctx);
} }
} }
@ -1370,6 +1399,7 @@ impl Clone for Buffer {
fragments: self.fragments.clone(), fragments: self.fragments.clone(),
insertion_splits: self.insertion_splits.clone(), insertion_splits: self.insertion_splits.clone(),
version: self.version.clone(), version: self.version.clone(),
saved_version: self.saved_version.clone(),
last_edit: self.last_edit.clone(), last_edit: self.last_edit.clone(),
selections: self.selections.clone(), selections: self.selections.clone(),
selections_last_update: self.selections_last_update.clone(), selections_last_update: self.selections_last_update.clone(),
@ -1395,6 +1425,8 @@ impl Snapshot {
#[derive(Clone, Debug, Eq, PartialEq)] #[derive(Clone, Debug, Eq, PartialEq)]
pub enum Event { pub enum Event {
Edited(Vec<Edit>), Edited(Vec<Edit>),
Dirtied,
Saved,
} }
impl Entity for Buffer { impl Entity for Buffer {
@ -1948,7 +1980,9 @@ impl ToPoint for usize {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use gpui::App;
use std::collections::BTreeMap; use std::collections::BTreeMap;
use std::{cell::RefCell, rc::Rc};
#[test] #[test]
fn test_edit() -> Result<()> { fn test_edit() -> Result<()> {
@ -1970,9 +2004,6 @@ mod tests {
#[test] #[test]
fn test_edit_events() { fn test_edit_events() {
use gpui::App;
use std::{cell::RefCell, rc::Rc};
App::test((), |mut app| async move { App::test((), |mut app| async move {
let buffer_1_events = Rc::new(RefCell::new(Vec::new())); let buffer_1_events = Rc::new(RefCell::new(Vec::new()));
let buffer_2_events = Rc::new(RefCell::new(Vec::new())); let buffer_2_events = Rc::new(RefCell::new(Vec::new()));
@ -1998,19 +2029,25 @@ mod tests {
let buffer_1_events = buffer_1_events.borrow(); let buffer_1_events = buffer_1_events.borrow();
assert_eq!( assert_eq!(
*buffer_1_events, *buffer_1_events,
vec![Event::Edited(vec![Edit { vec![
old_range: 2..4, Event::Edited(vec![Edit {
new_range: 2..5 old_range: 2..4,
}])] new_range: 2..5
},]),
Event::Dirtied
]
); );
let buffer_2_events = buffer_2_events.borrow(); let buffer_2_events = buffer_2_events.borrow();
assert_eq!( assert_eq!(
*buffer_2_events, *buffer_2_events,
vec![Event::Edited(vec![Edit { vec![
old_range: 2..4, Event::Edited(vec![Edit {
new_range: 2..5 old_range: 2..4,
}])] new_range: 2..5
},]),
Event::Dirtied
]
); );
}); });
} }
@ -2484,11 +2521,89 @@ mod tests {
#[test] #[test]
fn test_is_modified() -> Result<()> { fn test_is_modified() -> Result<()> {
let mut buffer = Buffer::new(0, "abc"); App::test((), |mut app| async move {
assert!(!buffer.is_modified()); let model = app.add_model(|_| Buffer::new(0, "abc"));
buffer.edit(vec![1..2], "", None)?; let events = Rc::new(RefCell::new(Vec::new()));
assert!(buffer.is_modified());
// initially, the buffer isn't dirty.
model.update(&mut app, |buffer, ctx| {
ctx.subscribe(&model, {
let events = events.clone();
move |_, event, _| events.borrow_mut().push(event.clone())
});
assert!(!buffer.is_dirty());
assert!(events.borrow().is_empty());
buffer.edit(vec![1..2], "", Some(ctx)).unwrap();
});
// after the first edit, the buffer is dirty, and emits a dirtied event.
model.update(&mut app, |buffer, ctx| {
assert!(buffer.text() == "ac");
assert!(buffer.is_dirty());
assert_eq!(
*events.borrow(),
&[
Event::Edited(vec![Edit {
old_range: 1..2,
new_range: 1..1
}]),
Event::Dirtied
]
);
events.borrow_mut().clear();
buffer.did_save(buffer.version(), ctx);
});
// after saving, the buffer is not dirty, and emits a saved event.
model.update(&mut app, |buffer, ctx| {
assert!(!buffer.is_dirty());
assert_eq!(*events.borrow(), &[Event::Saved]);
events.borrow_mut().clear();
buffer.edit(vec![1..1], "B", Some(ctx)).unwrap();
buffer.edit(vec![2..2], "D", Some(ctx)).unwrap();
});
// after editing again, the buffer is dirty, and emits another dirty event.
model.update(&mut app, |buffer, ctx| {
assert!(buffer.text() == "aBDc");
assert!(buffer.is_dirty());
assert_eq!(
*events.borrow(),
&[
Event::Edited(vec![Edit {
old_range: 1..1,
new_range: 1..2
}]),
Event::Dirtied,
Event::Edited(vec![Edit {
old_range: 2..2,
new_range: 2..3
}]),
],
);
events.borrow_mut().clear();
// TODO - currently, after restoring the buffer to its
// previously-saved state, the is still considered dirty.
buffer.edit(vec![1..3], "", Some(ctx)).unwrap();
assert!(buffer.text() == "ac");
assert!(buffer.is_dirty());
});
model.update(&mut app, |_, _| {
assert_eq!(
*events.borrow(),
&[Event::Edited(vec![Edit {
old_range: 1..3,
new_range: 1..1
},])]
);
});
});
Ok(()) Ok(())
} }

View file

@ -4,10 +4,10 @@ use super::{
}; };
use crate::{settings::Settings, watch, workspace}; use crate::{settings::Settings, watch, workspace};
use anyhow::Result; use anyhow::Result;
use futures_core::future::LocalBoxFuture;
use gpui::{ use gpui::{
fonts::Properties as FontProperties, keymap::Binding, text_layout, App, AppContext, Element, fonts::Properties as FontProperties, keymap::Binding, text_layout, App, AppContext, Element,
ElementBox, Entity, FontCache, ModelHandle, MutableAppContext, Task, View, ViewContext, ElementBox, Entity, FontCache, ModelHandle, View, ViewContext, WeakViewHandle,
WeakViewHandle,
}; };
use gpui::{geometry::vector::Vector2F, TextLayoutCache}; use gpui::{geometry::vector::Vector2F, TextLayoutCache};
use parking_lot::Mutex; use parking_lot::Mutex;
@ -82,18 +82,6 @@ pub enum SelectAction {
End, End,
} }
// impl workspace::Item for Buffer {
// type View = BufferView;
// fn build_view(
// buffer: ModelHandle<Self>,
// settings: watch::Receiver<Settings>,
// ctx: &mut ViewContext<Self::View>,
// ) -> Self::View {
// BufferView::for_buffer(buffer, settings, ctx)
// }
// }
pub struct BufferView { pub struct BufferView {
handle: WeakViewHandle<Self>, handle: WeakViewHandle<Self>,
buffer: ModelHandle<Buffer>, buffer: ModelHandle<Buffer>,
@ -1091,6 +1079,8 @@ impl BufferView {
) { ) {
match event { match event {
buffer::Event::Edited(_) => ctx.emit(Event::Edited), buffer::Event::Edited(_) => ctx.emit(Event::Edited),
buffer::Event::Dirtied => ctx.emit(Event::Dirtied),
buffer::Event::Saved => ctx.emit(Event::Saved),
} }
} }
} }
@ -1106,6 +1096,8 @@ pub enum Event {
Activate, Activate,
Edited, Edited,
Blurred, Blurred,
Dirtied,
Saved,
} }
impl Entity for BufferView { impl Entity for BufferView {
@ -1147,11 +1139,12 @@ impl workspace::Item for Buffer {
} }
impl workspace::ItemView for BufferView { impl workspace::ItemView for BufferView {
fn is_activate_event(event: &Self::Event) -> bool { fn should_activate_item_on_event(event: &Self::Event) -> bool {
match event { matches!(event, Event::Activate)
Event::Activate => true, }
_ => false,
} fn should_update_tab_on_event(event: &Self::Event) -> bool {
matches!(event, Event::Saved | Event::Dirtied)
} }
fn title(&self, app: &AppContext) -> std::string::String { fn title(&self, app: &AppContext) -> std::string::String {
@ -1178,9 +1171,13 @@ impl workspace::ItemView for BufferView {
Some(clone) Some(clone)
} }
fn save(&self, ctx: &mut MutableAppContext) -> Option<Task<Result<()>>> { fn save(&self, ctx: &mut ViewContext<Self>) -> LocalBoxFuture<'static, Result<()>> {
self.buffer.update(ctx, |buffer, ctx| buffer.save(ctx)) self.buffer.update(ctx, |buffer, ctx| buffer.save(ctx))
} }
fn is_dirty(&self, ctx: &AppContext) -> bool {
self.buffer.as_ref(ctx).is_dirty()
}
} }
impl Selection { impl Selection {

View file

@ -126,6 +126,7 @@ impl DisplayMap {
fn handle_buffer_event(&mut self, event: &buffer::Event, ctx: &mut ModelContext<Self>) { fn handle_buffer_event(&mut self, event: &buffer::Event, ctx: &mut ModelContext<Self>) {
match event { match event {
buffer::Event::Edited(edits) => self.fold_map.apply_edits(edits, ctx.app()).unwrap(), buffer::Event::Edited(edits) => self.fold_map.apply_edits(edits, ctx.app()).unwrap(),
_ => {}
} }
} }
} }

View file

@ -309,7 +309,7 @@ impl FileFinder {
} }
} }
Blurred => ctx.emit(Event::Dismissed), Blurred => ctx.emit(Event::Dismissed),
Activate => {} _ => {}
} }
} }

View file

@ -1,7 +1,11 @@
use super::{ItemViewHandle, SplitDirection}; use super::{ItemViewHandle, SplitDirection};
use crate::{settings::Settings, watch}; use crate::{settings::Settings, watch};
use gpui::{ use gpui::{
color::ColorU, elements::*, keymap::Binding, App, AppContext, Border, Entity, View, ViewContext, color::{ColorF, ColorU},
elements::*,
geometry::{rect::RectF, vector::vec2f},
keymap::Binding,
App, AppContext, Border, Entity, Quad, View, ViewContext,
}; };
use std::cmp; use std::cmp;
@ -190,7 +194,28 @@ impl Pane {
let padding = 6.; let padding = 6.;
let mut container = Container::new( let mut container = Container::new(
Align::new( Align::new(
Label::new(title, settings.ui_font_family, settings.ui_font_size).boxed(), Flex::row()
.with_child(
Label::new(title, settings.ui_font_family, settings.ui_font_size)
.boxed(),
)
.with_child(
Container::new(
LineBox::new(
settings.ui_font_family,
settings.ui_font_size,
ConstrainedBox::new(Self::render_modified_icon(
item.is_dirty(app),
))
.with_max_width(12.)
.boxed(),
)
.boxed(),
)
.with_margin_left(20.)
.boxed(),
)
.boxed(),
) )
.boxed(), .boxed(),
) )
@ -243,6 +268,26 @@ impl Pane {
row.boxed() row.boxed()
} }
fn render_modified_icon(is_modified: bool) -> ElementBox {
Canvas::new(move |bounds, ctx| {
if is_modified {
let padding = if bounds.height() < bounds.width() {
vec2f(bounds.width() - bounds.height(), 0.0)
} else {
vec2f(0.0, bounds.height() - bounds.width())
};
let square = RectF::new(bounds.origin() + padding / 2., bounds.size() - padding);
ctx.scene.push_quad(Quad {
bounds: square,
background: Some(ColorF::new(0.639, 0.839, 1.0, 1.0).to_u8()),
border: Default::default(),
corner_radius: square.width() / 2.,
});
}
})
.boxed()
}
} }
impl Entity for Pane { impl Entity for Pane {

View file

@ -1,8 +1,9 @@
use super::{pane, Pane, PaneGroup, SplitDirection, Workspace}; use super::{pane, Pane, PaneGroup, SplitDirection, Workspace};
use crate::{settings::Settings, watch}; use crate::{settings::Settings, watch};
use futures_core::future::LocalBoxFuture;
use gpui::{ use gpui::{
color::rgbu, elements::*, keymap::Binding, AnyViewHandle, App, AppContext, Entity, ModelHandle, color::rgbu, elements::*, keymap::Binding, AnyViewHandle, App, AppContext, Entity, ModelHandle,
MutableAppContext, Task, View, ViewContext, ViewHandle, MutableAppContext, View, ViewContext, ViewHandle,
}; };
use log::{error, info}; use log::{error, info};
use std::{collections::HashSet, path::PathBuf}; use std::{collections::HashSet, path::PathBuf};
@ -13,7 +14,6 @@ pub fn init(app: &mut App) {
} }
pub trait ItemView: View { pub trait ItemView: View {
fn is_activate_event(event: &Self::Event) -> bool;
fn title(&self, app: &AppContext) -> String; fn title(&self, app: &AppContext) -> String;
fn entry_id(&self, app: &AppContext) -> Option<(usize, usize)>; fn entry_id(&self, app: &AppContext) -> Option<(usize, usize)>;
fn clone_on_split(&self, _: &mut ViewContext<Self>) -> Option<Self> fn clone_on_split(&self, _: &mut ViewContext<Self>) -> Option<Self>
@ -22,8 +22,17 @@ pub trait ItemView: View {
{ {
None None
} }
fn save(&self, _: &mut MutableAppContext) -> Option<Task<anyhow::Result<()>>> { fn is_dirty(&self, _: &AppContext) -> bool {
None false
}
fn save(&self, _: &mut ViewContext<Self>) -> LocalBoxFuture<'static, anyhow::Result<()>> {
Box::pin(async { Ok(()) })
}
fn should_activate_item_on_event(_: &Self::Event) -> bool {
false
}
fn should_update_tab_on_event(_: &Self::Event) -> bool {
false
} }
} }
@ -35,7 +44,8 @@ pub trait ItemViewHandle: Send + Sync {
fn set_parent_pane(&self, pane: &ViewHandle<Pane>, app: &mut MutableAppContext); fn set_parent_pane(&self, pane: &ViewHandle<Pane>, app: &mut MutableAppContext);
fn id(&self) -> usize; fn id(&self) -> usize;
fn to_any(&self) -> AnyViewHandle; fn to_any(&self) -> AnyViewHandle;
fn save(&self, ctx: &mut MutableAppContext) -> Option<Task<anyhow::Result<()>>>; fn is_dirty(&self, ctx: &AppContext) -> bool;
fn save(&self, ctx: &mut MutableAppContext) -> LocalBoxFuture<'static, anyhow::Result<()>>;
} }
impl<T: ItemView> ItemViewHandle for ViewHandle<T> { impl<T: ItemView> ItemViewHandle for ViewHandle<T> {
@ -61,18 +71,25 @@ impl<T: ItemView> ItemViewHandle for ViewHandle<T> {
fn set_parent_pane(&self, pane: &ViewHandle<Pane>, app: &mut MutableAppContext) { fn set_parent_pane(&self, pane: &ViewHandle<Pane>, app: &mut MutableAppContext) {
pane.update(app, |_, ctx| { pane.update(app, |_, ctx| {
ctx.subscribe_to_view(self, |pane, item, event, ctx| { ctx.subscribe_to_view(self, |pane, item, event, ctx| {
if T::is_activate_event(event) { if T::should_activate_item_on_event(event) {
if let Some(ix) = pane.item_index(&item) { if let Some(ix) = pane.item_index(&item) {
pane.activate_item(ix, ctx); pane.activate_item(ix, ctx);
pane.activate(ctx); pane.activate(ctx);
} }
} }
if T::should_update_tab_on_event(event) {
ctx.notify()
}
}) })
}) })
} }
fn save(&self, ctx: &mut MutableAppContext) -> Option<Task<anyhow::Result<()>>> { fn save(&self, ctx: &mut MutableAppContext) -> LocalBoxFuture<'static, anyhow::Result<()>> {
self.update(ctx, |item, ctx| item.save(ctx.app_mut())) self.update(ctx, |item, ctx| item.save(ctx))
}
fn is_dirty(&self, ctx: &AppContext) -> bool {
self.as_ref(ctx).is_dirty(ctx)
} }
fn id(&self) -> usize { fn id(&self) -> usize {
@ -222,15 +239,14 @@ impl WorkspaceView {
pub fn save_active_item(&mut self, _: &(), ctx: &mut ViewContext<Self>) { pub fn save_active_item(&mut self, _: &(), ctx: &mut ViewContext<Self>) {
self.active_pane.update(ctx, |pane, ctx| { self.active_pane.update(ctx, |pane, ctx| {
if let Some(item) = pane.active_item() { if let Some(item) = pane.active_item() {
if let Some(task) = item.save(ctx.app_mut()) { let task = item.save(ctx.app_mut());
ctx.spawn(task, |_, result, _| { ctx.spawn(task, |_, result, _| {
if let Err(e) = result { if let Err(e) = result {
// TODO - present this error to the user // TODO - present this error to the user
error!("failed to save item: {:?}, ", e); error!("failed to save item: {:?}, ", e);
} }
}) })
.detach(); .detach()
}
} }
}); });
} }