mirror of
https://github.com/neovim/neovim.git
synced 2025-09-06 03:18:16 +00:00
Merge pull request #24503 from zeertzjq/backport
Backport of #24289 and #24489 to release-0.9
This commit is contained in:
@@ -128,6 +128,30 @@ typedef struct command_line_state {
|
|||||||
long *b_im_ptr;
|
long *b_im_ptr;
|
||||||
} CommandLineState;
|
} CommandLineState;
|
||||||
|
|
||||||
|
typedef struct cmdpreview_undo_info {
|
||||||
|
u_header_T *save_b_u_oldhead;
|
||||||
|
u_header_T *save_b_u_newhead;
|
||||||
|
u_header_T *save_b_u_curhead;
|
||||||
|
int save_b_u_numhead;
|
||||||
|
bool save_b_u_synced;
|
||||||
|
long save_b_u_seq_last;
|
||||||
|
long save_b_u_save_nr_last;
|
||||||
|
long save_b_u_seq_cur;
|
||||||
|
time_t save_b_u_time_cur;
|
||||||
|
long save_b_u_save_nr_cur;
|
||||||
|
char *save_b_u_line_ptr;
|
||||||
|
linenr_T save_b_u_line_lnum;
|
||||||
|
colnr_T save_b_u_line_colnr;
|
||||||
|
} CpUndoInfo;
|
||||||
|
|
||||||
|
typedef struct cmdpreview_buf_info {
|
||||||
|
buf_T *buf;
|
||||||
|
long save_b_p_ul;
|
||||||
|
int save_b_changed;
|
||||||
|
varnumber_T save_changedtick;
|
||||||
|
CpUndoInfo undo_info;
|
||||||
|
} CpBufInfo;
|
||||||
|
|
||||||
typedef struct cmdpreview_win_info {
|
typedef struct cmdpreview_win_info {
|
||||||
win_T *win;
|
win_T *win;
|
||||||
pos_T save_w_cursor;
|
pos_T save_w_cursor;
|
||||||
@@ -136,17 +160,6 @@ typedef struct cmdpreview_win_info {
|
|||||||
int save_w_p_cuc;
|
int save_w_p_cuc;
|
||||||
} CpWinInfo;
|
} CpWinInfo;
|
||||||
|
|
||||||
typedef struct cmdpreview_buf_info {
|
|
||||||
buf_T *buf;
|
|
||||||
bool save_b_u_synced;
|
|
||||||
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 {
|
typedef struct cmdpreview_info {
|
||||||
kvec_t(CpWinInfo) win_info;
|
kvec_t(CpWinInfo) win_info;
|
||||||
kvec_t(CpBufInfo) buf_info;
|
kvec_t(CpBufInfo) buf_info;
|
||||||
@@ -2272,8 +2285,48 @@ static void cmdpreview_close_win(void)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Save the undo state of a buffer for command preview.
|
||||||
|
static void cmdpreview_save_undo(CpUndoInfo *cp_undoinfo, buf_T *buf)
|
||||||
|
FUNC_ATTR_NONNULL_ALL
|
||||||
|
{
|
||||||
|
cp_undoinfo->save_b_u_synced = buf->b_u_synced;
|
||||||
|
cp_undoinfo->save_b_u_oldhead = buf->b_u_oldhead;
|
||||||
|
cp_undoinfo->save_b_u_newhead = buf->b_u_newhead;
|
||||||
|
cp_undoinfo->save_b_u_curhead = buf->b_u_curhead;
|
||||||
|
cp_undoinfo->save_b_u_numhead = buf->b_u_numhead;
|
||||||
|
cp_undoinfo->save_b_u_seq_last = buf->b_u_seq_last;
|
||||||
|
cp_undoinfo->save_b_u_save_nr_last = buf->b_u_save_nr_last;
|
||||||
|
cp_undoinfo->save_b_u_seq_cur = buf->b_u_seq_cur;
|
||||||
|
cp_undoinfo->save_b_u_time_cur = buf->b_u_time_cur;
|
||||||
|
cp_undoinfo->save_b_u_save_nr_cur = buf->b_u_save_nr_cur;
|
||||||
|
cp_undoinfo->save_b_u_line_ptr = buf->b_u_line_ptr;
|
||||||
|
cp_undoinfo->save_b_u_line_lnum = buf->b_u_line_lnum;
|
||||||
|
cp_undoinfo->save_b_u_line_colnr = buf->b_u_line_colnr;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Restore the undo state of a buffer for command preview.
|
||||||
|
static void cmdpreview_restore_undo(const CpUndoInfo *cp_undoinfo, buf_T *buf)
|
||||||
|
{
|
||||||
|
buf->b_u_oldhead = cp_undoinfo->save_b_u_oldhead;
|
||||||
|
buf->b_u_newhead = cp_undoinfo->save_b_u_newhead;
|
||||||
|
buf->b_u_curhead = cp_undoinfo->save_b_u_curhead;
|
||||||
|
buf->b_u_numhead = cp_undoinfo->save_b_u_numhead;
|
||||||
|
buf->b_u_seq_last = cp_undoinfo->save_b_u_seq_last;
|
||||||
|
buf->b_u_save_nr_last = cp_undoinfo->save_b_u_save_nr_last;
|
||||||
|
buf->b_u_seq_cur = cp_undoinfo->save_b_u_seq_cur;
|
||||||
|
buf->b_u_time_cur = cp_undoinfo->save_b_u_time_cur;
|
||||||
|
buf->b_u_save_nr_cur = cp_undoinfo->save_b_u_save_nr_cur;
|
||||||
|
buf->b_u_line_ptr = cp_undoinfo->save_b_u_line_ptr;
|
||||||
|
buf->b_u_line_lnum = cp_undoinfo->save_b_u_line_lnum;
|
||||||
|
buf->b_u_line_colnr = cp_undoinfo->save_b_u_line_colnr;
|
||||||
|
if (buf->b_u_curhead == NULL) {
|
||||||
|
buf->b_u_synced = cp_undoinfo->save_b_u_synced;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Save current state and prepare windows and buffers for command preview.
|
/// Save current state and prepare windows and buffers for command preview.
|
||||||
static void cmdpreview_prepare(CpInfo *cpinfo)
|
static void cmdpreview_prepare(CpInfo *cpinfo)
|
||||||
|
FUNC_ATTR_NONNULL_ALL
|
||||||
{
|
{
|
||||||
kv_init(cpinfo->buf_info);
|
kv_init(cpinfo->buf_info);
|
||||||
kv_init(cpinfo->win_info);
|
kv_init(cpinfo->win_info);
|
||||||
@@ -2289,14 +2342,13 @@ static void cmdpreview_prepare(CpInfo *cpinfo)
|
|||||||
CpBufInfo cp_bufinfo;
|
CpBufInfo cp_bufinfo;
|
||||||
cp_bufinfo.buf = buf;
|
cp_bufinfo.buf = buf;
|
||||||
|
|
||||||
cp_bufinfo.save_b_u_synced = buf->b_u_synced;
|
|
||||||
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_p_ul = buf->b_p_ul;
|
||||||
cp_bufinfo.save_b_changed = buf->b_changed;
|
cp_bufinfo.save_b_changed = buf->b_changed;
|
||||||
cp_bufinfo.save_changedtick = buf_get_changedtick(buf);
|
cp_bufinfo.save_changedtick = buf_get_changedtick(buf);
|
||||||
|
|
||||||
|
cmdpreview_save_undo(&cp_bufinfo.undo_info, buf);
|
||||||
|
u_clearall(buf);
|
||||||
|
|
||||||
kv_push(cpinfo->buf_info, cp_bufinfo);
|
kv_push(cpinfo->buf_info, cp_bufinfo);
|
||||||
|
|
||||||
buf->b_p_ul = LONG_MAX; // Make sure we can undo all changes
|
buf->b_p_ul = LONG_MAX; // Make sure we can undo all changes
|
||||||
@@ -2331,8 +2383,9 @@ static void cmdpreview_prepare(CpInfo *cpinfo)
|
|||||||
u_sync(true);
|
u_sync(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Restore the state of buffers and windows before command preview.
|
/// Restore the state of buffers and windows for command preview.
|
||||||
static void cmdpreview_restore_state(CpInfo *cpinfo)
|
static void cmdpreview_restore_state(CpInfo *cpinfo)
|
||||||
|
FUNC_ATTR_NONNULL_ALL
|
||||||
{
|
{
|
||||||
for (size_t i = 0; i < cpinfo->buf_info.size; i++) {
|
for (size_t i = 0; i < cpinfo->buf_info.size; i++) {
|
||||||
CpBufInfo cp_bufinfo = cpinfo->buf_info.items[i];
|
CpBufInfo cp_bufinfo = cpinfo->buf_info.items[i];
|
||||||
@@ -2340,31 +2393,29 @@ static void cmdpreview_restore_state(CpInfo *cpinfo)
|
|||||||
|
|
||||||
buf->b_changed = cp_bufinfo.save_b_changed;
|
buf->b_changed = cp_bufinfo.save_b_changed;
|
||||||
|
|
||||||
if (buf->b_u_seq_cur != cp_bufinfo.save_b_u_seq_cur) {
|
if (buf->b_u_seq_cur != cp_bufinfo.undo_info.save_b_u_seq_cur) {
|
||||||
int count = 0;
|
int count = 0;
|
||||||
|
|
||||||
// Calculate how many undo steps are necessary to restore earlier state.
|
// 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;
|
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 != NULL;
|
||||||
uhp = uhp->uh_next.ptr, ++count) {}
|
uhp = uhp->uh_next.ptr, ++count) {}
|
||||||
|
|
||||||
aco_save_T aco;
|
aco_save_T aco;
|
||||||
aucmd_prepbuf(&aco, buf);
|
aucmd_prepbuf(&aco, buf);
|
||||||
|
// Ensure all the entries will be undone
|
||||||
|
if (curbuf->b_u_synced == false) {
|
||||||
|
u_sync(true);
|
||||||
|
}
|
||||||
// Undo invisibly. This also moves the cursor!
|
// Undo invisibly. This also moves the cursor!
|
||||||
if (!u_undo_and_forget(count, false)) {
|
if (!u_undo_and_forget(count, false)) {
|
||||||
abort();
|
abort();
|
||||||
}
|
}
|
||||||
aucmd_restbuf(&aco);
|
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 (buf->b_u_curhead == NULL) {
|
u_blockfree(buf);
|
||||||
buf->b_u_synced = cp_bufinfo.save_b_u_synced;
|
cmdpreview_restore_undo(&cp_bufinfo.undo_info, buf);
|
||||||
}
|
|
||||||
|
|
||||||
if (cp_bufinfo.save_changedtick != buf_get_changedtick(buf)) {
|
if (cp_bufinfo.save_changedtick != buf_get_changedtick(buf)) {
|
||||||
buf_set_changedtick(buf, cp_bufinfo.save_changedtick);
|
buf_set_changedtick(buf, cp_bufinfo.save_changedtick);
|
||||||
|
@@ -435,6 +435,82 @@ describe("'inccommand' for user commands", function()
|
|||||||
]])
|
]])
|
||||||
assert_alive()
|
assert_alive()
|
||||||
end)
|
end)
|
||||||
|
|
||||||
|
it('no crash if preview callback executes undo #20036', function()
|
||||||
|
command('set inccommand=nosplit')
|
||||||
|
exec_lua([[
|
||||||
|
vim.api.nvim_create_user_command('Foo', function() end, {
|
||||||
|
nargs = '?',
|
||||||
|
preview = function(_, _, _)
|
||||||
|
vim.cmd.undo()
|
||||||
|
end,
|
||||||
|
})
|
||||||
|
]])
|
||||||
|
|
||||||
|
-- Clear undo history
|
||||||
|
command('set undolevels=-1')
|
||||||
|
feed('ggyyp')
|
||||||
|
command('set undolevels=1000')
|
||||||
|
|
||||||
|
feed('yypp:Fo')
|
||||||
|
assert_alive()
|
||||||
|
feed('<Esc>:Fo')
|
||||||
|
assert_alive()
|
||||||
|
end)
|
||||||
|
|
||||||
|
it('breaking undo chain in Insert mode works properly #20248', function()
|
||||||
|
command('set inccommand=nosplit')
|
||||||
|
command('inoremap . .<C-G>u')
|
||||||
|
exec_lua([[
|
||||||
|
vim.api.nvim_create_user_command('Test', function() end, {
|
||||||
|
nargs = 1,
|
||||||
|
preview = function(opts, _, _)
|
||||||
|
vim.cmd('norm i' .. opts.args)
|
||||||
|
return 1
|
||||||
|
end
|
||||||
|
})
|
||||||
|
]])
|
||||||
|
feed(':Test a.a.a.a.')
|
||||||
|
screen:expect([[
|
||||||
|
text on line 1 |
|
||||||
|
more text on line 2 |
|
||||||
|
oh no, even more text |
|
||||||
|
will the text ever stop |
|
||||||
|
oh well |
|
||||||
|
did the text stop |
|
||||||
|
why won't it stop |
|
||||||
|
make the text stop |
|
||||||
|
a.a.a.a. |
|
||||||
|
{2:~ }|
|
||||||
|
{2:~ }|
|
||||||
|
{2:~ }|
|
||||||
|
{2:~ }|
|
||||||
|
{2:~ }|
|
||||||
|
{2:~ }|
|
||||||
|
{2:~ }|
|
||||||
|
:Test a.a.a.a.^ |
|
||||||
|
]])
|
||||||
|
feed('<Esc>')
|
||||||
|
screen:expect([[
|
||||||
|
text on line 1 |
|
||||||
|
more text on line 2 |
|
||||||
|
oh no, even more text |
|
||||||
|
will the text ever stop |
|
||||||
|
oh well |
|
||||||
|
did the text stop |
|
||||||
|
why won't it stop |
|
||||||
|
make the text stop |
|
||||||
|
^ |
|
||||||
|
{2:~ }|
|
||||||
|
{2:~ }|
|
||||||
|
{2:~ }|
|
||||||
|
{2:~ }|
|
||||||
|
{2:~ }|
|
||||||
|
{2:~ }|
|
||||||
|
{2:~ }|
|
||||||
|
|
|
||||||
|
]])
|
||||||
|
end)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
describe("'inccommand' with multiple buffers", function()
|
describe("'inccommand' with multiple buffers", function()
|
||||||
|
Reference in New Issue
Block a user