forked from aniani/vim
patch 8.1.0634: text properties cannot cross line boundaries
Problem: Text properties cannot cross line boundaries. Solution: Support multi-line text properties.
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
*eval.txt* For Vim version 8.1. Last change: 2018 Dec 18
|
*eval.txt* For Vim version 8.1. Last change: 2018 Dec 24
|
||||||
|
|
||||||
|
|
||||||
VIM REFERENCE MANUAL by Bram Moolenaar
|
VIM REFERENCE MANUAL by Bram Moolenaar
|
||||||
@@ -2318,7 +2318,7 @@ prompt_setcallback({buf}, {expr}) none set prompt callback function
|
|||||||
prompt_setinterrupt({buf}, {text}) none set prompt interrupt function
|
prompt_setinterrupt({buf}, {text}) none set prompt interrupt function
|
||||||
prompt_setprompt({buf}, {text}) none set prompt text
|
prompt_setprompt({buf}, {text}) none set prompt text
|
||||||
prop_add({lnum}, {col}, {props}) none add a text property
|
prop_add({lnum}, {col}, {props}) none add a text property
|
||||||
prop_clear({lnum} [, {lnum-end} [, {bufnr}]])
|
prop_clear({lnum} [, {lnum-end} [, {props}]])
|
||||||
none remove all text properties
|
none remove all text properties
|
||||||
prop_find({props} [, {direction}])
|
prop_find({props} [, {direction}])
|
||||||
Dict search for a text property
|
Dict search for a text property
|
||||||
@@ -6695,7 +6695,7 @@ prop_add({lnum}, {col}, {props})
|
|||||||
used for a property that does not
|
used for a property that does not
|
||||||
continue in another line
|
continue in another line
|
||||||
"end_lnum" - line number for end of text
|
"end_lnum" - line number for end of text
|
||||||
"end_col" - column for end of text; not used when
|
"end_col" - last column of the text; not used when
|
||||||
"length" is present
|
"length" is present
|
||||||
"bufnr" - buffer to add the property to; when
|
"bufnr" - buffer to add the property to; when
|
||||||
omitted the current buffer is used
|
omitted the current buffer is used
|
||||||
@@ -6710,6 +6710,10 @@ prop_add({lnum}, {col}, {props})
|
|||||||
property that spans more than one line.
|
property that spans more than one line.
|
||||||
When neither "length" nor "end_col" are passed the property
|
When neither "length" nor "end_col" are passed the property
|
||||||
will apply to one character.
|
will apply to one character.
|
||||||
|
The property can end exactly at the last character of the
|
||||||
|
text, or just after it. In the last case, if text is appended
|
||||||
|
to the line, the text property size will increase, also when
|
||||||
|
the property type does not have "end_incl" set.
|
||||||
|
|
||||||
"type" will first be looked up in the buffer the property is
|
"type" will first be looked up in the buffer the property is
|
||||||
added to. When not found, the global property types are used.
|
added to. When not found, the global property types are used.
|
||||||
|
@@ -197,4 +197,34 @@ func Test_prop_clear_buf()
|
|||||||
bwipe!
|
bwipe!
|
||||||
endfunc
|
endfunc
|
||||||
|
|
||||||
|
func Test_prop_multiline()
|
||||||
|
call prop_type_add('comment', {'highlight': 'Directory'})
|
||||||
|
new
|
||||||
|
call setline(1, ['xxxxxxx', 'yyyyyyyyy', 'zzzzzzzz'])
|
||||||
|
|
||||||
|
" start halfway line 1, end halfway line 3
|
||||||
|
call prop_add(1, 3, {'end_lnum': 3, 'end_col': 5, 'type': 'comment'})
|
||||||
|
let expect1 = {'col': 3, 'length': 6, 'type': 'comment', 'start': 1, 'end': 0, 'id': 0}
|
||||||
|
call assert_equal([expect1], prop_list(1))
|
||||||
|
let expect2 = {'col': 1, 'length': 10, 'type': 'comment', 'start': 0, 'end': 0, 'id': 0}
|
||||||
|
call assert_equal([expect2], prop_list(2))
|
||||||
|
let expect3 = {'col': 1, 'length': 5, 'type': 'comment', 'start': 0, 'end': 1, 'id': 0}
|
||||||
|
call assert_equal([expect3], prop_list(3))
|
||||||
|
call prop_clear(1, 3)
|
||||||
|
|
||||||
|
" include all three lines
|
||||||
|
call prop_add(1, 1, {'end_lnum': 3, 'end_col': 999, 'type': 'comment'})
|
||||||
|
let expect1.col = 1
|
||||||
|
let expect1.length = 8
|
||||||
|
call assert_equal([expect1], prop_list(1))
|
||||||
|
call assert_equal([expect2], prop_list(2))
|
||||||
|
let expect3.length = 9
|
||||||
|
call assert_equal([expect3], prop_list(3))
|
||||||
|
call prop_clear(1, 3)
|
||||||
|
|
||||||
|
bwipe!
|
||||||
|
call prop_type_delete('comment')
|
||||||
|
endfunc
|
||||||
|
|
||||||
|
|
||||||
" TODO: screenshot test with highlighting
|
" TODO: screenshot test with highlighting
|
||||||
|
174
src/textprop.c
174
src/textprop.c
@@ -17,10 +17,12 @@
|
|||||||
* Text properties have a type, which can be used to specify highlighting.
|
* Text properties have a type, which can be used to specify highlighting.
|
||||||
*
|
*
|
||||||
* TODO:
|
* TODO:
|
||||||
|
* - When deleting a line where a prop ended, adjust flag of previous line.
|
||||||
|
* - When deleting a line where a prop started, adjust flag of next line.
|
||||||
|
* - When inserting a line add props that continue from previous line.
|
||||||
|
* - Adjust property column and length when text is inserted/deleted
|
||||||
* - Add an arrray for global_proptypes, to quickly lookup a proptype by ID
|
* - Add an arrray for global_proptypes, to quickly lookup a proptype by ID
|
||||||
* - Add an arrray for b_proptypes, to quickly lookup a proptype by ID
|
* - Add an arrray for b_proptypes, to quickly lookup a proptype by ID
|
||||||
* - adjust property column when text is inserted/deleted
|
|
||||||
* - support properties that continue over a line break
|
|
||||||
* - add mechanism to keep track of changed lines.
|
* - add mechanism to keep track of changed lines.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@@ -47,6 +49,7 @@ static int proptype_id = 0;
|
|||||||
|
|
||||||
static char_u e_type_not_exist[] = N_("E971: Property type %s does not exist");
|
static char_u e_type_not_exist[] = N_("E971: Property type %s does not exist");
|
||||||
static char_u e_invalid_col[] = N_("E964: Invalid column number: %ld");
|
static char_u e_invalid_col[] = N_("E964: Invalid column number: %ld");
|
||||||
|
static char_u e_invalid_lnum[] = N_("E966: Invalid line number: %ld");
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Find a property type by name, return the hashitem.
|
* Find a property type by name, return the hashitem.
|
||||||
@@ -139,9 +142,11 @@ get_bufnr_from_arg(typval_T *arg, buf_T **buf)
|
|||||||
f_prop_add(typval_T *argvars, typval_T *rettv UNUSED)
|
f_prop_add(typval_T *argvars, typval_T *rettv UNUSED)
|
||||||
{
|
{
|
||||||
linenr_T lnum;
|
linenr_T lnum;
|
||||||
colnr_T col;
|
linenr_T start_lnum;
|
||||||
|
linenr_T end_lnum;
|
||||||
|
colnr_T start_col;
|
||||||
|
colnr_T end_col;
|
||||||
dict_T *dict;
|
dict_T *dict;
|
||||||
colnr_T length = 1;
|
|
||||||
char_u *type_name;
|
char_u *type_name;
|
||||||
proptype_T *type;
|
proptype_T *type;
|
||||||
buf_T *buf = curbuf;
|
buf_T *buf = curbuf;
|
||||||
@@ -154,11 +159,11 @@ f_prop_add(typval_T *argvars, typval_T *rettv UNUSED)
|
|||||||
textprop_T tmp_prop;
|
textprop_T tmp_prop;
|
||||||
int i;
|
int i;
|
||||||
|
|
||||||
lnum = tv_get_number(&argvars[0]);
|
start_lnum = tv_get_number(&argvars[0]);
|
||||||
col = tv_get_number(&argvars[1]);
|
start_col = tv_get_number(&argvars[1]);
|
||||||
if (col < 1)
|
if (start_col < 1)
|
||||||
{
|
{
|
||||||
EMSGN(_(e_invalid_col), (long)col);
|
EMSGN(_(e_invalid_col), (long)start_col);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (argvars[2].v_type != VAR_DICT)
|
if (argvars[2].v_type != VAR_DICT)
|
||||||
@@ -177,22 +182,40 @@ f_prop_add(typval_T *argvars, typval_T *rettv UNUSED)
|
|||||||
|
|
||||||
if (dict_find(dict, (char_u *)"end_lnum", -1) != NULL)
|
if (dict_find(dict, (char_u *)"end_lnum", -1) != NULL)
|
||||||
{
|
{
|
||||||
// TODO: handle end_lnum
|
end_lnum = dict_get_number(dict, (char_u *)"end_lnum");
|
||||||
EMSG("Sorry, end_lnum not supported yet");
|
if (end_lnum < start_lnum)
|
||||||
return;
|
{
|
||||||
|
EMSG2(_(e_invargval), "end_lnum");
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
end_lnum = start_lnum;
|
||||||
|
|
||||||
if (dict_find(dict, (char_u *)"length", -1) != NULL)
|
if (dict_find(dict, (char_u *)"length", -1) != NULL)
|
||||||
length = dict_get_number(dict, (char_u *)"length");
|
{
|
||||||
|
long length = dict_get_number(dict, (char_u *)"length");
|
||||||
|
|
||||||
|
if (length < 1 || end_lnum > start_lnum)
|
||||||
|
{
|
||||||
|
EMSG2(_(e_invargval), "length");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
end_col = start_col + length - 1;
|
||||||
|
}
|
||||||
else if (dict_find(dict, (char_u *)"end_col", -1) != NULL)
|
else if (dict_find(dict, (char_u *)"end_col", -1) != NULL)
|
||||||
{
|
{
|
||||||
length = dict_get_number(dict, (char_u *)"end_col") - col;
|
end_col = dict_get_number(dict, (char_u *)"end_col");
|
||||||
if (length <= 0)
|
if (end_col <= 0)
|
||||||
{
|
{
|
||||||
EMSG2(_(e_invargval), "end_col");
|
EMSG2(_(e_invargval), "end_col");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else if (start_lnum == end_lnum)
|
||||||
|
end_col = start_col;
|
||||||
|
else
|
||||||
|
end_col = 1;
|
||||||
|
|
||||||
if (dict_find(dict, (char_u *)"id", -1) != NULL)
|
if (dict_find(dict, (char_u *)"id", -1) != NULL)
|
||||||
id = dict_get_number(dict, (char_u *)"id");
|
id = dict_get_number(dict, (char_u *)"id");
|
||||||
@@ -204,62 +227,87 @@ f_prop_add(typval_T *argvars, typval_T *rettv UNUSED)
|
|||||||
if (type == NULL)
|
if (type == NULL)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (lnum < 1 || lnum > buf->b_ml.ml_line_count)
|
if (start_lnum < 1 || start_lnum > buf->b_ml.ml_line_count)
|
||||||
{
|
{
|
||||||
EMSGN(_("E966: Invalid line number: %ld"), (long)lnum);
|
EMSGN(_(e_invalid_lnum), (long)start_lnum);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (end_lnum < start_lnum || end_lnum > buf->b_ml.ml_line_count)
|
||||||
|
{
|
||||||
|
EMSGN(_(e_invalid_lnum), (long)end_lnum);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fetch the line to get the ml_line_len field updated.
|
for (lnum = start_lnum; lnum <= end_lnum; ++lnum)
|
||||||
proplen = get_text_props(buf, lnum, &props, TRUE);
|
|
||||||
textlen = buf->b_ml.ml_line_len - proplen * sizeof(textprop_T);
|
|
||||||
|
|
||||||
if (col >= (colnr_T)textlen - 1)
|
|
||||||
{
|
{
|
||||||
EMSGN(_(e_invalid_col), (long)col);
|
colnr_T col; // start column
|
||||||
return;
|
long length; // in bytes
|
||||||
|
|
||||||
|
// Fetch the line to get the ml_line_len field updated.
|
||||||
|
proplen = get_text_props(buf, lnum, &props, TRUE);
|
||||||
|
textlen = buf->b_ml.ml_line_len - proplen * sizeof(textprop_T);
|
||||||
|
|
||||||
|
if (lnum == start_lnum)
|
||||||
|
col = start_col;
|
||||||
|
else
|
||||||
|
col = 1;
|
||||||
|
if (col - 1 > (colnr_T)textlen)
|
||||||
|
{
|
||||||
|
EMSGN(_(e_invalid_col), (long)start_col);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lnum == end_lnum)
|
||||||
|
length = end_col - col + 1;
|
||||||
|
else
|
||||||
|
length = textlen - col + 1;
|
||||||
|
if (length > textlen)
|
||||||
|
length = textlen; // can include the end-of-line
|
||||||
|
if (length < 1)
|
||||||
|
length = 1;
|
||||||
|
|
||||||
|
// Allocate the new line with space for the new proprety.
|
||||||
|
newtext = alloc(buf->b_ml.ml_line_len + sizeof(textprop_T));
|
||||||
|
if (newtext == NULL)
|
||||||
|
return;
|
||||||
|
// Copy the text, including terminating NUL.
|
||||||
|
mch_memmove(newtext, buf->b_ml.ml_line_ptr, textlen);
|
||||||
|
|
||||||
|
// Find the index where to insert the new property.
|
||||||
|
// Since the text properties are not aligned properly when stored with the
|
||||||
|
// text, we need to copy them as bytes before using it as a struct.
|
||||||
|
for (i = 0; i < proplen; ++i)
|
||||||
|
{
|
||||||
|
mch_memmove(&tmp_prop, props + i * sizeof(textprop_T),
|
||||||
|
sizeof(textprop_T));
|
||||||
|
if (tmp_prop.tp_col >= col)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
newprops = newtext + textlen;
|
||||||
|
if (i > 0)
|
||||||
|
mch_memmove(newprops, props, sizeof(textprop_T) * i);
|
||||||
|
|
||||||
|
tmp_prop.tp_col = col;
|
||||||
|
tmp_prop.tp_len = length;
|
||||||
|
tmp_prop.tp_id = id;
|
||||||
|
tmp_prop.tp_type = type->pt_id;
|
||||||
|
tmp_prop.tp_flags = (lnum > start_lnum ? TP_FLAG_CONT_PREV : 0)
|
||||||
|
| (lnum < end_lnum ? TP_FLAG_CONT_NEXT : 0);
|
||||||
|
mch_memmove(newprops + i * sizeof(textprop_T), &tmp_prop,
|
||||||
|
sizeof(textprop_T));
|
||||||
|
|
||||||
|
if (i < proplen)
|
||||||
|
mch_memmove(newprops + (i + 1) * sizeof(textprop_T),
|
||||||
|
props + i * sizeof(textprop_T),
|
||||||
|
sizeof(textprop_T) * (proplen - i));
|
||||||
|
|
||||||
|
if (buf->b_ml.ml_flags & ML_LINE_DIRTY)
|
||||||
|
vim_free(buf->b_ml.ml_line_ptr);
|
||||||
|
buf->b_ml.ml_line_ptr = newtext;
|
||||||
|
buf->b_ml.ml_line_len += sizeof(textprop_T);
|
||||||
|
buf->b_ml.ml_flags |= ML_LINE_DIRTY;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Allocate the new line with space for the new proprety.
|
|
||||||
newtext = alloc(buf->b_ml.ml_line_len + sizeof(textprop_T));
|
|
||||||
if (newtext == NULL)
|
|
||||||
return;
|
|
||||||
// Copy the text, including terminating NUL.
|
|
||||||
mch_memmove(newtext, buf->b_ml.ml_line_ptr, textlen);
|
|
||||||
|
|
||||||
// Find the index where to insert the new property.
|
|
||||||
// Since the text properties are not aligned properly when stored with the
|
|
||||||
// text, we need to copy them as bytes before using it as a struct.
|
|
||||||
for (i = 0; i < proplen; ++i)
|
|
||||||
{
|
|
||||||
mch_memmove(&tmp_prop, props + i * sizeof(textprop_T),
|
|
||||||
sizeof(textprop_T));
|
|
||||||
if (tmp_prop.tp_col >= col)
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
newprops = newtext + textlen;
|
|
||||||
if (i > 0)
|
|
||||||
mch_memmove(newprops, props, sizeof(textprop_T) * i);
|
|
||||||
|
|
||||||
tmp_prop.tp_col = col;
|
|
||||||
tmp_prop.tp_len = length;
|
|
||||||
tmp_prop.tp_id = id;
|
|
||||||
tmp_prop.tp_type = type->pt_id;
|
|
||||||
tmp_prop.tp_flags = 0;
|
|
||||||
mch_memmove(newprops + i * sizeof(textprop_T), &tmp_prop,
|
|
||||||
sizeof(textprop_T));
|
|
||||||
|
|
||||||
if (i < proplen)
|
|
||||||
mch_memmove(newprops + (i + 1) * sizeof(textprop_T),
|
|
||||||
props + i * sizeof(textprop_T),
|
|
||||||
sizeof(textprop_T) * (proplen - i));
|
|
||||||
|
|
||||||
if (buf->b_ml.ml_flags & ML_LINE_DIRTY)
|
|
||||||
vim_free(buf->b_ml.ml_line_ptr);
|
|
||||||
buf->b_ml.ml_line_ptr = newtext;
|
|
||||||
buf->b_ml.ml_line_len += sizeof(textprop_T);
|
|
||||||
buf->b_ml.ml_flags |= ML_LINE_DIRTY;
|
|
||||||
|
|
||||||
redraw_buf_later(buf, NOT_VALID);
|
redraw_buf_later(buf, NOT_VALID);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -799,6 +799,8 @@ static char *(features[]) =
|
|||||||
|
|
||||||
static int included_patches[] =
|
static int included_patches[] =
|
||||||
{ /* Add new patch number below this line */
|
{ /* Add new patch number below this line */
|
||||||
|
/**/
|
||||||
|
634,
|
||||||
/**/
|
/**/
|
||||||
633,
|
633,
|
||||||
/**/
|
/**/
|
||||||
|
Reference in New Issue
Block a user