aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/nvim/api/buffer.c284
-rw-r--r--src/nvim/edit.c3
-rw-r--r--src/nvim/ex_getln.c7
-rw-r--r--src/nvim/extmark.c17
-rw-r--r--src/nvim/message.c10
-rw-r--r--src/nvim/mouse.c46
-rw-r--r--src/nvim/normal.c15
-rw-r--r--src/nvim/option.c12
-rw-r--r--src/nvim/ui.c63
9 files changed, 359 insertions, 98 deletions
diff --git a/src/nvim/api/buffer.c b/src/nvim/api/buffer.c
index f1151d196a..8d82d22040 100644
--- a/src/nvim/api/buffer.c
+++ b/src/nvim/api/buffer.c
@@ -309,6 +309,27 @@ end:
return rv;
}
+static bool check_string_array(Array arr, bool disallow_nl, Error *err)
+{
+ for (size_t i = 0; i < arr.size; i++) {
+ if (arr.items[i].type != kObjectTypeString) {
+ api_set_error(err,
+ kErrorTypeValidation,
+ "All items in the replacement array must be strings");
+ return false;
+ }
+ // Disallow newlines in the middle of the line.
+ if (disallow_nl) {
+ const String l = arr.items[i].data.string;
+ if (memchr(l.data, NL, l.size)) {
+ api_set_error(err, kErrorTypeValidation,
+ "String cannot contain newlines");
+ return false;
+ }
+ }
+ }
+ return true;
+}
/// Sets (replaces) a line-range in the buffer.
///
@@ -362,22 +383,9 @@ void nvim_buf_set_lines(uint64_t channel_id,
return;
}
- for (size_t i = 0; i < replacement.size; i++) {
- if (replacement.items[i].type != kObjectTypeString) {
- api_set_error(err,
- kErrorTypeValidation,
- "All items in the replacement array must be strings");
- return;
- }
- // Disallow newlines in the middle of the line.
- if (channel_id != VIML_INTERNAL_CALL) {
- const String l = replacement.items[i].data.string;
- if (memchr(l.data, NL, l.size)) {
- api_set_error(err, kErrorTypeValidation,
- "String cannot contain newlines");
- return;
- }
- }
+ bool disallow_nl = (channel_id != VIML_INTERNAL_CALL);
+ if (!check_string_array(replacement, disallow_nl, err)) {
+ return;
}
size_t new_len = replacement.size;
@@ -487,6 +495,250 @@ end:
try_end(err);
}
+/// Sets (replaces) a range in the buffer
+///
+/// This is recommended over nvim_buf_set_lines when only modifying parts of a
+/// line, as extmarks will be preserved on non-modified parts of the touched
+/// lines.
+///
+/// Indexing is zero-based and end-exclusive.
+///
+/// To insert text at a given index, set `start` and `end` ranges to the same
+/// index. To delete a range, set `replacement` to an array containing
+/// an empty string, or simply an empty array.
+///
+/// Prefer nvim_buf_set_lines when adding or deleting entire lines only.
+///
+/// @param channel_id
+/// @param buffer Buffer handle, or 0 for current buffer
+/// @param start_row First line index
+/// @param start_column Last column
+/// @param end_row Last line index
+/// @param end_column Last column
+/// @param replacement Array of lines to use as replacement
+/// @param[out] err Error details, if any
+void nvim_buf_set_text(uint64_t channel_id, Buffer buffer,
+ Integer start_row, Integer start_col,
+ Integer end_row, Integer end_col,
+ ArrayOf(String) replacement, Error *err)
+ FUNC_API_SINCE(7)
+{
+ FIXED_TEMP_ARRAY(scratch, 1);
+ if (replacement.size == 0) {
+ scratch.items[0] = STRING_OBJ(STATIC_CSTR_AS_STRING(""));
+ replacement = scratch;
+ }
+
+ buf_T *buf = find_buffer_by_handle(buffer, err);
+ if (!buf) {
+ return;
+ }
+
+ bool oob = false;
+
+ // check range is ordered and everything!
+ // start_row, end_row within buffer len (except add text past the end?)
+ start_row = normalize_index(buf, start_row, &oob);
+ if (oob || start_row == buf->b_ml.ml_line_count + 1) {
+ api_set_error(err, kErrorTypeValidation, "start_row out of bounds");
+ return;
+ }
+
+ end_row = normalize_index(buf, end_row, &oob);
+ if (oob || end_row == buf->b_ml.ml_line_count + 1) {
+ api_set_error(err, kErrorTypeValidation, "end_row out of bounds");
+ return;
+ }
+
+ char *str_at_start = (char *)ml_get_buf(buf, start_row, false);
+ if (start_col < 0 || (size_t)start_col > strlen(str_at_start)) {
+ api_set_error(err, kErrorTypeValidation, "start_col out of bounds");
+ return;
+ }
+
+ char *str_at_end = (char *)ml_get_buf(buf, end_row, false);
+ size_t len_at_end = strlen(str_at_end);
+ if (end_col < 0 || (size_t)end_col > len_at_end) {
+ api_set_error(err, kErrorTypeValidation, "end_col out of bounds");
+ return;
+ }
+
+ if (start_row > end_row || (end_row == start_row && start_col > end_col)) {
+ api_set_error(err, kErrorTypeValidation, "start is higher than end");
+ return;
+ }
+
+ bool disallow_nl = (channel_id != VIML_INTERNAL_CALL);
+ if (!check_string_array(replacement, disallow_nl, err)) {
+ return;
+ }
+
+ size_t new_len = replacement.size;
+
+ bcount_t new_byte = 0;
+ bcount_t old_byte = 0;
+
+ // calculate byte size of old region before it gets modified/deleted
+ if (start_row == end_row) {
+ old_byte = (bcount_t)end_col - start_col;
+ } else {
+ const char *bufline;
+ old_byte += (bcount_t)strlen(str_at_start) - start_col;
+ for (int64_t i = 1; i < end_row - start_row; i++) {
+ int64_t lnum = start_row + i;
+
+ bufline = (char *)ml_get_buf(buf, lnum, false);
+ old_byte += (bcount_t)(strlen(bufline))+1;
+ }
+ old_byte += (bcount_t)end_col+1;
+ }
+
+ String first_item = replacement.items[0].data.string;
+ String last_item = replacement.items[replacement.size-1].data.string;
+
+ size_t firstlen = (size_t)start_col+first_item.size;
+ size_t last_part_len = strlen(str_at_end) - (size_t)end_col;
+ if (replacement.size == 1) {
+ firstlen += last_part_len;
+ }
+ char *first = xmallocz(firstlen), *last = NULL;
+ memcpy(first, str_at_start, (size_t)start_col);
+ memcpy(first+start_col, first_item.data, first_item.size);
+ memchrsub(first+start_col, NUL, NL, first_item.size);
+ if (replacement.size == 1) {
+ memcpy(first+start_col+first_item.size, str_at_end+end_col, last_part_len);
+ } else {
+ last = xmallocz(last_item.size+last_part_len);
+ memcpy(last, last_item.data, last_item.size);
+ memchrsub(last, NUL, NL, last_item.size);
+ memcpy(last+last_item.size, str_at_end+end_col, last_part_len);
+ }
+
+ char **lines = (new_len != 0) ? xcalloc(new_len, sizeof(char *)) : NULL;
+ lines[0] = first;
+ new_byte += (bcount_t)(first_item.size);
+ for (size_t i = 1; i < new_len-1; i++) {
+ const String l = replacement.items[i].data.string;
+
+ // Fill lines[i] with l's contents. Convert NULs to newlines as required by
+ // NL-used-for-NUL.
+ lines[i] = xmemdupz(l.data, l.size);
+ memchrsub(lines[i], NUL, NL, l.size);
+ new_byte += (bcount_t)(l.size)+1;
+ }
+ if (replacement.size > 1) {
+ lines[replacement.size-1] = last;
+ new_byte += (bcount_t)(last_item.size)+1;
+ }
+
+ try_start();
+ aco_save_T aco;
+ aucmd_prepbuf(&aco, (buf_T *)buf);
+
+ if (!MODIFIABLE(buf)) {
+ api_set_error(err, kErrorTypeException, "Buffer is not 'modifiable'");
+ 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) {
+ api_set_error(err, kErrorTypeException, "Failed to save undo information");
+ goto end;
+ }
+
+ ptrdiff_t extra = 0; // lines added to text, can be negative
+ size_t old_len = (size_t)(end_row-start_row+1);
+
+ // 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) ? (size_t)(old_len - new_len) : 0;
+ for (size_t i = 0; i < to_delete; i++) {
+ if (ml_delete((linenr_T)start_row, false) == FAIL) {
+ api_set_error(err, kErrorTypeException, "Failed to delete line");
+ goto end;
+ }
+ }
+
+ if (to_delete > 0) {
+ extra -= (ptrdiff_t)to_delete;
+ }
+
+ // For as long as possible, replace the existing old_len with the
+ // new old_len. This is a more efficient operation, as it requires
+ // less memory allocation and freeing.
+ size_t to_replace = old_len < new_len ? old_len : new_len;
+ for (size_t i = 0; i < to_replace; i++) {
+ int64_t lnum = start_row + (int64_t)i;
+
+ if (lnum >= MAXLNUM) {
+ api_set_error(err, kErrorTypeValidation, "Index value is too high");
+ goto end;
+ }
+
+ if (ml_replace((linenr_T)lnum, (char_u *)lines[i], false) == FAIL) {
+ api_set_error(err, kErrorTypeException, "Failed to replace line");
+ goto end;
+ }
+ // Mark lines that haven't been passed to the buffer as they need
+ // to be freed later
+ lines[i] = NULL;
+ }
+
+ // Now we may need to insert the remaining new old_len
+ for (size_t i = to_replace; i < new_len; i++) {
+ int64_t lnum = start_row + (int64_t)i - 1;
+
+ if (lnum >= MAXLNUM) {
+ api_set_error(err, kErrorTypeValidation, "Index value is too high");
+ goto end;
+ }
+
+ if (ml_append((linenr_T)lnum, (char_u *)lines[i], 0, false) == FAIL) {
+ api_set_error(err, kErrorTypeException, "Failed to insert line");
+ goto end;
+ }
+
+ // Same as with replacing, but we also need to free lines
+ xfree(lines[i]);
+ lines[i] = NULL;
+ 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,
+ (long)extra,
+ kExtmarkNOOP);
+
+ colnr_T col_extent = (colnr_T)(end_col
+ - ((end_row == start_row) ? start_col : 0));
+ 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, (long)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;
+ }
+ 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);
+}
+
/// Returns the byte offset of a line (0-indexed). |api-indexing|
///
/// Line 1 (index=0) has offset 0. UTF-8 bytes are counted. EOL is one byte.
diff --git a/src/nvim/edit.c b/src/nvim/edit.c
index 4546aa4419..876e53e3cd 100644
--- a/src/nvim/edit.c
+++ b/src/nvim/edit.c
@@ -8329,9 +8329,6 @@ static void ins_mouse(int c)
pos_T tpos;
win_T *old_curwin = curwin;
- if (!mouse_has(MOUSE_INSERT))
- return;
-
undisplay_dollar();
tpos = curwin->w_cursor;
if (do_mouse(NULL, c, BACKWARD, 1, 0)) {
diff --git a/src/nvim/ex_getln.c b/src/nvim/ex_getln.c
index 49a3c6e4b8..0f50d5153d 100644
--- a/src/nvim/ex_getln.c
+++ b/src/nvim/ex_getln.c
@@ -1884,9 +1884,6 @@ static int command_line_handle_key(CommandLineState *s)
return command_line_not_changed(s); // Ignore mouse
case K_MIDDLEMOUSE:
- if (!mouse_has(MOUSE_COMMAND)) {
- return command_line_not_changed(s); // Ignore mouse
- }
cmdline_paste(eval_has_provider("clipboard") ? '*' : 0, true, true);
redrawcmd();
return command_line_changed(s);
@@ -1910,10 +1907,6 @@ static int command_line_handle_key(CommandLineState *s)
s->ignore_drag_release = false;
}
- if (!mouse_has(MOUSE_COMMAND)) {
- return command_line_not_changed(s); // Ignore mouse
- }
-
ccline.cmdspos = cmd_startcol();
for (ccline.cmdpos = 0; ccline.cmdpos < ccline.cmdlen;
ccline.cmdpos++) {
diff --git a/src/nvim/extmark.c b/src/nvim/extmark.c
index ba685b158e..b2d8532cd7 100644
--- a/src/nvim/extmark.c
+++ b/src/nvim/extmark.c
@@ -560,6 +560,23 @@ void extmark_adjust(buf_T *buf,
new_row, 0, new_byte, undo);
}
+// Adjust extmarks following a text edit.
+//
+// @param buf
+// @param start_row Start row of the region to be changed
+// @param start_col Start col of the region to be changed
+// @param old_row End row of the region to be changed.
+// Encoded as an offset to start_row.
+// @param old_col End col of the region to be changed. Encodes
+// an offset from start_col if old_row = 0; otherwise,
+// encodes the end column of the old region.
+// @param old_byte Byte extent of the region to be changed.
+// @param new_row Row offset of the new region.
+// @param new_col Col offset of the new region. Encodes an offset from
+// start_col if new_row = 0; otherwise, encodes
+// the end column of the new region.
+// @param new_byte Byte extent of the new region.
+// @param undo
void extmark_splice(buf_T *buf,
int start_row, colnr_T start_col,
int old_row, colnr_T old_col, bcount_t old_byte,
diff --git a/src/nvim/message.c b/src/nvim/message.c
index 02a7732f5c..f94529c687 100644
--- a/src/nvim/message.c
+++ b/src/nvim/message.c
@@ -1157,15 +1157,7 @@ void wait_return(int redraw)
|| c == K_MIDDLEDRAG || c == K_MIDDLERELEASE
|| c == K_RIGHTDRAG || c == K_RIGHTRELEASE
|| c == K_MOUSELEFT || c == K_MOUSERIGHT
- || c == K_MOUSEDOWN || c == K_MOUSEUP
- || (!mouse_has(MOUSE_RETURN)
- && mouse_row < msg_row
- && (c == K_LEFTMOUSE
- || c == K_MIDDLEMOUSE
- || c == K_RIGHTMOUSE
- || c == K_X1MOUSE
- || c == K_X2MOUSE))
- );
+ || c == K_MOUSEDOWN || c == K_MOUSEUP);
os_breakcheck();
/*
* Avoid that the mouse-up event causes visual mode to start.
diff --git a/src/nvim/mouse.c b/src/nvim/mouse.c
index f05dade73f..ff471ea978 100644
--- a/src/nvim/mouse.c
+++ b/src/nvim/mouse.c
@@ -526,53 +526,9 @@ static win_T *mouse_find_grid_win(int *gridp, int *rowp, int *colp)
void setmouse(void)
{
ui_cursor_shape();
-
- // Be quick when mouse is off.
- if (*p_mouse == NUL) {
- return;
- }
-
- int checkfor = MOUSE_NORMAL; // assume normal mode
- if (VIsual_active) {
- checkfor = MOUSE_VISUAL;
- } else if (State == HITRETURN || State == ASKMORE || State == SETWSIZE) {
- checkfor = MOUSE_RETURN;
- } else if (State & INSERT) {
- checkfor = MOUSE_INSERT;
- } else if (State & CMDLINE) {
- checkfor = MOUSE_COMMAND;
- } else if (State == CONFIRM || State == EXTERNCMD) {
- checkfor = ' '; // don't use mouse for ":confirm" or ":!cmd"
- }
-
- if (mouse_has(checkfor)) {
- ui_call_mouse_on();
- } else {
- ui_call_mouse_off();
- }
+ ui_check_mouse();
}
-/*
- * Return true if
- * - "c" is in 'mouse', or
- * - 'a' is in 'mouse' and "c" is in MOUSE_A, or
- * - the current buffer is a help file and 'h' is in 'mouse' and we are in a
- * normal editing mode (not at hit-return message).
- */
-int mouse_has(int c)
-{
- for (char_u *p = p_mouse; *p; ++p)
- switch (*p) {
- case 'a': if (vim_strchr((char_u *)MOUSE_A, c) != NULL)
- return true;
- break;
- case MOUSE_HELP: if (c != MOUSE_RETURN && curbuf->b_help)
- return true;
- break;
- default: if (c == *p) return true; break;
- }
- return false;
-}
// Set orig_topline. Used when jumping to another window, so that a double
// click still works.
diff --git a/src/nvim/normal.c b/src/nvim/normal.c
index f93d772068..4e955667dc 100644
--- a/src/nvim/normal.c
+++ b/src/nvim/normal.c
@@ -2375,10 +2375,10 @@ do_mouse (
* Also paste at the cursor if the current mode isn't in 'mouse' (only
* happens for the GUI).
*/
- if ((State & INSERT) || !mouse_has(MOUSE_NORMAL)) {
- if (regname == '.')
+ if ((State & INSERT)) {
+ if (regname == '.') {
insert_reg(regname, true);
- else {
+ } else {
if (regname == 0 && eval_has_provider("clipboard")) {
regname = '*';
}
@@ -2558,8 +2558,9 @@ do_mouse (
* on a status line */
if (VIsual_active)
jump_flags |= MOUSE_MAY_STOP_VIS;
- } else if (mouse_has(MOUSE_VISUAL))
+ } else {
jump_flags |= MOUSE_MAY_VIS;
+ }
} else if (which_button == MOUSE_RIGHT) {
if (is_click && VIsual_active) {
/*
@@ -2575,8 +2576,7 @@ do_mouse (
}
}
jump_flags |= MOUSE_FOCUS;
- if (mouse_has(MOUSE_VISUAL))
- jump_flags |= MOUSE_MAY_VIS;
+ jump_flags |= MOUSE_MAY_VIS;
}
}
@@ -2790,8 +2790,7 @@ do_mouse (
/* Handle double clicks, unless on status line */
else if (in_status_line) {
} else if (in_sep_line) {
- } else if ((mod_mask & MOD_MASK_MULTI_CLICK) && (State & (NORMAL | INSERT))
- && mouse_has(MOUSE_VISUAL)) {
+ } else if ((mod_mask & MOD_MASK_MULTI_CLICK) && (State & (NORMAL | INSERT))) {
if (is_click || !VIsual_active) {
if (VIsual_active) {
orig_cursor = VIsual;
diff --git a/src/nvim/option.c b/src/nvim/option.c
index d60c8bc01c..d43dd9ba15 100644
--- a/src/nvim/option.c
+++ b/src/nvim/option.c
@@ -3210,11 +3210,7 @@ ambw_end:
}
if (varp == &p_mouse) {
- if (*p_mouse == NUL) {
- ui_call_mouse_off();
- } else {
- setmouse(); // in case 'mouse' changed
- }
+ setmouse(); // in case 'mouse' changed
}
if (curwin->w_curswant != MAXCOL
@@ -4984,11 +4980,7 @@ void ui_refresh_options(void)
ui_call_option_set(name, value);
}
if (p_mouse != NULL) {
- if (*p_mouse == NUL) {
- ui_call_mouse_off();
- } else {
- setmouse();
- }
+ setmouse();
}
}
diff --git a/src/nvim/ui.c b/src/nvim/ui.c
index 685da77b39..c6c09c80d7 100644
--- a/src/nvim/ui.c
+++ b/src/nvim/ui.c
@@ -61,6 +61,9 @@ static bool pending_mode_info_update = false;
static bool pending_mode_update = false;
static handle_T cursor_grid_handle = DEFAULT_GRID_HANDLE;
+static bool has_mouse = false;
+static int pending_has_mouse = -1;
+
#if MIN_LOG_LEVEL > DEBUG_LOG_LEVEL
# define UI_LOG(funname)
#else
@@ -220,6 +223,7 @@ void ui_refresh(void)
ui_mode_info_set();
pending_mode_update = true;
ui_cursor_shape();
+ pending_has_mouse = -1;
}
int ui_pum_get_height(void)
@@ -459,10 +463,69 @@ void ui_flush(void)
ui_call_mode_change(cstr_as_string(full_name), ui_mode_idx);
pending_mode_update = false;
}
+ if (pending_has_mouse != has_mouse) {
+ (has_mouse ? ui_call_mouse_on : ui_call_mouse_off)();
+ pending_has_mouse = has_mouse;
+ }
ui_call_flush();
}
+
+/// Check if 'mouse' is active for the current mode
+///
+/// TODO(bfredl): precompute the State -> active mapping when 'mouse' changes,
+/// then this can be checked directly in ui_flush()
+void ui_check_mouse(void)
+{
+ has_mouse = false;
+ // Be quick when mouse is off.
+ if (*p_mouse == NUL) {
+ return;
+ }
+
+ int checkfor = MOUSE_NORMAL; // assume normal mode
+ if (VIsual_active) {
+ checkfor = MOUSE_VISUAL;
+ } else if (State == HITRETURN || State == ASKMORE || State == SETWSIZE) {
+ checkfor = MOUSE_RETURN;
+ } else if (State & INSERT) {
+ checkfor = MOUSE_INSERT;
+ } else if (State & CMDLINE) {
+ checkfor = MOUSE_COMMAND;
+ } else if (State == CONFIRM || State == EXTERNCMD) {
+ checkfor = ' '; // don't use mouse for ":confirm" or ":!cmd"
+ }
+
+ // mouse should be active if at least one of the following is true:
+ // - "c" is in 'mouse', or
+ // - 'a' is in 'mouse' and "c" is in MOUSE_A, or
+ // - the current buffer is a help file and 'h' is in 'mouse' and we are in a
+ // normal editing mode (not at hit-return message).
+ for (char_u *p = p_mouse; *p; p++) {
+ switch (*p) {
+ case 'a':
+ if (vim_strchr((char_u *)MOUSE_A, checkfor) != NULL) {
+ has_mouse = true;
+ return;
+ }
+ break;
+ case MOUSE_HELP:
+ if (checkfor != MOUSE_RETURN && curbuf->b_help) {
+ has_mouse = true;
+ return;
+ }
+ break;
+ default:
+ if (checkfor == *p) {
+ has_mouse = true;
+ return;
+ }
+ }
+ }
+}
+
/// Check if current mode has changed.
+///
/// May update the shape of the cursor.
void ui_cursor_shape(void)
{