From 267052f891a8c5f4c5ef9b8b70130983ed05589f Mon Sep 17 00:00:00 2001 From: Julia Ryan Date: Mon, 20 Oct 2025 17:08:51 -0700 Subject: [PATCH] Editor end of input context (#40735) This is needed for #38914 and seems generally useful to have for contextual keybindings. Release Notes: - N/A --------- Co-authored-by: David Kleingeld --- crates/editor/src/editor.rs | 27 +++++++++++++++++++-------- crates/editor/src/editor_tests.rs | 21 +++++++++++++++++++++ 2 files changed, 40 insertions(+), 8 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 6923e5ca5bcbc02a1e2f857942afe97a650dd5cf..7955aac5e52d3a7fce7299b2d811636a9db2b085 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -2466,15 +2466,15 @@ impl Editor { }) } - pub fn key_context(&self, window: &Window, cx: &App) -> KeyContext { + pub fn key_context(&self, window: &mut Window, cx: &mut App) -> KeyContext { self.key_context_internal(self.has_active_edit_prediction(), window, cx) } fn key_context_internal( &self, has_active_edit_prediction: bool, - window: &Window, - cx: &App, + window: &mut Window, + cx: &mut App, ) -> KeyContext { let mut key_context = KeyContext::new_with_defaults(); key_context.add("Editor"); @@ -2551,6 +2551,17 @@ impl Editor { key_context.add("selection_mode"); } + let disjoint = self.selections.disjoint_anchors(); + let snapshot = self.snapshot(window, cx); + let snapshot = snapshot.buffer_snapshot(); + if self.mode == EditorMode::SingleLine + && let [selection] = disjoint + && selection.start == selection.end + && selection.end.to_offset(snapshot) == snapshot.len() + { + key_context.add("end_of_input"); + } + key_context } @@ -2604,8 +2615,8 @@ impl Editor { pub fn accept_edit_prediction_keybind( &self, accept_partial: bool, - window: &Window, - cx: &App, + window: &mut Window, + cx: &mut App, ) -> AcceptEditPredictionBinding { let key_context = self.key_context_internal(true, window, cx); let in_conflict = self.edit_prediction_in_conflict(); @@ -2747,7 +2758,7 @@ impl Editor { self.buffer().read(cx).title(cx) } - pub fn snapshot(&self, window: &mut Window, cx: &mut App) -> EditorSnapshot { + pub fn snapshot(&self, window: &Window, cx: &mut App) -> EditorSnapshot { let git_blame_gutter_max_author_length = self .render_git_blame_gutter(cx) .then(|| { @@ -9285,7 +9296,7 @@ impl Editor { fn render_edit_prediction_accept_keybind( &self, window: &mut Window, - cx: &App, + cx: &mut App, ) -> Option { let accept_binding = self.accept_edit_prediction_keybind(false, window, cx); let accept_keystroke = accept_binding.keystroke()?; @@ -9331,7 +9342,7 @@ impl Editor { label: impl Into, icon: Option, window: &mut Window, - cx: &App, + cx: &mut App, ) -> Stateful
{ let padding_right = if icon.is_some() { px(4.) } else { px(8.) }; diff --git a/crates/editor/src/editor_tests.rs b/crates/editor/src/editor_tests.rs index 239dc2969196d400a84824eabc0ca7e300851a35..3e520f4e2901ff378c64f405d082ad460349ec82 100644 --- a/crates/editor/src/editor_tests.rs +++ b/crates/editor/src/editor_tests.rs @@ -26838,3 +26838,24 @@ async fn test_copy_line_without_trailing_newline(cx: &mut TestAppContext) { cx.assert_editor_state("line1\nline2\nˇ"); } + +#[gpui::test] +async fn test_end_of_editor_context(cx: &mut TestAppContext) { + init_test(cx, |_| {}); + + let mut cx = EditorTestContext::new(cx).await; + + cx.set_state("line1\nline2ˇ"); + cx.update_editor(|e, window, cx| { + e.set_mode(EditorMode::SingleLine); + assert!(e.key_context(window, cx).contains("end_of_input")); + }); + cx.set_state("ˇline1\nline2"); + cx.update_editor(|e, window, cx| { + assert!(!e.key_context(window, cx).contains("end_of_input")); + }); + cx.set_state("line1ˇ\nline2"); + cx.update_editor(|e, window, cx| { + assert!(!e.key_context(window, cx).contains("end_of_input")); + }); +}