Skip to content

Commit a7b87a3

Browse files
committed
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.
1 parent edc603d commit a7b87a3

File tree

6 files changed

+222
-16
lines changed

6 files changed

+222
-16
lines changed

README.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -261,6 +261,23 @@ Setting `v:true` means adding margins in popup window. Blank lines at the top an
261261
content are inserted. And every line is indented with one whitespace character.
262262
Setting `v:false` to this variable removes all the margins.
263263

264+
#### `g:git_messenger_vimpopup_enabled` (Default: `v:false`)
265+
266+
When this value is set to `v:true`, enables the use of popup windows in Vim. This feature is
267+
experimental, and has some limitations as it is not possible to enter a popup window in Vim (unlike
268+
floating windows in Neovim). Similar to using Neovim with `g:git_messenger_always_into_popup` set to `v:true`.
269+
270+
#### `g:git_messenger_vimpopup_win_opts` (Default `{}`)
271+
272+
Options passed to `popup_create()` on opening a popup window in Vim. This is useful when you want to
273+
override some window options. See `:help popup-usage`.
274+
275+
The following example will add a border to the window in the default style.
276+
277+
```vim
278+
let g:git_messenger_vimpopup_win_opts = { 'border': [] }
279+
```
280+
264281
### Popup Window Highlight
265282

266283
This plugin uses color definitions from your colorscheme for highlighting stuffs in popup window by

autoload/gitmessenger.vim

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ let g:git_messenger_max_popup_width = get(g:, 'git_messenger_max_popup_width', v
1111
let g:git_messenger_date_format = get(g:, 'git_messenger_date_format', '%c')
1212
let g:git_messenger_conceal_word_diff_marker = get(g:, 'git_messenger_conceal_word_diff_marker', 1)
1313
let g:git_messenger_floating_win_opts = get(g:, 'git_messenger_floating_win_opts', {})
14+
let g:git_messenger_vimpopup_enabled = get(g:, 'git_messenger_vimpopup_enabled', v:false)
15+
let g:git_messenger_vimpopup_win_opts = get(g:, 'git_messenger_vimpopup_win_opts', {})
1416
let g:git_messenger_popup_content_margins = get(g:, 'git_messenger_popup_content_margins', v:true)
1517

1618
" All popup instances keyed by opener's bufnr to manage lifetime of popups

autoload/gitmessenger/blame.vim

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -192,18 +192,26 @@ function! s:blame__reveal_diff(include_all, word_diff) dict abort
192192
endif
193193

194194
" Remove diff hunks from popup
195-
let saved = getpos('.')
196-
try
197-
keepjumps execute 1
198-
let diff_pattern = g:git_messenger_popup_content_margins ? '^ diff --git ' : '^diff --git '
199-
let diff_offset = g:git_messenger_popup_content_margins ? 2 : 3
200-
let diff_start = search(diff_pattern, 'ncW')
201-
if diff_start > 1
195+
let diff_pattern = g:git_messenger_popup_content_margins ? '^ diff --git ' : '^diff --git '
196+
if has_key(self, 'popup') && has_key(self.popup, 'type') && self.popup.type ==# 'popup'
197+
let diff_offset = g:git_messenger_popup_content_margins ? 1 : 2
198+
let diff_start = match(self.state.contents, diff_pattern)
199+
if diff_start > 0
202200
let self.state.contents = self.state.contents[ : diff_start-diff_offset]
203201
endif
204-
finally
205-
keepjumps call setpos('.', saved)
206-
endtry
202+
else
203+
let diff_offset = g:git_messenger_popup_content_margins ? 2 : 3
204+
let saved = getpos('.')
205+
try
206+
keepjumps execute 1
207+
let diff_start = search(diff_pattern, 'ncW')
208+
if diff_start > 1
209+
let self.state.contents = self.state.contents[ : diff_start-diff_offset]
210+
endif
211+
finally
212+
keepjumps call setpos('.', saved)
213+
endtry
214+
endif
207215

208216
if next_diff ==# 'none'
209217
let self.state.diff = next_diff

autoload/gitmessenger/popup.vim

Lines changed: 163 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,15 @@ function! s:popup__close() dict abort
1212
return
1313
endif
1414

15-
let winnr = self.get_winnr()
16-
if winnr > 0
17-
" Without this 'noautocmd', the BufWipeout event will be triggered and
18-
" this function will be called again.
19-
noautocmd execute winnr . 'wincmd c'
15+
if self.type ==# 'popup'
16+
call popup_close(self.win_id)
17+
else
18+
let winnr = self.get_winnr()
19+
if winnr > 0
20+
" Without this 'noautocmd', the BufWipeout event will be triggered and
21+
" this function will be called again.
22+
noautocmd execute winnr . 'wincmd c'
23+
endif
2024
endif
2125

2226
unlet self.bufnr
@@ -48,6 +52,9 @@ endfunction
4852
let s:popup.set_buf_var = funcref('s:popup__set_buf_var')
4953

5054
function! s:popup__scroll(map) dict abort
55+
if self.type ==# 'popup'
56+
return
57+
endif
5158
let winnr = self.get_winnr()
5259
if winnr == 0
5360
return
@@ -60,6 +67,9 @@ endfunction
6067
let s:popup.scroll = funcref('s:popup__scroll')
6168

6269
function! s:popup__into() dict abort
70+
if self.type ==# 'popup'
71+
return
72+
endif
6373
let winnr = self.get_winnr()
6474
if winnr == 0
6575
return
@@ -150,10 +160,138 @@ function! s:popup__get_opener_winnr() dict abort
150160
endfunction
151161
let s:popup.get_opener_winnr = funcref('s:popup__get_opener_winnr')
152162

163+
function! s:popup__vimpopup_keymaps() dict abort
164+
" TODO: allow customisation via config var once happy with dict key names
165+
return {
166+
\ 'scroll_down_1': ["\<c-e>", "\<c-n>", "\<Down>"],
167+
\ 'scroll_up_1': ["\<c-y>", "\<c-p>", "\<Up>"],
168+
\ 'scroll_down_page': ["\<c-f>", "\<PageDown>"],
169+
\ 'scroll_up_page': ["\<c-b>", "\<PageUp>"],
170+
\ 'scroll_down_half': ["\<c-d>"],
171+
\ 'scroll_up_half': ["\<c-u>"],
172+
\ }
173+
endfunction
174+
let s:popup.vimpopup_keymaps = funcref('s:popup__vimpopup_keymaps')
175+
176+
function! s:popup__vimpopup_win_filter(win_id, key) dict abort
177+
" Note: default q handler assumes we are in the popup window, but in Vim we
178+
" cannot enter the popup window, so we override the handling here for now
179+
let keymaps = self.vimpopup_keymaps()
180+
if a:key ==# 'q'
181+
call self.close()
182+
elseif a:key ==# '?'
183+
call self.echo_help()
184+
elseif has_key(self.opts, 'mappings') && has_key(self.opts.mappings, a:key)
185+
call self.opts.mappings[a:key][0]()
186+
elseif index(keymaps['scroll_down_1'], a:key) >= 0
187+
call win_execute(a:win_id, "normal! \<c-e>")
188+
elseif index(keymaps['scroll_up_1'], a:key) >= 0
189+
call win_execute(a:win_id, "normal! \<c-y>")
190+
elseif index(keymaps['scroll_down_page'], a:key) >= 0
191+
call win_execute(a:win_id, "normal! \<c-f>")
192+
elseif index(keymaps['scroll_up_page'], a:key) >= 0
193+
call win_execute(a:win_id, "normal! \<c-b>")
194+
elseif index(keymaps['scroll_down_half'], a:key) >= 0
195+
call win_execute(a:win_id, "normal! \<c-d>")
196+
elseif index(keymaps['scroll_up_half'], a:key) >= 0
197+
call win_execute(a:win_id, "normal! \<c-u>")
198+
elseif a:key ==? "\<ScrollWheelUp>"
199+
let pos = getmousepos()
200+
if pos.winid == a:win_id
201+
call win_execute(a:win_id, "normal! 3\<c-y>")
202+
else
203+
return 0
204+
endif
205+
elseif a:key ==? "\<ScrollWheelDown>"
206+
let pos = getmousepos()
207+
if pos.winid == a:win_id
208+
call win_execute(a:win_id, "normal! 3\<c-e>")
209+
else
210+
return 0
211+
endif
212+
else
213+
return 0
214+
endif
215+
return 1
216+
endfunction
217+
let s:popup.vimpopup_win_filter = funcref('s:popup__vimpopup_win_filter')
218+
219+
function! s:popup__vimpopup_win_opts(width, height) dict abort
220+
" Note: calculations here are not the same as for Neovim floating window as
221+
" Vim popup positioning relative to the editor window is slightly different,
222+
" but the end result is that the popup is in same position in Vim as Neovim
223+
if self.opened_at[0] + a:height <= &lines
224+
let vert = 'top'
225+
let row = self.opened_at[0] + 1
226+
else
227+
let vert = 'bot'
228+
let row = self.opened_at[0] - 1
229+
endif
230+
231+
if self.opened_at[1] + a:width <= &columns
232+
let hor = 'left'
233+
let col = self.opened_at[1]
234+
else
235+
let hor = 'right'
236+
let col = self.opened_at[1]
237+
endif
238+
239+
" Note: scrollbar disabled as seems buggy, even in Vim 9.1, scrollbar does
240+
" not reliably appear when content does not fit, which means scroll is not
241+
" always enabled when needed, so handle scroll in filter function instead.
242+
" This now works the same as Neovim, no scrollbar, but mouse scroll works.
243+
return extend({
244+
\ 'line': row,
245+
\ 'col': col,
246+
\ 'pos': vert . hor,
247+
\ 'filtermode': 'n',
248+
\ 'filter': self.vimpopup_win_filter,
249+
\ 'minwidth': a:width,
250+
\ 'maxwidth': a:width,
251+
\ 'minheight': a:height,
252+
\ 'maxheight': a:height,
253+
\ 'scrollbar': v:false,
254+
\ 'highlight': 'gitmessengerPopupNormal'
255+
\ },
256+
\ g:git_messenger_vimpopup_win_opts)
257+
endfunction
258+
let s:popup.vimpopup_win_opts = funcref('s:popup__vimpopup_win_opts')
259+
260+
function! s:popup__vimpopup_win_callback(win_id, result) dict abort
261+
" Hacky custom cleanup for vimpopup, necessary as buffer never entered
262+
silent! unlet b:__gitmessenger_popup
263+
autocmd! plugin-git-messenger-close * <buffer>
264+
autocmd! plugin-git-messenger-buf-enter
265+
endfunction
266+
let s:popup.vimpopup_win_callback = funcref('s:popup__vimpopup_win_callback')
267+
153268
function! s:popup__open() dict abort
154269
let self.opened_at = s:get_global_pos()
155270
let self.opener_bufnr = bufnr('%')
156271
let self.opener_winid = win_getid()
272+
273+
if g:git_messenger_vimpopup_enabled && has('popupwin')
274+
let self.type = 'popup'
275+
let [width, height] = self.window_size()
276+
let win_id = popup_create('', self.vimpopup_win_opts(width, height))
277+
" Note: all local options are automatically set for new popup buffers
278+
" in Vim so we only need to override a few, see :help popup-buffer
279+
call win_execute(win_id, 'setlocal nomodified nofoldenable nomodeline conceallevel=2')
280+
call popup_settext(win_id, self.contents)
281+
call win_execute(win_id, 'setlocal nomodified nomodifiable')
282+
if has_key(self.opts, 'filetype')
283+
" Note: setbufvar() seems necessary to trigger Filetype autocmds
284+
call setbufvar(winbufnr(win_id), '&filetype', self.opts.filetype)
285+
endif
286+
" Allow multiple invocations of :GitMessenger command to toggle popup
287+
" See gitmessenger#popup#close_current_popup() and gitmessenger#new()
288+
let b:__gitmessenger_popup = self " local to opener, removed by callback
289+
call popup_setoptions(win_id, { 'callback': self.vimpopup_win_callback })
290+
let self.bufnr = winbufnr(win_id)
291+
let self.win_id = win_id
292+
return
293+
endif
294+
157295
let self.type = s:floating_window_available ? 'floating' : 'preview'
158296

159297
let [width, height] = self.window_size()
@@ -228,6 +366,17 @@ endfunction
228366
let s:popup.open = funcref('s:popup__open')
229367

230368
function! s:popup__update() dict abort
369+
370+
if self.type ==# 'popup'
371+
let [width, height] = self.window_size()
372+
let win_id = self.win_id
373+
call popup_setoptions(self.win_id, self.vimpopup_win_opts(width, height))
374+
call win_execute(win_id, 'setlocal modifiable')
375+
call popup_settext(win_id, self.contents)
376+
call win_execute(win_id, 'setlocal nomodified nomodifiable')
377+
return
378+
endif
379+
231380
" Note: `:noautocmd` to prevent BufLeave autocmd event (#13)
232381
" It should be ok because the cursor position is finally back to the first
233382
" position.
@@ -291,6 +440,15 @@ function! s:popup__echo_help() dict abort
291440
call sort(maps, 'i')
292441
let maps += ['?']
293442

443+
" When using Vim popup only one echo command output is shown in cmdline
444+
if self.type ==# 'popup'
445+
let lines = map(maps, {_, map ->
446+
\ map . ' : ' . ( map ==# '?' ? 'Show this help' : self.opts.mappings[map][1] )
447+
\ })
448+
echo join(lines, "\n")
449+
return
450+
endif
451+
294452
for map in maps
295453
if map ==# '?'
296454
let desc = 'Show this help'

doc/git-messenger.txt

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -297,6 +297,23 @@ Setting |v:false| to this variable removes all the margins. Removing margins
297297
might be useful when you enable borders of popup window with
298298
|g:git_messenger_floating_win_opts|.
299299

300+
*g:git_messenger_vimpopup_enabled* (Default: |v:false|)
301+
302+
When this value is set to |v:true|, enables the use of popup windows in Vim.
303+
This feature is experimental, and has some limitations as it is not possible
304+
to enter a popup window in Vim (unlike floating windows in Neovim). Similar
305+
to using Neovim with |g:git_messenger_always_into_popup| set to |v:true|.
306+
307+
*g:git_messenger_vimpopup_win_opts* (Default |{}|)
308+
309+
Options passed to `popup_create()` on opening a popup window in Vim. This is
310+
useful when you want to override some window options. See |popup-usage|.
311+
312+
The following example will add a border to the window in the default style.
313+
>
314+
let g:git_messenger_vimpopup_win_opts = { 'border': [] }
315+
<
316+
300317
==============================================================================
301318
HIGHLIGHTS *git-messenger-highlights*
302319

syntax/gitmessengerpopup.vim

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,11 @@ hi def link gitmessengerHeader Identifier
3535
hi def link gitmessengerHash Comment
3636
hi def link gitmessengerHistory Constant
3737
hi def link gitmessengerEmail gitmessengerPopupNormal
38-
hi def link gitmessengerPopupNormal NormalFloat
38+
if has('nvim')
39+
hi def link gitmessengerPopupNormal NormalFloat
40+
else
41+
hi def link gitmessengerPopupNormal Pmenu
42+
endif
3943

4044
hi def link diffOldFile diffFile
4145
hi def link diffNewFile diffFile

0 commit comments

Comments
 (0)