From a7b87a3691235f5c72610a7fa33737028a482585 Mon Sep 17 00:00:00 2001 From: Mark Woods Date: Tue, 27 May 2025 10:02:34 +0100 Subject: [PATCH 1/7] Add support for Vim 8.1+ popup windows This adds optional support for using Vim popup windows, which must be enabled by setting g:git_messenger_vimpopup_enabled to v:true, and works similar to Neovim with g:git_messenger_always_into_popup set to v:true It does require quite a bit of conditional code as you cannot enter a Vim popup, unlike a Neovim float or the preview window, but it works! Has been tested, manually, with Vim 8.1.2384 (first version of Vim to include support for +popupwin) and with Vim 9.1.950, both work fine. There are some limitations as you can't enter the popup, for example key mappings are specified in a filter function, not as buffer local maps, and there are likely to be some bugs as this is new, but generally it works as you probably expect, the popup opens in the same position as the Neovim floating window, the git-messenger mappings work, running the :GitMessenger command opens and closes the popup, mouse scrolling works, and some keys like CTRL-U, CTRL-D are mapped for scrolling up and down. --- README.md | 17 ++++ autoload/gitmessenger.vim | 2 + autoload/gitmessenger/blame.vim | 28 ++++-- autoload/gitmessenger/popup.vim | 168 +++++++++++++++++++++++++++++++- doc/git-messenger.txt | 17 ++++ syntax/gitmessengerpopup.vim | 6 +- 6 files changed, 222 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index 9fc5775..4ae3bf6 100644 --- a/README.md +++ b/README.md @@ -261,6 +261,23 @@ Setting `v:true` means adding margins in popup window. Blank lines at the top an content are inserted. And every line is indented with one whitespace character. Setting `v:false` to this variable removes all the margins. +#### `g:git_messenger_vimpopup_enabled` (Default: `v:false`) + +When this value is set to `v:true`, enables the use of popup windows in Vim. This feature is +experimental, and has some limitations as it is not possible to enter a popup window in Vim (unlike +floating windows in Neovim). Similar to using Neovim with `g:git_messenger_always_into_popup` set to `v:true`. + +#### `g:git_messenger_vimpopup_win_opts` (Default `{}`) + +Options passed to `popup_create()` on opening a popup window in Vim. This is useful when you want to +override some window options. See `:help popup-usage`. + +The following example will add a border to the window in the default style. + +```vim +let g:git_messenger_vimpopup_win_opts = { 'border': [] } +``` + ### Popup Window Highlight This plugin uses color definitions from your colorscheme for highlighting stuffs in popup window by diff --git a/autoload/gitmessenger.vim b/autoload/gitmessenger.vim index 83c920c..750fcd2 100644 --- a/autoload/gitmessenger.vim +++ b/autoload/gitmessenger.vim @@ -11,6 +11,8 @@ let g:git_messenger_max_popup_width = get(g:, 'git_messenger_max_popup_width', v let g:git_messenger_date_format = get(g:, 'git_messenger_date_format', '%c') let g:git_messenger_conceal_word_diff_marker = get(g:, 'git_messenger_conceal_word_diff_marker', 1) let g:git_messenger_floating_win_opts = get(g:, 'git_messenger_floating_win_opts', {}) +let g:git_messenger_vimpopup_enabled = get(g:, 'git_messenger_vimpopup_enabled', v:false) +let g:git_messenger_vimpopup_win_opts = get(g:, 'git_messenger_vimpopup_win_opts', {}) let g:git_messenger_popup_content_margins = get(g:, 'git_messenger_popup_content_margins', v:true) " All popup instances keyed by opener's bufnr to manage lifetime of popups diff --git a/autoload/gitmessenger/blame.vim b/autoload/gitmessenger/blame.vim index d42e7bc..7792d75 100644 --- a/autoload/gitmessenger/blame.vim +++ b/autoload/gitmessenger/blame.vim @@ -192,18 +192,26 @@ function! s:blame__reveal_diff(include_all, word_diff) dict abort endif " Remove diff hunks from popup - let saved = getpos('.') - try - keepjumps execute 1 - let diff_pattern = g:git_messenger_popup_content_margins ? '^ diff --git ' : '^diff --git ' - let diff_offset = g:git_messenger_popup_content_margins ? 2 : 3 - let diff_start = search(diff_pattern, 'ncW') - if diff_start > 1 + let diff_pattern = g:git_messenger_popup_content_margins ? '^ diff --git ' : '^diff --git ' + if has_key(self, 'popup') && has_key(self.popup, 'type') && self.popup.type ==# 'popup' + let diff_offset = g:git_messenger_popup_content_margins ? 1 : 2 + let diff_start = match(self.state.contents, diff_pattern) + if diff_start > 0 let self.state.contents = self.state.contents[ : diff_start-diff_offset] endif - finally - keepjumps call setpos('.', saved) - endtry + else + let diff_offset = g:git_messenger_popup_content_margins ? 2 : 3 + let saved = getpos('.') + try + keepjumps execute 1 + let diff_start = search(diff_pattern, 'ncW') + if diff_start > 1 + let self.state.contents = self.state.contents[ : diff_start-diff_offset] + endif + finally + keepjumps call setpos('.', saved) + endtry + endif if next_diff ==# 'none' let self.state.diff = next_diff diff --git a/autoload/gitmessenger/popup.vim b/autoload/gitmessenger/popup.vim index ed0d8ed..282834c 100644 --- a/autoload/gitmessenger/popup.vim +++ b/autoload/gitmessenger/popup.vim @@ -12,11 +12,15 @@ function! s:popup__close() dict abort return endif - let winnr = self.get_winnr() - if winnr > 0 - " Without this 'noautocmd', the BufWipeout event will be triggered and - " this function will be called again. - noautocmd execute winnr . 'wincmd c' + if self.type ==# 'popup' + call popup_close(self.win_id) + else + let winnr = self.get_winnr() + if winnr > 0 + " Without this 'noautocmd', the BufWipeout event will be triggered and + " this function will be called again. + noautocmd execute winnr . 'wincmd c' + endif endif unlet self.bufnr @@ -48,6 +52,9 @@ endfunction let s:popup.set_buf_var = funcref('s:popup__set_buf_var') function! s:popup__scroll(map) dict abort + if self.type ==# 'popup' + return + endif let winnr = self.get_winnr() if winnr == 0 return @@ -60,6 +67,9 @@ endfunction let s:popup.scroll = funcref('s:popup__scroll') function! s:popup__into() dict abort + if self.type ==# 'popup' + return + endif let winnr = self.get_winnr() if winnr == 0 return @@ -150,10 +160,138 @@ function! s:popup__get_opener_winnr() dict abort endfunction let s:popup.get_opener_winnr = funcref('s:popup__get_opener_winnr') +function! s:popup__vimpopup_keymaps() dict abort + " TODO: allow customisation via config var once happy with dict key names + return { + \ 'scroll_down_1': ["\", "\", "\"], + \ 'scroll_up_1': ["\", "\", "\"], + \ 'scroll_down_page': ["\", "\"], + \ 'scroll_up_page': ["\", "\"], + \ 'scroll_down_half': ["\"], + \ 'scroll_up_half': ["\"], + \ } +endfunction +let s:popup.vimpopup_keymaps = funcref('s:popup__vimpopup_keymaps') + +function! s:popup__vimpopup_win_filter(win_id, key) dict abort + " Note: default q handler assumes we are in the popup window, but in Vim we + " cannot enter the popup window, so we override the handling here for now + let keymaps = self.vimpopup_keymaps() + if a:key ==# 'q' + call self.close() + elseif a:key ==# '?' + call self.echo_help() + elseif has_key(self.opts, 'mappings') && has_key(self.opts.mappings, a:key) + call self.opts.mappings[a:key][0]() + elseif index(keymaps['scroll_down_1'], a:key) >= 0 + call win_execute(a:win_id, "normal! \") + elseif index(keymaps['scroll_up_1'], a:key) >= 0 + call win_execute(a:win_id, "normal! \") + elseif index(keymaps['scroll_down_page'], a:key) >= 0 + call win_execute(a:win_id, "normal! \") + elseif index(keymaps['scroll_up_page'], a:key) >= 0 + call win_execute(a:win_id, "normal! \") + elseif index(keymaps['scroll_down_half'], a:key) >= 0 + call win_execute(a:win_id, "normal! \") + elseif index(keymaps['scroll_up_half'], a:key) >= 0 + call win_execute(a:win_id, "normal! \") + elseif a:key ==? "\" + let pos = getmousepos() + if pos.winid == a:win_id + call win_execute(a:win_id, "normal! 3\") + else + return 0 + endif + elseif a:key ==? "\" + let pos = getmousepos() + if pos.winid == a:win_id + call win_execute(a:win_id, "normal! 3\") + else + return 0 + endif + else + return 0 + endif + return 1 +endfunction +let s:popup.vimpopup_win_filter = funcref('s:popup__vimpopup_win_filter') + +function! s:popup__vimpopup_win_opts(width, height) dict abort + " Note: calculations here are not the same as for Neovim floating window as + " Vim popup positioning relative to the editor window is slightly different, + " but the end result is that the popup is in same position in Vim as Neovim + if self.opened_at[0] + a:height <= &lines + let vert = 'top' + let row = self.opened_at[0] + 1 + else + let vert = 'bot' + let row = self.opened_at[0] - 1 + endif + + if self.opened_at[1] + a:width <= &columns + let hor = 'left' + let col = self.opened_at[1] + else + let hor = 'right' + let col = self.opened_at[1] + endif + + " Note: scrollbar disabled as seems buggy, even in Vim 9.1, scrollbar does + " not reliably appear when content does not fit, which means scroll is not + " always enabled when needed, so handle scroll in filter function instead. + " This now works the same as Neovim, no scrollbar, but mouse scroll works. + return extend({ + \ 'line': row, + \ 'col': col, + \ 'pos': vert . hor, + \ 'filtermode': 'n', + \ 'filter': self.vimpopup_win_filter, + \ 'minwidth': a:width, + \ 'maxwidth': a:width, + \ 'minheight': a:height, + \ 'maxheight': a:height, + \ 'scrollbar': v:false, + \ 'highlight': 'gitmessengerPopupNormal' + \ }, + \ g:git_messenger_vimpopup_win_opts) +endfunction +let s:popup.vimpopup_win_opts = funcref('s:popup__vimpopup_win_opts') + +function! s:popup__vimpopup_win_callback(win_id, result) dict abort + " Hacky custom cleanup for vimpopup, necessary as buffer never entered + silent! unlet b:__gitmessenger_popup + autocmd! plugin-git-messenger-close * + autocmd! plugin-git-messenger-buf-enter +endfunction +let s:popup.vimpopup_win_callback = funcref('s:popup__vimpopup_win_callback') + function! s:popup__open() dict abort let self.opened_at = s:get_global_pos() let self.opener_bufnr = bufnr('%') let self.opener_winid = win_getid() + + if g:git_messenger_vimpopup_enabled && has('popupwin') + let self.type = 'popup' + let [width, height] = self.window_size() + let win_id = popup_create('', self.vimpopup_win_opts(width, height)) + " Note: all local options are automatically set for new popup buffers + " in Vim so we only need to override a few, see :help popup-buffer + call win_execute(win_id, 'setlocal nomodified nofoldenable nomodeline conceallevel=2') + call popup_settext(win_id, self.contents) + call win_execute(win_id, 'setlocal nomodified nomodifiable') + if has_key(self.opts, 'filetype') + " Note: setbufvar() seems necessary to trigger Filetype autocmds + call setbufvar(winbufnr(win_id), '&filetype', self.opts.filetype) + endif + " Allow multiple invocations of :GitMessenger command to toggle popup + " See gitmessenger#popup#close_current_popup() and gitmessenger#new() + let b:__gitmessenger_popup = self " local to opener, removed by callback + call popup_setoptions(win_id, { 'callback': self.vimpopup_win_callback }) + let self.bufnr = winbufnr(win_id) + let self.win_id = win_id + return + endif + let self.type = s:floating_window_available ? 'floating' : 'preview' let [width, height] = self.window_size() @@ -228,6 +366,17 @@ endfunction let s:popup.open = funcref('s:popup__open') function! s:popup__update() dict abort + + if self.type ==# 'popup' + let [width, height] = self.window_size() + let win_id = self.win_id + call popup_setoptions(self.win_id, self.vimpopup_win_opts(width, height)) + call win_execute(win_id, 'setlocal modifiable') + call popup_settext(win_id, self.contents) + call win_execute(win_id, 'setlocal nomodified nomodifiable') + return + endif + " Note: `:noautocmd` to prevent BufLeave autocmd event (#13) " It should be ok because the cursor position is finally back to the first " position. @@ -291,6 +440,15 @@ function! s:popup__echo_help() dict abort call sort(maps, 'i') let maps += ['?'] + " When using Vim popup only one echo command output is shown in cmdline + if self.type ==# 'popup' + let lines = map(maps, {_, map -> + \ map . ' : ' . ( map ==# '?' ? 'Show this help' : self.opts.mappings[map][1] ) + \ }) + echo join(lines, "\n") + return + endif + for map in maps if map ==# '?' let desc = 'Show this help' diff --git a/doc/git-messenger.txt b/doc/git-messenger.txt index 6ce1632..29fa440 100644 --- a/doc/git-messenger.txt +++ b/doc/git-messenger.txt @@ -297,6 +297,23 @@ Setting |v:false| to this variable removes all the margins. Removing margins might be useful when you enable borders of popup window with |g:git_messenger_floating_win_opts|. +*g:git_messenger_vimpopup_enabled* (Default: |v:false|) + +When this value is set to |v:true|, enables the use of popup windows in Vim. +This feature is experimental, and has some limitations as it is not possible +to enter a popup window in Vim (unlike floating windows in Neovim). Similar +to using Neovim with |g:git_messenger_always_into_popup| set to |v:true|. + +*g:git_messenger_vimpopup_win_opts* (Default |{}|) + +Options passed to `popup_create()` on opening a popup window in Vim. This is +useful when you want to override some window options. See |popup-usage|. + +The following example will add a border to the window in the default style. +> + let g:git_messenger_vimpopup_win_opts = { 'border': [] } +< + ============================================================================== HIGHLIGHTS *git-messenger-highlights* diff --git a/syntax/gitmessengerpopup.vim b/syntax/gitmessengerpopup.vim index 10f8c0f..b2b15b5 100644 --- a/syntax/gitmessengerpopup.vim +++ b/syntax/gitmessengerpopup.vim @@ -35,7 +35,11 @@ hi def link gitmessengerHeader Identifier hi def link gitmessengerHash Comment hi def link gitmessengerHistory Constant hi def link gitmessengerEmail gitmessengerPopupNormal -hi def link gitmessengerPopupNormal NormalFloat +if has('nvim') + hi def link gitmessengerPopupNormal NormalFloat +else + hi def link gitmessengerPopupNormal Pmenu +endif hi def link diffOldFile diffFile hi def link diffNewFile diffFile From 68ee8f94ac413cfe1c52b87531ca248ccda4b437 Mon Sep 17 00:00:00 2001 From: Mark Woods Date: Fri, 30 May 2025 09:29:07 +0100 Subject: [PATCH 2/7] Prevent override of vim popup filter and callback These are required for the popup to function correctly, so make it an error to try and override them via g:git_messenger_vimpopup_win_opts --- autoload/gitmessenger/popup.vim | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/autoload/gitmessenger/popup.vim b/autoload/gitmessenger/popup.vim index 282834c..75db2cd 100644 --- a/autoload/gitmessenger/popup.vim +++ b/autoload/gitmessenger/popup.vim @@ -241,11 +241,14 @@ function! s:popup__vimpopup_win_opts(width, height) dict abort " always enabled when needed, so handle scroll in filter function instead. " This now works the same as Neovim, no scrollbar, but mouse scroll works. return extend({ + \ 'filter': self.vimpopup_win_filter, + \ 'callback': self.vimpopup_win_callback, + \ }, + \ extend({ \ 'line': row, \ 'col': col, \ 'pos': vert . hor, \ 'filtermode': 'n', - \ 'filter': self.vimpopup_win_filter, \ 'minwidth': a:width, \ 'maxwidth': a:width, \ 'minheight': a:height, @@ -253,7 +256,7 @@ function! s:popup__vimpopup_win_opts(width, height) dict abort \ 'scrollbar': v:false, \ 'highlight': 'gitmessengerPopupNormal' \ }, - \ g:git_messenger_vimpopup_win_opts) + \ g:git_messenger_vimpopup_win_opts), 'error') endfunction let s:popup.vimpopup_win_opts = funcref('s:popup__vimpopup_win_opts') @@ -286,7 +289,6 @@ function! s:popup__open() dict abort " Allow multiple invocations of :GitMessenger command to toggle popup " See gitmessenger#popup#close_current_popup() and gitmessenger#new() let b:__gitmessenger_popup = self " local to opener, removed by callback - call popup_setoptions(win_id, { 'callback': self.vimpopup_win_callback }) let self.bufnr = winbufnr(win_id) let self.win_id = win_id return From 051465e7ef1c7f8770893e995feaa20ec1c839bf Mon Sep 17 00:00:00 2001 From: Mark Woods Date: Sun, 1 Jun 2025 09:11:15 +0100 Subject: [PATCH 3/7] Fix vim popup bug when not closed on cursor move When g:git_messenger_close_on_cursor_moved is set to v:false, plugin-git-messenger-close autocmd won't exist, so use :silent! --- autoload/gitmessenger/popup.vim | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/autoload/gitmessenger/popup.vim b/autoload/gitmessenger/popup.vim index 75db2cd..ec7df05 100644 --- a/autoload/gitmessenger/popup.vim +++ b/autoload/gitmessenger/popup.vim @@ -263,8 +263,8 @@ let s:popup.vimpopup_win_opts = funcref('s:popup__vimpopup_win_opts') function! s:popup__vimpopup_win_callback(win_id, result) dict abort " Hacky custom cleanup for vimpopup, necessary as buffer never entered silent! unlet b:__gitmessenger_popup - autocmd! plugin-git-messenger-close * - autocmd! plugin-git-messenger-buf-enter + silent! autocmd! plugin-git-messenger-close * + silent! autocmd! plugin-git-messenger-buf-enter endfunction let s:popup.vimpopup_win_callback = funcref('s:popup__vimpopup_win_callback') From 620b710164dad3945d159560fd3ab2c348a5e40b Mon Sep 17 00:00:00 2001 From: Mark Woods Date: Thu, 5 Jun 2025 10:06:50 +0100 Subject: [PATCH 4/7] Emulate entering vim popup, more like Neovim float This updates the behaviour when Vim popup is enabled to work more like Neovim floating windows or the preview window. Entering a vim popup is emulated by initially disabling popup keymaps, and only enabling them once the popup is marked as "entered", which means the default behaviour is now almost identical for Vim popup, Neovim float and preview window. Running :GitMessenger opens the Vim popup, running it again enables the keymaps so you can navigate within the popup, running it again closes the popup, and g:git_messenger_always_into_popup is also respected. --- README.md | 4 +++- autoload/gitmessenger/popup.vim | 13 +++++++++++++ doc/git-messenger.txt | 7 +++++-- 3 files changed, 21 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 4ae3bf6..b902fa7 100644 --- a/README.md +++ b/README.md @@ -265,7 +265,9 @@ Setting `v:false` to this variable removes all the margins. When this value is set to `v:true`, enables the use of popup windows in Vim. This feature is experimental, and has some limitations as it is not possible to enter a popup window in Vim (unlike -floating windows in Neovim). Similar to using Neovim with `g:git_messenger_always_into_popup` set to `v:true`. +floating windows in Neovim). Entering a popup is emulated by initially disabling keyboard mappings +for the popup window, and only enabling them when it been marked as "entered", either by running the +`:GitMessenger` command a second time, or with `g:git_messenger_always_into_popup` set to `v:true`. #### `g:git_messenger_vimpopup_win_opts` (Default `{}`) diff --git a/autoload/gitmessenger/popup.vim b/autoload/gitmessenger/popup.vim index ec7df05..b6ad904 100644 --- a/autoload/gitmessenger/popup.vim +++ b/autoload/gitmessenger/popup.vim @@ -68,6 +68,7 @@ let s:popup.scroll = funcref('s:popup__scroll') function! s:popup__into() dict abort if self.type ==# 'popup' + let self.entered = v:true return endif let winnr = self.get_winnr() @@ -174,6 +175,10 @@ endfunction let s:popup.vimpopup_keymaps = funcref('s:popup__vimpopup_keymaps') function! s:popup__vimpopup_win_filter(win_id, key) dict abort + " if popup not marked as entered, do not handle any keys + if !self.entered + return 0 + endif " Note: default q handler assumes we are in the popup window, but in Vim we " cannot enter the popup window, so we override the handling here for now let keymaps = self.vimpopup_keymaps() @@ -286,6 +291,11 @@ function! s:popup__open() dict abort " Note: setbufvar() seems necessary to trigger Filetype autocmds call setbufvar(winbufnr(win_id), '&filetype', self.opts.filetype) endif + if has_key(self.opts, 'enter') && self.opts.enter + let self.entered = v:true + else + let self.entered = v:false + endif " Allow multiple invocations of :GitMessenger command to toggle popup " See gitmessenger#popup#close_current_popup() and gitmessenger#new() let b:__gitmessenger_popup = self " local to opener, removed by callback @@ -493,6 +503,9 @@ function! gitmessenger#popup#close_current_popup() abort if !exists('b:__gitmessenger_popup') return 0 endif + if b:__gitmessenger_popup.type ==# 'popup' && !b:__gitmessenger_popup.entered + return 0 + endif call b:__gitmessenger_popup.close() " TODO?: Back to opened_at pos by setpos() return 1 diff --git a/doc/git-messenger.txt b/doc/git-messenger.txt index 29fa440..13844ea 100644 --- a/doc/git-messenger.txt +++ b/doc/git-messenger.txt @@ -301,8 +301,11 @@ might be useful when you enable borders of popup window with When this value is set to |v:true|, enables the use of popup windows in Vim. This feature is experimental, and has some limitations as it is not possible -to enter a popup window in Vim (unlike floating windows in Neovim). Similar -to using Neovim with |g:git_messenger_always_into_popup| set to |v:true|. +to enter a popup window in Vim (unlike floating windows in Neovim). Entering a +popup is emulated by initially disabling keyboard mappings for the popup +window, and only enabling them when it been marked as "entered", either by +running the |:GitMessenger| command a second time, or with +|g:git_messenger_always_into_popup| set to |v:true|. *g:git_messenger_vimpopup_win_opts* (Default |{}|) From ef785fbe7ab3e154df4c4e471b5d22df165ff866 Mon Sep 17 00:00:00 2001 From: Mark Woods Date: Thu, 5 Jun 2025 10:53:03 +0100 Subject: [PATCH 5/7] Ensure vim popup callback called when leaving opener Needed to ensure the popup is not only closed, but opener buffer local variable storing a reference to the popup object/dict also removed. Without this switching windows or buffers while the popup is open leaves the buffer local var behind and breaks future calls to :GitMessenger --- autoload/gitmessenger/popup.vim | 2 ++ 1 file changed, 2 insertions(+) diff --git a/autoload/gitmessenger/popup.vim b/autoload/gitmessenger/popup.vim index b6ad904..51896bd 100644 --- a/autoload/gitmessenger/popup.vim +++ b/autoload/gitmessenger/popup.vim @@ -299,6 +299,8 @@ function! s:popup__open() dict abort " Allow multiple invocations of :GitMessenger command to toggle popup " See gitmessenger#popup#close_current_popup() and gitmessenger#new() let b:__gitmessenger_popup = self " local to opener, removed by callback + " Also ensure popup closed and callback called when leaving opener + autocmd BufWipeout,BufLeave ++once silent! call b:__gitmessenger_popup.close() let self.bufnr = winbufnr(win_id) let self.win_id = win_id return From d0808e7617782af5d3663919eff736b1e16b136d Mon Sep 17 00:00:00 2001 From: Mark Woods Date: Sat, 7 Jun 2025 12:39:10 +0100 Subject: [PATCH 6/7] Add mapping to yank current commit hash As you can't enter a Vim popup window, you can't easily yank the current commit hash, and that seems a natural thing to want to do (I certainly found myself wanting to do it soon after starting to use git-messenger). To address this, map 'c' to yank the current commit hash to v:register (kind of, while the code behaves the same as using v:register, it doesn't actually use v:register due to some clash with vim-cutlass). While this is added primarily to aid Vim popup users, it doesn't break anything for Neovim floating win or preview window users as the buffer is not modifiable, and it seems a handy mapping for all users anyway. --- README.md | 1 + autoload/gitmessenger/blame.vim | 17 +++++++++++++++++ doc/git-messenger.txt | 1 + 3 files changed, 19 insertions(+) diff --git a/README.md b/README.md index b902fa7..1070c0b 100644 --- a/README.md +++ b/README.md @@ -139,6 +139,7 @@ Following mappings are defined within popup window. | `D` | Toggle all unified diff hunks of the commit | | `r` | Toggle word diff hunks only in current file of the commit | | `R` | Toggle all word diff hunks of current commit | +| `c` | Yank/copy the current commit hash to `v:register` | | `?` | Show mappings help | ### Mappings diff --git a/autoload/gitmessenger/blame.vim b/autoload/gitmessenger/blame.vim index 7792d75..ea90393 100644 --- a/autoload/gitmessenger/blame.vim +++ b/autoload/gitmessenger/blame.vim @@ -63,6 +63,22 @@ function! s:blame__forward() dict abort endfunction let s:blame.forward = funcref('s:blame__forward') +function! s:blame__yank_hash() dict abort + " Note: v:register is blackhole here when vim-cutlass plugin used + " TODO: investigate further, it should be possible to use v:register + let register = '"' + if has('clipboard') + if stridx(&clipboard, 'unnamedplus') != -1 + let register = '+' + elseif stridx(&clipboard, 'unnamed') != -1 + let register = '*' + endif + endif + call setreg(register, self.state.commit) + echo 'git-messenger: yanked commit hash ' . self.state.commit +endfunction +let s:blame.yank_hash = funcref('s:blame__yank_hash') + function! s:blame__open_popup() dict abort if has_key(self, 'popup') && has_key(self.popup, 'bufnr') " Already popup is open. It means that now older commit is showing up. @@ -80,6 +96,7 @@ function! s:blame__open_popup() dict abort \ 'q': [{-> execute('close', '')}, 'Close popup window'], \ 'o': [funcref(self.back, [], self), 'Back to older commit'], \ 'O': [funcref(self.forward, [], self), 'Forward to newer commit'], + \ 'c': [funcref(self.yank_hash, [], self), 'Yank/copy current commit hash'], \ 'd': [funcref(self.reveal_diff, [v:false, v:false], self), "Toggle current file's diffs"], \ 'D': [funcref(self.reveal_diff, [v:true, v:false], self), 'Toggle all diffs'], \ 'r': [funcref(self.reveal_diff, [v:false, v:true], self), "Toggle current file's word diffs"], diff --git a/doc/git-messenger.txt b/doc/git-messenger.txt index 13844ea..9c3e5a4 100644 --- a/doc/git-messenger.txt +++ b/doc/git-messenger.txt @@ -137,6 +137,7 @@ COMMANDS *git-messenger-commands* | D | Toggle all unified diff hunks of the commit | | r | Toggle word diff hunks only in current file of the commit | | R | Toggle all word diff hunks of the commit | + | c | Yank/copy the current commit hash to |v:register| | | ? | Show mappings help | *:GitMessengerClose* From 89d9fe85eb5adf64a141f5b028150f1b3ec65217 Mon Sep 17 00:00:00 2001 From: Mark Woods Date: Mon, 9 Jun 2025 07:26:47 +0100 Subject: [PATCH 7/7] Add a test for new c mapping to yank commit hash --- test/all.vimspec | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/test/all.vimspec b/test/all.vimspec index 426e9c2..698b355 100644 --- a/test/all.vimspec +++ b/test/all.vimspec @@ -242,6 +242,33 @@ Describe git-messenger.vim Assert True(found, 'Got line: ' . string(getline(2))) End + It yanks current commit on c + GitMessenger + Assert IsNotNone(GetPopup()) + + GitMessenger + Assert Exists('b:__gitmessenger_popup') + + call setreg(v:register, 'foo') + Assert Equal(getreg(v:register), 'foo') + + normal c + + let lines = getline(1, '$') + let commit = lines[2] + let hash = matchstr(commit, '^ Commit: \+\zs[[:xdigit:]]\{7,}$') + Assert Equal(getreg(v:register), hash) + + " Confirm also works after moving to a different commit + normal o + normal c + + let lines = getline(1, '$') + let commit = lines[2] + let hash = matchstr(commit, '^ Commit: \+\zs[[:xdigit:]]\{7,}$') + Assert Equal(getreg(v:register), hash) + End + It does not cause #23 again " 1. Open the same buffer with multiple window " 2. Move cursror to the second window which opens the same