Detailed changes
@@ -10,6 +10,8 @@
// Whether to show the informational hover box when moving the mouse
// over symbols in the editor.
"hover_popover_enabled": true,
+ // Whether the cursor blinks in the editor.
+ "cursor_blink": true,
// Whether to pop the completions menu while typing in an editor without
// explicitly requesting it.
"show_completions_on_input": true,
@@ -69,8 +71,6 @@
// The column at which to soft-wrap lines, for buffers where soft-wrap
// is enabled.
"preferred_line_length": 80,
- // Whether the cursor blinks in the editor.
- "cursor_blink": true,
// Whether to indent lines using tab characters, as opposed to multiple
// spaces.
"hard_tabs": false,
@@ -0,0 +1,110 @@
+use std::time::Duration;
+
+use gpui::{Entity, ModelContext};
+use settings::Settings;
+use smol::Timer;
+
+pub struct BlinkManager {
+ blink_interval: Duration,
+
+ blink_epoch: usize,
+ blinking_paused: bool,
+ visible: bool,
+ enabled: bool,
+}
+
+impl BlinkManager {
+ pub fn new(blink_interval: Duration, cx: &mut ModelContext<Self>) -> Self {
+ let weak_handle = cx.weak_handle();
+ cx.observe_global::<Settings, _>(move |_, cx| {
+ if let Some(this) = weak_handle.upgrade(cx) {
+ // Make sure we blink the cursors if the setting is re-enabled
+ this.update(cx, |this, cx| this.blink_cursors(this.blink_epoch, cx));
+ }
+ })
+ .detach();
+
+ Self {
+ blink_interval,
+
+ blink_epoch: 0,
+ blinking_paused: false,
+ visible: true,
+ enabled: true,
+ }
+ }
+
+ fn next_blink_epoch(&mut self) -> usize {
+ self.blink_epoch += 1;
+ self.blink_epoch
+ }
+
+ pub fn pause_blinking(&mut self, cx: &mut ModelContext<Self>) {
+ if !self.visible {
+ self.visible = true;
+ cx.notify();
+ }
+
+ let epoch = self.next_blink_epoch();
+ let interval = self.blink_interval;
+ cx.spawn(|this, mut cx| {
+ let this = this.downgrade();
+ async move {
+ Timer::after(interval).await;
+ if let Some(this) = this.upgrade(&cx) {
+ this.update(&mut cx, |this, cx| this.resume_cursor_blinking(epoch, cx))
+ }
+ }
+ })
+ .detach();
+ }
+
+ fn resume_cursor_blinking(&mut self, epoch: usize, cx: &mut ModelContext<Self>) {
+ if epoch == self.blink_epoch {
+ self.blinking_paused = false;
+ self.blink_cursors(epoch, cx);
+ }
+ }
+
+ fn blink_cursors(&mut self, epoch: usize, cx: &mut ModelContext<Self>) {
+ if cx.global::<Settings>().cursor_blink {
+ if epoch == self.blink_epoch && self.enabled && !self.blinking_paused {
+ self.visible = !self.visible;
+ cx.notify();
+
+ let epoch = self.next_blink_epoch();
+ let interval = self.blink_interval;
+ cx.spawn(|this, mut cx| {
+ let this = this.downgrade();
+ async move {
+ Timer::after(interval).await;
+ if let Some(this) = this.upgrade(&cx) {
+ this.update(&mut cx, |this, cx| this.blink_cursors(epoch, cx));
+ }
+ }
+ })
+ .detach();
+ }
+ } else if !self.visible {
+ self.visible = true;
+ cx.notify();
+ }
+ }
+
+ pub fn enable(&mut self, cx: &mut ModelContext<Self>) {
+ self.enabled = true;
+ self.blink_cursors(self.blink_epoch, cx);
+ }
+
+ pub fn disable(&mut self, _: &mut ModelContext<Self>) {
+ self.enabled = true;
+ }
+
+ pub fn visible(&self) -> bool {
+ self.visible
+ }
+}
+
+impl Entity for BlinkManager {
+ type Event = ();
+}
@@ -1,3 +1,4 @@
+mod blink_manager;
pub mod display_map;
mod element;
mod highlight_matching_bracket;
@@ -16,6 +17,7 @@ pub mod test;
use aho_corasick::AhoCorasick;
use anyhow::Result;
+use blink_manager::BlinkManager;
use clock::ReplicaId;
use collections::{BTreeMap, Bound, HashMap, HashSet, VecDeque};
pub use display_map::DisplayPoint;
@@ -447,12 +449,10 @@ pub struct Editor {
override_text_style: Option<Box<OverrideTextStyle>>,
project: Option<ModelHandle<Project>>,
focused: bool,
- show_local_cursors: bool,
+ blink_manager: ModelHandle<BlinkManager>,
show_local_selections: bool,
show_scrollbars: bool,
hide_scrollbar_task: Option<Task<()>>,
- blink_epoch: usize,
- blinking_paused: bool,
mode: EditorMode,
vertical_scroll_margin: f32,
placeholder_text: Option<Arc<str>>,
@@ -1076,6 +1076,8 @@ impl Editor {
let selections = SelectionsCollection::new(display_map.clone(), buffer.clone());
+ let blink_manager = cx.add_model(|cx| BlinkManager::new(CURSOR_BLINK_INTERVAL, cx));
+
let mut this = Self {
handle: cx.weak_handle(),
buffer: buffer.clone(),
@@ -1097,12 +1099,10 @@ impl Editor {
scroll_top_anchor: Anchor::min(),
autoscroll_request: None,
focused: false,
- show_local_cursors: false,
+ blink_manager: blink_manager.clone(),
show_local_selections: true,
show_scrollbars: true,
hide_scrollbar_task: None,
- blink_epoch: 0,
- blinking_paused: false,
mode,
vertical_scroll_margin: 3.0,
placeholder_text: None,
@@ -1130,6 +1130,7 @@ impl Editor {
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(&blink_manager, |_, _, cx| cx.notify()),
],
};
this.end_selection(cx);
@@ -1542,7 +1543,7 @@ impl Editor {
refresh_matching_bracket_highlights(self, cx);
}
- self.pause_cursor_blinking(cx);
+ self.blink_manager.update(cx, BlinkManager::pause_blinking);
cx.emit(Event::SelectionsChanged { local });
cx.notify();
}
@@ -6111,70 +6112,8 @@ impl Editor {
highlights
}
- fn next_blink_epoch(&mut self) -> usize {
- self.blink_epoch += 1;
- self.blink_epoch
- }
-
- fn pause_cursor_blinking(&mut self, cx: &mut ViewContext<Self>) {
- if !self.focused {
- return;
- }
-
- self.show_local_cursors = true;
- cx.notify();
-
- let epoch = self.next_blink_epoch();
- cx.spawn(|this, mut cx| {
- let this = this.downgrade();
- async move {
- Timer::after(CURSOR_BLINK_INTERVAL).await;
- if let Some(this) = this.upgrade(&cx) {
- this.update(&mut cx, |this, cx| this.resume_cursor_blinking(epoch, cx))
- }
- }
- })
- .detach();
- }
-
- fn resume_cursor_blinking(&mut self, epoch: usize, cx: &mut ViewContext<Self>) {
- if epoch == self.blink_epoch {
- self.blinking_paused = false;
- self.blink_cursors(epoch, cx);
- }
- }
-
- fn blink_cursors(&mut self, epoch: usize, cx: &mut ViewContext<Self>) {
- if epoch == self.blink_epoch && self.focused && !self.blinking_paused {
- let newest_head = self.selections.newest::<usize>(cx).head();
- let language_name = self
- .buffer
- .read(cx)
- .language_at(newest_head, cx)
- .map(|l| l.name());
-
- self.show_local_cursors = !self.show_local_cursors
- || !cx
- .global::<Settings>()
- .cursor_blink(language_name.as_deref());
- cx.notify();
-
- let epoch = self.next_blink_epoch();
- cx.spawn(|this, mut cx| {
- let this = this.downgrade();
- async move {
- Timer::after(CURSOR_BLINK_INTERVAL).await;
- if let Some(this) = this.upgrade(&cx) {
- this.update(&mut cx, |this, cx| this.blink_cursors(epoch, cx));
- }
- }
- })
- .detach();
- }
- }
-
- pub fn show_local_cursors(&self) -> bool {
- self.show_local_cursors && self.focused
+ pub fn show_local_cursors(&self, cx: &AppContext) -> bool {
+ self.blink_manager.read(cx).visible() && self.focused
}
pub fn show_scrollbars(&self) -> bool {
@@ -6493,7 +6432,7 @@ impl View for Editor {
cx.focus(&rename.editor);
} else {
self.focused = true;
- self.blink_cursors(self.blink_epoch, cx);
+ self.blink_manager.update(cx, BlinkManager::enable);
self.buffer.update(cx, |buffer, cx| {
buffer.finalize_last_transaction(cx);
if self.leader_replica_id.is_none() {
@@ -6512,6 +6451,7 @@ impl View for Editor {
let blurred_event = EditorBlurred(cx.handle());
cx.emit_global(blurred_event);
self.focused = false;
+ self.blink_manager.update(cx, BlinkManager::disable);
self.buffer
.update(cx, |buffer, cx| buffer.remove_active_selections(cx));
self.hide_context_menu(cx);
@@ -705,7 +705,7 @@ impl EditorElement {
cx,
);
- if view.show_local_cursors() || *replica_id != local_replica_id {
+ if view.show_local_cursors(cx) || *replica_id != local_replica_id {
let cursor_position = selection.head;
if layout
.visible_display_row_range
@@ -28,6 +28,7 @@ pub struct Settings {
pub buffer_font_family: FamilyId,
pub default_buffer_font_size: f32,
pub buffer_font_size: f32,
+ pub cursor_blink: bool,
pub hover_popover_enabled: bool,
pub show_completions_on_input: bool,
pub vim_mode: bool,
@@ -79,7 +80,6 @@ pub struct GitGutterConfig {}
pub struct EditorSettings {
pub tab_size: Option<NonZeroU32>,
pub hard_tabs: Option<bool>,
- pub cursor_blink: Option<bool>,
pub soft_wrap: Option<SoftWrap>,
pub preferred_line_length: Option<u32>,
pub format_on_save: Option<FormatOnSave>,
@@ -235,6 +235,8 @@ pub struct SettingsFileContent {
#[serde(default)]
pub buffer_font_size: Option<f32>,
#[serde(default)]
+ pub cursor_blink: Option<bool>,
+ #[serde(default)]
pub hover_popover_enabled: Option<bool>,
#[serde(default)]
pub show_completions_on_input: Option<bool>,
@@ -293,6 +295,7 @@ impl Settings {
.unwrap(),
buffer_font_size: defaults.buffer_font_size.unwrap(),
default_buffer_font_size: defaults.buffer_font_size.unwrap(),
+ cursor_blink: defaults.cursor_blink.unwrap(),
hover_popover_enabled: defaults.hover_popover_enabled.unwrap(),
show_completions_on_input: defaults.show_completions_on_input.unwrap(),
projects_online_by_default: defaults.projects_online_by_default.unwrap(),
@@ -302,7 +305,6 @@ impl Settings {
editor_defaults: EditorSettings {
tab_size: required(defaults.editor.tab_size),
hard_tabs: required(defaults.editor.hard_tabs),
- cursor_blink: required(defaults.editor.cursor_blink),
soft_wrap: required(defaults.editor.soft_wrap),
preferred_line_length: required(defaults.editor.preferred_line_length),
format_on_save: required(defaults.editor.format_on_save),
@@ -348,6 +350,7 @@ impl Settings {
);
merge(&mut self.buffer_font_size, data.buffer_font_size);
merge(&mut self.default_buffer_font_size, data.buffer_font_size);
+ merge(&mut self.cursor_blink, data.cursor_blink);
merge(&mut self.hover_popover_enabled, data.hover_popover_enabled);
merge(
&mut self.show_completions_on_input,
@@ -392,10 +395,6 @@ impl Settings {
self.language_setting(language, |settings| settings.hard_tabs)
}
- pub fn cursor_blink(&self, language: Option<&str>) -> bool {
- self.language_setting(language, |settings| settings.cursor_blink)
- }
-
pub fn soft_wrap(&self, language: Option<&str>) -> SoftWrap {
self.language_setting(language, |settings| settings.soft_wrap)
}
@@ -442,6 +441,7 @@ impl Settings {
buffer_font_family: cx.font_cache().load_family(&["Monaco"]).unwrap(),
buffer_font_size: 14.,
default_buffer_font_size: 14.,
+ cursor_blink: true,
hover_popover_enabled: true,
show_completions_on_input: true,
vim_mode: false,
@@ -450,7 +450,6 @@ impl Settings {
editor_defaults: EditorSettings {
tab_size: Some(4.try_into().unwrap()),
hard_tabs: Some(false),
- cursor_blink: Some(true),
soft_wrap: Some(SoftWrap::None),
preferred_line_length: Some(80),
format_on_save: Some(FormatOnSave::On),