@@ -27,11 +27,14 @@ local M = {
2727 prev_msg = ' ' , -- Concatenated content of the previous message.
2828 virt = { -- Stored virt_text state.
2929 last = { {}, {}, {}, {} }, --- @type MsgContent[] status in last cmdline row.
30- msg = { {}, {} }, --- @type MsgContent[] [ (x )] indicators in message window.
30+ msg = { {}, {} }, --- @type MsgContent[] [ (x )] indicators in msg window.
31+ top = { {} }, --- @type MsgContent[] [ +x] top indicator in dialog window.
32+ bot = { {} }, --- @type MsgContent[] [ +x] bottom indicator in dialog window.
3133 idx = { mode = 1 , search = 2 , cmd = 3 , ruler = 4 , spill = 1 , dupe = 2 },
32- ids = {}, --- @type { [ ' last' | ' msg' ] : integer ? } Table of mark IDs.
34+ ids = {}, --- @type { [ ' last' | ' msg' | ' top ' | ' bot ' ] : integer ? } Table of mark IDs.
3335 delayed = false , -- Whether placement of 'last' virt_text is delayed.
3436 },
37+ on_dialog_key = 0 , -- vim.on_key namespace for paging in the dialog window.
3538}
3639
3740function M .msg :close ()
6669local cmd_on_key = nil
6770--- Place or delete a virtual text mark in the cmdline or message window.
6871---
69- --- @param type ' last' | ' msg'
70- --- @param tar ? ' cmd' | ' msg'
72+ --- @param type ' last' | ' msg' | ' top ' | ' bot '
73+ --- @param tar ? ' cmd' | ' msg' | ' dialog '
7174local function set_virttext (type , tar )
7275 if (type == ' last' and (ext .cmdheight == 0 or M .virt .delayed )) or cmd_on_key then
7376 return -- Don't show virtual text while cmdline, error or full message in cmdline is shown.
7477 end
7578
7679 -- Concatenate the components of M.virt[type] and calculate the concatenated width.
7780 local width , chunks = 0 , {} --- @type integer , [string , integer | string][]
78- local contents = type == ' last ' and M .virt . last or M . virt . msg
81+ local contents = M .virt [ type ] --- @type MsgContent[]
7982 for _ , content in ipairs (contents ) do
8083 for _ , chunk in ipairs (content ) do
8184 chunks [# chunks + 1 ] = { chunk [2 ], chunk [3 ] }
8285 width = width + api .nvim_strwidth (chunk [2 ])
8386 end
8487 end
88+ tar = tar or type == ' msg' and ext .cfg .msg .target or ' cmd'
8589
8690 if M .virt .ids [type ] and # chunks == 0 then
87- api .nvim_buf_del_extmark (ext .bufs .cmd , ext .ns , M .virt .ids [type ])
88- M .virt .ids [type ] = nil
91+ api .nvim_buf_del_extmark (ext .bufs [tar ], ext .ns , M .virt .ids [type ])
8992 M .cmd .last_col = type == ' last' and o .columns or M .cmd .last_col
93+ M .virt .ids [type ] = nil
9094 elseif # chunks > 0 then
91- tar = tar or type == ' msg' and ext .cfg .msg .target or ' cmd'
9295 local win = ext .wins [tar ]
96+ local line = (tar == ' msg' or type == ' top' ) and ' w0' or type == ' bot' and ' w$'
97+ local srow = line and fn .line (line , ext .wins .dialog ) - 1
9398 local erow = tar == ' cmd' and math.min (M .cmd .msg_row , api .nvim_buf_line_count (ext .bufs .cmd ) - 1 )
9499 local texth = api .nvim_win_text_height (win , {
95- max_height = api .nvim_win_get_height (win ),
96- start_row = tar == ' msg ' and fn . line ( ' w0 ' , ext . wins . msg ) - 1 or nil ,
100+ max_height = ( type == ' top ' or type == ' bot ' ) and 1 or api .nvim_win_get_height (win ),
101+ start_row = srow or nil ,
97102 end_row = erow or nil ,
98103 })
99104 local row = texth .end_row
100105 local col = fn .virtcol2col (win , row + 1 , texth .end_vcol )
101106 local scol = fn .screenpos (win , row + 1 , col ).col --- @type integer
102107
103- if type == ' msg ' then
108+ if type ~= ' last ' then
104109 -- Calculate at which column to place the virt_text such that it is at the end
105110 -- of the last visible message line, overlapping the message text if necessary,
106111 -- but not overlapping the 'last' virt_text.
@@ -115,7 +120,7 @@ local function set_virttext(type, tar)
115120 M .msg .width = maxwidth
116121 end
117122
118- local mwidth = tar == ' msg' and M .msg .width or M .cmd .last_col
123+ local mwidth = tar == ' msg' and M .msg .width or tar == ' dialog ' and o . columns or M .cmd .last_col
119124 if scol - offset + width > mwidth then
120125 col = fn .virtcol2col (win , row + 1 , texth .end_vcol - (scol - offset + width - mwidth ))
121126 end
@@ -231,15 +236,15 @@ function M.show_msg(tar, content, replace_last, append)
231236 for _ , chunk in ipairs (content ) do
232237 msg = msg .. chunk [2 ]
233238 end
234- dupe = (msg == M .prev_msg and ext .cmd .row == 0 and M .dupe + 1 or 0 )
239+ dupe = (msg == M .prev_msg and ext .cmd .srow == 0 and M .dupe + 1 or 0 )
235240 end
236241
237242 cr = M [tar ].count > 0 and msg :sub (1 , 1 ) == ' \r '
238243 restart = M [tar ].count > 0 and (replace_last or dupe > 0 )
239244 count = M [tar ].count + ((restart or msg == ' \n ' ) and 0 or 1 )
240245
241246 -- Ensure cmdline is clear when writing the first message.
242- if tar == ' cmd' and not will_full and dupe == 0 and M .cmd .count == 0 and ext .cmd .row == 0 then
247+ if tar == ' cmd' and not will_full and dupe == 0 and M .cmd .count == 0 and ext .cmd .srow == 0 then
243248 api .nvim_buf_set_lines (ext .bufs .cmd , 0 , - 1 , false , {})
244249 end
245250 end
@@ -252,7 +257,7 @@ function M.show_msg(tar, content, replace_last, append)
252257 local line_count = api .nvim_buf_line_count (ext .bufs [tar ])
253258 --- @type integer Start row after last line in the target buffer , unless
254259 --- this is the first message, or in case of a repeated or replaced message.
255- local row = M [tar ] and count <= 1 and not will_full and (tar == ' cmd' and ext .cmd .row or 0 )
260+ local row = M [tar ] and count <= 1 and not will_full and (tar == ' cmd' and ext .cmd .erow or 0 )
256261 or line_count - ((replace_last or restart or cr or append ) and 1 or 0 )
257262 local curline = (cr or append ) and api .nvim_buf_get_lines (ext .bufs [tar ], row , row + 1 , false )[1 ]
258263 local start_row , width = row , M .msg .width
@@ -307,10 +312,10 @@ function M.show_msg(tar, content, replace_last, append)
307312 end
308313 elseif tar == ' cmd' and dupe == 0 then
309314 fn .clearmatches (ext .wins .cmd ) -- Clear matchparen highlights.
310- if ext .cmd .row > 0 then
315+ if ext .cmd .srow > 0 then
311316 -- In block mode the cmdheight is already dynamic, so just print the full message
312317 -- regardless of height. Spoof cmdline_show to put cmdline below message.
313- ext .cmd .row = ext .cmd .row + 1 + row - start_row
318+ ext .cmd .srow = ext .cmd .srow + 1 + row - start_row
314319 ext .cmd .cmdline_show ({}, 0 , ' :' , ' ' , ext .cmd .indent , 0 , 0 )
315320 api .nvim__redraw ({ flush = true , cursor = true , win = ext .wins .cmd })
316321 else
@@ -340,7 +345,7 @@ function M.show_msg(tar, content, replace_last, append)
340345 end
341346
342347 -- Reset message state the next event loop iteration.
343- if start_row == 0 or ext .cmd .row > 0 then
348+ if start_row == 0 or ext .cmd .srow > 0 then
344349 vim .schedule (function ()
345350 col , M .cmd .count = 0 , 0
346351 end )
359364function M .msg_show (kind , content , replace_last , _ , append )
360365 if kind == ' empty' then
361366 -- A sole empty message clears the cmdline.
362- if ext .cfg .msg .target == ' cmd' and M .cmd .count == 0 then
367+ if ext .cfg .msg .target == ' cmd' and M .cmd .count == 0 and ext . cmd . srow == 0 then
363368 M .msg_clear ()
364369 end
365370 elseif kind == ' search_count' then
@@ -370,7 +375,7 @@ function M.msg_show(kind, content, replace_last, _, append)
370375 M .virt .last [M .virt .idx .search ] = content
371376 M .virt .last [M .virt .idx .cmd ] = { { 0 , (' ' ):rep (11 ) } }
372377 set_virttext (' last' )
373- elseif ext .cmd .prompt or kind == ' wildlist' then
378+ elseif ( ext .cmd .prompt or kind == ' wildlist' ) and ext . cmd . srow == 0 then
374379 -- Route to dialog that stays open so long as the cmdline prompt is active.
375380 replace_last = api .nvim_win_get_config (ext .wins .dialog ).hide or kind == ' wildlist'
376381 if kind == ' wildlist' then
@@ -383,7 +388,7 @@ function M.msg_show(kind, content, replace_last, _, append)
383388 -- Set the entered search command in the cmdline (if available).
384389 local tar = kind == ' search_cmd' and ' cmd' or ext .cfg .msg .target
385390 if tar == ' cmd' then
386- if ext .cmdheight == 0 or (ext .cmd .level > 0 and ext .cmd .row == 0 ) then
391+ if ext .cmdheight == 0 or (ext .cmd .level > 0 and ext .cmd .srow == 0 ) then
387392 return -- Do not overwrite an active cmdline unless in block mode.
388393 end
389394 -- Store the time when an important message was emitted in order to not overwrite
@@ -458,7 +463,7 @@ function M.msg_history_show(entries, prev_cmd)
458463 M .set_pos (' pager' )
459464end
460465
461- --- Adjust dimensions of the message windows after certain events.
466+ --- Adjust visibility and dimensions of the message windows after certain events.
462467---
463468--- @param type ? ' cmd' | ' dialog' | ' msg' | ' pager' Type of to be positioned window (nil for all ).
464469function M .set_pos (type )
@@ -469,10 +474,10 @@ function M.set_pos(type)
469474 local border = win ~= ext .wins .msg and { ' ' , top , ' ' , ' ' , ' ' , ' ' , ' ' , ' ' } or nil
470475 local config = {
471476 hide = false ,
472- relative = ' laststatus' ,
477+ relative = ( win == ext . wins . pager or win == ext . wins . dialog ) and ' editor ' or ' laststatus' ,
473478 border = border ,
474479 height = height ,
475- row = win == ext .wins .msg and 0 or 1 ,
480+ row = ( win == ext .wins .pager or win == ext . wins . dialog ) and o . lines - o . cmdheight or 0 ,
476481 col = 10000 ,
477482 focusable = type == ' cmd' or nil , -- Allow entering the cmdline window.
478483 }
@@ -511,6 +516,41 @@ function M.set_pos(type)
511516 end )
512517 vim .on_key (nil , ext .ns )
513518 end , ext .ns )
519+ elseif type == ' dialog' then
520+ -- Add virtual [+x] text to indicate scrolling is possible.
521+ local function set_top_bot_spill ()
522+ local topspill = fn .line (' w0' , ext .wins .dialog ) - 1
523+ local botspill = api .nvim_buf_line_count (ext .bufs .dialog ) - fn .line (' w$' , ext .wins .dialog )
524+ M .virt .top [1 ][1 ] = topspill > 0 and { 0 , (' [+%d]' ):format (topspill ) } or nil
525+ set_virttext (' top' , ' dialog' )
526+ M .virt .bot [1 ][1 ] = botspill > 0 and { 0 , (' [+%d]' ):format (botspill ) } or nil
527+ set_virttext (' bot' , ' dialog' )
528+ api .nvim__redraw ({ flush = true })
529+ end
530+ set_top_bot_spill ()
531+
532+ -- Allow paging in the dialog window, consume the key if the topline changes.
533+ M .dialog_on_key = vim .on_key (function (key , typed )
534+ if not typed then
535+ return
536+ end
537+ local page_keys = {
538+ g = ' gg' ,
539+ G = ' G' ,
540+ j = ' Lj' ,
541+ k = ' Hk' ,
542+ d = [[ \<C-D>]] ,
543+ u = [[ \<C-U>]] ,
544+ f = [[ \<C-F>]] ,
545+ b = [[ \<C-B>]] ,
546+ }
547+ if page_keys [key ] then
548+ local topline = fn .getwininfo (ext .wins .dialog )[1 ].topline
549+ fn .win_execute (ext .wins .dialog , (' exe "norm! %s"' ):format (page_keys [key ]))
550+ set_top_bot_spill ()
551+ return fn .getwininfo (ext .wins .dialog )[1 ].topline ~= topline and ' ' or nil
552+ end
553+ end )
514554 elseif type == ' msg' then
515555 -- Ensure last line is visible and first line is at top of window.
516556 local row = (texth .all > height and texth .end_row or 0 ) + 1
0 commit comments