Mercurial > vim
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 /* |