blob: f5b977a7a0fa71f1495e54cdf050c05e1e441fe1 [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 Moolenaare3d31b02018-12-24 23:07:04 +010020 * - When deleting a line where a prop ended, adjust flag of previous line.
21 * - When deleting a line where a prop started, adjust flag of next line.
22 * - When inserting a line add props that continue from previous line.
23 * - Adjust property column and length when text is inserted/deleted
Bram Moolenaar98aefe72018-12-13 22:20:09 +010024 * - Add an arrray for global_proptypes, to quickly lookup a proptype by ID
25 * - Add an arrray for b_proptypes, to quickly lookup a proptype by ID
Bram Moolenaar98aefe72018-12-13 22:20:09 +010026 * - add mechanism to keep track of changed lines.
27 */
28
29#include "vim.h"
30
31#if defined(FEAT_TEXT_PROP) || defined(PROTO)
32
33/*
34 * In a hashtable item "hi_key" points to "pt_name" in a proptype_T.
35 * This avoids adding a pointer to the hashtable item.
36 * PT2HIKEY() converts a proptype pointer to a hashitem key pointer.
37 * HIKEY2PT() converts a hashitem key pointer to a proptype pointer.
38 * HI2PT() converts a hashitem pointer to a proptype pointer.
39 */
40#define PT2HIKEY(p) ((p)->pt_name)
41#define HIKEY2PT(p) ((proptype_T *)((p) - offsetof(proptype_T, pt_name)))
42#define HI2PT(hi) HIKEY2PT((hi)->hi_key)
43
44// The global text property types.
45static hashtab_T *global_proptypes = NULL;
46
47// The last used text property type ID.
48static int proptype_id = 0;
49
50static char_u e_type_not_exist[] = N_("E971: Property type %s does not exist");
51static char_u e_invalid_col[] = N_("E964: Invalid column number: %ld");
Bram Moolenaare3d31b02018-12-24 23:07:04 +010052static char_u e_invalid_lnum[] = N_("E966: Invalid line number: %ld");
Bram Moolenaar98aefe72018-12-13 22:20:09 +010053
54/*
55 * Find a property type by name, return the hashitem.
56 * Returns NULL if the item can't be found.
57 */
58 static hashitem_T *
59find_prop_hi(char_u *name, buf_T *buf)
60{
61 hashtab_T *ht;
62 hashitem_T *hi;
63
64 if (*name == NUL)
65 return NULL;
66 if (buf == NULL)
67 ht = global_proptypes;
68 else
69 ht = buf->b_proptypes;
70
71 if (ht == NULL)
72 return NULL;
73 hi = hash_find(ht, name);
74 if (HASHITEM_EMPTY(hi))
75 return NULL;
76 return hi;
77}
78
79/*
80 * Like find_prop_hi() but return the property type.
81 */
82 static proptype_T *
83find_prop(char_u *name, buf_T *buf)
84{
85 hashitem_T *hi = find_prop_hi(name, buf);
86
87 if (hi == NULL)
88 return NULL;
89 return HI2PT(hi);
90}
91
92/*
93 * Lookup a property type by name. First in "buf" and when not found in the
94 * global types.
95 * When not found gives an error message and returns NULL.
96 */
97 static proptype_T *
98lookup_prop_type(char_u *name, buf_T *buf)
99{
100 proptype_T *type = find_prop(name, buf);
101
102 if (type == NULL)
103 type = find_prop(name, NULL);
104 if (type == NULL)
105 EMSG2(_(e_type_not_exist), name);
106 return type;
107}
108
109/*
110 * Get an optional "bufnr" item from the dict in "arg".
111 * When the argument is not used or "bufnr" is not present then "buf" is
112 * unchanged.
113 * If "bufnr" is valid or not present return OK.
114 * When "arg" is not a dict or "bufnr" is invalide return FAIL.
115 */
116 static int
117get_bufnr_from_arg(typval_T *arg, buf_T **buf)
118{
119 dictitem_T *di;
120
121 if (arg->v_type != VAR_DICT)
122 {
123 EMSG(_(e_dictreq));
124 return FAIL;
125 }
126 if (arg->vval.v_dict == NULL)
127 return OK; // NULL dict is like an empty dict
128 di = dict_find(arg->vval.v_dict, (char_u *)"bufnr", -1);
129 if (di != NULL)
130 {
131 *buf = get_buf_tv(&di->di_tv, FALSE);
132 if (*buf == NULL)
133 return FAIL;
134 }
135 return OK;
136}
137
138/*
139 * prop_add({lnum}, {col}, {props})
140 */
141 void
142f_prop_add(typval_T *argvars, typval_T *rettv UNUSED)
143{
144 linenr_T lnum;
Bram Moolenaare3d31b02018-12-24 23:07:04 +0100145 linenr_T start_lnum;
146 linenr_T end_lnum;
147 colnr_T start_col;
148 colnr_T end_col;
Bram Moolenaar98aefe72018-12-13 22:20:09 +0100149 dict_T *dict;
Bram Moolenaar98aefe72018-12-13 22:20:09 +0100150 char_u *type_name;
151 proptype_T *type;
152 buf_T *buf = curbuf;
153 int id = 0;
154 char_u *newtext;
155 int proplen;
156 size_t textlen;
157 char_u *props;
158 char_u *newprops;
Bram Moolenaarfb95e212018-12-14 12:18:11 +0100159 textprop_T tmp_prop;
Bram Moolenaar98aefe72018-12-13 22:20:09 +0100160 int i;
161
Bram Moolenaare3d31b02018-12-24 23:07:04 +0100162 start_lnum = tv_get_number(&argvars[0]);
163 start_col = tv_get_number(&argvars[1]);
164 if (start_col < 1)
Bram Moolenaar98aefe72018-12-13 22:20:09 +0100165 {
Bram Moolenaare3d31b02018-12-24 23:07:04 +0100166 EMSGN(_(e_invalid_col), (long)start_col);
Bram Moolenaar98aefe72018-12-13 22:20:09 +0100167 return;
168 }
169 if (argvars[2].v_type != VAR_DICT)
170 {
171 EMSG(_(e_dictreq));
172 return;
173 }
174 dict = argvars[2].vval.v_dict;
175
176 if (dict == NULL || dict_find(dict, (char_u *)"type", -1) == NULL)
177 {
178 EMSG(_("E965: missing property type name"));
179 return;
180 }
Bram Moolenaar8f667172018-12-14 15:38:31 +0100181 type_name = dict_get_string(dict, (char_u *)"type", FALSE);
Bram Moolenaar98aefe72018-12-13 22:20:09 +0100182
183 if (dict_find(dict, (char_u *)"end_lnum", -1) != NULL)
184 {
Bram Moolenaare3d31b02018-12-24 23:07:04 +0100185 end_lnum = dict_get_number(dict, (char_u *)"end_lnum");
186 if (end_lnum < start_lnum)
187 {
188 EMSG2(_(e_invargval), "end_lnum");
189 return;
190 }
Bram Moolenaar98aefe72018-12-13 22:20:09 +0100191 }
Bram Moolenaare3d31b02018-12-24 23:07:04 +0100192 else
193 end_lnum = start_lnum;
Bram Moolenaar98aefe72018-12-13 22:20:09 +0100194
195 if (dict_find(dict, (char_u *)"length", -1) != NULL)
Bram Moolenaare3d31b02018-12-24 23:07:04 +0100196 {
197 long length = dict_get_number(dict, (char_u *)"length");
198
199 if (length < 1 || end_lnum > start_lnum)
200 {
201 EMSG2(_(e_invargval), "length");
202 return;
203 }
204 end_col = start_col + length - 1;
205 }
Bram Moolenaar98aefe72018-12-13 22:20:09 +0100206 else if (dict_find(dict, (char_u *)"end_col", -1) != NULL)
207 {
Bram Moolenaare3d31b02018-12-24 23:07:04 +0100208 end_col = dict_get_number(dict, (char_u *)"end_col");
209 if (end_col <= 0)
Bram Moolenaar98aefe72018-12-13 22:20:09 +0100210 {
211 EMSG2(_(e_invargval), "end_col");
212 return;
213 }
214 }
Bram Moolenaare3d31b02018-12-24 23:07:04 +0100215 else if (start_lnum == end_lnum)
216 end_col = start_col;
217 else
218 end_col = 1;
Bram Moolenaar98aefe72018-12-13 22:20:09 +0100219
220 if (dict_find(dict, (char_u *)"id", -1) != NULL)
Bram Moolenaar8f667172018-12-14 15:38:31 +0100221 id = dict_get_number(dict, (char_u *)"id");
Bram Moolenaar98aefe72018-12-13 22:20:09 +0100222
223 if (get_bufnr_from_arg(&argvars[2], &buf) == FAIL)
224 return;
225
226 type = lookup_prop_type(type_name, buf);
227 if (type == NULL)
228 return;
229
Bram Moolenaare3d31b02018-12-24 23:07:04 +0100230 if (start_lnum < 1 || start_lnum > buf->b_ml.ml_line_count)
Bram Moolenaar98aefe72018-12-13 22:20:09 +0100231 {
Bram Moolenaare3d31b02018-12-24 23:07:04 +0100232 EMSGN(_(e_invalid_lnum), (long)start_lnum);
233 return;
234 }
235 if (end_lnum < start_lnum || end_lnum > buf->b_ml.ml_line_count)
236 {
237 EMSGN(_(e_invalid_lnum), (long)end_lnum);
Bram Moolenaar98aefe72018-12-13 22:20:09 +0100238 return;
239 }
240
Bram Moolenaare3d31b02018-12-24 23:07:04 +0100241 for (lnum = start_lnum; lnum <= end_lnum; ++lnum)
Bram Moolenaar98aefe72018-12-13 22:20:09 +0100242 {
Bram Moolenaare3d31b02018-12-24 23:07:04 +0100243 colnr_T col; // start column
244 long length; // in bytes
245
246 // Fetch the line to get the ml_line_len field updated.
247 proplen = get_text_props(buf, lnum, &props, TRUE);
248 textlen = buf->b_ml.ml_line_len - proplen * sizeof(textprop_T);
249
250 if (lnum == start_lnum)
251 col = start_col;
252 else
253 col = 1;
254 if (col - 1 > (colnr_T)textlen)
255 {
256 EMSGN(_(e_invalid_col), (long)start_col);
257 return;
258 }
259
260 if (lnum == end_lnum)
261 length = end_col - col + 1;
262 else
263 length = textlen - col + 1;
264 if (length > textlen)
265 length = textlen; // can include the end-of-line
266 if (length < 1)
267 length = 1;
268
269 // Allocate the new line with space for the new proprety.
270 newtext = alloc(buf->b_ml.ml_line_len + sizeof(textprop_T));
271 if (newtext == NULL)
272 return;
273 // Copy the text, including terminating NUL.
274 mch_memmove(newtext, buf->b_ml.ml_line_ptr, textlen);
275
276 // Find the index where to insert the new property.
277 // Since the text properties are not aligned properly when stored with the
278 // text, we need to copy them as bytes before using it as a struct.
279 for (i = 0; i < proplen; ++i)
280 {
281 mch_memmove(&tmp_prop, props + i * sizeof(textprop_T),
282 sizeof(textprop_T));
283 if (tmp_prop.tp_col >= col)
284 break;
285 }
286 newprops = newtext + textlen;
287 if (i > 0)
288 mch_memmove(newprops, props, sizeof(textprop_T) * i);
289
290 tmp_prop.tp_col = col;
291 tmp_prop.tp_len = length;
292 tmp_prop.tp_id = id;
293 tmp_prop.tp_type = type->pt_id;
294 tmp_prop.tp_flags = (lnum > start_lnum ? TP_FLAG_CONT_PREV : 0)
295 | (lnum < end_lnum ? TP_FLAG_CONT_NEXT : 0);
296 mch_memmove(newprops + i * sizeof(textprop_T), &tmp_prop,
297 sizeof(textprop_T));
298
299 if (i < proplen)
300 mch_memmove(newprops + (i + 1) * sizeof(textprop_T),
301 props + i * sizeof(textprop_T),
302 sizeof(textprop_T) * (proplen - i));
303
304 if (buf->b_ml.ml_flags & ML_LINE_DIRTY)
305 vim_free(buf->b_ml.ml_line_ptr);
306 buf->b_ml.ml_line_ptr = newtext;
307 buf->b_ml.ml_line_len += sizeof(textprop_T);
308 buf->b_ml.ml_flags |= ML_LINE_DIRTY;
Bram Moolenaar98aefe72018-12-13 22:20:09 +0100309 }
310
Bram Moolenaar98aefe72018-12-13 22:20:09 +0100311 redraw_buf_later(buf, NOT_VALID);
312}
313
314/*
315 * Return TRUE if any text properties are defined globally or for buffer
Bram Moolenaar8f667172018-12-14 15:38:31 +0100316 * "buf".
Bram Moolenaar98aefe72018-12-13 22:20:09 +0100317 */
318 int
319has_any_text_properties(buf_T *buf)
320{
321 return buf->b_proptypes != NULL || global_proptypes != NULL;
322}
323
324/*
Bram Moolenaarfb95e212018-12-14 12:18:11 +0100325 * Fetch the text properties for line "lnum" in buffer "buf".
Bram Moolenaar98aefe72018-12-13 22:20:09 +0100326 * Returns the number of text properties and, when non-zero, a pointer to the
327 * first one in "props" (note that it is not aligned, therefore the char_u
328 * pointer).
329 */
330 int
331get_text_props(buf_T *buf, linenr_T lnum, char_u **props, int will_change)
332{
333 char_u *text;
334 size_t textlen;
335 size_t proplen;
336
337 // Be quick when no text property types are defined.
338 if (!has_any_text_properties(buf))
339 return 0;
340
341 // Fetch the line to get the ml_line_len field updated.
342 text = ml_get_buf(buf, lnum, will_change);
343 textlen = STRLEN(text) + 1;
344 proplen = buf->b_ml.ml_line_len - textlen;
345 if (proplen % sizeof(textprop_T) != 0)
346 {
347 IEMSG(_("E967: text property info corrupted"));
348 return 0;
349 }
350 if (proplen > 0)
351 *props = text + textlen;
Bram Moolenaar4efe73b2018-12-16 14:37:39 +0100352 return (int)(proplen / sizeof(textprop_T));
Bram Moolenaar98aefe72018-12-13 22:20:09 +0100353}
354
355 static proptype_T *
356find_type_by_id(hashtab_T *ht, int id)
357{
358 long todo;
359 hashitem_T *hi;
360
361 if (ht == NULL)
362 return NULL;
363
364 // TODO: Make this faster by keeping a list of types sorted on ID and use
365 // a binary search.
366
367 todo = (long)ht->ht_used;
368 for (hi = ht->ht_array; todo > 0; ++hi)
369 {
370 if (!HASHITEM_EMPTY(hi))
371 {
372 proptype_T *prop = HI2PT(hi);
373
374 if (prop->pt_id == id)
375 return prop;
376 --todo;
377 }
378 }
379 return NULL;
380}
381
382/*
383 * Find a property type by ID in "buf" or globally.
384 * Returns NULL if not found.
385 */
386 proptype_T *
387text_prop_type_by_id(buf_T *buf, int id)
388{
389 proptype_T *type;
390
391 type = find_type_by_id(buf->b_proptypes, id);
392 if (type == NULL)
393 type = find_type_by_id(global_proptypes, id);
394 return type;
395}
396
397/*
398 * prop_clear({lnum} [, {lnum_end} [, {bufnr}]])
399 */
400 void
401f_prop_clear(typval_T *argvars, typval_T *rettv UNUSED)
402{
Bram Moolenaard155d7a2018-12-21 16:04:21 +0100403 linenr_T start = tv_get_number(&argvars[0]);
Bram Moolenaar98aefe72018-12-13 22:20:09 +0100404 linenr_T end = start;
405 linenr_T lnum;
406 buf_T *buf = curbuf;
407
408 if (argvars[1].v_type != VAR_UNKNOWN)
409 {
Bram Moolenaard155d7a2018-12-21 16:04:21 +0100410 end = tv_get_number(&argvars[1]);
Bram Moolenaar98aefe72018-12-13 22:20:09 +0100411 if (argvars[2].v_type != VAR_UNKNOWN)
412 {
413 if (get_bufnr_from_arg(&argvars[2], &buf) == FAIL)
414 return;
415 }
416 }
417 if (start < 1 || end < 1)
418 {
419 EMSG(_(e_invrange));
420 return;
421 }
422
423 for (lnum = start; lnum <= end; ++lnum)
424 {
425 char_u *text;
426 size_t len;
427
428 if (lnum > buf->b_ml.ml_line_count)
429 break;
430 text = ml_get_buf(buf, lnum, FALSE);
431 len = STRLEN(text) + 1;
432 if ((size_t)buf->b_ml.ml_line_len > len)
433 {
434 if (!(buf->b_ml.ml_flags & ML_LINE_DIRTY))
435 {
436 char_u *newtext = vim_strsave(text);
437
438 // need to allocate the line now
439 if (newtext == NULL)
440 return;
441 buf->b_ml.ml_line_ptr = newtext;
442 buf->b_ml.ml_flags |= ML_LINE_DIRTY;
443 }
Bram Moolenaar4efe73b2018-12-16 14:37:39 +0100444 buf->b_ml.ml_line_len = (int)len;
Bram Moolenaar98aefe72018-12-13 22:20:09 +0100445 }
446 }
447 redraw_buf_later(buf, NOT_VALID);
448}
449
450/*
451 * prop_list({lnum} [, {bufnr}])
452 */
453 void
454f_prop_list(typval_T *argvars, typval_T *rettv)
455{
Bram Moolenaard155d7a2018-12-21 16:04:21 +0100456 linenr_T lnum = tv_get_number(&argvars[0]);
Bram Moolenaar98aefe72018-12-13 22:20:09 +0100457 buf_T *buf = curbuf;
458
459 if (argvars[1].v_type != VAR_UNKNOWN)
460 {
461 if (get_bufnr_from_arg(&argvars[1], &buf) == FAIL)
462 return;
463 }
464 if (lnum < 1 || lnum > buf->b_ml.ml_line_count)
465 {
466 EMSG(_(e_invrange));
467 return;
468 }
469
470 if (rettv_list_alloc(rettv) == OK)
471 {
472 char_u *text = ml_get_buf(buf, lnum, FALSE);
473 size_t textlen = STRLEN(text) + 1;
Bram Moolenaar4efe73b2018-12-16 14:37:39 +0100474 int count = (int)((buf->b_ml.ml_line_len - textlen)
475 / sizeof(textprop_T));
Bram Moolenaar98aefe72018-12-13 22:20:09 +0100476 int i;
477 textprop_T prop;
478 proptype_T *pt;
479
480 for (i = 0; i < count; ++i)
481 {
482 dict_T *d = dict_alloc();
483
484 if (d == NULL)
485 break;
486 mch_memmove(&prop, text + textlen + i * sizeof(textprop_T),
487 sizeof(textprop_T));
488 dict_add_number(d, "col", prop.tp_col);
489 dict_add_number(d, "length", prop.tp_len);
490 dict_add_number(d, "id", prop.tp_id);
491 dict_add_number(d, "start", !(prop.tp_flags & TP_FLAG_CONT_PREV));
492 dict_add_number(d, "end", !(prop.tp_flags & TP_FLAG_CONT_NEXT));
493 pt = text_prop_type_by_id(buf, prop.tp_type);
494 if (pt != NULL)
495 dict_add_string(d, "type", pt->pt_name);
496
497 list_append_dict(rettv->vval.v_list, d);
498 }
499 }
500}
501
502/*
503 * prop_remove({props} [, {lnum} [, {lnum_end}]])
504 */
505 void
506f_prop_remove(typval_T *argvars, typval_T *rettv)
507{
508 linenr_T start = 1;
509 linenr_T end = 0;
510 linenr_T lnum;
511 dict_T *dict;
512 buf_T *buf = curbuf;
513 dictitem_T *di;
514 int do_all = FALSE;
515 int id = -1;
516 int type_id = -1;
517
518 rettv->vval.v_number = 0;
519 if (argvars[0].v_type != VAR_DICT || argvars[0].vval.v_dict == NULL)
520 {
521 EMSG(_(e_invarg));
522 return;
523 }
524
525 if (argvars[1].v_type != VAR_UNKNOWN)
526 {
Bram Moolenaard155d7a2018-12-21 16:04:21 +0100527 start = tv_get_number(&argvars[1]);
Bram Moolenaar98aefe72018-12-13 22:20:09 +0100528 end = start;
529 if (argvars[2].v_type != VAR_UNKNOWN)
Bram Moolenaard155d7a2018-12-21 16:04:21 +0100530 end = tv_get_number(&argvars[2]);
Bram Moolenaar98aefe72018-12-13 22:20:09 +0100531 if (start < 1 || end < 1)
532 {
533 EMSG(_(e_invrange));
534 return;
535 }
536 }
537
538 dict = argvars[0].vval.v_dict;
539 di = dict_find(dict, (char_u *)"bufnr", -1);
540 if (di != NULL)
541 {
542 buf = get_buf_tv(&di->di_tv, FALSE);
543 if (buf == NULL)
544 return;
545 }
546
547 di = dict_find(dict, (char_u*)"all", -1);
548 if (di != NULL)
Bram Moolenaar8f667172018-12-14 15:38:31 +0100549 do_all = dict_get_number(dict, (char_u *)"all");
Bram Moolenaar98aefe72018-12-13 22:20:09 +0100550
551 if (dict_find(dict, (char_u *)"id", -1) != NULL)
Bram Moolenaar8f667172018-12-14 15:38:31 +0100552 id = dict_get_number(dict, (char_u *)"id");
Bram Moolenaar98aefe72018-12-13 22:20:09 +0100553 if (dict_find(dict, (char_u *)"type", -1))
554 {
Bram Moolenaar8f667172018-12-14 15:38:31 +0100555 char_u *name = dict_get_string(dict, (char_u *)"type", FALSE);
Bram Moolenaar98aefe72018-12-13 22:20:09 +0100556 proptype_T *type = lookup_prop_type(name, buf);
557
558 if (type == NULL)
559 return;
560 type_id = type->pt_id;
561 }
562 if (id == -1 && type_id == -1)
563 {
564 EMSG(_("E968: Need at least one of 'id' or 'type'"));
565 return;
566 }
567
568 if (end == 0)
569 end = buf->b_ml.ml_line_count;
570 for (lnum = start; lnum <= end; ++lnum)
571 {
572 char_u *text;
573 size_t len;
574
575 if (lnum > buf->b_ml.ml_line_count)
576 break;
577 text = ml_get_buf(buf, lnum, FALSE);
578 len = STRLEN(text) + 1;
579 if ((size_t)buf->b_ml.ml_line_len > len)
580 {
581 static textprop_T textprop; // static because of alignment
582 unsigned idx;
583
584 for (idx = 0; idx < (buf->b_ml.ml_line_len - len)
585 / sizeof(textprop_T); ++idx)
586 {
587 char_u *cur_prop = buf->b_ml.ml_line_ptr + len
588 + idx * sizeof(textprop_T);
589 size_t taillen;
590
591 mch_memmove(&textprop, cur_prop, sizeof(textprop_T));
592 if (textprop.tp_id == id || textprop.tp_type == type_id)
593 {
594 if (!(buf->b_ml.ml_flags & ML_LINE_DIRTY))
595 {
596 char_u *newptr = alloc(buf->b_ml.ml_line_len);
597
598 // need to allocate the line to be able to change it
599 if (newptr == NULL)
600 return;
601 mch_memmove(newptr, buf->b_ml.ml_line_ptr,
602 buf->b_ml.ml_line_len);
603 buf->b_ml.ml_line_ptr = newptr;
604 curbuf->b_ml.ml_flags |= ML_LINE_DIRTY;
605 }
606
607 taillen = buf->b_ml.ml_line_len - len
608 - (idx + 1) * sizeof(textprop_T);
609 if (taillen > 0)
610 mch_memmove(cur_prop, cur_prop + sizeof(textprop_T),
611 taillen);
612 buf->b_ml.ml_line_len -= sizeof(textprop_T);
613 --idx;
614
615 ++rettv->vval.v_number;
616 if (!do_all)
617 break;
618 }
619 }
620 }
621 }
622 redraw_buf_later(buf, NOT_VALID);
623}
624
625/*
626 * Common for f_prop_type_add() and f_prop_type_change().
627 */
628 void
629prop_type_set(typval_T *argvars, int add)
630{
631 char_u *name;
632 buf_T *buf = NULL;
633 dict_T *dict;
634 dictitem_T *di;
635 proptype_T *prop;
636
Bram Moolenaard155d7a2018-12-21 16:04:21 +0100637 name = tv_get_string(&argvars[0]);
Bram Moolenaar98aefe72018-12-13 22:20:09 +0100638 if (*name == NUL)
639 {
640 EMSG(_(e_invarg));
641 return;
642 }
643
644 if (get_bufnr_from_arg(&argvars[1], &buf) == FAIL)
645 return;
646 dict = argvars[1].vval.v_dict;
647
648 prop = find_prop(name, buf);
649 if (add)
650 {
651 hashtab_T **htp;
652
653 if (prop != NULL)
654 {
655 EMSG2(_("E969: Property type %s already defined"), name);
656 return;
657 }
Bram Moolenaar4efe73b2018-12-16 14:37:39 +0100658 prop = (proptype_T *)alloc_clear((int)(sizeof(proptype_T) + STRLEN(name)));
Bram Moolenaar98aefe72018-12-13 22:20:09 +0100659 if (prop == NULL)
660 return;
661 STRCPY(prop->pt_name, name);
662 prop->pt_id = ++proptype_id;
663 htp = buf == NULL ? &global_proptypes : &buf->b_proptypes;
664 if (*htp == NULL)
665 {
666 *htp = (hashtab_T *)alloc(sizeof(hashtab_T));
667 if (*htp == NULL)
Bram Moolenaarfb95e212018-12-14 12:18:11 +0100668 {
669 vim_free(prop);
Bram Moolenaar98aefe72018-12-13 22:20:09 +0100670 return;
Bram Moolenaarfb95e212018-12-14 12:18:11 +0100671 }
Bram Moolenaar98aefe72018-12-13 22:20:09 +0100672 hash_init(*htp);
673 }
Bram Moolenaarfb95e212018-12-14 12:18:11 +0100674 hash_add(*htp, PT2HIKEY(prop));
Bram Moolenaar98aefe72018-12-13 22:20:09 +0100675 }
676 else
677 {
678 if (prop == NULL)
679 {
680 EMSG2(_(e_type_not_exist), name);
681 return;
682 }
683 }
684
685 if (dict != NULL)
686 {
687 di = dict_find(dict, (char_u *)"highlight", -1);
688 if (di != NULL)
689 {
690 char_u *highlight;
691 int hl_id = 0;
692
Bram Moolenaar8f667172018-12-14 15:38:31 +0100693 highlight = dict_get_string(dict, (char_u *)"highlight", FALSE);
Bram Moolenaar98aefe72018-12-13 22:20:09 +0100694 if (highlight != NULL && *highlight != NUL)
695 hl_id = syn_name2id(highlight);
696 if (hl_id <= 0)
697 {
698 EMSG2(_("E970: Unknown highlight group name: '%s'"),
699 highlight == NULL ? (char_u *)"" : highlight);
700 return;
701 }
702 prop->pt_hl_id = hl_id;
703 }
704
705 di = dict_find(dict, (char_u *)"priority", -1);
706 if (di != NULL)
Bram Moolenaard155d7a2018-12-21 16:04:21 +0100707 prop->pt_priority = tv_get_number(&di->di_tv);
Bram Moolenaar98aefe72018-12-13 22:20:09 +0100708
709 di = dict_find(dict, (char_u *)"start_incl", -1);
710 if (di != NULL)
711 {
Bram Moolenaard155d7a2018-12-21 16:04:21 +0100712 if (tv_get_number(&di->di_tv))
Bram Moolenaar98aefe72018-12-13 22:20:09 +0100713 prop->pt_flags |= PT_FLAG_INS_START_INCL;
714 else
715 prop->pt_flags &= ~PT_FLAG_INS_START_INCL;
716 }
717
718 di = dict_find(dict, (char_u *)"end_incl", -1);
719 if (di != NULL)
720 {
Bram Moolenaard155d7a2018-12-21 16:04:21 +0100721 if (tv_get_number(&di->di_tv))
Bram Moolenaar98aefe72018-12-13 22:20:09 +0100722 prop->pt_flags |= PT_FLAG_INS_END_INCL;
723 else
724 prop->pt_flags &= ~PT_FLAG_INS_END_INCL;
725 }
726 }
727}
728
729/*
730 * prop_type_add({name}, {props})
731 */
732 void
733f_prop_type_add(typval_T *argvars, typval_T *rettv UNUSED)
734{
735 prop_type_set(argvars, TRUE);
736}
737
738/*
739 * prop_type_change({name}, {props})
740 */
741 void
742f_prop_type_change(typval_T *argvars, typval_T *rettv UNUSED)
743{
744 prop_type_set(argvars, FALSE);
745}
746
747/*
748 * prop_type_delete({name} [, {bufnr}])
749 */
750 void
751f_prop_type_delete(typval_T *argvars, typval_T *rettv UNUSED)
752{
753 char_u *name;
754 buf_T *buf = NULL;
755 hashitem_T *hi;
756
Bram Moolenaard155d7a2018-12-21 16:04:21 +0100757 name = tv_get_string(&argvars[0]);
Bram Moolenaar98aefe72018-12-13 22:20:09 +0100758 if (*name == NUL)
759 {
760 EMSG(_(e_invarg));
761 return;
762 }
763
764 if (argvars[1].v_type != VAR_UNKNOWN)
765 {
766 if (get_bufnr_from_arg(&argvars[1], &buf) == FAIL)
767 return;
768 }
769
770 hi = find_prop_hi(name, buf);
771 if (hi != NULL)
772 {
773 hashtab_T *ht;
Bram Moolenaarfb95e212018-12-14 12:18:11 +0100774 proptype_T *prop = HI2PT(hi);
Bram Moolenaar98aefe72018-12-13 22:20:09 +0100775
776 if (buf == NULL)
777 ht = global_proptypes;
778 else
779 ht = buf->b_proptypes;
780 hash_remove(ht, hi);
Bram Moolenaarfb95e212018-12-14 12:18:11 +0100781 vim_free(prop);
Bram Moolenaar98aefe72018-12-13 22:20:09 +0100782 }
783}
784
785/*
786 * prop_type_get({name} [, {bufnr}])
787 */
788 void
789f_prop_type_get(typval_T *argvars, typval_T *rettv UNUSED)
790{
Bram Moolenaard155d7a2018-12-21 16:04:21 +0100791 char_u *name = tv_get_string(&argvars[0]);
Bram Moolenaar98aefe72018-12-13 22:20:09 +0100792
793 if (*name == NUL)
794 {
795 EMSG(_(e_invarg));
796 return;
797 }
798 if (rettv_dict_alloc(rettv) == OK)
799 {
800 proptype_T *prop = NULL;
801 buf_T *buf = NULL;
802
803 if (argvars[1].v_type != VAR_UNKNOWN)
804 {
805 if (get_bufnr_from_arg(&argvars[1], &buf) == FAIL)
806 return;
807 }
808
809 prop = find_prop(name, buf);
810 if (prop != NULL)
811 {
812 dict_T *d = rettv->vval.v_dict;
813
814 if (prop->pt_hl_id > 0)
815 dict_add_string(d, "highlight", syn_id2name(prop->pt_hl_id));
816 dict_add_number(d, "priority", prop->pt_priority);
817 dict_add_number(d, "start_incl",
818 (prop->pt_flags & PT_FLAG_INS_START_INCL) ? 1 : 0);
819 dict_add_number(d, "end_incl",
820 (prop->pt_flags & PT_FLAG_INS_END_INCL) ? 1 : 0);
821 if (buf != NULL)
822 dict_add_number(d, "bufnr", buf->b_fnum);
823 }
824 }
825}
826
827 static void
828list_types(hashtab_T *ht, list_T *l)
829{
830 long todo;
831 hashitem_T *hi;
832
833 todo = (long)ht->ht_used;
834 for (hi = ht->ht_array; todo > 0; ++hi)
835 {
836 if (!HASHITEM_EMPTY(hi))
837 {
838 proptype_T *prop = HI2PT(hi);
839
840 list_append_string(l, prop->pt_name, -1);
841 --todo;
842 }
843 }
844}
845
846/*
847 * prop_type_list([{bufnr}])
848 */
849 void
850f_prop_type_list(typval_T *argvars, typval_T *rettv UNUSED)
851{
852 buf_T *buf = NULL;
853
854 if (rettv_list_alloc(rettv) == OK)
855 {
856 if (argvars[0].v_type != VAR_UNKNOWN)
857 {
858 if (get_bufnr_from_arg(&argvars[0], &buf) == FAIL)
859 return;
860 }
861 if (buf == NULL)
862 {
863 if (global_proptypes != NULL)
864 list_types(global_proptypes, rettv->vval.v_list);
865 }
866 else if (buf->b_proptypes != NULL)
867 list_types(buf->b_proptypes, rettv->vval.v_list);
868 }
869}
870
871/*
872 * Free all property types in "ht".
873 */
874 static void
875clear_ht_prop_types(hashtab_T *ht)
876{
877 long todo;
878 hashitem_T *hi;
879
880 if (ht == NULL)
881 return;
882
883 todo = (long)ht->ht_used;
884 for (hi = ht->ht_array; todo > 0; ++hi)
885 {
886 if (!HASHITEM_EMPTY(hi))
887 {
888 proptype_T *prop = HI2PT(hi);
889
890 vim_free(prop);
891 --todo;
892 }
893 }
894
895 hash_clear(ht);
896 vim_free(ht);
897}
898
899#if defined(EXITFREE) || defined(PROTO)
900/*
Bram Moolenaarfb95e212018-12-14 12:18:11 +0100901 * Free all global property types.
Bram Moolenaar98aefe72018-12-13 22:20:09 +0100902 */
903 void
904clear_global_prop_types(void)
905{
906 clear_ht_prop_types(global_proptypes);
907 global_proptypes = NULL;
908}
909#endif
910
911/*
912 * Free all property types for "buf".
913 */
914 void
915clear_buf_prop_types(buf_T *buf)
916{
917 clear_ht_prop_types(buf->b_proptypes);
918 buf->b_proptypes = NULL;
919}
920
921#endif // FEAT_TEXT_PROP