mirror of
https://github.com/neovim/neovim.git
synced 2025-02-25 18:55:25 -06:00
Merge pull request #19360 from famiu/feat/multibuffer-inccommand
feat: multibuffer preview support for inccommand
This commit is contained in:
commit
3c545b9c62
@ -1466,10 +1466,10 @@ results (for "inccommand=split", or nil for "inccommand=nosplit").
|
||||
|
||||
Your command preview routine must implement this protocol:
|
||||
|
||||
1. Modify the current buffer as required for the preview (see
|
||||
1. Modify the target buffers as required for the preview (see
|
||||
|nvim_buf_set_text()| and |nvim_buf_set_lines()|).
|
||||
2. If preview buffer is provided, add necessary text to the preview buffer.
|
||||
3. Add required highlights to the current buffer. If preview buffer is
|
||||
3. Add required highlights to the target buffers. If preview buffer is
|
||||
provided, add required highlights to the preview buffer as well. All
|
||||
highlights must be added to the preview namespace which is provided as an
|
||||
argument to the preview callback (see |nvim_buf_add_highlight()| and
|
||||
@ -1480,8 +1480,8 @@ Your command preview routine must implement this protocol:
|
||||
2: Preview is shown and preview window is opened (if "inccommand=split").
|
||||
For "inccommand=nosplit" this is the same as 1.
|
||||
|
||||
After preview ends, Nvim discards all changes to the buffer and all highlights
|
||||
in the preview namespace.
|
||||
After preview ends, Nvim discards all changes to all buffers made during the
|
||||
preview and clears all highlights in the preview namespace.
|
||||
|
||||
Here's an example of a command to trim trailing whitespace from lines that
|
||||
supports incremental command preview:
|
||||
|
@ -148,7 +148,7 @@ struct cmdline_info {
|
||||
/// Last value of prompt_id, incremented when doing new prompt
|
||||
static unsigned last_prompt_id = 0;
|
||||
|
||||
// Struct to store the viewstate during 'incsearch' highlighting.
|
||||
// Struct to store the viewstate during 'incsearch' highlighting and 'inccommand' preview.
|
||||
typedef struct {
|
||||
colnr_T vs_curswant;
|
||||
colnr_T vs_leftcol;
|
||||
@ -200,6 +200,32 @@ typedef struct command_line_state {
|
||||
long *b_im_ptr;
|
||||
} CommandLineState;
|
||||
|
||||
typedef struct cmdpreview_win_info {
|
||||
win_T *win;
|
||||
pos_T save_w_cursor;
|
||||
viewstate_T save_viewstate;
|
||||
int save_w_p_cul;
|
||||
int save_w_p_cuc;
|
||||
} CpWinInfo;
|
||||
|
||||
typedef struct cmdpreview_buf_info {
|
||||
buf_T *buf;
|
||||
time_t save_b_u_time_cur;
|
||||
long save_b_u_seq_cur;
|
||||
u_header_T *save_b_u_newhead;
|
||||
long save_b_p_ul;
|
||||
int save_b_changed;
|
||||
varnumber_T save_changedtick;
|
||||
} CpBufInfo;
|
||||
|
||||
typedef struct cmdpreview_info {
|
||||
kvec_t(CpWinInfo) win_info;
|
||||
kvec_t(CpBufInfo) buf_info;
|
||||
bool save_hls;
|
||||
cmdmod_T save_cmdmod;
|
||||
garray_T save_view;
|
||||
} CpInfo;
|
||||
|
||||
typedef struct cmdline_info CmdlineInfo;
|
||||
|
||||
/// The current cmdline_info. It is initialized in getcmdline() and after that
|
||||
@ -242,26 +268,26 @@ static long cmdpreview_ns = 0;
|
||||
|
||||
static int cmd_hkmap = 0; // Hebrew mapping during command line
|
||||
|
||||
static void save_viewstate(viewstate_T *vs)
|
||||
static void save_viewstate(win_T *wp, viewstate_T *vs)
|
||||
FUNC_ATTR_NONNULL_ALL
|
||||
{
|
||||
vs->vs_curswant = curwin->w_curswant;
|
||||
vs->vs_leftcol = curwin->w_leftcol;
|
||||
vs->vs_topline = curwin->w_topline;
|
||||
vs->vs_topfill = curwin->w_topfill;
|
||||
vs->vs_botline = curwin->w_botline;
|
||||
vs->vs_empty_rows = curwin->w_empty_rows;
|
||||
vs->vs_curswant = wp->w_curswant;
|
||||
vs->vs_leftcol = wp->w_leftcol;
|
||||
vs->vs_topline = wp->w_topline;
|
||||
vs->vs_topfill = wp->w_topfill;
|
||||
vs->vs_botline = wp->w_botline;
|
||||
vs->vs_empty_rows = wp->w_empty_rows;
|
||||
}
|
||||
|
||||
static void restore_viewstate(viewstate_T *vs)
|
||||
static void restore_viewstate(win_T *wp, viewstate_T *vs)
|
||||
FUNC_ATTR_NONNULL_ALL
|
||||
{
|
||||
curwin->w_curswant = vs->vs_curswant;
|
||||
curwin->w_leftcol = vs->vs_leftcol;
|
||||
curwin->w_topline = vs->vs_topline;
|
||||
curwin->w_topfill = vs->vs_topfill;
|
||||
curwin->w_botline = vs->vs_botline;
|
||||
curwin->w_empty_rows = vs->vs_empty_rows;
|
||||
wp->w_curswant = vs->vs_curswant;
|
||||
wp->w_leftcol = vs->vs_leftcol;
|
||||
wp->w_topline = vs->vs_topline;
|
||||
wp->w_topfill = vs->vs_topfill;
|
||||
wp->w_botline = vs->vs_botline;
|
||||
wp->w_empty_rows = vs->vs_empty_rows;
|
||||
}
|
||||
|
||||
static void init_incsearch_state(incsearch_state_T *s)
|
||||
@ -273,8 +299,8 @@ static void init_incsearch_state(incsearch_state_T *s)
|
||||
clearpos(&s->match_end);
|
||||
s->save_cursor = curwin->w_cursor; // may be restored later
|
||||
s->search_start = curwin->w_cursor;
|
||||
save_viewstate(&s->init_viewstate);
|
||||
save_viewstate(&s->old_viewstate);
|
||||
save_viewstate(curwin, &s->init_viewstate);
|
||||
save_viewstate(curwin, &s->old_viewstate);
|
||||
}
|
||||
|
||||
/// Completion for |:checkhealth| command.
|
||||
@ -554,7 +580,7 @@ static void may_do_incsearch_highlighting(int firstc, long count, incsearch_stat
|
||||
|
||||
// first restore the old curwin values, so the screen is
|
||||
// positioned in the same way as the actual search command
|
||||
restore_viewstate(&s->old_viewstate);
|
||||
restore_viewstate(curwin, &s->old_viewstate);
|
||||
changed_cline_bef_curs();
|
||||
update_topline(curwin);
|
||||
|
||||
@ -664,7 +690,7 @@ static void finish_incsearch_highlighting(int gotesc, incsearch_state_T *s, bool
|
||||
}
|
||||
curwin->w_cursor = s->search_start; // -V519
|
||||
}
|
||||
restore_viewstate(&s->old_viewstate);
|
||||
restore_viewstate(curwin, &s->old_viewstate);
|
||||
highlight_match = false;
|
||||
|
||||
// by default search all lines
|
||||
@ -1663,7 +1689,7 @@ static int may_do_command_line_next_incsearch(int firstc, long count, incsearch_
|
||||
update_topline(curwin);
|
||||
validate_cursor();
|
||||
highlight_match = true;
|
||||
save_viewstate(&s->old_viewstate);
|
||||
save_viewstate(curwin, &s->old_viewstate);
|
||||
update_screen(NOT_VALID);
|
||||
highlight_match = false;
|
||||
redrawcmdline();
|
||||
@ -2395,6 +2421,126 @@ static void cmdpreview_close_win(void)
|
||||
}
|
||||
}
|
||||
|
||||
/// Save current state and prepare windows and buffers for command preview.
|
||||
static void cmdpreview_prepare(CpInfo *cpinfo)
|
||||
{
|
||||
kv_init(cpinfo->buf_info);
|
||||
kv_init(cpinfo->win_info);
|
||||
|
||||
FOR_ALL_WINDOWS_IN_TAB(win, curtab) {
|
||||
buf_T *buf = win->w_buffer;
|
||||
|
||||
// Don't save state of command preview buffer or preview window.
|
||||
if (buf->handle == cmdpreview_bufnr) {
|
||||
continue;
|
||||
}
|
||||
|
||||
CpBufInfo cp_bufinfo;
|
||||
cp_bufinfo.buf = buf;
|
||||
|
||||
cp_bufinfo.save_b_u_time_cur = buf->b_u_time_cur;
|
||||
cp_bufinfo.save_b_u_seq_cur = buf->b_u_seq_cur;
|
||||
cp_bufinfo.save_b_u_newhead = buf->b_u_newhead;
|
||||
cp_bufinfo.save_b_p_ul = buf->b_p_ul;
|
||||
cp_bufinfo.save_b_changed = buf->b_changed;
|
||||
cp_bufinfo.save_changedtick = buf_get_changedtick(buf);
|
||||
|
||||
kv_push(cpinfo->buf_info, cp_bufinfo);
|
||||
|
||||
buf->b_p_ul = LONG_MAX; // Make sure we can undo all changes
|
||||
|
||||
CpWinInfo cp_wininfo;
|
||||
cp_wininfo.win = win;
|
||||
|
||||
// Save window cursor position and viewstate
|
||||
cp_wininfo.save_w_cursor = win->w_cursor;
|
||||
save_viewstate(win, &cp_wininfo.save_viewstate);
|
||||
|
||||
// Save 'cursorline' and 'cursorcolumn'
|
||||
cp_wininfo.save_w_p_cul = win->w_p_cul;
|
||||
cp_wininfo.save_w_p_cuc = win->w_p_cuc;
|
||||
|
||||
kv_push(cpinfo->win_info, cp_wininfo);
|
||||
|
||||
win->w_p_cul = false; // Disable 'cursorline' so it doesn't mess up the highlights
|
||||
win->w_p_cuc = false; // Disable 'cursorcolumn' so it doesn't mess up the highlights
|
||||
}
|
||||
|
||||
cpinfo->save_hls = p_hls;
|
||||
cpinfo->save_cmdmod = cmdmod;
|
||||
win_size_save(&cpinfo->save_view);
|
||||
save_search_patterns();
|
||||
|
||||
p_hls = false; // Don't show search highlighting during live substitution
|
||||
cmdmod.cmod_split = 0; // Disable :leftabove/botright modifiers
|
||||
cmdmod.cmod_tab = 0; // Disable :tab modifier
|
||||
cmdmod.cmod_flags |= CMOD_NOSWAPFILE; // Disable swap for preview buffer
|
||||
}
|
||||
|
||||
// Restore the state of buffers and windows before command preview.
|
||||
static void cmdpreview_restore_state(CpInfo *cpinfo)
|
||||
{
|
||||
for (size_t i = 0; i < cpinfo->buf_info.size; i++) {
|
||||
CpBufInfo cp_bufinfo = cpinfo->buf_info.items[i];
|
||||
buf_T *buf = cp_bufinfo.buf;
|
||||
|
||||
buf->b_changed = cp_bufinfo.save_b_changed;
|
||||
|
||||
if (buf->b_u_seq_cur != cp_bufinfo.save_b_u_seq_cur) {
|
||||
int count = 0;
|
||||
|
||||
// Calculate how many undo steps are necessary to restore earlier state.
|
||||
for (u_header_T *uhp = buf->b_u_curhead ? buf->b_u_curhead : buf->b_u_newhead;
|
||||
uhp != NULL && uhp->uh_seq > cp_bufinfo.save_b_u_seq_cur;
|
||||
uhp = uhp->uh_next.ptr, ++count) {}
|
||||
|
||||
aco_save_T aco;
|
||||
aucmd_prepbuf(&aco, buf);
|
||||
// Undo invisibly. This also moves the cursor!
|
||||
if (!u_undo_and_forget(count)) {
|
||||
abort();
|
||||
}
|
||||
aucmd_restbuf(&aco);
|
||||
|
||||
// Restore newhead. It is meaningless when curhead is valid, but we must
|
||||
// restore it so that undotree() is identical before/after the preview.
|
||||
buf->b_u_newhead = cp_bufinfo.save_b_u_newhead;
|
||||
buf->b_u_time_cur = cp_bufinfo.save_b_u_time_cur;
|
||||
}
|
||||
if (cp_bufinfo.save_changedtick != buf_get_changedtick(buf)) {
|
||||
buf_set_changedtick(buf, cp_bufinfo.save_changedtick);
|
||||
}
|
||||
|
||||
buf->b_p_ul = cp_bufinfo.save_b_p_ul; // Restore 'undolevels'
|
||||
|
||||
// Clear preview highlights.
|
||||
extmark_clear(buf, (uint32_t)cmdpreview_ns, 0, 0, MAXLNUM, MAXCOL);
|
||||
}
|
||||
for (size_t i = 0; i < cpinfo->win_info.size; i++) {
|
||||
CpWinInfo cp_wininfo = cpinfo->win_info.items[i];
|
||||
win_T *win = cp_wininfo.win;
|
||||
|
||||
// Restore window cursor position and viewstate
|
||||
win->w_cursor = cp_wininfo.save_w_cursor;
|
||||
restore_viewstate(win, &cp_wininfo.save_viewstate);
|
||||
|
||||
// Restore 'cursorline' and 'cursorcolumn'
|
||||
win->w_p_cul = cp_wininfo.save_w_p_cul;
|
||||
win->w_p_cuc = cp_wininfo.save_w_p_cuc;
|
||||
|
||||
update_topline(win);
|
||||
}
|
||||
|
||||
cmdmod = cpinfo->save_cmdmod; // Restore cmdmod
|
||||
p_hls = cpinfo->save_hls; // Restore 'hlsearch'
|
||||
restore_search_patterns(); // Restore search patterns
|
||||
win_size_restore(&cpinfo->save_view); // Restore window sizes
|
||||
|
||||
ga_clear(&cpinfo->save_view);
|
||||
kv_destroy(cpinfo->win_info);
|
||||
kv_destroy(cpinfo->buf_info);
|
||||
}
|
||||
|
||||
/// Show 'inccommand' preview if command is previewable. It works like this:
|
||||
/// 1. Store current undo information so we can revert to current state later.
|
||||
/// 2. Execute the preview callback with the parsed command, preview buffer number and preview
|
||||
@ -2439,35 +2585,18 @@ static bool cmdpreview_may_show(CommandLineState *s)
|
||||
ea.line2 = lnum;
|
||||
}
|
||||
|
||||
time_t save_b_u_time_cur = curbuf->b_u_time_cur;
|
||||
long save_b_u_seq_cur = curbuf->b_u_seq_cur;
|
||||
u_header_T *save_b_u_newhead = curbuf->b_u_newhead;
|
||||
long save_b_p_ul = curbuf->b_p_ul;
|
||||
int save_b_changed = curbuf->b_changed;
|
||||
int save_w_p_cul = curwin->w_p_cul;
|
||||
int save_w_p_cuc = curwin->w_p_cuc;
|
||||
bool save_hls = p_hls;
|
||||
varnumber_T save_changedtick = buf_get_changedtick(curbuf);
|
||||
CpInfo cpinfo;
|
||||
bool icm_split = *p_icm == 's'; // inccommand=split
|
||||
buf_T *cmdpreview_buf;
|
||||
win_T *cmdpreview_win;
|
||||
cmdmod_T save_cmdmod = cmdmod;
|
||||
|
||||
cmdpreview = true;
|
||||
emsg_silent++; // Block error reporting as the command may be incomplete,
|
||||
// but still update v:errmsg
|
||||
msg_silent++; // Block messages, namely ones that prompt
|
||||
block_autocmds(); // Block events
|
||||
garray_T save_view;
|
||||
win_size_save(&save_view); // Save current window sizes
|
||||
save_search_patterns(); // Save search patterns
|
||||
curbuf->b_p_ul = LONG_MAX; // Make sure we can undo all changes
|
||||
curwin->w_p_cul = false; // Disable 'cursorline' so it doesn't mess up the highlights
|
||||
curwin->w_p_cuc = false; // Disable 'cursorcolumn' so it doesn't mess up the highlights
|
||||
p_hls = false; // Don't show search highlighting during live substitution
|
||||
cmdmod.cmod_split = 0; // Disable :leftabove/botright modifiers
|
||||
cmdmod.cmod_tab = 0; // Disable :tab modifier
|
||||
cmdmod.cmod_flags |= CMOD_NOSWAPFILE; // Disable swap for preview buffer
|
||||
|
||||
// Save current state and prepare for command preview.
|
||||
cmdpreview_prepare(&cpinfo);
|
||||
|
||||
// Open preview buffer if inccommand=split.
|
||||
if (!icm_split) {
|
||||
@ -2475,12 +2604,14 @@ static bool cmdpreview_may_show(CommandLineState *s)
|
||||
} else if ((cmdpreview_buf = cmdpreview_open_buf()) == NULL) {
|
||||
abort();
|
||||
}
|
||||
|
||||
// Setup preview namespace if it's not already set.
|
||||
if (!cmdpreview_ns) {
|
||||
cmdpreview_ns = (int)nvim_create_namespace((String)STRING_INIT);
|
||||
}
|
||||
|
||||
// Set cmdpreview state.
|
||||
cmdpreview = true;
|
||||
|
||||
// Execute the preview callback and use its return value to determine whether to show preview or
|
||||
// open the preview window. The preview callback also handles doing the changes and highlights for
|
||||
// the preview.
|
||||
@ -2499,7 +2630,7 @@ static bool cmdpreview_may_show(CommandLineState *s)
|
||||
cmdpreview_type = 1;
|
||||
}
|
||||
|
||||
// If preview callback is nonzero, update screen now.
|
||||
// If preview callback return value is nonzero, update screen now.
|
||||
if (cmdpreview_type != 0) {
|
||||
int save_rd = RedrawingDisabled;
|
||||
RedrawingDisabled = 0;
|
||||
@ -2511,44 +2642,13 @@ static bool cmdpreview_may_show(CommandLineState *s)
|
||||
if (icm_split && cmdpreview_type == 2 && cmdpreview_win != NULL) {
|
||||
cmdpreview_close_win();
|
||||
}
|
||||
// Clear preview highlights.
|
||||
extmark_clear(curbuf, (uint32_t)cmdpreview_ns, 0, 0, MAXLNUM, MAXCOL);
|
||||
|
||||
curbuf->b_changed = save_b_changed; // Preserve 'modified' during preview
|
||||
// Restore state.
|
||||
cmdpreview_restore_state(&cpinfo);
|
||||
|
||||
if (curbuf->b_u_seq_cur != save_b_u_seq_cur) {
|
||||
// Undo invisibly. This also moves the cursor!
|
||||
while (curbuf->b_u_seq_cur != save_b_u_seq_cur) {
|
||||
if (!u_undo_and_forget(1)) {
|
||||
abort();
|
||||
}
|
||||
}
|
||||
// Restore newhead. It is meaningless when curhead is valid, but we must
|
||||
// restore it so that undotree() is identical before/after the preview.
|
||||
curbuf->b_u_newhead = save_b_u_newhead;
|
||||
curbuf->b_u_time_cur = save_b_u_time_cur;
|
||||
}
|
||||
if (save_changedtick != buf_get_changedtick(curbuf)) {
|
||||
buf_set_changedtick(curbuf, save_changedtick);
|
||||
}
|
||||
|
||||
cmdmod = save_cmdmod; // Restore cmdmod
|
||||
p_hls = save_hls; // Restore 'hlsearch'
|
||||
curwin->w_p_cul = save_w_p_cul; // Restore 'cursorline'
|
||||
curwin->w_p_cuc = save_w_p_cuc; // Restore 'cursorcolumn'
|
||||
curbuf->b_p_ul = save_b_p_ul; // Restore 'undolevels'
|
||||
restore_search_patterns(); // Restore search patterns
|
||||
win_size_restore(&save_view); // Restore window sizes
|
||||
ga_clear(&save_view);
|
||||
unblock_autocmds(); // Unblock events
|
||||
msg_silent--; // Unblock messages
|
||||
emsg_silent--; // Unblock error reporting
|
||||
|
||||
// Restore the window "view".
|
||||
curwin->w_cursor = s->is_state.save_cursor;
|
||||
restore_viewstate(&s->is_state.old_viewstate);
|
||||
update_topline(curwin);
|
||||
|
||||
redrawcmdline();
|
||||
end:
|
||||
xfree(cmdline);
|
||||
|
@ -7,61 +7,82 @@ local feed = helpers.feed
|
||||
local command = helpers.command
|
||||
local assert_alive = helpers.assert_alive
|
||||
|
||||
-- Implements a :Replace command that works like :substitute.
|
||||
-- Implements a :Replace command that works like :substitute and has multibuffer support.
|
||||
local setup_replace_cmd = [[
|
||||
local function show_replace_preview(buf, use_preview_win, preview_ns, preview_buf, matches)
|
||||
local function show_replace_preview(use_preview_win, preview_ns, preview_buf, matches)
|
||||
-- Find the width taken by the largest line number, used for padding the line numbers
|
||||
local highest_lnum = math.max(matches[#matches][1], 1)
|
||||
local highest_lnum_width = math.floor(math.log10(highest_lnum))
|
||||
local preview_buf_line = 0
|
||||
|
||||
vim.g.prevns = preview_ns
|
||||
vim.g.prevbuf = preview_buf
|
||||
local multibuffer = #matches > 1
|
||||
|
||||
for _, match in ipairs(matches) do
|
||||
local lnum = match[1]
|
||||
local line_matches = match[2]
|
||||
local prefix
|
||||
local buf = match[1]
|
||||
local buf_matches = match[2]
|
||||
|
||||
if use_preview_win then
|
||||
prefix = string.format(
|
||||
'|%s%d| ',
|
||||
string.rep(' ', highest_lnum_width - math.floor(math.log10(lnum))),
|
||||
lnum
|
||||
)
|
||||
if multibuffer and #buf_matches > 0 and use_preview_win then
|
||||
local bufname = vim.api.nvim_buf_get_name(buf)
|
||||
|
||||
if bufname == "" then
|
||||
bufname = string.format("Buffer #%d", buf)
|
||||
end
|
||||
|
||||
vim.api.nvim_buf_set_lines(
|
||||
preview_buf,
|
||||
preview_buf_line,
|
||||
preview_buf_line,
|
||||
0,
|
||||
{ prefix .. vim.api.nvim_buf_get_lines(buf, lnum - 1, lnum, false)[1] }
|
||||
{ bufname .. ':' }
|
||||
)
|
||||
|
||||
preview_buf_line = preview_buf_line + 1
|
||||
end
|
||||
|
||||
for _, line_match in ipairs(line_matches) do
|
||||
vim.api.nvim_buf_add_highlight(
|
||||
buf,
|
||||
preview_ns,
|
||||
'Substitute',
|
||||
lnum - 1,
|
||||
line_match[1],
|
||||
line_match[2]
|
||||
)
|
||||
for _, buf_match in ipairs(buf_matches) do
|
||||
local lnum = buf_match[1]
|
||||
local line_matches = buf_match[2]
|
||||
local prefix
|
||||
|
||||
if use_preview_win then
|
||||
vim.api.nvim_buf_add_highlight(
|
||||
prefix = string.format(
|
||||
'|%s%d| ',
|
||||
string.rep(' ', highest_lnum_width - math.floor(math.log10(lnum))),
|
||||
lnum
|
||||
)
|
||||
|
||||
vim.api.nvim_buf_set_lines(
|
||||
preview_buf,
|
||||
preview_ns,
|
||||
'Substitute',
|
||||
preview_buf_line,
|
||||
#prefix + line_match[1],
|
||||
#prefix + line_match[2]
|
||||
preview_buf_line,
|
||||
0,
|
||||
{ prefix .. vim.api.nvim_buf_get_lines(buf, lnum - 1, lnum, false)[1] }
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
preview_buf_line = preview_buf_line + 1
|
||||
for _, line_match in ipairs(line_matches) do
|
||||
vim.api.nvim_buf_add_highlight(
|
||||
buf,
|
||||
preview_ns,
|
||||
'Substitute',
|
||||
lnum - 1,
|
||||
line_match[1],
|
||||
line_match[2]
|
||||
)
|
||||
|
||||
if use_preview_win then
|
||||
vim.api.nvim_buf_add_highlight(
|
||||
preview_buf,
|
||||
preview_ns,
|
||||
'Substitute',
|
||||
preview_buf_line,
|
||||
#prefix + line_match[1],
|
||||
#prefix + line_match[2]
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
preview_buf_line = preview_buf_line + 1
|
||||
end
|
||||
end
|
||||
|
||||
if use_preview_win then
|
||||
@ -72,94 +93,121 @@ local setup_replace_cmd = [[
|
||||
end
|
||||
|
||||
local function do_replace(opts, preview, preview_ns, preview_buf)
|
||||
local pat1 = opts.fargs[1] or ''
|
||||
local pat1 = opts.fargs[1]
|
||||
|
||||
if not pat1 then return end
|
||||
|
||||
local pat2 = opts.fargs[2] or ''
|
||||
local line1 = opts.line1
|
||||
local line2 = opts.line2
|
||||
|
||||
local buf = vim.api.nvim_get_current_buf()
|
||||
local lines = vim.api.nvim_buf_get_lines(buf, line1 - 1, line2, 0)
|
||||
local matches = {}
|
||||
|
||||
for i, line in ipairs(lines) do
|
||||
local startidx, endidx = 0, 0
|
||||
local line_matches = {}
|
||||
local num = 1
|
||||
-- Get list of valid and listed buffers
|
||||
local buffers = vim.tbl_filter(
|
||||
function(buf)
|
||||
if not (vim.api.nvim_buf_is_valid(buf) and vim.bo[buf].buflisted and buf ~= preview_buf)
|
||||
then
|
||||
return false
|
||||
end
|
||||
|
||||
while startidx ~= -1 do
|
||||
local match = vim.fn.matchstrpos(line, pat1, 0, num)
|
||||
startidx, endidx = match[2], match[3]
|
||||
-- Check if there's at least one window using the buffer
|
||||
for _, win in ipairs(vim.api.nvim_tabpage_list_wins(0)) do
|
||||
if vim.api.nvim_win_get_buf(win) == buf then
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
if startidx ~= -1 then
|
||||
line_matches[#line_matches+1] = { startidx, endidx }
|
||||
return false
|
||||
end,
|
||||
vim.api.nvim_list_bufs()
|
||||
)
|
||||
|
||||
for _, buf in ipairs(buffers) do
|
||||
local lines = vim.api.nvim_buf_get_lines(buf, line1 - 1, line2, false)
|
||||
local buf_matches = {}
|
||||
|
||||
for i, line in ipairs(lines) do
|
||||
local startidx, endidx = 0, 0
|
||||
local line_matches = {}
|
||||
local num = 1
|
||||
|
||||
while startidx ~= -1 do
|
||||
local match = vim.fn.matchstrpos(line, pat1, 0, num)
|
||||
startidx, endidx = match[2], match[3]
|
||||
|
||||
if startidx ~= -1 then
|
||||
line_matches[#line_matches+1] = { startidx, endidx }
|
||||
end
|
||||
|
||||
num = num + 1
|
||||
end
|
||||
|
||||
num = num + 1
|
||||
if #line_matches > 0 then
|
||||
buf_matches[#buf_matches+1] = { line1 + i - 1, line_matches }
|
||||
end
|
||||
end
|
||||
|
||||
if #line_matches > 0 then
|
||||
matches[#matches+1] = { line1 + i - 1, line_matches }
|
||||
end
|
||||
end
|
||||
local new_lines = {}
|
||||
|
||||
local new_lines = {}
|
||||
for _, buf_match in ipairs(buf_matches) do
|
||||
local lnum = buf_match[1]
|
||||
local line_matches = buf_match[2]
|
||||
local line = lines[lnum - line1 + 1]
|
||||
local pat_width_differences = {}
|
||||
|
||||
for _, match in ipairs(matches) do
|
||||
local lnum = match[1]
|
||||
local line_matches = match[2]
|
||||
local line = lines[lnum - line1 + 1]
|
||||
local pat_width_differences = {}
|
||||
-- If previewing, only replace the text in current buffer if pat2 isn't empty
|
||||
-- Otherwise, always replace the text
|
||||
if pat2 ~= '' or not preview then
|
||||
if preview then
|
||||
for _, line_match in ipairs(line_matches) do
|
||||
local startidx, endidx = unpack(line_match)
|
||||
local pat_match = line:sub(startidx + 1, endidx)
|
||||
|
||||
-- If previewing, only replace the text in current buffer if pat2 isn't empty
|
||||
-- Otherwise, always replace the text
|
||||
if pat2 ~= '' or not preview then
|
||||
pat_width_differences[#pat_width_differences+1] =
|
||||
#vim.fn.substitute(pat_match, pat1, pat2, 'g') - #pat_match
|
||||
end
|
||||
end
|
||||
|
||||
new_lines[lnum] = vim.fn.substitute(line, pat1, pat2, 'g')
|
||||
end
|
||||
|
||||
-- Highlight the matches if previewing
|
||||
if preview then
|
||||
for _, line_match in ipairs(line_matches) do
|
||||
local idx_offset = 0
|
||||
for i, line_match in ipairs(line_matches) do
|
||||
local startidx, endidx = unpack(line_match)
|
||||
local pat_match = line:sub(startidx + 1, endidx)
|
||||
-- Starting index of replacement text
|
||||
local repl_startidx = startidx + idx_offset
|
||||
-- Ending index of the replacement text (if pat2 isn't empty)
|
||||
local repl_endidx
|
||||
|
||||
pat_width_differences[#pat_width_differences+1] =
|
||||
#vim.fn.substitute(pat_match, pat1, pat2, 'g') - #pat_match
|
||||
if pat2 ~= '' then
|
||||
repl_endidx = endidx + idx_offset + pat_width_differences[i]
|
||||
else
|
||||
repl_endidx = endidx + idx_offset
|
||||
end
|
||||
|
||||
if pat2 ~= '' then
|
||||
idx_offset = idx_offset + pat_width_differences[i]
|
||||
end
|
||||
|
||||
line_matches[i] = { repl_startidx, repl_endidx }
|
||||
end
|
||||
end
|
||||
|
||||
new_lines[lnum] = vim.fn.substitute(line, pat1, pat2, 'g')
|
||||
end
|
||||
|
||||
-- Highlight the matches if previewing
|
||||
if preview then
|
||||
local idx_offset = 0
|
||||
for i, line_match in ipairs(line_matches) do
|
||||
local startidx, endidx = unpack(line_match)
|
||||
-- Starting index of replacement text
|
||||
local repl_startidx = startidx + idx_offset
|
||||
-- Ending index of the replacement text (if pat2 isn't empty)
|
||||
local repl_endidx
|
||||
|
||||
if pat2 ~= '' then
|
||||
repl_endidx = endidx + idx_offset + pat_width_differences[i]
|
||||
else
|
||||
repl_endidx = endidx + idx_offset
|
||||
end
|
||||
|
||||
if pat2 ~= '' then
|
||||
idx_offset = idx_offset + pat_width_differences[i]
|
||||
end
|
||||
|
||||
line_matches[i] = { repl_startidx, repl_endidx }
|
||||
end
|
||||
for lnum, line in pairs(new_lines) do
|
||||
vim.api.nvim_buf_set_lines(buf, lnum - 1, lnum, false, { line })
|
||||
end
|
||||
end
|
||||
|
||||
for lnum, line in pairs(new_lines) do
|
||||
vim.api.nvim_buf_set_lines(buf, lnum - 1, lnum, false, { line })
|
||||
matches[#matches+1] = { buf, buf_matches }
|
||||
end
|
||||
|
||||
if preview then
|
||||
local lnum = vim.api.nvim_win_get_cursor(0)[1]
|
||||
-- Use preview window only if preview buffer is provided and range isn't just the current line
|
||||
local use_preview_win = (preview_buf ~= nil) and (line1 ~= lnum or line2 ~= lnum)
|
||||
return show_replace_preview(buf, use_preview_win, preview_ns, preview_buf, matches)
|
||||
return show_replace_preview(use_preview_win, preview_ns, preview_buf, matches)
|
||||
end
|
||||
end
|
||||
|
||||
@ -354,3 +402,120 @@ describe("'inccommand' for user commands", function()
|
||||
assert_alive()
|
||||
end)
|
||||
end)
|
||||
|
||||
describe("'inccommand' with multiple buffers", function()
|
||||
local screen
|
||||
|
||||
before_each(function()
|
||||
clear()
|
||||
screen = Screen.new(40, 17)
|
||||
screen:set_default_attr_ids({
|
||||
[1] = {background = Screen.colors.Yellow1},
|
||||
[2] = {foreground = Screen.colors.Blue1, bold = true},
|
||||
[3] = {reverse = true},
|
||||
[4] = {reverse = true, bold = true}
|
||||
})
|
||||
screen:attach()
|
||||
exec_lua(setup_replace_cmd)
|
||||
command('set cmdwinheight=10')
|
||||
insert[[
|
||||
foo bar baz
|
||||
bar baz foo
|
||||
baz foo bar
|
||||
]]
|
||||
command('vsplit | enew')
|
||||
insert[[
|
||||
bar baz foo
|
||||
baz foo bar
|
||||
foo bar baz
|
||||
]]
|
||||
end)
|
||||
|
||||
it('works', function()
|
||||
command('set inccommand=nosplit')
|
||||
feed(':Replace foo bar')
|
||||
screen:expect([[
|
||||
bar baz {1:bar} │ {1:bar} bar baz |
|
||||
baz {1:bar} bar │ bar baz {1:bar} |
|
||||
{1:bar} bar baz │ baz {1:bar} bar |
|
||||
│ |
|
||||
{2:~ }│{2:~ }|
|
||||
{2:~ }│{2:~ }|
|
||||
{2:~ }│{2:~ }|
|
||||
{2:~ }│{2:~ }|
|
||||
{2:~ }│{2:~ }|
|
||||
{2:~ }│{2:~ }|
|
||||
{2:~ }│{2:~ }|
|
||||
{2:~ }│{2:~ }|
|
||||
{2:~ }│{2:~ }|
|
||||
{2:~ }│{2:~ }|
|
||||
{2:~ }│{2:~ }|
|
||||
{4:[No Name] [+] }{3:[No Name] [+] }|
|
||||
:Replace foo bar^ |
|
||||
]])
|
||||
feed('<CR>')
|
||||
screen:expect([[
|
||||
bar baz bar │ bar bar baz |
|
||||
baz bar bar │ bar baz bar |
|
||||
bar bar baz │ baz bar bar |
|
||||
^ │ |
|
||||
{2:~ }│{2:~ }|
|
||||
{2:~ }│{2:~ }|
|
||||
{2:~ }│{2:~ }|
|
||||
{2:~ }│{2:~ }|
|
||||
{2:~ }│{2:~ }|
|
||||
{2:~ }│{2:~ }|
|
||||
{2:~ }│{2:~ }|
|
||||
{2:~ }│{2:~ }|
|
||||
{2:~ }│{2:~ }|
|
||||
{2:~ }│{2:~ }|
|
||||
{2:~ }│{2:~ }|
|
||||
{4:[No Name] [+] }{3:[No Name] [+] }|
|
||||
:Replace foo bar |
|
||||
]])
|
||||
end)
|
||||
|
||||
it('works with inccommand=split', function()
|
||||
command('set inccommand=split')
|
||||
feed(':Replace foo bar')
|
||||
screen:expect([[
|
||||
bar baz {1:bar} │ {1:bar} bar baz |
|
||||
baz {1:bar} bar │ bar baz {1:bar} |
|
||||
{1:bar} bar baz │ baz {1:bar} bar |
|
||||
│ |
|
||||
{4:[No Name] [+] }{3:[No Name] [+] }|
|
||||
Buffer #1: |
|
||||
|1| {1:bar} bar baz |
|
||||
|2| bar baz {1:bar} |
|
||||
|3| baz {1:bar} bar |
|
||||
Buffer #2: |
|
||||
|1| bar baz {1:bar} |
|
||||
|2| baz {1:bar} bar |
|
||||
|3| {1:bar} bar baz |
|
||||
|
|
||||
{2:~ }|
|
||||
{3:[Preview] }|
|
||||
:Replace foo bar^ |
|
||||
]])
|
||||
feed('<CR>')
|
||||
screen:expect([[
|
||||
bar baz bar │ bar bar baz |
|
||||
baz bar bar │ bar baz bar |
|
||||
bar bar baz │ baz bar bar |
|
||||
^ │ |
|
||||
{2:~ }│{2:~ }|
|
||||
{2:~ }│{2:~ }|
|
||||
{2:~ }│{2:~ }|
|
||||
{2:~ }│{2:~ }|
|
||||
{2:~ }│{2:~ }|
|
||||
{2:~ }│{2:~ }|
|
||||
{2:~ }│{2:~ }|
|
||||
{2:~ }│{2:~ }|
|
||||
{2:~ }│{2:~ }|
|
||||
{2:~ }│{2:~ }|
|
||||
{2:~ }│{2:~ }|
|
||||
{4:[No Name] [+] }{3:[No Name] [+] }|
|
||||
:Replace foo bar |
|
||||
]])
|
||||
end)
|
||||
end)
|
||||
|
Loading…
Reference in New Issue
Block a user