From 885172f4dda7d89a78fd33b8317da03c69e37397 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Tue, 5 Jul 2022 13:01:27 +0200 Subject: [PATCH] Honor `Autosave` setting in `Editor` --- crates/editor/src/editor.rs | 78 ++++++++++++++++++++++++++++----- crates/settings/src/settings.rs | 16 +++---- 2 files changed, 75 insertions(+), 19 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 9c950b27be..ec65db3bf7 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -17,6 +17,7 @@ use collections::{BTreeMap, Bound, HashMap, HashSet, VecDeque}; pub use display_map::DisplayPoint; use display_map::*; pub use element::*; +use futures::{channel::oneshot, FutureExt}; use fuzzy::{StringMatch, StringMatchCandidate}; use gpui::{ actions, @@ -28,8 +29,8 @@ use gpui::{ impl_actions, impl_internal_actions, platform::CursorStyle, text_layout, AppContext, AsyncAppContext, ClipboardItem, Element, ElementBox, Entity, - ModelHandle, MutableAppContext, RenderContext, Task, View, ViewContext, ViewHandle, - WeakViewHandle, + ModelHandle, MutableAppContext, RenderContext, Subscription, Task, View, ViewContext, + ViewHandle, WeakViewHandle, }; use hover_popover::{hide_hover, HoverState}; pub use language::{char_kind, CharKind}; @@ -48,7 +49,7 @@ use ordered_float::OrderedFloat; use project::{LocationLink, Project, ProjectPath, ProjectTransaction}; use selections_collection::{resolve_multiple, MutableSelectionsCollection, SelectionsCollection}; use serde::{Deserialize, Serialize}; -use settings::Settings; +use settings::{Autosave, Settings}; use smallvec::SmallVec; use smol::Timer; use snippet::Snippet; @@ -436,6 +437,9 @@ pub struct Editor { leader_replica_id: Option, hover_state: HoverState, link_go_to_definition_state: LinkGoToDefinitionState, + pending_autosave: Option>>, + cancel_pending_autosave: Option>, + _subscriptions: Vec, } pub struct EditorSnapshot { @@ -973,17 +977,13 @@ impl Editor { cx, ) }); - cx.observe(&buffer, Self::on_buffer_changed).detach(); - cx.subscribe(&buffer, Self::on_buffer_event).detach(); - cx.observe(&display_map, Self::on_display_map_changed) - .detach(); let selections = SelectionsCollection::new(display_map.clone(), buffer.clone()); let mut this = Self { handle: cx.weak_handle(), - buffer, - display_map, + buffer: buffer.clone(), + display_map: display_map.clone(), selections, columnar_selection_tail: None, add_selections_state: None, @@ -1026,6 +1026,14 @@ impl Editor { leader_replica_id: None, hover_state: Default::default(), link_go_to_definition_state: Default::default(), + pending_autosave: Default::default(), + cancel_pending_autosave: Default::default(), + _subscriptions: vec![ + cx.observe(&buffer, Self::on_buffer_changed), + cx.subscribe(&buffer, Self::on_buffer_event), + cx.observe(&display_map, Self::on_display_map_changed), + cx.observe_window_activation(Self::on_window_activation_changed), + ], }; this.end_selection(cx); @@ -2148,7 +2156,10 @@ impl Editor { .iter() .zip(autoclose_pair.ranges.iter().map(|r| r.to_offset(&buffer))) { - if selection.is_empty() && autoclose_range.is_empty() && selection.start == autoclose_range.start { + if selection.is_empty() + && autoclose_range.is_empty() + && selection.start == autoclose_range.start + { new_selections.push(Selection { id: selection.id, start: selection.start - autoclose_pair.pair.start.len(), @@ -5570,6 +5581,33 @@ impl Editor { self.refresh_active_diagnostics(cx); self.refresh_code_actions(cx); cx.emit(Event::BufferEdited); + if let Autosave::AfterDelay { milliseconds } = cx.global::().autosave { + let pending_autosave = + self.pending_autosave.take().unwrap_or(Task::ready(None)); + if let Some(cancel_pending_autosave) = self.cancel_pending_autosave.take() { + let _ = cancel_pending_autosave.send(()); + } + + let (cancel_tx, mut cancel_rx) = oneshot::channel(); + self.cancel_pending_autosave = Some(cancel_tx); + self.pending_autosave = Some(cx.spawn_weak(|this, mut cx| async move { + let mut timer = futures::future::join( + cx.background().timer(Duration::from_millis(milliseconds)), + pending_autosave, + ) + .fuse(); + futures::select! { + _ = timer => {} + _ = cancel_rx => return None, + } + + this.upgrade(&cx)? + .update(&mut cx, |this, cx| this.autosave(cx)) + .await + .log_err(); + None + })); + } } language::Event::Reparsed => cx.emit(Event::Reparsed), language::Event::DirtyChanged => cx.emit(Event::DirtyChanged), @@ -5588,6 +5626,22 @@ impl Editor { cx.notify(); } + fn on_window_activation_changed(&mut self, active: bool, cx: &mut ViewContext) { + if !active && cx.global::().autosave == Autosave::OnWindowChange { + self.autosave(cx).detach_and_log_err(cx); + } + } + + fn autosave(&mut self, cx: &mut ViewContext) -> Task> { + if let Some(project) = self.project.clone() { + if self.buffer.read(cx).is_dirty(cx) && !self.buffer.read(cx).has_conflict(cx) { + return workspace::Item::save(self, project, cx); + } + } + + Task::ready(Ok(())) + } + pub fn set_searchable(&mut self, searchable: bool) { self.searchable = searchable; } @@ -5805,6 +5859,10 @@ impl View for Editor { hide_hover(self, cx); cx.emit(Event::Blurred); cx.notify(); + + if cx.global::().autosave == Autosave::OnFocusChange { + self.autosave(cx).detach_and_log_err(cx); + } } fn keymap_context(&self, _: &AppContext) -> gpui::keymap::Context { diff --git a/crates/settings/src/settings.rs b/crates/settings/src/settings.rs index 886035c543..0a59d28cc9 100644 --- a/crates/settings/src/settings.rs +++ b/crates/settings/src/settings.rs @@ -25,6 +25,7 @@ pub struct Settings { pub default_buffer_font_size: f32, pub hover_popover_enabled: bool, pub vim_mode: bool, + pub autosave: Autosave, pub language_settings: LanguageSettings, pub language_defaults: HashMap, LanguageSettings>, pub language_overrides: HashMap, LanguageSettings>, @@ -39,7 +40,6 @@ pub struct LanguageSettings { pub preferred_line_length: Option, pub format_on_save: Option, pub enable_language_server: Option, - pub autosave: Option, } #[derive(Copy, Clone, Debug, Deserialize, PartialEq, Eq, JsonSchema)] @@ -54,7 +54,7 @@ pub enum SoftWrap { #[serde(rename_all = "snake_case")] pub enum Autosave { Off, - AfterDelay { milliseconds: usize }, + AfterDelay { milliseconds: u64 }, OnFocusChange, OnWindowChange, } @@ -74,6 +74,8 @@ pub struct SettingsFileContent { #[serde(default)] pub format_on_save: Option, #[serde(default)] + pub autosave: Option, + #[serde(default)] pub enable_language_server: Option, #[serde(flatten)] pub editor: LanguageSettings, @@ -95,6 +97,7 @@ impl Settings { default_buffer_font_size: 15., hover_popover_enabled: true, vim_mode: false, + autosave: Autosave::Off, language_settings: Default::default(), language_defaults: Default::default(), language_overrides: Default::default(), @@ -138,11 +141,6 @@ impl Settings { .unwrap_or(true) } - pub fn autosave(&self, language: Option<&str>) -> Autosave { - self.language_setting(language, |settings| settings.autosave) - .unwrap_or(Autosave::Off) - } - pub fn enable_language_server(&self, language: Option<&str>) -> bool { self.language_setting(language, |settings| settings.enable_language_server) .unwrap_or(true) @@ -172,6 +170,7 @@ impl Settings { default_buffer_font_size: 14., hover_popover_enabled: true, vim_mode: false, + autosave: Autosave::Off, language_settings: Default::default(), language_defaults: Default::default(), language_overrides: Default::default(), @@ -213,6 +212,7 @@ impl Settings { merge(&mut self.default_buffer_font_size, data.buffer_font_size); merge(&mut self.hover_popover_enabled, data.hover_popover_enabled); merge(&mut self.vim_mode, data.vim_mode); + merge(&mut self.autosave, data.autosave); merge_option( &mut self.language_settings.format_on_save, data.format_on_save, @@ -227,7 +227,6 @@ impl Settings { &mut self.language_settings.preferred_line_length, data.editor.preferred_line_length, ); - merge_option(&mut self.language_settings.autosave, data.editor.autosave); for (language_name, settings) in data.language_overrides.clone().into_iter() { let target = self @@ -246,7 +245,6 @@ impl Settings { &mut target.preferred_line_length, settings.preferred_line_length, ); - merge_option(&mut target.autosave, settings.autosave); } } }