@@ -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
4852let s: popup .set_buf_var = funcref (' s:popup__set_buf_var' )
4953
5054function ! 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
6067let s: popup .scroll = funcref (' s:popup__scroll' )
6168
6269function ! 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
150160endfunction
151161let s: popup .get_opener_winnr = funcref (' s:popup__get_opener_winnr' )
152162
163+ function ! s: popup__vimpopup_keymaps () dict abort
164+ " TODO: make extensible 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+
153268function ! 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
228366let s: popup .open = funcref (' s:popup__open' )
229367
230368function ! 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'
0 commit comments