mirror of
https://github.com/zed-industries/zed.git
synced 2025-01-28 21:32:39 +00:00
Avoid panic by accessing view handle by global in wrong window (#2801)
View handles are window specific but the Vim global will be doing things in all windows, that would cause a panic when Vim attempted to update a status bar mode indicator in a background window Release Notes: - N/A
This commit is contained in:
commit
17fa15d989
5 changed files with 72 additions and 60 deletions
|
@ -1,20 +1,60 @@
|
||||||
use gpui::{elements::Label, AnyElement, Element, Entity, View, ViewContext};
|
use gpui::{
|
||||||
|
elements::{Empty, Label},
|
||||||
|
AnyElement, Element, Entity, Subscription, View, ViewContext,
|
||||||
|
};
|
||||||
|
use settings::SettingsStore;
|
||||||
use workspace::{item::ItemHandle, StatusItemView};
|
use workspace::{item::ItemHandle, StatusItemView};
|
||||||
|
|
||||||
use crate::state::Mode;
|
use crate::{state::Mode, Vim, VimEvent, VimModeSetting};
|
||||||
|
|
||||||
pub struct ModeIndicator {
|
pub struct ModeIndicator {
|
||||||
pub mode: Mode,
|
pub mode: Option<Mode>,
|
||||||
|
_subscription: Subscription,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ModeIndicator {
|
impl ModeIndicator {
|
||||||
pub fn new(mode: Mode) -> Self {
|
pub fn new(cx: &mut ViewContext<Self>) -> Self {
|
||||||
Self { mode }
|
let handle = cx.handle().downgrade();
|
||||||
|
|
||||||
|
let _subscription = cx.subscribe_global::<VimEvent, _>(move |&event, cx| {
|
||||||
|
if let Some(mode_indicator) = handle.upgrade(cx) {
|
||||||
|
match event {
|
||||||
|
VimEvent::ModeChanged { mode } => {
|
||||||
|
cx.update_window(mode_indicator.window_id(), |cx| {
|
||||||
|
mode_indicator.update(cx, move |mode_indicator, cx| {
|
||||||
|
mode_indicator.set_mode(mode, cx);
|
||||||
|
})
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
cx.observe_global::<SettingsStore, _>(move |mode_indicator, cx| {
|
||||||
|
if settings::get::<VimModeSetting>(cx).0 {
|
||||||
|
mode_indicator.mode = cx
|
||||||
|
.has_global::<Vim>()
|
||||||
|
.then(|| cx.global::<Vim>().state.mode);
|
||||||
|
} else {
|
||||||
|
mode_indicator.mode.take();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.detach();
|
||||||
|
|
||||||
|
// Vim doesn't exist in some tests
|
||||||
|
let mode = cx
|
||||||
|
.has_global::<Vim>()
|
||||||
|
.then(|| cx.global::<Vim>().state.mode);
|
||||||
|
|
||||||
|
Self {
|
||||||
|
mode,
|
||||||
|
_subscription,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_mode(&mut self, mode: Mode, cx: &mut ViewContext<Self>) {
|
pub fn set_mode(&mut self, mode: Mode, cx: &mut ViewContext<Self>) {
|
||||||
if mode != self.mode {
|
if self.mode != Some(mode) {
|
||||||
self.mode = mode;
|
self.mode = Some(mode);
|
||||||
cx.notify();
|
cx.notify();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -30,11 +70,16 @@ impl View for ModeIndicator {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
|
fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
|
||||||
|
let Some(mode) = self.mode.as_ref() else {
|
||||||
|
return Empty::new().into_any();
|
||||||
|
};
|
||||||
|
|
||||||
let theme = &theme::current(cx).workspace.status_bar;
|
let theme = &theme::current(cx).workspace.status_bar;
|
||||||
|
|
||||||
// we always choose text to be 12 monospace characters
|
// we always choose text to be 12 monospace characters
|
||||||
// so that as the mode indicator changes, the rest of the
|
// so that as the mode indicator changes, the rest of the
|
||||||
// UI stays still.
|
// UI stays still.
|
||||||
let text = match self.mode {
|
let text = match mode {
|
||||||
Mode::Normal => "-- NORMAL --",
|
Mode::Normal => "-- NORMAL --",
|
||||||
Mode::Insert => "-- INSERT --",
|
Mode::Insert => "-- INSERT --",
|
||||||
Mode::Visual { line: false } => "-- VISUAL --",
|
Mode::Visual { line: false } => "-- VISUAL --",
|
||||||
|
|
|
@ -215,7 +215,7 @@ async fn test_status_indicator(
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
cx.workspace(|_, cx| mode_indicator.read(cx).mode),
|
cx.workspace(|_, cx| mode_indicator.read(cx).mode),
|
||||||
Mode::Normal
|
Some(Mode::Normal)
|
||||||
);
|
);
|
||||||
|
|
||||||
// shows the correct mode
|
// shows the correct mode
|
||||||
|
@ -223,7 +223,7 @@ async fn test_status_indicator(
|
||||||
deterministic.run_until_parked();
|
deterministic.run_until_parked();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
cx.workspace(|_, cx| mode_indicator.read(cx).mode),
|
cx.workspace(|_, cx| mode_indicator.read(cx).mode),
|
||||||
Mode::Insert
|
Some(Mode::Insert)
|
||||||
);
|
);
|
||||||
|
|
||||||
// shows even in search
|
// shows even in search
|
||||||
|
@ -231,7 +231,7 @@ async fn test_status_indicator(
|
||||||
deterministic.run_until_parked();
|
deterministic.run_until_parked();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
cx.workspace(|_, cx| mode_indicator.read(cx).mode),
|
cx.workspace(|_, cx| mode_indicator.read(cx).mode),
|
||||||
Mode::Visual { line: false }
|
Some(Mode::Visual { line: false })
|
||||||
);
|
);
|
||||||
|
|
||||||
// hides if vim mode is disabled
|
// hides if vim mode is disabled
|
||||||
|
@ -239,15 +239,15 @@ async fn test_status_indicator(
|
||||||
deterministic.run_until_parked();
|
deterministic.run_until_parked();
|
||||||
cx.workspace(|workspace, cx| {
|
cx.workspace(|workspace, cx| {
|
||||||
let status_bar = workspace.status_bar().read(cx);
|
let status_bar = workspace.status_bar().read(cx);
|
||||||
let mode_indicator = status_bar.item_of_type::<ModeIndicator>();
|
let mode_indicator = status_bar.item_of_type::<ModeIndicator>().unwrap();
|
||||||
assert!(mode_indicator.is_none());
|
assert!(mode_indicator.read(cx).mode.is_none());
|
||||||
});
|
});
|
||||||
|
|
||||||
cx.enable_vim();
|
cx.enable_vim();
|
||||||
deterministic.run_until_parked();
|
deterministic.run_until_parked();
|
||||||
cx.workspace(|workspace, cx| {
|
cx.workspace(|workspace, cx| {
|
||||||
let status_bar = workspace.status_bar().read(cx);
|
let status_bar = workspace.status_bar().read(cx);
|
||||||
let mode_indicator = status_bar.item_of_type::<ModeIndicator>();
|
let mode_indicator = status_bar.item_of_type::<ModeIndicator>().unwrap();
|
||||||
assert!(mode_indicator.is_some());
|
assert!(mode_indicator.read(cx).mode.is_some());
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -43,6 +43,10 @@ impl<'a> VimTestContext<'a> {
|
||||||
toolbar.add_item(project_search_bar, cx);
|
toolbar.add_item(project_search_bar, cx);
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
workspace.status_bar().update(cx, |status_bar, cx| {
|
||||||
|
let vim_mode_indicator = cx.add_view(ModeIndicator::new);
|
||||||
|
status_bar.add_right_item(vim_mode_indicator, cx);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
Self { cx }
|
Self { cx }
|
||||||
|
|
|
@ -43,6 +43,11 @@ struct Number(u8);
|
||||||
actions!(vim, [Tab, Enter]);
|
actions!(vim, [Tab, Enter]);
|
||||||
impl_actions!(vim, [Number, SwitchMode, PushOperator]);
|
impl_actions!(vim, [Number, SwitchMode, PushOperator]);
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Debug)]
|
||||||
|
enum VimEvent {
|
||||||
|
ModeChanged { mode: Mode },
|
||||||
|
}
|
||||||
|
|
||||||
pub fn init(cx: &mut AppContext) {
|
pub fn init(cx: &mut AppContext) {
|
||||||
settings::register::<VimModeSetting>(cx);
|
settings::register::<VimModeSetting>(cx);
|
||||||
|
|
||||||
|
@ -121,8 +126,6 @@ pub fn observe_keystrokes(cx: &mut WindowContext) {
|
||||||
pub struct Vim {
|
pub struct Vim {
|
||||||
active_editor: Option<WeakViewHandle<Editor>>,
|
active_editor: Option<WeakViewHandle<Editor>>,
|
||||||
editor_subscription: Option<Subscription>,
|
editor_subscription: Option<Subscription>,
|
||||||
mode_indicator: Option<ViewHandle<ModeIndicator>>,
|
|
||||||
|
|
||||||
enabled: bool,
|
enabled: bool,
|
||||||
state: VimState,
|
state: VimState,
|
||||||
}
|
}
|
||||||
|
@ -181,9 +184,7 @@ impl Vim {
|
||||||
self.state.mode = mode;
|
self.state.mode = mode;
|
||||||
self.state.operator_stack.clear();
|
self.state.operator_stack.clear();
|
||||||
|
|
||||||
if let Some(mode_indicator) = &self.mode_indicator {
|
cx.emit_global(VimEvent::ModeChanged { mode });
|
||||||
mode_indicator.update(cx, |mode_indicator, cx| mode_indicator.set_mode(mode, cx))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sync editor settings like clip mode
|
// Sync editor settings like clip mode
|
||||||
self.sync_vim_settings(cx);
|
self.sync_vim_settings(cx);
|
||||||
|
@ -271,44 +272,6 @@ impl Vim {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn sync_mode_indicator(cx: &mut WindowContext) {
|
|
||||||
let Some(workspace) = cx.root_view()
|
|
||||||
.downcast_ref::<Workspace>()
|
|
||||||
.map(|workspace| workspace.downgrade()) else {
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
|
|
||||||
cx.spawn(|mut cx| async move {
|
|
||||||
workspace.update(&mut cx, |workspace, cx| {
|
|
||||||
Vim::update(cx, |vim, cx| {
|
|
||||||
workspace.status_bar().update(cx, |status_bar, cx| {
|
|
||||||
let current_position = status_bar.position_of_item::<ModeIndicator>();
|
|
||||||
|
|
||||||
if vim.enabled && current_position.is_none() {
|
|
||||||
if vim.mode_indicator.is_none() {
|
|
||||||
vim.mode_indicator =
|
|
||||||
Some(cx.add_view(|_| ModeIndicator::new(vim.state.mode)));
|
|
||||||
};
|
|
||||||
let mode_indicator = vim.mode_indicator.as_ref().unwrap();
|
|
||||||
let position = status_bar
|
|
||||||
.position_of_item::<language_selector::ActiveBufferLanguage>();
|
|
||||||
if let Some(position) = position {
|
|
||||||
status_bar.insert_item_after(position, mode_indicator.clone(), cx)
|
|
||||||
} else {
|
|
||||||
status_bar.add_left_item(mode_indicator.clone(), cx)
|
|
||||||
}
|
|
||||||
} else if !vim.enabled {
|
|
||||||
if let Some(position) = current_position {
|
|
||||||
status_bar.remove_item_at(position, cx)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
.detach_and_log_err(cx);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn set_enabled(&mut self, enabled: bool, cx: &mut AppContext) {
|
fn set_enabled(&mut self, enabled: bool, cx: &mut AppContext) {
|
||||||
if self.enabled != enabled {
|
if self.enabled != enabled {
|
||||||
self.enabled = enabled;
|
self.enabled = enabled;
|
||||||
|
@ -359,8 +322,6 @@ impl Vim {
|
||||||
self.unhook_vim_settings(editor, cx);
|
self.unhook_vim_settings(editor, cx);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
Vim::sync_mode_indicator(cx);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn unhook_vim_settings(&self, editor: &mut Editor, cx: &mut ViewContext<Editor>) {
|
fn unhook_vim_settings(&self, editor: &mut Editor, cx: &mut ViewContext<Editor>) {
|
||||||
|
|
|
@ -308,6 +308,7 @@ pub fn initialize_workspace(
|
||||||
);
|
);
|
||||||
let active_buffer_language =
|
let active_buffer_language =
|
||||||
cx.add_view(|_| language_selector::ActiveBufferLanguage::new(workspace));
|
cx.add_view(|_| language_selector::ActiveBufferLanguage::new(workspace));
|
||||||
|
let vim_mode_indicator = cx.add_view(|cx| vim::ModeIndicator::new(cx));
|
||||||
let feedback_button = cx.add_view(|_| {
|
let feedback_button = cx.add_view(|_| {
|
||||||
feedback::deploy_feedback_button::DeployFeedbackButton::new(workspace)
|
feedback::deploy_feedback_button::DeployFeedbackButton::new(workspace)
|
||||||
});
|
});
|
||||||
|
@ -319,6 +320,7 @@ pub fn initialize_workspace(
|
||||||
status_bar.add_right_item(feedback_button, cx);
|
status_bar.add_right_item(feedback_button, cx);
|
||||||
status_bar.add_right_item(copilot, cx);
|
status_bar.add_right_item(copilot, cx);
|
||||||
status_bar.add_right_item(active_buffer_language, cx);
|
status_bar.add_right_item(active_buffer_language, cx);
|
||||||
|
status_bar.add_right_item(vim_mode_indicator, cx);
|
||||||
status_bar.add_right_item(cursor_position, cx);
|
status_bar.add_right_item(cursor_position, cx);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue