Merge pull request #11356 from bfredl/extmark2

extmark API feature
This commit is contained in:
Björn Linse 2019-11-11 21:48:14 +01:00 committed by GitHub
commit 122426966e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 3884 additions and 58 deletions

View File

@ -438,6 +438,42 @@ Example: create a float with scratch buffer: >
call nvim_win_set_option(win, 'winhl', 'Normal:MyHighlight')
>
==============================================================================
Extended marks *api-extended-marks*
An extended mark (extmark) represents a buffer annotation that follows
movements as the buffer changes. They could be used to represent cursors,
folds, misspelled words, and anything else that needs to track a logical
location in the buffer over time.
Example:
We will set an extmark at the first row and third column. As the API is zero-
indexed, use row and column counts 0 and 2:
`let g:mark_ns = nvim_create_namespace('myplugin')`
`let g:mark_id = nvim_buf_set_extmark(0, g:mark_ns, 0, 0, 2)`
Passing in id=0 creates a new mark and returns the id. we can look-up a mark
by its id:
`echo nvim_buf_get_extmark_by_id(0, g:mark_ns, g:mark_id)`
=> [0, 2]
Or we can look-up all marks in a buffer for our namespace (or by a range):
`echo nvim_buf_get_extmarks(0, g:mark_ns, 0, -1, -1)`
=> [[1, 0, 2]]
Deleting the text all around an extended mark does not remove it. If you want
to remove an extended mark, use the |nvim_buf_del_extmark()| function.
The namespace ensures your plugin doesn't have to deal with extmarks created
by another plugin.
Mark positions changed by an edit will be restored on undo/redo. Creating and
deleting marks doesn't count as a buffer change on itself, i e new undo
states will not be created only for marks.
==============================================================================
Global Functions *api-global*
@ -1747,6 +1783,86 @@ nvim_buf_get_mark({buffer}, {name}) *nvim_buf_get_mark()*
Return: ~
(row, col) tuple
*nvim_buf_get_extmark_by_id()*
nvim_buf_get_extmark_by_id({buffer}, {ns_id}, {id})
Returns position for a given extmark id
Parameters: ~
{buffer} The buffer handle
{namespace} a identifier returned previously with
nvim_create_namespace
{id} the extmark id
Return: ~
(row, col) tuple or empty list () if extmark id was absent
*nvim_buf_get_extmarks()*
nvim_buf_get_extmarks({buffer}, {ns_id}, {start}, {end}, {opts})
List extmarks in a range (inclusive)
range ends can be specified as (row, col) tuples, as well as
extmark ids in the same namespace. In addition, 0 and -1 works
as shorthands for (0,0) and (-1,-1) respectively, so that all
marks in the buffer can be quieried as:
all_marks = nvim_buf_get_extmarks(0, my_ns, 0, -1, -1)
If end is a lower position than start, then the range will be
traversed backwards. This is mostly used with limited amount,
to be able to get the first marks prior to a given position.
Parameters: ~
{buffer} The buffer handle
{ns_id} An id returned previously from
nvim_create_namespace
{lower} One of: extmark id, (row, col) or 0, -1 for
buffer ends
{upper} One of: extmark id, (row, col) or 0, -1 for
buffer ends
{opts} additional options. Supports the keys:
• amount: Maximum number of marks to return •
Return: ~
[[nsmark_id, row, col], ...]
*nvim_buf_set_extmark()*
nvim_buf_set_extmark({buffer}, {ns_id}, {id}, {line}, {col}, {opts})
Create or update an extmark at a position
If an invalid namespace is given, an error will be raised.
To create a new extmark, pass in id=0. The new extmark id will
be returned. To move an existing mark, pass in its id.
It is also allowed to create a new mark by passing in a
previously unused id, but the caller must then keep track of
existing and unused ids itself. This is mainly useful over
RPC, to avoid needing to wait for the return value.
Parameters: ~
{buffer} The buffer handle
{ns_id} a identifier returned previously with
nvim_create_namespace
{id} The extmark's id or 0 to create a new mark.
{row} The row to set the extmark to.
{col} The column to set the extmark to.
{opts} Optional parameters. Currently not used.
Return: ~
the id of the extmark.
nvim_buf_del_extmark({buffer}, {ns_id}, {id}) *nvim_buf_del_extmark()*
Remove an extmark
Parameters: ~
{buffer} The buffer handle
{ns_id} a identifier returned previously with
nvim_create_namespace
{id} The extmarks's id
Return: ~
true on success, false if the extmark was not found.
*nvim_buf_add_highlight()*
nvim_buf_add_highlight({buffer}, {ns_id}, {hl_group}, {line},
{col_start}, {col_end})

View File

@ -23,7 +23,10 @@
#include "nvim/memory.h"
#include "nvim/misc1.h"
#include "nvim/ex_cmds.h"
#include "nvim/map_defs.h"
#include "nvim/map.h"
#include "nvim/mark.h"
#include "nvim/mark_extended.h"
#include "nvim/fileio.h"
#include "nvim/move.h"
#include "nvim/syntax.h"
@ -544,7 +547,8 @@ void nvim_buf_set_lines(uint64_t channel_id,
(linenr_T)(end - 1),
MAXLNUM,
(long)extra,
false);
false,
kExtmarkUndo);
changed_lines((linenr_T)start, 0, (linenr_T)end, (long)extra, true);
fix_cursor((linenr_T)start, (linenr_T)end, (linenr_T)extra);
@ -999,6 +1003,240 @@ ArrayOf(Integer, 2) nvim_buf_get_mark(Buffer buffer, String name, Error *err)
return rv;
}
/// Returns position for a given extmark id
///
/// @param buffer The buffer handle
/// @param namespace a identifier returned previously with nvim_create_namespace
/// @param id the extmark id
/// @param[out] err Details of an error that may have occurred
/// @return (row, col) tuple or empty list () if extmark id was absent
ArrayOf(Integer) nvim_buf_get_extmark_by_id(Buffer buffer, Integer ns_id,
Integer id, Error *err)
FUNC_API_SINCE(7)
{
Array rv = ARRAY_DICT_INIT;
buf_T *buf = find_buffer_by_handle(buffer, err);
if (!buf) {
return rv;
}
if (!ns_initialized((uint64_t)ns_id)) {
api_set_error(err, kErrorTypeValidation, _("Invalid ns_id"));
return rv;
}
ExtendedMark *extmark = extmark_from_id(buf,
(uint64_t)ns_id,
(uint64_t)id);
if (!extmark) {
return rv;
}
ADD(rv, INTEGER_OBJ((Integer)extmark->line->lnum-1));
ADD(rv, INTEGER_OBJ((Integer)extmark->col-1));
return rv;
}
/// List extmarks in a range (inclusive)
///
/// range ends can be specified as (row, col) tuples, as well as extmark
/// ids in the same namespace. In addition, 0 and -1 works as shorthands
/// for (0,0) and (-1,-1) respectively, so that all marks in the buffer can be
/// quieried as:
///
/// all_marks = nvim_buf_get_extmarks(0, my_ns, 0, -1, -1)
///
/// If end is a lower position than start, then the range will be traversed
/// backwards. This is mostly used with limited amount, to be able to get the
/// first marks prior to a given position.
///
/// @param buffer The buffer handle
/// @param ns_id An id returned previously from nvim_create_namespace
/// @param lower One of: extmark id, (row, col) or 0, -1 for buffer ends
/// @param upper One of: extmark id, (row, col) or 0, -1 for buffer ends
/// @param opts additional options. Supports the keys:
/// - amount: Maximum number of marks to return
/// @param[out] err Details of an error that may have occurred
/// @return [[nsmark_id, row, col], ...]
Array nvim_buf_get_extmarks(Buffer buffer, Integer ns_id,
Object start, Object end, Dictionary opts,
Error *err)
FUNC_API_SINCE(7)
{
Array rv = ARRAY_DICT_INIT;
buf_T *buf = find_buffer_by_handle(buffer, err);
if (!buf) {
return rv;
}
if (!ns_initialized((uint64_t)ns_id)) {
api_set_error(err, kErrorTypeValidation, _("Invalid ns_id"));
return rv;
}
Integer amount = -1;
for (size_t i = 0; i < opts.size; i++) {
String k = opts.items[i].key;
Object *v = &opts.items[i].value;
if (strequal("amount", k.data)) {
if (v->type != kObjectTypeInteger) {
api_set_error(err, kErrorTypeValidation, "amount is not an integer");
return rv;
}
amount = v->data.integer;
v->data.integer = LUA_NOREF;
} else {
api_set_error(err, kErrorTypeValidation, "unexpected key: %s", k.data);
return rv;
}
}
if (amount == 0) {
return rv;
}
bool reverse = false;
linenr_T l_lnum;
colnr_T l_col;
if (!set_extmark_index_from_obj(buf, ns_id, start, &l_lnum, &l_col, err)) {
return rv;
}
linenr_T u_lnum;
colnr_T u_col;
if (!set_extmark_index_from_obj(buf, ns_id, end, &u_lnum, &u_col, err)) {
return rv;
}
if (l_lnum > u_lnum || (l_lnum == u_lnum && l_col > u_col)) {
reverse = true;
linenr_T tmp_lnum = l_lnum;
l_lnum = u_lnum;
u_lnum = tmp_lnum;
colnr_T tmp_col = l_col;
l_col = u_col;
u_col = tmp_col;
}
ExtmarkArray marks = extmark_get(buf, (uint64_t)ns_id, l_lnum, l_col,
u_lnum, u_col, (int64_t)amount,
reverse);
for (size_t i = 0; i < kv_size(marks); i++) {
Array mark = ARRAY_DICT_INIT;
ExtendedMark *extmark = kv_A(marks, i);
ADD(mark, INTEGER_OBJ((Integer)extmark->mark_id));
ADD(mark, INTEGER_OBJ(extmark->line->lnum-1));
ADD(mark, INTEGER_OBJ(extmark->col-1));
ADD(rv, ARRAY_OBJ(mark));
}
kv_destroy(marks);
return rv;
}
/// Create or update an extmark at a position
///
/// If an invalid namespace is given, an error will be raised.
///
/// To create a new extmark, pass in id=0. The new extmark id will be
/// returned. To move an existing mark, pass in its id.
///
/// It is also allowed to create a new mark by passing in a previously unused
/// id, but the caller must then keep track of existing and unused ids itself.
/// This is mainly useful over RPC, to avoid needing to wait for the return
/// value.
///
/// @param buffer The buffer handle
/// @param ns_id a identifier returned previously with nvim_create_namespace
/// @param id The extmark's id or 0 to create a new mark.
/// @param row The row to set the extmark to.
/// @param col The column to set the extmark to.
/// @param opts Optional parameters. Currently not used.
/// @param[out] err Details of an error that may have occurred
/// @return the id of the extmark.
Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, Integer id,
Integer line, Integer col,
Dictionary opts, Error *err)
FUNC_API_SINCE(7)
{
buf_T *buf = find_buffer_by_handle(buffer, err);
if (!buf) {
return 0;
}
if (!ns_initialized((uint64_t)ns_id)) {
api_set_error(err, kErrorTypeValidation, _("Invalid ns_id"));
return 0;
}
if (opts.size > 0) {
api_set_error(err, kErrorTypeValidation, "opts dict isn't empty");
return 0;
}
size_t len = 0;
if (line < 0 || line > buf->b_ml.ml_line_count) {
api_set_error(err, kErrorTypeValidation, "line value outside range");
return 0;
} else if (line < buf->b_ml.ml_line_count) {
len = STRLEN(ml_get_buf(curbuf, (linenr_T)line+1, false));
}
if (col == -1) {
col = (Integer)len;
} else if (col < -1 || col > (Integer)len) {
api_set_error(err, kErrorTypeValidation, "col value outside range");
return 0;
}
uint64_t id_num;
if (id == 0) {
id_num = extmark_free_id_get(buf, (uint64_t)ns_id);
} else if (id > 0) {
id_num = (uint64_t)id;
} else {
api_set_error(err, kErrorTypeValidation, _("Invalid mark id"));
return 0;
}
extmark_set(buf, (uint64_t)ns_id, id_num,
(linenr_T)line+1, (colnr_T)col+1, kExtmarkUndo);
return (Integer)id_num;
}
/// Remove an extmark
///
/// @param buffer The buffer handle
/// @param ns_id a identifier returned previously with nvim_create_namespace
/// @param id The extmarks's id
/// @param[out] err Details of an error that may have occurred
/// @return true on success, false if the extmark was not found.
Boolean nvim_buf_del_extmark(Buffer buffer,
Integer ns_id,
Integer id,
Error *err)
FUNC_API_SINCE(7)
{
buf_T *buf = find_buffer_by_handle(buffer, err);
if (!buf) {
return false;
}
if (!ns_initialized((uint64_t)ns_id)) {
api_set_error(err, kErrorTypeValidation, _("Invalid ns_id"));
return false;
}
return extmark_del(buf, (uint64_t)ns_id, (uint64_t)id, kExtmarkUndo);
}
/// Adds a highlight to buffer.
///
/// Useful for plugins that dynamically generate highlights to a buffer
@ -1097,6 +1335,10 @@ void nvim_buf_clear_namespace(Buffer buffer,
}
bufhl_clear_line_range(buf, (int)ns_id, (int)line_start+1, (int)line_end);
extmark_clear(buf, ns_id == -1 ? 0 : (uint64_t)ns_id,
(linenr_T)line_start+1,
(linenr_T)line_end,
kExtmarkUndo);
}
/// Clears highlights and virtual text from namespace and range of lines

View File

@ -10,6 +10,7 @@
#include "nvim/api/private/helpers.h"
#include "nvim/api/private/defs.h"
#include "nvim/api/private/handle.h"
#include "nvim/api/vim.h"
#include "nvim/msgpack_rpc/helpers.h"
#include "nvim/lua/executor.h"
#include "nvim/ascii.h"
@ -23,6 +24,7 @@
#include "nvim/eval/typval.h"
#include "nvim/map_defs.h"
#include "nvim/map.h"
#include "nvim/mark_extended.h"
#include "nvim/option.h"
#include "nvim/option_defs.h"
#include "nvim/version.h"
@ -1505,3 +1507,131 @@ ArrayOf(Dictionary) keymap_array(String mode, buf_T *buf)
return mappings;
}
// Returns an extmark given an id or a positional index
// If throw == true then an error will be raised if nothing
// was found
// Returns NULL if something went wrong
ExtendedMark *extmark_from_id_or_pos(Buffer buffer,
Integer namespace,
Object id,
Error *err,
bool throw)
{
buf_T *buf = find_buffer_by_handle(buffer, err);
if (!buf) {
return NULL;
}
ExtendedMark *extmark = NULL;
if (id.type == kObjectTypeArray) {
if (id.data.array.size != 2) {
api_set_error(err, kErrorTypeValidation,
_("Position must have 2 elements"));
return NULL;
}
linenr_T row = (linenr_T)id.data.array.items[0].data.integer;
colnr_T col = (colnr_T)id.data.array.items[1].data.integer;
if (row < 1 || col < 1) {
if (throw) {
api_set_error(err, kErrorTypeValidation, _("Row and column MUST be > 0"));
}
return NULL;
}
extmark = extmark_from_pos(buf, (uint64_t)namespace, row, col);
} else if (id.type != kObjectTypeInteger) {
if (throw) {
api_set_error(err, kErrorTypeValidation,
_("Mark id must be an int or [row, col]"));
}
return NULL;
} else if (id.data.integer < 0) {
if (throw) {
api_set_error(err, kErrorTypeValidation, _("Mark id must be positive"));
}
return NULL;
} else {
extmark = extmark_from_id(buf,
(uint64_t)namespace,
(uint64_t)id.data.integer);
}
if (!extmark) {
if (throw) {
api_set_error(err, kErrorTypeValidation, _("Mark doesn't exist"));
}
return NULL;
}
return extmark;
}
// Is the Namespace in use?
bool ns_initialized(uint64_t ns)
{
if (ns < 1) {
return false;
}
return ns < (uint64_t)next_namespace_id;
}
/// Get line and column from extmark object
///
/// Extmarks may be queried from position or name or even special names
/// in the future such as "cursor". This function sets the line and col
/// to make the extmark functions recognize what's required
///
/// @param[out] lnum lnum to be set
/// @param[out] colnr col to be set
bool set_extmark_index_from_obj(buf_T *buf, Integer namespace,
Object obj, linenr_T *lnum, colnr_T *colnr,
Error *err)
{
// Check if it is mark id
if (obj.type == kObjectTypeInteger) {
Integer id = obj.data.integer;
if (id == 0) {
*lnum = 1;
*colnr = 1;
return true;
} else if (id == -1) {
*lnum = MAXLNUM;
*colnr = MAXCOL;
return true;
} else if (id < 0) {
api_set_error(err, kErrorTypeValidation, _("Mark id must be positive"));
return false;
}
ExtendedMark *extmark = extmark_from_id(buf, (uint64_t)namespace,
(uint64_t)id);
if (extmark) {
*lnum = extmark->line->lnum;
*colnr = extmark->col;
return true;
} else {
api_set_error(err, kErrorTypeValidation, _("No mark with requested id"));
return false;
}
// Check if it is a position
} else if (obj.type == kObjectTypeArray) {
Array pos = obj.data.array;
if (pos.size != 2
|| pos.items[0].type != kObjectTypeInteger
|| pos.items[1].type != kObjectTypeInteger) {
api_set_error(err, kErrorTypeValidation,
_("Position must have 2 integer elements"));
return false;
}
Integer line = pos.items[0].data.integer;
Integer col = pos.items[1].data.integer;
*lnum = (linenr_T)(line >= 0 ? line + 1 : MAXLNUM);
*colnr = (colnr_T)(col >= 0 ? col + 1 : MAXCOL);
return true;
} else {
api_set_error(err, kErrorTypeValidation,
_("Position must be a mark id Integer or position Array"));
return false;
}
}

View File

@ -39,6 +39,7 @@
#include "nvim/ops.h"
#include "nvim/option.h"
#include "nvim/state.h"
#include "nvim/mark_extended.h"
#include "nvim/syntax.h"
#include "nvim/getchar.h"
#include "nvim/os/input.h"

View File

@ -53,6 +53,7 @@
#include "nvim/indent_c.h"
#include "nvim/main.h"
#include "nvim/mark.h"
#include "nvim/mark_extended.h"
#include "nvim/mbyte.h"
#include "nvim/memline.h"
#include "nvim/memory.h"
@ -816,6 +817,7 @@ static void free_buffer_stuff(buf_T *buf, int free_flags)
}
uc_clear(&buf->b_ucmds); // clear local user commands
buf_delete_signs(buf, (char_u *)"*"); // delete any signs
extmark_free_all(buf); // delete any extmarks
bufhl_clear_all(buf); // delete any highligts
map_clear_int(buf, MAP_ALL_MODES, true, false); // clear local mappings
map_clear_int(buf, MAP_ALL_MODES, true, true); // clear local abbrevs
@ -5496,6 +5498,7 @@ void bufhl_clear_line_range(buf_T *buf,
linenr_T line_start,
linenr_T line_end)
{
// TODO(bfredl): implement kb_itr_interval to jump directly to the first line
kbitr_t(bufhl) itr;
BufhlLine *l, t = BUFHLLINE_INIT(line_start);
if (!kb_itr_get(bufhl, &buf->b_bufhl_info, &t, &itr)) {

View File

@ -115,6 +115,9 @@ typedef uint16_t disptick_T; // display tick type
#include "nvim/os/fs_defs.h" // for FileID
#include "nvim/terminal.h" // for Terminal
#include "nvim/lib/kbtree.h"
#include "nvim/mark_extended.h"
/*
* The taggy struct is used to store the information about a :tag command.
*/
@ -805,6 +808,10 @@ struct file_buffer {
kvec_t(BufhlLine *) b_bufhl_move_space; // temporary space for highlights
PMap(uint64_t) *b_extmark_ns; // extmark namespaces
kbtree_t(extmarklines) b_extlines; // extmarks
kvec_t(ExtMarkLine *) b_extmark_move_space; // temp space for extmarks
// array of channel_id:s which have asked to receive updates for this
// buffer.
kvec_t(uint64_t) update_channels;

View File

@ -17,6 +17,7 @@
#include "nvim/indent.h"
#include "nvim/indent_c.h"
#include "nvim/mark.h"
#include "nvim/mark_extended.h"
#include "nvim/memline.h"
#include "nvim/misc1.h"
#include "nvim/move.h"
@ -372,7 +373,7 @@ void appended_lines_mark(linenr_T lnum, long count)
// Skip mark_adjust when adding a line after the last one, there can't
// be marks there. But it's still needed in diff mode.
if (lnum + count < curbuf->b_ml.ml_line_count || curwin->w_p_diff) {
mark_adjust(lnum + 1, (linenr_T)MAXLNUM, count, 0L, false);
mark_adjust(lnum + 1, (linenr_T)MAXLNUM, count, 0L, false, kExtmarkUndo);
}
changed_lines(lnum + 1, 0, lnum + 1, count, true);
}
@ -390,7 +391,8 @@ void deleted_lines(linenr_T lnum, long count)
/// be triggered to display the cursor.
void deleted_lines_mark(linenr_T lnum, long count)
{
mark_adjust(lnum, (linenr_T)(lnum + count - 1), (long)MAXLNUM, -count, false);
mark_adjust(lnum, (linenr_T)(lnum + count - 1), (long)MAXLNUM, -count, false,
kExtmarkUndo);
changed_lines(lnum, 0, lnum + count, -count, true);
}
@ -951,6 +953,9 @@ int open_line(
bool did_append; // appended a new line
int saved_pi = curbuf->b_p_pi; // copy of preserveindent setting
linenr_T lnum = curwin->w_cursor.lnum;
colnr_T mincol = curwin->w_cursor.col + 1;
// make a copy of the current line so we can mess with it
char_u *saved_line = vim_strsave(get_cursor_line_ptr());
@ -1574,7 +1579,8 @@ int open_line(
// be marks there. But still needed in diff mode.
if (curwin->w_cursor.lnum + 1 < curbuf->b_ml.ml_line_count
|| curwin->w_p_diff) {
mark_adjust(curwin->w_cursor.lnum + 1, (linenr_T)MAXLNUM, 1L, 0L, false);
mark_adjust(curwin->w_cursor.lnum + 1, (linenr_T)MAXLNUM, 1L, 0L, false,
kExtmarkUndo);
}
did_append = true;
} else {
@ -1663,8 +1669,12 @@ int open_line(
if (flags & OPENLINE_MARKFIX) {
mark_col_adjust(curwin->w_cursor.lnum,
curwin->w_cursor.col + less_cols_off,
1L, (long)-less_cols, 0);
1L, (long)-less_cols, 0, kExtmarkNOOP);
}
// Always move extmarks - Here we move only the line where the
// cursor is, the previous mark_adjust takes care of the lines after
extmark_col_adjust(curbuf, lnum, mincol, 1L, (long)-less_cols,
kExtmarkUndo);
} else {
changed_bytes(curwin->w_cursor.lnum, curwin->w_cursor.col);
}

View File

@ -2690,7 +2690,8 @@ void ex_diffgetput(exarg_T *eap)
// Adjust marks. This will change the following entries!
if (added != 0) {
mark_adjust(lnum, lnum + count - 1, (long)MAXLNUM, (long)added, false);
mark_adjust(lnum, lnum + count - 1, (long)MAXLNUM, (long)added, false,
kExtmarkUndo);
if (curwin->w_cursor.lnum >= lnum) {
// Adjust the cursor position if it's in/after the changed
// lines.

View File

@ -28,6 +28,7 @@
#include "nvim/indent.h"
#include "nvim/indent_c.h"
#include "nvim/main.h"
#include "nvim/mark_extended.h"
#include "nvim/mbyte.h"
#include "nvim/memline.h"
#include "nvim/memory.h"
@ -1837,6 +1838,13 @@ change_indent (
xfree(new_line);
}
// change_indent seems to bec called twice, this combination only triggers
// once for both calls
if (new_cursor_col - vcol != 0) {
extmark_col_adjust(curbuf, curwin->w_cursor.lnum, 0, 0, amount,
kExtmarkUndo);
}
}
/*
@ -5587,6 +5595,9 @@ insertchar (
do_digraph(buf[i-1]); /* may be the start of a digraph */
buf[i] = NUL;
ins_str(buf);
extmark_col_adjust(curbuf, curwin->w_cursor.lnum,
(colnr_T)(curwin->w_cursor.col + 1), 0,
(long)STRLEN(buf), kExtmarkUndo);
if (flags & INSCHAR_CTRLV) {
redo_literal(*buf);
i = 1;
@ -5597,6 +5608,9 @@ insertchar (
} else {
int cc;
extmark_col_adjust(curbuf, curwin->w_cursor.lnum,
(colnr_T)(curwin->w_cursor.col + 1), 0,
1, kExtmarkUndo);
if ((cc = utf_char2len(c)) > 1) {
char_u buf[MB_MAXBYTES + 1];
@ -5606,10 +5620,11 @@ insertchar (
AppendCharToRedobuff(c);
} else {
ins_char(c);
if (flags & INSCHAR_CTRLV)
if (flags & INSCHAR_CTRLV) {
redo_literal(c);
else
} else {
AppendCharToRedobuff(c);
}
}
}
}
@ -6891,8 +6906,9 @@ static void mb_replace_pop_ins(int cc)
for (i = 1; i < n; ++i)
buf[i] = replace_pop();
ins_bytes_len(buf, n);
} else
} else {
ins_char(cc);
}
if (enc_utf8)
/* Handle composing chars. */
@ -8002,9 +8018,9 @@ static bool ins_bs(int c, int mode, int *inserted_space_p)
Insstart_orig.col = curwin->w_cursor.col;
}
if (State & VREPLACE_FLAG)
if (State & VREPLACE_FLAG) {
ins_char(' ');
else {
} else {
ins_str((char_u *)" ");
if ((State & REPLACE_FLAG))
replace_push(NUL);
@ -8482,8 +8498,17 @@ static bool ins_tab(void)
} else { // otherwise use "tabstop"
temp = (int)curbuf->b_p_ts;
}
temp -= get_nolist_virtcol() % temp;
// Move extmarks
extmark_col_adjust(curbuf,
curwin->w_cursor.lnum,
curwin->w_cursor.col,
0,
temp,
kExtmarkUndo);
/*
* Insert the first space with ins_char(). It will delete one char in
* replace mode. Insert the rest with ins_str(); it will not delete any
@ -8491,12 +8516,13 @@ static bool ins_tab(void)
*/
ins_char(' ');
while (--temp > 0) {
if (State & VREPLACE_FLAG)
if (State & VREPLACE_FLAG) {
ins_char(' ');
else {
} else {
ins_str((char_u *)" ");
if (State & REPLACE_FLAG) /* no char replaced */
if (State & REPLACE_FLAG) { // no char replaced
replace_push(NUL);
}
}
}

View File

@ -39,6 +39,7 @@
#include "nvim/buffer_updates.h"
#include "nvim/main.h"
#include "nvim/mark.h"
#include "nvim/mark_extended.h"
#include "nvim/mbyte.h"
#include "nvim/memline.h"
#include "nvim/message.h"
@ -658,10 +659,10 @@ void ex_sort(exarg_T *eap)
deleted = (long)(count - (lnum - eap->line2));
if (deleted > 0) {
mark_adjust(eap->line2 - deleted, eap->line2, (long)MAXLNUM, -deleted,
false);
false, kExtmarkUndo);
msgmore(-deleted);
} else if (deleted < 0) {
mark_adjust(eap->line2, MAXLNUM, -deleted, 0L, false);
mark_adjust(eap->line2, MAXLNUM, -deleted, 0L, false, kExtmarkUndo);
}
if (change_occurred || deleted != 0) {
changed_lines(eap->line1, 0, eap->line2 + 1, -deleted, true);
@ -874,10 +875,12 @@ int do_move(linenr_T line1, linenr_T line2, linenr_T dest)
* their final destination at the new text position -- webb
*/
last_line = curbuf->b_ml.ml_line_count;
mark_adjust_nofold(line1, line2, last_line - line2, 0L, true);
mark_adjust_nofold(line1, line2, last_line - line2, 0L, true, kExtmarkNoUndo);
extmark_adjust(curbuf, line1, line2, last_line - line2, 0L, kExtmarkNoUndo,
true);
changed_lines(last_line - num_lines + 1, 0, last_line + 1, num_lines, false);
if (dest >= line2) {
mark_adjust_nofold(line2 + 1, dest, -num_lines, 0L, false);
mark_adjust_nofold(line2 + 1, dest, -num_lines, 0L, false, kExtmarkNoUndo);
FOR_ALL_TAB_WINDOWS(tab, win) {
if (win->w_buffer == curbuf) {
foldMoveRange(&win->w_folds, line1, line2, dest);
@ -886,7 +889,8 @@ int do_move(linenr_T line1, linenr_T line2, linenr_T dest)
curbuf->b_op_start.lnum = dest - num_lines + 1;
curbuf->b_op_end.lnum = dest;
} else {
mark_adjust_nofold(dest + 1, line1 - 1, num_lines, 0L, false);
mark_adjust_nofold(dest + 1, line1 - 1, num_lines, 0L, false,
kExtmarkNoUndo);
FOR_ALL_TAB_WINDOWS(tab, win) {
if (win->w_buffer == curbuf) {
foldMoveRange(&win->w_folds, dest + 1, line1 - 1, line2);
@ -897,7 +901,9 @@ int do_move(linenr_T line1, linenr_T line2, linenr_T dest)
}
curbuf->b_op_start.col = curbuf->b_op_end.col = 0;
mark_adjust_nofold(last_line - num_lines + 1, last_line,
-(last_line - dest - extra), 0L, true);
-(last_line - dest - extra), 0L, true, kExtmarkNoUndo);
u_extmark_move(curbuf, line1, line2, last_line, dest, num_lines, extra);
changed_lines(last_line - num_lines + 1, 0, last_line + 1, -extra, false);
// send update regarding the new lines that were added
@ -1281,12 +1287,14 @@ static void do_filter(
if (cmdmod.keepmarks || vim_strchr(p_cpo, CPO_REMMARK) == NULL) {
if (read_linecount >= linecount) {
// move all marks from old lines to new lines
mark_adjust(line1, line2, linecount, 0L, false);
mark_adjust(line1, line2, linecount, 0L, false, kExtmarkUndo);
} else {
// move marks from old lines to new lines, delete marks
// that are in deleted lines
mark_adjust(line1, line1 + read_linecount - 1, linecount, 0L, false);
mark_adjust(line1 + read_linecount, line2, MAXLNUM, 0L, false);
mark_adjust(line1, line1 + read_linecount - 1, linecount, 0L, false,
kExtmarkUndo);
mark_adjust(line1 + read_linecount, line2, MAXLNUM, 0L, false,
kExtmarkUndo);
}
}
@ -3214,6 +3222,189 @@ static char_u *sub_parse_flags(char_u *cmd, subflags_T *subflags,
return cmd;
}
static void extmark_move_regmatch_single(lpos_T startpos,
lpos_T endpos,
linenr_T lnum,
int sublen)
{
colnr_T mincol;
colnr_T endcol;
colnr_T col_amount;
mincol = startpos.col + 1;
endcol = endpos.col + 1;
// There are cases such as :s/^/x/ where this happens
// a delete is simply not required.
if (mincol + 1 <= endcol) {
extmark_col_adjust_delete(curbuf,
lnum, mincol + 1, endcol, kExtmarkUndo, 0);
}
// Insert, sublen seems to be the value we need but + 1...
col_amount = sublen - 1;
extmark_col_adjust(curbuf, lnum, mincol, 0, col_amount, kExtmarkUndo);
}
static void extmark_move_regmatch_multi(ExtmarkSubMulti s, long i)
{
colnr_T mincol;
linenr_T u_lnum;
mincol = s.startpos.col + 1;
linenr_T n_u_lnum = s.lnum + s.endpos.lnum - s.startpos.lnum;
colnr_T n_after_newline_in_pat = s.endpos.col;
colnr_T n_before_newline_in_pat = mincol - s.cm_start.col;
long n_after_newline_in_sub;
if (!s.newline_in_sub) {
n_after_newline_in_sub = s.cm_end.col - s.cm_start.col;
} else {
n_after_newline_in_sub = s.cm_end.col;
}
if (s.newline_in_pat && !s.newline_in_sub) {
// -- Delete Pattern --
// 1. Move marks in the pattern
mincol = s.startpos.col + 1;
u_lnum = n_u_lnum;
assert(n_u_lnum == u_lnum);
extmark_copy_and_place(curbuf,
s.lnum, mincol,
u_lnum, n_after_newline_in_pat,
s.lnum, mincol,
kExtmarkUndo, true, NULL);
// 2. Move marks on last newline
mincol = mincol - n_before_newline_in_pat;
extmark_col_adjust(curbuf,
u_lnum,
n_after_newline_in_pat + 1,
-s.newline_in_pat,
mincol - n_after_newline_in_pat,
kExtmarkUndo);
// Take care of the lines after
extmark_adjust(curbuf,
u_lnum,
u_lnum,
MAXLNUM,
-s.newline_in_pat,
kExtmarkUndo,
false);
// 1. first insert the text in the substitutaion
extmark_col_adjust(curbuf,
s.lnum,
mincol + 1,
s.newline_in_sub,
n_after_newline_in_sub,
kExtmarkUndo);
} else {
// The data in sub_obj is as if the substituons above had already taken
// place. For our extmarks they haven't as we work from the bottom of the
// buffer up. Readjust the data.
n_u_lnum = s.lnum + s.endpos.lnum - s.startpos.lnum;
n_u_lnum = n_u_lnum - s.lnum_added;
// adjusted = L - (i-1)N
// where L = lnum value, N= lnum_added and i = iteration
linenr_T a_l_lnum = s.cm_start.lnum - ((i -1) * s.lnum_added);
linenr_T a_u_lnum = a_l_lnum + s.endpos.lnum;
assert(s.startpos.lnum == 0);
mincol = s.startpos.col + 1;
u_lnum = n_u_lnum;
if (!s.newline_in_pat && s.newline_in_sub) {
// -- Delete Pattern --
// 1. Move marks in the pattern
extmark_col_adjust_delete(curbuf,
a_l_lnum,
mincol + 1,
s.endpos.col + 1,
kExtmarkUndo,
s.eol);
extmark_adjust(curbuf,
a_u_lnum + 1,
MAXLNUM,
(long)s.newline_in_sub,
0,
kExtmarkUndo,
false);
// 3. Insert
extmark_col_adjust(curbuf,
a_l_lnum,
mincol,
s.newline_in_sub,
(long)-mincol + 1 + n_after_newline_in_sub,
kExtmarkUndo);
} else if (s.newline_in_pat && s.newline_in_sub) {
if (s.lnum_added >= 0) {
linenr_T u_col = n_after_newline_in_pat == 0
? 1 : n_after_newline_in_pat;
extmark_copy_and_place(curbuf,
a_l_lnum, mincol,
a_u_lnum, u_col,
a_l_lnum, mincol,
kExtmarkUndo, true, NULL);
// 2. Move marks on last newline
mincol = mincol - (colnr_T)n_before_newline_in_pat;
extmark_col_adjust(curbuf,
a_u_lnum,
(colnr_T)(n_after_newline_in_pat + 1),
-s.newline_in_pat,
mincol - n_after_newline_in_pat,
kExtmarkUndo);
// TODO(timeyyy): nothing to do here if lnum_added = 0
extmark_adjust(curbuf,
a_u_lnum + 1,
MAXLNUM,
(long)s.lnum_added,
0,
kExtmarkUndo,
false);
extmark_col_adjust(curbuf,
a_l_lnum,
mincol + 1,
s.newline_in_sub,
(long)-mincol + n_after_newline_in_sub,
kExtmarkUndo);
} else {
mincol = s.startpos.col + 1;
a_l_lnum = s.startpos.lnum + 1;
a_u_lnum = s.endpos.lnum + 1;
extmark_copy_and_place(curbuf,
a_l_lnum, mincol,
a_u_lnum, n_after_newline_in_pat,
a_l_lnum, mincol,
kExtmarkUndo, true, NULL);
// 2. Move marks on last newline
mincol = mincol - (colnr_T)n_before_newline_in_pat;
extmark_col_adjust(curbuf,
a_u_lnum,
(colnr_T)(n_after_newline_in_pat + 1),
-s.newline_in_pat,
mincol - n_after_newline_in_pat,
kExtmarkUndo);
extmark_adjust(curbuf,
a_u_lnum,
a_u_lnum,
MAXLNUM,
s.lnum_added,
kExtmarkUndo,
false);
// 3. Insert
extmark_col_adjust(curbuf,
a_l_lnum,
mincol + 1,
s.newline_in_sub,
(long)-mincol + n_after_newline_in_sub,
kExtmarkUndo);
}
}
}
}
/// Perform a substitution from line eap->line1 to line eap->line2 using the
/// command pointed to by eap->arg which should be of the form:
///
@ -3260,6 +3451,17 @@ static buf_T *do_sub(exarg_T *eap, proftime_T timeout,
int save_ma = 0;
int save_b_changed = curbuf->b_changed;
bool preview = (State & CMDPREVIEW);
extmark_sub_multi_vec_t extmark_sub_multi = KV_INITIAL_VALUE;
extmark_sub_single_vec_t extmark_sub_single = KV_INITIAL_VALUE;
linenr_T no_of_lines_changed = 0;
linenr_T newline_in_pat = 0;
linenr_T newline_in_sub = 0;
// inccomand tests fail without this check
if (!preview) {
// Requried for Undo to work for nsmarks,
u_save_cursor();
}
if (!global_busy) {
sub_nsubs = 0;
@ -3418,6 +3620,7 @@ static buf_T *do_sub(exarg_T *eap, proftime_T timeout,
// Check for a match on each line.
// If preview: limit to max('cmdwinheight', viewport).
linenr_T line2 = eap->line2;
for (linenr_T lnum = eap->line1;
lnum <= line2 && !got_quit && !aborting()
&& (!preview || preview_lines.lines_needed <= (linenr_T)p_cwh
@ -3876,6 +4079,7 @@ static buf_T *do_sub(exarg_T *eap, proftime_T timeout,
ADJUST_SUB_FIRSTLNUM();
// Now the trick is to replace CTRL-M chars with a real line
// break. This would make it impossible to insert a CTRL-M in
// the text. The line break can be avoided by preceding the
@ -3890,7 +4094,9 @@ static buf_T *do_sub(exarg_T *eap, proftime_T timeout,
*p1 = NUL; // truncate up to the CR
ml_append(lnum - 1, new_start,
(colnr_T)(p1 - new_start + 1), false);
mark_adjust(lnum + 1, (linenr_T)MAXLNUM, 1L, 0L, false);
mark_adjust(lnum + 1, (linenr_T)MAXLNUM, 1L, 0L, false,
kExtmarkNOOP);
if (subflags.do_ask) {
appended_lines(lnum - 1, 1L);
} else {
@ -3917,6 +4123,44 @@ static buf_T *do_sub(exarg_T *eap, proftime_T timeout,
current_match.end.lnum = lnum;
}
// Adjust extmarks, by delete and then insert
if (!preview) {
newline_in_pat = (regmatch.endpos[0].lnum
- regmatch.startpos[0].lnum);
newline_in_sub = current_match.end.lnum - current_match.start.lnum;
if (newline_in_pat || newline_in_sub) {
ExtmarkSubMulti sub_multi;
no_of_lines_changed = newline_in_sub - newline_in_pat;
sub_multi.newline_in_pat = newline_in_pat;
sub_multi.newline_in_sub = newline_in_sub;
sub_multi.lnum = lnum;
sub_multi.lnum_added = no_of_lines_changed;
sub_multi.cm_start = current_match.start;
sub_multi.cm_end = current_match.end;
sub_multi.startpos = regmatch.startpos[0];
sub_multi.endpos = regmatch.endpos[0];
sub_multi.eol = extmark_eol_col(curbuf, lnum);
kv_push(extmark_sub_multi, sub_multi);
// Collect information required for moving extmarks WITHOUT \n, \r
} else {
no_of_lines_changed = 0;
if (regmatch.startpos[0].col != -1) {
ExtmarkSubSingle sub_single;
sub_single.sublen = sublen;
sub_single.lnum = lnum;
sub_single.startpos = regmatch.startpos[0];
sub_single.endpos = regmatch.endpos[0];
kv_push(extmark_sub_single, sub_single);
}
}
}
// 4. If subflags.do_all is set, find next match.
// Prevent endless loop with patterns that match empty
// strings, e.g. :s/$/pat/g or :s/[a-z]* /(&)/g.
@ -3983,7 +4227,7 @@ skip:
ml_delete(lnum, false);
}
mark_adjust(lnum, lnum + nmatch_tl - 1,
(long)MAXLNUM, -nmatch_tl, false);
(long)MAXLNUM, -nmatch_tl, false, kExtmarkNOOP);
if (subflags.do_ask) {
deleted_lines(lnum, nmatch_tl);
}
@ -4159,6 +4403,35 @@ skip:
}
}
}
if (newline_in_pat || newline_in_sub) {
long n = (long)kv_size(extmark_sub_multi);
ExtmarkSubMulti sub_multi;
if (no_of_lines_changed < 0) {
for (i = 0; i < n; i++) {
sub_multi = kv_A(extmark_sub_multi, i);
extmark_move_regmatch_multi(sub_multi, i);
}
} else {
// Move extmarks in reverse order to avoid moving marks we just moved...
for (i = 0; i < n; i++) {
sub_multi = kv_Z(extmark_sub_multi, i);
extmark_move_regmatch_multi(sub_multi, n - i);
}
}
kv_destroy(extmark_sub_multi);
} else {
long n = (long)kv_size(extmark_sub_single);
ExtmarkSubSingle sub_single;
for (i = 0; i < n; i++) {
sub_single = kv_Z(extmark_sub_single, i);
extmark_move_regmatch_single(sub_single.startpos,
sub_single.endpos,
sub_single.lnum,
sub_single.sublen);
}
kv_destroy(extmark_sub_single);
}
kv_destroy(preview_lines.subresults);

View File

@ -25,6 +25,12 @@
* SUCH DAMAGE.
*/
// Gotchas
// -------
//
// if you delete from a kbtree while iterating over it you must use
// kb_del_itr and not kb_del otherwise the iterator might point to freed memory.
#ifndef NVIM_LIB_KBTREE_H
#define NVIM_LIB_KBTREE_H

View File

@ -905,9 +905,10 @@ void mark_adjust(linenr_T line1,
linenr_T line2,
long amount,
long amount_after,
bool end_temp)
bool end_temp,
ExtmarkOp op)
{
mark_adjust_internal(line1, line2, amount, amount_after, true, end_temp);
mark_adjust_internal(line1, line2, amount, amount_after, true, end_temp, op);
}
// mark_adjust_nofold() does the same as mark_adjust() but without adjusting
@ -916,14 +917,16 @@ void mark_adjust(linenr_T line1,
// calling foldMarkAdjust() with arguments line1, line2, amount, amount_after,
// for an example of why this may be necessary, see do_move().
void mark_adjust_nofold(linenr_T line1, linenr_T line2, long amount,
long amount_after, bool end_temp)
long amount_after, bool end_temp,
ExtmarkOp op)
{
mark_adjust_internal(line1, line2, amount, amount_after, false, end_temp);
mark_adjust_internal(line1, line2, amount, amount_after, false, end_temp, op);
}
static void mark_adjust_internal(linenr_T line1, linenr_T line2,
long amount, long amount_after,
bool adjust_folds, bool end_temp)
bool adjust_folds, bool end_temp,
ExtmarkOp op)
{
int i;
int fnum = curbuf->b_fnum;
@ -979,6 +982,9 @@ static void mark_adjust_internal(linenr_T line1, linenr_T line2,
sign_mark_adjust(line1, line2, amount, amount_after);
bufhl_mark_adjust(curbuf, line1, line2, amount, amount_after, end_temp);
if (op != kExtmarkNOOP) {
extmark_adjust(curbuf, line1, line2, amount, amount_after, op, end_temp);
}
}
/* previous context mark */
@ -1090,7 +1096,7 @@ static void mark_adjust_internal(linenr_T line1, linenr_T line2,
// cursor is inside them.
void mark_col_adjust(
linenr_T lnum, colnr_T mincol, long lnum_amount, long col_amount,
int spaces_removed)
int spaces_removed, ExtmarkOp op)
{
int i;
int fnum = curbuf->b_fnum;
@ -1110,6 +1116,13 @@ void mark_col_adjust(
col_adjust(&(namedfm[i].fmark.mark));
}
// Extmarks
if (op != kExtmarkNOOP) {
// TODO(timeyyy): consider spaces_removed? (behave like a delete)
extmark_col_adjust(curbuf, lnum, mincol, lnum_amount, col_amount,
kExtmarkUndo);
}
/* last Insert position */
col_adjust(&(curbuf->b_last_insert.mark));

1136
src/nvim/mark_extended.c Normal file

File diff suppressed because it is too large Load Diff

282
src/nvim/mark_extended.h Normal file
View File

@ -0,0 +1,282 @@
#ifndef NVIM_MARK_EXTENDED_H
#define NVIM_MARK_EXTENDED_H
#include "nvim/mark_extended_defs.h"
#include "nvim/buffer_defs.h" // for buf_T
// Macro Documentation: FOR_ALL_?
// Search exclusively using the range values given.
// Use MAXCOL/MAXLNUM for the start and end of the line/col.
// The ns parameter: Unless otherwise stated, this is only a starting point
// for the btree to searched in, the results being itterated over will
// still contain extmarks from other namespaces.
// see FOR_ALL_? for documentation
#define FOR_ALL_EXTMARKLINES(buf, l_lnum, u_lnum, code)\
kbitr_t(extmarklines) itr;\
ExtMarkLine t;\
t.lnum = l_lnum;\
if (!kb_itr_get(extmarklines, &buf->b_extlines, &t, &itr)) { \
kb_itr_next(extmarklines, &buf->b_extlines, &itr);\
}\
ExtMarkLine *extmarkline;\
for (; kb_itr_valid(&itr); kb_itr_next(extmarklines, \
&buf->b_extlines, &itr)) { \
extmarkline = kb_itr_key(&itr);\
if (extmarkline->lnum > u_lnum) { \
break;\
}\
code;\
}
// see FOR_ALL_? for documentation
#define FOR_ALL_EXTMARKLINES_PREV(buf, l_lnum, u_lnum, code)\
kbitr_t(extmarklines) itr;\
ExtMarkLine t;\
t.lnum = u_lnum;\
if (!kb_itr_get(extmarklines, &buf->b_extlines, &t, &itr)) { \
kb_itr_prev(extmarklines, &buf->b_extlines, &itr);\
}\
ExtMarkLine *extmarkline;\
for (; kb_itr_valid(&itr); kb_itr_prev(extmarklines, \
&buf->b_extlines, &itr)) { \
extmarkline = kb_itr_key(&itr);\
if (extmarkline->lnum < l_lnum) { \
break;\
}\
code;\
}
// see FOR_ALL_? for documentation
#define FOR_ALL_EXTMARKS(buf, ns, l_lnum, l_col, u_lnum, u_col, code)\
kbitr_t(markitems) mitr;\
ExtendedMark mt;\
mt.ns_id = ns;\
mt.mark_id = 0;\
mt.line = NULL;\
FOR_ALL_EXTMARKLINES(buf, l_lnum, u_lnum, { \
mt.col = (extmarkline->lnum != l_lnum) ? MINCOL : l_col;\
if (!kb_itr_get(markitems, &extmarkline->items, mt, &mitr)) { \
kb_itr_next(markitems, &extmarkline->items, &mitr);\
} \
ExtendedMark *extmark;\
for (; \
kb_itr_valid(&mitr); \
kb_itr_next(markitems, &extmarkline->items, &mitr)) { \
extmark = &kb_itr_key(&mitr);\
if (extmark->line->lnum == u_lnum \
&& extmark->col > u_col) { \
break;\
}\
code;\
}\
})
// see FOR_ALL_? for documentation
#define FOR_ALL_EXTMARKS_PREV(buf, ns, l_lnum, l_col, u_lnum, u_col, code)\
kbitr_t(markitems) mitr;\
ExtendedMark mt;\
mt.mark_id = sizeof(uint64_t);\
mt.ns_id = ns;\
FOR_ALL_EXTMARKLINES_PREV(buf, l_lnum, u_lnum, { \
mt.col = (extmarkline->lnum != u_lnum) ? MAXCOL : u_col;\
if (!kb_itr_get(markitems, &extmarkline->items, mt, &mitr)) { \
kb_itr_prev(markitems, &extmarkline->items, &mitr);\
} \
ExtendedMark *extmark;\
for (; \
kb_itr_valid(&mitr); \
kb_itr_prev(markitems, &extmarkline->items, &mitr)) { \
extmark = &kb_itr_key(&mitr);\
if (extmark->line->lnum == l_lnum \
&& extmark->col < l_col) { \
break;\
}\
code;\
}\
})
#define FOR_ALL_EXTMARKS_IN_LINE(items, l_col, u_col, code)\
kbitr_t(markitems) mitr;\
ExtendedMark mt;\
mt.ns_id = 0;\
mt.mark_id = 0;\
mt.line = NULL;\
mt.col = l_col;\
colnr_T extmarkline_u_col = u_col;\
if (!kb_itr_get(markitems, &items, mt, &mitr)) { \
kb_itr_next(markitems, &items, &mitr);\
} \
ExtendedMark *extmark;\
for (; kb_itr_valid(&mitr); kb_itr_next(markitems, &items, &mitr)) { \
extmark = &kb_itr_key(&mitr);\
if (extmark->col > extmarkline_u_col) { \
break;\
}\
code;\
}
typedef struct ExtmarkNs { // For namespacing extmarks
PMap(uint64_t) *map; // For fast lookup
uint64_t free_id; // For automatically assigning id's
} ExtmarkNs;
typedef kvec_t(ExtendedMark *) ExtmarkArray;
// Undo/redo extmarks
typedef enum {
kExtmarkNOOP, // Extmarks shouldn't be moved
kExtmarkUndo, // Operation should be reversable/undoable
kExtmarkNoUndo, // Operation should not be reversable
kExtmarkUndoNoRedo, // Operation should be undoable, but not redoable
} ExtmarkOp;
// adjust line numbers only, corresponding to mark_adjust call
typedef struct {
linenr_T line1;
linenr_T line2;
long amount;
long amount_after;
} Adjust;
// adjust columns after split/join line, like mark_col_adjust
typedef struct {
linenr_T lnum;
colnr_T mincol;
long col_amount;
long lnum_amount;
} ColAdjust;
// delete the columns between mincol and endcol
typedef struct {
linenr_T lnum;
colnr_T mincol;
colnr_T endcol;
int eol;
} ColAdjustDelete;
// adjust linenumbers after :move operation
typedef struct {
linenr_T line1;
linenr_T line2;
linenr_T last_line;
linenr_T dest;
linenr_T num_lines;
linenr_T extra;
} AdjustMove;
// TODO(bfredl): reconsider if we really should track mark creation/updating
// itself, these are not really "edit" operation.
// extmark was created
typedef struct {
uint64_t ns_id;
uint64_t mark_id;
linenr_T lnum;
colnr_T col;
} ExtmarkSet;
// extmark was updated
typedef struct {
uint64_t ns_id;
uint64_t mark_id;
linenr_T old_lnum;
colnr_T old_col;
linenr_T lnum;
colnr_T col;
} ExtmarkUpdate;
// copied mark before deletion (as operation is destructive)
typedef struct {
uint64_t ns_id;
uint64_t mark_id;
linenr_T lnum;
colnr_T col;
} ExtmarkCopy;
// also used as part of :move operation? probably can be simplified to one
// event.
typedef struct {
linenr_T l_lnum;
colnr_T l_col;
linenr_T u_lnum;
colnr_T u_col;
linenr_T p_lnum;
colnr_T p_col;
} ExtmarkCopyPlace;
// extmark was cleared.
// TODO(bfredl): same reconsideration as for ExtmarkSet/ExtmarkUpdate
typedef struct {
uint64_t ns_id;
linenr_T l_lnum;
linenr_T u_lnum;
} ExtmarkClear;
typedef enum {
kLineAdjust,
kColAdjust,
kColAdjustDelete,
kAdjustMove,
kExtmarkSet,
kExtmarkDel,
kExtmarkUpdate,
kExtmarkCopy,
kExtmarkCopyPlace,
kExtmarkClear,
} UndoObjectType;
// TODO(bfredl): reduce the number of undo action types
struct undo_object {
UndoObjectType type;
union {
Adjust adjust;
ColAdjust col_adjust;
ColAdjustDelete col_adjust_delete;
AdjustMove move;
ExtmarkSet set;
ExtmarkUpdate update;
ExtmarkCopy copy;
ExtmarkCopyPlace copy_place;
ExtmarkClear clear;
} data;
};
// For doing move of extmarks in substitutions
typedef struct {
lpos_T startpos;
lpos_T endpos;
linenr_T lnum;
int sublen;
} ExtmarkSubSingle;
// For doing move of extmarks in substitutions
typedef struct {
lpos_T startpos;
lpos_T endpos;
linenr_T lnum;
linenr_T newline_in_pat;
linenr_T newline_in_sub;
linenr_T lnum_added;
lpos_T cm_start; // start of the match
lpos_T cm_end; // end of the match
int eol; // end of the match
} ExtmarkSubMulti;
typedef kvec_t(ExtmarkSubSingle) extmark_sub_single_vec_t;
typedef kvec_t(ExtmarkSubMulti) extmark_sub_multi_vec_t;
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "mark_extended.h.generated.h"
#endif
#endif // NVIM_MARK_EXTENDED_H

View File

@ -0,0 +1,54 @@
#ifndef NVIM_MARK_EXTENDED_DEFS_H
#define NVIM_MARK_EXTENDED_DEFS_H
#include "nvim/pos.h" // for colnr_T
#include "nvim/map.h" // for uint64_t
#include "nvim/lib/kbtree.h"
#include "nvim/lib/kvec.h"
struct ExtMarkLine;
typedef struct ExtendedMark
{
uint64_t ns_id;
uint64_t mark_id;
struct ExtMarkLine *line;
colnr_T col;
} ExtendedMark;
// We only need to compare columns as rows are stored in a different tree.
// Marks are ordered by: position, namespace, mark_id
// This improves moving marks but slows down all other use cases (searches)
static inline int extmark_cmp(ExtendedMark a, ExtendedMark b)
{
int cmp = kb_generic_cmp(a.col, b.col);
if (cmp != 0) {
return cmp;
}
cmp = kb_generic_cmp(a.ns_id, b.ns_id);
if (cmp != 0) {
return cmp;
}
return kb_generic_cmp(a.mark_id, b.mark_id);
}
#define markitems_cmp(a, b) (extmark_cmp((a), (b)))
KBTREE_INIT(markitems, ExtendedMark, markitems_cmp, 10)
typedef struct ExtMarkLine
{
linenr_T lnum;
kbtree_t(markitems) items;
} ExtMarkLine;
#define EXTMARKLINE_CMP(a, b) (kb_generic_cmp((a)->lnum, (b)->lnum))
KBTREE_INIT(extmarklines, ExtMarkLine *, EXTMARKLINE_CMP, 10)
typedef struct undo_object ExtmarkUndoObject;
typedef kvec_t(ExtmarkUndoObject) extmark_undo_vec_t;
#endif // NVIM_MARK_EXTENDED_DEFS_H

View File

@ -30,7 +30,6 @@
#include "nvim/indent_c.h"
#include "nvim/buffer_updates.h"
#include "nvim/main.h"
#include "nvim/mark.h"
#include "nvim/mbyte.h"
#include "nvim/memline.h"
#include "nvim/memory.h"

View File

@ -49,6 +49,7 @@
#include "nvim/undo.h"
#include "nvim/macros.h"
#include "nvim/window.h"
#include "nvim/lib/kvec.h"
#include "nvim/os/input.h"
#include "nvim/os/time.h"
@ -306,6 +307,15 @@ void shift_line(
change_indent(INDENT_SET, count, false, NUL, call_changed_bytes);
} else {
(void)set_indent(count, call_changed_bytes ? SIN_CHANGED : 0);
colnr_T mincol = (curwin->w_cursor.col + 1) -p_sw;
colnr_T col_amount = left ? -p_sw : p_sw;
extmark_col_adjust(curbuf,
curwin->w_cursor.lnum,
mincol,
0,
col_amount,
kExtmarkUndo);
}
}
@ -479,6 +489,10 @@ static void shift_block(oparg_T *oap, int amount)
State = oldstate;
curwin->w_cursor.col = oldcol;
p_ri = old_p_ri;
colnr_T col_amount = left ? -p_sw : p_sw;
extmark_col_adjust(curbuf, curwin->w_cursor.lnum,
curwin->w_cursor.col, 0, col_amount, kExtmarkUndo);
}
/*
@ -623,10 +637,19 @@ void op_reindent(oparg_T *oap, Indenter how)
amount = how(); /* get the indent for this line */
if (amount >= 0 && set_indent(amount, SIN_UNDO)) {
/* did change the indent, call changed_lines() later */
if (first_changed == 0)
// did change the indent, call changed_lines() later
if (first_changed == 0) {
first_changed = curwin->w_cursor.lnum;
}
last_changed = curwin->w_cursor.lnum;
// Adjust extmarks
extmark_col_adjust(curbuf,
curwin->w_cursor.lnum,
0, // mincol
0, // lnum_amount
amount, // col_amount
kExtmarkUndo);
}
}
++curwin->w_cursor.lnum;
@ -1621,6 +1644,8 @@ int op_delete(oparg_T *oap)
curwin->w_cursor.col = 0;
(void)del_bytes((colnr_T)n, !virtual_op,
oap->op_type == OP_DELETE && !oap->is_VIsual);
extmark_col_adjust(curbuf, curwin->w_cursor.lnum,
(colnr_T)0, 0L, (long)-n, kExtmarkUndo);
curwin->w_cursor = curpos; // restore curwin->w_cursor
(void)do_join(2, false, false, false, false);
}
@ -1632,10 +1657,36 @@ setmarks:
if (oap->motion_type == kMTBlockWise) {
curbuf->b_op_end.lnum = oap->end.lnum;
curbuf->b_op_end.col = oap->start.col;
} else
} else {
curbuf->b_op_end = oap->start;
}
curbuf->b_op_start = oap->start;
// TODO(timeyyy): refactor: Move extended marks
// + 1 to change to buf mode,
// and + 1 because we only move marks after the deleted col
colnr_T mincol = oap->start.col + 1 + 1;
colnr_T endcol;
if (oap->motion_type == kMTBlockWise) {
// TODO(timeyyy): refactor extmark_col_adjust to take lnumstart, lnum_end ?
endcol = bd.end_vcol + 1;
for (lnum = curwin->w_cursor.lnum; lnum <= oap->end.lnum; lnum++) {
extmark_col_adjust_delete(curbuf, lnum, mincol, endcol,
kExtmarkUndo, 0);
}
// Delete characters within one line,
// The case with multiple lines is handled by do_join
} else if (oap->motion_type == kMTCharWise && oap->line_count == 1) {
// + 1 to change to buf mode, then plus 1 to fit function requirements
endcol = oap->end.col + 1 + 1;
lnum = curwin->w_cursor.lnum;
if (oap->is_VIsual == false) {
endcol = MAX(endcol - 1, mincol);
}
extmark_col_adjust_delete(curbuf, lnum, mincol, endcol, kExtmarkUndo, 0);
}
return OK;
}
@ -2031,8 +2082,8 @@ bool swapchar(int op_type, pos_T *pos)
pos_T sp = curwin->w_cursor;
curwin->w_cursor = *pos;
/* don't use del_char(), it also removes composing chars */
del_bytes(utf_ptr2len(get_cursor_pos_ptr()), FALSE, FALSE);
// don't use del_char(), it also removes composing chars
del_bytes(utf_ptr2len(get_cursor_pos_ptr()), false, false);
ins_char(nc);
curwin->w_cursor = sp;
} else {
@ -2105,8 +2156,9 @@ void op_insert(oparg_T *oap, long count1)
* values in "bd". */
if (u_save_cursor() == FAIL)
return;
for (i = 0; i < bd.endspaces; i++)
for (i = 0; i < bd.endspaces; i++) {
ins_char(' ');
}
bd.textlen += bd.endspaces;
}
} else {
@ -2224,6 +2276,10 @@ void op_insert(oparg_T *oap, long count1)
xfree(ins_text);
}
}
colnr_T col = oap->start.col;
for (linenr_T lnum = oap->start.lnum; lnum <= oap->end.lnum; lnum++) {
extmark_col_adjust(curbuf, lnum, col, 0, 1, kExtmarkUndo);
}
}
/*
@ -2694,6 +2750,27 @@ static void do_autocmd_textyankpost(oparg_T *oap, yankreg_T *reg)
}
static void extmarks_do_put(int dir,
size_t totlen,
MotionType y_type,
linenr_T lnum,
colnr_T col)
{
// adjust extmarks
colnr_T col_amount = (colnr_T)(dir == FORWARD ? totlen-1 : totlen);
// Move extmark with char put
if (y_type == kMTCharWise) {
extmark_col_adjust(curbuf, lnum, col, 0, col_amount, kExtmarkUndo);
// Move extmark with blockwise put
} else if (y_type == kMTBlockWise) {
for (lnum = curbuf->b_op_start.lnum;
lnum <= curbuf->b_op_end.lnum;
lnum++) {
extmark_col_adjust(curbuf, lnum, col, 0, col_amount, kExtmarkUndo);
}
}
}
/*
* Put contents of register "regname" into the text.
* Caller must check "regname" to be valid!
@ -2708,8 +2785,8 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags)
char_u *oldp;
int yanklen;
size_t totlen = 0; // init for gcc
linenr_T lnum;
colnr_T col;
linenr_T lnum = 0;
colnr_T col = 0;
size_t i; // index in y_array[]
MotionType y_type;
size_t y_size;
@ -3286,11 +3363,11 @@ error:
curbuf->b_op_start.lnum++;
}
// Skip mark_adjust when adding lines after the last one, there
// can't be marks there. But still needed in diff mode.
// can't be marks there.
if (curbuf->b_op_start.lnum + (y_type == kMTCharWise) - 1 + nr_lines
< curbuf->b_ml.ml_line_count || curwin->w_p_diff) {
< curbuf->b_ml.ml_line_count) {
mark_adjust(curbuf->b_op_start.lnum + (y_type == kMTCharWise),
(linenr_T)MAXLNUM, nr_lines, 0L, false);
(linenr_T)MAXLNUM, nr_lines, 0L, false, kExtmarkUndo);
}
// note changed text for displaying and folding
@ -3352,6 +3429,8 @@ end:
/* If the cursor is past the end of the line put it at the end. */
adjust_cursor_eol();
extmarks_do_put(dir, totlen, y_type, lnum, col);
}
/*
@ -3745,6 +3824,7 @@ int do_join(size_t count,
* column. This is not Vi compatible, but Vi deletes the marks, thus that
* should not really be a problem.
*/
for (t = (linenr_T)count - 1;; t--) {
cend -= currsize;
memmove(cend, curr, (size_t)currsize);
@ -3756,12 +3836,18 @@ int do_join(size_t count,
// If deleting more spaces than adding, the cursor moves no more than
// what is added if it is inside these spaces.
const int spaces_removed = (int)((curr - curr_start) - spaces[t]);
linenr_T lnum = curwin->w_cursor.lnum + t;
colnr_T mincol = (colnr_T)0;
long lnum_amount = (linenr_T)-t;
long col_amount = (long)(cend - newp - spaces_removed);
mark_col_adjust(lnum, mincol, lnum_amount, col_amount, spaces_removed,
kExtmarkUndo);
mark_col_adjust(curwin->w_cursor.lnum + t, (colnr_T)0, (linenr_T)-t,
(long)(cend - newp - spaces_removed), spaces_removed);
if (t == 0) {
break;
}
curr = curr_start = ml_get((linenr_T)(curwin->w_cursor.lnum + t - 1));
if (remove_comments)
curr += comments[t - 1];
@ -3769,6 +3855,7 @@ int do_join(size_t count,
curr = skipwhite(curr);
currsize = (int)STRLEN(curr);
}
ml_replace(curwin->w_cursor.lnum, newp, false);
if (setmark) {
@ -4189,14 +4276,14 @@ format_lines(
if (next_leader_len > 0) {
(void)del_bytes(next_leader_len, false, false);
mark_col_adjust(curwin->w_cursor.lnum, (colnr_T)0, 0L,
(long)-next_leader_len, 0);
(long)-next_leader_len, 0, kExtmarkUndo);
} else if (second_indent > 0) { // the "leader" for FO_Q_SECOND
int indent = (int)getwhitecols_curline();
if (indent > 0) {
(void)del_bytes(indent, FALSE, FALSE);
mark_col_adjust(curwin->w_cursor.lnum,
(colnr_T)0, 0L, (long)-indent, 0);
(colnr_T)0, 0L, (long)-indent, 0, kExtmarkUndo);
}
}
curwin->w_cursor.lnum--;
@ -4539,7 +4626,7 @@ void op_addsub(oparg_T *oap, linenr_T Prenum1, bool g_cmd)
int do_addsub(int op_type, pos_T *pos, int length, linenr_T Prenum1)
{
int col;
char_u *buf1;
char_u *buf1 = NULL;
char_u buf2[NUMBUFLEN];
int pre; // 'X' or 'x': hex; '0': octal; 'B' or 'b': bin
static bool hexupper = false; // 0xABC
@ -4848,7 +4935,6 @@ int do_addsub(int op_type, pos_T *pos, int length, linenr_T Prenum1)
*ptr = NUL;
STRCAT(buf1, buf2);
ins_str(buf1); // insert the new number
xfree(buf1);
endpos = curwin->w_cursor;
if (curwin->w_cursor.col) {
curwin->w_cursor.col--;
@ -4862,7 +4948,25 @@ int do_addsub(int op_type, pos_T *pos, int length, linenr_T Prenum1)
curbuf->b_op_end.col--;
}
// if buf1 wasn't allocated, only a singe ASCII char was changed in-place.
if (did_change && buf1 != NULL) {
extmark_col_adjust_delete(curbuf,
pos->lnum,
startpos.col + 2,
endpos.col + 1 + length,
kExtmarkUndo,
0);
long col_amount = (long)STRLEN(buf1);
extmark_col_adjust(curbuf,
pos->lnum,
startpos.col + 1,
0,
col_amount,
kExtmarkUndo);
}
theend:
xfree(buf1);
if (visual) {
curwin->w_cursor = save_cursor;
} else if (did_change) {

View File

@ -14,6 +14,10 @@ typedef int colnr_T;
enum { MAXLNUM = 0x7fffffff };
/// Maximal column number, 31 bits
enum { MAXCOL = 0x7fffffff };
// Minimum line number
enum { MINLNUM = 1 };
// minimum column number
enum { MINCOL = 1 };
/*
* position in file or buffer

View File

@ -91,7 +91,9 @@
#include "nvim/fileio.h"
#include "nvim/fold.h"
#include "nvim/buffer_updates.h"
#include "nvim/pos.h" // MAXLNUM
#include "nvim/mark.h"
#include "nvim/mark_extended.h"
#include "nvim/memline.h"
#include "nvim/message.h"
#include "nvim/misc1.h"
@ -106,6 +108,7 @@
#include "nvim/types.h"
#include "nvim/os/os.h"
#include "nvim/os/time.h"
#include "nvim/lib/kvec.h"
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "undo.c.generated.h"
@ -384,6 +387,7 @@ int u_savecommon(linenr_T top, linenr_T bot, linenr_T newbot, int reload)
* up the undo info when out of memory.
*/
uhp = xmalloc(sizeof(u_header_T));
kv_init(uhp->uh_extmark);
#ifdef U_DEBUG
uhp->uh_magic = UH_MAGIC;
#endif
@ -2249,10 +2253,10 @@ static void u_undoredo(int undo, bool do_buf_event)
xfree((char_u *)uep->ue_array);
}
/* adjust marks */
// Adjust marks
if (oldsize != newsize) {
mark_adjust(top + 1, top + oldsize, (long)MAXLNUM,
(long)newsize - (long)oldsize, false);
(long)newsize - (long)oldsize, false, kExtmarkNOOP);
if (curbuf->b_op_start.lnum > top + oldsize) {
curbuf->b_op_start.lnum += newsize - oldsize;
}
@ -2285,6 +2289,23 @@ static void u_undoredo(int undo, bool do_buf_event)
newlist = uep;
}
// Adjust Extmarks
ExtmarkUndoObject undo_info;
if (undo) {
for (i = (int)kv_size(curhead->uh_extmark) - 1; i > -1; i--) {
undo_info = kv_A(curhead->uh_extmark, i);
extmark_apply_undo(undo_info, undo);
}
// redo
} else {
for (i = 0; i < (int)kv_size(curhead->uh_extmark); i++) {
undo_info = kv_A(curhead->uh_extmark, i);
extmark_apply_undo(undo_info, undo);
}
}
// finish Adjusting extmarks
curhead->uh_entry = newlist;
curhead->uh_flags = new_flags;
if ((old_flags & UH_EMPTYBUF) && BUFEMPTY()) {
@ -2828,6 +2849,8 @@ u_freeentries(
u_freeentry(uep, uep->ue_size);
}
kv_destroy(uhp->uh_extmark);
#ifdef U_DEBUG
uhp->uh_magic = 0;
#endif
@ -3022,3 +3045,28 @@ list_T *u_eval_tree(const u_header_T *const first_uhp)
return list;
}
// Given the buffer, Return the undo header. If none is set, set one first.
// NULL will be returned if e.g undolevels = -1 (undo disabled)
u_header_T *u_force_get_undo_header(buf_T *buf)
{
u_header_T *uhp = NULL;
if (buf->b_u_curhead != NULL) {
uhp = buf->b_u_curhead;
} else if (buf->b_u_newhead) {
uhp = buf->b_u_newhead;
}
// Create the first undo header for the buffer
if (!uhp) {
// TODO(timeyyy): there would be a better way to do this!
u_save_cursor();
uhp = buf->b_u_curhead;
if (!uhp) {
uhp = buf->b_u_newhead;
if (get_undolevel() > 0 && !uhp) {
abort();
}
}
}
return uhp;
}

View File

@ -4,6 +4,7 @@
#include <time.h> // for time_t
#include "nvim/pos.h"
#include "nvim/mark_extended_defs.h"
#include "nvim/mark_defs.h"
typedef struct u_header u_header_T;
@ -56,14 +57,15 @@ struct u_header {
u_entry_T *uh_getbot_entry; /* pointer to where ue_bot must be set */
pos_T uh_cursor; /* cursor position before saving */
long uh_cursor_vcol;
int uh_flags; /* see below */
fmark_T uh_namedm[NMARKS]; /* marks before undo/after redo */
visualinfo_T uh_visual; /* Visual areas before undo/after redo */
time_t uh_time; /* timestamp when the change was made */
long uh_save_nr; /* set when the file was saved after the
changes in this block */
int uh_flags; // see below
fmark_T uh_namedm[NMARKS]; // marks before undo/after redo
extmark_undo_vec_t uh_extmark; // info to move extmarks
visualinfo_T uh_visual; // Visual areas before undo/after redo
time_t uh_time; // timestamp when the change was made
long uh_save_nr; // set when the file was saved after the
// changes in this block
#ifdef U_DEBUG
int uh_magic; /* magic number to check allocation */
int uh_magic; // magic number to check allocation
#endif
};

File diff suppressed because it is too large Load Diff