Pull blink functionality out of editor and into blink manager. Make blink manager subscribe to settings changes in order to start blinking properly when it is re-enabled.
Co-Authored-By: Mikayla Maki <mikayla@zed.dev>
This commit is contained in:
parent
09a0b3eb55
commit
54cf6fa838
5 changed files with 131 additions and 82 deletions
|
@ -10,6 +10,8 @@
|
||||||
// Whether to show the informational hover box when moving the mouse
|
// Whether to show the informational hover box when moving the mouse
|
||||||
// over symbols in the editor.
|
// over symbols in the editor.
|
||||||
"hover_popover_enabled": true,
|
"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
|
// Whether to pop the completions menu while typing in an editor without
|
||||||
// explicitly requesting it.
|
// explicitly requesting it.
|
||||||
"show_completions_on_input": true,
|
"show_completions_on_input": true,
|
||||||
|
@ -69,8 +71,6 @@
|
||||||
// The column at which to soft-wrap lines, for buffers where soft-wrap
|
// The column at which to soft-wrap lines, for buffers where soft-wrap
|
||||||
// is enabled.
|
// is enabled.
|
||||||
"preferred_line_length": 80,
|
"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
|
// Whether to indent lines using tab characters, as opposed to multiple
|
||||||
// spaces.
|
// spaces.
|
||||||
"hard_tabs": false,
|
"hard_tabs": false,
|
||||||
|
|
110
crates/editor/src/blink_manager.rs
Normal file
110
crates/editor/src/blink_manager.rs
Normal file
|
@ -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;
|
pub mod display_map;
|
||||||
mod element;
|
mod element;
|
||||||
mod highlight_matching_bracket;
|
mod highlight_matching_bracket;
|
||||||
|
@ -16,6 +17,7 @@ pub mod test;
|
||||||
|
|
||||||
use aho_corasick::AhoCorasick;
|
use aho_corasick::AhoCorasick;
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
|
use blink_manager::BlinkManager;
|
||||||
use clock::ReplicaId;
|
use clock::ReplicaId;
|
||||||
use collections::{BTreeMap, Bound, HashMap, HashSet, VecDeque};
|
use collections::{BTreeMap, Bound, HashMap, HashSet, VecDeque};
|
||||||
pub use display_map::DisplayPoint;
|
pub use display_map::DisplayPoint;
|
||||||
|
@ -447,12 +449,10 @@ pub struct Editor {
|
||||||
override_text_style: Option<Box<OverrideTextStyle>>,
|
override_text_style: Option<Box<OverrideTextStyle>>,
|
||||||
project: Option<ModelHandle<Project>>,
|
project: Option<ModelHandle<Project>>,
|
||||||
focused: bool,
|
focused: bool,
|
||||||
show_local_cursors: bool,
|
blink_manager: ModelHandle<BlinkManager>,
|
||||||
show_local_selections: bool,
|
show_local_selections: bool,
|
||||||
show_scrollbars: bool,
|
show_scrollbars: bool,
|
||||||
hide_scrollbar_task: Option<Task<()>>,
|
hide_scrollbar_task: Option<Task<()>>,
|
||||||
blink_epoch: usize,
|
|
||||||
blinking_paused: bool,
|
|
||||||
mode: EditorMode,
|
mode: EditorMode,
|
||||||
vertical_scroll_margin: f32,
|
vertical_scroll_margin: f32,
|
||||||
placeholder_text: Option<Arc<str>>,
|
placeholder_text: Option<Arc<str>>,
|
||||||
|
@ -1076,6 +1076,8 @@ impl Editor {
|
||||||
|
|
||||||
let selections = SelectionsCollection::new(display_map.clone(), buffer.clone());
|
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 {
|
let mut this = Self {
|
||||||
handle: cx.weak_handle(),
|
handle: cx.weak_handle(),
|
||||||
buffer: buffer.clone(),
|
buffer: buffer.clone(),
|
||||||
|
@ -1097,12 +1099,10 @@ impl Editor {
|
||||||
scroll_top_anchor: Anchor::min(),
|
scroll_top_anchor: Anchor::min(),
|
||||||
autoscroll_request: None,
|
autoscroll_request: None,
|
||||||
focused: false,
|
focused: false,
|
||||||
show_local_cursors: false,
|
blink_manager: blink_manager.clone(),
|
||||||
show_local_selections: true,
|
show_local_selections: true,
|
||||||
show_scrollbars: true,
|
show_scrollbars: true,
|
||||||
hide_scrollbar_task: None,
|
hide_scrollbar_task: None,
|
||||||
blink_epoch: 0,
|
|
||||||
blinking_paused: false,
|
|
||||||
mode,
|
mode,
|
||||||
vertical_scroll_margin: 3.0,
|
vertical_scroll_margin: 3.0,
|
||||||
placeholder_text: None,
|
placeholder_text: None,
|
||||||
|
@ -1130,6 +1130,7 @@ impl Editor {
|
||||||
cx.observe(&buffer, Self::on_buffer_changed),
|
cx.observe(&buffer, Self::on_buffer_changed),
|
||||||
cx.subscribe(&buffer, Self::on_buffer_event),
|
cx.subscribe(&buffer, Self::on_buffer_event),
|
||||||
cx.observe(&display_map, Self::on_display_map_changed),
|
cx.observe(&display_map, Self::on_display_map_changed),
|
||||||
|
cx.observe(&blink_manager, |_, _, cx| cx.notify()),
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
this.end_selection(cx);
|
this.end_selection(cx);
|
||||||
|
@ -1542,7 +1543,7 @@ impl Editor {
|
||||||
refresh_matching_bracket_highlights(self, cx);
|
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.emit(Event::SelectionsChanged { local });
|
||||||
cx.notify();
|
cx.notify();
|
||||||
}
|
}
|
||||||
|
@ -6111,70 +6112,8 @@ impl Editor {
|
||||||
highlights
|
highlights
|
||||||
}
|
}
|
||||||
|
|
||||||
fn next_blink_epoch(&mut self) -> usize {
|
pub fn show_local_cursors(&self, cx: &AppContext) -> bool {
|
||||||
self.blink_epoch += 1;
|
self.blink_manager.read(cx).visible() && self.focused
|
||||||
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_scrollbars(&self) -> bool {
|
pub fn show_scrollbars(&self) -> bool {
|
||||||
|
@ -6493,7 +6432,7 @@ impl View for Editor {
|
||||||
cx.focus(&rename.editor);
|
cx.focus(&rename.editor);
|
||||||
} else {
|
} else {
|
||||||
self.focused = true;
|
self.focused = true;
|
||||||
self.blink_cursors(self.blink_epoch, cx);
|
self.blink_manager.update(cx, BlinkManager::enable);
|
||||||
self.buffer.update(cx, |buffer, cx| {
|
self.buffer.update(cx, |buffer, cx| {
|
||||||
buffer.finalize_last_transaction(cx);
|
buffer.finalize_last_transaction(cx);
|
||||||
if self.leader_replica_id.is_none() {
|
if self.leader_replica_id.is_none() {
|
||||||
|
@ -6512,6 +6451,7 @@ impl View for Editor {
|
||||||
let blurred_event = EditorBlurred(cx.handle());
|
let blurred_event = EditorBlurred(cx.handle());
|
||||||
cx.emit_global(blurred_event);
|
cx.emit_global(blurred_event);
|
||||||
self.focused = false;
|
self.focused = false;
|
||||||
|
self.blink_manager.update(cx, BlinkManager::disable);
|
||||||
self.buffer
|
self.buffer
|
||||||
.update(cx, |buffer, cx| buffer.remove_active_selections(cx));
|
.update(cx, |buffer, cx| buffer.remove_active_selections(cx));
|
||||||
self.hide_context_menu(cx);
|
self.hide_context_menu(cx);
|
||||||
|
|
|
@ -705,7 +705,7 @@ impl EditorElement {
|
||||||
cx,
|
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;
|
let cursor_position = selection.head;
|
||||||
if layout
|
if layout
|
||||||
.visible_display_row_range
|
.visible_display_row_range
|
||||||
|
|
|
@ -28,6 +28,7 @@ pub struct Settings {
|
||||||
pub buffer_font_family: FamilyId,
|
pub buffer_font_family: FamilyId,
|
||||||
pub default_buffer_font_size: f32,
|
pub default_buffer_font_size: f32,
|
||||||
pub buffer_font_size: f32,
|
pub buffer_font_size: f32,
|
||||||
|
pub cursor_blink: bool,
|
||||||
pub hover_popover_enabled: bool,
|
pub hover_popover_enabled: bool,
|
||||||
pub show_completions_on_input: bool,
|
pub show_completions_on_input: bool,
|
||||||
pub vim_mode: bool,
|
pub vim_mode: bool,
|
||||||
|
@ -79,7 +80,6 @@ pub struct GitGutterConfig {}
|
||||||
pub struct EditorSettings {
|
pub struct EditorSettings {
|
||||||
pub tab_size: Option<NonZeroU32>,
|
pub tab_size: Option<NonZeroU32>,
|
||||||
pub hard_tabs: Option<bool>,
|
pub hard_tabs: Option<bool>,
|
||||||
pub cursor_blink: Option<bool>,
|
|
||||||
pub soft_wrap: Option<SoftWrap>,
|
pub soft_wrap: Option<SoftWrap>,
|
||||||
pub preferred_line_length: Option<u32>,
|
pub preferred_line_length: Option<u32>,
|
||||||
pub format_on_save: Option<FormatOnSave>,
|
pub format_on_save: Option<FormatOnSave>,
|
||||||
|
@ -235,6 +235,8 @@ pub struct SettingsFileContent {
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub buffer_font_size: Option<f32>,
|
pub buffer_font_size: Option<f32>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
|
pub cursor_blink: Option<bool>,
|
||||||
|
#[serde(default)]
|
||||||
pub hover_popover_enabled: Option<bool>,
|
pub hover_popover_enabled: Option<bool>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub show_completions_on_input: Option<bool>,
|
pub show_completions_on_input: Option<bool>,
|
||||||
|
@ -293,6 +295,7 @@ impl Settings {
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
buffer_font_size: defaults.buffer_font_size.unwrap(),
|
buffer_font_size: defaults.buffer_font_size.unwrap(),
|
||||||
default_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(),
|
hover_popover_enabled: defaults.hover_popover_enabled.unwrap(),
|
||||||
show_completions_on_input: defaults.show_completions_on_input.unwrap(),
|
show_completions_on_input: defaults.show_completions_on_input.unwrap(),
|
||||||
projects_online_by_default: defaults.projects_online_by_default.unwrap(),
|
projects_online_by_default: defaults.projects_online_by_default.unwrap(),
|
||||||
|
@ -302,7 +305,6 @@ impl Settings {
|
||||||
editor_defaults: EditorSettings {
|
editor_defaults: EditorSettings {
|
||||||
tab_size: required(defaults.editor.tab_size),
|
tab_size: required(defaults.editor.tab_size),
|
||||||
hard_tabs: required(defaults.editor.hard_tabs),
|
hard_tabs: required(defaults.editor.hard_tabs),
|
||||||
cursor_blink: required(defaults.editor.cursor_blink),
|
|
||||||
soft_wrap: required(defaults.editor.soft_wrap),
|
soft_wrap: required(defaults.editor.soft_wrap),
|
||||||
preferred_line_length: required(defaults.editor.preferred_line_length),
|
preferred_line_length: required(defaults.editor.preferred_line_length),
|
||||||
format_on_save: required(defaults.editor.format_on_save),
|
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.buffer_font_size, data.buffer_font_size);
|
||||||
merge(&mut self.default_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.hover_popover_enabled, data.hover_popover_enabled);
|
||||||
merge(
|
merge(
|
||||||
&mut self.show_completions_on_input,
|
&mut self.show_completions_on_input,
|
||||||
|
@ -392,10 +395,6 @@ impl Settings {
|
||||||
self.language_setting(language, |settings| settings.hard_tabs)
|
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 {
|
pub fn soft_wrap(&self, language: Option<&str>) -> SoftWrap {
|
||||||
self.language_setting(language, |settings| settings.soft_wrap)
|
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_family: cx.font_cache().load_family(&["Monaco"]).unwrap(),
|
||||||
buffer_font_size: 14.,
|
buffer_font_size: 14.,
|
||||||
default_buffer_font_size: 14.,
|
default_buffer_font_size: 14.,
|
||||||
|
cursor_blink: true,
|
||||||
hover_popover_enabled: true,
|
hover_popover_enabled: true,
|
||||||
show_completions_on_input: true,
|
show_completions_on_input: true,
|
||||||
vim_mode: false,
|
vim_mode: false,
|
||||||
|
@ -450,7 +450,6 @@ impl Settings {
|
||||||
editor_defaults: EditorSettings {
|
editor_defaults: EditorSettings {
|
||||||
tab_size: Some(4.try_into().unwrap()),
|
tab_size: Some(4.try_into().unwrap()),
|
||||||
hard_tabs: Some(false),
|
hard_tabs: Some(false),
|
||||||
cursor_blink: Some(true),
|
|
||||||
soft_wrap: Some(SoftWrap::None),
|
soft_wrap: Some(SoftWrap::None),
|
||||||
preferred_line_length: Some(80),
|
preferred_line_length: Some(80),
|
||||||
format_on_save: Some(FormatOnSave::On),
|
format_on_save: Some(FormatOnSave::On),
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue