diff --git a/runtime/doc/ui.txt b/runtime/doc/ui.txt index de54ce59b6..b243d9ba50 100644 --- a/runtime/doc/ui.txt +++ b/runtime/doc/ui.txt @@ -592,6 +592,12 @@ tabs. When |ext_messages| is active, no message grid is used, and this event will not be sent. +["win_viewport", grid, win, topline, botline, curline, curcol] + Indicates the range of buffer text displayed in the window, as well + as the cursor position in the buffer. All positions are zero-based. + `botline` is set to one more than the line count of the buffer, if + there are filler lines past the end. + ============================================================================== Popupmenu Events *ui-popupmenu* diff --git a/src/nvim/api/ui_events.in.h b/src/nvim/api/ui_events.in.h index 6677e248cf..ab31db39e9 100644 --- a/src/nvim/api/ui_events.in.h +++ b/src/nvim/api/ui_events.in.h @@ -115,6 +115,10 @@ void win_close(Integer grid) void msg_set_pos(Integer grid, Integer row, Boolean scrolled, String sep_char) FUNC_API_SINCE(6) FUNC_API_BRIDGE_IMPL FUNC_API_COMPOSITOR_IMPL; +void win_viewport(Integer grid, Window win, Integer topline, + Integer botline, Integer curline, Integer curcol) + FUNC_API_SINCE(7) FUNC_API_REMOTE_ONLY; + void popupmenu_show(Array items, Integer selected, Integer row, Integer col, Integer grid) FUNC_API_SINCE(3) FUNC_API_REMOTE_ONLY; diff --git a/src/nvim/buffer_defs.h b/src/nvim/buffer_defs.h index 3993f61a3d..366766c292 100644 --- a/src/nvim/buffer_defs.h +++ b/src/nvim/buffer_defs.h @@ -1190,6 +1190,8 @@ struct window_S { to adjust w_valid */ colnr_T w_valid_leftcol; /* last known w_leftcol */ + bool w_viewport_invalid; + /* * w_cline_height is the number of physical lines taken by the buffer line * that the cursor is on. We use this to avoid extra calls to plines(). diff --git a/src/nvim/move.c b/src/nvim/move.c index e7ed98d4f0..d4f82bc601 100644 --- a/src/nvim/move.c +++ b/src/nvim/move.c @@ -86,6 +86,7 @@ static void comp_botline(win_T *wp) /* wp->w_botline is the line that is just below the window */ wp->w_botline = lnum; wp->w_valid |= VALID_BOTLINE|VALID_BOTLINE_AP; + wp->w_viewport_invalid = true; set_empty_rows(wp, done); @@ -151,6 +152,7 @@ void update_topline(void) curwin->w_topline = curwin->w_cursor.lnum; curwin->w_botline = curwin->w_topline; curwin->w_valid |= VALID_BOTLINE|VALID_BOTLINE_AP; + curwin->w_viewport_invalid = true; curwin->w_scbind_pos = 1; return; } @@ -175,6 +177,7 @@ void update_topline(void) curwin->w_topline = 1; curwin->w_botline = 2; curwin->w_valid |= VALID_BOTLINE|VALID_BOTLINE_AP; + curwin->w_viewport_invalid = true; curwin->w_scbind_pos = 1; } /* @@ -311,6 +314,7 @@ void update_topline(void) } } curwin->w_valid |= VALID_TOPLINE; + curwin->w_viewport_invalid = true; win_check_anchored_floats(curwin); /* @@ -407,6 +411,7 @@ void check_cursor_moved(win_T *wp) |VALID_CHEIGHT|VALID_CROW|VALID_TOPLINE); wp->w_valid_cursor = wp->w_cursor; wp->w_valid_leftcol = wp->w_leftcol; + wp->w_viewport_invalid = true; } else if (wp->w_cursor.col != wp->w_valid_cursor.col || wp->w_leftcol != wp->w_valid_leftcol || wp->w_cursor.coladd != wp->w_valid_cursor.coladd @@ -415,6 +420,7 @@ void check_cursor_moved(win_T *wp) wp->w_valid_cursor.col = wp->w_cursor.col; wp->w_valid_leftcol = wp->w_leftcol; wp->w_valid_cursor.coladd = wp->w_cursor.coladd; + wp->w_viewport_invalid = true; } } @@ -1458,6 +1464,7 @@ void scroll_cursor_top(int min_scroll, int always) curwin->w_valid &= ~(VALID_WROW|VALID_CROW|VALID_BOTLINE|VALID_BOTLINE_AP); curwin->w_valid |= VALID_TOPLINE; + curwin->w_viewport_invalid = true; } } @@ -1662,6 +1669,7 @@ void scroll_cursor_bot(int min_scroll, int set_topbot) curwin->w_valid = old_valid; } curwin->w_valid |= VALID_TOPLINE; + curwin->w_viewport_invalid = true; } /// Recompute topline to put the cursor halfway across the window @@ -1818,6 +1826,7 @@ void cursor_correct(void) } } curwin->w_valid |= VALID_TOPLINE; + curwin->w_viewport_invalid = true; } diff --git a/src/nvim/normal.c b/src/nvim/normal.c index 6c92b136da..be131f7e00 100644 --- a/src/nvim/normal.c +++ b/src/nvim/normal.c @@ -4121,6 +4121,7 @@ void scroll_redraw(int up, long count) } if (curwin->w_cursor.lnum != prev_lnum) coladvance(curwin->w_curswant); + curwin->w_viewport_invalid = true; redraw_later(VALID); } diff --git a/src/nvim/screen.c b/src/nvim/screen.c index 2e2c6ca737..6e1273ed0c 100644 --- a/src/nvim/screen.c +++ b/src/nvim/screen.c @@ -417,7 +417,7 @@ int update_screen(int type) need_wait_return = false; } - win_ui_flush_positions(); + win_ui_flush(); msg_ext_check_clear(); /* reset cmdline_row now (may have been changed temporarily) */ @@ -1629,6 +1629,7 @@ static void win_update(win_T *wp) * changes are relevant). */ wp->w_valid |= VALID_BOTLINE; + wp->w_viewport_invalid = true; if (wp == curwin && wp->w_botline != old_botline && !recursive) { recursive = TRUE; curwin->w_valid &= ~VALID_TOPLINE; @@ -1648,7 +1649,7 @@ static void win_update(win_T *wp) /* restore got_int, unless CTRL-C was hit while redrawing */ if (!got_int) got_int = save_got_int; -} +} // NOLINT(readability/fn_size) /// Returns width of the signcolumn that should be used for the whole window /// diff --git a/src/nvim/ui.c b/src/nvim/ui.c index 0f841760d6..3a5aa95ad3 100644 --- a/src/nvim/ui.c +++ b/src/nvim/ui.c @@ -424,7 +424,7 @@ int ui_current_col(void) void ui_flush(void) { cmdline_ui_flush(); - win_ui_flush_positions(); + win_ui_flush(); msg_ext_ui_flush(); msg_scroll_flush(); diff --git a/src/nvim/window.c b/src/nvim/window.c index fb3f1e0c9f..9ec47f8e89 100644 --- a/src/nvim/window.c +++ b/src/nvim/window.c @@ -773,6 +773,21 @@ static void ui_ext_win_position(win_T *wp) } +void ui_ext_win_viewport(win_T *wp) +{ + if ((wp == curwin || ui_has(kUIMultigrid)) && wp->w_viewport_invalid) { + int botline = wp->w_botline; + if (botline == wp->w_buffer->b_ml.ml_line_count+1 + && wp->w_empty_rows == 0) { + // TODO(bfredl): The might be more cases to consider, like how does this + // interact with incomplete final line? Diff filler lines? + botline = wp->w_buffer->b_ml.ml_line_count; + } + ui_call_win_viewport(wp->w_grid.handle, wp->handle, wp->w_topline-1, + botline, wp->w_cursor.lnum-1, wp->w_cursor.col); + wp->w_viewport_invalid = false; + } +} static bool parse_float_anchor(String anchor, FloatAnchor *out) { @@ -4688,6 +4703,7 @@ static win_T *win_alloc(win_T *after, int hidden) new_wp->w_scbind_pos = 1; new_wp->w_floating = 0; new_wp->w_float_config = FLOAT_CONFIG_INIT; + new_wp->w_viewport_invalid = true; // use global option for global-local options new_wp->w_p_so = -1; @@ -6992,7 +7008,7 @@ void get_framelayout(const frame_T *fr, list_T *l, bool outer) } } -void win_ui_flush_positions(void) +void win_ui_flush(void) { FOR_ALL_TAB_WINDOWS(tp, wp) { if (wp->w_pos_changed && wp->w_grid.chars != NULL) { @@ -7003,6 +7019,9 @@ void win_ui_flush_positions(void) } wp->w_pos_changed = false; } + if (tp == curtab) { + ui_ext_win_viewport(wp); + } } } diff --git a/test/functional/ui/float_spec.lua b/test/functional/ui/float_spec.lua index 7a5569c14b..639e311ae6 100644 --- a/test/functional/ui/float_spec.lua +++ b/test/functional/ui/float_spec.lua @@ -976,6 +976,28 @@ describe('floatwin', function() {2:~ }| ]], float_pos={ [5] = {{id = 1002}, "NE", 4, 0, 50, true} + }, win_viewport = { + [2] = { + topline = 0, + botline = 3, + curline = 0, + curcol = 3, + win = { id = 1000 } + }, + [4] = { + topline = 0, + botline = 3, + curline = 0, + curcol = 3, + win = { id = 1001 } + }, + [5] = { + topline = 0, + botline = 2, + curline = 0, + curcol = 0, + win = { id = 1002 } + } }} else screen:expect([[ diff --git a/test/functional/ui/multigrid_spec.lua b/test/functional/ui/multigrid_spec.lua index 01ffe80be3..e4d1187dea 100644 --- a/test/functional/ui/multigrid_spec.lua +++ b/test/functional/ui/multigrid_spec.lua @@ -1962,4 +1962,191 @@ describe('ext_multigrid', function() {1:~ }| ]]} end) + + it('has viewport information', function() + screen:try_resize(48, 8) + screen:expect{grid=[[ + ## grid 1 + [2:------------------------------------------------]| + [2:------------------------------------------------]| + [2:------------------------------------------------]| + [2:------------------------------------------------]| + [2:------------------------------------------------]| + [2:------------------------------------------------]| + {11:[No Name] }| + [3:------------------------------------------------]| + ## grid 2 + ^ | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + ## grid 3 + | + ]], win_viewport={ + [2] = {win = { id = 1000 }, topline = 0, botline = 2, curline = 0, curcol = 0} + }} + insert([[ + Lorem ipsum dolor sit amet, consectetur + adipisicing elit, sed do eiusmod tempor + incididunt ut labore et dolore magna aliqua. + Ut enim ad minim veniam, quis nostrud + exercitation ullamco laboris nisi ut aliquip ex + ea commodo consequat. Duis aute irure dolor in + reprehenderit in voluptate velit esse cillum + dolore eu fugiat nulla pariatur. Excepteur sint + occaecat cupidatat non proident, sunt in culpa + qui officia deserunt mollit anim id est + laborum.]]) + + screen:expect{grid=[[ + ## grid 1 + [2:------------------------------------------------]| + [2:------------------------------------------------]| + [2:------------------------------------------------]| + [2:------------------------------------------------]| + [2:------------------------------------------------]| + [2:------------------------------------------------]| + {11:[No Name] [+] }| + [3:------------------------------------------------]| + ## grid 2 + ea commodo consequat. Duis aute irure dolor in | + reprehenderit in voluptate velit esse cillum | + dolore eu fugiat nulla pariatur. Excepteur sint | + occaecat cupidatat non proident, sunt in culpa | + qui officia deserunt mollit anim id est | + laborum^. | + ## grid 3 + | + ]], win_viewport={ + [2] = {win = {id = 1000}, topline = 5, botline = 11, curline = 10, curcol = 7}, + }} + + + feed('') + screen:expect{grid=[[ + ## grid 1 + [2:------------------------------------------------]| + [2:------------------------------------------------]| + [2:------------------------------------------------]| + [2:------------------------------------------------]| + [2:------------------------------------------------]| + [2:------------------------------------------------]| + {11:[No Name] [+] }| + [3:------------------------------------------------]| + ## grid 2 + incididunt ut labore et dolore magna aliqua. | + Ut enim ad minim veniam, quis nostrud | + exercitation ullamco laboris nisi ut aliquip ex | + ea commodo consequat. Duis aute irure dolor in | + reprehenderit in voluptate velit esse cillum | + ^dolore eu fugiat nulla pariatur. Excepteur sint | + ## grid 3 + | + ]], win_viewport={ + [2] = {win = {id = 1000}, topline = 2, botline = 9, curline = 7, curcol = 0}, + }} + + command("split") + screen:expect{grid=[[ + ## grid 1 + [4:------------------------------------------------]| + [4:------------------------------------------------]| + [4:------------------------------------------------]| + {11:[No Name] [+] }| + [2:------------------------------------------------]| + [2:------------------------------------------------]| + {12:[No Name] [+] }| + [3:------------------------------------------------]| + ## grid 2 + reprehenderit in voluptate velit esse cillum | + dolore eu fugiat nulla pariatur. Excepteur sint | + ## grid 3 + | + ## grid 4 + ea commodo consequat. Duis aute irure dolor in | + reprehenderit in voluptate velit esse cillum | + ^dolore eu fugiat nulla pariatur. Excepteur sint | + ]], win_viewport={ + [2] = {win = {id = 1000}, topline = 6, botline = 9, curline = 7, curcol = 0}, + [4] = {win = {id = 1001}, topline = 5, botline = 9, curline = 7, curcol = 0}, + }} + + feed("b") + screen:expect{grid=[[ + ## grid 1 + [4:------------------------------------------------]| + [4:------------------------------------------------]| + [4:------------------------------------------------]| + {11:[No Name] [+] }| + [2:------------------------------------------------]| + [2:------------------------------------------------]| + {12:[No Name] [+] }| + [3:------------------------------------------------]| + ## grid 2 + reprehenderit in voluptate velit esse cillum | + dolore eu fugiat nulla pariatur. Excepteur sint | + ## grid 3 + | + ## grid 4 + ea commodo consequat. Duis aute irure dolor in | + reprehenderit in voluptate velit esse ^cillum | + dolore eu fugiat nulla pariatur. Excepteur sint | + ]], win_viewport={ + [2] = {win = {id = 1000}, topline = 6, botline = 9, curline = 7, curcol = 0}, + [4] = {win = {id = 1001}, topline = 5, botline = 9, curline = 6, curcol = 38}, + }} + + feed("2k") + screen:expect{grid=[[ + ## grid 1 + [4:------------------------------------------------]| + [4:------------------------------------------------]| + [4:------------------------------------------------]| + {11:[No Name] [+] }| + [2:------------------------------------------------]| + [2:------------------------------------------------]| + {12:[No Name] [+] }| + [3:------------------------------------------------]| + ## grid 2 + reprehenderit in voluptate velit esse cillum | + dolore eu fugiat nulla pariatur. Excepteur sint | + ## grid 3 + | + ## grid 4 + exercitation ullamco laboris nisi ut a^liquip ex | + ea commodo consequat. Duis aute irure dolor in | + reprehenderit in voluptate velit esse cillum | + ]], win_viewport={ + [2] = {win = {id = 1000}, topline = 6, botline = 9, curline = 7, curcol = 0}, + [4] = {win = {id = 1001}, topline = 4, botline = 8, curline = 4, curcol = 38}, + }} + + -- handles non-current window + meths.win_set_cursor(1000, {1, 10}) + screen:expect{grid=[[ + ## grid 1 + [4:------------------------------------------------]| + [4:------------------------------------------------]| + [4:------------------------------------------------]| + {11:[No Name] [+] }| + [2:------------------------------------------------]| + [2:------------------------------------------------]| + {12:[No Name] [+] }| + [3:------------------------------------------------]| + ## grid 2 + Lorem ipsum dolor sit amet, consectetur | + adipisicing elit, sed do eiusmod tempor | + ## grid 3 + | + ## grid 4 + exercitation ullamco laboris nisi ut a^liquip ex | + ea commodo consequat. Duis aute irure dolor in | + reprehenderit in voluptate velit esse cillum | + ]], win_viewport={ + [2] = {win = {id = 1000}, topline = 0, botline = 3, curline = 0, curcol = 10}, + [4] = {win = {id = 1001}, topline = 4, botline = 8, curline = 4, curcol = 38}, + }} + end) end) diff --git a/test/functional/ui/screen.lua b/test/functional/ui/screen.lua index 64f784afe3..bf979e89f4 100644 --- a/test/functional/ui/screen.lua +++ b/test/functional/ui/screen.lua @@ -158,6 +158,7 @@ function Screen.new(width, height) wildmenu_items = nil, wildmenu_selected = nil, win_position = {}, + win_viewport = {}, float_pos = {}, msg_grid = nil, msg_grid_pos = nil, @@ -254,7 +255,7 @@ end -- canonical order of ext keys, used to generate asserts local ext_keys = { 'popupmenu', 'cmdline', 'cmdline_block', 'wildmenu_items', 'wildmenu_pos', - 'messages', 'showmode', 'showcmd', 'ruler', 'float_pos', + 'messages', 'showmode', 'showcmd', 'ruler', 'float_pos', 'win_viewport' } -- Asserts that the screen state eventually matches an expected state. @@ -421,6 +422,9 @@ screen:redraw_debug() to show all intermediate screen states. ]]) if expected.mode ~= nil then extstate.mode = self.mode end + if expected.win_viewport == nil then + extstate.win_viewport = nil + end -- Convert assertion errors into invalid screen state descriptions. for _, k in ipairs(concat_tables(ext_keys, {'mode'})) do @@ -726,6 +730,7 @@ function Screen:_handle_grid_destroy(grid) self._grids[grid] = nil if self._options.ext_multigrid then self.win_position[grid] = nil + self.win_viewport[grid] = nil end end @@ -746,14 +751,24 @@ function Screen:_handle_grid_cursor_goto(grid, row, col) end function Screen:_handle_win_pos(grid, win, startrow, startcol, width, height) - self.win_position[grid] = { - win = win, - startrow = startrow, - startcol = startcol, - width = width, - height = height - } - self.float_pos[grid] = nil + self.win_position[grid] = { + win = win, + startrow = startrow, + startcol = startcol, + width = width, + height = height + } + self.float_pos[grid] = nil +end + +function Screen:_handle_win_viewport(grid, win, topline, botline, curline, curcol) + self.win_viewport[grid] = { + win = win, + topline = topline, + botline = botline, + curline = curline, + curcol = curcol + } end function Screen:_handle_win_float_pos(grid, ...) @@ -1130,6 +1145,8 @@ function Screen:_extstate_repr(attr_state) messages[i] = {kind=entry[1], content=self:_chunks_repr(entry[2], attr_state)} end + local win_viewport = (next(self.win_viewport) and self.win_viewport) or nil + return { popupmenu=self.popupmenu, cmdline=cmdline, @@ -1141,7 +1158,8 @@ function Screen:_extstate_repr(attr_state) showcmd=self:_chunks_repr(self.showcmd, attr_state), ruler=self:_chunks_repr(self.ruler, attr_state), msg_history=msg_history, - float_pos=self.float_pos + float_pos=self.float_pos, + win_viewport=win_viewport, } end @@ -1216,10 +1234,6 @@ function Screen:render(headers, attr_state, preview) return rv end -local remove_all_metatables = function(item, path) - if path[#path] ~= inspect.METATABLE then return item end -end - -- Returns the current screen state in the form of a screen:expect() -- keyword-args map. function Screen:get_snapshot(attrs, ignore) @@ -1269,6 +1283,26 @@ function Screen:get_snapshot(attrs, ignore) return kwargs, ext_state, attr_state end +local function fmt_ext_state(name, state) + if name == "win_viewport" then + local str = "{\n" + for k,v in pairs(state) do + str = (str.." ["..k.."] = {win = {id = "..v.win.id.."}, topline = " + ..v.topline..", botline = "..v.botline..", curline = "..v.curline + ..", curcol = "..v.curcol.."},\n") + end + return str .. "}" + else + -- TODO(bfredl): improve formatting of more states + local function remove_all_metatables(item, path) + if path[#path] ~= inspect.METATABLE then + return item + end + end + return inspect(state,{process=remove_all_metatables}) + end +end + function Screen:print_snapshot(attrs, ignore) local kwargs, ext_state, attr_state = self:get_snapshot(attrs, ignore) local attrstr = "" @@ -1291,9 +1325,8 @@ function Screen:print_snapshot(attrs, ignore) print(kwargs.grid) io.stdout:write( "]]"..attrstr) for _, k in ipairs(ext_keys) do - if ext_state[k] ~= nil then - -- TODO(bfredl): improve formatting - io.stdout:write(", "..k.."="..inspect(ext_state[k],{process=remove_all_metatables})) + if ext_state[k] ~= nil and not (k == "win_viewport" and not self.options.ext_multigrid) then + io.stdout:write(", "..k.."="..fmt_ext_state(k, ext_state[k])) end end print("}\n")