blob: ade99a7bf63b1748268d312df0f1881363c27282 [file] [log] [blame]
Bram Moolenaar98aefe72018-12-13 22:20:09 +01001/* 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.
12 *
13 * Text properties are attached to the text. They move with the text when
14 * text is inserted/deleted.
15 *
16 * Text properties have a user specified ID number, which can be unique.
17 * Text properties have a type, which can be used to specify highlighting.
18 *
19 * TODO:
Bram Moolenaarb413d2e2018-12-25 23:15:46 +010020 * - mismatch in column 1 being the first column
21 * - Let props overrule syntax HL.
Bram Moolenaare3d31b02018-12-24 23:07:04 +010022 * - When deleting a line where a prop ended, adjust flag of previous line.
23 * - When deleting a line where a prop started, adjust flag of next line.
24 * - When inserting a line add props that continue from previous line.
25 * - Adjust property column and length when text is inserted/deleted
Bram Moolenaar98aefe72018-12-13 22:20:09 +010026 * - Add an arrray for global_proptypes, to quickly lookup a proptype by ID
27 * - Add an arrray for b_proptypes, to quickly lookup a proptype by ID
Bram Moolenaarb413d2e2018-12-25 23:15:46 +010028 * - Also test line2byte() with many lines, so that ml_updatechunk() is taken
29 * into account.
Bram Moolenaar98aefe72018-12-13 22:20:09 +010030 * - add mechanism to keep track of changed lines.
31 */
32
33#include "vim.h"
34
35#if defined(FEAT_TEXT_PROP) || defined(PROTO)
36
37/*
38 * In a hashtable item "hi_key" points to "pt_name" in a proptype_T.
39 * This avoids adding a pointer to the hashtable item.
40 * PT2HIKEY() converts a proptype pointer to a hashitem key pointer.
41 * HIKEY2PT() converts a hashitem key pointer to a proptype pointer.
42 * HI2PT() converts a hashitem pointer to a proptype pointer.
43 */
44#define PT2HIKEY(p) ((p)->pt_name)
45#define HIKEY2PT(p) ((proptype_T *)((p) - offsetof(proptype_T, pt_name)))
46#define HI2PT(hi) HIKEY2PT((hi)->hi_key)
47
48// The global text property types.
49static hashtab_T *global_proptypes = NULL;
50
51// The last used text property type ID.
52static int proptype_id = 0;
53
54static char_u e_type_not_exist[] = N_("E971: Property type %s does not exist");
55static char_u e_invalid_col[] = N_("E964: Invalid column number: %ld");
Bram Moolenaare3d31b02018-12-24 23:07:04 +010056static char_u e_invalid_lnum[] = N_("E966: Invalid line number: %ld");
Bram Moolenaar98aefe72018-12-13 22:20:09 +010057
58/*
59 * Find a property type by name, return the hashitem.
60 * Returns NULL if the item can't be found.
61 */
62 static hashitem_T *
63find_prop_hi(char_u *name, buf_T *buf)
64{
65 hashtab_T *ht;
66 hashitem_T *hi;
67
68 if (*name == NUL)
69 return NULL;
70 if (buf == NULL)
71 ht = global_proptypes;
72 else
73 ht = buf->b_proptypes;
74
75 if (ht == NULL)
76 return NULL;
77 hi = hash_find(ht, name);
78 if (HASHITEM_EMPTY(hi))
79 return NULL;
80 return hi;
81}
82
83/*
84 * Like find_prop_hi() but return the property type.
85 */
86 static proptype_T *
87find_prop(char_u *name, buf_T *buf)
88{
89 hashitem_T *hi = find_prop_hi(name, buf);
90
91 if (hi == NULL)
92 return NULL;
93 return HI2PT(hi);
94}
95
96/*
97 * Lookup a property type by name. First in "buf" and when not found in the
98 * global types.
99 * When not found gives an error message and returns NULL.
100 */
101 static proptype_T *
102lookup_prop_type(char_u *name, buf_T *buf)
103{
104 proptype_T *type = find_prop(name, buf);
105
106 if (type == NULL)
107 type = find_prop(name, NULL);
108 if (type == NULL)
109 EMSG2(_(e_type_not_exist), name);
110 return type;
111}
112
113/*
114 * Get an optional "bufnr" item from the dict in "arg".
115 * When the argument is not used or "bufnr" is not present then "buf" is
116 * unchanged.
117 * If "bufnr" is valid or not present return OK.
118 * When "arg" is not a dict or "bufnr" is invalide return FAIL.
119 */
120 static int
121get_bufnr_from_arg(typval_T *arg, buf_T **buf)
122{
123 dictitem_T *di;
124
125 if (arg->v_type != VAR_DICT)
126 {
127 EMSG(_(e_dictreq));
128 return FAIL;
129 }
130 if (arg->vval.v_dict == NULL)
131 return OK; // NULL dict is like an empty dict
132 di = dict_find(arg->vval.v_dict, (char_u *)"bufnr", -1);
133 if (di != NULL)
134 {
135 *buf = get_buf_tv(&di->di_tv, FALSE);
136 if (*buf == NULL)
137 return FAIL;
138 }
139 return OK;
140}
141
142/*
143 * prop_add({lnum}, {col}, {props})
144 */
145 void
146f_prop_add(typval_T *argvars, typval_T *rettv UNUSED)
147{
148 linenr_T lnum;
Bram Moolenaare3d31b02018-12-24 23:07:04 +0100149 linenr_T start_lnum;
150 linenr_T end_lnum;
151 colnr_T start_col;
152 colnr_T end_col;
Bram Moolenaar98aefe72018-12-13 22:20:09 +0100153 dict_T *dict;
Bram Moolenaar98aefe72018-12-13 22:20:09 +0100154 char_u *type_name;
155 proptype_T *type;
156 buf_T *buf = curbuf;
157 int id = 0;
158 char_u *newtext;
159 int proplen;
160 size_t textlen;
161 char_u *props;
162 char_u *newprops;
Bram Moolenaarfb95e212018-12-14 12:18:11 +0100163 textprop_T tmp_prop;
Bram Moolenaar98aefe72018-12-13 22:20:09 +0100164 int i;
165
Bram Moolenaare3d31b02018-12-24 23:07:04 +0100166 start_lnum = tv_get_number(&argvars[0]);
167 start_col = tv_get_number(&argvars[1]);
168 if (start_col < 1)
Bram Moolenaar98aefe72018-12-13 22:20:09 +0100169 {
Bram Moolenaare3d31b02018-12-24 23:07:04 +0100170 EMSGN(_(e_invalid_col), (long)start_col);
Bram Moolenaar98aefe72018-12-13 22:20:09 +0100171 return;
172 }
173 if (argvars[2].v_type != VAR_DICT)
174 {
175 EMSG(_(e_dictreq));
176 return;
177 }
178 dict = argvars[2].vval.v_dict;
179
180 if (dict == NULL || dict_find(dict, (char_u *)"type", -1) == NULL)
181 {
182 EMSG(_("E965: missing property type name"));
183 return;
184 }
Bram Moolenaar8f667172018-12-14 15:38:31 +0100185 type_name = dict_get_string(dict, (char_u *)"type", FALSE);
Bram Moolenaar98aefe72018-12-13 22:20:09 +0100186
187 if (dict_find(dict, (char_u *)"end_lnum", -1) != NULL)
188 {
Bram Moolenaare3d31b02018-12-24 23:07:04 +0100189 end_lnum = dict_get_number(dict, (char_u *)"end_lnum");
190 if (end_lnum < start_lnum)
191 {
192 EMSG2(_(e_invargval), "end_lnum");
193 return;
194 }
Bram Moolenaar98aefe72018-12-13 22:20:09 +0100195 }
Bram Moolenaare3d31b02018-12-24 23:07:04 +0100196 else
197 end_lnum = start_lnum;
Bram Moolenaar98aefe72018-12-13 22:20:09 +0100198
199 if (dict_find(dict, (char_u *)"length", -1) != NULL)
Bram Moolenaare3d31b02018-12-24 23:07:04 +0100200 {
201 long length = dict_get_number(dict, (char_u *)"length");
202
203 if (length < 1 || end_lnum > start_lnum)
204 {
205 EMSG2(_(e_invargval), "length");
206 return;
207 }
208 end_col = start_col + length - 1;
209 }
Bram Moolenaar98aefe72018-12-13 22:20:09 +0100210 else if (dict_find(dict, (char_u *)"end_col", -1) != NULL)
211 {
Bram Moolenaare3d31b02018-12-24 23:07:04 +0100212 end_col = dict_get_number(dict, (char_u *)"end_col");
213 if (end_col <= 0)
Bram Moolenaar98aefe72018-12-13 22:20:09 +0100214 {
215 EMSG2(_(e_invargval), "end_col");
216 return;
217 }
218 }
Bram Moolenaare3d31b02018-12-24 23:07:04 +0100219 else if (start_lnum == end_lnum)
220 end_col = start_col;
221 else
222 end_col = 1;
Bram Moolenaar98aefe72018-12-13 22:20:09 +0100223
224 if (dict_find(dict, (char_u *)"id", -1) != NULL)
Bram Moolenaar8f667172018-12-14 15:38:31 +0100225 id = dict_get_number(dict, (char_u *)"id");
Bram Moolenaar98aefe72018-12-13 22:20:09 +0100226
227 if (get_bufnr_from_arg(&argvars[2], &buf) == FAIL)
228 return;
229
230 type = lookup_prop_type(type_name, buf);
231 if (type == NULL)
232 return;
233
Bram Moolenaare3d31b02018-12-24 23:07:04 +0100234 if (start_lnum < 1 || start_lnum > buf->b_ml.ml_line_count)
Bram Moolenaar98aefe72018-12-13 22:20:09 +0100235 {
Bram Moolenaare3d31b02018-12-24 23:07:04 +0100236 EMSGN(_(e_invalid_lnum), (long)start_lnum);
237 return;
238 }
239 if (end_lnum < start_lnum || end_lnum > buf->b_ml.ml_line_count)
240 {
241 EMSGN(_(e_invalid_lnum), (long)end_lnum);
Bram Moolenaar98aefe72018-12-13 22:20:09 +0100242 return;
243 }
244
Bram Moolenaare3d31b02018-12-24 23:07:04 +0100245 for (lnum = start_lnum; lnum <= end_lnum; ++lnum)
Bram Moolenaar98aefe72018-12-13 22:20:09 +0100246 {
Bram Moolenaare3d31b02018-12-24 23:07:04 +0100247 colnr_T col; // start column
248 long length; // in bytes
249
250 // Fetch the line to get the ml_line_len field updated.
251 proplen = get_text_props(buf, lnum, &props, TRUE);
252 textlen = buf->b_ml.ml_line_len - proplen * sizeof(textprop_T);
253
254 if (lnum == start_lnum)
255 col = start_col;
256 else
257 col = 1;
258 if (col - 1 > (colnr_T)textlen)
259 {
260 EMSGN(_(e_invalid_col), (long)start_col);
261 return;
262 }
263
264 if (lnum == end_lnum)
265 length = end_col - col + 1;
266 else
267 length = textlen - col + 1;
Bram Moolenaarb413d2e2018-12-25 23:15:46 +0100268 if (length > (long)textlen)
Bram Moolenaare3d31b02018-12-24 23:07:04 +0100269 length = textlen; // can include the end-of-line
270 if (length < 1)
271 length = 1;
272
273 // Allocate the new line with space for the new proprety.
274 newtext = alloc(buf->b_ml.ml_line_len + sizeof(textprop_T));
275 if (newtext == NULL)
276 return;
277 // Copy the text, including terminating NUL.
278 mch_memmove(newtext, buf->b_ml.ml_line_ptr, textlen);
279
280 // Find the index where to insert the new property.
281 // Since the text properties are not aligned properly when stored with the
282 // text, we need to copy them as bytes before using it as a struct.
283 for (i = 0; i < proplen; ++i)
284 {
285 mch_memmove(&tmp_prop, props + i * sizeof(textprop_T),
286 sizeof(textprop_T));
287 if (tmp_prop.tp_col >= col)
288 break;
289 }
290 newprops = newtext + textlen;
291 if (i > 0)
292 mch_memmove(newprops, props, sizeof(textprop_T) * i);
293
294 tmp_prop.tp_col = col;
295 tmp_prop.tp_len = length;
296 tmp_prop.tp_id = id;
297 tmp_prop.tp_type = type->pt_id;
298 tmp_prop.tp_flags = (lnum > start_lnum ? TP_FLAG_CONT_PREV : 0)
299 | (lnum < end_lnum ? TP_FLAG_CONT_NEXT : 0);
300 mch_memmove(newprops + i * sizeof(textprop_T), &tmp_prop,
301 sizeof(textprop_T));
302
303 if (i < proplen)
304 mch_memmove(newprops + (i + 1) * sizeof(textprop_T),
305 props + i * sizeof(textprop_T),
306 sizeof(textprop_T) * (proplen - i));
307
308 if (buf->b_ml.ml_flags & ML_LINE_DIRTY)
309 vim_free(buf->b_ml.ml_line_ptr);
310 buf->b_ml.ml_line_ptr = newtext;
311 buf->b_ml.ml_line_len += sizeof(textprop_T);
312 buf->b_ml.ml_flags |= ML_LINE_DIRTY;
Bram Moolenaar98aefe72018-12-13 22:20:09 +0100313 }
314
Bram Moolenaarb413d2e2018-12-25 23:15:46 +0100315 buf->b_has_textprop = TRUE; // this is never reset
Bram Moolenaar98aefe72018-12-13 22:20:09 +0100316 redraw_buf_later(buf, NOT_VALID);
317}
318
319/*
Bram Moolenaarfb95e212018-12-14 12:18:11 +0100320 * Fetch the text properties for line "lnum" in buffer "buf".
Bram Moolenaar98aefe72018-12-13 22:20:09 +0100321 * Returns the number of text properties and, when non-zero, a pointer to the
322 * first one in "props" (note that it is not aligned, therefore the char_u
323 * pointer).
324 */
325 int
326get_text_props(buf_T *buf, linenr_T lnum, char_u **props, int will_change)
327{
328 char_u *text;
329 size_t textlen;
330 size_t proplen;
331
Bram Moolenaarb413d2e2018-12-25 23:15:46 +0100332 // Be quick when no text property types have been defined or the buffer,
333 // unless we are adding one.
334 if (!buf->b_has_textprop && !will_change)
Bram Moolenaar98aefe72018-12-13 22:20:09 +0100335 return 0;
336
337 // Fetch the line to get the ml_line_len field updated.
338 text = ml_get_buf(buf, lnum, will_change);
339 textlen = STRLEN(text) + 1;
340 proplen = buf->b_ml.ml_line_len - textlen;
341 if (proplen % sizeof(textprop_T) != 0)
342 {
343 IEMSG(_("E967: text property info corrupted"));
344 return 0;
345 }
346 if (proplen > 0)
347 *props = text + textlen;
Bram Moolenaar4efe73b2018-12-16 14:37:39 +0100348 return (int)(proplen / sizeof(textprop_T));
Bram Moolenaar98aefe72018-12-13 22:20:09 +0100349}
350
351 static proptype_T *
352find_type_by_id(hashtab_T *ht, int id)
353{
354 long todo;
355 hashitem_T *hi;
356
357 if (ht == NULL)
358 return NULL;
359
360 // TODO: Make this faster by keeping a list of types sorted on ID and use
361 // a binary search.
362
363 todo = (long)ht->ht_used;
364 for (hi = ht->ht_array; todo > 0; ++hi)
365 {
366 if (!HASHITEM_EMPTY(hi))
367 {
368 proptype_T *prop = HI2PT(hi);
369
370 if (prop->pt_id == id)
371 return prop;
372 --todo;
373 }
374 }
375 return NULL;
376}
377
378/*
379 * Find a property type by ID in "buf" or globally.
380 * Returns NULL if not found.
381 */
382 proptype_T *
383text_prop_type_by_id(buf_T *buf, int id)
384{
385 proptype_T *type;
386
387 type = find_type_by_id(buf->b_proptypes, id);
388 if (type == NULL)
389 type = find_type_by_id(global_proptypes, id);
390 return type;
391}
392
393/*
394 * prop_clear({lnum} [, {lnum_end} [, {bufnr}]])
395 */
396 void
397f_prop_clear(typval_T *argvars, typval_T *rettv UNUSED)
398{
Bram Moolenaard155d7a2018-12-21 16:04:21 +0100399 linenr_T start = tv_get_number(&argvars[0]);
Bram Moolenaar98aefe72018-12-13 22:20:09 +0100400 linenr_T end = start;
401 linenr_T lnum;
402 buf_T *buf = curbuf;
403
404 if (argvars[1].v_type != VAR_UNKNOWN)
405 {
Bram Moolenaard155d7a2018-12-21 16:04:21 +0100406 end = tv_get_number(&argvars[1]);
Bram Moolenaar98aefe72018-12-13 22:20:09 +0100407 if (argvars[2].v_type != VAR_UNKNOWN)
408 {
409 if (get_bufnr_from_arg(&argvars[2], &buf) == FAIL)
410 return;
411 }
412 }
413 if (start < 1 || end < 1)
414 {
415 EMSG(_(e_invrange));
416 return;
417 }
418
419 for (lnum = start; lnum <= end; ++lnum)
420 {
421 char_u *text;
422 size_t len;
423
424 if (lnum > buf->b_ml.ml_line_count)
425 break;
426 text = ml_get_buf(buf, lnum, FALSE);
427 len = STRLEN(text) + 1;
428 if ((size_t)buf->b_ml.ml_line_len > len)
429 {
430 if (!(buf->b_ml.ml_flags & ML_LINE_DIRTY))
431 {
432 char_u *newtext = vim_strsave(text);
433
434 // need to allocate the line now
435 if (newtext == NULL)
436 return;
437 buf->b_ml.ml_line_ptr = newtext;
438 buf->b_ml.ml_flags |= ML_LINE_DIRTY;
439 }
Bram Moolenaar4efe73b2018-12-16 14:37:39 +0100440 buf->b_ml.ml_line_len = (int)len;
Bram Moolenaar98aefe72018-12-13 22:20:09 +0100441 }
442 }
443 redraw_buf_later(buf, NOT_VALID);
444}
445
446/*
447 * prop_list({lnum} [, {bufnr}])
448 */
449 void
450f_prop_list(typval_T *argvars, typval_T *rettv)
451{
Bram Moolenaard155d7a2018-12-21 16:04:21 +0100452 linenr_T lnum = tv_get_number(&argvars[0]);
Bram Moolenaar98aefe72018-12-13 22:20:09 +0100453 buf_T *buf = curbuf;
454
455 if (argvars[1].v_type != VAR_UNKNOWN)
456 {
457 if (get_bufnr_from_arg(&argvars[1], &buf) == FAIL)
458 return;
459 }
460 if (lnum < 1 || lnum > buf->b_ml.ml_line_count)
461 {
462 EMSG(_(e_invrange));
463 return;
464 }
465
466 if (rettv_list_alloc(rettv) == OK)
467 {
468 char_u *text = ml_get_buf(buf, lnum, FALSE);
469 size_t textlen = STRLEN(text) + 1;
Bram Moolenaar4efe73b2018-12-16 14:37:39 +0100470 int count = (int)((buf->b_ml.ml_line_len - textlen)
471 / sizeof(textprop_T));
Bram Moolenaar98aefe72018-12-13 22:20:09 +0100472 int i;
473 textprop_T prop;
474 proptype_T *pt;
475
476 for (i = 0; i < count; ++i)
477 {
478 dict_T *d = dict_alloc();
479
480 if (d == NULL)
481 break;
482 mch_memmove(&prop, text + textlen + i * sizeof(textprop_T),
483 sizeof(textprop_T));
484 dict_add_number(d, "col", prop.tp_col);
485 dict_add_number(d, "length", prop.tp_len);
486 dict_add_number(d, "id", prop.tp_id);
487 dict_add_number(d, "start", !(prop.tp_flags & TP_FLAG_CONT_PREV));
488 dict_add_number(d, "end", !(prop.tp_flags & TP_FLAG_CONT_NEXT));
489 pt = text_prop_type_by_id(buf, prop.tp_type);
490 if (pt != NULL)
491 dict_add_string(d, "type", pt->pt_name);
492
493 list_append_dict(rettv->vval.v_list, d);
494 }
495 }
496}
497
498/*
499 * prop_remove({props} [, {lnum} [, {lnum_end}]])
500 */
501 void
502f_prop_remove(typval_T *argvars, typval_T *rettv)
503{
504 linenr_T start = 1;
505 linenr_T end = 0;
506 linenr_T lnum;
507 dict_T *dict;
508 buf_T *buf = curbuf;
509 dictitem_T *di;
510 int do_all = FALSE;
511 int id = -1;
512 int type_id = -1;
513
514 rettv->vval.v_number = 0;
515 if (argvars[0].v_type != VAR_DICT || argvars[0].vval.v_dict == NULL)
516 {
517 EMSG(_(e_invarg));
518 return;
519 }
520
521 if (argvars[1].v_type != VAR_UNKNOWN)
522 {
Bram Moolenaard155d7a2018-12-21 16:04:21 +0100523 start = tv_get_number(&argvars[1]);
Bram Moolenaar98aefe72018-12-13 22:20:09 +0100524 end = start;
525 if (argvars[2].v_type != VAR_UNKNOWN)
Bram Moolenaard155d7a2018-12-21 16:04:21 +0100526 end = tv_get_number(&argvars[2]);
Bram Moolenaar98aefe72018-12-13 22:20:09 +0100527 if (start < 1 || end < 1)
528 {
529 EMSG(_(e_invrange));
530 return;
531 }
532 }
533
534 dict = argvars[0].vval.v_dict;
535 di = dict_find(dict, (char_u *)"bufnr", -1);
536 if (di != NULL)
537 {
538 buf = get_buf_tv(&di->di_tv, FALSE);
539 if (buf == NULL)
540 return;
541 }
542
543 di = dict_find(dict, (char_u*)"all", -1);
544 if (di != NULL)
Bram Moolenaar8f667172018-12-14 15:38:31 +0100545 do_all = dict_get_number(dict, (char_u *)"all");
Bram Moolenaar98aefe72018-12-13 22:20:09 +0100546
547 if (dict_find(dict, (char_u *)"id", -1) != NULL)
Bram Moolenaar8f667172018-12-14 15:38:31 +0100548 id = dict_get_number(dict, (char_u *)"id");
Bram Moolenaar98aefe72018-12-13 22:20:09 +0100549 if (dict_find(dict, (char_u *)"type", -1))
550 {
Bram Moolenaar8f667172018-12-14 15:38:31 +0100551 char_u *name = dict_get_string(dict, (char_u *)"type", FALSE);
Bram Moolenaar98aefe72018-12-13 22:20:09 +0100552 proptype_T *type = lookup_prop_type(name, buf);
553
554 if (type == NULL)
555 return;
556 type_id = type->pt_id;
557 }
558 if (id == -1 && type_id == -1)
559 {
560 EMSG(_("E968: Need at least one of 'id' or 'type'"));
561 return;
562 }
563
564 if (end == 0)
565 end = buf->b_ml.ml_line_count;
566 for (lnum = start; lnum <= end; ++lnum)
567 {
568 char_u *text;
569 size_t len;
570
571 if (lnum > buf->b_ml.ml_line_count)
572 break;
573 text = ml_get_buf(buf, lnum, FALSE);
574 len = STRLEN(text) + 1;
575 if ((size_t)buf->b_ml.ml_line_len > len)
576 {
577 static textprop_T textprop; // static because of alignment
578 unsigned idx;
579
580 for (idx = 0; idx < (buf->b_ml.ml_line_len - len)
581 / sizeof(textprop_T); ++idx)
582 {
583 char_u *cur_prop = buf->b_ml.ml_line_ptr + len
584 + idx * sizeof(textprop_T);
585 size_t taillen;
586
587 mch_memmove(&textprop, cur_prop, sizeof(textprop_T));
588 if (textprop.tp_id == id || textprop.tp_type == type_id)
589 {
590 if (!(buf->b_ml.ml_flags & ML_LINE_DIRTY))
591 {
592 char_u *newptr = alloc(buf->b_ml.ml_line_len);
593
594 // need to allocate the line to be able to change it
595 if (newptr == NULL)
596 return;
597 mch_memmove(newptr, buf->b_ml.ml_line_ptr,
598 buf->b_ml.ml_line_len);
599 buf->b_ml.ml_line_ptr = newptr;
600 curbuf->b_ml.ml_flags |= ML_LINE_DIRTY;
601 }
602
603 taillen = buf->b_ml.ml_line_len - len
604 - (idx + 1) * sizeof(textprop_T);
605 if (taillen > 0)
606 mch_memmove(cur_prop, cur_prop + sizeof(textprop_T),
607 taillen);
608 buf->b_ml.ml_line_len -= sizeof(textprop_T);
609 --idx;
610
611 ++rettv->vval.v_number;
612 if (!do_all)
613 break;
614 }
615 }
616 }
617 }
618 redraw_buf_later(buf, NOT_VALID);
619}
620
621/*
622 * Common for f_prop_type_add() and f_prop_type_change().
623 */
624 void
625prop_type_set(typval_T *argvars, int add)
626{
627 char_u *name;
628 buf_T *buf = NULL;
629 dict_T *dict;
630 dictitem_T *di;
631 proptype_T *prop;
632
Bram Moolenaard155d7a2018-12-21 16:04:21 +0100633 name = tv_get_string(&argvars[0]);
Bram Moolenaar98aefe72018-12-13 22:20:09 +0100634 if (*name == NUL)
635 {
636 EMSG(_(e_invarg));
637 return;
638 }
639
640 if (get_bufnr_from_arg(&argvars[1], &buf) == FAIL)
641 return;
642 dict = argvars[1].vval.v_dict;
643
644 prop = find_prop(name, buf);
645 if (add)
646 {
647 hashtab_T **htp;
648
649 if (prop != NULL)
650 {
651 EMSG2(_("E969: Property type %s already defined"), name);
652 return;
653 }
Bram Moolenaar4efe73b2018-12-16 14:37:39 +0100654 prop = (proptype_T *)alloc_clear((int)(sizeof(proptype_T) + STRLEN(name)));
Bram Moolenaar98aefe72018-12-13 22:20:09 +0100655 if (prop == NULL)
656 return;
657 STRCPY(prop->pt_name, name);
658 prop->pt_id = ++proptype_id;
659 htp = buf == NULL ? &global_proptypes : &buf->b_proptypes;
660 if (*htp == NULL)
661 {
662 *htp = (hashtab_T *)alloc(sizeof(hashtab_T));
663 if (*htp == NULL)
Bram Moolenaarfb95e212018-12-14 12:18:11 +0100664 {
665 vim_free(prop);
Bram Moolenaar98aefe72018-12-13 22:20:09 +0100666 return;
Bram Moolenaarfb95e212018-12-14 12:18:11 +0100667 }
Bram Moolenaar98aefe72018-12-13 22:20:09 +0100668 hash_init(*htp);
669 }
Bram Moolenaarfb95e212018-12-14 12:18:11 +0100670 hash_add(*htp, PT2HIKEY(prop));
Bram Moolenaar98aefe72018-12-13 22:20:09 +0100671 }
672 else
673 {
674 if (prop == NULL)
675 {
676 EMSG2(_(e_type_not_exist), name);
677 return;
678 }
679 }
680
681 if (dict != NULL)
682 {
683 di = dict_find(dict, (char_u *)"highlight", -1);
684 if (di != NULL)
685 {
686 char_u *highlight;
687 int hl_id = 0;
688
Bram Moolenaar8f667172018-12-14 15:38:31 +0100689 highlight = dict_get_string(dict, (char_u *)"highlight", FALSE);
Bram Moolenaar98aefe72018-12-13 22:20:09 +0100690 if (highlight != NULL && *highlight != NUL)
691 hl_id = syn_name2id(highlight);
692 if (hl_id <= 0)
693 {
694 EMSG2(_("E970: Unknown highlight group name: '%s'"),
695 highlight == NULL ? (char_u *)"" : highlight);
696 return;
697 }
698 prop->pt_hl_id = hl_id;
699 }
700
701 di = dict_find(dict, (char_u *)"priority", -1);
702 if (di != NULL)
Bram Moolenaard155d7a2018-12-21 16:04:21 +0100703 prop->pt_priority = tv_get_number(&di->di_tv);
Bram Moolenaar98aefe72018-12-13 22:20:09 +0100704
705 di = dict_find(dict, (char_u *)"start_incl", -1);
706 if (di != NULL)
707 {
Bram Moolenaard155d7a2018-12-21 16:04:21 +0100708 if (tv_get_number(&di->di_tv))
Bram Moolenaar98aefe72018-12-13 22:20:09 +0100709 prop->pt_flags |= PT_FLAG_INS_START_INCL;
710 else
711 prop->pt_flags &= ~PT_FLAG_INS_START_INCL;
712 }
713
714 di = dict_find(dict, (char_u *)"end_incl", -1);
715 if (di != NULL)
716 {
Bram Moolenaard155d7a2018-12-21 16:04:21 +0100717 if (tv_get_number(&di->di_tv))
Bram Moolenaar98aefe72018-12-13 22:20:09 +0100718 prop->pt_flags |= PT_FLAG_INS_END_INCL;
719 else
720 prop->pt_flags &= ~PT_FLAG_INS_END_INCL;
721 }
722 }
723}
724
725/*
726 * prop_type_add({name}, {props})
727 */
728 void
729f_prop_type_add(typval_T *argvars, typval_T *rettv UNUSED)
730{
731 prop_type_set(argvars, TRUE);
732}
733
734/*
735 * prop_type_change({name}, {props})
736 */
737 void
738f_prop_type_change(typval_T *argvars, typval_T *rettv UNUSED)
739{
740 prop_type_set(argvars, FALSE);
741}
742
743/*
744 * prop_type_delete({name} [, {bufnr}])
745 */
746 void
747f_prop_type_delete(typval_T *argvars, typval_T *rettv UNUSED)
748{
749 char_u *name;
750 buf_T *buf = NULL;
751 hashitem_T *hi;
752
Bram Moolenaard155d7a2018-12-21 16:04:21 +0100753 name = tv_get_string(&argvars[0]);
Bram Moolenaar98aefe72018-12-13 22:20:09 +0100754 if (*name == NUL)
755 {
756 EMSG(_(e_invarg));
757 return;
758 }
759
760 if (argvars[1].v_type != VAR_UNKNOWN)
761 {
762 if (get_bufnr_from_arg(&argvars[1], &buf) == FAIL)
763 return;
764 }
765
766 hi = find_prop_hi(name, buf);
767 if (hi != NULL)
768 {
769 hashtab_T *ht;
Bram Moolenaarfb95e212018-12-14 12:18:11 +0100770 proptype_T *prop = HI2PT(hi);
Bram Moolenaar98aefe72018-12-13 22:20:09 +0100771
772 if (buf == NULL)
773 ht = global_proptypes;
774 else
775 ht = buf->b_proptypes;
776 hash_remove(ht, hi);
Bram Moolenaarfb95e212018-12-14 12:18:11 +0100777 vim_free(prop);
Bram Moolenaar98aefe72018-12-13 22:20:09 +0100778 }
779}
780
781/*
782 * prop_type_get({name} [, {bufnr}])
783 */
784 void
785f_prop_type_get(typval_T *argvars, typval_T *rettv UNUSED)
786{
Bram Moolenaard155d7a2018-12-21 16:04:21 +0100787 char_u *name = tv_get_string(&argvars[0]);
Bram Moolenaar98aefe72018-12-13 22:20:09 +0100788
789 if (*name == NUL)
790 {
791 EMSG(_(e_invarg));
792 return;
793 }
794 if (rettv_dict_alloc(rettv) == OK)
795 {
796 proptype_T *prop = NULL;
797 buf_T *buf = NULL;
798
799 if (argvars[1].v_type != VAR_UNKNOWN)
800 {
801 if (get_bufnr_from_arg(&argvars[1], &buf) == FAIL)
802 return;
803 }
804
805 prop = find_prop(name, buf);
806 if (prop != NULL)
807 {
808 dict_T *d = rettv->vval.v_dict;
809
810 if (prop->pt_hl_id > 0)
811 dict_add_string(d, "highlight", syn_id2name(prop->pt_hl_id));
812 dict_add_number(d, "priority", prop->pt_priority);
813 dict_add_number(d, "start_incl",
814 (prop->pt_flags & PT_FLAG_INS_START_INCL) ? 1 : 0);
815 dict_add_number(d, "end_incl",
816 (prop->pt_flags & PT_FLAG_INS_END_INCL) ? 1 : 0);
817 if (buf != NULL)
818 dict_add_number(d, "bufnr", buf->b_fnum);
819 }
820 }
821}
822
823 static void
824list_types(hashtab_T *ht, list_T *l)
825{
826 long todo;
827 hashitem_T *hi;
828
829 todo = (long)ht->ht_used;
830 for (hi = ht->ht_array; todo > 0; ++hi)
831 {
832 if (!HASHITEM_EMPTY(hi))
833 {
834 proptype_T *prop = HI2PT(hi);
835
836 list_append_string(l, prop->pt_name, -1);
837 --todo;
838 }
839 }
840}
841
842/*
843 * prop_type_list([{bufnr}])
844 */
845 void
846f_prop_type_list(typval_T *argvars, typval_T *rettv UNUSED)
847{
848 buf_T *buf = NULL;
849
850 if (rettv_list_alloc(rettv) == OK)
851 {
852 if (argvars[0].v_type != VAR_UNKNOWN)
853 {
854 if (get_bufnr_from_arg(&argvars[0], &buf) == FAIL)
855 return;
856 }
857 if (buf == NULL)
858 {
859 if (global_proptypes != NULL)
860 list_types(global_proptypes, rettv->vval.v_list);
861 }
862 else if (buf->b_proptypes != NULL)
863 list_types(buf->b_proptypes, rettv->vval.v_list);
864 }
865}
866
867/*
868 * Free all property types in "ht".
869 */
870 static void
871clear_ht_prop_types(hashtab_T *ht)
872{
873 long todo;
874 hashitem_T *hi;
875
876 if (ht == NULL)
877 return;
878
879 todo = (long)ht->ht_used;
880 for (hi = ht->ht_array; todo > 0; ++hi)
881 {
882 if (!HASHITEM_EMPTY(hi))
883 {
884 proptype_T *prop = HI2PT(hi);
885
886 vim_free(prop);
887 --todo;
888 }
889 }
890
891 hash_clear(ht);
892 vim_free(ht);
893}
894
895#if defined(EXITFREE) || defined(PROTO)
896/*
Bram Moolenaarfb95e212018-12-14 12:18:11 +0100897 * Free all global property types.
Bram Moolenaar98aefe72018-12-13 22:20:09 +0100898 */
899 void
900clear_global_prop_types(void)
901{
902 clear_ht_prop_types(global_proptypes);
903 global_proptypes = NULL;
904}
905#endif
906
907/*
908 * Free all property types for "buf".
909 */
910 void
911clear_buf_prop_types(buf_T *buf)
912{
913 clear_ht_prop_types(buf->b_proptypes);
914 buf->b_proptypes = NULL;
915}
916
917#endif // FEAT_TEXT_PROP