nsmarks: initial commit
This commit is contained in:
parent
e757f4d536
commit
a9065a5051
11
README.md
11
README.md
|
@ -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
|
||||
|
|
|
@ -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*
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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)) {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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));
|
||||
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -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
|
|
@ -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
|
|
@ -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"
|
||||
|
|
185
src/nvim/ops.c
185
src/nvim/ops.c
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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
|
@ -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>]])
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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*
|
||||
|
|
Loading…
Reference in New Issue