From 459fe185a442c35f0c46a664ff91e6c0ac1269a1 Mon Sep 17 00:00:00 2001 From: Nikita Demin Date: Wed, 19 Aug 2026 21:35:38 +0000 Subject: [PATCH] editor: Add in_preview boolean context for keybindings (#61777) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # Objective - Add an `in_preview` context that is available when the current editor is in preview mode (opened from project panel or go to definition). - closes https://github.com/zed-industries/zed/discussions/61765 ## Solution - In `editor.rs`, added a check in `key_context()` that detects if the editor is the active preview item in its pane, and if so, adds `"in_preview"` to the key context. - Uses existing `workspace.pane_for_item_id()` and `pane.is_active_preview_item()` methods — no new APIs introduced. ## Testing - Added two tests in `editor_tests.rs`: - `test_in_preview_context_added_when_in_preview_mode` — verifies `in_preview` is present when editor is set as preview item. - `test_in_preview_context_not_added_when_not_in_preview_mode` — verifies `in_preview` is absent for regular (non-preview) tabs. - Tested on Linux. ## Self-Review Checklist: - [x] I've reviewed my own diff for quality, security, and reliability - [x] Unsafe blocks (if any) have justifying comments - [x] The content adheres to Zed's UI standards ([UX/UI](https://github.com/zed-industries/zed/blob/main/CONTRIBUTING.md#uiux-checklist) and [icon](https://github.com/zed-industries/zed/blob/main/crates/icons/README.md) guidelines) - [x] Tests cover the new/changed behavior - [x] Performance impact has been considered and is acceptable --- Release Notes: - Added `in_preview` keybinding context for when an editor is in preview mode. --- crates/editor/src/editor.rs | 8 ++++ crates/editor/src/editor_tests.rs | 71 +++++++++++++++++++++++++++++++ crates/workspace/src/pane.rs | 9 ++-- 3 files changed, 85 insertions(+), 3 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index a720ce5151..f5b485141a 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -2787,6 +2787,14 @@ impl Editor { key_context.add("diffs_expanded"); } + if self + .nav_history + .as_ref() + .is_some_and(ItemNavHistory::is_preview_item) + { + key_context.add("in_preview"); + } + key_context } diff --git a/crates/editor/src/editor_tests.rs b/crates/editor/src/editor_tests.rs index 379c5fcef5..062c869d78 100644 --- a/crates/editor/src/editor_tests.rs +++ b/crates/editor/src/editor_tests.rs @@ -43364,3 +43364,74 @@ async fn test_select_delimiters_expansion(cx: &mut TestAppContext) { cx.dispatch_action(SelectInsideDelimiters); cx.assert_editor_state("foo(«x, { a: 1 }ˇ»);"); } + +#[gpui::test] +async fn test_in_preview_key_context(cx: &mut TestAppContext) { + init_test(cx, |_| {}); + cx.update(|cx| { + SettingsStore::update_global(cx, |store, cx| { + store.update_user_settings(cx, |settings: &mut SettingsContent| { + settings.preview_tabs.get_or_insert_default().enabled = Some(true); + }); + }); + }); + + let fs = FakeFs::new(cx.executor()); + fs.insert_tree( + path!("/root"), + serde_json::json!({ + "test.txt": "hello", + }), + ) + .await; + let project = Project::test(fs, [path!("/root").as_ref()], cx).await; + let (multi_workspace, cx) = + cx.add_window_view(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx)); + let workspace = multi_workspace.read_with(cx, |mw, _| mw.workspace().clone()); + let worktree_id = workspace.update(cx, |workspace, cx| { + workspace.project().update(cx, |project, cx| { + project.worktrees(cx).next().unwrap().read(cx).id() + }) + }); + + let editor = workspace + .update_in(cx, |workspace, window, cx| { + workspace.open_path_preview( + (worktree_id, rel_path("test.txt")), + None, + true, + true, + true, + window, + cx, + ) + }) + .unwrap() + .await + .downcast::() + .unwrap(); + + let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone()); + pane.read_with(cx, |pane, _| { + assert!(pane.is_active_preview_item(editor.entity_id())); + }); + + cx.focus(&editor); + editor.update_in(cx, |editor, window, cx| { + assert!( + editor.key_context(window, cx).contains("in_preview"), + "Expected 'in_preview' context when the editor is the pane's preview item" + ); + }); + + pane.update(cx, |pane, _cx| { + pane.unpreview_item_if_preview(editor.entity_id()); + }); + + editor.update_in(cx, |editor, window, cx| { + assert!( + !editor.key_context(window, cx).contains("in_preview"), + "Expected no 'in_preview' context after the tab is unpreviewed" + ); + }); +} diff --git a/crates/workspace/src/pane.rs b/crates/workspace/src/pane.rs index 71075a56dd..f57f526751 100644 --- a/crates/workspace/src/pane.rs +++ b/crates/workspace/src/pane.rs @@ -4661,6 +4661,10 @@ impl Render for Pane { } impl ItemNavHistory { + pub fn is_preview_item(&self) -> bool { + self.history.0.lock().preview_item_id == Some(self.item.id()) + } + pub fn push( &mut self, data: Option, @@ -4672,19 +4676,18 @@ impl ItemNavHistory { .upgrade() .is_some_and(|item| item.include_in_nav_history()) { - let is_preview_item = self.history.0.lock().preview_item_id == Some(self.item.id()); + let is_preview_item = self.is_preview_item(); self.history .push(data, self.item.clone(), is_preview_item, row, cx); } } pub fn navigation_entry(&self, data: Option>) -> NavigationEntry { - let is_preview_item = self.history.0.lock().preview_item_id == Some(self.item.id()); NavigationEntry { item: self.item.clone(), data, timestamp: 0, - is_preview: is_preview_item, + is_preview: self.is_preview_item(), row: None, } }