Skip to content

Commit

Permalink
feat(api): add nvim__redraw for more granular redrawing
Browse files Browse the repository at this point in the history
Experimental and subject to future changes.
Add a way to redraw certain elements that are not redrawn while Nvim is waiting
for input, or currently have no API to do so. This API covers all that can be
done with the :redraw* commands, in addition to the following new features:
- Immediately move the cursor to a (non-current) window.
- Target a specific window or buffer to mark for redraw.
- Mark a buffer range for redraw (replaces nvim__buf_redraw_range()).
- Redraw the 'statuscolumn'.
  • Loading branch information
luukvbaal committed May 2, 2024
1 parent 7b14eb5 commit 037ea6e
Show file tree
Hide file tree
Showing 17 changed files with 540 additions and 31 deletions.
29 changes: 29 additions & 0 deletions runtime/doc/api.txt
Original file line number Diff line number Diff line change
Expand Up @@ -1566,6 +1566,35 @@ nvim__invalidate_glyph_cache() *nvim__invalidate_glyph_cache()*
For testing. The condition in schar_cache_clear_if_full is hard to reach,
so this function can be used to force a cache clear in a test.

nvim__redraw({opts}) *nvim__redraw()*
EXPERIMENTAL: this API may change in the future.

Instruct Nvim to redraw various components.

Parameters: ~
{opts} Optional parameters.
• win: Target a specific |window-ID| as described below.
• buf: Target a specific buffer number as described below.
• flush: Update the screen with pending updates.
• valid: When present mark `win`, `buf`, or all windows for
redraw. When `true`, only redraw changed lines (useful for
decoration providers). When `false`, forcefully redraw.
• range: Redraw a range in `buf`, the buffer in `win` or the
current buffer (useful for decoration providers). Expects a
tuple `[first, last]` with the first and last line number of
the range, 0-based end-exclusive |api-indexing|.
• cursor: Immediately update cursor position on the screen in
`win` or the current window.
• statuscolumn: Redraw the 'statuscolumn' in `buf`, `win` or
all windows.
• statusline: Redraw the 'statusline' in `buf`, `win` or all
windows.
• winbar: Redraw the 'winbar' in `buf`, `win` or all windows.
• tabline: Redraw the 'tabline'.

See also: ~
|:redraw|

nvim__stats() *nvim__stats()*
Gets internal stats.

Expand Down
6 changes: 5 additions & 1 deletion runtime/doc/various.txt
Original file line number Diff line number Diff line change
Expand Up @@ -14,32 +14,36 @@ Various commands *various*
*CTRL-L*
CTRL-L Clears and redraws the screen. The redraw may happen
later, after processing typeahead.
See also |nvim__redraw()|.
*CTRL-L-default*
By default, also clears search highlighting
|:nohlsearch| and updates diffs |:diffupdate|.
|default-mappings|

*:mod* *:mode*
:mod[e] Clears and redraws the screen.
See also |nvim__redraw()|.

*:redr* *:redraw*
:redr[aw][!] Redraws pending screen updates now, or the entire
screen if "!" is included. To CLEAR the screen use
|:mode| or |CTRL-L|.
Useful to update the screen during a script or
function (or a mapping if 'lazyredraw' set).
See also |nvim__redraw()|.

*:redraws* *:redrawstatus*
:redraws[tatus][!] Redraws the status line and window bar of the current
window, or all status lines and window bars if "!" is
included. Redraws the commandline instead if it contains
the 'ruler'. Useful if 'statusline' or 'winbar' includes
an item that doesn't cause automatic updating.
See also |nvim__redraw()|.

*:redrawt* *:redrawtabline*
:redrawt[abline] Redraw the tabline. Useful to update the tabline when
'tabline' includes an item that doesn't trigger
automatic updating.
automatic updating. See also |nvim__redraw()|.

*N<Del>*
<Del> When entering a number: Remove the last digit.
Expand Down
32 changes: 26 additions & 6 deletions runtime/lua/vim/_meta/api.lua

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

12 changes: 12 additions & 0 deletions runtime/lua/vim/_meta/api_keysets.lua

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 3 additions & 3 deletions runtime/lua/vim/lsp/inlay_hint.lua
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ function M.on_inlayhint(err, result, ctx, _)
if num_unprocessed == 0 then
client_hints[client_id] = {}
bufstate.version = ctx.version
api.nvim__buf_redraw_range(bufnr, 0, -1)
api.nvim__redraw({ buf = bufnr, valid = true })
return
end

Expand Down Expand Up @@ -91,7 +91,7 @@ function M.on_inlayhint(err, result, ctx, _)

client_hints[client_id] = new_lnum_hints
bufstate.version = ctx.version
api.nvim__buf_redraw_range(bufnr, 0, -1)
api.nvim__redraw({ buf = bufnr, valid = true })
end

--- |lsp-handler| for the method `textDocument/inlayHint/refresh`
Expand Down Expand Up @@ -224,7 +224,7 @@ local function clear(bufnr)
end
end
api.nvim_buf_clear_namespace(bufnr, namespace, 0, -1)
api.nvim__buf_redraw_range(bufnr, 0, -1)
api.nvim__redraw({ buf = bufnr, valid = true })
end

--- Disable inlay hints for a buffer
Expand Down
2 changes: 1 addition & 1 deletion runtime/lua/vim/lsp/semantic_tokens.lua
Original file line number Diff line number Diff line change
Expand Up @@ -394,7 +394,7 @@ function STHighlighter:process_response(response, client, version)
current_result.namespace_cleared = false

-- redraw all windows displaying buffer
api.nvim__buf_redraw_range(self.bufnr, 0, -1)
api.nvim__redraw({ buf = self.bufnr, valid = true })
end

--- on_win handler for the decoration provider (see |nvim_set_decoration_provider|)
Expand Down
4 changes: 2 additions & 2 deletions runtime/lua/vim/treesitter/highlighter.lua
Original file line number Diff line number Diff line change
Expand Up @@ -215,7 +215,7 @@ end
---@param start_row integer
---@param new_end integer
function TSHighlighter:on_bytes(_, _, start_row, _, _, _, _, _, new_end)
api.nvim__buf_redraw_range(self.bufnr, start_row, start_row + new_end + 1)
api.nvim__redraw({ buf = self.bufnr, range = { start_row, start_row + new_end + 1 } })
end

---@package
Expand All @@ -227,7 +227,7 @@ end
---@param changes Range6[]
function TSHighlighter:on_changedtree(changes)
for _, ch in ipairs(changes) do
api.nvim__buf_redraw_range(self.bufnr, ch[1], ch[4] + 1)
api.nvim__redraw({ buf = self.bufnr, range = { ch[1], ch[4] + 1 } })
end
end

Expand Down
14 changes: 0 additions & 14 deletions src/nvim/api/buffer.c
Original file line number Diff line number Diff line change
Expand Up @@ -230,20 +230,6 @@ Boolean nvim_buf_detach(uint64_t channel_id, Buffer buffer, Error *err)
return true;
}

/// @nodoc
void nvim__buf_redraw_range(Buffer buffer, Integer first, Integer last, Error *err)
{
buf_T *buf = find_buffer_by_handle(buffer, err);
if (!buf) {
return;
}
if (last < 0) {
last = buf->b_ml.ml_line_count;
}

redraw_buf_range_later(buf, (linenr_T)first + 1, (linenr_T)last);
}

/// Gets a line-range from the buffer.
///
/// Indexing is zero-based, end-exclusive. Negative indices are interpreted
Expand Down
14 changes: 14 additions & 0 deletions src/nvim/api/keysets_defs.h
Original file line number Diff line number Diff line change
Expand Up @@ -373,3 +373,17 @@ typedef struct {
Boolean ignore_blank_lines;
Boolean indent_heuristic;
} Dict(xdl_diff);

typedef struct {
OptionalKeys is_set__redraw_;
Boolean flush;
Boolean cursor;
Boolean valid;
Boolean statuscolumn;
Boolean statusline;
Boolean tabline;
Boolean winbar;
Array range;
Window win;
Buffer buf;
} Dict(redraw);
156 changes: 156 additions & 0 deletions src/nvim/api/vim.c
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@
#include "nvim/mapping.h"
#include "nvim/mark.h"
#include "nvim/mark_defs.h"
#include "nvim/math.h"
#include "nvim/mbyte.h"
#include "nvim/memline.h"
#include "nvim/memory.h"
Expand Down Expand Up @@ -2305,3 +2306,158 @@ Dictionary nvim__complete_set(Integer index, Dict(complete_set) *opts, Arena *ar
}
return rv;
}

static void redraw_status(win_T *wp, Dict(redraw) *opts, bool *flush)
{
if (opts->statuscolumn && *wp->w_p_stc != NUL) {
wp->w_nrwidth_line_count = 0;
changed_window_setting(wp);
}
win_grid_alloc(wp);

// Flush later in case winbar was just hidden or shown for the first time, or
// statuscolumn is being drawn.
if (wp->w_lines_valid == 0) {
*flush = true;
}

// Mark for redraw in case flush will happen, otherwise redraw now.
if (*flush && (opts->statusline || opts->winbar)) {
wp->w_redr_status = true;
} else if (opts->statusline || opts->winbar) {
win_check_ns_hl(wp);
if (opts->winbar) {
win_redr_winbar(wp);
}
if (opts->statusline) {
win_redr_status(wp);
}
win_check_ns_hl(NULL);
}
}

/// EXPERIMENTAL: this API may change in the future.
///
/// Instruct Nvim to redraw various components.
///
/// @see |:redraw|
///
/// @param opts Optional parameters.
/// - win: Target a specific |window-ID| as described below.
/// - buf: Target a specific buffer number as described below.
/// - flush: Update the screen with pending updates.
/// - valid: When present mark `win`, `buf`, or all windows for
/// redraw. When `true`, only redraw changed lines (useful for
/// decoration providers). When `false`, forcefully redraw.
/// - range: Redraw a range in `buf`, the buffer in `win` or the
/// current buffer (useful for decoration providers). Expects a
/// tuple `[first, last]` with the first and last line number
/// of the range, 0-based end-exclusive |api-indexing|.
/// - cursor: Immediately update cursor position on the screen in
/// `win` or the current window.
/// - statuscolumn: Redraw the 'statuscolumn' in `buf`, `win` or
/// all windows.
/// - statusline: Redraw the 'statusline' in `buf`, `win` or all
/// windows.
/// - winbar: Redraw the 'winbar' in `buf`, `win` or all windows.
/// - tabline: Redraw the 'tabline'.
void nvim__redraw(Dict(redraw) *opts, Error *err)
FUNC_API_SINCE(12)
{
win_T *win = NULL;
buf_T *buf = NULL;

if (HAS_KEY(opts, redraw, win)) {
win = find_window_by_handle(opts->win, err);
if (ERROR_SET(err)) {
return;
}
}

if (HAS_KEY(opts, redraw, buf)) {
VALIDATE(win == NULL, "%s", "cannot use both 'buf' and 'win'", {
return;
});
buf = find_buffer_by_handle(opts->buf, err);
if (ERROR_SET(err)) {
return;
}
}

int count = (win != NULL) + (buf != NULL);
VALIDATE(popcount(opts->is_set__redraw_) > count, "%s", "at least one action required", {
return;
});

if (HAS_KEY(opts, redraw, valid)) {
// UPD_VALID redraw type does not actually do anything on it's own. Setting
// it here without scrolling or changing buffer text seems pointless but
// the expectation is that this may be called by decoration providers whose
// "on_win" callback may set "w_redr_top/bot".
int type = opts->valid ? UPD_VALID : UPD_NOT_VALID;
if (win != NULL) {
redraw_later(win, type);
} else if (buf != NULL) {
redraw_buf_later(buf, type);
} else {
redraw_all_later(type);
}
}

if (HAS_KEY(opts, redraw, range)) {
VALIDATE(kv_size(opts->range) == 2
&& kv_A(opts->range, 0).type == kObjectTypeInteger
&& kv_A(opts->range, 1).type == kObjectTypeInteger
&& kv_A(opts->range, 0).data.integer >= 0
&& kv_A(opts->range, 1).data.integer >= -1,
"%s", "Invalid 'range': Expected 2-tuple of Integers", {
return;
});
linenr_T first = (linenr_T)kv_A(opts->range, 0).data.integer + 1;
linenr_T last = (linenr_T)kv_A(opts->range, 1).data.integer;
if (last < 0) {
last = buf->b_ml.ml_line_count;
}
redraw_buf_range_later(win ? win->w_buffer : (buf ? buf : curbuf), first, last);
}

if (opts->cursor) {
setcursor_mayforce(win ? win : curwin, true);
}

bool flush = opts->flush;
if (opts->tabline) {
// Flush later in case tabline was just hidden or shown for the first time.
if (redraw_tabline && firstwin->w_lines_valid == 0) {
flush = true;
} else {
draw_tabline();
}
}

bool save_lz = p_lz;
int save_rd = RedrawingDisabled;
RedrawingDisabled = 0;
p_lz = false;
if (opts->statuscolumn || opts->statusline || opts->winbar) {
if (win == NULL) {
FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
if (buf == NULL || wp->w_buffer == buf) {
redraw_status(wp, opts, &flush);
}
}
} else {
redraw_status(win, opts, &flush);
}
}

// Flush pending screen updates if "flush" or "clear" is true, or when
// redrawing a status component may have changed the grid dimensions.
if (flush && !cmdpreview) {
update_screen();
}
ui_flush();

RedrawingDisabled = save_rd;
p_lz = save_lz;
}

0 comments on commit 037ea6e

Please sign in to comment.