xref: /vim-8.2.3635/src/textprop.c (revision ccfb7c67)
198aefe7cSBram Moolenaar /* vi:set ts=8 sts=4 sw=4 noet:
298aefe7cSBram Moolenaar  *
398aefe7cSBram Moolenaar  * VIM - Vi IMproved	by Bram Moolenaar
498aefe7cSBram Moolenaar  *
598aefe7cSBram Moolenaar  * Do ":help uganda"  in Vim to read copying and usage conditions.
698aefe7cSBram Moolenaar  * Do ":help credits" in Vim to see a list of people who contributed.
798aefe7cSBram Moolenaar  * See README.txt for an overview of the Vim source code.
898aefe7cSBram Moolenaar  */
998aefe7cSBram Moolenaar 
1098aefe7cSBram Moolenaar /*
1145dd07f1SBram Moolenaar  * Text properties implementation.  See ":help text-properties".
1298aefe7cSBram Moolenaar  *
1398aefe7cSBram Moolenaar  * TODO:
14b9c67a51SBram Moolenaar  * - Adjust text property column and length when text is inserted/deleted.
154164bb20SBram Moolenaar  *   -> a :substitute with a multi-line match
16f9e3e09fSBram Moolenaar  *   -> search for changed_bytes() from misc1.c
1780e737ccSBram Moolenaar  *   -> search for mark_col_adjust()
18b9c67a51SBram Moolenaar  * - Perhaps we only need TP_FLAG_CONT_NEXT and can drop TP_FLAG_CONT_PREV?
1932aa1020SBram Moolenaar  * - Add an array for global_proptypes, to quickly lookup a prop type by ID
2032aa1020SBram Moolenaar  * - Add an array for b_proptypes, to quickly lookup a prop type by ID
21b56ac049SBram Moolenaar  * - Checking the text length to detect text properties is slow.  Use a flag in
22b56ac049SBram Moolenaar  *   the index, like DB_MARKED?
23b413d2e6SBram Moolenaar  * - Also test line2byte() with many lines, so that ml_updatechunk() is taken
24b413d2e6SBram Moolenaar  *   into account.
25c6663883SBram Moolenaar  * - Perhaps have a window-local option to disable highlighting from text
26c6663883SBram Moolenaar  *   properties?
2798aefe7cSBram Moolenaar  */
2898aefe7cSBram Moolenaar 
2998aefe7cSBram Moolenaar #include "vim.h"
3098aefe7cSBram Moolenaar 
3105ad5ff0SBram Moolenaar #if defined(FEAT_PROP_POPUP) || defined(PROTO)
3298aefe7cSBram Moolenaar 
3398aefe7cSBram Moolenaar /*
3498aefe7cSBram Moolenaar  * In a hashtable item "hi_key" points to "pt_name" in a proptype_T.
3598aefe7cSBram Moolenaar  * This avoids adding a pointer to the hashtable item.
3698aefe7cSBram Moolenaar  * PT2HIKEY() converts a proptype pointer to a hashitem key pointer.
3798aefe7cSBram Moolenaar  * HIKEY2PT() converts a hashitem key pointer to a proptype pointer.
3898aefe7cSBram Moolenaar  * HI2PT() converts a hashitem pointer to a proptype pointer.
3998aefe7cSBram Moolenaar  */
4098aefe7cSBram Moolenaar #define PT2HIKEY(p)  ((p)->pt_name)
4198aefe7cSBram Moolenaar #define HIKEY2PT(p)   ((proptype_T *)((p) - offsetof(proptype_T, pt_name)))
4298aefe7cSBram Moolenaar #define HI2PT(hi)      HIKEY2PT((hi)->hi_key)
4398aefe7cSBram Moolenaar 
4498aefe7cSBram Moolenaar // The global text property types.
4598aefe7cSBram Moolenaar static hashtab_T *global_proptypes = NULL;
4698aefe7cSBram Moolenaar 
4798aefe7cSBram Moolenaar // The last used text property type ID.
4898aefe7cSBram Moolenaar static int proptype_id = 0;
4998aefe7cSBram Moolenaar 
5098aefe7cSBram Moolenaar static char_u e_type_not_exist[] = N_("E971: Property type %s does not exist");
5198aefe7cSBram Moolenaar static char_u e_invalid_col[] = N_("E964: Invalid column number: %ld");
52e3d31b02SBram Moolenaar static char_u e_invalid_lnum[] = N_("E966: Invalid line number: %ld");
5398aefe7cSBram Moolenaar 
5498aefe7cSBram Moolenaar /*
5598aefe7cSBram Moolenaar  * Find a property type by name, return the hashitem.
5698aefe7cSBram Moolenaar  * Returns NULL if the item can't be found.
5798aefe7cSBram Moolenaar  */
5898aefe7cSBram Moolenaar     static hashitem_T *
find_prop_hi(char_u * name,buf_T * buf)5998aefe7cSBram Moolenaar find_prop_hi(char_u *name, buf_T *buf)
6098aefe7cSBram Moolenaar {
6198aefe7cSBram Moolenaar     hashtab_T	*ht;
6298aefe7cSBram Moolenaar     hashitem_T	*hi;
6398aefe7cSBram Moolenaar 
6498aefe7cSBram Moolenaar     if (*name == NUL)
6598aefe7cSBram Moolenaar 	return NULL;
6698aefe7cSBram Moolenaar     if (buf == NULL)
6798aefe7cSBram Moolenaar 	ht = global_proptypes;
6898aefe7cSBram Moolenaar     else
6998aefe7cSBram Moolenaar 	ht = buf->b_proptypes;
7098aefe7cSBram Moolenaar 
7198aefe7cSBram Moolenaar     if (ht == NULL)
7298aefe7cSBram Moolenaar 	return NULL;
7398aefe7cSBram Moolenaar     hi = hash_find(ht, name);
7498aefe7cSBram Moolenaar     if (HASHITEM_EMPTY(hi))
7598aefe7cSBram Moolenaar 	return NULL;
7698aefe7cSBram Moolenaar     return hi;
7798aefe7cSBram Moolenaar }
7898aefe7cSBram Moolenaar 
7998aefe7cSBram Moolenaar /*
8098aefe7cSBram Moolenaar  * Like find_prop_hi() but return the property type.
8198aefe7cSBram Moolenaar  */
8298aefe7cSBram Moolenaar     static proptype_T *
find_prop(char_u * name,buf_T * buf)8398aefe7cSBram Moolenaar find_prop(char_u *name, buf_T *buf)
8498aefe7cSBram Moolenaar {
8598aefe7cSBram Moolenaar     hashitem_T	*hi = find_prop_hi(name, buf);
8698aefe7cSBram Moolenaar 
8798aefe7cSBram Moolenaar     if (hi == NULL)
8898aefe7cSBram Moolenaar 	return NULL;
8998aefe7cSBram Moolenaar     return HI2PT(hi);
9098aefe7cSBram Moolenaar }
9198aefe7cSBram Moolenaar 
9298aefe7cSBram Moolenaar /*
9312034e22SBram Moolenaar  * Get the prop type ID of "name".
9412034e22SBram Moolenaar  * When not found return zero.
9512034e22SBram Moolenaar  */
9612034e22SBram Moolenaar     int
find_prop_type_id(char_u * name,buf_T * buf)9712034e22SBram Moolenaar find_prop_type_id(char_u *name, buf_T *buf)
9812034e22SBram Moolenaar {
9912034e22SBram Moolenaar     proptype_T *pt = find_prop(name, buf);
10012034e22SBram Moolenaar 
10112034e22SBram Moolenaar     if (pt == NULL)
10212034e22SBram Moolenaar 	return 0;
10312034e22SBram Moolenaar     return pt->pt_id;
10412034e22SBram Moolenaar }
10512034e22SBram Moolenaar 
10612034e22SBram Moolenaar /*
10798aefe7cSBram Moolenaar  * Lookup a property type by name.  First in "buf" and when not found in the
10898aefe7cSBram Moolenaar  * global types.
10998aefe7cSBram Moolenaar  * When not found gives an error message and returns NULL.
11098aefe7cSBram Moolenaar  */
11198aefe7cSBram Moolenaar     static proptype_T *
lookup_prop_type(char_u * name,buf_T * buf)11298aefe7cSBram Moolenaar lookup_prop_type(char_u *name, buf_T *buf)
11398aefe7cSBram Moolenaar {
11498aefe7cSBram Moolenaar     proptype_T *type = find_prop(name, buf);
11598aefe7cSBram Moolenaar 
11698aefe7cSBram Moolenaar     if (type == NULL)
11798aefe7cSBram Moolenaar 	type = find_prop(name, NULL);
11898aefe7cSBram Moolenaar     if (type == NULL)
119f9e3e09fSBram Moolenaar 	semsg(_(e_type_not_exist), name);
12098aefe7cSBram Moolenaar     return type;
12198aefe7cSBram Moolenaar }
12298aefe7cSBram Moolenaar 
12398aefe7cSBram Moolenaar /*
12498aefe7cSBram Moolenaar  * Get an optional "bufnr" item from the dict in "arg".
12598aefe7cSBram Moolenaar  * When the argument is not used or "bufnr" is not present then "buf" is
12698aefe7cSBram Moolenaar  * unchanged.
12798aefe7cSBram Moolenaar  * If "bufnr" is valid or not present return OK.
12832aa1020SBram Moolenaar  * When "arg" is not a dict or "bufnr" is invalid return FAIL.
12998aefe7cSBram Moolenaar  */
13098aefe7cSBram Moolenaar     static int
get_bufnr_from_arg(typval_T * arg,buf_T ** buf)13198aefe7cSBram Moolenaar get_bufnr_from_arg(typval_T *arg, buf_T **buf)
13298aefe7cSBram Moolenaar {
13398aefe7cSBram Moolenaar     dictitem_T	*di;
13498aefe7cSBram Moolenaar 
13598aefe7cSBram Moolenaar     if (arg->v_type != VAR_DICT)
13698aefe7cSBram Moolenaar     {
137f9e3e09fSBram Moolenaar 	emsg(_(e_dictreq));
13898aefe7cSBram Moolenaar 	return FAIL;
13998aefe7cSBram Moolenaar     }
14098aefe7cSBram Moolenaar     if (arg->vval.v_dict == NULL)
14198aefe7cSBram Moolenaar 	return OK;  // NULL dict is like an empty dict
14298aefe7cSBram Moolenaar     di = dict_find(arg->vval.v_dict, (char_u *)"bufnr", -1);
143e2390c7fSMartin Tournoij     if (di != NULL && (di->di_tv.v_type != VAR_NUMBER
144e2390c7fSMartin Tournoij 					      || di->di_tv.vval.v_number != 0))
14598aefe7cSBram Moolenaar     {
146f0884c5fSBram Moolenaar 	*buf = get_buf_arg(&di->di_tv);
14798aefe7cSBram Moolenaar 	if (*buf == NULL)
14898aefe7cSBram Moolenaar 	    return FAIL;
14998aefe7cSBram Moolenaar     }
15098aefe7cSBram Moolenaar     return OK;
15198aefe7cSBram Moolenaar }
15298aefe7cSBram Moolenaar 
15398aefe7cSBram Moolenaar /*
15498aefe7cSBram Moolenaar  * prop_add({lnum}, {col}, {props})
15598aefe7cSBram Moolenaar  */
15698aefe7cSBram Moolenaar     void
f_prop_add(typval_T * argvars,typval_T * rettv UNUSED)15798aefe7cSBram Moolenaar f_prop_add(typval_T *argvars, typval_T *rettv UNUSED)
15898aefe7cSBram Moolenaar {
159e3d31b02SBram Moolenaar     linenr_T	start_lnum;
160e3d31b02SBram Moolenaar     colnr_T	start_col;
16198aefe7cSBram Moolenaar 
16283494b4aSYegappan Lakshmanan     if (in_vim9script()
16383494b4aSYegappan Lakshmanan 	    && (check_for_number_arg(argvars, 0) == FAIL
16483494b4aSYegappan Lakshmanan 		|| check_for_number_arg(argvars, 1) == FAIL
16583494b4aSYegappan Lakshmanan 		|| check_for_dict_arg(argvars, 2) == FAIL))
16683494b4aSYegappan Lakshmanan 	return;
16783494b4aSYegappan Lakshmanan 
168e3d31b02SBram Moolenaar     start_lnum = tv_get_number(&argvars[0]);
169e3d31b02SBram Moolenaar     start_col = tv_get_number(&argvars[1]);
170e3d31b02SBram Moolenaar     if (start_col < 1)
17198aefe7cSBram Moolenaar     {
172f9e3e09fSBram Moolenaar 	semsg(_(e_invalid_col), (long)start_col);
17398aefe7cSBram Moolenaar 	return;
17498aefe7cSBram Moolenaar     }
17598aefe7cSBram Moolenaar     if (argvars[2].v_type != VAR_DICT)
17698aefe7cSBram Moolenaar     {
177f9e3e09fSBram Moolenaar 	emsg(_(e_dictreq));
17898aefe7cSBram Moolenaar 	return;
17998aefe7cSBram Moolenaar     }
1807a8d0278SBram Moolenaar 
1817a8d0278SBram Moolenaar     prop_add_common(start_lnum, start_col, argvars[2].vval.v_dict,
1827a8d0278SBram Moolenaar 							  curbuf, &argvars[2]);
1837a8d0278SBram Moolenaar }
1847a8d0278SBram Moolenaar 
1857a8d0278SBram Moolenaar /*
186*ccfb7c67SYegappan Lakshmanan  * Attach a text property 'type_name' to the text starting
187*ccfb7c67SYegappan Lakshmanan  * at [start_lnum, start_col] and ending at [end_lnum, end_col] in
188*ccfb7c67SYegappan Lakshmanan  * the buffer 'buf' and assign identifier 'id'.
189*ccfb7c67SYegappan Lakshmanan  */
190*ccfb7c67SYegappan Lakshmanan     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*ccfb7c67SYegappan Lakshmanan prop_add_one(
192*ccfb7c67SYegappan Lakshmanan 	buf_T		*buf,
193*ccfb7c67SYegappan Lakshmanan 	char_u		*type_name,
194*ccfb7c67SYegappan Lakshmanan 	int		id,
195*ccfb7c67SYegappan Lakshmanan 	linenr_T	start_lnum,
196*ccfb7c67SYegappan Lakshmanan 	linenr_T	end_lnum,
197*ccfb7c67SYegappan Lakshmanan 	colnr_T		start_col,
198*ccfb7c67SYegappan Lakshmanan 	colnr_T		end_col)
199*ccfb7c67SYegappan Lakshmanan {
200*ccfb7c67SYegappan Lakshmanan     proptype_T	*type;
201*ccfb7c67SYegappan Lakshmanan     linenr_T	lnum;
202*ccfb7c67SYegappan Lakshmanan     int		proplen;
203*ccfb7c67SYegappan Lakshmanan     char_u	*props = NULL;
204*ccfb7c67SYegappan Lakshmanan     char_u	*newprops;
205*ccfb7c67SYegappan Lakshmanan     size_t	textlen;
206*ccfb7c67SYegappan Lakshmanan     char_u	*newtext;
207*ccfb7c67SYegappan Lakshmanan     int		i;
208*ccfb7c67SYegappan Lakshmanan     textprop_T	tmp_prop;
209*ccfb7c67SYegappan Lakshmanan 
210*ccfb7c67SYegappan Lakshmanan     type = lookup_prop_type(type_name, buf);
211*ccfb7c67SYegappan Lakshmanan     if (type == NULL)
212*ccfb7c67SYegappan Lakshmanan 	return FAIL;
213*ccfb7c67SYegappan Lakshmanan 
214*ccfb7c67SYegappan Lakshmanan     if (start_lnum < 1 || start_lnum > buf->b_ml.ml_line_count)
215*ccfb7c67SYegappan Lakshmanan     {
216*ccfb7c67SYegappan Lakshmanan 	semsg(_(e_invalid_lnum), (long)start_lnum);
217*ccfb7c67SYegappan Lakshmanan 	return FAIL;
218*ccfb7c67SYegappan Lakshmanan     }
219*ccfb7c67SYegappan Lakshmanan     if (end_lnum < start_lnum || end_lnum > buf->b_ml.ml_line_count)
220*ccfb7c67SYegappan Lakshmanan     {
221*ccfb7c67SYegappan Lakshmanan 	semsg(_(e_invalid_lnum), (long)end_lnum);
222*ccfb7c67SYegappan Lakshmanan 	return FAIL;
223*ccfb7c67SYegappan Lakshmanan     }
224*ccfb7c67SYegappan Lakshmanan 
225*ccfb7c67SYegappan Lakshmanan     if (buf->b_ml.ml_mfp == NULL)
226*ccfb7c67SYegappan Lakshmanan     {
227*ccfb7c67SYegappan Lakshmanan 	emsg(_("E275: Cannot add text property to unloaded buffer"));
228*ccfb7c67SYegappan Lakshmanan 	return FAIL;
229*ccfb7c67SYegappan Lakshmanan     }
230*ccfb7c67SYegappan Lakshmanan 
231*ccfb7c67SYegappan Lakshmanan     for (lnum = start_lnum; lnum <= end_lnum; ++lnum)
232*ccfb7c67SYegappan Lakshmanan     {
233*ccfb7c67SYegappan Lakshmanan 	colnr_T col;	// start column
234*ccfb7c67SYegappan Lakshmanan 	long	length;	// in bytes
235*ccfb7c67SYegappan Lakshmanan 
236*ccfb7c67SYegappan Lakshmanan 	// Fetch the line to get the ml_line_len field updated.
237*ccfb7c67SYegappan Lakshmanan 	proplen = get_text_props(buf, lnum, &props, TRUE);
238*ccfb7c67SYegappan Lakshmanan 	textlen = buf->b_ml.ml_line_len - proplen * sizeof(textprop_T);
239*ccfb7c67SYegappan Lakshmanan 
240*ccfb7c67SYegappan Lakshmanan 	if (lnum == start_lnum)
241*ccfb7c67SYegappan Lakshmanan 	    col = start_col;
242*ccfb7c67SYegappan Lakshmanan 	else
243*ccfb7c67SYegappan Lakshmanan 	    col = 1;
244*ccfb7c67SYegappan Lakshmanan 	if (col - 1 > (colnr_T)textlen)
245*ccfb7c67SYegappan Lakshmanan 	{
246*ccfb7c67SYegappan Lakshmanan 	    semsg(_(e_invalid_col), (long)start_col);
247*ccfb7c67SYegappan Lakshmanan 	    return FAIL;
248*ccfb7c67SYegappan Lakshmanan 	}
249*ccfb7c67SYegappan Lakshmanan 
250*ccfb7c67SYegappan Lakshmanan 	if (lnum == end_lnum)
251*ccfb7c67SYegappan Lakshmanan 	    length = end_col - col;
252*ccfb7c67SYegappan Lakshmanan 	else
253*ccfb7c67SYegappan Lakshmanan 	    length = (int)textlen - col + 1;
254*ccfb7c67SYegappan Lakshmanan 	if (length > (long)textlen)
255*ccfb7c67SYegappan Lakshmanan 	    length = (int)textlen;	// can include the end-of-line
256*ccfb7c67SYegappan Lakshmanan 	if (length < 0)
257*ccfb7c67SYegappan Lakshmanan 	    length = 0;		// zero-width property
258*ccfb7c67SYegappan Lakshmanan 
259*ccfb7c67SYegappan Lakshmanan 	// Allocate the new line with space for the new property.
260*ccfb7c67SYegappan Lakshmanan 	newtext = alloc(buf->b_ml.ml_line_len + sizeof(textprop_T));
261*ccfb7c67SYegappan Lakshmanan 	if (newtext == NULL)
262*ccfb7c67SYegappan Lakshmanan 	    return FAIL;
263*ccfb7c67SYegappan Lakshmanan 	// Copy the text, including terminating NUL.
264*ccfb7c67SYegappan Lakshmanan 	mch_memmove(newtext, buf->b_ml.ml_line_ptr, textlen);
265*ccfb7c67SYegappan Lakshmanan 
266*ccfb7c67SYegappan Lakshmanan 	// Find the index where to insert the new property.
267*ccfb7c67SYegappan Lakshmanan 	// Since the text properties are not aligned properly when stored with
268*ccfb7c67SYegappan Lakshmanan 	// the text, we need to copy them as bytes before using it as a struct.
269*ccfb7c67SYegappan Lakshmanan 	for (i = 0; i < proplen; ++i)
270*ccfb7c67SYegappan Lakshmanan 	{
271*ccfb7c67SYegappan Lakshmanan 	    mch_memmove(&tmp_prop, props + i * sizeof(textprop_T),
272*ccfb7c67SYegappan Lakshmanan 							   sizeof(textprop_T));
273*ccfb7c67SYegappan Lakshmanan 	    if (tmp_prop.tp_col >= col)
274*ccfb7c67SYegappan Lakshmanan 		break;
275*ccfb7c67SYegappan Lakshmanan 	}
276*ccfb7c67SYegappan Lakshmanan 	newprops = newtext + textlen;
277*ccfb7c67SYegappan Lakshmanan 	if (i > 0)
278*ccfb7c67SYegappan Lakshmanan 	    mch_memmove(newprops, props, sizeof(textprop_T) * i);
279*ccfb7c67SYegappan Lakshmanan 
280*ccfb7c67SYegappan Lakshmanan 	tmp_prop.tp_col = col;
281*ccfb7c67SYegappan Lakshmanan 	tmp_prop.tp_len = length;
282*ccfb7c67SYegappan Lakshmanan 	tmp_prop.tp_id = id;
283*ccfb7c67SYegappan Lakshmanan 	tmp_prop.tp_type = type->pt_id;
284*ccfb7c67SYegappan Lakshmanan 	tmp_prop.tp_flags = (lnum > start_lnum ? TP_FLAG_CONT_PREV : 0)
285*ccfb7c67SYegappan Lakshmanan 			  | (lnum < end_lnum ? TP_FLAG_CONT_NEXT : 0);
286*ccfb7c67SYegappan Lakshmanan 	mch_memmove(newprops + i * sizeof(textprop_T), &tmp_prop,
287*ccfb7c67SYegappan Lakshmanan 							   sizeof(textprop_T));
288*ccfb7c67SYegappan Lakshmanan 
289*ccfb7c67SYegappan Lakshmanan 	if (i < proplen)
290*ccfb7c67SYegappan Lakshmanan 	    mch_memmove(newprops + (i + 1) * sizeof(textprop_T),
291*ccfb7c67SYegappan Lakshmanan 					    props + i * sizeof(textprop_T),
292*ccfb7c67SYegappan Lakshmanan 					    sizeof(textprop_T) * (proplen - i));
293*ccfb7c67SYegappan Lakshmanan 
294*ccfb7c67SYegappan Lakshmanan 	if (buf->b_ml.ml_flags & ML_LINE_DIRTY)
295*ccfb7c67SYegappan Lakshmanan 	    vim_free(buf->b_ml.ml_line_ptr);
296*ccfb7c67SYegappan Lakshmanan 	buf->b_ml.ml_line_ptr = newtext;
297*ccfb7c67SYegappan Lakshmanan 	buf->b_ml.ml_line_len += sizeof(textprop_T);
298*ccfb7c67SYegappan Lakshmanan 	buf->b_ml.ml_flags |= ML_LINE_DIRTY;
299*ccfb7c67SYegappan Lakshmanan     }
300*ccfb7c67SYegappan Lakshmanan 
301*ccfb7c67SYegappan Lakshmanan     changed_lines_buf(buf, start_lnum, end_lnum + 1, 0);
302*ccfb7c67SYegappan Lakshmanan     return OK;
303*ccfb7c67SYegappan Lakshmanan }
304*ccfb7c67SYegappan Lakshmanan 
305*ccfb7c67SYegappan Lakshmanan /*
306*ccfb7c67SYegappan Lakshmanan  * prop_add_list()
307*ccfb7c67SYegappan Lakshmanan  * First argument specifies the text property:
308*ccfb7c67SYegappan Lakshmanan  *   {'type': <str>, 'id': <num>, 'bufnr': <num>}
309*ccfb7c67SYegappan Lakshmanan  * Second argument is a List where each item is a List with the following
310*ccfb7c67SYegappan Lakshmanan  * entries: [lnum, start_col, end_col]
311*ccfb7c67SYegappan Lakshmanan  */
312*ccfb7c67SYegappan Lakshmanan     void
f_prop_add_list(typval_T * argvars,typval_T * rettv UNUSED)313*ccfb7c67SYegappan Lakshmanan f_prop_add_list(typval_T *argvars, typval_T *rettv UNUSED)
314*ccfb7c67SYegappan Lakshmanan {
315*ccfb7c67SYegappan Lakshmanan     dict_T	*dict;
316*ccfb7c67SYegappan Lakshmanan     char_u	*type_name;
317*ccfb7c67SYegappan Lakshmanan     buf_T	*buf = curbuf;
318*ccfb7c67SYegappan Lakshmanan     int		id = 0;
319*ccfb7c67SYegappan Lakshmanan     listitem_T	*li;
320*ccfb7c67SYegappan Lakshmanan     list_T	*pos_list;
321*ccfb7c67SYegappan Lakshmanan     linenr_T	start_lnum;
322*ccfb7c67SYegappan Lakshmanan     colnr_T	start_col;
323*ccfb7c67SYegappan Lakshmanan     linenr_T	end_lnum;
324*ccfb7c67SYegappan Lakshmanan     colnr_T	end_col;
325*ccfb7c67SYegappan Lakshmanan     int		error = FALSE;
326*ccfb7c67SYegappan Lakshmanan 
327*ccfb7c67SYegappan Lakshmanan     if (check_for_dict_arg(argvars, 0) == FAIL
328*ccfb7c67SYegappan Lakshmanan 	    || check_for_list_arg(argvars, 1) == FAIL)
329*ccfb7c67SYegappan Lakshmanan 	return;
330*ccfb7c67SYegappan Lakshmanan 
331*ccfb7c67SYegappan Lakshmanan     if (argvars[1].vval.v_list == NULL)
332*ccfb7c67SYegappan Lakshmanan     {
333*ccfb7c67SYegappan Lakshmanan 	emsg(_(e_listreq));
334*ccfb7c67SYegappan Lakshmanan 	return;
335*ccfb7c67SYegappan Lakshmanan     }
336*ccfb7c67SYegappan Lakshmanan 
337*ccfb7c67SYegappan Lakshmanan     dict = argvars[0].vval.v_dict;
338*ccfb7c67SYegappan Lakshmanan     if (dict == NULL || dict_find(dict, (char_u *)"type", -1) == NULL)
339*ccfb7c67SYegappan Lakshmanan     {
340*ccfb7c67SYegappan Lakshmanan 	emsg(_("E965: missing property type name"));
341*ccfb7c67SYegappan Lakshmanan 	return;
342*ccfb7c67SYegappan Lakshmanan     }
343*ccfb7c67SYegappan Lakshmanan     type_name = dict_get_string(dict, (char_u *)"type", FALSE);
344*ccfb7c67SYegappan Lakshmanan 
345*ccfb7c67SYegappan Lakshmanan     if (dict_find(dict, (char_u *)"id", -1) != NULL)
346*ccfb7c67SYegappan Lakshmanan 	id = dict_get_number(dict, (char_u *)"id");
347*ccfb7c67SYegappan Lakshmanan 
348*ccfb7c67SYegappan Lakshmanan     if (get_bufnr_from_arg(&argvars[0], &buf) == FAIL)
349*ccfb7c67SYegappan Lakshmanan 	return;
350*ccfb7c67SYegappan Lakshmanan 
351*ccfb7c67SYegappan Lakshmanan     FOR_ALL_LIST_ITEMS(argvars[1].vval.v_list, li)
352*ccfb7c67SYegappan Lakshmanan     {
353*ccfb7c67SYegappan Lakshmanan 	if (li->li_tv.v_type != VAR_LIST || li->li_tv.vval.v_list == NULL)
354*ccfb7c67SYegappan Lakshmanan 	{
355*ccfb7c67SYegappan Lakshmanan 	    emsg(_(e_listreq));
356*ccfb7c67SYegappan Lakshmanan 	    return;
357*ccfb7c67SYegappan Lakshmanan 	}
358*ccfb7c67SYegappan Lakshmanan 
359*ccfb7c67SYegappan Lakshmanan 	pos_list = li->li_tv.vval.v_list;
360*ccfb7c67SYegappan Lakshmanan 	start_lnum = list_find_nr(pos_list, 0L, &error);
361*ccfb7c67SYegappan Lakshmanan 	start_col = list_find_nr(pos_list, 1L, &error);
362*ccfb7c67SYegappan Lakshmanan 	end_lnum = list_find_nr(pos_list, 2L, &error);
363*ccfb7c67SYegappan Lakshmanan 	end_col = list_find_nr(pos_list, 3L, &error);
364*ccfb7c67SYegappan Lakshmanan 	if (error || start_lnum <= 0 || start_col <= 0
365*ccfb7c67SYegappan Lakshmanan 		|| end_lnum <= 0 || end_col <= 0)
366*ccfb7c67SYegappan Lakshmanan 	{
367*ccfb7c67SYegappan Lakshmanan 	    emsg(_(e_invarg));
368*ccfb7c67SYegappan Lakshmanan 	    return;
369*ccfb7c67SYegappan Lakshmanan 	}
370*ccfb7c67SYegappan Lakshmanan 	if (prop_add_one(buf, type_name, id, start_lnum, end_lnum,
371*ccfb7c67SYegappan Lakshmanan 						start_col, end_col) == FAIL)
372*ccfb7c67SYegappan Lakshmanan 	    return;
373*ccfb7c67SYegappan Lakshmanan     }
374*ccfb7c67SYegappan Lakshmanan 
375*ccfb7c67SYegappan Lakshmanan     buf->b_has_textprop = TRUE;  // this is never reset
376*ccfb7c67SYegappan Lakshmanan     redraw_buf_later(buf, VALID);
377*ccfb7c67SYegappan Lakshmanan }
378*ccfb7c67SYegappan Lakshmanan 
379*ccfb7c67SYegappan Lakshmanan /*
3807a8d0278SBram Moolenaar  * Shared between prop_add() and popup_create().
3817a8d0278SBram Moolenaar  * "dict_arg" is the function argument of a dict containing "bufnr".
3827a8d0278SBram Moolenaar  * it is NULL for popup_create().
3837a8d0278SBram Moolenaar  */
3847a8d0278SBram Moolenaar     void
prop_add_common(linenr_T start_lnum,colnr_T start_col,dict_T * dict,buf_T * default_buf,typval_T * dict_arg)3857a8d0278SBram Moolenaar prop_add_common(
3867a8d0278SBram Moolenaar 	linenr_T    start_lnum,
3877a8d0278SBram Moolenaar 	colnr_T	    start_col,
3887a8d0278SBram Moolenaar 	dict_T	    *dict,
3897a8d0278SBram Moolenaar 	buf_T	    *default_buf,
3907a8d0278SBram Moolenaar 	typval_T    *dict_arg)
3917a8d0278SBram Moolenaar {
3927a8d0278SBram Moolenaar     linenr_T	end_lnum;
3937a8d0278SBram Moolenaar     colnr_T	end_col;
3947a8d0278SBram Moolenaar     char_u	*type_name;
3957a8d0278SBram Moolenaar     buf_T	*buf = default_buf;
3967a8d0278SBram Moolenaar     int		id = 0;
39798aefe7cSBram Moolenaar 
39898aefe7cSBram Moolenaar     if (dict == NULL || dict_find(dict, (char_u *)"type", -1) == NULL)
39998aefe7cSBram Moolenaar     {
400f9e3e09fSBram Moolenaar 	emsg(_("E965: missing property type name"));
40198aefe7cSBram Moolenaar 	return;
40298aefe7cSBram Moolenaar     }
4038f66717aSBram Moolenaar     type_name = dict_get_string(dict, (char_u *)"type", FALSE);
40498aefe7cSBram Moolenaar 
40598aefe7cSBram Moolenaar     if (dict_find(dict, (char_u *)"end_lnum", -1) != NULL)
40698aefe7cSBram Moolenaar     {
407e3d31b02SBram Moolenaar 	end_lnum = dict_get_number(dict, (char_u *)"end_lnum");
408e3d31b02SBram Moolenaar 	if (end_lnum < start_lnum)
409e3d31b02SBram Moolenaar 	{
410f9e3e09fSBram Moolenaar 	    semsg(_(e_invargval), "end_lnum");
41198aefe7cSBram Moolenaar 	    return;
41298aefe7cSBram Moolenaar 	}
413e3d31b02SBram Moolenaar     }
414e3d31b02SBram Moolenaar     else
415e3d31b02SBram Moolenaar 	end_lnum = start_lnum;
41698aefe7cSBram Moolenaar 
41798aefe7cSBram Moolenaar     if (dict_find(dict, (char_u *)"length", -1) != NULL)
418e3d31b02SBram Moolenaar     {
419e3d31b02SBram Moolenaar 	long length = dict_get_number(dict, (char_u *)"length");
420e3d31b02SBram Moolenaar 
421b9c67a51SBram Moolenaar 	if (length < 0 || end_lnum > start_lnum)
422e3d31b02SBram Moolenaar 	{
423f9e3e09fSBram Moolenaar 	    semsg(_(e_invargval), "length");
424e3d31b02SBram Moolenaar 	    return;
425e3d31b02SBram Moolenaar 	}
426b9c67a51SBram Moolenaar 	end_col = start_col + length;
427e3d31b02SBram Moolenaar     }
42898aefe7cSBram Moolenaar     else if (dict_find(dict, (char_u *)"end_col", -1) != NULL)
42998aefe7cSBram Moolenaar     {
430e3d31b02SBram Moolenaar 	end_col = dict_get_number(dict, (char_u *)"end_col");
431e3d31b02SBram Moolenaar 	if (end_col <= 0)
43298aefe7cSBram Moolenaar 	{
433f9e3e09fSBram Moolenaar 	    semsg(_(e_invargval), "end_col");
43498aefe7cSBram Moolenaar 	    return;
43598aefe7cSBram Moolenaar 	}
43698aefe7cSBram Moolenaar     }
437e3d31b02SBram Moolenaar     else if (start_lnum == end_lnum)
438e3d31b02SBram Moolenaar 	end_col = start_col;
439e3d31b02SBram Moolenaar     else
440e3d31b02SBram Moolenaar 	end_col = 1;
44198aefe7cSBram Moolenaar 
44298aefe7cSBram Moolenaar     if (dict_find(dict, (char_u *)"id", -1) != NULL)
4438f66717aSBram Moolenaar 	id = dict_get_number(dict, (char_u *)"id");
44498aefe7cSBram Moolenaar 
4457a8d0278SBram Moolenaar     if (dict_arg != NULL && get_bufnr_from_arg(dict_arg, &buf) == FAIL)
44698aefe7cSBram Moolenaar 	return;
44798aefe7cSBram Moolenaar 
448*ccfb7c67SYegappan Lakshmanan     prop_add_one(buf, type_name, id, start_lnum, end_lnum, start_col, end_col);
44998aefe7cSBram Moolenaar 
450b413d2e6SBram Moolenaar     buf->b_has_textprop = TRUE;  // this is never reset
4511764faa3SBram Moolenaar     redraw_buf_later(buf, VALID);
45298aefe7cSBram Moolenaar }
45398aefe7cSBram Moolenaar 
45498aefe7cSBram Moolenaar /*
455fb95e212SBram Moolenaar  * Fetch the text properties for line "lnum" in buffer "buf".
45698aefe7cSBram Moolenaar  * Returns the number of text properties and, when non-zero, a pointer to the
45798aefe7cSBram Moolenaar  * first one in "props" (note that it is not aligned, therefore the char_u
45898aefe7cSBram Moolenaar  * pointer).
45998aefe7cSBram Moolenaar  */
46098aefe7cSBram Moolenaar     int
get_text_props(buf_T * buf,linenr_T lnum,char_u ** props,int will_change)46198aefe7cSBram Moolenaar get_text_props(buf_T *buf, linenr_T lnum, char_u **props, int will_change)
46298aefe7cSBram Moolenaar {
46398aefe7cSBram Moolenaar     char_u *text;
46498aefe7cSBram Moolenaar     size_t textlen;
46598aefe7cSBram Moolenaar     size_t proplen;
46698aefe7cSBram Moolenaar 
467b413d2e6SBram Moolenaar     // Be quick when no text property types have been defined or the buffer,
468b413d2e6SBram Moolenaar     // unless we are adding one.
469d79eef2eSBram Moolenaar     if ((!buf->b_has_textprop && !will_change) || buf->b_ml.ml_mfp == NULL)
47098aefe7cSBram Moolenaar 	return 0;
47198aefe7cSBram Moolenaar 
47298aefe7cSBram Moolenaar     // Fetch the line to get the ml_line_len field updated.
47398aefe7cSBram Moolenaar     text = ml_get_buf(buf, lnum, will_change);
47498aefe7cSBram Moolenaar     textlen = STRLEN(text) + 1;
47598aefe7cSBram Moolenaar     proplen = buf->b_ml.ml_line_len - textlen;
47698aefe7cSBram Moolenaar     if (proplen % sizeof(textprop_T) != 0)
47798aefe7cSBram Moolenaar     {
478f9e3e09fSBram Moolenaar 	iemsg(_("E967: text property info corrupted"));
47998aefe7cSBram Moolenaar 	return 0;
48098aefe7cSBram Moolenaar     }
48198aefe7cSBram Moolenaar     if (proplen > 0)
48298aefe7cSBram Moolenaar 	*props = text + textlen;
4834efe73b4SBram Moolenaar     return (int)(proplen / sizeof(textprop_T));
48498aefe7cSBram Moolenaar }
48598aefe7cSBram Moolenaar 
48687be9be1SBram Moolenaar /**
48787be9be1SBram Moolenaar  * Return the number of text properties on line "lnum" in the current buffer.
48887be9be1SBram Moolenaar  * When "only_starting" is true only text properties starting in this line will
48987be9be1SBram Moolenaar  * be considered.
49087be9be1SBram Moolenaar  */
49187be9be1SBram Moolenaar     int
count_props(linenr_T lnum,int only_starting)49287be9be1SBram Moolenaar count_props(linenr_T lnum, int only_starting)
49387be9be1SBram Moolenaar {
49487be9be1SBram Moolenaar     char_u	*props;
49587be9be1SBram Moolenaar     int		proplen = get_text_props(curbuf, lnum, &props, 0);
49687be9be1SBram Moolenaar     int		result = proplen;
49787be9be1SBram Moolenaar     int		i;
49887be9be1SBram Moolenaar     textprop_T	prop;
49987be9be1SBram Moolenaar 
50087be9be1SBram Moolenaar     if (only_starting)
50187be9be1SBram Moolenaar 	for (i = 0; i < proplen; ++i)
50287be9be1SBram Moolenaar 	{
50387be9be1SBram Moolenaar 	    mch_memmove(&prop, props + i * sizeof(prop), sizeof(prop));
50487be9be1SBram Moolenaar 	    if (prop.tp_flags & TP_FLAG_CONT_PREV)
50587be9be1SBram Moolenaar 		--result;
50687be9be1SBram Moolenaar 	}
50787be9be1SBram Moolenaar     return result;
50887be9be1SBram Moolenaar }
50987be9be1SBram Moolenaar 
5104164bb20SBram Moolenaar /*
51112034e22SBram Moolenaar  * Find text property "type_id" in the visible lines of window "wp".
51212034e22SBram Moolenaar  * Match "id" when it is > 0.
51312034e22SBram Moolenaar  * Returns FAIL when not found.
51412034e22SBram Moolenaar  */
51512034e22SBram Moolenaar     int
find_visible_prop(win_T * wp,int type_id,int id,textprop_T * prop,linenr_T * found_lnum)51612034e22SBram Moolenaar find_visible_prop(win_T *wp, int type_id, int id, textprop_T *prop,
51712034e22SBram Moolenaar 							  linenr_T *found_lnum)
51812034e22SBram Moolenaar {
51912034e22SBram Moolenaar     linenr_T		lnum;
52012034e22SBram Moolenaar     char_u		*props;
52112034e22SBram Moolenaar     int			count;
52212034e22SBram Moolenaar     int			i;
52312034e22SBram Moolenaar 
52412034e22SBram Moolenaar     // w_botline may not have been updated yet.
52523999d79SBram Moolenaar     validate_botline_win(wp);
52612034e22SBram Moolenaar     for (lnum = wp->w_topline; lnum < wp->w_botline; ++lnum)
52712034e22SBram Moolenaar     {
52812034e22SBram Moolenaar 	count = get_text_props(wp->w_buffer, lnum, &props, FALSE);
52912034e22SBram Moolenaar 	for (i = 0; i < count; ++i)
53012034e22SBram Moolenaar 	{
53112034e22SBram Moolenaar 	    mch_memmove(prop, props + i * sizeof(textprop_T),
53212034e22SBram Moolenaar 							   sizeof(textprop_T));
53312034e22SBram Moolenaar 	    if (prop->tp_type == type_id && (id <= 0 || prop->tp_id == id))
53412034e22SBram Moolenaar 	    {
53512034e22SBram Moolenaar 		*found_lnum = lnum;
53612034e22SBram Moolenaar 		return OK;
53712034e22SBram Moolenaar 	    }
53812034e22SBram Moolenaar 	}
53912034e22SBram Moolenaar     }
54012034e22SBram Moolenaar     return FAIL;
54112034e22SBram Moolenaar }
54212034e22SBram Moolenaar 
54312034e22SBram Moolenaar /*
5444164bb20SBram Moolenaar  * Set the text properties for line "lnum" to "props" with length "len".
5454164bb20SBram Moolenaar  * If "len" is zero text properties are removed, "props" is not used.
5464164bb20SBram Moolenaar  * Any existing text properties are dropped.
5474164bb20SBram Moolenaar  * Only works for the current buffer.
5484164bb20SBram Moolenaar  */
5494164bb20SBram Moolenaar     static void
set_text_props(linenr_T lnum,char_u * props,int len)5504164bb20SBram Moolenaar set_text_props(linenr_T lnum, char_u *props, int len)
5514164bb20SBram Moolenaar {
5524164bb20SBram Moolenaar     char_u  *text;
5534164bb20SBram Moolenaar     char_u  *newtext;
5548aef43b6SBram Moolenaar     int	    textlen;
5554164bb20SBram Moolenaar 
5564164bb20SBram Moolenaar     text = ml_get(lnum);
5578aef43b6SBram Moolenaar     textlen = (int)STRLEN(text) + 1;
5584164bb20SBram Moolenaar     newtext = alloc(textlen + len);
5594164bb20SBram Moolenaar     if (newtext == NULL)
5604164bb20SBram Moolenaar 	return;
5614164bb20SBram Moolenaar     mch_memmove(newtext, text, textlen);
5624164bb20SBram Moolenaar     if (len > 0)
5634164bb20SBram Moolenaar 	mch_memmove(newtext + textlen, props, len);
5644164bb20SBram Moolenaar     if (curbuf->b_ml.ml_flags & ML_LINE_DIRTY)
5654164bb20SBram Moolenaar 	vim_free(curbuf->b_ml.ml_line_ptr);
5664164bb20SBram Moolenaar     curbuf->b_ml.ml_line_ptr = newtext;
5674164bb20SBram Moolenaar     curbuf->b_ml.ml_line_len = textlen + len;
5684164bb20SBram Moolenaar     curbuf->b_ml.ml_flags |= ML_LINE_DIRTY;
5694164bb20SBram Moolenaar }
5704164bb20SBram Moolenaar 
57198aefe7cSBram Moolenaar     static proptype_T *
find_type_by_id(hashtab_T * ht,int id)57298aefe7cSBram Moolenaar find_type_by_id(hashtab_T *ht, int id)
57398aefe7cSBram Moolenaar {
57498aefe7cSBram Moolenaar     long	todo;
57598aefe7cSBram Moolenaar     hashitem_T	*hi;
57698aefe7cSBram Moolenaar 
57798aefe7cSBram Moolenaar     if (ht == NULL)
57898aefe7cSBram Moolenaar 	return NULL;
57998aefe7cSBram Moolenaar 
58098aefe7cSBram Moolenaar     // TODO: Make this faster by keeping a list of types sorted on ID and use
58198aefe7cSBram Moolenaar     // a binary search.
58298aefe7cSBram Moolenaar 
58398aefe7cSBram Moolenaar     todo = (long)ht->ht_used;
58498aefe7cSBram Moolenaar     for (hi = ht->ht_array; todo > 0; ++hi)
58598aefe7cSBram Moolenaar     {
58698aefe7cSBram Moolenaar 	if (!HASHITEM_EMPTY(hi))
58798aefe7cSBram Moolenaar 	{
58898aefe7cSBram Moolenaar 	    proptype_T *prop = HI2PT(hi);
58998aefe7cSBram Moolenaar 
59098aefe7cSBram Moolenaar 	    if (prop->pt_id == id)
59198aefe7cSBram Moolenaar 		return prop;
59298aefe7cSBram Moolenaar 	    --todo;
59398aefe7cSBram Moolenaar 	}
59498aefe7cSBram Moolenaar     }
59598aefe7cSBram Moolenaar     return NULL;
59698aefe7cSBram Moolenaar }
59798aefe7cSBram Moolenaar 
59898aefe7cSBram Moolenaar /*
599e05a89acSBram Moolenaar  * Fill 'dict' with text properties in 'prop'.
600e05a89acSBram Moolenaar  */
601e05a89acSBram Moolenaar     static void
prop_fill_dict(dict_T * dict,textprop_T * prop,buf_T * buf)602e05a89acSBram Moolenaar prop_fill_dict(dict_T *dict, textprop_T *prop, buf_T *buf)
603e05a89acSBram Moolenaar {
604e05a89acSBram Moolenaar     proptype_T *pt;
605e2390c7fSMartin Tournoij     int buflocal = TRUE;
606e05a89acSBram Moolenaar 
607e05a89acSBram Moolenaar     dict_add_number(dict, "col", prop->tp_col);
608e05a89acSBram Moolenaar     dict_add_number(dict, "length", prop->tp_len);
609e05a89acSBram Moolenaar     dict_add_number(dict, "id", prop->tp_id);
610e05a89acSBram Moolenaar     dict_add_number(dict, "start", !(prop->tp_flags & TP_FLAG_CONT_PREV));
611e05a89acSBram Moolenaar     dict_add_number(dict, "end", !(prop->tp_flags & TP_FLAG_CONT_NEXT));
612e2390c7fSMartin Tournoij 
613e2390c7fSMartin Tournoij     pt = find_type_by_id(buf->b_proptypes, prop->tp_type);
614e2390c7fSMartin Tournoij     if (pt == NULL)
615e2390c7fSMartin Tournoij     {
616e2390c7fSMartin Tournoij 	pt = find_type_by_id(global_proptypes, prop->tp_type);
617e2390c7fSMartin Tournoij 	buflocal = FALSE;
618e2390c7fSMartin Tournoij     }
619e05a89acSBram Moolenaar     if (pt != NULL)
620e05a89acSBram Moolenaar 	dict_add_string(dict, "type", pt->pt_name);
621e2390c7fSMartin Tournoij 
622e2390c7fSMartin Tournoij     if (buflocal)
623e2390c7fSMartin Tournoij 	dict_add_number(dict, "type_bufnr", buf->b_fnum);
624e2390c7fSMartin Tournoij     else
625e2390c7fSMartin Tournoij 	dict_add_number(dict, "type_bufnr", 0);
626e05a89acSBram Moolenaar }
627e05a89acSBram Moolenaar 
628e05a89acSBram Moolenaar /*
62998aefe7cSBram Moolenaar  * Find a property type by ID in "buf" or globally.
63098aefe7cSBram Moolenaar  * Returns NULL if not found.
63198aefe7cSBram Moolenaar  */
63298aefe7cSBram Moolenaar     proptype_T *
text_prop_type_by_id(buf_T * buf,int id)63398aefe7cSBram Moolenaar text_prop_type_by_id(buf_T *buf, int id)
63498aefe7cSBram Moolenaar {
63598aefe7cSBram Moolenaar     proptype_T *type;
63698aefe7cSBram Moolenaar 
63798aefe7cSBram Moolenaar     type = find_type_by_id(buf->b_proptypes, id);
63898aefe7cSBram Moolenaar     if (type == NULL)
63998aefe7cSBram Moolenaar 	type = find_type_by_id(global_proptypes, id);
64098aefe7cSBram Moolenaar     return type;
64198aefe7cSBram Moolenaar }
64298aefe7cSBram Moolenaar 
64398aefe7cSBram Moolenaar /*
64498aefe7cSBram Moolenaar  * prop_clear({lnum} [, {lnum_end} [, {bufnr}]])
64598aefe7cSBram Moolenaar  */
64698aefe7cSBram Moolenaar     void
f_prop_clear(typval_T * argvars,typval_T * rettv UNUSED)64798aefe7cSBram Moolenaar f_prop_clear(typval_T *argvars, typval_T *rettv UNUSED)
64898aefe7cSBram Moolenaar {
64983494b4aSYegappan Lakshmanan     linenr_T start;
65083494b4aSYegappan Lakshmanan     linenr_T end;
65198aefe7cSBram Moolenaar     linenr_T lnum;
65298aefe7cSBram Moolenaar     buf_T    *buf = curbuf;
653da1dbed0SBram Moolenaar     int	    did_clear = FALSE;
65498aefe7cSBram Moolenaar 
65583494b4aSYegappan Lakshmanan     if (in_vim9script()
65683494b4aSYegappan Lakshmanan 	    && (check_for_number_arg(argvars, 0) == FAIL
65783494b4aSYegappan Lakshmanan 		|| check_for_opt_number_arg(argvars, 1) == FAIL
65883494b4aSYegappan Lakshmanan 		|| (argvars[1].v_type != VAR_UNKNOWN
65983494b4aSYegappan Lakshmanan 		    && check_for_opt_dict_arg(argvars, 2) == FAIL)))
66083494b4aSYegappan Lakshmanan 	return;
66183494b4aSYegappan Lakshmanan 
66283494b4aSYegappan Lakshmanan     start = tv_get_number(&argvars[0]);
66383494b4aSYegappan Lakshmanan     end = start;
66498aefe7cSBram Moolenaar     if (argvars[1].v_type != VAR_UNKNOWN)
66598aefe7cSBram Moolenaar     {
666d155d7a8SBram Moolenaar 	end = tv_get_number(&argvars[1]);
66798aefe7cSBram Moolenaar 	if (argvars[2].v_type != VAR_UNKNOWN)
66898aefe7cSBram Moolenaar 	{
66998aefe7cSBram Moolenaar 	    if (get_bufnr_from_arg(&argvars[2], &buf) == FAIL)
67098aefe7cSBram Moolenaar 		return;
67198aefe7cSBram Moolenaar 	}
67298aefe7cSBram Moolenaar     }
67398aefe7cSBram Moolenaar     if (start < 1 || end < 1)
67498aefe7cSBram Moolenaar     {
675108010aaSBram Moolenaar 	emsg(_(e_invalid_range));
67698aefe7cSBram Moolenaar 	return;
67798aefe7cSBram Moolenaar     }
67898aefe7cSBram Moolenaar 
67998aefe7cSBram Moolenaar     for (lnum = start; lnum <= end; ++lnum)
68098aefe7cSBram Moolenaar     {
68198aefe7cSBram Moolenaar 	char_u *text;
68298aefe7cSBram Moolenaar 	size_t len;
68398aefe7cSBram Moolenaar 
68498aefe7cSBram Moolenaar 	if (lnum > buf->b_ml.ml_line_count)
68598aefe7cSBram Moolenaar 	    break;
68698aefe7cSBram Moolenaar 	text = ml_get_buf(buf, lnum, FALSE);
68798aefe7cSBram Moolenaar 	len = STRLEN(text) + 1;
68898aefe7cSBram Moolenaar 	if ((size_t)buf->b_ml.ml_line_len > len)
68998aefe7cSBram Moolenaar 	{
690da1dbed0SBram Moolenaar 	    did_clear = TRUE;
69198aefe7cSBram Moolenaar 	    if (!(buf->b_ml.ml_flags & ML_LINE_DIRTY))
69298aefe7cSBram Moolenaar 	    {
69398aefe7cSBram Moolenaar 		char_u *newtext = vim_strsave(text);
69498aefe7cSBram Moolenaar 
69598aefe7cSBram Moolenaar 		// need to allocate the line now
69698aefe7cSBram Moolenaar 		if (newtext == NULL)
69798aefe7cSBram Moolenaar 		    return;
69898aefe7cSBram Moolenaar 		buf->b_ml.ml_line_ptr = newtext;
69998aefe7cSBram Moolenaar 		buf->b_ml.ml_flags |= ML_LINE_DIRTY;
70098aefe7cSBram Moolenaar 	    }
7014efe73b4SBram Moolenaar 	    buf->b_ml.ml_line_len = (int)len;
70298aefe7cSBram Moolenaar 	}
70398aefe7cSBram Moolenaar     }
704da1dbed0SBram Moolenaar     if (did_clear)
70598aefe7cSBram Moolenaar 	redraw_buf_later(buf, NOT_VALID);
70698aefe7cSBram Moolenaar }
70798aefe7cSBram Moolenaar 
70898aefe7cSBram Moolenaar /*
709e05a89acSBram Moolenaar  * prop_find({props} [, {direction}])
710e05a89acSBram Moolenaar  */
711e05a89acSBram Moolenaar     void
f_prop_find(typval_T * argvars,typval_T * rettv)712e05a89acSBram Moolenaar f_prop_find(typval_T *argvars, typval_T *rettv)
713e05a89acSBram Moolenaar {
714e05a89acSBram Moolenaar     pos_T       *cursor = &curwin->w_cursor;
715e05a89acSBram Moolenaar     dict_T      *dict;
716e05a89acSBram Moolenaar     buf_T       *buf = curbuf;
717e05a89acSBram Moolenaar     dictitem_T  *di;
718e05a89acSBram Moolenaar     int		lnum_start;
719e05a89acSBram Moolenaar     int		start_pos_has_prop = 0;
720e05a89acSBram Moolenaar     int		seen_end = 0;
7210d4d9ee9SBram Moolenaar     int		id = 0;
7220d4d9ee9SBram Moolenaar     int		id_found = FALSE;
723e05a89acSBram Moolenaar     int		type_id = -1;
724e05a89acSBram Moolenaar     int		skipstart = 0;
725e05a89acSBram Moolenaar     int		lnum = -1;
726e05a89acSBram Moolenaar     int		col = -1;
727e05a89acSBram Moolenaar     int		dir = 1;    // 1 = forward, -1 = backward
72824f21fdfSBram Moolenaar     int		both;
729e05a89acSBram Moolenaar 
7304490ec4eSYegappan Lakshmanan     if (in_vim9script()
7314490ec4eSYegappan Lakshmanan 	    && (check_for_dict_arg(argvars, 0) == FAIL
7324490ec4eSYegappan Lakshmanan 		|| check_for_opt_string_arg(argvars, 1) == FAIL))
7334490ec4eSYegappan Lakshmanan 	return;
7344490ec4eSYegappan Lakshmanan 
735e05a89acSBram Moolenaar     if (argvars[0].v_type != VAR_DICT || argvars[0].vval.v_dict == NULL)
736e05a89acSBram Moolenaar     {
7375b73992dSYegappan Lakshmanan 	emsg(_(e_dictreq));
738e05a89acSBram Moolenaar 	return;
739e05a89acSBram Moolenaar     }
740e05a89acSBram Moolenaar     dict = argvars[0].vval.v_dict;
741e05a89acSBram Moolenaar 
742e05a89acSBram Moolenaar     if (get_bufnr_from_arg(&argvars[0], &buf) == FAIL)
743e05a89acSBram Moolenaar 	return;
744e05a89acSBram Moolenaar     if (buf->b_ml.ml_mfp == NULL)
745e05a89acSBram Moolenaar 	return;
746e05a89acSBram Moolenaar 
747e05a89acSBram Moolenaar     if (argvars[1].v_type != VAR_UNKNOWN)
748e05a89acSBram Moolenaar     {
749e05a89acSBram Moolenaar 	char_u      *dir_s = tv_get_string(&argvars[1]);
750e05a89acSBram Moolenaar 
751e05a89acSBram Moolenaar 	if (*dir_s == 'b')
752e05a89acSBram Moolenaar 	    dir = -1;
753e05a89acSBram Moolenaar 	else if (*dir_s != 'f')
754e05a89acSBram Moolenaar 	{
755e05a89acSBram Moolenaar 	    emsg(_(e_invarg));
756e05a89acSBram Moolenaar 	    return;
757e05a89acSBram Moolenaar 	}
758e05a89acSBram Moolenaar     }
759e05a89acSBram Moolenaar 
760e05a89acSBram Moolenaar     di = dict_find(dict, (char_u *)"lnum", -1);
761e05a89acSBram Moolenaar     if (di != NULL)
762e05a89acSBram Moolenaar 	lnum = tv_get_number(&di->di_tv);
763e05a89acSBram Moolenaar 
764e05a89acSBram Moolenaar     di = dict_find(dict, (char_u *)"col", -1);
765e05a89acSBram Moolenaar     if (di != NULL)
766e05a89acSBram Moolenaar 	col = tv_get_number(&di->di_tv);
767e05a89acSBram Moolenaar 
768e05a89acSBram Moolenaar     if (lnum == -1)
769e05a89acSBram Moolenaar     {
770e05a89acSBram Moolenaar 	lnum = cursor->lnum;
771e05a89acSBram Moolenaar 	col = cursor->col + 1;
772e05a89acSBram Moolenaar     }
773e05a89acSBram Moolenaar     else if (col == -1)
774e05a89acSBram Moolenaar 	col = 1;
775e05a89acSBram Moolenaar 
776e05a89acSBram Moolenaar     if (lnum < 1 || lnum > buf->b_ml.ml_line_count)
777e05a89acSBram Moolenaar     {
778108010aaSBram Moolenaar 	emsg(_(e_invalid_range));
779e05a89acSBram Moolenaar 	return;
780e05a89acSBram Moolenaar     }
781e05a89acSBram Moolenaar 
782eb24556dSBram Moolenaar     skipstart = dict_get_bool(dict, (char_u *)"skipstart", 0);
783e05a89acSBram Moolenaar 
784e05a89acSBram Moolenaar     if (dict_find(dict, (char_u *)"id", -1) != NULL)
7858e3fc135SBram Moolenaar     {
786e05a89acSBram Moolenaar 	id = dict_get_number(dict, (char_u *)"id");
787e041dde7SBram Moolenaar 	id_found = TRUE;
7888e3fc135SBram Moolenaar     }
789e05a89acSBram Moolenaar     if (dict_find(dict, (char_u *)"type", -1))
790e05a89acSBram Moolenaar     {
791e05a89acSBram Moolenaar 	char_u	    *name = dict_get_string(dict, (char_u *)"type", FALSE);
792e05a89acSBram Moolenaar 	proptype_T  *type = lookup_prop_type(name, buf);
793e05a89acSBram Moolenaar 
794e05a89acSBram Moolenaar 	if (type == NULL)
795e05a89acSBram Moolenaar 	    return;
796e05a89acSBram Moolenaar 	type_id = type->pt_id;
797e05a89acSBram Moolenaar     }
79824f21fdfSBram Moolenaar     both = dict_get_bool(dict, (char_u *)"both", FALSE);
7990d4d9ee9SBram Moolenaar     if (!id_found && type_id == -1)
800e05a89acSBram Moolenaar     {
801e05a89acSBram Moolenaar 	emsg(_("E968: Need at least one of 'id' or 'type'"));
802e05a89acSBram Moolenaar 	return;
803e05a89acSBram Moolenaar     }
8040d4d9ee9SBram Moolenaar     if (both && (!id_found || type_id == -1))
80524f21fdfSBram Moolenaar     {
80624f21fdfSBram Moolenaar 	emsg(_("E860: Need 'id' and 'type' with 'both'"));
80724f21fdfSBram Moolenaar 	return;
80824f21fdfSBram Moolenaar     }
809e05a89acSBram Moolenaar 
810e05a89acSBram Moolenaar     lnum_start = lnum;
811e05a89acSBram Moolenaar 
812e05a89acSBram Moolenaar     if (rettv_dict_alloc(rettv) == FAIL)
813e05a89acSBram Moolenaar 	return;
814e05a89acSBram Moolenaar 
815e05a89acSBram Moolenaar     while (1)
816e05a89acSBram Moolenaar     {
817e05a89acSBram Moolenaar 	char_u	*text = ml_get_buf(buf, lnum, FALSE);
818e05a89acSBram Moolenaar 	size_t	textlen = STRLEN(text) + 1;
819e05a89acSBram Moolenaar 	int	count = (int)((buf->b_ml.ml_line_len - textlen)
820e05a89acSBram Moolenaar 							 / sizeof(textprop_T));
821e05a89acSBram Moolenaar 	int	    i;
822e05a89acSBram Moolenaar 	textprop_T  prop;
823e05a89acSBram Moolenaar 	int	    prop_start;
824e05a89acSBram Moolenaar 	int	    prop_end;
825e05a89acSBram Moolenaar 
826e05a89acSBram Moolenaar 	for (i = 0; i < count; ++i)
827e05a89acSBram Moolenaar 	{
828e05a89acSBram Moolenaar 	    mch_memmove(&prop, text + textlen + i * sizeof(textprop_T),
829e05a89acSBram Moolenaar 			    sizeof(textprop_T));
830e05a89acSBram Moolenaar 
831346f18e5SBram Moolenaar 	    if (lnum == lnum_start)
832965fd8d4SBram Moolenaar 	    {
83366b98854SBram Moolenaar 		if (dir < 0)
83466b98854SBram Moolenaar 		{
83566b98854SBram Moolenaar 		    if (col < prop.tp_col)
83666b98854SBram Moolenaar 			break;
83766b98854SBram Moolenaar 		}
83866b98854SBram Moolenaar 		else if (prop.tp_col + prop.tp_len - (prop.tp_len != 0) < col)
83966b98854SBram Moolenaar 		    continue;
840965fd8d4SBram Moolenaar 	    }
84124f21fdfSBram Moolenaar 	    if (both ? prop.tp_id == id && prop.tp_type == type_id
8420d4d9ee9SBram Moolenaar 		     : (id_found && prop.tp_id == id)
8430d4d9ee9SBram Moolenaar 						    || prop.tp_type == type_id)
844e05a89acSBram Moolenaar 	    {
845e05a89acSBram Moolenaar 		// Check if the starting position has text props.
84666b98854SBram Moolenaar 		if (lnum_start == lnum
84766b98854SBram Moolenaar 			&& col >= prop.tp_col
84866b98854SBram Moolenaar 			&& (col <= prop.tp_col + prop.tp_len
84966b98854SBram Moolenaar 							 - (prop.tp_len != 0)))
850e05a89acSBram Moolenaar 		    start_pos_has_prop = 1;
851e05a89acSBram Moolenaar 
852e05a89acSBram Moolenaar 		prop_start = !(prop.tp_flags & TP_FLAG_CONT_PREV);
853e05a89acSBram Moolenaar 		prop_end = !(prop.tp_flags & TP_FLAG_CONT_NEXT);
854e05a89acSBram Moolenaar 		if (!prop_start && prop_end && dir > 0)
855e05a89acSBram Moolenaar 		    seen_end = 1;
856e05a89acSBram Moolenaar 
857e05a89acSBram Moolenaar 		// Skip lines without the start flag.
858e05a89acSBram Moolenaar 		if (!prop_start)
859e05a89acSBram Moolenaar 		{
860e05a89acSBram Moolenaar 		    // Always search backwards for start when search started
861e05a89acSBram Moolenaar 		    // on a prop and we're not skipping.
862e05a89acSBram Moolenaar 		    if (start_pos_has_prop && !skipstart)
863e05a89acSBram Moolenaar 			dir = -1;
8644da7a259SBram Moolenaar 		    continue;
865e05a89acSBram Moolenaar 		}
866e05a89acSBram Moolenaar 
867e05a89acSBram Moolenaar 		// If skipstart is true, skip the prop at start pos (even if
868e05a89acSBram Moolenaar 		// continued from another line).
869e05a89acSBram Moolenaar 		if (start_pos_has_prop && skipstart && !seen_end)
870e05a89acSBram Moolenaar 		{
871e05a89acSBram Moolenaar 		    start_pos_has_prop = 0;
8724da7a259SBram Moolenaar 		    continue;
873e05a89acSBram Moolenaar 		}
874e05a89acSBram Moolenaar 
875e05a89acSBram Moolenaar 		prop_fill_dict(rettv->vval.v_dict, &prop, buf);
876e05a89acSBram Moolenaar 		dict_add_number(rettv->vval.v_dict, "lnum", lnum);
877e05a89acSBram Moolenaar 
878e05a89acSBram Moolenaar 		return;
879e05a89acSBram Moolenaar 	    }
880e05a89acSBram Moolenaar 	}
881e05a89acSBram Moolenaar 
882e05a89acSBram Moolenaar 	if (dir > 0)
883e05a89acSBram Moolenaar 	{
884e05a89acSBram Moolenaar 	    if (lnum >= buf->b_ml.ml_line_count)
885e05a89acSBram Moolenaar 		break;
886e05a89acSBram Moolenaar 	    lnum++;
887e05a89acSBram Moolenaar 	}
888e05a89acSBram Moolenaar 	else
889e05a89acSBram Moolenaar 	{
890e05a89acSBram Moolenaar 	    if (lnum <= 1)
891e05a89acSBram Moolenaar 		break;
892e05a89acSBram Moolenaar 	    lnum--;
893e05a89acSBram Moolenaar 	}
89466b98854SBram Moolenaar 	// Adjust col to indicate that we're continuing from prev/next line.
89566b98854SBram Moolenaar 	col = dir < 0 ? buf->b_ml.ml_line_len : 1;
896e05a89acSBram Moolenaar     }
897e05a89acSBram Moolenaar }
898e05a89acSBram Moolenaar 
899e05a89acSBram Moolenaar /*
90098aefe7cSBram Moolenaar  * prop_list({lnum} [, {bufnr}])
90198aefe7cSBram Moolenaar  */
90298aefe7cSBram Moolenaar     void
f_prop_list(typval_T * argvars,typval_T * rettv)90398aefe7cSBram Moolenaar f_prop_list(typval_T *argvars, typval_T *rettv)
90498aefe7cSBram Moolenaar {
9051a71d31bSYegappan Lakshmanan     linenr_T lnum;
90698aefe7cSBram Moolenaar     buf_T    *buf = curbuf;
90798aefe7cSBram Moolenaar 
9081a71d31bSYegappan Lakshmanan     if (in_vim9script()
9091a71d31bSYegappan Lakshmanan 	    && (check_for_number_arg(argvars, 0) == FAIL
91083494b4aSYegappan Lakshmanan 		|| check_for_opt_dict_arg(argvars, 1) == FAIL))
9111a71d31bSYegappan Lakshmanan 	return;
9121a71d31bSYegappan Lakshmanan 
9131a71d31bSYegappan Lakshmanan     lnum = tv_get_number(&argvars[0]);
91498aefe7cSBram Moolenaar     if (argvars[1].v_type != VAR_UNKNOWN)
91598aefe7cSBram Moolenaar     {
91698aefe7cSBram Moolenaar 	if (get_bufnr_from_arg(&argvars[1], &buf) == FAIL)
91798aefe7cSBram Moolenaar 	    return;
91898aefe7cSBram Moolenaar     }
91998aefe7cSBram Moolenaar     if (lnum < 1 || lnum > buf->b_ml.ml_line_count)
92098aefe7cSBram Moolenaar     {
921108010aaSBram Moolenaar 	emsg(_(e_invalid_range));
92298aefe7cSBram Moolenaar 	return;
92398aefe7cSBram Moolenaar     }
92498aefe7cSBram Moolenaar 
92598aefe7cSBram Moolenaar     if (rettv_list_alloc(rettv) == OK)
92698aefe7cSBram Moolenaar     {
92798aefe7cSBram Moolenaar 	char_u	    *text = ml_get_buf(buf, lnum, FALSE);
92898aefe7cSBram Moolenaar 	size_t	    textlen = STRLEN(text) + 1;
9294efe73b4SBram Moolenaar 	int	    count = (int)((buf->b_ml.ml_line_len - textlen)
9304efe73b4SBram Moolenaar 							 / sizeof(textprop_T));
93198aefe7cSBram Moolenaar 	int	    i;
93298aefe7cSBram Moolenaar 	textprop_T  prop;
93398aefe7cSBram Moolenaar 
93498aefe7cSBram Moolenaar 	for (i = 0; i < count; ++i)
93598aefe7cSBram Moolenaar 	{
93698aefe7cSBram Moolenaar 	    dict_T *d = dict_alloc();
93798aefe7cSBram Moolenaar 
93898aefe7cSBram Moolenaar 	    if (d == NULL)
93998aefe7cSBram Moolenaar 		break;
94098aefe7cSBram Moolenaar 	    mch_memmove(&prop, text + textlen + i * sizeof(textprop_T),
94198aefe7cSBram Moolenaar 							   sizeof(textprop_T));
942e05a89acSBram Moolenaar 	    prop_fill_dict(d, &prop, buf);
94398aefe7cSBram Moolenaar 	    list_append_dict(rettv->vval.v_list, d);
94498aefe7cSBram Moolenaar 	}
94598aefe7cSBram Moolenaar     }
94698aefe7cSBram Moolenaar }
94798aefe7cSBram Moolenaar 
94898aefe7cSBram Moolenaar /*
94998aefe7cSBram Moolenaar  * prop_remove({props} [, {lnum} [, {lnum_end}]])
95098aefe7cSBram Moolenaar  */
95198aefe7cSBram Moolenaar     void
f_prop_remove(typval_T * argvars,typval_T * rettv)95298aefe7cSBram Moolenaar f_prop_remove(typval_T *argvars, typval_T *rettv)
95398aefe7cSBram Moolenaar {
95498aefe7cSBram Moolenaar     linenr_T	start = 1;
95598aefe7cSBram Moolenaar     linenr_T	end = 0;
95698aefe7cSBram Moolenaar     linenr_T	lnum;
957965c0448SBram Moolenaar     linenr_T	first_changed = 0;
958965c0448SBram Moolenaar     linenr_T	last_changed = 0;
95998aefe7cSBram Moolenaar     dict_T	*dict;
96098aefe7cSBram Moolenaar     buf_T	*buf = curbuf;
961a5a40c56SBram Moolenaar     int		do_all;
96298aefe7cSBram Moolenaar     int		id = -1;
96398aefe7cSBram Moolenaar     int		type_id = -1;
964a5a40c56SBram Moolenaar     int		both;
96598aefe7cSBram Moolenaar 
96698aefe7cSBram Moolenaar     rettv->vval.v_number = 0;
96783494b4aSYegappan Lakshmanan 
96883494b4aSYegappan Lakshmanan     if (in_vim9script()
96983494b4aSYegappan Lakshmanan 	    && (check_for_dict_arg(argvars, 0) == FAIL
97083494b4aSYegappan Lakshmanan 		|| check_for_opt_number_arg(argvars, 1) == FAIL
97183494b4aSYegappan Lakshmanan 		|| (argvars[1].v_type != VAR_UNKNOWN
97283494b4aSYegappan Lakshmanan 		    && check_for_opt_number_arg(argvars, 2) == FAIL)))
97383494b4aSYegappan Lakshmanan 	return;
97483494b4aSYegappan Lakshmanan 
97598aefe7cSBram Moolenaar     if (argvars[0].v_type != VAR_DICT || argvars[0].vval.v_dict == NULL)
97698aefe7cSBram Moolenaar     {
977f9e3e09fSBram Moolenaar 	emsg(_(e_invarg));
97898aefe7cSBram Moolenaar 	return;
97998aefe7cSBram Moolenaar     }
98098aefe7cSBram Moolenaar 
98198aefe7cSBram Moolenaar     if (argvars[1].v_type != VAR_UNKNOWN)
98298aefe7cSBram Moolenaar     {
983d155d7a8SBram Moolenaar 	start = tv_get_number(&argvars[1]);
98498aefe7cSBram Moolenaar 	end = start;
98598aefe7cSBram Moolenaar 	if (argvars[2].v_type != VAR_UNKNOWN)
986d155d7a8SBram Moolenaar 	    end = tv_get_number(&argvars[2]);
98798aefe7cSBram Moolenaar 	if (start < 1 || end < 1)
98898aefe7cSBram Moolenaar 	{
989108010aaSBram Moolenaar 	    emsg(_(e_invalid_range));
99098aefe7cSBram Moolenaar 	    return;
99198aefe7cSBram Moolenaar 	}
99298aefe7cSBram Moolenaar     }
99398aefe7cSBram Moolenaar 
99498aefe7cSBram Moolenaar     dict = argvars[0].vval.v_dict;
995f0884c5fSBram Moolenaar     if (get_bufnr_from_arg(&argvars[0], &buf) == FAIL)
99698aefe7cSBram Moolenaar 	return;
997f0884c5fSBram Moolenaar     if (buf->b_ml.ml_mfp == NULL)
998f0884c5fSBram Moolenaar 	return;
99998aefe7cSBram Moolenaar 
1000a5a40c56SBram Moolenaar     do_all = dict_get_bool(dict, (char_u *)"all", FALSE);
100198aefe7cSBram Moolenaar 
100298aefe7cSBram Moolenaar     if (dict_find(dict, (char_u *)"id", -1) != NULL)
10038f66717aSBram Moolenaar 	id = dict_get_number(dict, (char_u *)"id");
100498aefe7cSBram Moolenaar     if (dict_find(dict, (char_u *)"type", -1))
100598aefe7cSBram Moolenaar     {
10068f66717aSBram Moolenaar 	char_u	    *name = dict_get_string(dict, (char_u *)"type", FALSE);
100798aefe7cSBram Moolenaar 	proptype_T  *type = lookup_prop_type(name, buf);
100898aefe7cSBram Moolenaar 
100998aefe7cSBram Moolenaar 	if (type == NULL)
101098aefe7cSBram Moolenaar 	    return;
101198aefe7cSBram Moolenaar 	type_id = type->pt_id;
101298aefe7cSBram Moolenaar     }
1013a5a40c56SBram Moolenaar     both = dict_get_bool(dict, (char_u *)"both", FALSE);
1014a5a40c56SBram Moolenaar 
101598aefe7cSBram Moolenaar     if (id == -1 && type_id == -1)
101698aefe7cSBram Moolenaar     {
1017f9e3e09fSBram Moolenaar 	emsg(_("E968: Need at least one of 'id' or 'type'"));
101898aefe7cSBram Moolenaar 	return;
101998aefe7cSBram Moolenaar     }
102049b79bd4SBram Moolenaar     if (both && (id == -1 || type_id == -1))
102149b79bd4SBram Moolenaar     {
102249b79bd4SBram Moolenaar 	emsg(_("E860: Need 'id' and 'type' with 'both'"));
102349b79bd4SBram Moolenaar 	return;
102449b79bd4SBram Moolenaar     }
102598aefe7cSBram Moolenaar 
102698aefe7cSBram Moolenaar     if (end == 0)
102798aefe7cSBram Moolenaar 	end = buf->b_ml.ml_line_count;
102898aefe7cSBram Moolenaar     for (lnum = start; lnum <= end; ++lnum)
102998aefe7cSBram Moolenaar     {
103098aefe7cSBram Moolenaar 	char_u *text;
103198aefe7cSBram Moolenaar 	size_t len;
103298aefe7cSBram Moolenaar 
103398aefe7cSBram Moolenaar 	if (lnum > buf->b_ml.ml_line_count)
103498aefe7cSBram Moolenaar 	    break;
103598aefe7cSBram Moolenaar 	text = ml_get_buf(buf, lnum, FALSE);
103698aefe7cSBram Moolenaar 	len = STRLEN(text) + 1;
103798aefe7cSBram Moolenaar 	if ((size_t)buf->b_ml.ml_line_len > len)
103898aefe7cSBram Moolenaar 	{
103998aefe7cSBram Moolenaar 	    static textprop_T	textprop;  // static because of alignment
104098aefe7cSBram Moolenaar 	    unsigned		idx;
104198aefe7cSBram Moolenaar 
104298aefe7cSBram Moolenaar 	    for (idx = 0; idx < (buf->b_ml.ml_line_len - len)
104398aefe7cSBram Moolenaar 						   / sizeof(textprop_T); ++idx)
104498aefe7cSBram Moolenaar 	    {
104598aefe7cSBram Moolenaar 		char_u *cur_prop = buf->b_ml.ml_line_ptr + len
104698aefe7cSBram Moolenaar 						    + idx * sizeof(textprop_T);
104798aefe7cSBram Moolenaar 		size_t	taillen;
104898aefe7cSBram Moolenaar 
104998aefe7cSBram Moolenaar 		mch_memmove(&textprop, cur_prop, sizeof(textprop_T));
105049b79bd4SBram Moolenaar 		if (both ? textprop.tp_id == id && textprop.tp_type == type_id
105149b79bd4SBram Moolenaar 			 : textprop.tp_id == id || textprop.tp_type == type_id)
105298aefe7cSBram Moolenaar 		{
105398aefe7cSBram Moolenaar 		    if (!(buf->b_ml.ml_flags & ML_LINE_DIRTY))
105498aefe7cSBram Moolenaar 		    {
105598aefe7cSBram Moolenaar 			char_u *newptr = alloc(buf->b_ml.ml_line_len);
105698aefe7cSBram Moolenaar 
105798aefe7cSBram Moolenaar 			// need to allocate the line to be able to change it
105898aefe7cSBram Moolenaar 			if (newptr == NULL)
105998aefe7cSBram Moolenaar 			    return;
106098aefe7cSBram Moolenaar 			mch_memmove(newptr, buf->b_ml.ml_line_ptr,
106198aefe7cSBram Moolenaar 							buf->b_ml.ml_line_len);
106298aefe7cSBram Moolenaar 			buf->b_ml.ml_line_ptr = newptr;
10630a2f578eSBram Moolenaar 			buf->b_ml.ml_flags |= ML_LINE_DIRTY;
10640a2f578eSBram Moolenaar 
10650a2f578eSBram Moolenaar 			cur_prop = buf->b_ml.ml_line_ptr + len
10660a2f578eSBram Moolenaar 						    + idx * sizeof(textprop_T);
106798aefe7cSBram Moolenaar 		    }
106898aefe7cSBram Moolenaar 
106998aefe7cSBram Moolenaar 		    taillen = buf->b_ml.ml_line_len - len
107098aefe7cSBram Moolenaar 					      - (idx + 1) * sizeof(textprop_T);
107198aefe7cSBram Moolenaar 		    if (taillen > 0)
107298aefe7cSBram Moolenaar 			mch_memmove(cur_prop, cur_prop + sizeof(textprop_T),
107398aefe7cSBram Moolenaar 								      taillen);
107498aefe7cSBram Moolenaar 		    buf->b_ml.ml_line_len -= sizeof(textprop_T);
107598aefe7cSBram Moolenaar 		    --idx;
107698aefe7cSBram Moolenaar 
1077965c0448SBram Moolenaar 		    if (first_changed == 0)
1078965c0448SBram Moolenaar 			first_changed = lnum;
1079965c0448SBram Moolenaar 		    last_changed = lnum;
108098aefe7cSBram Moolenaar 		    ++rettv->vval.v_number;
108198aefe7cSBram Moolenaar 		    if (!do_all)
108298aefe7cSBram Moolenaar 			break;
108398aefe7cSBram Moolenaar 		}
108498aefe7cSBram Moolenaar 	    }
108598aefe7cSBram Moolenaar 	}
108698aefe7cSBram Moolenaar     }
1087965c0448SBram Moolenaar     if (first_changed > 0)
1088fc643e60SBram Moolenaar     {
1089965c0448SBram Moolenaar 	changed_lines_buf(buf, first_changed, last_changed + 1, 0);
1090fc643e60SBram Moolenaar 	redraw_buf_later(buf, VALID);
1091fc643e60SBram Moolenaar     }
1092fc643e60SBram Moolenaar }
109398aefe7cSBram Moolenaar 
109498aefe7cSBram Moolenaar /*
109598aefe7cSBram Moolenaar  * Common for f_prop_type_add() and f_prop_type_change().
109698aefe7cSBram Moolenaar  */
10975843f5f3SBram Moolenaar     static void
prop_type_set(typval_T * argvars,int add)109898aefe7cSBram Moolenaar prop_type_set(typval_T *argvars, int add)
109998aefe7cSBram Moolenaar {
110098aefe7cSBram Moolenaar     char_u	*name;
110198aefe7cSBram Moolenaar     buf_T	*buf = NULL;
110298aefe7cSBram Moolenaar     dict_T	*dict;
110398aefe7cSBram Moolenaar     dictitem_T  *di;
110498aefe7cSBram Moolenaar     proptype_T	*prop;
110598aefe7cSBram Moolenaar 
11064490ec4eSYegappan Lakshmanan     if (in_vim9script()
11074490ec4eSYegappan Lakshmanan 	    && (check_for_string_arg(argvars, 0) == FAIL
11084490ec4eSYegappan Lakshmanan 		|| check_for_dict_arg(argvars, 1) == FAIL))
11094490ec4eSYegappan Lakshmanan 	return;
11104490ec4eSYegappan Lakshmanan 
1111d155d7a8SBram Moolenaar     name = tv_get_string(&argvars[0]);
111298aefe7cSBram Moolenaar     if (*name == NUL)
111398aefe7cSBram Moolenaar     {
1114f9e3e09fSBram Moolenaar 	emsg(_(e_invarg));
111598aefe7cSBram Moolenaar 	return;
111698aefe7cSBram Moolenaar     }
111798aefe7cSBram Moolenaar 
111898aefe7cSBram Moolenaar     if (get_bufnr_from_arg(&argvars[1], &buf) == FAIL)
111998aefe7cSBram Moolenaar 	return;
112098aefe7cSBram Moolenaar     dict = argvars[1].vval.v_dict;
112198aefe7cSBram Moolenaar 
112298aefe7cSBram Moolenaar     prop = find_prop(name, buf);
112398aefe7cSBram Moolenaar     if (add)
112498aefe7cSBram Moolenaar     {
112598aefe7cSBram Moolenaar 	hashtab_T **htp;
112698aefe7cSBram Moolenaar 
112798aefe7cSBram Moolenaar 	if (prop != NULL)
112898aefe7cSBram Moolenaar 	{
1129f9e3e09fSBram Moolenaar 	    semsg(_("E969: Property type %s already defined"), name);
113098aefe7cSBram Moolenaar 	    return;
113198aefe7cSBram Moolenaar 	}
113247ed553fSBram Moolenaar 	prop = alloc_clear(offsetof(proptype_T, pt_name) + STRLEN(name) + 1);
113398aefe7cSBram Moolenaar 	if (prop == NULL)
113498aefe7cSBram Moolenaar 	    return;
113598aefe7cSBram Moolenaar 	STRCPY(prop->pt_name, name);
113698aefe7cSBram Moolenaar 	prop->pt_id = ++proptype_id;
11370743ef9fSBram Moolenaar 	prop->pt_flags = PT_FLAG_COMBINE;
113898aefe7cSBram Moolenaar 	htp = buf == NULL ? &global_proptypes : &buf->b_proptypes;
113998aefe7cSBram Moolenaar 	if (*htp == NULL)
114098aefe7cSBram Moolenaar 	{
1141c799fe20SBram Moolenaar 	    *htp = ALLOC_ONE(hashtab_T);
114298aefe7cSBram Moolenaar 	    if (*htp == NULL)
1143fb95e212SBram Moolenaar 	    {
1144fb95e212SBram Moolenaar 		vim_free(prop);
114598aefe7cSBram Moolenaar 		return;
1146fb95e212SBram Moolenaar 	    }
114798aefe7cSBram Moolenaar 	    hash_init(*htp);
114898aefe7cSBram Moolenaar 	}
1149fb95e212SBram Moolenaar 	hash_add(*htp, PT2HIKEY(prop));
115098aefe7cSBram Moolenaar     }
115198aefe7cSBram Moolenaar     else
115298aefe7cSBram Moolenaar     {
115398aefe7cSBram Moolenaar 	if (prop == NULL)
115498aefe7cSBram Moolenaar 	{
1155f9e3e09fSBram Moolenaar 	    semsg(_(e_type_not_exist), name);
115698aefe7cSBram Moolenaar 	    return;
115798aefe7cSBram Moolenaar 	}
115898aefe7cSBram Moolenaar     }
115998aefe7cSBram Moolenaar 
116098aefe7cSBram Moolenaar     if (dict != NULL)
116198aefe7cSBram Moolenaar     {
116298aefe7cSBram Moolenaar 	di = dict_find(dict, (char_u *)"highlight", -1);
116398aefe7cSBram Moolenaar 	if (di != NULL)
116498aefe7cSBram Moolenaar 	{
116598aefe7cSBram Moolenaar 	    char_u	*highlight;
116698aefe7cSBram Moolenaar 	    int		hl_id = 0;
116798aefe7cSBram Moolenaar 
11688f66717aSBram Moolenaar 	    highlight = dict_get_string(dict, (char_u *)"highlight", FALSE);
116998aefe7cSBram Moolenaar 	    if (highlight != NULL && *highlight != NUL)
117098aefe7cSBram Moolenaar 		hl_id = syn_name2id(highlight);
117198aefe7cSBram Moolenaar 	    if (hl_id <= 0)
117298aefe7cSBram Moolenaar 	    {
1173f9e3e09fSBram Moolenaar 		semsg(_("E970: Unknown highlight group name: '%s'"),
117498aefe7cSBram Moolenaar 			highlight == NULL ? (char_u *)"" : highlight);
117598aefe7cSBram Moolenaar 		return;
117698aefe7cSBram Moolenaar 	    }
117798aefe7cSBram Moolenaar 	    prop->pt_hl_id = hl_id;
117898aefe7cSBram Moolenaar 	}
117998aefe7cSBram Moolenaar 
118058187f1cSBram Moolenaar 	di = dict_find(dict, (char_u *)"combine", -1);
118158187f1cSBram Moolenaar 	if (di != NULL)
118258187f1cSBram Moolenaar 	{
1183fa2e38dfSBram Moolenaar 	    if (tv_get_bool(&di->di_tv))
118458187f1cSBram Moolenaar 		prop->pt_flags |= PT_FLAG_COMBINE;
118558187f1cSBram Moolenaar 	    else
118658187f1cSBram Moolenaar 		prop->pt_flags &= ~PT_FLAG_COMBINE;
118758187f1cSBram Moolenaar 	}
118858187f1cSBram Moolenaar 
118998aefe7cSBram Moolenaar 	di = dict_find(dict, (char_u *)"priority", -1);
119098aefe7cSBram Moolenaar 	if (di != NULL)
1191d155d7a8SBram Moolenaar 	    prop->pt_priority = tv_get_number(&di->di_tv);
119298aefe7cSBram Moolenaar 
119398aefe7cSBram Moolenaar 	di = dict_find(dict, (char_u *)"start_incl", -1);
119498aefe7cSBram Moolenaar 	if (di != NULL)
119598aefe7cSBram Moolenaar 	{
1196fa2e38dfSBram Moolenaar 	    if (tv_get_bool(&di->di_tv))
119798aefe7cSBram Moolenaar 		prop->pt_flags |= PT_FLAG_INS_START_INCL;
119898aefe7cSBram Moolenaar 	    else
119998aefe7cSBram Moolenaar 		prop->pt_flags &= ~PT_FLAG_INS_START_INCL;
120098aefe7cSBram Moolenaar 	}
120198aefe7cSBram Moolenaar 
120298aefe7cSBram Moolenaar 	di = dict_find(dict, (char_u *)"end_incl", -1);
120398aefe7cSBram Moolenaar 	if (di != NULL)
120498aefe7cSBram Moolenaar 	{
1205fa2e38dfSBram Moolenaar 	    if (tv_get_bool(&di->di_tv))
120698aefe7cSBram Moolenaar 		prop->pt_flags |= PT_FLAG_INS_END_INCL;
120798aefe7cSBram Moolenaar 	    else
120898aefe7cSBram Moolenaar 		prop->pt_flags &= ~PT_FLAG_INS_END_INCL;
120998aefe7cSBram Moolenaar 	}
121098aefe7cSBram Moolenaar     }
121198aefe7cSBram Moolenaar }
121298aefe7cSBram Moolenaar 
121398aefe7cSBram Moolenaar /*
121498aefe7cSBram Moolenaar  * prop_type_add({name}, {props})
121598aefe7cSBram Moolenaar  */
121698aefe7cSBram Moolenaar     void
f_prop_type_add(typval_T * argvars,typval_T * rettv UNUSED)121798aefe7cSBram Moolenaar f_prop_type_add(typval_T *argvars, typval_T *rettv UNUSED)
121898aefe7cSBram Moolenaar {
121998aefe7cSBram Moolenaar     prop_type_set(argvars, TRUE);
122098aefe7cSBram Moolenaar }
122198aefe7cSBram Moolenaar 
122298aefe7cSBram Moolenaar /*
122398aefe7cSBram Moolenaar  * prop_type_change({name}, {props})
122498aefe7cSBram Moolenaar  */
122598aefe7cSBram Moolenaar     void
f_prop_type_change(typval_T * argvars,typval_T * rettv UNUSED)122698aefe7cSBram Moolenaar f_prop_type_change(typval_T *argvars, typval_T *rettv UNUSED)
122798aefe7cSBram Moolenaar {
122898aefe7cSBram Moolenaar     prop_type_set(argvars, FALSE);
122998aefe7cSBram Moolenaar }
123098aefe7cSBram Moolenaar 
123198aefe7cSBram Moolenaar /*
123298aefe7cSBram Moolenaar  * prop_type_delete({name} [, {bufnr}])
123398aefe7cSBram Moolenaar  */
123498aefe7cSBram Moolenaar     void
f_prop_type_delete(typval_T * argvars,typval_T * rettv UNUSED)123598aefe7cSBram Moolenaar f_prop_type_delete(typval_T *argvars, typval_T *rettv UNUSED)
123698aefe7cSBram Moolenaar {
123798aefe7cSBram Moolenaar     char_u	*name;
123898aefe7cSBram Moolenaar     buf_T	*buf = NULL;
123998aefe7cSBram Moolenaar     hashitem_T	*hi;
124098aefe7cSBram Moolenaar 
12414490ec4eSYegappan Lakshmanan     if (in_vim9script()
12424490ec4eSYegappan Lakshmanan 	    && (check_for_string_arg(argvars, 0) == FAIL
12434490ec4eSYegappan Lakshmanan 		|| check_for_opt_dict_arg(argvars, 1) == FAIL))
12444490ec4eSYegappan Lakshmanan 	return;
12454490ec4eSYegappan Lakshmanan 
1246d155d7a8SBram Moolenaar     name = tv_get_string(&argvars[0]);
124798aefe7cSBram Moolenaar     if (*name == NUL)
124898aefe7cSBram Moolenaar     {
1249f9e3e09fSBram Moolenaar 	emsg(_(e_invarg));
125098aefe7cSBram Moolenaar 	return;
125198aefe7cSBram Moolenaar     }
125298aefe7cSBram Moolenaar 
125398aefe7cSBram Moolenaar     if (argvars[1].v_type != VAR_UNKNOWN)
125498aefe7cSBram Moolenaar     {
125598aefe7cSBram Moolenaar 	if (get_bufnr_from_arg(&argvars[1], &buf) == FAIL)
125698aefe7cSBram Moolenaar 	    return;
125798aefe7cSBram Moolenaar     }
125898aefe7cSBram Moolenaar 
125998aefe7cSBram Moolenaar     hi = find_prop_hi(name, buf);
126098aefe7cSBram Moolenaar     if (hi != NULL)
126198aefe7cSBram Moolenaar     {
126298aefe7cSBram Moolenaar 	hashtab_T	*ht;
1263fb95e212SBram Moolenaar 	proptype_T	*prop = HI2PT(hi);
126498aefe7cSBram Moolenaar 
126598aefe7cSBram Moolenaar 	if (buf == NULL)
126698aefe7cSBram Moolenaar 	    ht = global_proptypes;
126798aefe7cSBram Moolenaar 	else
126898aefe7cSBram Moolenaar 	    ht = buf->b_proptypes;
126998aefe7cSBram Moolenaar 	hash_remove(ht, hi);
1270fb95e212SBram Moolenaar 	vim_free(prop);
127198aefe7cSBram Moolenaar     }
127298aefe7cSBram Moolenaar }
127398aefe7cSBram Moolenaar 
127498aefe7cSBram Moolenaar /*
1275e2390c7fSMartin Tournoij  * prop_type_get({name} [, {props}])
127698aefe7cSBram Moolenaar  */
127798aefe7cSBram Moolenaar     void
f_prop_type_get(typval_T * argvars,typval_T * rettv)12783d8a513bSBram Moolenaar f_prop_type_get(typval_T *argvars, typval_T *rettv)
127998aefe7cSBram Moolenaar {
12804490ec4eSYegappan Lakshmanan     char_u *name;
128198aefe7cSBram Moolenaar 
12824490ec4eSYegappan Lakshmanan     if (in_vim9script()
12834490ec4eSYegappan Lakshmanan 	    && (check_for_string_arg(argvars, 0) == FAIL
12844490ec4eSYegappan Lakshmanan 		|| check_for_opt_dict_arg(argvars, 1) == FAIL))
12854490ec4eSYegappan Lakshmanan 	return;
12864490ec4eSYegappan Lakshmanan 
12874490ec4eSYegappan Lakshmanan     name = tv_get_string(&argvars[0]);
128898aefe7cSBram Moolenaar     if (*name == NUL)
128998aefe7cSBram Moolenaar     {
1290f9e3e09fSBram Moolenaar 	emsg(_(e_invarg));
129198aefe7cSBram Moolenaar 	return;
129298aefe7cSBram Moolenaar     }
129398aefe7cSBram Moolenaar     if (rettv_dict_alloc(rettv) == OK)
129498aefe7cSBram Moolenaar     {
129598aefe7cSBram Moolenaar 	proptype_T  *prop = NULL;
129698aefe7cSBram Moolenaar 	buf_T	    *buf = NULL;
129798aefe7cSBram Moolenaar 
129898aefe7cSBram Moolenaar 	if (argvars[1].v_type != VAR_UNKNOWN)
129998aefe7cSBram Moolenaar 	{
130098aefe7cSBram Moolenaar 	    if (get_bufnr_from_arg(&argvars[1], &buf) == FAIL)
130198aefe7cSBram Moolenaar 		return;
130298aefe7cSBram Moolenaar 	}
130398aefe7cSBram Moolenaar 
130498aefe7cSBram Moolenaar 	prop = find_prop(name, buf);
130598aefe7cSBram Moolenaar 	if (prop != NULL)
130698aefe7cSBram Moolenaar 	{
130798aefe7cSBram Moolenaar 	    dict_T *d = rettv->vval.v_dict;
130898aefe7cSBram Moolenaar 
130998aefe7cSBram Moolenaar 	    if (prop->pt_hl_id > 0)
131098aefe7cSBram Moolenaar 		dict_add_string(d, "highlight", syn_id2name(prop->pt_hl_id));
131198aefe7cSBram Moolenaar 	    dict_add_number(d, "priority", prop->pt_priority);
131258187f1cSBram Moolenaar 	    dict_add_number(d, "combine",
131358187f1cSBram Moolenaar 				   (prop->pt_flags & PT_FLAG_COMBINE) ? 1 : 0);
131498aefe7cSBram Moolenaar 	    dict_add_number(d, "start_incl",
131598aefe7cSBram Moolenaar 			    (prop->pt_flags & PT_FLAG_INS_START_INCL) ? 1 : 0);
131698aefe7cSBram Moolenaar 	    dict_add_number(d, "end_incl",
131798aefe7cSBram Moolenaar 			      (prop->pt_flags & PT_FLAG_INS_END_INCL) ? 1 : 0);
131898aefe7cSBram Moolenaar 	    if (buf != NULL)
131998aefe7cSBram Moolenaar 		dict_add_number(d, "bufnr", buf->b_fnum);
132098aefe7cSBram Moolenaar 	}
132198aefe7cSBram Moolenaar     }
132298aefe7cSBram Moolenaar }
132398aefe7cSBram Moolenaar 
132498aefe7cSBram Moolenaar     static void
list_types(hashtab_T * ht,list_T * l)132598aefe7cSBram Moolenaar list_types(hashtab_T *ht, list_T *l)
132698aefe7cSBram Moolenaar {
132798aefe7cSBram Moolenaar     long	todo;
132898aefe7cSBram Moolenaar     hashitem_T	*hi;
132998aefe7cSBram Moolenaar 
133098aefe7cSBram Moolenaar     todo = (long)ht->ht_used;
133198aefe7cSBram Moolenaar     for (hi = ht->ht_array; todo > 0; ++hi)
133298aefe7cSBram Moolenaar     {
133398aefe7cSBram Moolenaar 	if (!HASHITEM_EMPTY(hi))
133498aefe7cSBram Moolenaar 	{
133598aefe7cSBram Moolenaar 	    proptype_T *prop = HI2PT(hi);
133698aefe7cSBram Moolenaar 
133798aefe7cSBram Moolenaar 	    list_append_string(l, prop->pt_name, -1);
133898aefe7cSBram Moolenaar 	    --todo;
133998aefe7cSBram Moolenaar 	}
134098aefe7cSBram Moolenaar     }
134198aefe7cSBram Moolenaar }
134298aefe7cSBram Moolenaar 
134398aefe7cSBram Moolenaar /*
134498aefe7cSBram Moolenaar  * prop_type_list([{bufnr}])
134598aefe7cSBram Moolenaar  */
134698aefe7cSBram Moolenaar     void
f_prop_type_list(typval_T * argvars,typval_T * rettv UNUSED)134798aefe7cSBram Moolenaar f_prop_type_list(typval_T *argvars, typval_T *rettv UNUSED)
134898aefe7cSBram Moolenaar {
134998aefe7cSBram Moolenaar     buf_T *buf = NULL;
135098aefe7cSBram Moolenaar 
135198aefe7cSBram Moolenaar     if (rettv_list_alloc(rettv) == OK)
135298aefe7cSBram Moolenaar     {
13534490ec4eSYegappan Lakshmanan 	if (in_vim9script() && check_for_opt_dict_arg(argvars, 0) == FAIL)
13544490ec4eSYegappan Lakshmanan 	    return;
13554490ec4eSYegappan Lakshmanan 
135698aefe7cSBram Moolenaar 	if (argvars[0].v_type != VAR_UNKNOWN)
135798aefe7cSBram Moolenaar 	{
135898aefe7cSBram Moolenaar 	    if (get_bufnr_from_arg(&argvars[0], &buf) == FAIL)
135998aefe7cSBram Moolenaar 		return;
136098aefe7cSBram Moolenaar 	}
136198aefe7cSBram Moolenaar 	if (buf == NULL)
136298aefe7cSBram Moolenaar 	{
136398aefe7cSBram Moolenaar 	    if (global_proptypes != NULL)
136498aefe7cSBram Moolenaar 		list_types(global_proptypes, rettv->vval.v_list);
136598aefe7cSBram Moolenaar 	}
136698aefe7cSBram Moolenaar 	else if (buf->b_proptypes != NULL)
136798aefe7cSBram Moolenaar 	    list_types(buf->b_proptypes, rettv->vval.v_list);
136898aefe7cSBram Moolenaar     }
136998aefe7cSBram Moolenaar }
137098aefe7cSBram Moolenaar 
137198aefe7cSBram Moolenaar /*
137298aefe7cSBram Moolenaar  * Free all property types in "ht".
137398aefe7cSBram Moolenaar  */
137498aefe7cSBram Moolenaar     static void
clear_ht_prop_types(hashtab_T * ht)137598aefe7cSBram Moolenaar clear_ht_prop_types(hashtab_T *ht)
137698aefe7cSBram Moolenaar {
137798aefe7cSBram Moolenaar     long	todo;
137898aefe7cSBram Moolenaar     hashitem_T	*hi;
137998aefe7cSBram Moolenaar 
138098aefe7cSBram Moolenaar     if (ht == NULL)
138198aefe7cSBram Moolenaar 	return;
138298aefe7cSBram Moolenaar 
138398aefe7cSBram Moolenaar     todo = (long)ht->ht_used;
138498aefe7cSBram Moolenaar     for (hi = ht->ht_array; todo > 0; ++hi)
138598aefe7cSBram Moolenaar     {
138698aefe7cSBram Moolenaar 	if (!HASHITEM_EMPTY(hi))
138798aefe7cSBram Moolenaar 	{
138898aefe7cSBram Moolenaar 	    proptype_T *prop = HI2PT(hi);
138998aefe7cSBram Moolenaar 
139098aefe7cSBram Moolenaar 	    vim_free(prop);
139198aefe7cSBram Moolenaar 	    --todo;
139298aefe7cSBram Moolenaar 	}
139398aefe7cSBram Moolenaar     }
139498aefe7cSBram Moolenaar 
139598aefe7cSBram Moolenaar     hash_clear(ht);
139698aefe7cSBram Moolenaar     vim_free(ht);
139798aefe7cSBram Moolenaar }
139898aefe7cSBram Moolenaar 
139998aefe7cSBram Moolenaar #if defined(EXITFREE) || defined(PROTO)
140098aefe7cSBram Moolenaar /*
1401fb95e212SBram Moolenaar  * Free all global property types.
140298aefe7cSBram Moolenaar  */
140398aefe7cSBram Moolenaar     void
clear_global_prop_types(void)140498aefe7cSBram Moolenaar clear_global_prop_types(void)
140598aefe7cSBram Moolenaar {
140698aefe7cSBram Moolenaar     clear_ht_prop_types(global_proptypes);
140798aefe7cSBram Moolenaar     global_proptypes = NULL;
140898aefe7cSBram Moolenaar }
140998aefe7cSBram Moolenaar #endif
141098aefe7cSBram Moolenaar 
141198aefe7cSBram Moolenaar /*
141298aefe7cSBram Moolenaar  * Free all property types for "buf".
141398aefe7cSBram Moolenaar  */
141498aefe7cSBram Moolenaar     void
clear_buf_prop_types(buf_T * buf)141598aefe7cSBram Moolenaar clear_buf_prop_types(buf_T *buf)
141698aefe7cSBram Moolenaar {
141798aefe7cSBram Moolenaar     clear_ht_prop_types(buf->b_proptypes);
141898aefe7cSBram Moolenaar     buf->b_proptypes = NULL;
141998aefe7cSBram Moolenaar }
142098aefe7cSBram Moolenaar 
142187be9be1SBram Moolenaar // Struct used to return two values from adjust_prop().
142287be9be1SBram Moolenaar typedef struct
142387be9be1SBram Moolenaar {
142487be9be1SBram Moolenaar     int dirty;	    // if the property was changed
142587be9be1SBram Moolenaar     int can_drop;   // whether after this change, the prop may be removed
142687be9be1SBram Moolenaar } adjustres_T;
142787be9be1SBram Moolenaar 
142887be9be1SBram Moolenaar /*
142987be9be1SBram Moolenaar  * Adjust the property for "added" bytes (can be negative) inserted at "col".
143087be9be1SBram Moolenaar  *
143187be9be1SBram Moolenaar  * Note that "col" is zero-based, while tp_col is one-based.
143287be9be1SBram Moolenaar  * Only for the current buffer.
143387be9be1SBram Moolenaar  * "flags" can have:
143487be9be1SBram Moolenaar  * APC_SUBSTITUTE:	Text is replaced, not inserted.
143587be9be1SBram Moolenaar  */
143687be9be1SBram Moolenaar     static adjustres_T
adjust_prop(textprop_T * prop,colnr_T col,int added,int flags)143787be9be1SBram Moolenaar adjust_prop(
143887be9be1SBram Moolenaar 	textprop_T *prop,
143987be9be1SBram Moolenaar 	colnr_T col,
144087be9be1SBram Moolenaar 	int added,
144187be9be1SBram Moolenaar 	int flags)
144287be9be1SBram Moolenaar {
144387be9be1SBram Moolenaar     proptype_T	*pt = text_prop_type_by_id(curbuf, prop->tp_type);
144487be9be1SBram Moolenaar     int		start_incl = (pt != NULL
144587be9be1SBram Moolenaar 				    && (pt->pt_flags & PT_FLAG_INS_START_INCL))
144687be9be1SBram Moolenaar 						   || (flags & APC_SUBSTITUTE);
144787be9be1SBram Moolenaar     int		end_incl = (pt != NULL
144887be9be1SBram Moolenaar 				     && (pt->pt_flags & PT_FLAG_INS_END_INCL));
144987be9be1SBram Moolenaar 		// Do not drop zero-width props if they later can increase in
145087be9be1SBram Moolenaar 		// size.
145187be9be1SBram Moolenaar     int		droppable = !(start_incl || end_incl);
145287be9be1SBram Moolenaar     adjustres_T res = {TRUE, FALSE};
145387be9be1SBram Moolenaar 
145487be9be1SBram Moolenaar     if (added > 0)
145587be9be1SBram Moolenaar     {
145687be9be1SBram Moolenaar 	if (col + 1 <= prop->tp_col
145787be9be1SBram Moolenaar 		- (start_incl || (prop->tp_len == 0 && end_incl)))
145887be9be1SBram Moolenaar 	    // Change is entirely before the text property: Only shift
145987be9be1SBram Moolenaar 	    prop->tp_col += added;
146087be9be1SBram Moolenaar 	else if (col + 1 < prop->tp_col + prop->tp_len + end_incl)
146187be9be1SBram Moolenaar 	    // Insertion was inside text property
146287be9be1SBram Moolenaar 	    prop->tp_len += added;
146387be9be1SBram Moolenaar     }
146487be9be1SBram Moolenaar     else if (prop->tp_col > col + 1)
146587be9be1SBram Moolenaar     {
146687be9be1SBram Moolenaar 	if (prop->tp_col + added < col + 1)
146787be9be1SBram Moolenaar 	{
146887be9be1SBram Moolenaar 	    prop->tp_len += (prop->tp_col - 1 - col) + added;
146987be9be1SBram Moolenaar 	    prop->tp_col = col + 1;
147087be9be1SBram Moolenaar 	    if (prop->tp_len <= 0)
147187be9be1SBram Moolenaar 	    {
147287be9be1SBram Moolenaar 		prop->tp_len = 0;
147387be9be1SBram Moolenaar 		res.can_drop = droppable;
147487be9be1SBram Moolenaar 	    }
147587be9be1SBram Moolenaar 	}
147687be9be1SBram Moolenaar 	else
147787be9be1SBram Moolenaar 	    prop->tp_col += added;
147887be9be1SBram Moolenaar     }
147987be9be1SBram Moolenaar     else if (prop->tp_len > 0 && prop->tp_col + prop->tp_len > col)
148087be9be1SBram Moolenaar     {
148187be9be1SBram Moolenaar 	int after = col - added - (prop->tp_col - 1 + prop->tp_len);
148287be9be1SBram Moolenaar 
148387be9be1SBram Moolenaar 	prop->tp_len += after > 0 ? added + after : added;
148487be9be1SBram Moolenaar 	res.can_drop = prop->tp_len <= 0 && droppable;
148587be9be1SBram Moolenaar     }
148687be9be1SBram Moolenaar     else
148787be9be1SBram Moolenaar 	res.dirty = FALSE;
148887be9be1SBram Moolenaar 
148987be9be1SBram Moolenaar     return res;
149087be9be1SBram Moolenaar }
149187be9be1SBram Moolenaar 
1492b9c67a51SBram Moolenaar /*
1493b9c67a51SBram Moolenaar  * Adjust the columns of text properties in line "lnum" after position "col" to
1494b9c67a51SBram Moolenaar  * shift by "bytes_added" (can be negative).
149544746aa1SBram Moolenaar  * Note that "col" is zero-based, while tp_col is one-based.
149644746aa1SBram Moolenaar  * Only for the current buffer.
1497f3333b02SBram Moolenaar  * "flags" can have:
1498f3333b02SBram Moolenaar  * APC_SAVE_FOR_UNDO:	Call u_savesub() before making changes to the line.
1499f3333b02SBram Moolenaar  * APC_SUBSTITUTE:	Text is replaced, not inserted.
15008055d173SBram Moolenaar  * Caller is expected to check b_has_textprop and "bytes_added" being non-zero.
1501338dfdadSBram Moolenaar  * Returns TRUE when props were changed.
1502b9c67a51SBram Moolenaar  */
1503338dfdadSBram Moolenaar     int
adjust_prop_columns(linenr_T lnum,colnr_T col,int bytes_added,int flags)1504196d157fSBram Moolenaar adjust_prop_columns(
1505196d157fSBram Moolenaar 	linenr_T    lnum,
1506196d157fSBram Moolenaar 	colnr_T	    col,
1507338dfdadSBram Moolenaar 	int	    bytes_added,
1508f3333b02SBram Moolenaar 	int	    flags)
1509b9c67a51SBram Moolenaar {
151044746aa1SBram Moolenaar     int		proplen;
151144746aa1SBram Moolenaar     char_u	*props;
151244746aa1SBram Moolenaar     int		dirty = FALSE;
1513196d157fSBram Moolenaar     int		ri, wi;
1514196d157fSBram Moolenaar     size_t	textlen;
1515196d157fSBram Moolenaar 
1516196d157fSBram Moolenaar     if (text_prop_frozen > 0)
1517338dfdadSBram Moolenaar 	return FALSE;
151844746aa1SBram Moolenaar 
151944746aa1SBram Moolenaar     proplen = get_text_props(curbuf, lnum, &props, TRUE);
152044746aa1SBram Moolenaar     if (proplen == 0)
1521338dfdadSBram Moolenaar 	return FALSE;
1522196d157fSBram Moolenaar     textlen = curbuf->b_ml.ml_line_len - proplen * sizeof(textprop_T);
152344746aa1SBram Moolenaar 
1524196d157fSBram Moolenaar     wi = 0; // write index
1525196d157fSBram Moolenaar     for (ri = 0; ri < proplen; ++ri)
152644746aa1SBram Moolenaar     {
152712f20038SBram Moolenaar 	textprop_T	prop;
152887be9be1SBram Moolenaar 	adjustres_T	res;
1529f3333b02SBram Moolenaar 
153087be9be1SBram Moolenaar 	mch_memmove(&prop, props + ri * sizeof(prop), sizeof(prop));
153187be9be1SBram Moolenaar 	res = adjust_prop(&prop, col, bytes_added, flags);
153287be9be1SBram Moolenaar 	if (res.dirty)
1533f3333b02SBram Moolenaar 	{
1534f3333b02SBram Moolenaar 	    // Save for undo if requested and not done yet.
1535cf070112SBram Moolenaar 	    if ((flags & APC_SAVE_FOR_UNDO) && !dirty
1536cf070112SBram Moolenaar 						    && u_savesub(lnum) == FAIL)
1537cf070112SBram Moolenaar 		return FALSE;
1538f3333b02SBram Moolenaar 	    dirty = TRUE;
15398902b31fSBram Moolenaar 
15408902b31fSBram Moolenaar 	    // u_savesub() may have updated curbuf->b_ml, fetch it again
15418902b31fSBram Moolenaar 	    if (curbuf->b_ml.ml_line_lnum != lnum)
15428902b31fSBram Moolenaar 		proplen = get_text_props(curbuf, lnum, &props, TRUE);
1543f3333b02SBram Moolenaar 	}
154487be9be1SBram Moolenaar 	if (res.can_drop)
154587be9be1SBram Moolenaar 	    continue; // Drop this text property
154612f20038SBram Moolenaar 	mch_memmove(props + wi * sizeof(textprop_T), &prop, sizeof(textprop_T));
1547196d157fSBram Moolenaar 	++wi;
154844746aa1SBram Moolenaar     }
154944746aa1SBram Moolenaar     if (dirty)
155044746aa1SBram Moolenaar     {
15514614f53eSBram Moolenaar 	colnr_T newlen = (int)textlen + wi * (colnr_T)sizeof(textprop_T);
15524614f53eSBram Moolenaar 
15534614f53eSBram Moolenaar 	if ((curbuf->b_ml.ml_flags & ML_LINE_DIRTY) == 0)
15544614f53eSBram Moolenaar 	    curbuf->b_ml.ml_line_ptr =
15554614f53eSBram Moolenaar 				 vim_memsave(curbuf->b_ml.ml_line_ptr, newlen);
155644746aa1SBram Moolenaar 	curbuf->b_ml.ml_flags |= ML_LINE_DIRTY;
15574614f53eSBram Moolenaar 	curbuf->b_ml.ml_line_len = newlen;
155844746aa1SBram Moolenaar     }
1559338dfdadSBram Moolenaar     return dirty;
1560b9c67a51SBram Moolenaar }
1561b9c67a51SBram Moolenaar 
15624164bb20SBram Moolenaar /*
15634164bb20SBram Moolenaar  * Adjust text properties for a line that was split in two.
156445dd07f1SBram Moolenaar  * "lnum_props" is the line that has the properties from before the split.
156545dd07f1SBram Moolenaar  * "lnum_top" is the top line.
156645dd07f1SBram Moolenaar  * "kept" is the number of bytes kept in the first line, while
15674164bb20SBram Moolenaar  * "deleted" is the number of bytes deleted.
15684164bb20SBram Moolenaar  */
15694164bb20SBram Moolenaar     void
adjust_props_for_split(linenr_T lnum_props,linenr_T lnum_top,int kept,int deleted)157045dd07f1SBram Moolenaar adjust_props_for_split(
157145dd07f1SBram Moolenaar 	linenr_T lnum_props,
157245dd07f1SBram Moolenaar 	linenr_T lnum_top,
157345dd07f1SBram Moolenaar 	int kept,
157445dd07f1SBram Moolenaar 	int deleted)
15754164bb20SBram Moolenaar {
15764164bb20SBram Moolenaar     char_u	*props;
15774164bb20SBram Moolenaar     int		count;
15784164bb20SBram Moolenaar     garray_T    prevprop;
15794164bb20SBram Moolenaar     garray_T    nextprop;
15804164bb20SBram Moolenaar     int		i;
15814164bb20SBram Moolenaar     int		skipped = kept + deleted;
15824164bb20SBram Moolenaar 
15834164bb20SBram Moolenaar     if (!curbuf->b_has_textprop)
15844164bb20SBram Moolenaar 	return;
158545dd07f1SBram Moolenaar 
158645dd07f1SBram Moolenaar     // Get the text properties from "lnum_props".
158745dd07f1SBram Moolenaar     count = get_text_props(curbuf, lnum_props, &props, FALSE);
15884164bb20SBram Moolenaar     ga_init2(&prevprop, sizeof(textprop_T), 10);
15894164bb20SBram Moolenaar     ga_init2(&nextprop, sizeof(textprop_T), 10);
15904164bb20SBram Moolenaar 
15914164bb20SBram Moolenaar     // Keep the relevant ones in the first line, reducing the length if needed.
15924164bb20SBram Moolenaar     // Copy the ones that include the split to the second line.
15934164bb20SBram Moolenaar     // Move the ones after the split to the second line.
15944164bb20SBram Moolenaar     for (i = 0; i < count; ++i)
15954164bb20SBram Moolenaar     {
15964164bb20SBram Moolenaar 	textprop_T  prop;
159787be9be1SBram Moolenaar 	proptype_T *pt;
159887be9be1SBram Moolenaar 	int	    start_incl, end_incl;
159987be9be1SBram Moolenaar 	int	    cont_prev, cont_next;
16004164bb20SBram Moolenaar 
16014164bb20SBram Moolenaar 	// copy the prop to an aligned structure
16024164bb20SBram Moolenaar 	mch_memmove(&prop, props + i * sizeof(textprop_T), sizeof(textprop_T));
16034164bb20SBram Moolenaar 
160487be9be1SBram Moolenaar 	pt = text_prop_type_by_id(curbuf, prop.tp_type);
160587be9be1SBram Moolenaar 	start_incl = (pt != NULL && (pt->pt_flags & PT_FLAG_INS_START_INCL));
160687be9be1SBram Moolenaar 	end_incl = (pt != NULL && (pt->pt_flags & PT_FLAG_INS_END_INCL));
160787be9be1SBram Moolenaar 	cont_prev = prop.tp_col + !start_incl <= kept;
160887be9be1SBram Moolenaar 	cont_next = skipped <= prop.tp_col + prop.tp_len - !end_incl;
160987be9be1SBram Moolenaar 
161087be9be1SBram Moolenaar 	if (cont_prev && ga_grow(&prevprop, 1) == OK)
16114164bb20SBram Moolenaar 	{
161287be9be1SBram Moolenaar 	    textprop_T *p = ((textprop_T *)prevprop.ga_data) + prevprop.ga_len;
161387be9be1SBram Moolenaar 
16144164bb20SBram Moolenaar 	    *p = prop;
161587be9be1SBram Moolenaar 	    ++prevprop.ga_len;
16164164bb20SBram Moolenaar 	    if (p->tp_col + p->tp_len >= kept)
16174164bb20SBram Moolenaar 		p->tp_len = kept - p->tp_col;
161887be9be1SBram Moolenaar 	    if (cont_next)
161987be9be1SBram Moolenaar 		p->tp_flags |= TP_FLAG_CONT_NEXT;
16204164bb20SBram Moolenaar 	}
16214164bb20SBram Moolenaar 
16225c65e6a0SBram Moolenaar 	// Only add the property to the next line if the length is bigger than
16235c65e6a0SBram Moolenaar 	// zero.
162487be9be1SBram Moolenaar 	if (cont_next && ga_grow(&nextprop, 1) == OK)
16254164bb20SBram Moolenaar 	{
162687be9be1SBram Moolenaar 	    textprop_T *p = ((textprop_T *)nextprop.ga_data) + nextprop.ga_len;
16274164bb20SBram Moolenaar 	    *p = prop;
162887be9be1SBram Moolenaar 	    ++nextprop.ga_len;
16294164bb20SBram Moolenaar 	    if (p->tp_col > skipped)
16304164bb20SBram Moolenaar 		p->tp_col -= skipped - 1;
16314164bb20SBram Moolenaar 	    else
16324164bb20SBram Moolenaar 	    {
16334164bb20SBram Moolenaar 		p->tp_len -= skipped - p->tp_col;
16344164bb20SBram Moolenaar 		p->tp_col = 1;
16354164bb20SBram Moolenaar 	    }
163687be9be1SBram Moolenaar 	    if (cont_prev)
163787be9be1SBram Moolenaar 		p->tp_flags |= TP_FLAG_CONT_PREV;
16384164bb20SBram Moolenaar 	}
16394164bb20SBram Moolenaar     }
16404164bb20SBram Moolenaar 
164145dd07f1SBram Moolenaar     set_text_props(lnum_top, prevprop.ga_data,
164245dd07f1SBram Moolenaar 					 prevprop.ga_len * sizeof(textprop_T));
16434164bb20SBram Moolenaar     ga_clear(&prevprop);
164445dd07f1SBram Moolenaar     set_text_props(lnum_top + 1, nextprop.ga_data,
164545dd07f1SBram Moolenaar 					 nextprop.ga_len * sizeof(textprop_T));
16464164bb20SBram Moolenaar     ga_clear(&nextprop);
16474164bb20SBram Moolenaar }
16484164bb20SBram Moolenaar 
164980e737ccSBram Moolenaar /*
165087be9be1SBram Moolenaar  * Prepend properties of joined line "lnum" to "new_props".
165180e737ccSBram Moolenaar  */
165280e737ccSBram Moolenaar     void
prepend_joined_props(char_u * new_props,int propcount,int * props_remaining,linenr_T lnum,int add_all,long col,int removed)165387be9be1SBram Moolenaar prepend_joined_props(
165487be9be1SBram Moolenaar 	char_u	    *new_props,
165587be9be1SBram Moolenaar 	int	    propcount,
165687be9be1SBram Moolenaar 	int	    *props_remaining,
165780e737ccSBram Moolenaar 	linenr_T    lnum,
165887be9be1SBram Moolenaar 	int	    add_all,
165980e737ccSBram Moolenaar 	long	    col,
166080e737ccSBram Moolenaar 	int	    removed)
166180e737ccSBram Moolenaar {
166280e737ccSBram Moolenaar     char_u *props;
166387be9be1SBram Moolenaar     int	    proplen = get_text_props(curbuf, lnum, &props, FALSE);
166487be9be1SBram Moolenaar     int	    i;
166580e737ccSBram Moolenaar 
166687be9be1SBram Moolenaar     for (i = proplen; i-- > 0; )
166780e737ccSBram Moolenaar     {
166887be9be1SBram Moolenaar 	textprop_T  prop;
166987be9be1SBram Moolenaar 	int	    end;
167080e737ccSBram Moolenaar 
167187be9be1SBram Moolenaar 	mch_memmove(&prop, props + i * sizeof(prop), sizeof(prop));
167287be9be1SBram Moolenaar 	end = !(prop.tp_flags & TP_FLAG_CONT_NEXT);
167387be9be1SBram Moolenaar 
167487be9be1SBram Moolenaar 	adjust_prop(&prop, 0, -removed, 0); // Remove leading spaces
16758e7d6223SBram Moolenaar 	adjust_prop(&prop, -1, col, 0); // Make line start at its final column
167687be9be1SBram Moolenaar 
167787be9be1SBram Moolenaar 	if (add_all || end)
167887be9be1SBram Moolenaar 	    mch_memmove(new_props + --(*props_remaining) * sizeof(prop),
167987be9be1SBram Moolenaar 							  &prop, sizeof(prop));
168080e737ccSBram Moolenaar 	else
168180e737ccSBram Moolenaar 	{
168287be9be1SBram Moolenaar 	    int j;
168387be9be1SBram Moolenaar 	    int	found = FALSE;
168480e737ccSBram Moolenaar 
168587be9be1SBram Moolenaar 	    // Search for continuing prop.
168687be9be1SBram Moolenaar 	    for (j = *props_remaining; j < propcount; ++j)
168780e737ccSBram Moolenaar 	    {
168887be9be1SBram Moolenaar 		textprop_T op;
168980e737ccSBram Moolenaar 
169087be9be1SBram Moolenaar 		mch_memmove(&op, new_props + j * sizeof(op), sizeof(op));
169187be9be1SBram Moolenaar 		if ((op.tp_flags & TP_FLAG_CONT_PREV)
169287be9be1SBram Moolenaar 			&& op.tp_id == prop.tp_id && op.tp_type == prop.tp_type)
169380e737ccSBram Moolenaar 		{
169487be9be1SBram Moolenaar 		    found = TRUE;
169587be9be1SBram Moolenaar 		    op.tp_len += op.tp_col - prop.tp_col;
169687be9be1SBram Moolenaar 		    op.tp_col = prop.tp_col;
169787be9be1SBram Moolenaar 		    // Start/end is taken care of when deleting joined lines
169887be9be1SBram Moolenaar 		    op.tp_flags = prop.tp_flags;
169987be9be1SBram Moolenaar 		    mch_memmove(new_props + j * sizeof(op), &op, sizeof(op));
170087be9be1SBram Moolenaar 		    break;
170180e737ccSBram Moolenaar 		}
1702277e79adSBram Moolenaar 	    }
170387be9be1SBram Moolenaar 	    if (!found)
170487be9be1SBram Moolenaar 		internal_error("text property above joined line not found");
170580e737ccSBram Moolenaar 	}
170687be9be1SBram Moolenaar     }
170780e737ccSBram Moolenaar }
170880e737ccSBram Moolenaar 
170905ad5ff0SBram Moolenaar #endif // FEAT_PROP_POPUP
1710