From f358a79e2807ef1a44bb4f3cb4b11a41710eea44 Mon Sep 17 00:00:00 2001 From: Agus Zubiaga Date: Tue, 5 May 2026 16:54:05 -0300 Subject: [PATCH] editor: Fix panic in text_layout_details when editor has not been laid out MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes a panic at `Editor::text_layout_details` when called against an editor whose element has never been laid out (i.e., `set_style` has never been called, so the cached `style` is still `None`). We've seen this crash once through a helix motion. The exact production sequence isn't clear — for the editor to receive a vim action without ever having been drawn, the active item would have to have changed inside the same update tick that ends with the deferred `search_submit`, which is narrow but not impossible. --- crates/editor/src/editor.rs | 2 +- crates/vim/src/helix.rs | 55 +++++++++++++++++++++++++++++++++++++ 2 files changed, 56 insertions(+), 1 deletion(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index a57b705856040b..3334c26e59282c 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -6047,7 +6047,7 @@ impl Editor { pub fn text_layout_details(&self, window: &mut Window, cx: &mut App) -> TextLayoutDetails { TextLayoutDetails { text_system: window.text_system().clone(), - editor_style: self.style.clone().unwrap(), + editor_style: self.style.clone().unwrap_or_else(|| self.create_style(cx)), rem_size: window.rem_size(), scroll_anchor: self.scroll_manager.shared_scroll_anchor(cx), visible_rows: self.visible_line_count(), diff --git a/crates/vim/src/helix.rs b/crates/vim/src/helix.rs index d61b0547aef5ce..544a19167ac905 100644 --- a/crates/vim/src/helix.rs +++ b/crates/vim/src/helix.rs @@ -2960,6 +2960,61 @@ mod test { cx.assert_state("«ˇone» two three", Mode::HelixSelect); } + // Regression test for ZED-758: helix motions called + // `Editor::text_layout_details` on an editor whose `style` had never + // been set, panicking on `unwrap()`. + #[gpui::test] + async fn test_helix_motion_on_unrendered_editor(cx: &mut gpui::TestAppContext) { + use editor::{Editor, EditorMode, SelectionEffects}; + use multi_buffer::{MultiBuffer, MultiBufferOffset}; + + VimTestContext::init(cx); + cx.update(|cx| { + VimTestContext::init_keybindings(true, cx); + SettingsStore::update_global(cx, |store, cx| { + store.update_user_settings(cx, |s| { + s.vim_mode = Some(true); + s.helix_mode = Some(true); + }); + }); + }); + + let cx = cx.add_empty_window(); + + let editor = cx.update(|window, cx| { + use gpui::AppContext as _; + let buffer = MultiBuffer::build_simple("one two three", cx); + cx.new(|cx| { + let mut editor = Editor::new(EditorMode::full(), buffer, None, window, cx); + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { + s.select_ranges([MultiBufferOffset(4)..MultiBufferOffset(4)]) + }); + editor + }) + }); + + let vim = editor + .read_with(cx, |editor, _| editor.addon::().cloned()) + .expect("VimAddon should be auto-attached to new editors when vim mode is enabled"); + + cx.update(|window, cx| { + vim.entity.update(cx, |vim, cx| { + vim.switch_mode(Mode::HelixNormal, true, window, cx); + vim.helix_move_and_collapse(crate::motion::Motion::Left, None, window, cx); + }); + }); + + let cursor_offset = cx.update(|_, cx| { + editor.update(cx, |editor, cx| { + editor + .selections + .newest::(&editor.display_snapshot(cx)) + .head() + }) + }); + assert_eq!(cursor_offset, MultiBufferOffset(3)); + } + #[gpui::test] async fn test_helix_select_regex(cx: &mut gpui::TestAppContext) { let mut cx = VimTestContext::new(cx, true).await;