Mercurial > vim
comparison src/textprop.c @ 20583:d067be761cd7 v8.2.0845
patch 8.2.0845: text properties crossing lines not handled correctly
Commit: https://github.com/vim/vim/commit/87be9be1db6b6d8fb57ef14e05f23a84e5e8bea0
Author: Bram Moolenaar <Bram@vim.org>
Date: Sat May 30 15:32:02 2020 +0200
patch 8.2.0845: text properties crossing lines not handled correctly
Problem: Text properties crossing lines not handled correctly.
Solution: When joining lines merge text properties if possible.
(Axel Forsman, closes #5839, closes #5683)
author | Bram Moolenaar <Bram@vim.org> |
---|---|
date | Sat, 30 May 2020 15:45:04 +0200 |
parents | d2153928b376 |
children | 71a36879ac2a |
comparison
equal
deleted
inserted
replaced
20582:49d2f01322db | 20583:d067be761cd7 |
---|---|
378 if (proplen > 0) | 378 if (proplen > 0) |
379 *props = text + textlen; | 379 *props = text + textlen; |
380 return (int)(proplen / sizeof(textprop_T)); | 380 return (int)(proplen / sizeof(textprop_T)); |
381 } | 381 } |
382 | 382 |
383 /** | |
384 * Return the number of text properties on line "lnum" in the current buffer. | |
385 * When "only_starting" is true only text properties starting in this line will | |
386 * be considered. | |
387 */ | |
388 int | |
389 count_props(linenr_T lnum, int only_starting) | |
390 { | |
391 char_u *props; | |
392 int proplen = get_text_props(curbuf, lnum, &props, 0); | |
393 int result = proplen; | |
394 int i; | |
395 textprop_T prop; | |
396 | |
397 if (only_starting) | |
398 for (i = 0; i < proplen; ++i) | |
399 { | |
400 mch_memmove(&prop, props + i * sizeof(prop), sizeof(prop)); | |
401 if (prop.tp_flags & TP_FLAG_CONT_PREV) | |
402 --result; | |
403 } | |
404 return result; | |
405 } | |
406 | |
383 /* | 407 /* |
384 * Find text property "type_id" in the visible lines of window "wp". | 408 * Find text property "type_id" in the visible lines of window "wp". |
385 * Match "id" when it is > 0. | 409 * Match "id" when it is > 0. |
386 * Returns FAIL when not found. | 410 * Returns FAIL when not found. |
387 */ | 411 */ |
562 { | 586 { |
563 pos_T *cursor = &curwin->w_cursor; | 587 pos_T *cursor = &curwin->w_cursor; |
564 dict_T *dict; | 588 dict_T *dict; |
565 buf_T *buf = curbuf; | 589 buf_T *buf = curbuf; |
566 dictitem_T *di; | 590 dictitem_T *di; |
567 int lnum_start; | 591 int lnum_start; |
568 int start_pos_has_prop = 0; | 592 int start_pos_has_prop = 0; |
569 int seen_end = 0; | 593 int seen_end = 0; |
570 int id = -1; | 594 int id = -1; |
571 int type_id = -1; | 595 int type_id = -1; |
572 int skipstart = 0; | 596 int skipstart = 0; |
573 int lnum = -1; | 597 int lnum = -1; |
574 int col = -1; | 598 int col = -1; |
575 int dir = 1; // 1 = forward, -1 = backward | 599 int dir = 1; // 1 = forward, -1 = backward |
576 | 600 |
577 if (argvars[0].v_type != VAR_DICT || argvars[0].vval.v_dict == NULL) | 601 if (argvars[0].v_type != VAR_DICT || argvars[0].vval.v_dict == NULL) |
578 { | 602 { |
579 emsg(_(e_invarg)); | 603 emsg(_(e_invarg)); |
580 return; | 604 return; |
650 while (1) | 674 while (1) |
651 { | 675 { |
652 char_u *text = ml_get_buf(buf, lnum, FALSE); | 676 char_u *text = ml_get_buf(buf, lnum, FALSE); |
653 size_t textlen = STRLEN(text) + 1; | 677 size_t textlen = STRLEN(text) + 1; |
654 int count = (int)((buf->b_ml.ml_line_len - textlen) | 678 int count = (int)((buf->b_ml.ml_line_len - textlen) |
655 / sizeof(textprop_T)); | 679 / sizeof(textprop_T)); |
656 int i; | 680 int i; |
657 textprop_T prop; | 681 textprop_T prop; |
658 int prop_start; | 682 int prop_start; |
659 int prop_end; | 683 int prop_end; |
660 | 684 |
854 break; | 878 break; |
855 text = ml_get_buf(buf, lnum, FALSE); | 879 text = ml_get_buf(buf, lnum, FALSE); |
856 len = STRLEN(text) + 1; | 880 len = STRLEN(text) + 1; |
857 if ((size_t)buf->b_ml.ml_line_len > len) | 881 if ((size_t)buf->b_ml.ml_line_len > len) |
858 { | 882 { |
859 static textprop_T textprop; // static because of alignment | 883 static textprop_T textprop; // static because of alignment |
860 unsigned idx; | 884 unsigned idx; |
861 | 885 |
862 for (idx = 0; idx < (buf->b_ml.ml_line_len - len) | 886 for (idx = 0; idx < (buf->b_ml.ml_line_len - len) |
863 / sizeof(textprop_T); ++idx) | 887 / sizeof(textprop_T); ++idx) |
864 { | 888 { |
865 char_u *cur_prop = buf->b_ml.ml_line_ptr + len | 889 char_u *cur_prop = buf->b_ml.ml_line_ptr + len |
1210 { | 1234 { |
1211 clear_ht_prop_types(buf->b_proptypes); | 1235 clear_ht_prop_types(buf->b_proptypes); |
1212 buf->b_proptypes = NULL; | 1236 buf->b_proptypes = NULL; |
1213 } | 1237 } |
1214 | 1238 |
1239 // Struct used to return two values from adjust_prop(). | |
1240 typedef struct | |
1241 { | |
1242 int dirty; // if the property was changed | |
1243 int can_drop; // whether after this change, the prop may be removed | |
1244 } adjustres_T; | |
1245 | |
1246 /* | |
1247 * Adjust the property for "added" bytes (can be negative) inserted at "col". | |
1248 * | |
1249 * Note that "col" is zero-based, while tp_col is one-based. | |
1250 * Only for the current buffer. | |
1251 * "flags" can have: | |
1252 * APC_SUBSTITUTE: Text is replaced, not inserted. | |
1253 */ | |
1254 static adjustres_T | |
1255 adjust_prop( | |
1256 textprop_T *prop, | |
1257 colnr_T col, | |
1258 int added, | |
1259 int flags) | |
1260 { | |
1261 proptype_T *pt = text_prop_type_by_id(curbuf, prop->tp_type); | |
1262 int start_incl = (pt != NULL | |
1263 && (pt->pt_flags & PT_FLAG_INS_START_INCL)) | |
1264 || (flags & APC_SUBSTITUTE); | |
1265 int end_incl = (pt != NULL | |
1266 && (pt->pt_flags & PT_FLAG_INS_END_INCL)); | |
1267 // Do not drop zero-width props if they later can increase in | |
1268 // size. | |
1269 int droppable = !(start_incl || end_incl); | |
1270 adjustres_T res = {TRUE, FALSE}; | |
1271 | |
1272 if (added > 0) | |
1273 { | |
1274 if (col + 1 <= prop->tp_col | |
1275 - (start_incl || (prop->tp_len == 0 && end_incl))) | |
1276 // Change is entirely before the text property: Only shift | |
1277 prop->tp_col += added; | |
1278 else if (col + 1 < prop->tp_col + prop->tp_len + end_incl) | |
1279 // Insertion was inside text property | |
1280 prop->tp_len += added; | |
1281 } | |
1282 else if (prop->tp_col > col + 1) | |
1283 { | |
1284 if (prop->tp_col + added < col + 1) | |
1285 { | |
1286 prop->tp_len += (prop->tp_col - 1 - col) + added; | |
1287 prop->tp_col = col + 1; | |
1288 if (prop->tp_len <= 0) | |
1289 { | |
1290 prop->tp_len = 0; | |
1291 res.can_drop = droppable; | |
1292 } | |
1293 } | |
1294 else | |
1295 prop->tp_col += added; | |
1296 } | |
1297 else if (prop->tp_len > 0 && prop->tp_col + prop->tp_len > col) | |
1298 { | |
1299 int after = col - added - (prop->tp_col - 1 + prop->tp_len); | |
1300 | |
1301 prop->tp_len += after > 0 ? added + after : added; | |
1302 res.can_drop = prop->tp_len <= 0 && droppable; | |
1303 } | |
1304 else | |
1305 res.dirty = FALSE; | |
1306 | |
1307 return res; | |
1308 } | |
1309 | |
1215 /* | 1310 /* |
1216 * Adjust the columns of text properties in line "lnum" after position "col" to | 1311 * Adjust the columns of text properties in line "lnum" after position "col" to |
1217 * shift by "bytes_added" (can be negative). | 1312 * shift by "bytes_added" (can be negative). |
1218 * Note that "col" is zero-based, while tp_col is one-based. | 1313 * Note that "col" is zero-based, while tp_col is one-based. |
1219 * Only for the current buffer. | 1314 * Only for the current buffer. |
1230 int bytes_added, | 1325 int bytes_added, |
1231 int flags) | 1326 int flags) |
1232 { | 1327 { |
1233 int proplen; | 1328 int proplen; |
1234 char_u *props; | 1329 char_u *props; |
1235 proptype_T *pt; | |
1236 int dirty = FALSE; | 1330 int dirty = FALSE; |
1237 int ri, wi; | 1331 int ri, wi; |
1238 size_t textlen; | 1332 size_t textlen; |
1239 | 1333 |
1240 if (text_prop_frozen > 0) | 1334 if (text_prop_frozen > 0) |
1247 | 1341 |
1248 wi = 0; // write index | 1342 wi = 0; // write index |
1249 for (ri = 0; ri < proplen; ++ri) | 1343 for (ri = 0; ri < proplen; ++ri) |
1250 { | 1344 { |
1251 textprop_T prop; | 1345 textprop_T prop; |
1252 int start_incl, end_incl; | 1346 adjustres_T res; |
1253 int can_drop; | 1347 |
1254 | 1348 mch_memmove(&prop, props + ri * sizeof(prop), sizeof(prop)); |
1255 mch_memmove(&prop, props + ri * sizeof(textprop_T), sizeof(textprop_T)); | 1349 res = adjust_prop(&prop, col, bytes_added, flags); |
1256 pt = text_prop_type_by_id(curbuf, prop.tp_type); | 1350 if (res.dirty) |
1257 start_incl = (pt != NULL && (pt->pt_flags & PT_FLAG_INS_START_INCL)) | 1351 { |
1258 || (flags & APC_SUBSTITUTE); | |
1259 end_incl = (pt != NULL && (pt->pt_flags & PT_FLAG_INS_END_INCL)); | |
1260 // Do not drop zero-width props if they later can increase in size | |
1261 can_drop = !(start_incl || end_incl); | |
1262 | |
1263 if (bytes_added > 0) | |
1264 { | |
1265 if (col + 1 <= prop.tp_col | |
1266 - (start_incl || (prop.tp_len == 0 && end_incl))) | |
1267 { | |
1268 // Change is entirely before the text property: Only shift | |
1269 prop.tp_col += bytes_added; | |
1270 // Save for undo if requested and not done yet. | |
1271 if ((flags & APC_SAVE_FOR_UNDO) && !dirty) | |
1272 u_savesub(lnum); | |
1273 dirty = TRUE; | |
1274 } | |
1275 else if (col + 1 < prop.tp_col + prop.tp_len + end_incl) | |
1276 { | |
1277 // Insertion was inside text property | |
1278 prop.tp_len += bytes_added; | |
1279 // Save for undo if requested and not done yet. | |
1280 if ((flags & APC_SAVE_FOR_UNDO) && !dirty) | |
1281 u_savesub(lnum); | |
1282 dirty = TRUE; | |
1283 } | |
1284 } | |
1285 else if (prop.tp_col > col + 1) | |
1286 { | |
1287 int len_changed = FALSE; | |
1288 | |
1289 if (prop.tp_col + bytes_added < col + 1) | |
1290 { | |
1291 prop.tp_len += (prop.tp_col - 1 - col) + bytes_added; | |
1292 prop.tp_col = col + 1; | |
1293 len_changed = TRUE; | |
1294 } | |
1295 else | |
1296 prop.tp_col += bytes_added; | |
1297 // Save for undo if requested and not done yet. | 1352 // Save for undo if requested and not done yet. |
1298 if ((flags & APC_SAVE_FOR_UNDO) && !dirty) | 1353 if ((flags & APC_SAVE_FOR_UNDO) && !dirty) |
1299 u_savesub(lnum); | 1354 u_savesub(lnum); |
1300 dirty = TRUE; | 1355 dirty = TRUE; |
1301 if (len_changed && prop.tp_len <= 0) | 1356 } |
1302 { | 1357 if (res.can_drop) |
1303 prop.tp_len = 0; | 1358 continue; // Drop this text property |
1304 if (can_drop) | |
1305 continue; // drop this text property | |
1306 } | |
1307 } | |
1308 else if (prop.tp_len > 0 && prop.tp_col + prop.tp_len > col) | |
1309 { | |
1310 int after = col - bytes_added - (prop.tp_col - 1 + prop.tp_len); | |
1311 | |
1312 if (after > 0) | |
1313 prop.tp_len += bytes_added + after; | |
1314 else | |
1315 prop.tp_len += bytes_added; | |
1316 // Save for undo if requested and not done yet. | |
1317 if ((flags & APC_SAVE_FOR_UNDO) && !dirty) | |
1318 u_savesub(lnum); | |
1319 dirty = TRUE; | |
1320 if (prop.tp_len <= 0 && can_drop) | |
1321 continue; // drop this text property | |
1322 } | |
1323 | |
1324 mch_memmove(props + wi * sizeof(textprop_T), &prop, sizeof(textprop_T)); | 1359 mch_memmove(props + wi * sizeof(textprop_T), &prop, sizeof(textprop_T)); |
1325 ++wi; | 1360 ++wi; |
1326 } | 1361 } |
1327 if (dirty) | 1362 if (dirty) |
1328 { | 1363 { |
1370 // Copy the ones that include the split to the second line. | 1405 // Copy the ones that include the split to the second line. |
1371 // Move the ones after the split to the second line. | 1406 // Move the ones after the split to the second line. |
1372 for (i = 0; i < count; ++i) | 1407 for (i = 0; i < count; ++i) |
1373 { | 1408 { |
1374 textprop_T prop; | 1409 textprop_T prop; |
1375 textprop_T *p; | 1410 proptype_T *pt; |
1411 int start_incl, end_incl; | |
1412 int cont_prev, cont_next; | |
1376 | 1413 |
1377 // copy the prop to an aligned structure | 1414 // copy the prop to an aligned structure |
1378 mch_memmove(&prop, props + i * sizeof(textprop_T), sizeof(textprop_T)); | 1415 mch_memmove(&prop, props + i * sizeof(textprop_T), sizeof(textprop_T)); |
1379 | 1416 |
1380 if (prop.tp_col < kept && ga_grow(&prevprop, 1) == OK) | 1417 pt = text_prop_type_by_id(curbuf, prop.tp_type); |
1381 { | 1418 start_incl = (pt != NULL && (pt->pt_flags & PT_FLAG_INS_START_INCL)); |
1382 p = ((textprop_T *)prevprop.ga_data) + prevprop.ga_len; | 1419 end_incl = (pt != NULL && (pt->pt_flags & PT_FLAG_INS_END_INCL)); |
1420 cont_prev = prop.tp_col + !start_incl <= kept; | |
1421 cont_next = skipped <= prop.tp_col + prop.tp_len - !end_incl; | |
1422 | |
1423 if (cont_prev && ga_grow(&prevprop, 1) == OK) | |
1424 { | |
1425 textprop_T *p = ((textprop_T *)prevprop.ga_data) + prevprop.ga_len; | |
1426 | |
1383 *p = prop; | 1427 *p = prop; |
1428 ++prevprop.ga_len; | |
1384 if (p->tp_col + p->tp_len >= kept) | 1429 if (p->tp_col + p->tp_len >= kept) |
1385 p->tp_len = kept - p->tp_col; | 1430 p->tp_len = kept - p->tp_col; |
1386 ++prevprop.ga_len; | 1431 if (cont_next) |
1432 p->tp_flags |= TP_FLAG_CONT_NEXT; | |
1387 } | 1433 } |
1388 | 1434 |
1389 // Only add the property to the next line if the length is bigger than | 1435 // Only add the property to the next line if the length is bigger than |
1390 // zero. | 1436 // zero. |
1391 if (prop.tp_col + prop.tp_len > skipped && ga_grow(&nextprop, 1) == OK) | 1437 if (cont_next && ga_grow(&nextprop, 1) == OK) |
1392 { | 1438 { |
1393 p = ((textprop_T *)nextprop.ga_data) + nextprop.ga_len; | 1439 textprop_T *p = ((textprop_T *)nextprop.ga_data) + nextprop.ga_len; |
1394 *p = prop; | 1440 *p = prop; |
1441 ++nextprop.ga_len; | |
1395 if (p->tp_col > skipped) | 1442 if (p->tp_col > skipped) |
1396 p->tp_col -= skipped - 1; | 1443 p->tp_col -= skipped - 1; |
1397 else | 1444 else |
1398 { | 1445 { |
1399 p->tp_len -= skipped - p->tp_col; | 1446 p->tp_len -= skipped - p->tp_col; |
1400 p->tp_col = 1; | 1447 p->tp_col = 1; |
1401 } | 1448 } |
1402 ++nextprop.ga_len; | 1449 if (cont_prev) |
1450 p->tp_flags |= TP_FLAG_CONT_PREV; | |
1403 } | 1451 } |
1404 } | 1452 } |
1405 | 1453 |
1406 set_text_props(lnum_top, prevprop.ga_data, | 1454 set_text_props(lnum_top, prevprop.ga_data, |
1407 prevprop.ga_len * sizeof(textprop_T)); | 1455 prevprop.ga_len * sizeof(textprop_T)); |
1410 nextprop.ga_len * sizeof(textprop_T)); | 1458 nextprop.ga_len * sizeof(textprop_T)); |
1411 ga_clear(&nextprop); | 1459 ga_clear(&nextprop); |
1412 } | 1460 } |
1413 | 1461 |
1414 /* | 1462 /* |
1415 * Line "lnum" has been joined and will end up at column "col" in the new line. | 1463 * Prepend properties of joined line "lnum" to "new_props". |
1416 * "removed" bytes have been removed from the start of the line, properties | |
1417 * there are to be discarded. | |
1418 * Move the adjusted text properties to an allocated string, store it in | |
1419 * "prop_line" and adjust the columns. | |
1420 */ | 1464 */ |
1421 void | 1465 void |
1422 adjust_props_for_join( | 1466 prepend_joined_props( |
1467 char_u *new_props, | |
1468 int propcount, | |
1469 int *props_remaining, | |
1423 linenr_T lnum, | 1470 linenr_T lnum, |
1424 textprop_T **prop_line, | 1471 int add_all, |
1425 int *prop_length, | |
1426 long col, | 1472 long col, |
1427 int removed) | 1473 int removed) |
1428 { | 1474 { |
1429 int proplen; | 1475 char_u *props; |
1430 char_u *props; | 1476 int proplen = get_text_props(curbuf, lnum, &props, FALSE); |
1431 int ri; | 1477 int i; |
1432 int wi = 0; | 1478 |
1433 | 1479 for (i = proplen; i-- > 0; ) |
1434 proplen = get_text_props(curbuf, lnum, &props, FALSE); | 1480 { |
1435 if (proplen > 0) | 1481 textprop_T prop; |
1436 { | 1482 int end; |
1437 *prop_line = ALLOC_MULT(textprop_T, proplen); | 1483 |
1438 if (*prop_line != NULL) | 1484 mch_memmove(&prop, props + i * sizeof(prop), sizeof(prop)); |
1439 { | 1485 end = !(prop.tp_flags & TP_FLAG_CONT_NEXT); |
1440 for (ri = 0; ri < proplen; ++ri) | 1486 |
1487 adjust_prop(&prop, 0, -removed, 0); // Remove leading spaces | |
1488 adjust_prop(&prop, -1, col, 0); // Make line start at its final colum | |
1489 | |
1490 if (add_all || end) | |
1491 mch_memmove(new_props + --(*props_remaining) * sizeof(prop), | |
1492 &prop, sizeof(prop)); | |
1493 else | |
1494 { | |
1495 int j; | |
1496 int found = FALSE; | |
1497 | |
1498 // Search for continuing prop. | |
1499 for (j = *props_remaining; j < propcount; ++j) | |
1441 { | 1500 { |
1442 textprop_T *cp = *prop_line + wi; | 1501 textprop_T op; |
1443 | 1502 |
1444 mch_memmove(cp, props + ri * sizeof(textprop_T), | 1503 mch_memmove(&op, new_props + j * sizeof(op), sizeof(op)); |
1445 sizeof(textprop_T)); | 1504 if ((op.tp_flags & TP_FLAG_CONT_PREV) |
1446 if (cp->tp_col + cp->tp_len > removed) | 1505 && op.tp_id == prop.tp_id && op.tp_type == prop.tp_type) |
1447 { | 1506 { |
1448 if (cp->tp_col > removed) | 1507 found = TRUE; |
1449 cp->tp_col += col; | 1508 op.tp_len += op.tp_col - prop.tp_col; |
1450 else | 1509 op.tp_col = prop.tp_col; |
1451 { | 1510 // Start/end is taken care of when deleting joined lines |
1452 // property was partly deleted, make it shorter | 1511 op.tp_flags = prop.tp_flags; |
1453 cp->tp_len -= removed - cp->tp_col; | 1512 mch_memmove(new_props + j * sizeof(op), &op, sizeof(op)); |
1454 cp->tp_col = col; | 1513 break; |
1455 } | |
1456 ++wi; | |
1457 } | 1514 } |
1458 } | 1515 } |
1459 } | 1516 if (!found) |
1460 *prop_length = wi; | 1517 internal_error("text property above joined line not found"); |
1461 } | 1518 } |
1462 } | 1519 } |
1463 | |
1464 /* | |
1465 * After joining lines: concatenate the text and the properties of all joined | |
1466 * lines into one line and replace the line. | |
1467 */ | |
1468 void | |
1469 join_prop_lines( | |
1470 linenr_T lnum, | |
1471 char_u *newp, | |
1472 textprop_T **prop_lines, | |
1473 int *prop_lengths, | |
1474 int count) | |
1475 { | |
1476 size_t proplen = 0; | |
1477 size_t oldproplen; | |
1478 char_u *props; | |
1479 int i; | |
1480 size_t len; | |
1481 char_u *line; | |
1482 size_t l; | |
1483 | |
1484 for (i = 0; i < count - 1; ++i) | |
1485 proplen += prop_lengths[i]; | |
1486 if (proplen == 0) | |
1487 { | |
1488 ml_replace(lnum, newp, FALSE); | |
1489 return; | |
1490 } | |
1491 | |
1492 // get existing properties of the joined line | |
1493 oldproplen = get_text_props(curbuf, lnum, &props, FALSE); | |
1494 | |
1495 len = STRLEN(newp) + 1; | |
1496 line = alloc(len + (oldproplen + proplen) * sizeof(textprop_T)); | |
1497 if (line == NULL) | |
1498 return; | |
1499 mch_memmove(line, newp, len); | |
1500 if (oldproplen > 0) | |
1501 { | |
1502 l = oldproplen * sizeof(textprop_T); | |
1503 mch_memmove(line + len, props, l); | |
1504 len += l; | |
1505 } | |
1506 | |
1507 for (i = 0; i < count - 1; ++i) | |
1508 if (prop_lines[i] != NULL) | |
1509 { | |
1510 l = prop_lengths[i] * sizeof(textprop_T); | |
1511 mch_memmove(line + len, prop_lines[i], l); | |
1512 len += l; | |
1513 vim_free(prop_lines[i]); | |
1514 } | |
1515 | |
1516 ml_replace_len(lnum, line, (colnr_T)len, TRUE, FALSE); | |
1517 vim_free(newp); | |
1518 vim_free(prop_lines); | |
1519 vim_free(prop_lengths); | |
1520 } | 1520 } |
1521 | 1521 |
1522 #endif // FEAT_PROP_POPUP | 1522 #endif // FEAT_PROP_POPUP |