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