refactor(change): do API changes to buffer without curbuf switch

Most of the messy things when changing a non-current buffer is
not about the buffer, it is about windows. In particular, it is about
`curwin`.

When editing a non-current buffer which is displayed in some other
window in the current tabpage, one such window will be "borrowed" as the
curwin. But this means if two or more non-current windows displayed the buffers,
one of them will be treated differenty. this is not desirable.

In particular, with nvim_buf_set_text, cursor _column_ position was only
corrected for one single window. Two new tests are added: the test
with just one non-current window passes, but the one with two didn't.

Two corresponding such tests were also added for nvim_buf_set_lines.
This already worked correctly on master, but make sure this is
well-tested for future refactors.

Also, nvim_create_buf no longer invokes autocmds just because you happened
to use `scratch=true`. No option value was changed, therefore OptionSet
must not be fired.
This commit is contained in:
bfredl
2023-08-21 14:52:17 +02:00
parent 1635c9e75e
commit 0081549547
33 changed files with 737 additions and 548 deletions

View File

@@ -386,27 +386,29 @@ void nvim_buf_set_lines(uint64_t channel_id, Buffer buffer, Integer start, Integ
}
try_start();
aco_save_T aco;
aucmd_prepbuf(&aco, buf);
if (!MODIFIABLE(buf)) {
api_set_error(err, kErrorTypeException, "Buffer is not 'modifiable'");
goto end;
}
if (u_save((linenr_T)(start - 1), (linenr_T)end) == FAIL) {
if (!buf_ensure_loaded(buf)) {
goto end;
}
if (u_save_buf(buf, (linenr_T)(start - 1), (linenr_T)end) == FAIL) {
api_set_error(err, kErrorTypeException, "Failed to save undo information");
goto end;
}
bcount_t deleted_bytes = get_region_bytecount(curbuf, (linenr_T)start, (linenr_T)end, 0, 0);
bcount_t deleted_bytes = get_region_bytecount(buf, (linenr_T)start, (linenr_T)end, 0, 0);
// If the size of the range is reducing (ie, new_len < old_len) we
// need to delete some old_len. We do this at the start, by
// repeatedly deleting line "start".
size_t to_delete = (new_len < old_len) ? old_len - new_len : 0;
for (size_t i = 0; i < to_delete; i++) {
if (ml_delete((linenr_T)start, false) == FAIL) {
if (ml_delete_buf(buf, (linenr_T)start, false) == FAIL) {
api_set_error(err, kErrorTypeException, "Failed to delete line");
goto end;
}
@@ -428,7 +430,7 @@ void nvim_buf_set_lines(uint64_t channel_id, Buffer buffer, Integer start, Integ
goto end;
});
if (ml_replace((linenr_T)lnum, lines[i], false) == FAIL) {
if (ml_replace_buf(buf, (linenr_T)lnum, lines[i], false) == FAIL) {
api_set_error(err, kErrorTypeException, "Failed to replace line");
goto end;
}
@@ -447,7 +449,7 @@ void nvim_buf_set_lines(uint64_t channel_id, Buffer buffer, Integer start, Integ
goto end;
});
if (ml_append((linenr_T)lnum, lines[i], 0, false) == FAIL) {
if (ml_append_buf(buf, (linenr_T)lnum, lines[i], 0, false) == FAIL) {
api_set_error(err, kErrorTypeException, "Failed to insert line");
goto end;
}
@@ -462,20 +464,18 @@ void nvim_buf_set_lines(uint64_t channel_id, Buffer buffer, Integer start, Integ
// Adjust marks. Invalidate any which lie in the
// changed range, and move any in the remainder of the buffer.
// Only adjust marks if we managed to switch to a window that holds
// the buffer, otherwise line numbers will be invalid.
mark_adjust((linenr_T)start,
(linenr_T)(end - 1),
MAXLNUM,
(linenr_T)extra,
kExtmarkNOOP);
mark_adjust_buf(buf, (linenr_T)start, (linenr_T)(end - 1), MAXLNUM, (linenr_T)extra,
true, true, kExtmarkNOOP);
extmark_splice(curbuf, (int)start - 1, 0, (int)(end - start), 0,
extmark_splice(buf, (int)start - 1, 0, (int)(end - start), 0,
deleted_bytes, (int)new_len, 0, inserted_bytes,
kExtmarkUndo);
changed_lines((linenr_T)start, 0, (linenr_T)end, (linenr_T)extra, true);
fix_cursor((linenr_T)start, (linenr_T)end, (linenr_T)extra);
changed_lines(buf, (linenr_T)start, 0, (linenr_T)end, (linenr_T)extra, true);
if (curwin->w_buffer == buf) {
// mark_adjust_buf handles non-current windows
fix_cursor(curwin, (linenr_T)start, (linenr_T)end, (linenr_T)extra);
}
end:
for (size_t i = 0; i < new_len; i++) {
@@ -483,7 +483,6 @@ end:
}
xfree(lines);
aucmd_restbuf(&aco);
try_end(err);
}
@@ -630,17 +629,19 @@ void nvim_buf_set_text(uint64_t channel_id, Buffer buffer, Integer start_row, In
}
try_start();
aco_save_T aco;
aucmd_prepbuf(&aco, buf);
if (!MODIFIABLE(buf)) {
api_set_error(err, kErrorTypeException, "Buffer is not 'modifiable'");
goto end;
}
if (!buf_ensure_loaded(buf)) {
goto end;
}
// Small note about undo states: unlike set_lines, we want to save the
// undo state of one past the end_row, since end_row is inclusive.
if (u_save((linenr_T)start_row - 1, (linenr_T)end_row + 1) == FAIL) {
if (u_save_buf(buf, (linenr_T)start_row - 1, (linenr_T)end_row + 1) == FAIL) {
api_set_error(err, kErrorTypeException, "Failed to save undo information");
goto end;
}
@@ -653,7 +654,7 @@ void nvim_buf_set_text(uint64_t channel_id, Buffer buffer, Integer start_row, In
// repeatedly deleting line "start".
size_t to_delete = (new_len < old_len) ? old_len - new_len : 0;
for (size_t i = 0; i < to_delete; i++) {
if (ml_delete((linenr_T)start_row, false) == FAIL) {
if (ml_delete_buf(buf, (linenr_T)start_row, false) == FAIL) {
api_set_error(err, kErrorTypeException, "Failed to delete line");
goto end;
}
@@ -674,7 +675,7 @@ void nvim_buf_set_text(uint64_t channel_id, Buffer buffer, Integer start_row, In
goto end;
});
if (ml_replace((linenr_T)lnum, lines[i], false) == FAIL) {
if (ml_replace_buf(buf, (linenr_T)lnum, lines[i], false) == FAIL) {
api_set_error(err, kErrorTypeException, "Failed to replace line");
goto end;
}
@@ -691,7 +692,7 @@ void nvim_buf_set_text(uint64_t channel_id, Buffer buffer, Integer start_row, In
goto end;
});
if (ml_append((linenr_T)lnum, lines[i], 0, false) == FAIL) {
if (ml_append_buf(buf, (linenr_T)lnum, lines[i], 0, false) == FAIL) {
api_set_error(err, kErrorTypeException, "Failed to insert line");
goto end;
}
@@ -702,35 +703,37 @@ void nvim_buf_set_text(uint64_t channel_id, Buffer buffer, Integer start_row, In
extra++;
}
// Adjust marks. Invalidate any which lie in the
// changed range, and move any in the remainder of the buffer.
mark_adjust((linenr_T)start_row,
(linenr_T)end_row,
MAXLNUM,
(linenr_T)extra,
kExtmarkNOOP);
colnr_T col_extent = (colnr_T)(end_col
- ((end_row == start_row) ? start_col : 0));
// Adjust marks. Invalidate any which lie in the
// changed range, and move any in the remainder of the buffer.
// Do not adjust any cursors. need to use column-aware logic (below)
mark_adjust_buf(buf, (linenr_T)start_row, (linenr_T)end_row, MAXLNUM, (linenr_T)extra,
true, false, kExtmarkNOOP);
extmark_splice(buf, (int)start_row - 1, (colnr_T)start_col,
(int)(end_row - start_row), col_extent, old_byte,
(int)new_len - 1, (colnr_T)last_item.size, new_byte,
kExtmarkUndo);
changed_lines((linenr_T)start_row, 0, (linenr_T)end_row + 1, (linenr_T)extra, true);
changed_lines(buf, (linenr_T)start_row, 0, (linenr_T)end_row + 1, (linenr_T)extra, true);
// adjust cursor like an extmark ( i e it was inside last_part_len)
if (curwin->w_cursor.lnum == end_row && curwin->w_cursor.col > end_col) {
curwin->w_cursor.col -= col_extent - (colnr_T)last_item.size;
FOR_ALL_TAB_WINDOWS(tp, win) {
if (win->w_buffer == buf) {
// adjust cursor like an extmark ( i e it was inside last_part_len)
if (win->w_cursor.lnum == end_row && win->w_cursor.col > end_col) {
win->w_cursor.col -= col_extent - (colnr_T)last_item.size;
}
fix_cursor(win, (linenr_T)start_row, (linenr_T)end_row, (linenr_T)extra);
}
}
fix_cursor((linenr_T)start_row, (linenr_T)end_row, (linenr_T)extra);
end:
for (size_t i = 0; i < new_len; i++) {
xfree(lines[i]);
}
xfree(lines);
aucmd_restbuf(&aco);
try_end(err);
early_end:
@@ -1317,21 +1320,19 @@ Dictionary nvim__buf_stats(Buffer buffer, Error *err)
// Check if deleting lines made the cursor position invalid.
// Changed lines from `lo` to `hi`; added `extra` lines (negative if deleted).
static void fix_cursor(linenr_T lo, linenr_T hi, linenr_T extra)
static void fix_cursor(win_T *win, linenr_T lo, linenr_T hi, linenr_T extra)
{
if (curwin->w_cursor.lnum >= lo) {
if (win->w_cursor.lnum >= lo) {
// Adjust cursor position if it's in/after the changed lines.
if (curwin->w_cursor.lnum >= hi) {
curwin->w_cursor.lnum += extra;
check_cursor_col();
if (win->w_cursor.lnum >= hi) {
win->w_cursor.lnum += extra;
} else if (extra < 0) {
check_cursor();
} else {
check_cursor_col();
check_cursor_lnum(win);
}
changed_cline_bef_curs();
check_cursor_col_win(win);
changed_cline_bef_curs(win);
}
invalidate_botline();
invalidate_botline(win);
}
/// Initialise a string array either: