nsmarks: initial commit

This commit is contained in:
timeyyy 2017-01-18 13:20:07 +01:00 committed by Björn Linse
parent e757f4d536
commit a9065a5051
27 changed files with 3968 additions and 81 deletions

View File

@ -148,3 +148,14 @@ See `LICENSE` for details.
[Gentoo]: https://packages.gentoo.org/packages/app-editors/neovim
<!-- vim: set tw=80: -->
CC=clang make functionaltest CMAKE_EXTRA_FLAGS="-DCLANG_ASAN_UBSAN=ON" TEST_FILE=test/functional/api TEST_TAG=extmarks
CC=clang make functionaltest CMAKE_EXTRA_FLAGS="-DCLANG_ASAN_UBSAN=ON" TEST_FILE=test/functional/ui/mouse_spec.lua TEST_TAG=blah
CC=clang make functionaltest CMAKE_EXTRA_FLAGS="-DCLANG_ASAN_UBSAN=ON"
CC=clang make functionaltest CMAKE_EXTRA_FLAGS="-DCLANG_ASAN_UBSAN=OFF" TEST_FILE=test/functional/api/mark_extended_spec.lua TEST_TAG=broken
# TODO debug in container with clion...
https://github.com/shuhaoliu/docker-clion-dev

View File

@ -438,6 +438,36 @@ Example: create a float with scratch buffer: >
call nvim_win_set_option(win, 'winhl', 'Normal:MyHighlight')
>
==============================================================================
Buffer extended marks *api-extended-marks*
An extended mark represents a buffer annotation that remains logically
stationary even 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 row 1, column three.
`let g:mark_ns = nvim_create_namespace('myplugin')`
`let g:mark_id = nvim_buf_set_mark(0, :g:mark_ns, 0, 1, 3)`
Note: the mark id was randomly generated because we used an inital value of 0
`echo nvim_buf_lookup_mark(0, g:mark_ns, g:mark_id)`
=> [1, 1, 3]
`echo nvim_buf_get_marks(0, g:mark_ns, [1, 1], [1, 3], -1, 0)`
=> [[1, 1, 3]]
Deleting the text all around an extended mark does not remove it. If you want
to remove an extended mark, use the |nvim_buf_unset_mark()| function.
The namepsace ensures you only ever work with the extended marks you mean to.
Calling set and unset of marks will not automatically prop a new undo.
==============================================================================
Global Functions *api-global*
@ -1449,6 +1479,22 @@ nvim_select_popupmenu_item({item}, {insert}, {finish}, {opts})
nvim__inspect_cell({grid}, {row}, {col}) *nvim__inspect_cell()*
TODO: Documentation
*nvim_init_mark_ns()*
nvim_init_mark_ns({namespace})
Create a new namepsace for holding extended marks. The id
of the namespace is returned, this namespace id is required
for using the rest of the extended marks api.
Parameters:~
{namespace} String name to be assigned to the namespace
*nvim_mark_get_ns_ids()*
nvim_mark_get_ns_ids()
Returns a list of extended mark namespaces.
Pairs of ids and string names are returned.
An empty list will be returned if no namespaces are set.
==============================================================================
Buffer Functions *api-buffer*

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,213 @@ ArrayOf(Integer, 2) nvim_buf_get_mark(Buffer buffer, String name, Error *err)
return rv;
}
/// Returns position info 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 namespace,
Integer id, Error *err)
FUNC_API_SINCE(6)
{
Array rv = ARRAY_DICT_INIT;
buf_T *buf = find_buffer_by_handle(buffer, err);
if (!buf) {
return rv;
}
if (!ns_initialized((uint64_t)namespace)) {
api_set_error(err, kErrorTypeValidation, _("Invalid mark namespace"));
return rv;
}
ExtendedMark *extmark = extmark_from_id(buf,
(uint64_t)namespace,
(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 namespace 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 amount Maximum number of marks to return or -1 for all marks found
/// /// @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, Integer amount,
Error *err)
FUNC_API_SINCE(6)
{
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 mark namespace"));
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 a namespaced mark at a position
///
/// If an invalid namespace is given, an error will be raised.
///
/// @param buffer The buffer handle
/// @param ns_id a identifier returned previously with nvim_create_namespace
/// @param id The extmark's id or 0 for next free id
/// @param row The row to set the extmark to.
/// @param col The column to set the extmark to.
/// @param[out] err Details of an error that may have occurred
/// @return the nsmark_id for a new mark, or 0 for an update
Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, Integer id,
Integer line, Integer col, Error *err)
FUNC_API_SINCE(6)
{
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 mark namespace"));
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;
}
bool new = extmark_set(buf, (uint64_t)ns_id, id_num,
(linenr_T)line+1,
(colnr_T)col+1,
kExtmarkUndo);
if (new) {
return (Integer)id_num;
} else {
return 0;
}
}
/// 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 no extmarks found
Boolean nvim_buf_del_extmark(Buffer buffer,
Integer ns_id,
Integer id,
Error *err)
FUNC_API_SINCE(6)
{
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 mark namespace"));
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 +1308,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

@ -24,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"
@ -1507,6 +1508,63 @@ 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)
@ -1516,3 +1574,62 @@ bool ns_initialized(uint64_t ns)
}
return ns < (uint64_t)next_namespace_id;
}
// Extmarks may be queried from position or name or even special names
// in the future such as "cursor". This macro sets the line and col
// to make the extmark functions recognize what's required
//
// *lnum: linenr_T, lnum to be set
// *col: colnr_T, 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(extlines) 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"
@ -105,6 +106,8 @@ typedef struct {
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "ex_cmds.c.generated.h"
#include "mark_extended.h"
#endif
/// ":ascii" and "ga" implementation
@ -658,10 +661,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 +877,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 +891,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 +903,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 +1289,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 +3224,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 +3453,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 +3622,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 +4081,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 +4096,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 +4125,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 +4229,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 +4405,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

@ -184,6 +184,7 @@ MAP_IMPL(String, MsgpackRpcRequestHandler, MSGPACK_HANDLER_INITIALIZER)
#define KVEC_INITIALIZER { .size = 0, .capacity = 0, .items = NULL }
MAP_IMPL(HlEntry, int, DEFAULT_INITIALIZER)
MAP_IMPL(String, handle_T, 0)
MAP_IMPL(uint64_t, cstr_t, DEFAULT_INITIALIZER)
/// Deletes a key:value pair from a string:pointer map, and frees the

View File

@ -42,6 +42,7 @@ MAP_DECLS(handle_T, ptr_t)
MAP_DECLS(String, MsgpackRpcRequestHandler)
MAP_DECLS(HlEntry, int)
MAP_DECLS(String, handle_T)
MAP_DECLS(uint64_t, cstr_t)
#define map_new(T, U) map_##T##_##U##_new
#define map_free(T, U) map_##T##_##U##_free

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));

1262
src/nvim/mark_extended.c Normal file

File diff suppressed because it is too large Load Diff

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

@ -0,0 +1,267 @@
#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(extlines) itr;\
ExtMarkLine t;\
t.lnum = l_lnum;\
if (!kb_itr_get(extlines, &buf->b_extlines, &t, &itr)) { \
kb_itr_next(extlines, &buf->b_extlines, &itr);\
}\
ExtMarkLine *extline;\
for (; kb_itr_valid(&itr); kb_itr_next(extlines, &buf->b_extlines, &itr)) { \
extline = kb_itr_key(&itr);\
if (extline->lnum > u_lnum) { \
break;\
}\
code;\
}
// see FOR_ALL_? for documentation
#define FOR_ALL_EXTMARKLINES_PREV(buf, l_lnum, u_lnum, code)\
kbitr_t(extlines) itr;\
ExtMarkLine t;\
t.lnum = u_lnum;\
if (!kb_itr_get(extlines, &buf->b_extlines, &t, &itr)) { \
kb_itr_prev(extlines, &buf->b_extlines, &itr);\
}\
ExtMarkLine *extline;\
for (; kb_itr_valid(&itr); kb_itr_prev(extlines, &buf->b_extlines, &itr)) { \
extline = kb_itr_key(&itr);\
if (extline->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 = (extline->lnum != l_lnum) ? MINCOL : l_col;\
if (!kb_itr_get(markitems, &extline->items, mt, &mitr)) { \
kb_itr_next(markitems, &extline->items, &mitr);\
} \
ExtendedMark *extmark;\
for (; \
kb_itr_valid(&mitr); \
kb_itr_next(markitems, &extline->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 = (extline->lnum != u_lnum) ? MAXCOL : u_col;\
if (!kb_itr_get(markitems, &extline->items, mt, &mitr)) { \
kb_itr_prev(markitems, &extline->items, &mitr);\
} \
ExtendedMark *extmark;\
for (; \
kb_itr_valid(&mitr); \
kb_itr_prev(markitems, &extline->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 extline_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 > extline_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;
typedef kvec_t(ExtMarkLine *) ExtlineArray;
// 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;
typedef struct {
linenr_T line1;
linenr_T line2;
long amount;
long amount_after;
} Adjust;
typedef struct {
linenr_T lnum;
colnr_T mincol;
long col_amount;
long lnum_amount;
} ColAdjust;
typedef struct {
linenr_T lnum;
colnr_T mincol;
colnr_T endcol;
int eol;
} ColAdjustDelete;
typedef struct {
linenr_T line1;
linenr_T line2;
linenr_T last_line;
linenr_T dest;
linenr_T num_lines;
linenr_T extra;
} AdjustMove;
typedef struct {
uint64_t ns_id;
uint64_t mark_id;
linenr_T lnum;
colnr_T col;
} ExtmarkSet;
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;
typedef struct {
uint64_t ns_id;
uint64_t mark_id;
linenr_T lnum;
colnr_T col;
} ExtmarkCopy;
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;
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;
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 extline_cmp(a, b) (kb_generic_cmp((a)->lnum, (b)->lnum))
KBTREE_INIT(extlines, ExtMarkLine *, extline_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"
@ -126,6 +127,30 @@ static char opchars[][3] =
{ Ctrl_X, NUL, false }, // OP_NR_SUB
};
char *nvim_lltoa(int64_t val, int base)
{
static char buf[64] = { 0 };
int i = 62;
int sign = (val < 0);
if (sign) {
val = -val;
}
if (val == 0) {
return "0";
}
for (; val && i ; i--, val /= base) {
buf[i] = "0123456789abcdef"[val % base];
}
if (sign) {
buf[i--] = '-';
}
return &buf[i+1];
}
/*
* Translate a command name into an operator type.
* Must only be called with a valid operator name!
@ -306,6 +331,20 @@ 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 col_amount;
colnr_T mincol = (curwin->w_cursor.col + 1) -p_sw;
if (left) {
col_amount = -p_sw;
} else {
col_amount = p_sw;
}
extmark_col_adjust(curbuf,
curwin->w_cursor.lnum,
mincol,
0,
col_amount,
kExtmarkUndo);
}
}
@ -479,6 +518,13 @@ 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 = p_sw;
if (left) {
col_amount = -col_amount;
}
extmark_col_adjust(curbuf, curwin->w_cursor.lnum,
curwin->w_cursor.col, 0, col_amount, kExtmarkUndo);
}
/*
@ -623,10 +669,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;
@ -1614,13 +1669,15 @@ int op_delete(oparg_T *oap)
curpos = curwin->w_cursor; // remember curwin->w_cursor
curwin->w_cursor.lnum++;
del_lines(oap->line_count - 2, false);
del_lines(oap->line_count - 2, true);
// delete from start of line until op_end
n = (oap->end.col + 1 - !oap->inclusive);
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 +1689,41 @@ 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) {
// for some reason we required this :/
endcol = endcol - 1;
// for some reason we required this :/
if (endcol < mincol) {
endcol = mincol;
}
}
extmark_col_adjust_delete(curbuf, lnum, mincol, endcol, kExtmarkUndo, 0);
}
return OK;
}
@ -2031,8 +2119,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 +2193,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 +2313,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 +2787,34 @@ static void do_autocmd_textyankpost(oparg_T *oap, yankreg_T *reg)
}
// Function length couldn't be over 500 lines..
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;
if (dir == FORWARD) {
col_amount = (colnr_T)(totlen-1);
} else {
col_amount = (colnr_T)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 +2829,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 +3407,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 +3473,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 +3868,12 @@ 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.
*/
linenr_T lnum;
colnr_T mincol;
long lnum_amount;
long col_amount;
for (t = (linenr_T)count - 1;; t--) {
cend -= currsize;
memmove(cend, curr, (size_t)currsize);
@ -3756,12 +3885,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]);
lnum = curwin->w_cursor.lnum + t;
mincol = (colnr_T)0;
lnum_amount = (linenr_T)-t;
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 +3904,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 +4325,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--;
@ -4862,6 +4998,23 @@ int do_addsub(int op_type, pos_T *pos, int length, linenr_T Prenum1)
curbuf->b_op_end.col--;
}
if (did_change) {
extmark_col_adjust_delete(curbuf,
pos->lnum,
startpos.col + 2,
endpos.col + 1 + length,
kExtmarkUndo,
0);
long col_amount = (long)strlen(nvim_lltoa((int64_t)n, 10));
col_amount = negative ? col_amount + 1 : col_amount;
extmark_col_adjust(curbuf,
pos->lnum,
startpos.col + 1,
0,
col_amount,
kExtmarkUndo);
}
theend:
if (visual) {
curwin->w_cursor = save_cursor;

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,9 @@ u_freeentries(
u_freeentry(uep, uep->ue_size);
}
// TODO(timeyyy): is this the correct place? ...
kv_destroy(uhp->uh_extmark);
#ifdef U_DEBUG
uhp->uh_magic = 0;
#endif
@ -3022,3 +3046,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 *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) {
assert(uhp);
}
}
}
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

View File

@ -8,22 +8,22 @@ local expect = helpers.expect
describe('multi-line regexp', function()
setup(clear)
it('is working', function()
it('is working #fail', function()
insert([[
1 aa
bb
cc
2 dd
ee
3 ef
gh
4 ij
5 a8
8b c9
9d
6 e7
77f
xxxxx]])
1 aa
bb
cc
2 dd
ee
3 ef
gh
4 ij
5 a8
8b c9
9d
6 e7
77f
xxxxx]])
-- Test if replacing a line break works with a back reference
feed([[:/^1/,/^2/s/\n\(.\)/ \1/<cr>]])

View File

@ -131,7 +131,7 @@ describe(":substitute, inccommand=split interactivity", function()
end)
end)
describe(":substitute, 'inccommand' preserves", function()
describe(":substitute, 'inccommand' preserves #inc", function()
before_each(clear)
it('listed buffers (:ls)', function()
@ -293,7 +293,7 @@ describe(":substitute, 'inccommand' preserves", function()
end)
describe(":substitute, 'inccommand' preserves undo", function()
describe(":substitute, 'inccommand' preserves undo #inc", function()
local cases = { "", "split", "nosplit" }
local substrings = {
@ -1962,7 +1962,7 @@ describe(":substitute", function()
clear()
end)
it("inccommand=split, highlights multiline substitutions", function()
it("inccommand=split, highlights multiline substitutions #inc2", function()
common_setup(screen, "split", multiline_text)
feed("gg")
@ -2024,7 +2024,7 @@ describe(":substitute", function()
]])
end)
it("inccommand=nosplit, highlights multiline substitutions", function()
it("inccommand=nosplit, highlights multiline substitutions #inc2", function()
common_setup(screen, "nosplit", multiline_text)
feed("gg")
@ -2117,7 +2117,7 @@ describe(":substitute", function()
]])
end)
it("inccommand=split, with \\zs", function()
it("inccommand=split, with \\zs #inc", function()
common_setup(screen, "split", multiline_text)
feed("gg")
@ -2141,7 +2141,7 @@ describe(":substitute", function()
]])
end)
it("inccommand=nosplit, with \\zs", function()
it("inccommand=nosplit, with \\zs #inc", function()
common_setup(screen, "nosplit", multiline_text)
feed("gg")
@ -2212,7 +2212,7 @@ describe(":substitute", function()
]])
end)
it("inccommand=split, contraction of lines", function()
it("inccommand=split, contraction of lines #inc2", function()
local text = [[
T T123 T T123 T2T TT T23423424
x
@ -2261,7 +2261,7 @@ describe(":substitute", function()
]])
end)
it("inccommand=nosplit, contraction of lines", function()
it("inccommand=nosplit, contraction of lines #inc2", function()
local text = [[
T T123 T T123 T2T TT T23423424
x

View File

@ -817,7 +817,6 @@ describe('ui/mouse/input', function()
feed_command('syntax match NonText "cats" conceal cchar=X')
feed_command('syntax match NonText "x" conceal cchar=>')
-- First column is there to retain the tabs.
insert([[
|Section *t1*
| *t2* *t3* *t4*