Mercurial > vim
diff 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 |
line wrap: on
line diff
--- a/src/textprop.c +++ b/src/textprop.c @@ -380,6 +380,30 @@ get_text_props(buf_T *buf, linenr_T lnum return (int)(proplen / sizeof(textprop_T)); } +/** + * Return the number of text properties on line "lnum" in the current buffer. + * When "only_starting" is true only text properties starting in this line will + * be considered. + */ + int +count_props(linenr_T lnum, int only_starting) +{ + char_u *props; + int proplen = get_text_props(curbuf, lnum, &props, 0); + int result = proplen; + int i; + textprop_T prop; + + if (only_starting) + for (i = 0; i < proplen; ++i) + { + mch_memmove(&prop, props + i * sizeof(prop), sizeof(prop)); + if (prop.tp_flags & TP_FLAG_CONT_PREV) + --result; + } + return result; +} + /* * Find text property "type_id" in the visible lines of window "wp". * Match "id" when it is > 0. @@ -564,15 +588,15 @@ f_prop_find(typval_T *argvars, typval_T dict_T *dict; buf_T *buf = curbuf; dictitem_T *di; - int lnum_start; - int start_pos_has_prop = 0; - int seen_end = 0; - int id = -1; - int type_id = -1; - int skipstart = 0; - int lnum = -1; - int col = -1; - int dir = 1; // 1 = forward, -1 = backward + int lnum_start; + int start_pos_has_prop = 0; + int seen_end = 0; + int id = -1; + int type_id = -1; + int skipstart = 0; + int lnum = -1; + int col = -1; + int dir = 1; // 1 = forward, -1 = backward if (argvars[0].v_type != VAR_DICT || argvars[0].vval.v_dict == NULL) { @@ -652,7 +676,7 @@ f_prop_find(typval_T *argvars, typval_T char_u *text = ml_get_buf(buf, lnum, FALSE); size_t textlen = STRLEN(text) + 1; int count = (int)((buf->b_ml.ml_line_len - textlen) - / sizeof(textprop_T)); + / sizeof(textprop_T)); int i; textprop_T prop; int prop_start; @@ -856,8 +880,8 @@ f_prop_remove(typval_T *argvars, typval_ len = STRLEN(text) + 1; if ((size_t)buf->b_ml.ml_line_len > len) { - static textprop_T textprop; // static because of alignment - unsigned idx; + static textprop_T textprop; // static because of alignment + unsigned idx; for (idx = 0; idx < (buf->b_ml.ml_line_len - len) / sizeof(textprop_T); ++idx) @@ -1212,6 +1236,77 @@ clear_buf_prop_types(buf_T *buf) buf->b_proptypes = NULL; } +// Struct used to return two values from adjust_prop(). +typedef struct +{ + int dirty; // if the property was changed + int can_drop; // whether after this change, the prop may be removed +} adjustres_T; + +/* + * Adjust the property for "added" bytes (can be negative) inserted at "col". + * + * Note that "col" is zero-based, while tp_col is one-based. + * Only for the current buffer. + * "flags" can have: + * APC_SUBSTITUTE: Text is replaced, not inserted. + */ + static adjustres_T +adjust_prop( + textprop_T *prop, + colnr_T col, + int added, + int flags) +{ + proptype_T *pt = text_prop_type_by_id(curbuf, prop->tp_type); + int start_incl = (pt != NULL + && (pt->pt_flags & PT_FLAG_INS_START_INCL)) + || (flags & APC_SUBSTITUTE); + int end_incl = (pt != NULL + && (pt->pt_flags & PT_FLAG_INS_END_INCL)); + // Do not drop zero-width props if they later can increase in + // size. + int droppable = !(start_incl || end_incl); + adjustres_T res = {TRUE, FALSE}; + + if (added > 0) + { + if (col + 1 <= prop->tp_col + - (start_incl || (prop->tp_len == 0 && end_incl))) + // Change is entirely before the text property: Only shift + prop->tp_col += added; + else if (col + 1 < prop->tp_col + prop->tp_len + end_incl) + // Insertion was inside text property + prop->tp_len += added; + } + else if (prop->tp_col > col + 1) + { + if (prop->tp_col + added < col + 1) + { + prop->tp_len += (prop->tp_col - 1 - col) + added; + prop->tp_col = col + 1; + if (prop->tp_len <= 0) + { + prop->tp_len = 0; + res.can_drop = droppable; + } + } + else + prop->tp_col += added; + } + else if (prop->tp_len > 0 && prop->tp_col + prop->tp_len > col) + { + int after = col - added - (prop->tp_col - 1 + prop->tp_len); + + prop->tp_len += after > 0 ? added + after : added; + res.can_drop = prop->tp_len <= 0 && droppable; + } + else + res.dirty = FALSE; + + return res; +} + /* * Adjust the columns of text properties in line "lnum" after position "col" to * shift by "bytes_added" (can be negative). @@ -1232,7 +1327,6 @@ adjust_prop_columns( { int proplen; char_u *props; - proptype_T *pt; int dirty = FALSE; int ri, wi; size_t textlen; @@ -1249,78 +1343,19 @@ adjust_prop_columns( for (ri = 0; ri < proplen; ++ri) { textprop_T prop; - int start_incl, end_incl; - int can_drop; + adjustres_T res; - mch_memmove(&prop, props + ri * sizeof(textprop_T), sizeof(textprop_T)); - pt = text_prop_type_by_id(curbuf, prop.tp_type); - start_incl = (pt != NULL && (pt->pt_flags & PT_FLAG_INS_START_INCL)) - || (flags & APC_SUBSTITUTE); - end_incl = (pt != NULL && (pt->pt_flags & PT_FLAG_INS_END_INCL)); - // Do not drop zero-width props if they later can increase in size - can_drop = !(start_incl || end_incl); - - if (bytes_added > 0) + mch_memmove(&prop, props + ri * sizeof(prop), sizeof(prop)); + res = adjust_prop(&prop, col, bytes_added, flags); + if (res.dirty) { - if (col + 1 <= prop.tp_col - - (start_incl || (prop.tp_len == 0 && end_incl))) - { - // Change is entirely before the text property: Only shift - prop.tp_col += bytes_added; - // Save for undo if requested and not done yet. - if ((flags & APC_SAVE_FOR_UNDO) && !dirty) - u_savesub(lnum); - dirty = TRUE; - } - else if (col + 1 < prop.tp_col + prop.tp_len + end_incl) - { - // Insertion was inside text property - prop.tp_len += bytes_added; - // Save for undo if requested and not done yet. - if ((flags & APC_SAVE_FOR_UNDO) && !dirty) - u_savesub(lnum); - dirty = TRUE; - } - } - else if (prop.tp_col > col + 1) - { - int len_changed = FALSE; - - if (prop.tp_col + bytes_added < col + 1) - { - prop.tp_len += (prop.tp_col - 1 - col) + bytes_added; - prop.tp_col = col + 1; - len_changed = TRUE; - } - else - prop.tp_col += bytes_added; // Save for undo if requested and not done yet. if ((flags & APC_SAVE_FOR_UNDO) && !dirty) u_savesub(lnum); dirty = TRUE; - if (len_changed && prop.tp_len <= 0) - { - prop.tp_len = 0; - if (can_drop) - continue; // drop this text property - } } - else if (prop.tp_len > 0 && prop.tp_col + prop.tp_len > col) - { - int after = col - bytes_added - (prop.tp_col - 1 + prop.tp_len); - - if (after > 0) - prop.tp_len += bytes_added + after; - else - prop.tp_len += bytes_added; - // Save for undo if requested and not done yet. - if ((flags & APC_SAVE_FOR_UNDO) && !dirty) - u_savesub(lnum); - dirty = TRUE; - if (prop.tp_len <= 0 && can_drop) - continue; // drop this text property - } - + if (res.can_drop) + continue; // Drop this text property mch_memmove(props + wi * sizeof(textprop_T), &prop, sizeof(textprop_T)); ++wi; } @@ -1372,26 +1407,38 @@ adjust_props_for_split( for (i = 0; i < count; ++i) { textprop_T prop; - textprop_T *p; + proptype_T *pt; + int start_incl, end_incl; + int cont_prev, cont_next; // copy the prop to an aligned structure mch_memmove(&prop, props + i * sizeof(textprop_T), sizeof(textprop_T)); - if (prop.tp_col < kept && ga_grow(&prevprop, 1) == OK) + pt = text_prop_type_by_id(curbuf, prop.tp_type); + start_incl = (pt != NULL && (pt->pt_flags & PT_FLAG_INS_START_INCL)); + end_incl = (pt != NULL && (pt->pt_flags & PT_FLAG_INS_END_INCL)); + cont_prev = prop.tp_col + !start_incl <= kept; + cont_next = skipped <= prop.tp_col + prop.tp_len - !end_incl; + + if (cont_prev && ga_grow(&prevprop, 1) == OK) { - p = ((textprop_T *)prevprop.ga_data) + prevprop.ga_len; + textprop_T *p = ((textprop_T *)prevprop.ga_data) + prevprop.ga_len; + *p = prop; + ++prevprop.ga_len; if (p->tp_col + p->tp_len >= kept) p->tp_len = kept - p->tp_col; - ++prevprop.ga_len; + if (cont_next) + p->tp_flags |= TP_FLAG_CONT_NEXT; } // Only add the property to the next line if the length is bigger than // zero. - if (prop.tp_col + prop.tp_len > skipped && ga_grow(&nextprop, 1) == OK) + if (cont_next && ga_grow(&nextprop, 1) == OK) { - p = ((textprop_T *)nextprop.ga_data) + nextprop.ga_len; + textprop_T *p = ((textprop_T *)nextprop.ga_data) + nextprop.ga_len; *p = prop; + ++nextprop.ga_len; if (p->tp_col > skipped) p->tp_col -= skipped - 1; else @@ -1399,7 +1446,8 @@ adjust_props_for_split( p->tp_len -= skipped - p->tp_col; p->tp_col = 1; } - ++nextprop.ga_len; + if (cont_prev) + p->tp_flags |= TP_FLAG_CONT_PREV; } } @@ -1412,111 +1460,63 @@ adjust_props_for_split( } /* - * Line "lnum" has been joined and will end up at column "col" in the new line. - * "removed" bytes have been removed from the start of the line, properties - * there are to be discarded. - * Move the adjusted text properties to an allocated string, store it in - * "prop_line" and adjust the columns. + * Prepend properties of joined line "lnum" to "new_props". */ void -adjust_props_for_join( +prepend_joined_props( + char_u *new_props, + int propcount, + int *props_remaining, linenr_T lnum, - textprop_T **prop_line, - int *prop_length, + int add_all, long col, int removed) { - int proplen; - char_u *props; - int ri; - int wi = 0; + char_u *props; + int proplen = get_text_props(curbuf, lnum, &props, FALSE); + int i; + + for (i = proplen; i-- > 0; ) + { + textprop_T prop; + int end; + + mch_memmove(&prop, props + i * sizeof(prop), sizeof(prop)); + end = !(prop.tp_flags & TP_FLAG_CONT_NEXT); - proplen = get_text_props(curbuf, lnum, &props, FALSE); - if (proplen > 0) - { - *prop_line = ALLOC_MULT(textprop_T, proplen); - if (*prop_line != NULL) + adjust_prop(&prop, 0, -removed, 0); // Remove leading spaces + adjust_prop(&prop, -1, col, 0); // Make line start at its final colum + + if (add_all || end) + mch_memmove(new_props + --(*props_remaining) * sizeof(prop), + &prop, sizeof(prop)); + else { - for (ri = 0; ri < proplen; ++ri) + int j; + int found = FALSE; + + // Search for continuing prop. + for (j = *props_remaining; j < propcount; ++j) { - textprop_T *cp = *prop_line + wi; + textprop_T op; - mch_memmove(cp, props + ri * sizeof(textprop_T), - sizeof(textprop_T)); - if (cp->tp_col + cp->tp_len > removed) + mch_memmove(&op, new_props + j * sizeof(op), sizeof(op)); + if ((op.tp_flags & TP_FLAG_CONT_PREV) + && op.tp_id == prop.tp_id && op.tp_type == prop.tp_type) { - if (cp->tp_col > removed) - cp->tp_col += col; - else - { - // property was partly deleted, make it shorter - cp->tp_len -= removed - cp->tp_col; - cp->tp_col = col; - } - ++wi; + found = TRUE; + op.tp_len += op.tp_col - prop.tp_col; + op.tp_col = prop.tp_col; + // Start/end is taken care of when deleting joined lines + op.tp_flags = prop.tp_flags; + mch_memmove(new_props + j * sizeof(op), &op, sizeof(op)); + break; } } + if (!found) + internal_error("text property above joined line not found"); } - *prop_length = wi; } } -/* - * After joining lines: concatenate the text and the properties of all joined - * lines into one line and replace the line. - */ - void -join_prop_lines( - linenr_T lnum, - char_u *newp, - textprop_T **prop_lines, - int *prop_lengths, - int count) -{ - size_t proplen = 0; - size_t oldproplen; - char_u *props; - int i; - size_t len; - char_u *line; - size_t l; - - for (i = 0; i < count - 1; ++i) - proplen += prop_lengths[i]; - if (proplen == 0) - { - ml_replace(lnum, newp, FALSE); - return; - } - - // get existing properties of the joined line - oldproplen = get_text_props(curbuf, lnum, &props, FALSE); - - len = STRLEN(newp) + 1; - line = alloc(len + (oldproplen + proplen) * sizeof(textprop_T)); - if (line == NULL) - return; - mch_memmove(line, newp, len); - if (oldproplen > 0) - { - l = oldproplen * sizeof(textprop_T); - mch_memmove(line + len, props, l); - len += l; - } - - for (i = 0; i < count - 1; ++i) - if (prop_lines[i] != NULL) - { - l = prop_lengths[i] * sizeof(textprop_T); - mch_memmove(line + len, prop_lines[i], l); - len += l; - vim_free(prop_lines[i]); - } - - ml_replace_len(lnum, line, (colnr_T)len, TRUE, FALSE); - vim_free(newp); - vim_free(prop_lines); - vim_free(prop_lengths); -} - #endif // FEAT_PROP_POPUP