0
0
mirror of https://github.com/vim/vim.git synced 2025-09-29 04:34:16 -04:00

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:
Bram Moolenaar
2018-12-24 23:07:04 +01:00
parent cd929f7ba8
commit e3d31b02a5
4 changed files with 150 additions and 66 deletions

View File

@@ -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
@@ -2318,7 +2318,7 @@ prompt_setcallback({buf}, {expr}) none set prompt callback function
prompt_setinterrupt({buf}, {text}) none set prompt interrupt function
prompt_setprompt({buf}, {text}) none set prompt text
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
prop_find({props} [, {direction}])
Dict search for a text property
@@ -6695,7 +6695,7 @@ prop_add({lnum}, {col}, {props})
used for a property that does not
continue in another line
"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
"bufnr" - buffer to add the property to; when
omitted the current buffer is used
@@ -6710,6 +6710,10 @@ prop_add({lnum}, {col}, {props})
property that spans more than one line.
When neither "length" nor "end_col" are passed the property
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
added to. When not found, the global property types are used.

View File

@@ -197,4 +197,34 @@ func Test_prop_clear_buf()
bwipe!
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

View File

@@ -17,10 +17,12 @@
* Text properties have a type, which can be used to specify highlighting.
*
* 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 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.
*/
@@ -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_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.
@@ -139,9 +142,11 @@ get_bufnr_from_arg(typval_T *arg, buf_T **buf)
f_prop_add(typval_T *argvars, typval_T *rettv UNUSED)
{
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;
colnr_T length = 1;
char_u *type_name;
proptype_T *type;
buf_T *buf = curbuf;
@@ -154,11 +159,11 @@ f_prop_add(typval_T *argvars, typval_T *rettv UNUSED)
textprop_T tmp_prop;
int i;
lnum = tv_get_number(&argvars[0]);
col = tv_get_number(&argvars[1]);
if (col < 1)
start_lnum = tv_get_number(&argvars[0]);
start_col = tv_get_number(&argvars[1]);
if (start_col < 1)
{
EMSGN(_(e_invalid_col), (long)col);
EMSGN(_(e_invalid_col), (long)start_col);
return;
}
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)
{
// TODO: handle end_lnum
EMSG("Sorry, end_lnum not supported yet");
return;
end_lnum = dict_get_number(dict, (char_u *)"end_lnum");
if (end_lnum < start_lnum)
{
EMSG2(_(e_invargval), "end_lnum");
return;
}
}
else
end_lnum = start_lnum;
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)
{
length = dict_get_number(dict, (char_u *)"end_col") - col;
if (length <= 0)
end_col = dict_get_number(dict, (char_u *)"end_col");
if (end_col <= 0)
{
EMSG2(_(e_invargval), "end_col");
return;
}
}
else if (start_lnum == end_lnum)
end_col = start_col;
else
end_col = 1;
if (dict_find(dict, (char_u *)"id", -1) != NULL)
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)
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;
}
// 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 (col >= (colnr_T)textlen - 1)
for (lnum = start_lnum; lnum <= end_lnum; ++lnum)
{
EMSGN(_(e_invalid_col), (long)col);
return;
colnr_T col; // start column
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);
}

View File

@@ -799,6 +799,8 @@ static char *(features[]) =
static int included_patches[] =
{ /* Add new patch number below this line */
/**/
634,
/**/
633,
/**/