Use a different tab icon color for buffers with conflicts

Max Brunsfeld created

Change summary

zed/src/editor/buffer/mod.rs  |  7 +++++--
zed/src/editor/buffer_view.rs |  5 +++++
zed/src/workspace.rs          |  8 ++++++++
zed/src/workspace/pane.rs     | 27 +++++++++++++++++++--------
4 files changed, 37 insertions(+), 10 deletions(-)

Detailed changes

zed/src/editor/buffer/mod.rs 🔗

@@ -390,9 +390,11 @@ impl Buffer {
                             move |this, history, ctx| {
                                 if let (Ok(history), true) = (history, this.version == version) {
                                     let task = this.set_text_via_diff(history.base_text, ctx);
-                                    ctx.spawn(task, |this, ops, _| {
+                                    ctx.spawn(task, move |this, ops, ctx| {
                                         if ops.is_some() {
                                             this.saved_version = this.version.clone();
+                                            this.saved_mtime = file.mtime();
+                                            ctx.emit(Event::Reloaded);
                                         }
                                     })
                                     .detach();
@@ -586,7 +588,7 @@ impl Buffer {
             && self
                 .file
                 .as_ref()
-                .map_or(false, |f| f.mtime() != self.saved_mtime)
+                .map_or(false, |f| f.mtime() > self.saved_mtime)
     }
 
     pub fn version(&self) -> time::Global {
@@ -1931,6 +1933,7 @@ pub enum Event {
     Dirtied,
     Saved,
     FileHandleChanged,
+    Reloaded,
 }
 
 impl Entity for Buffer {

zed/src/editor/buffer_view.rs 🔗

@@ -2401,6 +2401,7 @@ impl BufferView {
             buffer::Event::Dirtied => ctx.emit(Event::Dirtied),
             buffer::Event::Saved => ctx.emit(Event::Saved),
             buffer::Event::FileHandleChanged => ctx.emit(Event::FileHandleChanged),
+            buffer::Event::Reloaded => ctx.emit(Event::FileHandleChanged),
         }
     }
 }
@@ -2505,6 +2506,10 @@ impl workspace::ItemView for BufferView {
     fn is_dirty(&self, ctx: &AppContext) -> bool {
         self.buffer.read(ctx).is_dirty()
     }
+
+    fn has_conflict(&self, ctx: &AppContext) -> bool {
+        self.buffer.read(ctx).has_conflict()
+    }
 }
 
 #[cfg(test)]

zed/src/workspace.rs 🔗

@@ -119,6 +119,9 @@ pub trait ItemView: View {
     fn is_dirty(&self, _: &AppContext) -> bool {
         false
     }
+    fn has_conflict(&self, _: &AppContext) -> bool {
+        false
+    }
     fn save(
         &mut self,
         _: Option<FileHandle>,
@@ -157,6 +160,7 @@ pub trait ItemViewHandle: Send + Sync {
     fn id(&self) -> usize;
     fn to_any(&self) -> AnyViewHandle;
     fn is_dirty(&self, ctx: &AppContext) -> bool;
+    fn has_conflict(&self, ctx: &AppContext) -> bool;
     fn save(
         &self,
         file: Option<FileHandle>,
@@ -247,6 +251,10 @@ impl<T: ItemView> ItemViewHandle for ViewHandle<T> {
         self.read(ctx).is_dirty(ctx)
     }
 
+    fn has_conflict(&self, ctx: &AppContext) -> bool {
+        self.read(ctx).has_conflict(ctx)
+    }
+
     fn id(&self) -> usize {
         self.id()
     }

zed/src/workspace/pane.rs 🔗

@@ -228,6 +228,7 @@ impl Pane {
                                         line_height - 2.,
                                         mouse_state.hovered,
                                         item.is_dirty(ctx),
+                                        item.has_conflict(ctx),
                                         ctx,
                                     ))
                                     .right()
@@ -296,15 +297,25 @@ impl Pane {
         item_id: usize,
         close_icon_size: f32,
         tab_hovered: bool,
-        is_modified: bool,
+        is_dirty: bool,
+        has_conflict: bool,
         ctx: &AppContext,
     ) -> ElementBox {
         enum TabCloseButton {}
 
-        let modified_color = ColorU::from_u32(0x556de8ff);
-        let mut clicked_color = modified_color;
+        let dirty_color = ColorU::from_u32(0x556de8ff);
+        let conflict_color = ColorU::from_u32(0xe45349ff);
+        let mut clicked_color = dirty_color;
         clicked_color.a = 180;
 
+        let current_color = if has_conflict {
+            Some(conflict_color)
+        } else if is_dirty {
+            Some(dirty_color)
+        } else {
+            None
+        };
+
         let icon = if tab_hovered {
             let mut icon = Svg::new("icons/x.svg");
 
@@ -314,13 +325,13 @@ impl Pane {
                         .with_background_color(if mouse_state.clicked {
                             clicked_color
                         } else {
-                            modified_color
+                            dirty_color
                         })
                         .with_corner_radius(close_icon_size / 2.)
                         .boxed()
                 } else {
-                    if is_modified {
-                        icon = icon.with_color(modified_color);
+                    if let Some(current_color) = current_color {
+                        icon = icon.with_color(current_color);
                     }
                     icon.boxed()
                 }
@@ -331,11 +342,11 @@ impl Pane {
             let diameter = 8.;
             ConstrainedBox::new(
                 Canvas::new(move |bounds, ctx| {
-                    if is_modified {
+                    if let Some(current_color) = current_color {
                         let square = RectF::new(bounds.origin(), vec2f(diameter, diameter));
                         ctx.scene.push_quad(Quad {
                             bounds: square,
-                            background: Some(modified_color),
+                            background: Some(current_color),
                             border: Default::default(),
                             corner_radius: diameter / 2.,
                         });