blob: 8ad795ac0906384a7f1b5051218e845206810a92 [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/*
Bram Moolenaar45dd07f2019-05-15 22:45:37 +020011 * Text properties implementation. See ":help text-properties".
Bram Moolenaar98aefe72018-12-13 22:20:09 +010012 *
13 * TODO:
Bram Moolenaarb9c67a52019-01-01 19:49:20 +010014 * - Adjust text property column and length when text is inserted/deleted.
Bram Moolenaard79eef22019-05-24 20:41:55 +020015 * -> :substitute with multiple matches, issue #4427
Bram Moolenaar4164bb22019-01-04 23:09:49 +010016 * -> a :substitute with a multi-line match
Bram Moolenaarf9e3e092019-01-13 23:38:42 +010017 * -> search for changed_bytes() from misc1.c
Bram Moolenaar80e737c2019-05-17 19:56:34 +020018 * -> search for mark_col_adjust()
Bram Moolenaarb9c67a52019-01-01 19:49:20 +010019 * - Perhaps we only need TP_FLAG_CONT_NEXT and can drop TP_FLAG_CONT_PREV?
Bram Moolenaarb56ac042018-12-28 23:22:40 +010020 * - Add an arrray for global_proptypes, to quickly lookup a prop type by ID
21 * - Add an arrray for b_proptypes, to quickly lookup a prop type by ID
22 * - Checking the text length to detect text properties is slow. Use a flag in
23 * the index, like DB_MARKED?
Bram Moolenaarb413d2e2018-12-25 23:15:46 +010024 * - Also test line2byte() with many lines, so that ml_updatechunk() is taken
25 * into account.
Bram Moolenaarc6663882019-02-22 19:14:54 +010026 * - Perhaps have a window-local option to disable highlighting from text
27 * properties?
Bram Moolenaar98aefe72018-12-13 22:20:09 +010028 */
29
30#include "vim.h"
31
32#if defined(FEAT_TEXT_PROP) || defined(PROTO)
33
34/*
35 * In a hashtable item "hi_key" points to "pt_name" in a proptype_T.
36 * This avoids adding a pointer to the hashtable item.
37 * PT2HIKEY() converts a proptype pointer to a hashitem key pointer.
38 * HIKEY2PT() converts a hashitem key pointer to a proptype pointer.
39 * HI2PT() converts a hashitem pointer to a proptype pointer.
40 */
41#define PT2HIKEY(p) ((p)->pt_name)
42#define HIKEY2PT(p) ((proptype_T *)((p) - offsetof(proptype_T, pt_name)))
43#define HI2PT(hi) HIKEY2PT((hi)->hi_key)
44
45// The global text property types.
46static hashtab_T *global_proptypes = NULL;
47
48// The last used text property type ID.
49static int proptype_id = 0;
50
51static char_u e_type_not_exist[] = N_("E971: Property type %s does not exist");
52static char_u e_invalid_col[] = N_("E964: Invalid column number: %ld");
Bram Moolenaare3d31b02018-12-24 23:07:04 +010053static char_u e_invalid_lnum[] = N_("E966: Invalid line number: %ld");
Bram Moolenaar98aefe72018-12-13 22:20:09 +010054
55/*
56 * Find a property type by name, return the hashitem.
57 * Returns NULL if the item can't be found.
58 */
59 static hashitem_T *
60find_prop_hi(char_u *name, buf_T *buf)
61{
62 hashtab_T *ht;
63 hashitem_T *hi;
64
65 if (*name == NUL)
66 return NULL;
67 if (buf == NULL)
68 ht = global_proptypes;
69 else
70 ht = buf->b_proptypes;
71
72 if (ht == NULL)
73 return NULL;
74 hi = hash_find(ht, name);
75 if (HASHITEM_EMPTY(hi))
76 return NULL;
77 return hi;
78}
79
80/*
81 * Like find_prop_hi() but return the property type.
82 */
83 static proptype_T *
84find_prop(char_u *name, buf_T *buf)
85{
86 hashitem_T *hi = find_prop_hi(name, buf);
87
88 if (hi == NULL)
89 return NULL;
90 return HI2PT(hi);
91}
92
93/*
94 * Lookup a property type by name. First in "buf" and when not found in the
95 * global types.
96 * When not found gives an error message and returns NULL.
97 */
98 static proptype_T *
99lookup_prop_type(char_u *name, buf_T *buf)
100{
101 proptype_T *type = find_prop(name, buf);
102
103 if (type == NULL)
104 type = find_prop(name, NULL);
105 if (type == NULL)
Bram Moolenaarf9e3e092019-01-13 23:38:42 +0100106 semsg(_(e_type_not_exist), name);
Bram Moolenaar98aefe72018-12-13 22:20:09 +0100107 return type;
108}
109
110/*
111 * Get an optional "bufnr" item from the dict in "arg".
112 * When the argument is not used or "bufnr" is not present then "buf" is
113 * unchanged.
114 * If "bufnr" is valid or not present return OK.
115 * When "arg" is not a dict or "bufnr" is invalide return FAIL.
116 */
117 static int
118get_bufnr_from_arg(typval_T *arg, buf_T **buf)
119{
120 dictitem_T *di;
121
122 if (arg->v_type != VAR_DICT)
123 {
Bram Moolenaarf9e3e092019-01-13 23:38:42 +0100124 emsg(_(e_dictreq));
Bram Moolenaar98aefe72018-12-13 22:20:09 +0100125 return FAIL;
126 }
127 if (arg->vval.v_dict == NULL)
128 return OK; // NULL dict is like an empty dict
129 di = dict_find(arg->vval.v_dict, (char_u *)"bufnr", -1);
130 if (di != NULL)
131 {
Bram Moolenaarf0884c52019-05-24 21:22:29 +0200132 *buf = get_buf_arg(&di->di_tv);
Bram Moolenaar98aefe72018-12-13 22:20:09 +0100133 if (*buf == NULL)
134 return FAIL;
135 }
136 return OK;
137}
138
139/*
140 * prop_add({lnum}, {col}, {props})
141 */
142 void
143f_prop_add(typval_T *argvars, typval_T *rettv UNUSED)
144{
Bram Moolenaare3d31b02018-12-24 23:07:04 +0100145 linenr_T start_lnum;
Bram Moolenaare3d31b02018-12-24 23:07:04 +0100146 colnr_T start_col;
Bram Moolenaar98aefe72018-12-13 22:20:09 +0100147
Bram Moolenaare3d31b02018-12-24 23:07:04 +0100148 start_lnum = tv_get_number(&argvars[0]);
149 start_col = tv_get_number(&argvars[1]);
150 if (start_col < 1)
Bram Moolenaar98aefe72018-12-13 22:20:09 +0100151 {
Bram Moolenaarf9e3e092019-01-13 23:38:42 +0100152 semsg(_(e_invalid_col), (long)start_col);
Bram Moolenaar98aefe72018-12-13 22:20:09 +0100153 return;
154 }
155 if (argvars[2].v_type != VAR_DICT)
156 {
Bram Moolenaarf9e3e092019-01-13 23:38:42 +0100157 emsg(_(e_dictreq));
Bram Moolenaar98aefe72018-12-13 22:20:09 +0100158 return;
159 }
Bram Moolenaar7a8d0272019-05-26 23:32:06 +0200160
161 prop_add_common(start_lnum, start_col, argvars[2].vval.v_dict,
162 curbuf, &argvars[2]);
163}
164
165/*
166 * Shared between prop_add() and popup_create().
167 * "dict_arg" is the function argument of a dict containing "bufnr".
168 * it is NULL for popup_create().
169 */
170 void
171prop_add_common(
172 linenr_T start_lnum,
173 colnr_T start_col,
174 dict_T *dict,
175 buf_T *default_buf,
176 typval_T *dict_arg)
177{
178 linenr_T lnum;
179 linenr_T end_lnum;
180 colnr_T end_col;
181 char_u *type_name;
182 proptype_T *type;
183 buf_T *buf = default_buf;
184 int id = 0;
185 char_u *newtext;
186 int proplen;
187 size_t textlen;
188 char_u *props = NULL;
189 char_u *newprops;
190 textprop_T tmp_prop;
191 int i;
Bram Moolenaar98aefe72018-12-13 22:20:09 +0100192
193 if (dict == NULL || dict_find(dict, (char_u *)"type", -1) == NULL)
194 {
Bram Moolenaarf9e3e092019-01-13 23:38:42 +0100195 emsg(_("E965: missing property type name"));
Bram Moolenaar98aefe72018-12-13 22:20:09 +0100196 return;
197 }
Bram Moolenaar8f667172018-12-14 15:38:31 +0100198 type_name = dict_get_string(dict, (char_u *)"type", FALSE);
Bram Moolenaar98aefe72018-12-13 22:20:09 +0100199
200 if (dict_find(dict, (char_u *)"end_lnum", -1) != NULL)
201 {
Bram Moolenaare3d31b02018-12-24 23:07:04 +0100202 end_lnum = dict_get_number(dict, (char_u *)"end_lnum");
203 if (end_lnum < start_lnum)
204 {
Bram Moolenaarf9e3e092019-01-13 23:38:42 +0100205 semsg(_(e_invargval), "end_lnum");
Bram Moolenaare3d31b02018-12-24 23:07:04 +0100206 return;
207 }
Bram Moolenaar98aefe72018-12-13 22:20:09 +0100208 }
Bram Moolenaare3d31b02018-12-24 23:07:04 +0100209 else
210 end_lnum = start_lnum;
Bram Moolenaar98aefe72018-12-13 22:20:09 +0100211
212 if (dict_find(dict, (char_u *)"length", -1) != NULL)
Bram Moolenaare3d31b02018-12-24 23:07:04 +0100213 {
214 long length = dict_get_number(dict, (char_u *)"length");
215
Bram Moolenaarb9c67a52019-01-01 19:49:20 +0100216 if (length < 0 || end_lnum > start_lnum)
Bram Moolenaare3d31b02018-12-24 23:07:04 +0100217 {
Bram Moolenaarf9e3e092019-01-13 23:38:42 +0100218 semsg(_(e_invargval), "length");
Bram Moolenaare3d31b02018-12-24 23:07:04 +0100219 return;
220 }
Bram Moolenaarb9c67a52019-01-01 19:49:20 +0100221 end_col = start_col + length;
Bram Moolenaare3d31b02018-12-24 23:07:04 +0100222 }
Bram Moolenaar98aefe72018-12-13 22:20:09 +0100223 else if (dict_find(dict, (char_u *)"end_col", -1) != NULL)
224 {
Bram Moolenaare3d31b02018-12-24 23:07:04 +0100225 end_col = dict_get_number(dict, (char_u *)"end_col");
226 if (end_col <= 0)
Bram Moolenaar98aefe72018-12-13 22:20:09 +0100227 {
Bram Moolenaarf9e3e092019-01-13 23:38:42 +0100228 semsg(_(e_invargval), "end_col");
Bram Moolenaar98aefe72018-12-13 22:20:09 +0100229 return;
230 }
231 }
Bram Moolenaare3d31b02018-12-24 23:07:04 +0100232 else if (start_lnum == end_lnum)
233 end_col = start_col;
234 else
235 end_col = 1;
Bram Moolenaar98aefe72018-12-13 22:20:09 +0100236
237 if (dict_find(dict, (char_u *)"id", -1) != NULL)
Bram Moolenaar8f667172018-12-14 15:38:31 +0100238 id = dict_get_number(dict, (char_u *)"id");
Bram Moolenaar98aefe72018-12-13 22:20:09 +0100239
Bram Moolenaar7a8d0272019-05-26 23:32:06 +0200240 if (dict_arg != NULL && get_bufnr_from_arg(dict_arg, &buf) == FAIL)
Bram Moolenaar98aefe72018-12-13 22:20:09 +0100241 return;
242
243 type = lookup_prop_type(type_name, buf);
244 if (type == NULL)
245 return;
246
Bram Moolenaare3d31b02018-12-24 23:07:04 +0100247 if (start_lnum < 1 || start_lnum > buf->b_ml.ml_line_count)
Bram Moolenaar98aefe72018-12-13 22:20:09 +0100248 {
Bram Moolenaarf9e3e092019-01-13 23:38:42 +0100249 semsg(_(e_invalid_lnum), (long)start_lnum);
Bram Moolenaare3d31b02018-12-24 23:07:04 +0100250 return;
251 }
252 if (end_lnum < start_lnum || end_lnum > buf->b_ml.ml_line_count)
253 {
Bram Moolenaarf9e3e092019-01-13 23:38:42 +0100254 semsg(_(e_invalid_lnum), (long)end_lnum);
Bram Moolenaar98aefe72018-12-13 22:20:09 +0100255 return;
256 }
257
Bram Moolenaard79eef22019-05-24 20:41:55 +0200258 if (buf->b_ml.ml_mfp == NULL)
259 ml_open(buf);
260
Bram Moolenaare3d31b02018-12-24 23:07:04 +0100261 for (lnum = start_lnum; lnum <= end_lnum; ++lnum)
Bram Moolenaar98aefe72018-12-13 22:20:09 +0100262 {
Bram Moolenaare3d31b02018-12-24 23:07:04 +0100263 colnr_T col; // start column
264 long length; // in bytes
265
266 // Fetch the line to get the ml_line_len field updated.
267 proplen = get_text_props(buf, lnum, &props, TRUE);
268 textlen = buf->b_ml.ml_line_len - proplen * sizeof(textprop_T);
269
270 if (lnum == start_lnum)
271 col = start_col;
272 else
273 col = 1;
274 if (col - 1 > (colnr_T)textlen)
275 {
Bram Moolenaarf9e3e092019-01-13 23:38:42 +0100276 semsg(_(e_invalid_col), (long)start_col);
Bram Moolenaare3d31b02018-12-24 23:07:04 +0100277 return;
278 }
279
280 if (lnum == end_lnum)
Bram Moolenaarb9c67a52019-01-01 19:49:20 +0100281 length = end_col - col;
Bram Moolenaare3d31b02018-12-24 23:07:04 +0100282 else
Bram Moolenaar4b7214e2019-01-03 21:55:32 +0100283 length = (int)textlen - col + 1;
Bram Moolenaarb413d2e2018-12-25 23:15:46 +0100284 if (length > (long)textlen)
Bram Moolenaar4b7214e2019-01-03 21:55:32 +0100285 length = (int)textlen; // can include the end-of-line
Bram Moolenaarb9c67a52019-01-01 19:49:20 +0100286 if (length < 0)
287 length = 0; // zero-width property
Bram Moolenaare3d31b02018-12-24 23:07:04 +0100288
289 // Allocate the new line with space for the new proprety.
290 newtext = alloc(buf->b_ml.ml_line_len + sizeof(textprop_T));
291 if (newtext == NULL)
292 return;
293 // Copy the text, including terminating NUL.
294 mch_memmove(newtext, buf->b_ml.ml_line_ptr, textlen);
295
296 // Find the index where to insert the new property.
Bram Moolenaar7a8d0272019-05-26 23:32:06 +0200297 // Since the text properties are not aligned properly when stored with
298 // the text, we need to copy them as bytes before using it as a struct.
Bram Moolenaare3d31b02018-12-24 23:07:04 +0100299 for (i = 0; i < proplen; ++i)
300 {
301 mch_memmove(&tmp_prop, props + i * sizeof(textprop_T),
Bram Moolenaar7a8d0272019-05-26 23:32:06 +0200302 sizeof(textprop_T));
Bram Moolenaare3d31b02018-12-24 23:07:04 +0100303 if (tmp_prop.tp_col >= col)
304 break;
305 }
306 newprops = newtext + textlen;
307 if (i > 0)
308 mch_memmove(newprops, props, sizeof(textprop_T) * i);
309
310 tmp_prop.tp_col = col;
311 tmp_prop.tp_len = length;
312 tmp_prop.tp_id = id;
313 tmp_prop.tp_type = type->pt_id;
314 tmp_prop.tp_flags = (lnum > start_lnum ? TP_FLAG_CONT_PREV : 0)
315 | (lnum < end_lnum ? TP_FLAG_CONT_NEXT : 0);
316 mch_memmove(newprops + i * sizeof(textprop_T), &tmp_prop,
Bram Moolenaar7a8d0272019-05-26 23:32:06 +0200317 sizeof(textprop_T));
Bram Moolenaare3d31b02018-12-24 23:07:04 +0100318
319 if (i < proplen)
320 mch_memmove(newprops + (i + 1) * sizeof(textprop_T),
321 props + i * sizeof(textprop_T),
322 sizeof(textprop_T) * (proplen - i));
323
324 if (buf->b_ml.ml_flags & ML_LINE_DIRTY)
325 vim_free(buf->b_ml.ml_line_ptr);
326 buf->b_ml.ml_line_ptr = newtext;
327 buf->b_ml.ml_line_len += sizeof(textprop_T);
328 buf->b_ml.ml_flags |= ML_LINE_DIRTY;
Bram Moolenaar98aefe72018-12-13 22:20:09 +0100329 }
330
Bram Moolenaarb413d2e2018-12-25 23:15:46 +0100331 buf->b_has_textprop = TRUE; // this is never reset
Bram Moolenaar98aefe72018-12-13 22:20:09 +0100332 redraw_buf_later(buf, NOT_VALID);
333}
334
335/*
Bram Moolenaarfb95e212018-12-14 12:18:11 +0100336 * Fetch the text properties for line "lnum" in buffer "buf".
Bram Moolenaar98aefe72018-12-13 22:20:09 +0100337 * Returns the number of text properties and, when non-zero, a pointer to the
338 * first one in "props" (note that it is not aligned, therefore the char_u
339 * pointer).
340 */
341 int
342get_text_props(buf_T *buf, linenr_T lnum, char_u **props, int will_change)
343{
344 char_u *text;
345 size_t textlen;
346 size_t proplen;
347
Bram Moolenaarb413d2e2018-12-25 23:15:46 +0100348 // Be quick when no text property types have been defined or the buffer,
349 // unless we are adding one.
Bram Moolenaard79eef22019-05-24 20:41:55 +0200350 if ((!buf->b_has_textprop && !will_change) || buf->b_ml.ml_mfp == NULL)
Bram Moolenaar98aefe72018-12-13 22:20:09 +0100351 return 0;
352
353 // Fetch the line to get the ml_line_len field updated.
354 text = ml_get_buf(buf, lnum, will_change);
355 textlen = STRLEN(text) + 1;
356 proplen = buf->b_ml.ml_line_len - textlen;
357 if (proplen % sizeof(textprop_T) != 0)
358 {
Bram Moolenaarf9e3e092019-01-13 23:38:42 +0100359 iemsg(_("E967: text property info corrupted"));
Bram Moolenaar98aefe72018-12-13 22:20:09 +0100360 return 0;
361 }
362 if (proplen > 0)
363 *props = text + textlen;
Bram Moolenaar4efe73b2018-12-16 14:37:39 +0100364 return (int)(proplen / sizeof(textprop_T));
Bram Moolenaar98aefe72018-12-13 22:20:09 +0100365}
366
Bram Moolenaar4164bb22019-01-04 23:09:49 +0100367/*
368 * Set the text properties for line "lnum" to "props" with length "len".
369 * If "len" is zero text properties are removed, "props" is not used.
370 * Any existing text properties are dropped.
371 * Only works for the current buffer.
372 */
373 static void
374set_text_props(linenr_T lnum, char_u *props, int len)
375{
Bram Moolenaar8aef43b2019-01-08 20:14:35 +0100376 char_u *text;
377 char_u *newtext;
378 int textlen;
Bram Moolenaar4164bb22019-01-04 23:09:49 +0100379
380 text = ml_get(lnum);
Bram Moolenaar8aef43b2019-01-08 20:14:35 +0100381 textlen = (int)STRLEN(text) + 1;
Bram Moolenaar4164bb22019-01-04 23:09:49 +0100382 newtext = alloc(textlen + len);
383 if (newtext == NULL)
384 return;
385 mch_memmove(newtext, text, textlen);
386 if (len > 0)
387 mch_memmove(newtext + textlen, props, len);
388 if (curbuf->b_ml.ml_flags & ML_LINE_DIRTY)
389 vim_free(curbuf->b_ml.ml_line_ptr);
390 curbuf->b_ml.ml_line_ptr = newtext;
391 curbuf->b_ml.ml_line_len = textlen + len;
392 curbuf->b_ml.ml_flags |= ML_LINE_DIRTY;
393}
394
Bram Moolenaar98aefe72018-12-13 22:20:09 +0100395 static proptype_T *
396find_type_by_id(hashtab_T *ht, int id)
397{
398 long todo;
399 hashitem_T *hi;
400
401 if (ht == NULL)
402 return NULL;
403
404 // TODO: Make this faster by keeping a list of types sorted on ID and use
405 // a binary search.
406
407 todo = (long)ht->ht_used;
408 for (hi = ht->ht_array; todo > 0; ++hi)
409 {
410 if (!HASHITEM_EMPTY(hi))
411 {
412 proptype_T *prop = HI2PT(hi);
413
414 if (prop->pt_id == id)
415 return prop;
416 --todo;
417 }
418 }
419 return NULL;
420}
421
422/*
423 * Find a property type by ID in "buf" or globally.
424 * Returns NULL if not found.
425 */
426 proptype_T *
427text_prop_type_by_id(buf_T *buf, int id)
428{
429 proptype_T *type;
430
431 type = find_type_by_id(buf->b_proptypes, id);
432 if (type == NULL)
433 type = find_type_by_id(global_proptypes, id);
434 return type;
435}
436
437/*
438 * prop_clear({lnum} [, {lnum_end} [, {bufnr}]])
439 */
440 void
441f_prop_clear(typval_T *argvars, typval_T *rettv UNUSED)
442{
Bram Moolenaard155d7a2018-12-21 16:04:21 +0100443 linenr_T start = tv_get_number(&argvars[0]);
Bram Moolenaar98aefe72018-12-13 22:20:09 +0100444 linenr_T end = start;
445 linenr_T lnum;
446 buf_T *buf = curbuf;
447
448 if (argvars[1].v_type != VAR_UNKNOWN)
449 {
Bram Moolenaard155d7a2018-12-21 16:04:21 +0100450 end = tv_get_number(&argvars[1]);
Bram Moolenaar98aefe72018-12-13 22:20:09 +0100451 if (argvars[2].v_type != VAR_UNKNOWN)
452 {
453 if (get_bufnr_from_arg(&argvars[2], &buf) == FAIL)
454 return;
455 }
456 }
457 if (start < 1 || end < 1)
458 {
Bram Moolenaarf9e3e092019-01-13 23:38:42 +0100459 emsg(_(e_invrange));
Bram Moolenaar98aefe72018-12-13 22:20:09 +0100460 return;
461 }
462
463 for (lnum = start; lnum <= end; ++lnum)
464 {
465 char_u *text;
466 size_t len;
467
468 if (lnum > buf->b_ml.ml_line_count)
469 break;
470 text = ml_get_buf(buf, lnum, FALSE);
471 len = STRLEN(text) + 1;
472 if ((size_t)buf->b_ml.ml_line_len > len)
473 {
474 if (!(buf->b_ml.ml_flags & ML_LINE_DIRTY))
475 {
476 char_u *newtext = vim_strsave(text);
477
478 // need to allocate the line now
479 if (newtext == NULL)
480 return;
481 buf->b_ml.ml_line_ptr = newtext;
482 buf->b_ml.ml_flags |= ML_LINE_DIRTY;
483 }
Bram Moolenaar4efe73b2018-12-16 14:37:39 +0100484 buf->b_ml.ml_line_len = (int)len;
Bram Moolenaar98aefe72018-12-13 22:20:09 +0100485 }
486 }
487 redraw_buf_later(buf, NOT_VALID);
488}
489
490/*
491 * prop_list({lnum} [, {bufnr}])
492 */
493 void
494f_prop_list(typval_T *argvars, typval_T *rettv)
495{
Bram Moolenaard155d7a2018-12-21 16:04:21 +0100496 linenr_T lnum = tv_get_number(&argvars[0]);
Bram Moolenaar98aefe72018-12-13 22:20:09 +0100497 buf_T *buf = curbuf;
498
499 if (argvars[1].v_type != VAR_UNKNOWN)
500 {
501 if (get_bufnr_from_arg(&argvars[1], &buf) == FAIL)
502 return;
503 }
504 if (lnum < 1 || lnum > buf->b_ml.ml_line_count)
505 {
Bram Moolenaarf9e3e092019-01-13 23:38:42 +0100506 emsg(_(e_invrange));
Bram Moolenaar98aefe72018-12-13 22:20:09 +0100507 return;
508 }
509
510 if (rettv_list_alloc(rettv) == OK)
511 {
512 char_u *text = ml_get_buf(buf, lnum, FALSE);
513 size_t textlen = STRLEN(text) + 1;
Bram Moolenaar4efe73b2018-12-16 14:37:39 +0100514 int count = (int)((buf->b_ml.ml_line_len - textlen)
515 / sizeof(textprop_T));
Bram Moolenaar98aefe72018-12-13 22:20:09 +0100516 int i;
517 textprop_T prop;
518 proptype_T *pt;
519
520 for (i = 0; i < count; ++i)
521 {
522 dict_T *d = dict_alloc();
523
524 if (d == NULL)
525 break;
526 mch_memmove(&prop, text + textlen + i * sizeof(textprop_T),
527 sizeof(textprop_T));
528 dict_add_number(d, "col", prop.tp_col);
529 dict_add_number(d, "length", prop.tp_len);
530 dict_add_number(d, "id", prop.tp_id);
531 dict_add_number(d, "start", !(prop.tp_flags & TP_FLAG_CONT_PREV));
532 dict_add_number(d, "end", !(prop.tp_flags & TP_FLAG_CONT_NEXT));
533 pt = text_prop_type_by_id(buf, prop.tp_type);
534 if (pt != NULL)
535 dict_add_string(d, "type", pt->pt_name);
536
537 list_append_dict(rettv->vval.v_list, d);
538 }
539 }
540}
541
542/*
543 * prop_remove({props} [, {lnum} [, {lnum_end}]])
544 */
545 void
546f_prop_remove(typval_T *argvars, typval_T *rettv)
547{
548 linenr_T start = 1;
549 linenr_T end = 0;
550 linenr_T lnum;
551 dict_T *dict;
552 buf_T *buf = curbuf;
553 dictitem_T *di;
554 int do_all = FALSE;
555 int id = -1;
556 int type_id = -1;
557
558 rettv->vval.v_number = 0;
559 if (argvars[0].v_type != VAR_DICT || argvars[0].vval.v_dict == NULL)
560 {
Bram Moolenaarf9e3e092019-01-13 23:38:42 +0100561 emsg(_(e_invarg));
Bram Moolenaar98aefe72018-12-13 22:20:09 +0100562 return;
563 }
564
565 if (argvars[1].v_type != VAR_UNKNOWN)
566 {
Bram Moolenaard155d7a2018-12-21 16:04:21 +0100567 start = tv_get_number(&argvars[1]);
Bram Moolenaar98aefe72018-12-13 22:20:09 +0100568 end = start;
569 if (argvars[2].v_type != VAR_UNKNOWN)
Bram Moolenaard155d7a2018-12-21 16:04:21 +0100570 end = tv_get_number(&argvars[2]);
Bram Moolenaar98aefe72018-12-13 22:20:09 +0100571 if (start < 1 || end < 1)
572 {
Bram Moolenaarf9e3e092019-01-13 23:38:42 +0100573 emsg(_(e_invrange));
Bram Moolenaar98aefe72018-12-13 22:20:09 +0100574 return;
575 }
576 }
577
578 dict = argvars[0].vval.v_dict;
Bram Moolenaarf0884c52019-05-24 21:22:29 +0200579 if (get_bufnr_from_arg(&argvars[0], &buf) == FAIL)
580 return;
581 if (buf->b_ml.ml_mfp == NULL)
582 return;
Bram Moolenaar98aefe72018-12-13 22:20:09 +0100583
584 di = dict_find(dict, (char_u*)"all", -1);
585 if (di != NULL)
Bram Moolenaar8f667172018-12-14 15:38:31 +0100586 do_all = dict_get_number(dict, (char_u *)"all");
Bram Moolenaar98aefe72018-12-13 22:20:09 +0100587
588 if (dict_find(dict, (char_u *)"id", -1) != NULL)
Bram Moolenaar8f667172018-12-14 15:38:31 +0100589 id = dict_get_number(dict, (char_u *)"id");
Bram Moolenaar98aefe72018-12-13 22:20:09 +0100590 if (dict_find(dict, (char_u *)"type", -1))
591 {
Bram Moolenaar8f667172018-12-14 15:38:31 +0100592 char_u *name = dict_get_string(dict, (char_u *)"type", FALSE);
Bram Moolenaar98aefe72018-12-13 22:20:09 +0100593 proptype_T *type = lookup_prop_type(name, buf);
594
595 if (type == NULL)
596 return;
597 type_id = type->pt_id;
598 }
599 if (id == -1 && type_id == -1)
600 {
Bram Moolenaarf9e3e092019-01-13 23:38:42 +0100601 emsg(_("E968: Need at least one of 'id' or 'type'"));
Bram Moolenaar98aefe72018-12-13 22:20:09 +0100602 return;
603 }
604
605 if (end == 0)
606 end = buf->b_ml.ml_line_count;
607 for (lnum = start; lnum <= end; ++lnum)
608 {
609 char_u *text;
610 size_t len;
611
612 if (lnum > buf->b_ml.ml_line_count)
613 break;
614 text = ml_get_buf(buf, lnum, FALSE);
615 len = STRLEN(text) + 1;
616 if ((size_t)buf->b_ml.ml_line_len > len)
617 {
618 static textprop_T textprop; // static because of alignment
619 unsigned idx;
620
621 for (idx = 0; idx < (buf->b_ml.ml_line_len - len)
622 / sizeof(textprop_T); ++idx)
623 {
624 char_u *cur_prop = buf->b_ml.ml_line_ptr + len
625 + idx * sizeof(textprop_T);
626 size_t taillen;
627
628 mch_memmove(&textprop, cur_prop, sizeof(textprop_T));
629 if (textprop.tp_id == id || textprop.tp_type == type_id)
630 {
631 if (!(buf->b_ml.ml_flags & ML_LINE_DIRTY))
632 {
633 char_u *newptr = alloc(buf->b_ml.ml_line_len);
634
635 // need to allocate the line to be able to change it
636 if (newptr == NULL)
637 return;
638 mch_memmove(newptr, buf->b_ml.ml_line_ptr,
639 buf->b_ml.ml_line_len);
640 buf->b_ml.ml_line_ptr = newptr;
Bram Moolenaar0a2f5782019-03-22 13:20:43 +0100641 buf->b_ml.ml_flags |= ML_LINE_DIRTY;
642
643 cur_prop = buf->b_ml.ml_line_ptr + len
Bram Moolenaarf0884c52019-05-24 21:22:29 +0200644 + idx * sizeof(textprop_T);
Bram Moolenaar98aefe72018-12-13 22:20:09 +0100645 }
646
647 taillen = buf->b_ml.ml_line_len - len
648 - (idx + 1) * sizeof(textprop_T);
649 if (taillen > 0)
650 mch_memmove(cur_prop, cur_prop + sizeof(textprop_T),
651 taillen);
652 buf->b_ml.ml_line_len -= sizeof(textprop_T);
653 --idx;
654
655 ++rettv->vval.v_number;
656 if (!do_all)
657 break;
658 }
659 }
660 }
661 }
662 redraw_buf_later(buf, NOT_VALID);
663}
664
665/*
666 * Common for f_prop_type_add() and f_prop_type_change().
667 */
668 void
669prop_type_set(typval_T *argvars, int add)
670{
671 char_u *name;
672 buf_T *buf = NULL;
673 dict_T *dict;
674 dictitem_T *di;
675 proptype_T *prop;
676
Bram Moolenaard155d7a2018-12-21 16:04:21 +0100677 name = tv_get_string(&argvars[0]);
Bram Moolenaar98aefe72018-12-13 22:20:09 +0100678 if (*name == NUL)
679 {
Bram Moolenaarf9e3e092019-01-13 23:38:42 +0100680 emsg(_(e_invarg));
Bram Moolenaar98aefe72018-12-13 22:20:09 +0100681 return;
682 }
683
684 if (get_bufnr_from_arg(&argvars[1], &buf) == FAIL)
685 return;
686 dict = argvars[1].vval.v_dict;
687
688 prop = find_prop(name, buf);
689 if (add)
690 {
691 hashtab_T **htp;
692
693 if (prop != NULL)
694 {
Bram Moolenaarf9e3e092019-01-13 23:38:42 +0100695 semsg(_("E969: Property type %s already defined"), name);
Bram Moolenaar98aefe72018-12-13 22:20:09 +0100696 return;
697 }
Bram Moolenaar18a4ba22019-05-24 19:39:03 +0200698 prop = (proptype_T *)alloc_clear(sizeof(proptype_T) + STRLEN(name));
Bram Moolenaar98aefe72018-12-13 22:20:09 +0100699 if (prop == NULL)
700 return;
701 STRCPY(prop->pt_name, name);
702 prop->pt_id = ++proptype_id;
703 htp = buf == NULL ? &global_proptypes : &buf->b_proptypes;
704 if (*htp == NULL)
705 {
706 *htp = (hashtab_T *)alloc(sizeof(hashtab_T));
707 if (*htp == NULL)
Bram Moolenaarfb95e212018-12-14 12:18:11 +0100708 {
709 vim_free(prop);
Bram Moolenaar98aefe72018-12-13 22:20:09 +0100710 return;
Bram Moolenaarfb95e212018-12-14 12:18:11 +0100711 }
Bram Moolenaar98aefe72018-12-13 22:20:09 +0100712 hash_init(*htp);
713 }
Bram Moolenaarfb95e212018-12-14 12:18:11 +0100714 hash_add(*htp, PT2HIKEY(prop));
Bram Moolenaar98aefe72018-12-13 22:20:09 +0100715 }
716 else
717 {
718 if (prop == NULL)
719 {
Bram Moolenaarf9e3e092019-01-13 23:38:42 +0100720 semsg(_(e_type_not_exist), name);
Bram Moolenaar98aefe72018-12-13 22:20:09 +0100721 return;
722 }
723 }
724
725 if (dict != NULL)
726 {
727 di = dict_find(dict, (char_u *)"highlight", -1);
728 if (di != NULL)
729 {
730 char_u *highlight;
731 int hl_id = 0;
732
Bram Moolenaar8f667172018-12-14 15:38:31 +0100733 highlight = dict_get_string(dict, (char_u *)"highlight", FALSE);
Bram Moolenaar98aefe72018-12-13 22:20:09 +0100734 if (highlight != NULL && *highlight != NUL)
735 hl_id = syn_name2id(highlight);
736 if (hl_id <= 0)
737 {
Bram Moolenaarf9e3e092019-01-13 23:38:42 +0100738 semsg(_("E970: Unknown highlight group name: '%s'"),
Bram Moolenaar98aefe72018-12-13 22:20:09 +0100739 highlight == NULL ? (char_u *)"" : highlight);
740 return;
741 }
742 prop->pt_hl_id = hl_id;
743 }
744
Bram Moolenaar58187f12019-05-05 16:33:47 +0200745 di = dict_find(dict, (char_u *)"combine", -1);
746 if (di != NULL)
747 {
748 if (tv_get_number(&di->di_tv))
749 prop->pt_flags |= PT_FLAG_COMBINE;
750 else
751 prop->pt_flags &= ~PT_FLAG_COMBINE;
752 }
753
Bram Moolenaar98aefe72018-12-13 22:20:09 +0100754 di = dict_find(dict, (char_u *)"priority", -1);
755 if (di != NULL)
Bram Moolenaard155d7a2018-12-21 16:04:21 +0100756 prop->pt_priority = tv_get_number(&di->di_tv);
Bram Moolenaar98aefe72018-12-13 22:20:09 +0100757
758 di = dict_find(dict, (char_u *)"start_incl", -1);
759 if (di != NULL)
760 {
Bram Moolenaard155d7a2018-12-21 16:04:21 +0100761 if (tv_get_number(&di->di_tv))
Bram Moolenaar98aefe72018-12-13 22:20:09 +0100762 prop->pt_flags |= PT_FLAG_INS_START_INCL;
763 else
764 prop->pt_flags &= ~PT_FLAG_INS_START_INCL;
765 }
766
767 di = dict_find(dict, (char_u *)"end_incl", -1);
768 if (di != NULL)
769 {
Bram Moolenaard155d7a2018-12-21 16:04:21 +0100770 if (tv_get_number(&di->di_tv))
Bram Moolenaar98aefe72018-12-13 22:20:09 +0100771 prop->pt_flags |= PT_FLAG_INS_END_INCL;
772 else
773 prop->pt_flags &= ~PT_FLAG_INS_END_INCL;
774 }
775 }
776}
777
778/*
779 * prop_type_add({name}, {props})
780 */
781 void
782f_prop_type_add(typval_T *argvars, typval_T *rettv UNUSED)
783{
784 prop_type_set(argvars, TRUE);
785}
786
787/*
788 * prop_type_change({name}, {props})
789 */
790 void
791f_prop_type_change(typval_T *argvars, typval_T *rettv UNUSED)
792{
793 prop_type_set(argvars, FALSE);
794}
795
796/*
797 * prop_type_delete({name} [, {bufnr}])
798 */
799 void
800f_prop_type_delete(typval_T *argvars, typval_T *rettv UNUSED)
801{
802 char_u *name;
803 buf_T *buf = NULL;
804 hashitem_T *hi;
805
Bram Moolenaard155d7a2018-12-21 16:04:21 +0100806 name = tv_get_string(&argvars[0]);
Bram Moolenaar98aefe72018-12-13 22:20:09 +0100807 if (*name == NUL)
808 {
Bram Moolenaarf9e3e092019-01-13 23:38:42 +0100809 emsg(_(e_invarg));
Bram Moolenaar98aefe72018-12-13 22:20:09 +0100810 return;
811 }
812
813 if (argvars[1].v_type != VAR_UNKNOWN)
814 {
815 if (get_bufnr_from_arg(&argvars[1], &buf) == FAIL)
816 return;
817 }
818
819 hi = find_prop_hi(name, buf);
820 if (hi != NULL)
821 {
822 hashtab_T *ht;
Bram Moolenaarfb95e212018-12-14 12:18:11 +0100823 proptype_T *prop = HI2PT(hi);
Bram Moolenaar98aefe72018-12-13 22:20:09 +0100824
825 if (buf == NULL)
826 ht = global_proptypes;
827 else
828 ht = buf->b_proptypes;
829 hash_remove(ht, hi);
Bram Moolenaarfb95e212018-12-14 12:18:11 +0100830 vim_free(prop);
Bram Moolenaar98aefe72018-12-13 22:20:09 +0100831 }
832}
833
834/*
835 * prop_type_get({name} [, {bufnr}])
836 */
837 void
838f_prop_type_get(typval_T *argvars, typval_T *rettv UNUSED)
839{
Bram Moolenaard155d7a2018-12-21 16:04:21 +0100840 char_u *name = tv_get_string(&argvars[0]);
Bram Moolenaar98aefe72018-12-13 22:20:09 +0100841
842 if (*name == NUL)
843 {
Bram Moolenaarf9e3e092019-01-13 23:38:42 +0100844 emsg(_(e_invarg));
Bram Moolenaar98aefe72018-12-13 22:20:09 +0100845 return;
846 }
847 if (rettv_dict_alloc(rettv) == OK)
848 {
849 proptype_T *prop = NULL;
850 buf_T *buf = NULL;
851
852 if (argvars[1].v_type != VAR_UNKNOWN)
853 {
854 if (get_bufnr_from_arg(&argvars[1], &buf) == FAIL)
855 return;
856 }
857
858 prop = find_prop(name, buf);
859 if (prop != NULL)
860 {
861 dict_T *d = rettv->vval.v_dict;
862
863 if (prop->pt_hl_id > 0)
864 dict_add_string(d, "highlight", syn_id2name(prop->pt_hl_id));
865 dict_add_number(d, "priority", prop->pt_priority);
Bram Moolenaar58187f12019-05-05 16:33:47 +0200866 dict_add_number(d, "combine",
867 (prop->pt_flags & PT_FLAG_COMBINE) ? 1 : 0);
Bram Moolenaar98aefe72018-12-13 22:20:09 +0100868 dict_add_number(d, "start_incl",
869 (prop->pt_flags & PT_FLAG_INS_START_INCL) ? 1 : 0);
870 dict_add_number(d, "end_incl",
871 (prop->pt_flags & PT_FLAG_INS_END_INCL) ? 1 : 0);
872 if (buf != NULL)
873 dict_add_number(d, "bufnr", buf->b_fnum);
874 }
875 }
876}
877
878 static void
879list_types(hashtab_T *ht, list_T *l)
880{
881 long todo;
882 hashitem_T *hi;
883
884 todo = (long)ht->ht_used;
885 for (hi = ht->ht_array; todo > 0; ++hi)
886 {
887 if (!HASHITEM_EMPTY(hi))
888 {
889 proptype_T *prop = HI2PT(hi);
890
891 list_append_string(l, prop->pt_name, -1);
892 --todo;
893 }
894 }
895}
896
897/*
898 * prop_type_list([{bufnr}])
899 */
900 void
901f_prop_type_list(typval_T *argvars, typval_T *rettv UNUSED)
902{
903 buf_T *buf = NULL;
904
905 if (rettv_list_alloc(rettv) == OK)
906 {
907 if (argvars[0].v_type != VAR_UNKNOWN)
908 {
909 if (get_bufnr_from_arg(&argvars[0], &buf) == FAIL)
910 return;
911 }
912 if (buf == NULL)
913 {
914 if (global_proptypes != NULL)
915 list_types(global_proptypes, rettv->vval.v_list);
916 }
917 else if (buf->b_proptypes != NULL)
918 list_types(buf->b_proptypes, rettv->vval.v_list);
919 }
920}
921
922/*
923 * Free all property types in "ht".
924 */
925 static void
926clear_ht_prop_types(hashtab_T *ht)
927{
928 long todo;
929 hashitem_T *hi;
930
931 if (ht == NULL)
932 return;
933
934 todo = (long)ht->ht_used;
935 for (hi = ht->ht_array; todo > 0; ++hi)
936 {
937 if (!HASHITEM_EMPTY(hi))
938 {
939 proptype_T *prop = HI2PT(hi);
940
941 vim_free(prop);
942 --todo;
943 }
944 }
945
946 hash_clear(ht);
947 vim_free(ht);
948}
949
950#if defined(EXITFREE) || defined(PROTO)
951/*
Bram Moolenaarfb95e212018-12-14 12:18:11 +0100952 * Free all global property types.
Bram Moolenaar98aefe72018-12-13 22:20:09 +0100953 */
954 void
955clear_global_prop_types(void)
956{
957 clear_ht_prop_types(global_proptypes);
958 global_proptypes = NULL;
959}
960#endif
961
962/*
963 * Free all property types for "buf".
964 */
965 void
966clear_buf_prop_types(buf_T *buf)
967{
968 clear_ht_prop_types(buf->b_proptypes);
969 buf->b_proptypes = NULL;
970}
971
Bram Moolenaarb9c67a52019-01-01 19:49:20 +0100972/*
973 * Adjust the columns of text properties in line "lnum" after position "col" to
974 * shift by "bytes_added" (can be negative).
Bram Moolenaar44746aa2019-01-02 00:02:11 +0100975 * Note that "col" is zero-based, while tp_col is one-based.
976 * Only for the current buffer.
Bram Moolenaarf3333b02019-05-19 22:53:40 +0200977 * "flags" can have:
978 * APC_SAVE_FOR_UNDO: Call u_savesub() before making changes to the line.
979 * APC_SUBSTITUTE: Text is replaced, not inserted.
Bram Moolenaar8055d172019-05-17 22:57:26 +0200980 * Caller is expected to check b_has_textprop and "bytes_added" being non-zero.
Bram Moolenaar338dfda2019-05-19 15:19:57 +0200981 * Returns TRUE when props were changed.
Bram Moolenaarb9c67a52019-01-01 19:49:20 +0100982 */
Bram Moolenaar338dfda2019-05-19 15:19:57 +0200983 int
Bram Moolenaar196d1572019-01-02 23:47:18 +0100984adjust_prop_columns(
985 linenr_T lnum,
986 colnr_T col,
Bram Moolenaar338dfda2019-05-19 15:19:57 +0200987 int bytes_added,
Bram Moolenaarf3333b02019-05-19 22:53:40 +0200988 int flags)
Bram Moolenaarb9c67a52019-01-01 19:49:20 +0100989{
Bram Moolenaar44746aa2019-01-02 00:02:11 +0100990 int proplen;
991 char_u *props;
992 textprop_T tmp_prop;
993 proptype_T *pt;
994 int dirty = FALSE;
Bram Moolenaar196d1572019-01-02 23:47:18 +0100995 int ri, wi;
996 size_t textlen;
997
998 if (text_prop_frozen > 0)
Bram Moolenaar338dfda2019-05-19 15:19:57 +0200999 return FALSE;
Bram Moolenaar44746aa2019-01-02 00:02:11 +01001000
1001 proplen = get_text_props(curbuf, lnum, &props, TRUE);
1002 if (proplen == 0)
Bram Moolenaar338dfda2019-05-19 15:19:57 +02001003 return FALSE;
Bram Moolenaar196d1572019-01-02 23:47:18 +01001004 textlen = curbuf->b_ml.ml_line_len - proplen * sizeof(textprop_T);
Bram Moolenaar44746aa2019-01-02 00:02:11 +01001005
Bram Moolenaar196d1572019-01-02 23:47:18 +01001006 wi = 0; // write index
1007 for (ri = 0; ri < proplen; ++ri)
Bram Moolenaar44746aa2019-01-02 00:02:11 +01001008 {
Bram Moolenaarf3333b02019-05-19 22:53:40 +02001009 int start_incl;
1010
Bram Moolenaar196d1572019-01-02 23:47:18 +01001011 mch_memmove(&tmp_prop, props + ri * sizeof(textprop_T),
Bram Moolenaar44746aa2019-01-02 00:02:11 +01001012 sizeof(textprop_T));
1013 pt = text_prop_type_by_id(curbuf, tmp_prop.tp_type);
Bram Moolenaarf3333b02019-05-19 22:53:40 +02001014 start_incl = (flags & APC_SUBSTITUTE) ||
1015 (pt != NULL && (pt->pt_flags & PT_FLAG_INS_START_INCL));
Bram Moolenaar44746aa2019-01-02 00:02:11 +01001016
Bram Moolenaar196d1572019-01-02 23:47:18 +01001017 if (bytes_added > 0
Bram Moolenaarf3333b02019-05-19 22:53:40 +02001018 && (tmp_prop.tp_col >= col + (start_incl ? 2 : 1)))
1019 {
1020 if (tmp_prop.tp_col < col + (start_incl ? 2 : 1))
1021 {
1022 tmp_prop.tp_len += (tmp_prop.tp_col - 1 - col) + bytes_added;
1023 tmp_prop.tp_col = col + 1;
1024 }
1025 else
1026 tmp_prop.tp_col += bytes_added;
1027 // Save for undo if requested and not done yet.
1028 if ((flags & APC_SAVE_FOR_UNDO) && !dirty)
1029 u_savesub(lnum);
1030 dirty = TRUE;
1031 }
1032 else if (bytes_added <= 0 && (tmp_prop.tp_col > col + 1))
Bram Moolenaar44746aa2019-01-02 00:02:11 +01001033 {
Bram Moolenaar8055d172019-05-17 22:57:26 +02001034 if (tmp_prop.tp_col + bytes_added < col + 1)
1035 {
1036 tmp_prop.tp_len += (tmp_prop.tp_col - 1 - col) + bytes_added;
1037 tmp_prop.tp_col = col + 1;
1038 }
1039 else
1040 tmp_prop.tp_col += bytes_added;
Bram Moolenaar338dfda2019-05-19 15:19:57 +02001041 // Save for undo if requested and not done yet.
Bram Moolenaarf3333b02019-05-19 22:53:40 +02001042 if ((flags & APC_SAVE_FOR_UNDO) && !dirty)
Bram Moolenaar338dfda2019-05-19 15:19:57 +02001043 u_savesub(lnum);
Bram Moolenaar44746aa2019-01-02 00:02:11 +01001044 dirty = TRUE;
Bram Moolenaar8055d172019-05-17 22:57:26 +02001045 if (tmp_prop.tp_len <= 0)
1046 continue; // drop this text property
Bram Moolenaar44746aa2019-01-02 00:02:11 +01001047 }
Bram Moolenaar196d1572019-01-02 23:47:18 +01001048 else if (tmp_prop.tp_len > 0
1049 && tmp_prop.tp_col + tmp_prop.tp_len > col
Bram Moolenaar4614f532019-01-06 12:54:55 +01001050 + ((pt != NULL && (pt->pt_flags & PT_FLAG_INS_END_INCL))
Bram Moolenaar196d1572019-01-02 23:47:18 +01001051 ? 0 : 1))
Bram Moolenaar44746aa2019-01-02 00:02:11 +01001052 {
Bram Moolenaar8055d172019-05-17 22:57:26 +02001053 int after = col - bytes_added
1054 - (tmp_prop.tp_col - 1 + tmp_prop.tp_len);
1055 if (after > 0)
1056 tmp_prop.tp_len += bytes_added + after;
1057 else
1058 tmp_prop.tp_len += bytes_added;
Bram Moolenaar338dfda2019-05-19 15:19:57 +02001059 // Save for undo if requested and not done yet.
Bram Moolenaarf3333b02019-05-19 22:53:40 +02001060 if ((flags & APC_SAVE_FOR_UNDO) && !dirty)
Bram Moolenaar338dfda2019-05-19 15:19:57 +02001061 u_savesub(lnum);
Bram Moolenaar44746aa2019-01-02 00:02:11 +01001062 dirty = TRUE;
Bram Moolenaar196d1572019-01-02 23:47:18 +01001063 if (tmp_prop.tp_len <= 0)
1064 continue; // drop this text property
Bram Moolenaar44746aa2019-01-02 00:02:11 +01001065 }
Bram Moolenaar196d1572019-01-02 23:47:18 +01001066 mch_memmove(props + wi * sizeof(textprop_T), &tmp_prop,
Bram Moolenaar44746aa2019-01-02 00:02:11 +01001067 sizeof(textprop_T));
Bram Moolenaar196d1572019-01-02 23:47:18 +01001068 ++wi;
1069 }
1070 if (dirty)
1071 {
Bram Moolenaar4614f532019-01-06 12:54:55 +01001072 colnr_T newlen = (int)textlen + wi * (colnr_T)sizeof(textprop_T);
1073
1074 if ((curbuf->b_ml.ml_flags & ML_LINE_DIRTY) == 0)
1075 curbuf->b_ml.ml_line_ptr =
1076 vim_memsave(curbuf->b_ml.ml_line_ptr, newlen);
Bram Moolenaar196d1572019-01-02 23:47:18 +01001077 curbuf->b_ml.ml_flags |= ML_LINE_DIRTY;
Bram Moolenaar4614f532019-01-06 12:54:55 +01001078 curbuf->b_ml.ml_line_len = newlen;
Bram Moolenaar44746aa2019-01-02 00:02:11 +01001079 }
Bram Moolenaar338dfda2019-05-19 15:19:57 +02001080 return dirty;
Bram Moolenaarb9c67a52019-01-01 19:49:20 +01001081}
1082
Bram Moolenaar4164bb22019-01-04 23:09:49 +01001083/*
1084 * Adjust text properties for a line that was split in two.
Bram Moolenaar45dd07f2019-05-15 22:45:37 +02001085 * "lnum_props" is the line that has the properties from before the split.
1086 * "lnum_top" is the top line.
1087 * "kept" is the number of bytes kept in the first line, while
Bram Moolenaar4164bb22019-01-04 23:09:49 +01001088 * "deleted" is the number of bytes deleted.
1089 */
1090 void
Bram Moolenaar45dd07f2019-05-15 22:45:37 +02001091adjust_props_for_split(
1092 linenr_T lnum_props,
1093 linenr_T lnum_top,
1094 int kept,
1095 int deleted)
Bram Moolenaar4164bb22019-01-04 23:09:49 +01001096{
1097 char_u *props;
1098 int count;
1099 garray_T prevprop;
1100 garray_T nextprop;
1101 int i;
1102 int skipped = kept + deleted;
1103
1104 if (!curbuf->b_has_textprop)
1105 return;
Bram Moolenaar45dd07f2019-05-15 22:45:37 +02001106
1107 // Get the text properties from "lnum_props".
1108 count = get_text_props(curbuf, lnum_props, &props, FALSE);
Bram Moolenaar4164bb22019-01-04 23:09:49 +01001109 ga_init2(&prevprop, sizeof(textprop_T), 10);
1110 ga_init2(&nextprop, sizeof(textprop_T), 10);
1111
Bram Moolenaar4164bb22019-01-04 23:09:49 +01001112 // Keep the relevant ones in the first line, reducing the length if needed.
1113 // Copy the ones that include the split to the second line.
1114 // Move the ones after the split to the second line.
1115 for (i = 0; i < count; ++i)
1116 {
1117 textprop_T prop;
1118 textprop_T *p;
1119
1120 // copy the prop to an aligned structure
1121 mch_memmove(&prop, props + i * sizeof(textprop_T), sizeof(textprop_T));
1122
1123 if (prop.tp_col < kept && ga_grow(&prevprop, 1) == OK)
1124 {
1125 p = ((textprop_T *)prevprop.ga_data) + prevprop.ga_len;
1126 *p = prop;
1127 if (p->tp_col + p->tp_len >= kept)
1128 p->tp_len = kept - p->tp_col;
1129 ++prevprop.ga_len;
1130 }
1131
Bram Moolenaar5c65e6a2019-05-17 11:08:56 +02001132 // Only add the property to the next line if the length is bigger than
1133 // zero.
1134 if (prop.tp_col + prop.tp_len > skipped && ga_grow(&nextprop, 1) == OK)
Bram Moolenaar4164bb22019-01-04 23:09:49 +01001135 {
1136 p = ((textprop_T *)nextprop.ga_data) + nextprop.ga_len;
1137 *p = prop;
1138 if (p->tp_col > skipped)
1139 p->tp_col -= skipped - 1;
1140 else
1141 {
1142 p->tp_len -= skipped - p->tp_col;
1143 p->tp_col = 1;
1144 }
1145 ++nextprop.ga_len;
1146 }
1147 }
1148
Bram Moolenaar45dd07f2019-05-15 22:45:37 +02001149 set_text_props(lnum_top, prevprop.ga_data,
1150 prevprop.ga_len * sizeof(textprop_T));
Bram Moolenaar4164bb22019-01-04 23:09:49 +01001151 ga_clear(&prevprop);
Bram Moolenaar45dd07f2019-05-15 22:45:37 +02001152 set_text_props(lnum_top + 1, nextprop.ga_data,
1153 nextprop.ga_len * sizeof(textprop_T));
Bram Moolenaar4164bb22019-01-04 23:09:49 +01001154 ga_clear(&nextprop);
1155}
1156
Bram Moolenaar80e737c2019-05-17 19:56:34 +02001157/*
1158 * Line "lnum" has been joined and will end up at column "col" in the new line.
1159 * "removed" bytes have been removed from the start of the line, properties
1160 * there are to be discarded.
1161 * Move the adjusted text properties to an allocated string, store it in
1162 * "prop_line" and adjust the columns.
1163 */
1164 void
1165adjust_props_for_join(
1166 linenr_T lnum,
1167 textprop_T **prop_line,
1168 int *prop_length,
1169 long col,
1170 int removed)
1171{
1172 int proplen;
1173 char_u *props;
1174 int ri;
1175 int wi = 0;
1176
1177 proplen = get_text_props(curbuf, lnum, &props, FALSE);
1178 if (proplen > 0)
1179 {
1180 *prop_line = (textprop_T *)alloc(proplen * (int)sizeof(textprop_T));
1181 if (*prop_line != NULL)
1182 {
1183 for (ri = 0; ri < proplen; ++ri)
1184 {
1185 textprop_T *cp = *prop_line + wi;
1186
1187 mch_memmove(cp, props + ri * sizeof(textprop_T),
1188 sizeof(textprop_T));
1189 if (cp->tp_col + cp->tp_len > removed)
1190 {
1191 if (cp->tp_col > removed)
1192 cp->tp_col += col;
1193 else
1194 {
1195 // property was partly deleted, make it shorter
1196 cp->tp_len -= removed - cp->tp_col;
1197 cp->tp_col = col;
1198 }
1199 ++wi;
1200 }
1201 }
1202 }
1203 *prop_length = wi;
1204 }
1205}
1206
1207/*
1208 * After joining lines: concatenate the text and the properties of all joined
1209 * lines into one line and replace the line.
1210 */
1211 void
1212join_prop_lines(
1213 linenr_T lnum,
1214 char_u *newp,
1215 textprop_T **prop_lines,
1216 int *prop_lengths,
1217 int count)
1218{
1219 size_t proplen = 0;
1220 size_t oldproplen;
1221 char_u *props;
1222 int i;
Bram Moolenaare2ad8262019-05-24 13:22:22 +02001223 size_t len;
Bram Moolenaar80e737c2019-05-17 19:56:34 +02001224 char_u *line;
1225 size_t l;
1226
1227 for (i = 0; i < count - 1; ++i)
1228 proplen += prop_lengths[i];
1229 if (proplen == 0)
1230 {
1231 ml_replace(lnum, newp, FALSE);
1232 return;
1233 }
1234
1235 // get existing properties of the joined line
1236 oldproplen = get_text_props(curbuf, lnum, &props, FALSE);
1237
Bram Moolenaare2ad8262019-05-24 13:22:22 +02001238 len = STRLEN(newp) + 1;
Bram Moolenaar51e14382019-05-25 20:21:28 +02001239 line = alloc(len + (oldproplen + proplen) * sizeof(textprop_T));
Bram Moolenaar80e737c2019-05-17 19:56:34 +02001240 if (line == NULL)
1241 return;
1242 mch_memmove(line, newp, len);
1243 l = oldproplen * sizeof(textprop_T);
1244 mch_memmove(line + len, props, l);
1245 len += l;
1246
1247 for (i = 0; i < count - 1; ++i)
1248 if (prop_lines[i] != NULL)
1249 {
1250 l = prop_lengths[i] * sizeof(textprop_T);
1251 mch_memmove(line + len, prop_lines[i], l);
1252 len += l;
1253 vim_free(prop_lines[i]);
1254 }
1255
Bram Moolenaare2ad8262019-05-24 13:22:22 +02001256 ml_replace_len(lnum, line, (colnr_T)len, TRUE, FALSE);
Bram Moolenaar80e737c2019-05-17 19:56:34 +02001257 vim_free(newp);
1258 vim_free(prop_lines);
1259 vim_free(prop_lengths);
1260}
1261
Bram Moolenaar98aefe72018-12-13 22:20:09 +01001262#endif // FEAT_TEXT_PROP