comparison src/textprop.c @ 15251:17525ca95e1e v8.1.0634

patch 8.1.0634: text properties cannot cross line boundaries commit https://github.com/vim/vim/commit/e3d31b02a56710e64ef0c1eb6ac5afcc57cb4890 Author: Bram Moolenaar <Bram@vim.org> Date: Mon Dec 24 23:07:04 2018 +0100 patch 8.1.0634: text properties cannot cross line boundaries Problem: Text properties cannot cross line boundaries. Solution: Support multi-line text properties.
author Bram Moolenaar <Bram@vim.org>
date Mon, 24 Dec 2018 23:15:05 +0100
parents de63593896b3
children 19e79a1ed6b6
comparison
equal deleted inserted replaced
15250:82208e2fbdf3 15251:17525ca95e1e
15 * 15 *
16 * Text properties have a user specified ID number, which can be unique. 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. 17 * Text properties have a type, which can be used to specify highlighting.
18 * 18 *
19 * TODO: 19 * TODO:
20 * - 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
20 * - Add an arrray for global_proptypes, to quickly lookup a proptype by ID 24 * - Add an arrray for global_proptypes, to quickly lookup a proptype by ID
21 * - Add an arrray for b_proptypes, to quickly lookup a proptype by ID 25 * - Add an arrray for b_proptypes, to quickly lookup a proptype by ID
22 * - adjust property column when text is inserted/deleted
23 * - support properties that continue over a line break
24 * - add mechanism to keep track of changed lines. 26 * - add mechanism to keep track of changed lines.
25 */ 27 */
26 28
27 #include "vim.h" 29 #include "vim.h"
28 30
45 // The last used text property type ID. 47 // The last used text property type ID.
46 static int proptype_id = 0; 48 static int proptype_id = 0;
47 49
48 static char_u e_type_not_exist[] = N_("E971: Property type %s does not exist"); 50 static char_u e_type_not_exist[] = N_("E971: Property type %s does not exist");
49 static char_u e_invalid_col[] = N_("E964: Invalid column number: %ld"); 51 static char_u e_invalid_col[] = N_("E964: Invalid column number: %ld");
52 static char_u e_invalid_lnum[] = N_("E966: Invalid line number: %ld");
50 53
51 /* 54 /*
52 * Find a property type by name, return the hashitem. 55 * Find a property type by name, return the hashitem.
53 * Returns NULL if the item can't be found. 56 * Returns NULL if the item can't be found.
54 */ 57 */
137 */ 140 */
138 void 141 void
139 f_prop_add(typval_T *argvars, typval_T *rettv UNUSED) 142 f_prop_add(typval_T *argvars, typval_T *rettv UNUSED)
140 { 143 {
141 linenr_T lnum; 144 linenr_T lnum;
142 colnr_T col; 145 linenr_T start_lnum;
146 linenr_T end_lnum;
147 colnr_T start_col;
148 colnr_T end_col;
143 dict_T *dict; 149 dict_T *dict;
144 colnr_T length = 1;
145 char_u *type_name; 150 char_u *type_name;
146 proptype_T *type; 151 proptype_T *type;
147 buf_T *buf = curbuf; 152 buf_T *buf = curbuf;
148 int id = 0; 153 int id = 0;
149 char_u *newtext; 154 char_u *newtext;
152 char_u *props; 157 char_u *props;
153 char_u *newprops; 158 char_u *newprops;
154 textprop_T tmp_prop; 159 textprop_T tmp_prop;
155 int i; 160 int i;
156 161
157 lnum = tv_get_number(&argvars[0]); 162 start_lnum = tv_get_number(&argvars[0]);
158 col = tv_get_number(&argvars[1]); 163 start_col = tv_get_number(&argvars[1]);
159 if (col < 1) 164 if (start_col < 1)
160 { 165 {
161 EMSGN(_(e_invalid_col), (long)col); 166 EMSGN(_(e_invalid_col), (long)start_col);
162 return; 167 return;
163 } 168 }
164 if (argvars[2].v_type != VAR_DICT) 169 if (argvars[2].v_type != VAR_DICT)
165 { 170 {
166 EMSG(_(e_dictreq)); 171 EMSG(_(e_dictreq));
175 } 180 }
176 type_name = dict_get_string(dict, (char_u *)"type", FALSE); 181 type_name = dict_get_string(dict, (char_u *)"type", FALSE);
177 182
178 if (dict_find(dict, (char_u *)"end_lnum", -1) != NULL) 183 if (dict_find(dict, (char_u *)"end_lnum", -1) != NULL)
179 { 184 {
180 // TODO: handle end_lnum 185 end_lnum = dict_get_number(dict, (char_u *)"end_lnum");
181 EMSG("Sorry, end_lnum not supported yet"); 186 if (end_lnum < start_lnum)
182 return; 187 {
183 } 188 EMSG2(_(e_invargval), "end_lnum");
189 return;
190 }
191 }
192 else
193 end_lnum = start_lnum;
184 194
185 if (dict_find(dict, (char_u *)"length", -1) != NULL) 195 if (dict_find(dict, (char_u *)"length", -1) != NULL)
186 length = dict_get_number(dict, (char_u *)"length"); 196 {
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 }
187 else if (dict_find(dict, (char_u *)"end_col", -1) != NULL) 206 else if (dict_find(dict, (char_u *)"end_col", -1) != NULL)
188 { 207 {
189 length = dict_get_number(dict, (char_u *)"end_col") - col; 208 end_col = dict_get_number(dict, (char_u *)"end_col");
190 if (length <= 0) 209 if (end_col <= 0)
191 { 210 {
192 EMSG2(_(e_invargval), "end_col"); 211 EMSG2(_(e_invargval), "end_col");
193 return; 212 return;
194 } 213 }
195 } 214 }
215 else if (start_lnum == end_lnum)
216 end_col = start_col;
217 else
218 end_col = 1;
196 219
197 if (dict_find(dict, (char_u *)"id", -1) != NULL) 220 if (dict_find(dict, (char_u *)"id", -1) != NULL)
198 id = dict_get_number(dict, (char_u *)"id"); 221 id = dict_get_number(dict, (char_u *)"id");
199 222
200 if (get_bufnr_from_arg(&argvars[2], &buf) == FAIL) 223 if (get_bufnr_from_arg(&argvars[2], &buf) == FAIL)
202 225
203 type = lookup_prop_type(type_name, buf); 226 type = lookup_prop_type(type_name, buf);
204 if (type == NULL) 227 if (type == NULL)
205 return; 228 return;
206 229
207 if (lnum < 1 || lnum > buf->b_ml.ml_line_count) 230 if (start_lnum < 1 || start_lnum > buf->b_ml.ml_line_count)
208 { 231 {
209 EMSGN(_("E966: Invalid line number: %ld"), (long)lnum); 232 EMSGN(_(e_invalid_lnum), (long)start_lnum);
210 return; 233 return;
211 } 234 }
212 235 if (end_lnum < start_lnum || end_lnum > buf->b_ml.ml_line_count)
213 // Fetch the line to get the ml_line_len field updated. 236 {
214 proplen = get_text_props(buf, lnum, &props, TRUE); 237 EMSGN(_(e_invalid_lnum), (long)end_lnum);
215 textlen = buf->b_ml.ml_line_len - proplen * sizeof(textprop_T); 238 return;
216 239 }
217 if (col >= (colnr_T)textlen - 1) 240
218 { 241 for (lnum = start_lnum; lnum <= end_lnum; ++lnum)
219 EMSGN(_(e_invalid_col), (long)col); 242 {
220 return; 243 colnr_T col; // start column
221 } 244 long length; // in bytes
222 245
223 // Allocate the new line with space for the new proprety. 246 // Fetch the line to get the ml_line_len field updated.
224 newtext = alloc(buf->b_ml.ml_line_len + sizeof(textprop_T)); 247 proplen = get_text_props(buf, lnum, &props, TRUE);
225 if (newtext == NULL) 248 textlen = buf->b_ml.ml_line_len - proplen * sizeof(textprop_T);
226 return; 249
227 // Copy the text, including terminating NUL. 250 if (lnum == start_lnum)
228 mch_memmove(newtext, buf->b_ml.ml_line_ptr, textlen); 251 col = start_col;
229 252 else
230 // Find the index where to insert the new property. 253 col = 1;
231 // Since the text properties are not aligned properly when stored with the 254 if (col - 1 > (colnr_T)textlen)
232 // text, we need to copy them as bytes before using it as a struct. 255 {
233 for (i = 0; i < proplen; ++i) 256 EMSGN(_(e_invalid_col), (long)start_col);
234 { 257 return;
235 mch_memmove(&tmp_prop, props + i * sizeof(textprop_T), 258 }
236 sizeof(textprop_T)); 259
237 if (tmp_prop.tp_col >= col) 260 if (lnum == end_lnum)
238 break; 261 length = end_col - col + 1;
239 } 262 else
240 newprops = newtext + textlen; 263 length = textlen - col + 1;
241 if (i > 0) 264 if (length > textlen)
242 mch_memmove(newprops, props, sizeof(textprop_T) * i); 265 length = textlen; // can include the end-of-line
243 266 if (length < 1)
244 tmp_prop.tp_col = col; 267 length = 1;
245 tmp_prop.tp_len = length; 268
246 tmp_prop.tp_id = id; 269 // Allocate the new line with space for the new proprety.
247 tmp_prop.tp_type = type->pt_id; 270 newtext = alloc(buf->b_ml.ml_line_len + sizeof(textprop_T));
248 tmp_prop.tp_flags = 0; 271 if (newtext == NULL)
249 mch_memmove(newprops + i * sizeof(textprop_T), &tmp_prop, 272 return;
250 sizeof(textprop_T)); 273 // Copy the text, including terminating NUL.
251 274 mch_memmove(newtext, buf->b_ml.ml_line_ptr, textlen);
252 if (i < proplen) 275
253 mch_memmove(newprops + (i + 1) * sizeof(textprop_T), 276 // Find the index where to insert the new property.
254 props + i * sizeof(textprop_T), 277 // Since the text properties are not aligned properly when stored with the
255 sizeof(textprop_T) * (proplen - i)); 278 // text, we need to copy them as bytes before using it as a struct.
256 279 for (i = 0; i < proplen; ++i)
257 if (buf->b_ml.ml_flags & ML_LINE_DIRTY) 280 {
258 vim_free(buf->b_ml.ml_line_ptr); 281 mch_memmove(&tmp_prop, props + i * sizeof(textprop_T),
259 buf->b_ml.ml_line_ptr = newtext; 282 sizeof(textprop_T));
260 buf->b_ml.ml_line_len += sizeof(textprop_T); 283 if (tmp_prop.tp_col >= col)
261 buf->b_ml.ml_flags |= ML_LINE_DIRTY; 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;
309 }
262 310
263 redraw_buf_later(buf, NOT_VALID); 311 redraw_buf_later(buf, NOT_VALID);
264 } 312 }
265 313
266 /* 314 /*