diff --git a/assets/keymaps/default-linux.json b/assets/keymaps/default-linux.json index 377a26242b..4918e654fc 100644 --- a/assets/keymaps/default-linux.json +++ b/assets/keymaps/default-linux.json @@ -483,6 +483,7 @@ "ctrl-k ctrl-d": ["editor::SelectNext", { "replace_newest": true }], // editor.action.moveSelectionToNextFindMatch / find_under_expand_skip "ctrl-k ctrl-shift-d": ["editor::SelectPrevious", { "replace_newest": true }], // editor.action.moveSelectionToPreviousFindMatch "ctrl-k ctrl-i": "editor::Hover", + "ctrl-k ctrl-b": "editor::BlameHover", "ctrl-/": ["editor::ToggleComments", { "advance_downwards": false }], "f8": ["editor::GoToDiagnostic", { "severity": { "min": "hint", "max": "error" } }], "shift-f8": ["editor::GoToPreviousDiagnostic", { "severity": { "min": "hint", "max": "error" } }], diff --git a/assets/keymaps/default-macos.json b/assets/keymaps/default-macos.json index 712d73b8ec..60f29b1da1 100644 --- a/assets/keymaps/default-macos.json +++ b/assets/keymaps/default-macos.json @@ -537,6 +537,7 @@ "ctrl-cmd-d": ["editor::SelectPrevious", { "replace_newest": false }], // editor.action.addSelectionToPreviousFindMatch "cmd-k ctrl-cmd-d": ["editor::SelectPrevious", { "replace_newest": true }], // editor.action.moveSelectionToPreviousFindMatch "cmd-k cmd-i": "editor::Hover", + "cmd-k cmd-b": "editor::BlameHover", "cmd-/": ["editor::ToggleComments", { "advance_downwards": false }], "f8": ["editor::GoToDiagnostic", { "severity": { "min": "hint", "max": "error" } }], "shift-f8": ["editor::GoToPreviousDiagnostic", { "severity": { "min": "hint", "max": "error" } }], diff --git a/assets/keymaps/vim.json b/assets/keymaps/vim.json index 04e6b0bcd4..d0cf4621a5 100644 --- a/assets/keymaps/vim.json +++ b/assets/keymaps/vim.json @@ -124,6 +124,7 @@ "g r a": "editor::ToggleCodeActions", "g g": "vim::StartOfDocument", "g h": "editor::Hover", + "g B": "editor::BlameHover", "g t": "pane::ActivateNextItem", "g shift-t": "pane::ActivatePreviousItem", "g d": "editor::GoToDefinition", diff --git a/crates/editor/src/actions.rs b/crates/editor/src/actions.rs index 87463d246d..8557b57f46 100644 --- a/crates/editor/src/actions.rs +++ b/crates/editor/src/actions.rs @@ -322,6 +322,8 @@ actions!( ApplyDiffHunk, /// Deletes the character before the cursor. Backspace, + /// Shows git blame information for the current line. + BlameHover, /// Cancels the current operation. Cancel, /// Cancels the running flycheck operation. diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index b8dcdd35e0..1f985eeb7c 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -950,6 +950,7 @@ struct InlineBlamePopover { hide_task: Option>, popover_bounds: Option>, popover_state: InlineBlamePopoverState, + keyboard_grace: bool, } enum SelectionDragState { @@ -6517,21 +6518,55 @@ impl Editor { } } + pub fn blame_hover(&mut self, _: &BlameHover, window: &mut Window, cx: &mut Context) { + let snapshot = self.snapshot(window, cx); + let cursor = self.selections.newest::(cx).head(); + let Some((buffer, point, _)) = snapshot.buffer_snapshot.point_to_buffer_point(cursor) + else { + return; + }; + + let Some(blame) = self.blame.as_ref() else { + return; + }; + + let row_info = RowInfo { + buffer_id: Some(buffer.remote_id()), + buffer_row: Some(point.row), + ..Default::default() + }; + let Some(blame_entry) = blame + .update(cx, |blame, cx| blame.blame_for_rows(&[row_info], cx).next()) + .flatten() + else { + return; + }; + + let anchor = self.selections.newest_anchor().head(); + let position = self.to_pixel_point(anchor, &snapshot, window); + if let (Some(position), Some(last_bounds)) = (position, self.last_bounds) { + self.show_blame_popover(&blame_entry, position + last_bounds.origin, true, cx); + }; + } + fn show_blame_popover( &mut self, blame_entry: &BlameEntry, position: gpui::Point, + ignore_timeout: bool, cx: &mut Context, ) { if let Some(state) = &mut self.inline_blame_popover { state.hide_task.take(); } else { - let delay = EditorSettings::get_global(cx).hover_popover_delay; + let blame_popover_delay = EditorSettings::get_global(cx).hover_popover_delay; let blame_entry = blame_entry.clone(); let show_task = cx.spawn(async move |editor, cx| { - cx.background_executor() - .timer(std::time::Duration::from_millis(delay)) - .await; + if !ignore_timeout { + cx.background_executor() + .timer(std::time::Duration::from_millis(blame_popover_delay)) + .await; + } editor .update(cx, |editor, cx| { editor.inline_blame_popover_show_task.take(); @@ -6560,6 +6595,7 @@ impl Editor { commit_message: details, markdown, }, + keyboard_grace: ignore_timeout, }); cx.notify(); }) diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index fef185bb15..cbff544c7e 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -216,6 +216,7 @@ impl EditorElement { register_action(editor, window, Editor::newline_above); register_action(editor, window, Editor::newline_below); register_action(editor, window, Editor::backspace); + register_action(editor, window, Editor::blame_hover); register_action(editor, window, Editor::delete); register_action(editor, window, Editor::tab); register_action(editor, window, Editor::backtab); @@ -1143,10 +1144,14 @@ impl EditorElement { .as_ref() .and_then(|state| state.popover_bounds) .map_or(false, |bounds| bounds.contains(&event.position)); + let keyboard_grace = editor + .inline_blame_popover + .as_ref() + .map_or(false, |state| state.keyboard_grace); if mouse_over_inline_blame || mouse_over_popover { - editor.show_blame_popover(&blame_entry, event.position, cx); - } else { + editor.show_blame_popover(&blame_entry, event.position, false, cx); + } else if !keyboard_grace { editor.hide_blame_popover(cx); } } else {