blob: 53b43e164feae2aaa718b47e21d0dff27ecd990f [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 Moolenaarb9c67a52019-01-01 19:49:20 +010020 * - Adjust text property column and length when text is inserted/deleted.
21 * - Perhaps we only need TP_FLAG_CONT_NEXT and can drop TP_FLAG_CONT_PREV?
Bram Moolenaarb56ac042018-12-28 23:22:40 +010022 * - Add an arrray for global_proptypes, to quickly lookup a prop type by ID
23 * - Add an arrray for b_proptypes, to quickly lookup a prop type by ID
24 * - Checking the text length to detect text properties is slow. Use a flag in
25 * the index, like DB_MARKED?
Bram Moolenaarb413d2e2018-12-25 23:15:46 +010026 * - Also test line2byte() with many lines, so that ml_updatechunk() is taken
27 * into account.
Bram Moolenaar98aefe72018-12-13 22:20:09 +010028 * - add mechanism to keep track of changed lines.
29 */
30
31#include "vim.h"
32
33#if defined(FEAT_TEXT_PROP) || defined(PROTO)
34
35/*
36 * In a hashtable item "hi_key" points to "pt_name" in a proptype_T.
37 * This avoids adding a pointer to the hashtable item.
38 * PT2HIKEY() converts a proptype pointer to a hashitem key pointer.
39 * HIKEY2PT() converts a hashitem key pointer to a proptype pointer.
40 * HI2PT() converts a hashitem pointer to a proptype pointer.
41 */
42#define PT2HIKEY(p) ((p)->pt_name)
43#define HIKEY2PT(p) ((proptype_T *)((p) - offsetof(proptype_T, pt_name)))
44#define HI2PT(hi) HIKEY2PT((hi)->hi_key)
45
46// The global text property types.
47static hashtab_T *global_proptypes = NULL;
48
49// The last used text property type ID.
50static int proptype_id = 0;
51
52static char_u e_type_not_exist[] = N_("E971: Property type %s does not exist");
53static char_u e_invalid_col[] = N_("E964: Invalid column number: %ld");
Bram Moolenaare3d31b02018-12-24 23:07:04 +010054static char_u e_invalid_lnum[] = N_("E966: Invalid line number: %ld");
Bram Moolenaar98aefe72018-12-13 22:20:09 +010055
56/*
57 * Find a property type by name, return the hashitem.
58 * Returns NULL if the item can't be found.
59 */
60 static hashitem_T *
61find_prop_hi(char_u *name, buf_T *buf)
62{
63 hashtab_T *ht;
64 hashitem_T *hi;
65
66 if (*name == NUL)
67 return NULL;
68 if (buf == NULL)
69 ht = global_proptypes;
70 else
71 ht = buf->b_proptypes;
72
73 if (ht == NULL)
74 return NULL;
75 hi = hash_find(ht, name);
76 if (HASHITEM_EMPTY(hi))
77 return NULL;
78 return hi;
79}
80
81/*
82 * Like find_prop_hi() but return the property type.
83 */
84 static proptype_T *
85find_prop(char_u *name, buf_T *buf)
86{
87 hashitem_T *hi = find_prop_hi(name, buf);
88
89 if (hi == NULL)
90 return NULL;
91 return HI2PT(hi);
92}
93
94/*
95 * Lookup a property type by name. First in "buf" and when not found in the
96 * global types.
97 * When not found gives an error message and returns NULL.
98 */
99 static proptype_T *
100lookup_prop_type(char_u *name, buf_T *buf)
101{
102 proptype_T *type = find_prop(name, buf);
103
104 if (type == NULL)
105 type = find_prop(name, NULL);
106 if (type == NULL)
107 EMSG2(_(e_type_not_exist), name);
108 return type;
109}
110
111/*
112 * Get an optional "bufnr" item from the dict in "arg".
113 * When the argument is not used or "bufnr" is not present then "buf" is
114 * unchanged.
115 * If "bufnr" is valid or not present return OK.
116 * When "arg" is not a dict or "bufnr" is invalide return FAIL.
117 */
118 static int
119get_bufnr_from_arg(typval_T *arg, buf_T **buf)
120{
121 dictitem_T *di;
122
123 if (arg->v_type != VAR_DICT)
124 {
125 EMSG(_(e_dictreq));
126 return FAIL;
127 }
128 if (arg->vval.v_dict == NULL)
129 return OK; // NULL dict is like an empty dict
130 di = dict_find(arg->vval.v_dict, (char_u *)"bufnr", -1);
131 if (di != NULL)
132 {
Bram Moolenaarf2d79fa2019-01-03 22:19:27 +0100133 *buf = tv_get_buf(&di->di_tv, FALSE);
Bram Moolenaar98aefe72018-12-13 22:20:09 +0100134 if (*buf == NULL)
135 return FAIL;
136 }
137 return OK;
138}
139
140/*
141 * prop_add({lnum}, {col}, {props})
142 */
143 void
144f_prop_add(typval_T *argvars, typval_T *rettv UNUSED)
145{
146 linenr_T lnum;
Bram Moolenaare3d31b02018-12-24 23:07:04 +0100147 linenr_T start_lnum;
148 linenr_T end_lnum;
149 colnr_T start_col;
150 colnr_T end_col;
Bram Moolenaar98aefe72018-12-13 22:20:09 +0100151 dict_T *dict;
Bram Moolenaar98aefe72018-12-13 22:20:09 +0100152 char_u *type_name;
153 proptype_T *type;
154 buf_T *buf = curbuf;
155 int id = 0;
156 char_u *newtext;
157 int proplen;
158 size_t textlen;
159 char_u *props;
160 char_u *newprops;
Bram Moolenaarfb95e212018-12-14 12:18:11 +0100161 textprop_T tmp_prop;
Bram Moolenaar98aefe72018-12-13 22:20:09 +0100162 int i;
163
Bram Moolenaare3d31b02018-12-24 23:07:04 +0100164 start_lnum = tv_get_number(&argvars[0]);
165 start_col = tv_get_number(&argvars[1]);
166 if (start_col < 1)
Bram Moolenaar98aefe72018-12-13 22:20:09 +0100167 {
Bram Moolenaare3d31b02018-12-24 23:07:04 +0100168 EMSGN(_(e_invalid_col), (long)start_col);
Bram Moolenaar98aefe72018-12-13 22:20:09 +0100169 return;
170 }
171 if (argvars[2].v_type != VAR_DICT)
172 {
173 EMSG(_(e_dictreq));
174 return;
175 }
176 dict = argvars[2].vval.v_dict;
177
178 if (dict == NULL || dict_find(dict, (char_u *)"type", -1) == NULL)
179 {
180 EMSG(_("E965: missing property type name"));
181 return;
182 }
Bram Moolenaar8f667172018-12-14 15:38:31 +0100183 type_name = dict_get_string(dict, (char_u *)"type", FALSE);
Bram Moolenaar98aefe72018-12-13 22:20:09 +0100184
185 if (dict_find(dict, (char_u *)"end_lnum", -1) != NULL)
186 {
Bram Moolenaare3d31b02018-12-24 23:07:04 +0100187 end_lnum = dict_get_number(dict, (char_u *)"end_lnum");
188 if (end_lnum < start_lnum)
189 {
190 EMSG2(_(e_invargval), "end_lnum");
191 return;
192 }
Bram Moolenaar98aefe72018-12-13 22:20:09 +0100193 }
Bram Moolenaare3d31b02018-12-24 23:07:04 +0100194 else
195 end_lnum = start_lnum;
Bram Moolenaar98aefe72018-12-13 22:20:09 +0100196
197 if (dict_find(dict, (char_u *)"length", -1) != NULL)
Bram Moolenaare3d31b02018-12-24 23:07:04 +0100198 {
199 long length = dict_get_number(dict, (char_u *)"length");
200
Bram Moolenaarb9c67a52019-01-01 19:49:20 +0100201 if (length < 0 || end_lnum > start_lnum)
Bram Moolenaare3d31b02018-12-24 23:07:04 +0100202 {
203 EMSG2(_(e_invargval), "length");
204 return;
205 }
Bram Moolenaarb9c67a52019-01-01 19:49:20 +0100206 end_col = start_col + length;
Bram Moolenaare3d31b02018-12-24 23:07:04 +0100207 }
Bram Moolenaar98aefe72018-12-13 22:20:09 +0100208 else if (dict_find(dict, (char_u *)"end_col", -1) != NULL)
209 {
Bram Moolenaare3d31b02018-12-24 23:07:04 +0100210 end_col = dict_get_number(dict, (char_u *)"end_col");
211 if (end_col <= 0)
Bram Moolenaar98aefe72018-12-13 22:20:09 +0100212 {
213 EMSG2(_(e_invargval), "end_col");
214 return;
215 }
216 }
Bram Moolenaare3d31b02018-12-24 23:07:04 +0100217 else if (start_lnum == end_lnum)
218 end_col = start_col;
219 else
220 end_col = 1;
Bram Moolenaar98aefe72018-12-13 22:20:09 +0100221
222 if (dict_find(dict, (char_u *)"id", -1) != NULL)
Bram Moolenaar8f667172018-12-14 15:38:31 +0100223 id = dict_get_number(dict, (char_u *)"id");
Bram Moolenaar98aefe72018-12-13 22:20:09 +0100224
225 if (get_bufnr_from_arg(&argvars[2], &buf) == FAIL)
226 return;
227
228 type = lookup_prop_type(type_name, buf);
229 if (type == NULL)
230 return;
231
Bram Moolenaare3d31b02018-12-24 23:07:04 +0100232 if (start_lnum < 1 || start_lnum > buf->b_ml.ml_line_count)
Bram Moolenaar98aefe72018-12-13 22:20:09 +0100233 {
Bram Moolenaare3d31b02018-12-24 23:07:04 +0100234 EMSGN(_(e_invalid_lnum), (long)start_lnum);
235 return;
236 }
237 if (end_lnum < start_lnum || end_lnum > buf->b_ml.ml_line_count)
238 {
239 EMSGN(_(e_invalid_lnum), (long)end_lnum);
Bram Moolenaar98aefe72018-12-13 22:20:09 +0100240 return;
241 }
242
Bram Moolenaare3d31b02018-12-24 23:07:04 +0100243 for (lnum = start_lnum; lnum <= end_lnum; ++lnum)
Bram Moolenaar98aefe72018-12-13 22:20:09 +0100244 {
Bram Moolenaare3d31b02018-12-24 23:07:04 +0100245 colnr_T col; // start column
246 long length; // in bytes
247
248 // Fetch the line to get the ml_line_len field updated.
249 proplen = get_text_props(buf, lnum, &props, TRUE);
250 textlen = buf->b_ml.ml_line_len - proplen * sizeof(textprop_T);
251
252 if (lnum == start_lnum)
253 col = start_col;
254 else
255 col = 1;
256 if (col - 1 > (colnr_T)textlen)
257 {
258 EMSGN(_(e_invalid_col), (long)start_col);
259 return;
260 }
261
262 if (lnum == end_lnum)
Bram Moolenaarb9c67a52019-01-01 19:49:20 +0100263 length = end_col - col;
Bram Moolenaare3d31b02018-12-24 23:07:04 +0100264 else
Bram Moolenaar4b7214e2019-01-03 21:55:32 +0100265 length = (int)textlen - col + 1;
Bram Moolenaarb413d2e2018-12-25 23:15:46 +0100266 if (length > (long)textlen)
Bram Moolenaar4b7214e2019-01-03 21:55:32 +0100267 length = (int)textlen; // can include the end-of-line
Bram Moolenaarb9c67a52019-01-01 19:49:20 +0100268 if (length < 0)
269 length = 0; // zero-width property
Bram Moolenaare3d31b02018-12-24 23:07:04 +0100270
271 // Allocate the new line with space for the new proprety.
272 newtext = alloc(buf->b_ml.ml_line_len + sizeof(textprop_T));
273 if (newtext == NULL)
274 return;
275 // Copy the text, including terminating NUL.
276 mch_memmove(newtext, buf->b_ml.ml_line_ptr, textlen);
277
278 // Find the index where to insert the new property.
279 // Since the text properties are not aligned properly when stored with the
280 // text, we need to copy them as bytes before using it as a struct.
281 for (i = 0; i < proplen; ++i)
282 {
283 mch_memmove(&tmp_prop, props + i * sizeof(textprop_T),
284 sizeof(textprop_T));
285 if (tmp_prop.tp_col >= col)
286 break;
287 }
288 newprops = newtext + textlen;
289 if (i > 0)
290 mch_memmove(newprops, props, sizeof(textprop_T) * i);
291
292 tmp_prop.tp_col = col;
293 tmp_prop.tp_len = length;
294 tmp_prop.tp_id = id;
295 tmp_prop.tp_type = type->pt_id;
296 tmp_prop.tp_flags = (lnum > start_lnum ? TP_FLAG_CONT_PREV : 0)
297 | (lnum < end_lnum ? TP_FLAG_CONT_NEXT : 0);
298 mch_memmove(newprops + i * sizeof(textprop_T), &tmp_prop,
299 sizeof(textprop_T));
300
301 if (i < proplen)
302 mch_memmove(newprops + (i + 1) * sizeof(textprop_T),
303 props + i * sizeof(textprop_T),
304 sizeof(textprop_T) * (proplen - i));
305
306 if (buf->b_ml.ml_flags & ML_LINE_DIRTY)
307 vim_free(buf->b_ml.ml_line_ptr);
308 buf->b_ml.ml_line_ptr = newtext;
309 buf->b_ml.ml_line_len += sizeof(textprop_T);
310 buf->b_ml.ml_flags |= ML_LINE_DIRTY;
Bram Moolenaar98aefe72018-12-13 22:20:09 +0100311 }
312
Bram Moolenaarb413d2e2018-12-25 23:15:46 +0100313 buf->b_has_textprop = TRUE; // this is never reset
Bram Moolenaar98aefe72018-12-13 22:20:09 +0100314 redraw_buf_later(buf, NOT_VALID);
315}
316
317/*
Bram Moolenaarfb95e212018-12-14 12:18:11 +0100318 * Fetch the text properties for line "lnum" in buffer "buf".
Bram Moolenaar98aefe72018-12-13 22:20:09 +0100319 * Returns the number of text properties and, when non-zero, a pointer to the
320 * first one in "props" (note that it is not aligned, therefore the char_u
321 * pointer).
322 */
323 int
324get_text_props(buf_T *buf, linenr_T lnum, char_u **props, int will_change)
325{
326 char_u *text;
327 size_t textlen;
328 size_t proplen;
329
Bram Moolenaarb413d2e2018-12-25 23:15:46 +0100330 // Be quick when no text property types have been defined or the buffer,
331 // unless we are adding one.
332 if (!buf->b_has_textprop && !will_change)
Bram Moolenaar98aefe72018-12-13 22:20:09 +0100333 return 0;
334
335 // Fetch the line to get the ml_line_len field updated.
336 text = ml_get_buf(buf, lnum, will_change);
337 textlen = STRLEN(text) + 1;
338 proplen = buf->b_ml.ml_line_len - textlen;
339 if (proplen % sizeof(textprop_T) != 0)
340 {
341 IEMSG(_("E967: text property info corrupted"));
342 return 0;
343 }
344 if (proplen > 0)
345 *props = text + textlen;
Bram Moolenaar4efe73b2018-12-16 14:37:39 +0100346 return (int)(proplen / sizeof(textprop_T));
Bram Moolenaar98aefe72018-12-13 22:20:09 +0100347}
348
349 static proptype_T *
350find_type_by_id(hashtab_T *ht, int id)
351{
352 long todo;
353 hashitem_T *hi;
354
355 if (ht == NULL)
356 return NULL;
357
358 // TODO: Make this faster by keeping a list of types sorted on ID and use
359 // a binary search.
360
361 todo = (long)ht->ht_used;
362 for (hi = ht->ht_array; todo > 0; ++hi)
363 {
364 if (!HASHITEM_EMPTY(hi))
365 {
366 proptype_T *prop = HI2PT(hi);
367
368 if (prop->pt_id == id)
369 return prop;
370 --todo;
371 }
372 }
373 return NULL;
374}
375
376/*
377 * Find a property type by ID in "buf" or globally.
378 * Returns NULL if not found.
379 */
380 proptype_T *
381text_prop_type_by_id(buf_T *buf, int id)
382{
383 proptype_T *type;
384
385 type = find_type_by_id(buf->b_proptypes, id);
386 if (type == NULL)
387 type = find_type_by_id(global_proptypes, id);
388 return type;
389}
390
391/*
392 * prop_clear({lnum} [, {lnum_end} [, {bufnr}]])
393 */
394 void
395f_prop_clear(typval_T *argvars, typval_T *rettv UNUSED)
396{
Bram Moolenaard155d7a2018-12-21 16:04:21 +0100397 linenr_T start = tv_get_number(&argvars[0]);
Bram Moolenaar98aefe72018-12-13 22:20:09 +0100398 linenr_T end = start;
399 linenr_T lnum;
400 buf_T *buf = curbuf;
401
402 if (argvars[1].v_type != VAR_UNKNOWN)
403 {
Bram Moolenaard155d7a2018-12-21 16:04:21 +0100404 end = tv_get_number(&argvars[1]);
Bram Moolenaar98aefe72018-12-13 22:20:09 +0100405 if (argvars[2].v_type != VAR_UNKNOWN)
406 {
407 if (get_bufnr_from_arg(&argvars[2], &buf) == FAIL)
408 return;
409 }
410 }
411 if (start < 1 || end < 1)
412 {
413 EMSG(_(e_invrange));
414 return;
415 }
416
417 for (lnum = start; lnum <= end; ++lnum)
418 {
419 char_u *text;
420 size_t len;
421
422 if (lnum > buf->b_ml.ml_line_count)
423 break;
424 text = ml_get_buf(buf, lnum, FALSE);
425 len = STRLEN(text) + 1;
426 if ((size_t)buf->b_ml.ml_line_len > len)
427 {
428 if (!(buf->b_ml.ml_flags & ML_LINE_DIRTY))
429 {
430 char_u *newtext = vim_strsave(text);
431
432 // need to allocate the line now
433 if (newtext == NULL)
434 return;
435 buf->b_ml.ml_line_ptr = newtext;
436 buf->b_ml.ml_flags |= ML_LINE_DIRTY;
437 }
Bram Moolenaar4efe73b2018-12-16 14:37:39 +0100438 buf->b_ml.ml_line_len = (int)len;
Bram Moolenaar98aefe72018-12-13 22:20:09 +0100439 }
440 }
441 redraw_buf_later(buf, NOT_VALID);
442}
443
444/*
445 * prop_list({lnum} [, {bufnr}])
446 */
447 void
448f_prop_list(typval_T *argvars, typval_T *rettv)
449{
Bram Moolenaard155d7a2018-12-21 16:04:21 +0100450 linenr_T lnum = tv_get_number(&argvars[0]);
Bram Moolenaar98aefe72018-12-13 22:20:09 +0100451 buf_T *buf = curbuf;
452
453 if (argvars[1].v_type != VAR_UNKNOWN)
454 {
455 if (get_bufnr_from_arg(&argvars[1], &buf) == FAIL)
456 return;
457 }
458 if (lnum < 1 || lnum > buf->b_ml.ml_line_count)
459 {
460 EMSG(_(e_invrange));
461 return;
462 }
463
464 if (rettv_list_alloc(rettv) == OK)
465 {
466 char_u *text = ml_get_buf(buf, lnum, FALSE);
467 size_t textlen = STRLEN(text) + 1;
Bram Moolenaar4efe73b2018-12-16 14:37:39 +0100468 int count = (int)((buf->b_ml.ml_line_len - textlen)
469 / sizeof(textprop_T));
Bram Moolenaar98aefe72018-12-13 22:20:09 +0100470 int i;
471 textprop_T prop;
472 proptype_T *pt;
473
474 for (i = 0; i < count; ++i)
475 {
476 dict_T *d = dict_alloc();
477
478 if (d == NULL)
479 break;
480 mch_memmove(&prop, text + textlen + i * sizeof(textprop_T),
481 sizeof(textprop_T));
482 dict_add_number(d, "col", prop.tp_col);
483 dict_add_number(d, "length", prop.tp_len);
484 dict_add_number(d, "id", prop.tp_id);
485 dict_add_number(d, "start", !(prop.tp_flags & TP_FLAG_CONT_PREV));
486 dict_add_number(d, "end", !(prop.tp_flags & TP_FLAG_CONT_NEXT));
487 pt = text_prop_type_by_id(buf, prop.tp_type);
488 if (pt != NULL)
489 dict_add_string(d, "type", pt->pt_name);
490
491 list_append_dict(rettv->vval.v_list, d);
492 }
493 }
494}
495
496/*
497 * prop_remove({props} [, {lnum} [, {lnum_end}]])
498 */
499 void
500f_prop_remove(typval_T *argvars, typval_T *rettv)
501{
502 linenr_T start = 1;
503 linenr_T end = 0;
504 linenr_T lnum;
505 dict_T *dict;
506 buf_T *buf = curbuf;
507 dictitem_T *di;
508 int do_all = FALSE;
509 int id = -1;
510 int type_id = -1;
511
512 rettv->vval.v_number = 0;
513 if (argvars[0].v_type != VAR_DICT || argvars[0].vval.v_dict == NULL)
514 {
515 EMSG(_(e_invarg));
516 return;
517 }
518
519 if (argvars[1].v_type != VAR_UNKNOWN)
520 {
Bram Moolenaard155d7a2018-12-21 16:04:21 +0100521 start = tv_get_number(&argvars[1]);
Bram Moolenaar98aefe72018-12-13 22:20:09 +0100522 end = start;
523 if (argvars[2].v_type != VAR_UNKNOWN)
Bram Moolenaard155d7a2018-12-21 16:04:21 +0100524 end = tv_get_number(&argvars[2]);
Bram Moolenaar98aefe72018-12-13 22:20:09 +0100525 if (start < 1 || end < 1)
526 {
527 EMSG(_(e_invrange));
528 return;
529 }
530 }
531
532 dict = argvars[0].vval.v_dict;
533 di = dict_find(dict, (char_u *)"bufnr", -1);
534 if (di != NULL)
535 {
Bram Moolenaarf2d79fa2019-01-03 22:19:27 +0100536 buf = tv_get_buf(&di->di_tv, FALSE);
Bram Moolenaar98aefe72018-12-13 22:20:09 +0100537 if (buf == NULL)
538 return;
539 }
540
541 di = dict_find(dict, (char_u*)"all", -1);
542 if (di != NULL)
Bram Moolenaar8f667172018-12-14 15:38:31 +0100543 do_all = dict_get_number(dict, (char_u *)"all");
Bram Moolenaar98aefe72018-12-13 22:20:09 +0100544
545 if (dict_find(dict, (char_u *)"id", -1) != NULL)
Bram Moolenaar8f667172018-12-14 15:38:31 +0100546 id = dict_get_number(dict, (char_u *)"id");
Bram Moolenaar98aefe72018-12-13 22:20:09 +0100547 if (dict_find(dict, (char_u *)"type", -1))
548 {
Bram Moolenaar8f667172018-12-14 15:38:31 +0100549 char_u *name = dict_get_string(dict, (char_u *)"type", FALSE);
Bram Moolenaar98aefe72018-12-13 22:20:09 +0100550 proptype_T *type = lookup_prop_type(name, buf);
551
552 if (type == NULL)
553 return;
554 type_id = type->pt_id;
555 }
556 if (id == -1 && type_id == -1)
557 {
558 EMSG(_("E968: Need at least one of 'id' or 'type'"));
559 return;
560 }
561
562 if (end == 0)
563 end = buf->b_ml.ml_line_count;
564 for (lnum = start; lnum <= end; ++lnum)
565 {
566 char_u *text;
567 size_t len;
568
569 if (lnum > buf->b_ml.ml_line_count)
570 break;
571 text = ml_get_buf(buf, lnum, FALSE);
572 len = STRLEN(text) + 1;
573 if ((size_t)buf->b_ml.ml_line_len > len)
574 {
575 static textprop_T textprop; // static because of alignment
576 unsigned idx;
577
578 for (idx = 0; idx < (buf->b_ml.ml_line_len - len)
579 / sizeof(textprop_T); ++idx)
580 {
581 char_u *cur_prop = buf->b_ml.ml_line_ptr + len
582 + idx * sizeof(textprop_T);
583 size_t taillen;
584
585 mch_memmove(&textprop, cur_prop, sizeof(textprop_T));
586 if (textprop.tp_id == id || textprop.tp_type == type_id)
587 {
588 if (!(buf->b_ml.ml_flags & ML_LINE_DIRTY))
589 {
590 char_u *newptr = alloc(buf->b_ml.ml_line_len);
591
592 // need to allocate the line to be able to change it
593 if (newptr == NULL)
594 return;
595 mch_memmove(newptr, buf->b_ml.ml_line_ptr,
596 buf->b_ml.ml_line_len);
597 buf->b_ml.ml_line_ptr = newptr;
598 curbuf->b_ml.ml_flags |= ML_LINE_DIRTY;
599 }
600
601 taillen = buf->b_ml.ml_line_len - len
602 - (idx + 1) * sizeof(textprop_T);
603 if (taillen > 0)
604 mch_memmove(cur_prop, cur_prop + sizeof(textprop_T),
605 taillen);
606 buf->b_ml.ml_line_len -= sizeof(textprop_T);
607 --idx;
608
609 ++rettv->vval.v_number;
610 if (!do_all)
611 break;
612 }
613 }
614 }
615 }
616 redraw_buf_later(buf, NOT_VALID);
617}
618
619/*
620 * Common for f_prop_type_add() and f_prop_type_change().
621 */
622 void
623prop_type_set(typval_T *argvars, int add)
624{
625 char_u *name;
626 buf_T *buf = NULL;
627 dict_T *dict;
628 dictitem_T *di;
629 proptype_T *prop;
630
Bram Moolenaard155d7a2018-12-21 16:04:21 +0100631 name = tv_get_string(&argvars[0]);
Bram Moolenaar98aefe72018-12-13 22:20:09 +0100632 if (*name == NUL)
633 {
634 EMSG(_(e_invarg));
635 return;
636 }
637
638 if (get_bufnr_from_arg(&argvars[1], &buf) == FAIL)
639 return;
640 dict = argvars[1].vval.v_dict;
641
642 prop = find_prop(name, buf);
643 if (add)
644 {
645 hashtab_T **htp;
646
647 if (prop != NULL)
648 {
649 EMSG2(_("E969: Property type %s already defined"), name);
650 return;
651 }
Bram Moolenaar4efe73b2018-12-16 14:37:39 +0100652 prop = (proptype_T *)alloc_clear((int)(sizeof(proptype_T) + STRLEN(name)));
Bram Moolenaar98aefe72018-12-13 22:20:09 +0100653 if (prop == NULL)
654 return;
655 STRCPY(prop->pt_name, name);
656 prop->pt_id = ++proptype_id;
657 htp = buf == NULL ? &global_proptypes : &buf->b_proptypes;
658 if (*htp == NULL)
659 {
660 *htp = (hashtab_T *)alloc(sizeof(hashtab_T));
661 if (*htp == NULL)
Bram Moolenaarfb95e212018-12-14 12:18:11 +0100662 {
663 vim_free(prop);
Bram Moolenaar98aefe72018-12-13 22:20:09 +0100664 return;
Bram Moolenaarfb95e212018-12-14 12:18:11 +0100665 }
Bram Moolenaar98aefe72018-12-13 22:20:09 +0100666 hash_init(*htp);
667 }
Bram Moolenaarfb95e212018-12-14 12:18:11 +0100668 hash_add(*htp, PT2HIKEY(prop));
Bram Moolenaar98aefe72018-12-13 22:20:09 +0100669 }
670 else
671 {
672 if (prop == NULL)
673 {
674 EMSG2(_(e_type_not_exist), name);
675 return;
676 }
677 }
678
679 if (dict != NULL)
680 {
681 di = dict_find(dict, (char_u *)"highlight", -1);
682 if (di != NULL)
683 {
684 char_u *highlight;
685 int hl_id = 0;
686
Bram Moolenaar8f667172018-12-14 15:38:31 +0100687 highlight = dict_get_string(dict, (char_u *)"highlight", FALSE);
Bram Moolenaar98aefe72018-12-13 22:20:09 +0100688 if (highlight != NULL && *highlight != NUL)
689 hl_id = syn_name2id(highlight);
690 if (hl_id <= 0)
691 {
692 EMSG2(_("E970: Unknown highlight group name: '%s'"),
693 highlight == NULL ? (char_u *)"" : highlight);
694 return;
695 }
696 prop->pt_hl_id = hl_id;
697 }
698
699 di = dict_find(dict, (char_u *)"priority", -1);
700 if (di != NULL)
Bram Moolenaard155d7a2018-12-21 16:04:21 +0100701 prop->pt_priority = tv_get_number(&di->di_tv);
Bram Moolenaar98aefe72018-12-13 22:20:09 +0100702
703 di = dict_find(dict, (char_u *)"start_incl", -1);
704 if (di != NULL)
705 {
Bram Moolenaard155d7a2018-12-21 16:04:21 +0100706 if (tv_get_number(&di->di_tv))
Bram Moolenaar98aefe72018-12-13 22:20:09 +0100707 prop->pt_flags |= PT_FLAG_INS_START_INCL;
708 else
709 prop->pt_flags &= ~PT_FLAG_INS_START_INCL;
710 }
711
712 di = dict_find(dict, (char_u *)"end_incl", -1);
713 if (di != NULL)
714 {
Bram Moolenaard155d7a2018-12-21 16:04:21 +0100715 if (tv_get_number(&di->di_tv))
Bram Moolenaar98aefe72018-12-13 22:20:09 +0100716 prop->pt_flags |= PT_FLAG_INS_END_INCL;
717 else
718 prop->pt_flags &= ~PT_FLAG_INS_END_INCL;
719 }
720 }
721}
722
723/*
724 * prop_type_add({name}, {props})
725 */
726 void
727f_prop_type_add(typval_T *argvars, typval_T *rettv UNUSED)
728{
729 prop_type_set(argvars, TRUE);
730}
731
732/*
733 * prop_type_change({name}, {props})
734 */
735 void
736f_prop_type_change(typval_T *argvars, typval_T *rettv UNUSED)
737{
738 prop_type_set(argvars, FALSE);
739}
740
741/*
742 * prop_type_delete({name} [, {bufnr}])
743 */
744 void
745f_prop_type_delete(typval_T *argvars, typval_T *rettv UNUSED)
746{
747 char_u *name;
748 buf_T *buf = NULL;
749 hashitem_T *hi;
750
Bram Moolenaard155d7a2018-12-21 16:04:21 +0100751 name = tv_get_string(&argvars[0]);
Bram Moolenaar98aefe72018-12-13 22:20:09 +0100752 if (*name == NUL)
753 {
754 EMSG(_(e_invarg));
755 return;
756 }
757
758 if (argvars[1].v_type != VAR_UNKNOWN)
759 {
760 if (get_bufnr_from_arg(&argvars[1], &buf) == FAIL)
761 return;
762 }
763
764 hi = find_prop_hi(name, buf);
765 if (hi != NULL)
766 {
767 hashtab_T *ht;
Bram Moolenaarfb95e212018-12-14 12:18:11 +0100768 proptype_T *prop = HI2PT(hi);
Bram Moolenaar98aefe72018-12-13 22:20:09 +0100769
770 if (buf == NULL)
771 ht = global_proptypes;
772 else
773 ht = buf->b_proptypes;
774 hash_remove(ht, hi);
Bram Moolenaarfb95e212018-12-14 12:18:11 +0100775 vim_free(prop);
Bram Moolenaar98aefe72018-12-13 22:20:09 +0100776 }
777}
778
779/*
780 * prop_type_get({name} [, {bufnr}])
781 */
782 void
783f_prop_type_get(typval_T *argvars, typval_T *rettv UNUSED)
784{
Bram Moolenaard155d7a2018-12-21 16:04:21 +0100785 char_u *name = tv_get_string(&argvars[0]);
Bram Moolenaar98aefe72018-12-13 22:20:09 +0100786
787 if (*name == NUL)
788 {
789 EMSG(_(e_invarg));
790 return;
791 }
792 if (rettv_dict_alloc(rettv) == OK)
793 {
794 proptype_T *prop = NULL;
795 buf_T *buf = NULL;
796
797 if (argvars[1].v_type != VAR_UNKNOWN)
798 {
799 if (get_bufnr_from_arg(&argvars[1], &buf) == FAIL)
800 return;
801 }
802
803 prop = find_prop(name, buf);
804 if (prop != NULL)
805 {
806 dict_T *d = rettv->vval.v_dict;
807
808 if (prop->pt_hl_id > 0)
809 dict_add_string(d, "highlight", syn_id2name(prop->pt_hl_id));
810 dict_add_number(d, "priority", prop->pt_priority);
811 dict_add_number(d, "start_incl",
812 (prop->pt_flags & PT_FLAG_INS_START_INCL) ? 1 : 0);
813 dict_add_number(d, "end_incl",
814 (prop->pt_flags & PT_FLAG_INS_END_INCL) ? 1 : 0);
815 if (buf != NULL)
816 dict_add_number(d, "bufnr", buf->b_fnum);
817 }
818 }
819}
820
821 static void
822list_types(hashtab_T *ht, list_T *l)
823{
824 long todo;
825 hashitem_T *hi;
826
827 todo = (long)ht->ht_used;
828 for (hi = ht->ht_array; todo > 0; ++hi)
829 {
830 if (!HASHITEM_EMPTY(hi))
831 {
832 proptype_T *prop = HI2PT(hi);
833
834 list_append_string(l, prop->pt_name, -1);
835 --todo;
836 }
837 }
838}
839
840/*
841 * prop_type_list([{bufnr}])
842 */
843 void
844f_prop_type_list(typval_T *argvars, typval_T *rettv UNUSED)
845{
846 buf_T *buf = NULL;
847
848 if (rettv_list_alloc(rettv) == OK)
849 {
850 if (argvars[0].v_type != VAR_UNKNOWN)
851 {
852 if (get_bufnr_from_arg(&argvars[0], &buf) == FAIL)
853 return;
854 }
855 if (buf == NULL)
856 {
857 if (global_proptypes != NULL)
858 list_types(global_proptypes, rettv->vval.v_list);
859 }
860 else if (buf->b_proptypes != NULL)
861 list_types(buf->b_proptypes, rettv->vval.v_list);
862 }
863}
864
865/*
866 * Free all property types in "ht".
867 */
868 static void
869clear_ht_prop_types(hashtab_T *ht)
870{
871 long todo;
872 hashitem_T *hi;
873
874 if (ht == NULL)
875 return;
876
877 todo = (long)ht->ht_used;
878 for (hi = ht->ht_array; todo > 0; ++hi)
879 {
880 if (!HASHITEM_EMPTY(hi))
881 {
882 proptype_T *prop = HI2PT(hi);
883
884 vim_free(prop);
885 --todo;
886 }
887 }
888
889 hash_clear(ht);
890 vim_free(ht);
891}
892
893#if defined(EXITFREE) || defined(PROTO)
894/*
Bram Moolenaarfb95e212018-12-14 12:18:11 +0100895 * Free all global property types.
Bram Moolenaar98aefe72018-12-13 22:20:09 +0100896 */
897 void
898clear_global_prop_types(void)
899{
900 clear_ht_prop_types(global_proptypes);
901 global_proptypes = NULL;
902}
903#endif
904
905/*
906 * Free all property types for "buf".
907 */
908 void
909clear_buf_prop_types(buf_T *buf)
910{
911 clear_ht_prop_types(buf->b_proptypes);
912 buf->b_proptypes = NULL;
913}
914
Bram Moolenaarb9c67a52019-01-01 19:49:20 +0100915/*
916 * Adjust the columns of text properties in line "lnum" after position "col" to
917 * shift by "bytes_added" (can be negative).
Bram Moolenaar44746aa2019-01-02 00:02:11 +0100918 * Note that "col" is zero-based, while tp_col is one-based.
919 * Only for the current buffer.
920 * Called is expected to check b_has_textprop and "bytes_added" being non-zero.
Bram Moolenaarb9c67a52019-01-01 19:49:20 +0100921 */
922 void
Bram Moolenaar196d1572019-01-02 23:47:18 +0100923adjust_prop_columns(
924 linenr_T lnum,
925 colnr_T col,
926 int bytes_added)
Bram Moolenaarb9c67a52019-01-01 19:49:20 +0100927{
Bram Moolenaar44746aa2019-01-02 00:02:11 +0100928 int proplen;
929 char_u *props;
930 textprop_T tmp_prop;
931 proptype_T *pt;
932 int dirty = FALSE;
Bram Moolenaar196d1572019-01-02 23:47:18 +0100933 int ri, wi;
934 size_t textlen;
935
936 if (text_prop_frozen > 0)
937 return;
Bram Moolenaar44746aa2019-01-02 00:02:11 +0100938
939 proplen = get_text_props(curbuf, lnum, &props, TRUE);
940 if (proplen == 0)
941 return;
Bram Moolenaar196d1572019-01-02 23:47:18 +0100942 textlen = curbuf->b_ml.ml_line_len - proplen * sizeof(textprop_T);
Bram Moolenaar44746aa2019-01-02 00:02:11 +0100943
Bram Moolenaar196d1572019-01-02 23:47:18 +0100944 wi = 0; // write index
945 for (ri = 0; ri < proplen; ++ri)
Bram Moolenaar44746aa2019-01-02 00:02:11 +0100946 {
Bram Moolenaar196d1572019-01-02 23:47:18 +0100947 mch_memmove(&tmp_prop, props + ri * sizeof(textprop_T),
Bram Moolenaar44746aa2019-01-02 00:02:11 +0100948 sizeof(textprop_T));
949 pt = text_prop_type_by_id(curbuf, tmp_prop.tp_type);
950
Bram Moolenaar196d1572019-01-02 23:47:18 +0100951 if (bytes_added > 0
952 ? (tmp_prop.tp_col >= col + (pt != NULL && (pt->pt_flags & PT_FLAG_INS_START_INCL) ? 2 : 1))
953 : (tmp_prop.tp_col > col + 1))
Bram Moolenaar44746aa2019-01-02 00:02:11 +0100954 {
955 tmp_prop.tp_col += bytes_added;
956 dirty = TRUE;
957 }
Bram Moolenaar196d1572019-01-02 23:47:18 +0100958 else if (tmp_prop.tp_len > 0
959 && tmp_prop.tp_col + tmp_prop.tp_len > col
960 + ((pt != NULL && (pt->pt_flags & PT_FLAG_INS_END_INCL))
961 ? 0 : 1))
Bram Moolenaar44746aa2019-01-02 00:02:11 +0100962 {
963 tmp_prop.tp_len += bytes_added;
964 dirty = TRUE;
Bram Moolenaar196d1572019-01-02 23:47:18 +0100965 if (tmp_prop.tp_len <= 0)
966 continue; // drop this text property
Bram Moolenaar44746aa2019-01-02 00:02:11 +0100967 }
Bram Moolenaar196d1572019-01-02 23:47:18 +0100968 mch_memmove(props + wi * sizeof(textprop_T), &tmp_prop,
Bram Moolenaar44746aa2019-01-02 00:02:11 +0100969 sizeof(textprop_T));
Bram Moolenaar196d1572019-01-02 23:47:18 +0100970 ++wi;
971 }
972 if (dirty)
973 {
974 curbuf->b_ml.ml_flags |= ML_LINE_DIRTY;
Bram Moolenaar4b7214e2019-01-03 21:55:32 +0100975 curbuf->b_ml.ml_line_len = (int)textlen + wi * sizeof(textprop_T);
Bram Moolenaar44746aa2019-01-02 00:02:11 +0100976 }
Bram Moolenaarb9c67a52019-01-01 19:49:20 +0100977}
978
Bram Moolenaar98aefe72018-12-13 22:20:09 +0100979#endif // FEAT_TEXT_PROP