xref: /vim-8.2.3635/src/textprop.c (revision ccfb7c67)
1 /* vi:set ts=8 sts=4 sw=4 noet:
2  *
3  * VIM - Vi IMproved	by Bram Moolenaar
4  *
5  * Do ":help uganda"  in Vim to read copying and usage conditions.
6  * Do ":help credits" in Vim to see a list of people who contributed.
7  * See README.txt for an overview of the Vim source code.
8  */
9 
10 /*
11  * Text properties implementation.  See ":help text-properties".
12  *
13  * TODO:
14  * - Adjust text property column and length when text is inserted/deleted.
15  *   -> a :substitute with a multi-line match
16  *   -> search for changed_bytes() from misc1.c
17  *   -> search for mark_col_adjust()
18  * - Perhaps we only need TP_FLAG_CONT_NEXT and can drop TP_FLAG_CONT_PREV?
19  * - Add an array for global_proptypes, to quickly lookup a prop type by ID
20  * - Add an array for b_proptypes, to quickly lookup a prop type by ID
21  * - Checking the text length to detect text properties is slow.  Use a flag in
22  *   the index, like DB_MARKED?
23  * - Also test line2byte() with many lines, so that ml_updatechunk() is taken
24  *   into account.
25  * - Perhaps have a window-local option to disable highlighting from text
26  *   properties?
27  */
28 
29 #include "vim.h"
30 
31 #if defined(FEAT_PROP_POPUP) || defined(PROTO)
32 
33 /*
34  * In a hashtable item "hi_key" points to "pt_name" in a proptype_T.
35  * This avoids adding a pointer to the hashtable item.
36  * PT2HIKEY() converts a proptype pointer to a hashitem key pointer.
37  * HIKEY2PT() converts a hashitem key pointer to a proptype pointer.
38  * HI2PT() converts a hashitem pointer to a proptype pointer.
39  */
40 #define PT2HIKEY(p)  ((p)->pt_name)
41 #define HIKEY2PT(p)   ((proptype_T *)((p) - offsetof(proptype_T, pt_name)))
42 #define HI2PT(hi)      HIKEY2PT((hi)->hi_key)
43 
44 // The global text property types.
45 static hashtab_T *global_proptypes = NULL;
46 
47 // The last used text property type ID.
48 static int proptype_id = 0;
49 
50 static char_u e_type_not_exist[] = N_("E971: Property type %s does not exist");
51 static char_u e_invalid_col[] = N_("E964: Invalid column number: %ld");
52 static char_u e_invalid_lnum[] = N_("E966: Invalid line number: %ld");
53 
54 /*
55  * Find a property type by name, return the hashitem.
56  * Returns NULL if the item can't be found.
57  */
58     static hashitem_T *
find_prop_hi(char_u * name,buf_T * buf)59 find_prop_hi(char_u *name, buf_T *buf)
60 {
61     hashtab_T	*ht;
62     hashitem_T	*hi;
63 
64     if (*name == NUL)
65 	return NULL;
66     if (buf == NULL)
67 	ht = global_proptypes;
68     else
69 	ht = buf->b_proptypes;
70 
71     if (ht == NULL)
72 	return NULL;
73     hi = hash_find(ht, name);
74     if (HASHITEM_EMPTY(hi))
75 	return NULL;
76     return hi;
77 }
78 
79 /*
80  * Like find_prop_hi() but return the property type.
81  */
82     static proptype_T *
find_prop(char_u * name,buf_T * buf)83 find_prop(char_u *name, buf_T *buf)
84 {
85     hashitem_T	*hi = find_prop_hi(name, buf);
86 
87     if (hi == NULL)
88 	return NULL;
89     return HI2PT(hi);
90 }
91 
92 /*
93  * Get the prop type ID of "name".
94  * When not found return zero.
95  */
96     int
find_prop_type_id(char_u * name,buf_T * buf)97 find_prop_type_id(char_u *name, buf_T *buf)
98 {
99     proptype_T *pt = find_prop(name, buf);
100 
101     if (pt == NULL)
102 	return 0;
103     return pt->pt_id;
104 }
105 
106 /*
107  * Lookup a property type by name.  First in "buf" and when not found in the
108  * global types.
109  * When not found gives an error message and returns NULL.
110  */
111     static proptype_T *
lookup_prop_type(char_u * name,buf_T * buf)112 lookup_prop_type(char_u *name, buf_T *buf)
113 {
114     proptype_T *type = find_prop(name, buf);
115 
116     if (type == NULL)
117 	type = find_prop(name, NULL);
118     if (type == NULL)
119 	semsg(_(e_type_not_exist), name);
120     return type;
121 }
122 
123 /*
124  * Get an optional "bufnr" item from the dict in "arg".
125  * When the argument is not used or "bufnr" is not present then "buf" is
126  * unchanged.
127  * If "bufnr" is valid or not present return OK.
128  * When "arg" is not a dict or "bufnr" is invalid return FAIL.
129  */
130     static int
get_bufnr_from_arg(typval_T * arg,buf_T ** buf)131 get_bufnr_from_arg(typval_T *arg, buf_T **buf)
132 {
133     dictitem_T	*di;
134 
135     if (arg->v_type != VAR_DICT)
136     {
137 	emsg(_(e_dictreq));
138 	return FAIL;
139     }
140     if (arg->vval.v_dict == NULL)
141 	return OK;  // NULL dict is like an empty dict
142     di = dict_find(arg->vval.v_dict, (char_u *)"bufnr", -1);
143     if (di != NULL && (di->di_tv.v_type != VAR_NUMBER
144 					      || di->di_tv.vval.v_number != 0))
145     {
146 	*buf = get_buf_arg(&di->di_tv);
147 	if (*buf == NULL)
148 	    return FAIL;
149     }
150     return OK;
151 }
152 
153 /*
154  * prop_add({lnum}, {col}, {props})
155  */
156     void
f_prop_add(typval_T * argvars,typval_T * rettv UNUSED)157 f_prop_add(typval_T *argvars, typval_T *rettv UNUSED)
158 {
159     linenr_T	start_lnum;
160     colnr_T	start_col;
161 
162     if (in_vim9script()
163 	    && (check_for_number_arg(argvars, 0) == FAIL
164 		|| check_for_number_arg(argvars, 1) == FAIL
165 		|| check_for_dict_arg(argvars, 2) == FAIL))
166 	return;
167 
168     start_lnum = tv_get_number(&argvars[0]);
169     start_col = tv_get_number(&argvars[1]);
170     if (start_col < 1)
171     {
172 	semsg(_(e_invalid_col), (long)start_col);
173 	return;
174     }
175     if (argvars[2].v_type != VAR_DICT)
176     {
177 	emsg(_(e_dictreq));
178 	return;
179     }
180 
181     prop_add_common(start_lnum, start_col, argvars[2].vval.v_dict,
182 							  curbuf, &argvars[2]);
183 }
184 
185 /*
186  * Attach a text property 'type_name' to the text starting
187  * at [start_lnum, start_col] and ending at [end_lnum, end_col] in
188  * the buffer 'buf' and assign identifier 'id'.
189  */
190     static int
prop_add_one(buf_T * buf,char_u * type_name,int id,linenr_T start_lnum,linenr_T end_lnum,colnr_T start_col,colnr_T end_col)191 prop_add_one(
192 	buf_T		*buf,
193 	char_u		*type_name,
194 	int		id,
195 	linenr_T	start_lnum,
196 	linenr_T	end_lnum,
197 	colnr_T		start_col,
198 	colnr_T		end_col)
199 {
200     proptype_T	*type;
201     linenr_T	lnum;
202     int		proplen;
203     char_u	*props = NULL;
204     char_u	*newprops;
205     size_t	textlen;
206     char_u	*newtext;
207     int		i;
208     textprop_T	tmp_prop;
209 
210     type = lookup_prop_type(type_name, buf);
211     if (type == NULL)
212 	return FAIL;
213 
214     if (start_lnum < 1 || start_lnum > buf->b_ml.ml_line_count)
215     {
216 	semsg(_(e_invalid_lnum), (long)start_lnum);
217 	return FAIL;
218     }
219     if (end_lnum < start_lnum || end_lnum > buf->b_ml.ml_line_count)
220     {
221 	semsg(_(e_invalid_lnum), (long)end_lnum);
222 	return FAIL;
223     }
224 
225     if (buf->b_ml.ml_mfp == NULL)
226     {
227 	emsg(_("E275: Cannot add text property to unloaded buffer"));
228 	return FAIL;
229     }
230 
231     for (lnum = start_lnum; lnum <= end_lnum; ++lnum)
232     {
233 	colnr_T col;	// start column
234 	long	length;	// in bytes
235 
236 	// Fetch the line to get the ml_line_len field updated.
237 	proplen = get_text_props(buf, lnum, &props, TRUE);
238 	textlen = buf->b_ml.ml_line_len - proplen * sizeof(textprop_T);
239 
240 	if (lnum == start_lnum)
241 	    col = start_col;
242 	else
243 	    col = 1;
244 	if (col - 1 > (colnr_T)textlen)
245 	{
246 	    semsg(_(e_invalid_col), (long)start_col);
247 	    return FAIL;
248 	}
249 
250 	if (lnum == end_lnum)
251 	    length = end_col - col;
252 	else
253 	    length = (int)textlen - col + 1;
254 	if (length > (long)textlen)
255 	    length = (int)textlen;	// can include the end-of-line
256 	if (length < 0)
257 	    length = 0;		// zero-width property
258 
259 	// Allocate the new line with space for the new property.
260 	newtext = alloc(buf->b_ml.ml_line_len + sizeof(textprop_T));
261 	if (newtext == NULL)
262 	    return FAIL;
263 	// Copy the text, including terminating NUL.
264 	mch_memmove(newtext, buf->b_ml.ml_line_ptr, textlen);
265 
266 	// Find the index where to insert the new property.
267 	// Since the text properties are not aligned properly when stored with
268 	// the text, we need to copy them as bytes before using it as a struct.
269 	for (i = 0; i < proplen; ++i)
270 	{
271 	    mch_memmove(&tmp_prop, props + i * sizeof(textprop_T),
272 							   sizeof(textprop_T));
273 	    if (tmp_prop.tp_col >= col)
274 		break;
275 	}
276 	newprops = newtext + textlen;
277 	if (i > 0)
278 	    mch_memmove(newprops, props, sizeof(textprop_T) * i);
279 
280 	tmp_prop.tp_col = col;
281 	tmp_prop.tp_len = length;
282 	tmp_prop.tp_id = id;
283 	tmp_prop.tp_type = type->pt_id;
284 	tmp_prop.tp_flags = (lnum > start_lnum ? TP_FLAG_CONT_PREV : 0)
285 			  | (lnum < end_lnum ? TP_FLAG_CONT_NEXT : 0);
286 	mch_memmove(newprops + i * sizeof(textprop_T), &tmp_prop,
287 							   sizeof(textprop_T));
288 
289 	if (i < proplen)
290 	    mch_memmove(newprops + (i + 1) * sizeof(textprop_T),
291 					    props + i * sizeof(textprop_T),
292 					    sizeof(textprop_T) * (proplen - i));
293 
294 	if (buf->b_ml.ml_flags & ML_LINE_DIRTY)
295 	    vim_free(buf->b_ml.ml_line_ptr);
296 	buf->b_ml.ml_line_ptr = newtext;
297 	buf->b_ml.ml_line_len += sizeof(textprop_T);
298 	buf->b_ml.ml_flags |= ML_LINE_DIRTY;
299     }
300 
301     changed_lines_buf(buf, start_lnum, end_lnum + 1, 0);
302     return OK;
303 }
304 
305 /*
306  * prop_add_list()
307  * First argument specifies the text property:
308  *   {'type': <str>, 'id': <num>, 'bufnr': <num>}
309  * Second argument is a List where each item is a List with the following
310  * entries: [lnum, start_col, end_col]
311  */
312     void
f_prop_add_list(typval_T * argvars,typval_T * rettv UNUSED)313 f_prop_add_list(typval_T *argvars, typval_T *rettv UNUSED)
314 {
315     dict_T	*dict;
316     char_u	*type_name;
317     buf_T	*buf = curbuf;
318     int		id = 0;
319     listitem_T	*li;
320     list_T	*pos_list;
321     linenr_T	start_lnum;
322     colnr_T	start_col;
323     linenr_T	end_lnum;
324     colnr_T	end_col;
325     int		error = FALSE;
326 
327     if (check_for_dict_arg(argvars, 0) == FAIL
328 	    || check_for_list_arg(argvars, 1) == FAIL)
329 	return;
330 
331     if (argvars[1].vval.v_list == NULL)
332     {
333 	emsg(_(e_listreq));
334 	return;
335     }
336 
337     dict = argvars[0].vval.v_dict;
338     if (dict == NULL || dict_find(dict, (char_u *)"type", -1) == NULL)
339     {
340 	emsg(_("E965: missing property type name"));
341 	return;
342     }
343     type_name = dict_get_string(dict, (char_u *)"type", FALSE);
344 
345     if (dict_find(dict, (char_u *)"id", -1) != NULL)
346 	id = dict_get_number(dict, (char_u *)"id");
347 
348     if (get_bufnr_from_arg(&argvars[0], &buf) == FAIL)
349 	return;
350 
351     FOR_ALL_LIST_ITEMS(argvars[1].vval.v_list, li)
352     {
353 	if (li->li_tv.v_type != VAR_LIST || li->li_tv.vval.v_list == NULL)
354 	{
355 	    emsg(_(e_listreq));
356 	    return;
357 	}
358 
359 	pos_list = li->li_tv.vval.v_list;
360 	start_lnum = list_find_nr(pos_list, 0L, &error);
361 	start_col = list_find_nr(pos_list, 1L, &error);
362 	end_lnum = list_find_nr(pos_list, 2L, &error);
363 	end_col = list_find_nr(pos_list, 3L, &error);
364 	if (error || start_lnum <= 0 || start_col <= 0
365 		|| end_lnum <= 0 || end_col <= 0)
366 	{
367 	    emsg(_(e_invarg));
368 	    return;
369 	}
370 	if (prop_add_one(buf, type_name, id, start_lnum, end_lnum,
371 						start_col, end_col) == FAIL)
372 	    return;
373     }
374 
375     buf->b_has_textprop = TRUE;  // this is never reset
376     redraw_buf_later(buf, VALID);
377 }
378 
379 /*
380  * Shared between prop_add() and popup_create().
381  * "dict_arg" is the function argument of a dict containing "bufnr".
382  * it is NULL for popup_create().
383  */
384     void
prop_add_common(linenr_T start_lnum,colnr_T start_col,dict_T * dict,buf_T * default_buf,typval_T * dict_arg)385 prop_add_common(
386 	linenr_T    start_lnum,
387 	colnr_T	    start_col,
388 	dict_T	    *dict,
389 	buf_T	    *default_buf,
390 	typval_T    *dict_arg)
391 {
392     linenr_T	end_lnum;
393     colnr_T	end_col;
394     char_u	*type_name;
395     buf_T	*buf = default_buf;
396     int		id = 0;
397 
398     if (dict == NULL || dict_find(dict, (char_u *)"type", -1) == NULL)
399     {
400 	emsg(_("E965: missing property type name"));
401 	return;
402     }
403     type_name = dict_get_string(dict, (char_u *)"type", FALSE);
404 
405     if (dict_find(dict, (char_u *)"end_lnum", -1) != NULL)
406     {
407 	end_lnum = dict_get_number(dict, (char_u *)"end_lnum");
408 	if (end_lnum < start_lnum)
409 	{
410 	    semsg(_(e_invargval), "end_lnum");
411 	    return;
412 	}
413     }
414     else
415 	end_lnum = start_lnum;
416 
417     if (dict_find(dict, (char_u *)"length", -1) != NULL)
418     {
419 	long length = dict_get_number(dict, (char_u *)"length");
420 
421 	if (length < 0 || end_lnum > start_lnum)
422 	{
423 	    semsg(_(e_invargval), "length");
424 	    return;
425 	}
426 	end_col = start_col + length;
427     }
428     else if (dict_find(dict, (char_u *)"end_col", -1) != NULL)
429     {
430 	end_col = dict_get_number(dict, (char_u *)"end_col");
431 	if (end_col <= 0)
432 	{
433 	    semsg(_(e_invargval), "end_col");
434 	    return;
435 	}
436     }
437     else if (start_lnum == end_lnum)
438 	end_col = start_col;
439     else
440 	end_col = 1;
441 
442     if (dict_find(dict, (char_u *)"id", -1) != NULL)
443 	id = dict_get_number(dict, (char_u *)"id");
444 
445     if (dict_arg != NULL && get_bufnr_from_arg(dict_arg, &buf) == FAIL)
446 	return;
447 
448     prop_add_one(buf, type_name, id, start_lnum, end_lnum, start_col, end_col);
449 
450     buf->b_has_textprop = TRUE;  // this is never reset
451     redraw_buf_later(buf, VALID);
452 }
453 
454 /*
455  * Fetch the text properties for line "lnum" in buffer "buf".
456  * Returns the number of text properties and, when non-zero, a pointer to the
457  * first one in "props" (note that it is not aligned, therefore the char_u
458  * pointer).
459  */
460     int
get_text_props(buf_T * buf,linenr_T lnum,char_u ** props,int will_change)461 get_text_props(buf_T *buf, linenr_T lnum, char_u **props, int will_change)
462 {
463     char_u *text;
464     size_t textlen;
465     size_t proplen;
466 
467     // Be quick when no text property types have been defined or the buffer,
468     // unless we are adding one.
469     if ((!buf->b_has_textprop && !will_change) || buf->b_ml.ml_mfp == NULL)
470 	return 0;
471 
472     // Fetch the line to get the ml_line_len field updated.
473     text = ml_get_buf(buf, lnum, will_change);
474     textlen = STRLEN(text) + 1;
475     proplen = buf->b_ml.ml_line_len - textlen;
476     if (proplen % sizeof(textprop_T) != 0)
477     {
478 	iemsg(_("E967: text property info corrupted"));
479 	return 0;
480     }
481     if (proplen > 0)
482 	*props = text + textlen;
483     return (int)(proplen / sizeof(textprop_T));
484 }
485 
486 /**
487  * Return the number of text properties on line "lnum" in the current buffer.
488  * When "only_starting" is true only text properties starting in this line will
489  * be considered.
490  */
491     int
count_props(linenr_T lnum,int only_starting)492 count_props(linenr_T lnum, int only_starting)
493 {
494     char_u	*props;
495     int		proplen = get_text_props(curbuf, lnum, &props, 0);
496     int		result = proplen;
497     int		i;
498     textprop_T	prop;
499 
500     if (only_starting)
501 	for (i = 0; i < proplen; ++i)
502 	{
503 	    mch_memmove(&prop, props + i * sizeof(prop), sizeof(prop));
504 	    if (prop.tp_flags & TP_FLAG_CONT_PREV)
505 		--result;
506 	}
507     return result;
508 }
509 
510 /*
511  * Find text property "type_id" in the visible lines of window "wp".
512  * Match "id" when it is > 0.
513  * Returns FAIL when not found.
514  */
515     int
find_visible_prop(win_T * wp,int type_id,int id,textprop_T * prop,linenr_T * found_lnum)516 find_visible_prop(win_T *wp, int type_id, int id, textprop_T *prop,
517 							  linenr_T *found_lnum)
518 {
519     linenr_T		lnum;
520     char_u		*props;
521     int			count;
522     int			i;
523 
524     // w_botline may not have been updated yet.
525     validate_botline_win(wp);
526     for (lnum = wp->w_topline; lnum < wp->w_botline; ++lnum)
527     {
528 	count = get_text_props(wp->w_buffer, lnum, &props, FALSE);
529 	for (i = 0; i < count; ++i)
530 	{
531 	    mch_memmove(prop, props + i * sizeof(textprop_T),
532 							   sizeof(textprop_T));
533 	    if (prop->tp_type == type_id && (id <= 0 || prop->tp_id == id))
534 	    {
535 		*found_lnum = lnum;
536 		return OK;
537 	    }
538 	}
539     }
540     return FAIL;
541 }
542 
543 /*
544  * Set the text properties for line "lnum" to "props" with length "len".
545  * If "len" is zero text properties are removed, "props" is not used.
546  * Any existing text properties are dropped.
547  * Only works for the current buffer.
548  */
549     static void
set_text_props(linenr_T lnum,char_u * props,int len)550 set_text_props(linenr_T lnum, char_u *props, int len)
551 {
552     char_u  *text;
553     char_u  *newtext;
554     int	    textlen;
555 
556     text = ml_get(lnum);
557     textlen = (int)STRLEN(text) + 1;
558     newtext = alloc(textlen + len);
559     if (newtext == NULL)
560 	return;
561     mch_memmove(newtext, text, textlen);
562     if (len > 0)
563 	mch_memmove(newtext + textlen, props, len);
564     if (curbuf->b_ml.ml_flags & ML_LINE_DIRTY)
565 	vim_free(curbuf->b_ml.ml_line_ptr);
566     curbuf->b_ml.ml_line_ptr = newtext;
567     curbuf->b_ml.ml_line_len = textlen + len;
568     curbuf->b_ml.ml_flags |= ML_LINE_DIRTY;
569 }
570 
571     static proptype_T *
find_type_by_id(hashtab_T * ht,int id)572 find_type_by_id(hashtab_T *ht, int id)
573 {
574     long	todo;
575     hashitem_T	*hi;
576 
577     if (ht == NULL)
578 	return NULL;
579 
580     // TODO: Make this faster by keeping a list of types sorted on ID and use
581     // a binary search.
582 
583     todo = (long)ht->ht_used;
584     for (hi = ht->ht_array; todo > 0; ++hi)
585     {
586 	if (!HASHITEM_EMPTY(hi))
587 	{
588 	    proptype_T *prop = HI2PT(hi);
589 
590 	    if (prop->pt_id == id)
591 		return prop;
592 	    --todo;
593 	}
594     }
595     return NULL;
596 }
597 
598 /*
599  * Fill 'dict' with text properties in 'prop'.
600  */
601     static void
prop_fill_dict(dict_T * dict,textprop_T * prop,buf_T * buf)602 prop_fill_dict(dict_T *dict, textprop_T *prop, buf_T *buf)
603 {
604     proptype_T *pt;
605     int buflocal = TRUE;
606 
607     dict_add_number(dict, "col", prop->tp_col);
608     dict_add_number(dict, "length", prop->tp_len);
609     dict_add_number(dict, "id", prop->tp_id);
610     dict_add_number(dict, "start", !(prop->tp_flags & TP_FLAG_CONT_PREV));
611     dict_add_number(dict, "end", !(prop->tp_flags & TP_FLAG_CONT_NEXT));
612 
613     pt = find_type_by_id(buf->b_proptypes, prop->tp_type);
614     if (pt == NULL)
615     {
616 	pt = find_type_by_id(global_proptypes, prop->tp_type);
617 	buflocal = FALSE;
618     }
619     if (pt != NULL)
620 	dict_add_string(dict, "type", pt->pt_name);
621 
622     if (buflocal)
623 	dict_add_number(dict, "type_bufnr", buf->b_fnum);
624     else
625 	dict_add_number(dict, "type_bufnr", 0);
626 }
627 
628 /*
629  * Find a property type by ID in "buf" or globally.
630  * Returns NULL if not found.
631  */
632     proptype_T *
text_prop_type_by_id(buf_T * buf,int id)633 text_prop_type_by_id(buf_T *buf, int id)
634 {
635     proptype_T *type;
636 
637     type = find_type_by_id(buf->b_proptypes, id);
638     if (type == NULL)
639 	type = find_type_by_id(global_proptypes, id);
640     return type;
641 }
642 
643 /*
644  * prop_clear({lnum} [, {lnum_end} [, {bufnr}]])
645  */
646     void
f_prop_clear(typval_T * argvars,typval_T * rettv UNUSED)647 f_prop_clear(typval_T *argvars, typval_T *rettv UNUSED)
648 {
649     linenr_T start;
650     linenr_T end;
651     linenr_T lnum;
652     buf_T    *buf = curbuf;
653     int	    did_clear = FALSE;
654 
655     if (in_vim9script()
656 	    && (check_for_number_arg(argvars, 0) == FAIL
657 		|| check_for_opt_number_arg(argvars, 1) == FAIL
658 		|| (argvars[1].v_type != VAR_UNKNOWN
659 		    && check_for_opt_dict_arg(argvars, 2) == FAIL)))
660 	return;
661 
662     start = tv_get_number(&argvars[0]);
663     end = start;
664     if (argvars[1].v_type != VAR_UNKNOWN)
665     {
666 	end = tv_get_number(&argvars[1]);
667 	if (argvars[2].v_type != VAR_UNKNOWN)
668 	{
669 	    if (get_bufnr_from_arg(&argvars[2], &buf) == FAIL)
670 		return;
671 	}
672     }
673     if (start < 1 || end < 1)
674     {
675 	emsg(_(e_invalid_range));
676 	return;
677     }
678 
679     for (lnum = start; lnum <= end; ++lnum)
680     {
681 	char_u *text;
682 	size_t len;
683 
684 	if (lnum > buf->b_ml.ml_line_count)
685 	    break;
686 	text = ml_get_buf(buf, lnum, FALSE);
687 	len = STRLEN(text) + 1;
688 	if ((size_t)buf->b_ml.ml_line_len > len)
689 	{
690 	    did_clear = TRUE;
691 	    if (!(buf->b_ml.ml_flags & ML_LINE_DIRTY))
692 	    {
693 		char_u *newtext = vim_strsave(text);
694 
695 		// need to allocate the line now
696 		if (newtext == NULL)
697 		    return;
698 		buf->b_ml.ml_line_ptr = newtext;
699 		buf->b_ml.ml_flags |= ML_LINE_DIRTY;
700 	    }
701 	    buf->b_ml.ml_line_len = (int)len;
702 	}
703     }
704     if (did_clear)
705 	redraw_buf_later(buf, NOT_VALID);
706 }
707 
708 /*
709  * prop_find({props} [, {direction}])
710  */
711     void
f_prop_find(typval_T * argvars,typval_T * rettv)712 f_prop_find(typval_T *argvars, typval_T *rettv)
713 {
714     pos_T       *cursor = &curwin->w_cursor;
715     dict_T      *dict;
716     buf_T       *buf = curbuf;
717     dictitem_T  *di;
718     int		lnum_start;
719     int		start_pos_has_prop = 0;
720     int		seen_end = 0;
721     int		id = 0;
722     int		id_found = FALSE;
723     int		type_id = -1;
724     int		skipstart = 0;
725     int		lnum = -1;
726     int		col = -1;
727     int		dir = 1;    // 1 = forward, -1 = backward
728     int		both;
729 
730     if (in_vim9script()
731 	    && (check_for_dict_arg(argvars, 0) == FAIL
732 		|| check_for_opt_string_arg(argvars, 1) == FAIL))
733 	return;
734 
735     if (argvars[0].v_type != VAR_DICT || argvars[0].vval.v_dict == NULL)
736     {
737 	emsg(_(e_dictreq));
738 	return;
739     }
740     dict = argvars[0].vval.v_dict;
741 
742     if (get_bufnr_from_arg(&argvars[0], &buf) == FAIL)
743 	return;
744     if (buf->b_ml.ml_mfp == NULL)
745 	return;
746 
747     if (argvars[1].v_type != VAR_UNKNOWN)
748     {
749 	char_u      *dir_s = tv_get_string(&argvars[1]);
750 
751 	if (*dir_s == 'b')
752 	    dir = -1;
753 	else if (*dir_s != 'f')
754 	{
755 	    emsg(_(e_invarg));
756 	    return;
757 	}
758     }
759 
760     di = dict_find(dict, (char_u *)"lnum", -1);
761     if (di != NULL)
762 	lnum = tv_get_number(&di->di_tv);
763 
764     di = dict_find(dict, (char_u *)"col", -1);
765     if (di != NULL)
766 	col = tv_get_number(&di->di_tv);
767 
768     if (lnum == -1)
769     {
770 	lnum = cursor->lnum;
771 	col = cursor->col + 1;
772     }
773     else if (col == -1)
774 	col = 1;
775 
776     if (lnum < 1 || lnum > buf->b_ml.ml_line_count)
777     {
778 	emsg(_(e_invalid_range));
779 	return;
780     }
781 
782     skipstart = dict_get_bool(dict, (char_u *)"skipstart", 0);
783 
784     if (dict_find(dict, (char_u *)"id", -1) != NULL)
785     {
786 	id = dict_get_number(dict, (char_u *)"id");
787 	id_found = TRUE;
788     }
789     if (dict_find(dict, (char_u *)"type", -1))
790     {
791 	char_u	    *name = dict_get_string(dict, (char_u *)"type", FALSE);
792 	proptype_T  *type = lookup_prop_type(name, buf);
793 
794 	if (type == NULL)
795 	    return;
796 	type_id = type->pt_id;
797     }
798     both = dict_get_bool(dict, (char_u *)"both", FALSE);
799     if (!id_found && type_id == -1)
800     {
801 	emsg(_("E968: Need at least one of 'id' or 'type'"));
802 	return;
803     }
804     if (both && (!id_found || type_id == -1))
805     {
806 	emsg(_("E860: Need 'id' and 'type' with 'both'"));
807 	return;
808     }
809 
810     lnum_start = lnum;
811 
812     if (rettv_dict_alloc(rettv) == FAIL)
813 	return;
814 
815     while (1)
816     {
817 	char_u	*text = ml_get_buf(buf, lnum, FALSE);
818 	size_t	textlen = STRLEN(text) + 1;
819 	int	count = (int)((buf->b_ml.ml_line_len - textlen)
820 							 / sizeof(textprop_T));
821 	int	    i;
822 	textprop_T  prop;
823 	int	    prop_start;
824 	int	    prop_end;
825 
826 	for (i = 0; i < count; ++i)
827 	{
828 	    mch_memmove(&prop, text + textlen + i * sizeof(textprop_T),
829 			    sizeof(textprop_T));
830 
831 	    if (lnum == lnum_start)
832 	    {
833 		if (dir < 0)
834 		{
835 		    if (col < prop.tp_col)
836 			break;
837 		}
838 		else if (prop.tp_col + prop.tp_len - (prop.tp_len != 0) < col)
839 		    continue;
840 	    }
841 	    if (both ? prop.tp_id == id && prop.tp_type == type_id
842 		     : (id_found && prop.tp_id == id)
843 						    || prop.tp_type == type_id)
844 	    {
845 		// Check if the starting position has text props.
846 		if (lnum_start == lnum
847 			&& col >= prop.tp_col
848 			&& (col <= prop.tp_col + prop.tp_len
849 							 - (prop.tp_len != 0)))
850 		    start_pos_has_prop = 1;
851 
852 		prop_start = !(prop.tp_flags & TP_FLAG_CONT_PREV);
853 		prop_end = !(prop.tp_flags & TP_FLAG_CONT_NEXT);
854 		if (!prop_start && prop_end && dir > 0)
855 		    seen_end = 1;
856 
857 		// Skip lines without the start flag.
858 		if (!prop_start)
859 		{
860 		    // Always search backwards for start when search started
861 		    // on a prop and we're not skipping.
862 		    if (start_pos_has_prop && !skipstart)
863 			dir = -1;
864 		    continue;
865 		}
866 
867 		// If skipstart is true, skip the prop at start pos (even if
868 		// continued from another line).
869 		if (start_pos_has_prop && skipstart && !seen_end)
870 		{
871 		    start_pos_has_prop = 0;
872 		    continue;
873 		}
874 
875 		prop_fill_dict(rettv->vval.v_dict, &prop, buf);
876 		dict_add_number(rettv->vval.v_dict, "lnum", lnum);
877 
878 		return;
879 	    }
880 	}
881 
882 	if (dir > 0)
883 	{
884 	    if (lnum >= buf->b_ml.ml_line_count)
885 		break;
886 	    lnum++;
887 	}
888 	else
889 	{
890 	    if (lnum <= 1)
891 		break;
892 	    lnum--;
893 	}
894 	// Adjust col to indicate that we're continuing from prev/next line.
895 	col = dir < 0 ? buf->b_ml.ml_line_len : 1;
896     }
897 }
898 
899 /*
900  * prop_list({lnum} [, {bufnr}])
901  */
902     void
f_prop_list(typval_T * argvars,typval_T * rettv)903 f_prop_list(typval_T *argvars, typval_T *rettv)
904 {
905     linenr_T lnum;
906     buf_T    *buf = curbuf;
907 
908     if (in_vim9script()
909 	    && (check_for_number_arg(argvars, 0) == FAIL
910 		|| check_for_opt_dict_arg(argvars, 1) == FAIL))
911 	return;
912 
913     lnum = tv_get_number(&argvars[0]);
914     if (argvars[1].v_type != VAR_UNKNOWN)
915     {
916 	if (get_bufnr_from_arg(&argvars[1], &buf) == FAIL)
917 	    return;
918     }
919     if (lnum < 1 || lnum > buf->b_ml.ml_line_count)
920     {
921 	emsg(_(e_invalid_range));
922 	return;
923     }
924 
925     if (rettv_list_alloc(rettv) == OK)
926     {
927 	char_u	    *text = ml_get_buf(buf, lnum, FALSE);
928 	size_t	    textlen = STRLEN(text) + 1;
929 	int	    count = (int)((buf->b_ml.ml_line_len - textlen)
930 							 / sizeof(textprop_T));
931 	int	    i;
932 	textprop_T  prop;
933 
934 	for (i = 0; i < count; ++i)
935 	{
936 	    dict_T *d = dict_alloc();
937 
938 	    if (d == NULL)
939 		break;
940 	    mch_memmove(&prop, text + textlen + i * sizeof(textprop_T),
941 							   sizeof(textprop_T));
942 	    prop_fill_dict(d, &prop, buf);
943 	    list_append_dict(rettv->vval.v_list, d);
944 	}
945     }
946 }
947 
948 /*
949  * prop_remove({props} [, {lnum} [, {lnum_end}]])
950  */
951     void
f_prop_remove(typval_T * argvars,typval_T * rettv)952 f_prop_remove(typval_T *argvars, typval_T *rettv)
953 {
954     linenr_T	start = 1;
955     linenr_T	end = 0;
956     linenr_T	lnum;
957     linenr_T	first_changed = 0;
958     linenr_T	last_changed = 0;
959     dict_T	*dict;
960     buf_T	*buf = curbuf;
961     int		do_all;
962     int		id = -1;
963     int		type_id = -1;
964     int		both;
965 
966     rettv->vval.v_number = 0;
967 
968     if (in_vim9script()
969 	    && (check_for_dict_arg(argvars, 0) == FAIL
970 		|| check_for_opt_number_arg(argvars, 1) == FAIL
971 		|| (argvars[1].v_type != VAR_UNKNOWN
972 		    && check_for_opt_number_arg(argvars, 2) == FAIL)))
973 	return;
974 
975     if (argvars[0].v_type != VAR_DICT || argvars[0].vval.v_dict == NULL)
976     {
977 	emsg(_(e_invarg));
978 	return;
979     }
980 
981     if (argvars[1].v_type != VAR_UNKNOWN)
982     {
983 	start = tv_get_number(&argvars[1]);
984 	end = start;
985 	if (argvars[2].v_type != VAR_UNKNOWN)
986 	    end = tv_get_number(&argvars[2]);
987 	if (start < 1 || end < 1)
988 	{
989 	    emsg(_(e_invalid_range));
990 	    return;
991 	}
992     }
993 
994     dict = argvars[0].vval.v_dict;
995     if (get_bufnr_from_arg(&argvars[0], &buf) == FAIL)
996 	return;
997     if (buf->b_ml.ml_mfp == NULL)
998 	return;
999 
1000     do_all = dict_get_bool(dict, (char_u *)"all", FALSE);
1001 
1002     if (dict_find(dict, (char_u *)"id", -1) != NULL)
1003 	id = dict_get_number(dict, (char_u *)"id");
1004     if (dict_find(dict, (char_u *)"type", -1))
1005     {
1006 	char_u	    *name = dict_get_string(dict, (char_u *)"type", FALSE);
1007 	proptype_T  *type = lookup_prop_type(name, buf);
1008 
1009 	if (type == NULL)
1010 	    return;
1011 	type_id = type->pt_id;
1012     }
1013     both = dict_get_bool(dict, (char_u *)"both", FALSE);
1014 
1015     if (id == -1 && type_id == -1)
1016     {
1017 	emsg(_("E968: Need at least one of 'id' or 'type'"));
1018 	return;
1019     }
1020     if (both && (id == -1 || type_id == -1))
1021     {
1022 	emsg(_("E860: Need 'id' and 'type' with 'both'"));
1023 	return;
1024     }
1025 
1026     if (end == 0)
1027 	end = buf->b_ml.ml_line_count;
1028     for (lnum = start; lnum <= end; ++lnum)
1029     {
1030 	char_u *text;
1031 	size_t len;
1032 
1033 	if (lnum > buf->b_ml.ml_line_count)
1034 	    break;
1035 	text = ml_get_buf(buf, lnum, FALSE);
1036 	len = STRLEN(text) + 1;
1037 	if ((size_t)buf->b_ml.ml_line_len > len)
1038 	{
1039 	    static textprop_T	textprop;  // static because of alignment
1040 	    unsigned		idx;
1041 
1042 	    for (idx = 0; idx < (buf->b_ml.ml_line_len - len)
1043 						   / sizeof(textprop_T); ++idx)
1044 	    {
1045 		char_u *cur_prop = buf->b_ml.ml_line_ptr + len
1046 						    + idx * sizeof(textprop_T);
1047 		size_t	taillen;
1048 
1049 		mch_memmove(&textprop, cur_prop, sizeof(textprop_T));
1050 		if (both ? textprop.tp_id == id && textprop.tp_type == type_id
1051 			 : textprop.tp_id == id || textprop.tp_type == type_id)
1052 		{
1053 		    if (!(buf->b_ml.ml_flags & ML_LINE_DIRTY))
1054 		    {
1055 			char_u *newptr = alloc(buf->b_ml.ml_line_len);
1056 
1057 			// need to allocate the line to be able to change it
1058 			if (newptr == NULL)
1059 			    return;
1060 			mch_memmove(newptr, buf->b_ml.ml_line_ptr,
1061 							buf->b_ml.ml_line_len);
1062 			buf->b_ml.ml_line_ptr = newptr;
1063 			buf->b_ml.ml_flags |= ML_LINE_DIRTY;
1064 
1065 			cur_prop = buf->b_ml.ml_line_ptr + len
1066 						    + idx * sizeof(textprop_T);
1067 		    }
1068 
1069 		    taillen = buf->b_ml.ml_line_len - len
1070 					      - (idx + 1) * sizeof(textprop_T);
1071 		    if (taillen > 0)
1072 			mch_memmove(cur_prop, cur_prop + sizeof(textprop_T),
1073 								      taillen);
1074 		    buf->b_ml.ml_line_len -= sizeof(textprop_T);
1075 		    --idx;
1076 
1077 		    if (first_changed == 0)
1078 			first_changed = lnum;
1079 		    last_changed = lnum;
1080 		    ++rettv->vval.v_number;
1081 		    if (!do_all)
1082 			break;
1083 		}
1084 	    }
1085 	}
1086     }
1087     if (first_changed > 0)
1088     {
1089 	changed_lines_buf(buf, first_changed, last_changed + 1, 0);
1090 	redraw_buf_later(buf, VALID);
1091     }
1092 }
1093 
1094 /*
1095  * Common for f_prop_type_add() and f_prop_type_change().
1096  */
1097     static void
prop_type_set(typval_T * argvars,int add)1098 prop_type_set(typval_T *argvars, int add)
1099 {
1100     char_u	*name;
1101     buf_T	*buf = NULL;
1102     dict_T	*dict;
1103     dictitem_T  *di;
1104     proptype_T	*prop;
1105 
1106     if (in_vim9script()
1107 	    && (check_for_string_arg(argvars, 0) == FAIL
1108 		|| check_for_dict_arg(argvars, 1) == FAIL))
1109 	return;
1110 
1111     name = tv_get_string(&argvars[0]);
1112     if (*name == NUL)
1113     {
1114 	emsg(_(e_invarg));
1115 	return;
1116     }
1117 
1118     if (get_bufnr_from_arg(&argvars[1], &buf) == FAIL)
1119 	return;
1120     dict = argvars[1].vval.v_dict;
1121 
1122     prop = find_prop(name, buf);
1123     if (add)
1124     {
1125 	hashtab_T **htp;
1126 
1127 	if (prop != NULL)
1128 	{
1129 	    semsg(_("E969: Property type %s already defined"), name);
1130 	    return;
1131 	}
1132 	prop = alloc_clear(offsetof(proptype_T, pt_name) + STRLEN(name) + 1);
1133 	if (prop == NULL)
1134 	    return;
1135 	STRCPY(prop->pt_name, name);
1136 	prop->pt_id = ++proptype_id;
1137 	prop->pt_flags = PT_FLAG_COMBINE;
1138 	htp = buf == NULL ? &global_proptypes : &buf->b_proptypes;
1139 	if (*htp == NULL)
1140 	{
1141 	    *htp = ALLOC_ONE(hashtab_T);
1142 	    if (*htp == NULL)
1143 	    {
1144 		vim_free(prop);
1145 		return;
1146 	    }
1147 	    hash_init(*htp);
1148 	}
1149 	hash_add(*htp, PT2HIKEY(prop));
1150     }
1151     else
1152     {
1153 	if (prop == NULL)
1154 	{
1155 	    semsg(_(e_type_not_exist), name);
1156 	    return;
1157 	}
1158     }
1159 
1160     if (dict != NULL)
1161     {
1162 	di = dict_find(dict, (char_u *)"highlight", -1);
1163 	if (di != NULL)
1164 	{
1165 	    char_u	*highlight;
1166 	    int		hl_id = 0;
1167 
1168 	    highlight = dict_get_string(dict, (char_u *)"highlight", FALSE);
1169 	    if (highlight != NULL && *highlight != NUL)
1170 		hl_id = syn_name2id(highlight);
1171 	    if (hl_id <= 0)
1172 	    {
1173 		semsg(_("E970: Unknown highlight group name: '%s'"),
1174 			highlight == NULL ? (char_u *)"" : highlight);
1175 		return;
1176 	    }
1177 	    prop->pt_hl_id = hl_id;
1178 	}
1179 
1180 	di = dict_find(dict, (char_u *)"combine", -1);
1181 	if (di != NULL)
1182 	{
1183 	    if (tv_get_bool(&di->di_tv))
1184 		prop->pt_flags |= PT_FLAG_COMBINE;
1185 	    else
1186 		prop->pt_flags &= ~PT_FLAG_COMBINE;
1187 	}
1188 
1189 	di = dict_find(dict, (char_u *)"priority", -1);
1190 	if (di != NULL)
1191 	    prop->pt_priority = tv_get_number(&di->di_tv);
1192 
1193 	di = dict_find(dict, (char_u *)"start_incl", -1);
1194 	if (di != NULL)
1195 	{
1196 	    if (tv_get_bool(&di->di_tv))
1197 		prop->pt_flags |= PT_FLAG_INS_START_INCL;
1198 	    else
1199 		prop->pt_flags &= ~PT_FLAG_INS_START_INCL;
1200 	}
1201 
1202 	di = dict_find(dict, (char_u *)"end_incl", -1);
1203 	if (di != NULL)
1204 	{
1205 	    if (tv_get_bool(&di->di_tv))
1206 		prop->pt_flags |= PT_FLAG_INS_END_INCL;
1207 	    else
1208 		prop->pt_flags &= ~PT_FLAG_INS_END_INCL;
1209 	}
1210     }
1211 }
1212 
1213 /*
1214  * prop_type_add({name}, {props})
1215  */
1216     void
f_prop_type_add(typval_T * argvars,typval_T * rettv UNUSED)1217 f_prop_type_add(typval_T *argvars, typval_T *rettv UNUSED)
1218 {
1219     prop_type_set(argvars, TRUE);
1220 }
1221 
1222 /*
1223  * prop_type_change({name}, {props})
1224  */
1225     void
f_prop_type_change(typval_T * argvars,typval_T * rettv UNUSED)1226 f_prop_type_change(typval_T *argvars, typval_T *rettv UNUSED)
1227 {
1228     prop_type_set(argvars, FALSE);
1229 }
1230 
1231 /*
1232  * prop_type_delete({name} [, {bufnr}])
1233  */
1234     void
f_prop_type_delete(typval_T * argvars,typval_T * rettv UNUSED)1235 f_prop_type_delete(typval_T *argvars, typval_T *rettv UNUSED)
1236 {
1237     char_u	*name;
1238     buf_T	*buf = NULL;
1239     hashitem_T	*hi;
1240 
1241     if (in_vim9script()
1242 	    && (check_for_string_arg(argvars, 0) == FAIL
1243 		|| check_for_opt_dict_arg(argvars, 1) == FAIL))
1244 	return;
1245 
1246     name = tv_get_string(&argvars[0]);
1247     if (*name == NUL)
1248     {
1249 	emsg(_(e_invarg));
1250 	return;
1251     }
1252 
1253     if (argvars[1].v_type != VAR_UNKNOWN)
1254     {
1255 	if (get_bufnr_from_arg(&argvars[1], &buf) == FAIL)
1256 	    return;
1257     }
1258 
1259     hi = find_prop_hi(name, buf);
1260     if (hi != NULL)
1261     {
1262 	hashtab_T	*ht;
1263 	proptype_T	*prop = HI2PT(hi);
1264 
1265 	if (buf == NULL)
1266 	    ht = global_proptypes;
1267 	else
1268 	    ht = buf->b_proptypes;
1269 	hash_remove(ht, hi);
1270 	vim_free(prop);
1271     }
1272 }
1273 
1274 /*
1275  * prop_type_get({name} [, {props}])
1276  */
1277     void
f_prop_type_get(typval_T * argvars,typval_T * rettv)1278 f_prop_type_get(typval_T *argvars, typval_T *rettv)
1279 {
1280     char_u *name;
1281 
1282     if (in_vim9script()
1283 	    && (check_for_string_arg(argvars, 0) == FAIL
1284 		|| check_for_opt_dict_arg(argvars, 1) == FAIL))
1285 	return;
1286 
1287     name = tv_get_string(&argvars[0]);
1288     if (*name == NUL)
1289     {
1290 	emsg(_(e_invarg));
1291 	return;
1292     }
1293     if (rettv_dict_alloc(rettv) == OK)
1294     {
1295 	proptype_T  *prop = NULL;
1296 	buf_T	    *buf = NULL;
1297 
1298 	if (argvars[1].v_type != VAR_UNKNOWN)
1299 	{
1300 	    if (get_bufnr_from_arg(&argvars[1], &buf) == FAIL)
1301 		return;
1302 	}
1303 
1304 	prop = find_prop(name, buf);
1305 	if (prop != NULL)
1306 	{
1307 	    dict_T *d = rettv->vval.v_dict;
1308 
1309 	    if (prop->pt_hl_id > 0)
1310 		dict_add_string(d, "highlight", syn_id2name(prop->pt_hl_id));
1311 	    dict_add_number(d, "priority", prop->pt_priority);
1312 	    dict_add_number(d, "combine",
1313 				   (prop->pt_flags & PT_FLAG_COMBINE) ? 1 : 0);
1314 	    dict_add_number(d, "start_incl",
1315 			    (prop->pt_flags & PT_FLAG_INS_START_INCL) ? 1 : 0);
1316 	    dict_add_number(d, "end_incl",
1317 			      (prop->pt_flags & PT_FLAG_INS_END_INCL) ? 1 : 0);
1318 	    if (buf != NULL)
1319 		dict_add_number(d, "bufnr", buf->b_fnum);
1320 	}
1321     }
1322 }
1323 
1324     static void
list_types(hashtab_T * ht,list_T * l)1325 list_types(hashtab_T *ht, list_T *l)
1326 {
1327     long	todo;
1328     hashitem_T	*hi;
1329 
1330     todo = (long)ht->ht_used;
1331     for (hi = ht->ht_array; todo > 0; ++hi)
1332     {
1333 	if (!HASHITEM_EMPTY(hi))
1334 	{
1335 	    proptype_T *prop = HI2PT(hi);
1336 
1337 	    list_append_string(l, prop->pt_name, -1);
1338 	    --todo;
1339 	}
1340     }
1341 }
1342 
1343 /*
1344  * prop_type_list([{bufnr}])
1345  */
1346     void
f_prop_type_list(typval_T * argvars,typval_T * rettv UNUSED)1347 f_prop_type_list(typval_T *argvars, typval_T *rettv UNUSED)
1348 {
1349     buf_T *buf = NULL;
1350 
1351     if (rettv_list_alloc(rettv) == OK)
1352     {
1353 	if (in_vim9script() && check_for_opt_dict_arg(argvars, 0) == FAIL)
1354 	    return;
1355 
1356 	if (argvars[0].v_type != VAR_UNKNOWN)
1357 	{
1358 	    if (get_bufnr_from_arg(&argvars[0], &buf) == FAIL)
1359 		return;
1360 	}
1361 	if (buf == NULL)
1362 	{
1363 	    if (global_proptypes != NULL)
1364 		list_types(global_proptypes, rettv->vval.v_list);
1365 	}
1366 	else if (buf->b_proptypes != NULL)
1367 	    list_types(buf->b_proptypes, rettv->vval.v_list);
1368     }
1369 }
1370 
1371 /*
1372  * Free all property types in "ht".
1373  */
1374     static void
clear_ht_prop_types(hashtab_T * ht)1375 clear_ht_prop_types(hashtab_T *ht)
1376 {
1377     long	todo;
1378     hashitem_T	*hi;
1379 
1380     if (ht == NULL)
1381 	return;
1382 
1383     todo = (long)ht->ht_used;
1384     for (hi = ht->ht_array; todo > 0; ++hi)
1385     {
1386 	if (!HASHITEM_EMPTY(hi))
1387 	{
1388 	    proptype_T *prop = HI2PT(hi);
1389 
1390 	    vim_free(prop);
1391 	    --todo;
1392 	}
1393     }
1394 
1395     hash_clear(ht);
1396     vim_free(ht);
1397 }
1398 
1399 #if defined(EXITFREE) || defined(PROTO)
1400 /*
1401  * Free all global property types.
1402  */
1403     void
clear_global_prop_types(void)1404 clear_global_prop_types(void)
1405 {
1406     clear_ht_prop_types(global_proptypes);
1407     global_proptypes = NULL;
1408 }
1409 #endif
1410 
1411 /*
1412  * Free all property types for "buf".
1413  */
1414     void
clear_buf_prop_types(buf_T * buf)1415 clear_buf_prop_types(buf_T *buf)
1416 {
1417     clear_ht_prop_types(buf->b_proptypes);
1418     buf->b_proptypes = NULL;
1419 }
1420 
1421 // Struct used to return two values from adjust_prop().
1422 typedef struct
1423 {
1424     int dirty;	    // if the property was changed
1425     int can_drop;   // whether after this change, the prop may be removed
1426 } adjustres_T;
1427 
1428 /*
1429  * Adjust the property for "added" bytes (can be negative) inserted at "col".
1430  *
1431  * Note that "col" is zero-based, while tp_col is one-based.
1432  * Only for the current buffer.
1433  * "flags" can have:
1434  * APC_SUBSTITUTE:	Text is replaced, not inserted.
1435  */
1436     static adjustres_T
adjust_prop(textprop_T * prop,colnr_T col,int added,int flags)1437 adjust_prop(
1438 	textprop_T *prop,
1439 	colnr_T col,
1440 	int added,
1441 	int flags)
1442 {
1443     proptype_T	*pt = text_prop_type_by_id(curbuf, prop->tp_type);
1444     int		start_incl = (pt != NULL
1445 				    && (pt->pt_flags & PT_FLAG_INS_START_INCL))
1446 						   || (flags & APC_SUBSTITUTE);
1447     int		end_incl = (pt != NULL
1448 				     && (pt->pt_flags & PT_FLAG_INS_END_INCL));
1449 		// Do not drop zero-width props if they later can increase in
1450 		// size.
1451     int		droppable = !(start_incl || end_incl);
1452     adjustres_T res = {TRUE, FALSE};
1453 
1454     if (added > 0)
1455     {
1456 	if (col + 1 <= prop->tp_col
1457 		- (start_incl || (prop->tp_len == 0 && end_incl)))
1458 	    // Change is entirely before the text property: Only shift
1459 	    prop->tp_col += added;
1460 	else if (col + 1 < prop->tp_col + prop->tp_len + end_incl)
1461 	    // Insertion was inside text property
1462 	    prop->tp_len += added;
1463     }
1464     else if (prop->tp_col > col + 1)
1465     {
1466 	if (prop->tp_col + added < col + 1)
1467 	{
1468 	    prop->tp_len += (prop->tp_col - 1 - col) + added;
1469 	    prop->tp_col = col + 1;
1470 	    if (prop->tp_len <= 0)
1471 	    {
1472 		prop->tp_len = 0;
1473 		res.can_drop = droppable;
1474 	    }
1475 	}
1476 	else
1477 	    prop->tp_col += added;
1478     }
1479     else if (prop->tp_len > 0 && prop->tp_col + prop->tp_len > col)
1480     {
1481 	int after = col - added - (prop->tp_col - 1 + prop->tp_len);
1482 
1483 	prop->tp_len += after > 0 ? added + after : added;
1484 	res.can_drop = prop->tp_len <= 0 && droppable;
1485     }
1486     else
1487 	res.dirty = FALSE;
1488 
1489     return res;
1490 }
1491 
1492 /*
1493  * Adjust the columns of text properties in line "lnum" after position "col" to
1494  * shift by "bytes_added" (can be negative).
1495  * Note that "col" is zero-based, while tp_col is one-based.
1496  * Only for the current buffer.
1497  * "flags" can have:
1498  * APC_SAVE_FOR_UNDO:	Call u_savesub() before making changes to the line.
1499  * APC_SUBSTITUTE:	Text is replaced, not inserted.
1500  * Caller is expected to check b_has_textprop and "bytes_added" being non-zero.
1501  * Returns TRUE when props were changed.
1502  */
1503     int
adjust_prop_columns(linenr_T lnum,colnr_T col,int bytes_added,int flags)1504 adjust_prop_columns(
1505 	linenr_T    lnum,
1506 	colnr_T	    col,
1507 	int	    bytes_added,
1508 	int	    flags)
1509 {
1510     int		proplen;
1511     char_u	*props;
1512     int		dirty = FALSE;
1513     int		ri, wi;
1514     size_t	textlen;
1515 
1516     if (text_prop_frozen > 0)
1517 	return FALSE;
1518 
1519     proplen = get_text_props(curbuf, lnum, &props, TRUE);
1520     if (proplen == 0)
1521 	return FALSE;
1522     textlen = curbuf->b_ml.ml_line_len - proplen * sizeof(textprop_T);
1523 
1524     wi = 0; // write index
1525     for (ri = 0; ri < proplen; ++ri)
1526     {
1527 	textprop_T	prop;
1528 	adjustres_T	res;
1529 
1530 	mch_memmove(&prop, props + ri * sizeof(prop), sizeof(prop));
1531 	res = adjust_prop(&prop, col, bytes_added, flags);
1532 	if (res.dirty)
1533 	{
1534 	    // Save for undo if requested and not done yet.
1535 	    if ((flags & APC_SAVE_FOR_UNDO) && !dirty
1536 						    && u_savesub(lnum) == FAIL)
1537 		return FALSE;
1538 	    dirty = TRUE;
1539 
1540 	    // u_savesub() may have updated curbuf->b_ml, fetch it again
1541 	    if (curbuf->b_ml.ml_line_lnum != lnum)
1542 		proplen = get_text_props(curbuf, lnum, &props, TRUE);
1543 	}
1544 	if (res.can_drop)
1545 	    continue; // Drop this text property
1546 	mch_memmove(props + wi * sizeof(textprop_T), &prop, sizeof(textprop_T));
1547 	++wi;
1548     }
1549     if (dirty)
1550     {
1551 	colnr_T newlen = (int)textlen + wi * (colnr_T)sizeof(textprop_T);
1552 
1553 	if ((curbuf->b_ml.ml_flags & ML_LINE_DIRTY) == 0)
1554 	    curbuf->b_ml.ml_line_ptr =
1555 				 vim_memsave(curbuf->b_ml.ml_line_ptr, newlen);
1556 	curbuf->b_ml.ml_flags |= ML_LINE_DIRTY;
1557 	curbuf->b_ml.ml_line_len = newlen;
1558     }
1559     return dirty;
1560 }
1561 
1562 /*
1563  * Adjust text properties for a line that was split in two.
1564  * "lnum_props" is the line that has the properties from before the split.
1565  * "lnum_top" is the top line.
1566  * "kept" is the number of bytes kept in the first line, while
1567  * "deleted" is the number of bytes deleted.
1568  */
1569     void
adjust_props_for_split(linenr_T lnum_props,linenr_T lnum_top,int kept,int deleted)1570 adjust_props_for_split(
1571 	linenr_T lnum_props,
1572 	linenr_T lnum_top,
1573 	int kept,
1574 	int deleted)
1575 {
1576     char_u	*props;
1577     int		count;
1578     garray_T    prevprop;
1579     garray_T    nextprop;
1580     int		i;
1581     int		skipped = kept + deleted;
1582 
1583     if (!curbuf->b_has_textprop)
1584 	return;
1585 
1586     // Get the text properties from "lnum_props".
1587     count = get_text_props(curbuf, lnum_props, &props, FALSE);
1588     ga_init2(&prevprop, sizeof(textprop_T), 10);
1589     ga_init2(&nextprop, sizeof(textprop_T), 10);
1590 
1591     // Keep the relevant ones in the first line, reducing the length if needed.
1592     // Copy the ones that include the split to the second line.
1593     // Move the ones after the split to the second line.
1594     for (i = 0; i < count; ++i)
1595     {
1596 	textprop_T  prop;
1597 	proptype_T *pt;
1598 	int	    start_incl, end_incl;
1599 	int	    cont_prev, cont_next;
1600 
1601 	// copy the prop to an aligned structure
1602 	mch_memmove(&prop, props + i * sizeof(textprop_T), sizeof(textprop_T));
1603 
1604 	pt = text_prop_type_by_id(curbuf, prop.tp_type);
1605 	start_incl = (pt != NULL && (pt->pt_flags & PT_FLAG_INS_START_INCL));
1606 	end_incl = (pt != NULL && (pt->pt_flags & PT_FLAG_INS_END_INCL));
1607 	cont_prev = prop.tp_col + !start_incl <= kept;
1608 	cont_next = skipped <= prop.tp_col + prop.tp_len - !end_incl;
1609 
1610 	if (cont_prev && ga_grow(&prevprop, 1) == OK)
1611 	{
1612 	    textprop_T *p = ((textprop_T *)prevprop.ga_data) + prevprop.ga_len;
1613 
1614 	    *p = prop;
1615 	    ++prevprop.ga_len;
1616 	    if (p->tp_col + p->tp_len >= kept)
1617 		p->tp_len = kept - p->tp_col;
1618 	    if (cont_next)
1619 		p->tp_flags |= TP_FLAG_CONT_NEXT;
1620 	}
1621 
1622 	// Only add the property to the next line if the length is bigger than
1623 	// zero.
1624 	if (cont_next && ga_grow(&nextprop, 1) == OK)
1625 	{
1626 	    textprop_T *p = ((textprop_T *)nextprop.ga_data) + nextprop.ga_len;
1627 	    *p = prop;
1628 	    ++nextprop.ga_len;
1629 	    if (p->tp_col > skipped)
1630 		p->tp_col -= skipped - 1;
1631 	    else
1632 	    {
1633 		p->tp_len -= skipped - p->tp_col;
1634 		p->tp_col = 1;
1635 	    }
1636 	    if (cont_prev)
1637 		p->tp_flags |= TP_FLAG_CONT_PREV;
1638 	}
1639     }
1640 
1641     set_text_props(lnum_top, prevprop.ga_data,
1642 					 prevprop.ga_len * sizeof(textprop_T));
1643     ga_clear(&prevprop);
1644     set_text_props(lnum_top + 1, nextprop.ga_data,
1645 					 nextprop.ga_len * sizeof(textprop_T));
1646     ga_clear(&nextprop);
1647 }
1648 
1649 /*
1650  * Prepend properties of joined line "lnum" to "new_props".
1651  */
1652     void
prepend_joined_props(char_u * new_props,int propcount,int * props_remaining,linenr_T lnum,int add_all,long col,int removed)1653 prepend_joined_props(
1654 	char_u	    *new_props,
1655 	int	    propcount,
1656 	int	    *props_remaining,
1657 	linenr_T    lnum,
1658 	int	    add_all,
1659 	long	    col,
1660 	int	    removed)
1661 {
1662     char_u *props;
1663     int	    proplen = get_text_props(curbuf, lnum, &props, FALSE);
1664     int	    i;
1665 
1666     for (i = proplen; i-- > 0; )
1667     {
1668 	textprop_T  prop;
1669 	int	    end;
1670 
1671 	mch_memmove(&prop, props + i * sizeof(prop), sizeof(prop));
1672 	end = !(prop.tp_flags & TP_FLAG_CONT_NEXT);
1673 
1674 	adjust_prop(&prop, 0, -removed, 0); // Remove leading spaces
1675 	adjust_prop(&prop, -1, col, 0); // Make line start at its final column
1676 
1677 	if (add_all || end)
1678 	    mch_memmove(new_props + --(*props_remaining) * sizeof(prop),
1679 							  &prop, sizeof(prop));
1680 	else
1681 	{
1682 	    int j;
1683 	    int	found = FALSE;
1684 
1685 	    // Search for continuing prop.
1686 	    for (j = *props_remaining; j < propcount; ++j)
1687 	    {
1688 		textprop_T op;
1689 
1690 		mch_memmove(&op, new_props + j * sizeof(op), sizeof(op));
1691 		if ((op.tp_flags & TP_FLAG_CONT_PREV)
1692 			&& op.tp_id == prop.tp_id && op.tp_type == prop.tp_type)
1693 		{
1694 		    found = TRUE;
1695 		    op.tp_len += op.tp_col - prop.tp_col;
1696 		    op.tp_col = prop.tp_col;
1697 		    // Start/end is taken care of when deleting joined lines
1698 		    op.tp_flags = prop.tp_flags;
1699 		    mch_memmove(new_props + j * sizeof(op), &op, sizeof(op));
1700 		    break;
1701 		}
1702 	    }
1703 	    if (!found)
1704 		internal_error("text property above joined line not found");
1705 	}
1706     }
1707 }
1708 
1709 #endif // FEAT_PROP_POPUP
1710