vim-patch:8.2.0844: text properties crossing lines not handled correctly

Problem:    Text properties crossing lines not handled correctly.
Solution:   When saving for undo include an extra line when needed and do not
            adjust properties when undoing. (Axel Forsman, closes vim/vim#5875)

ML_DEL_UNDO, ML_APPEND_UNDO are no-opt because textprop feature is N/A.

a9d4b84d97

Co-authored-by: Bram Moolenaar <Bram@vim.org>
This commit is contained in:
Jan Edmund Lazo
2025-08-16 17:59:58 -04:00
parent 09964b7ef0
commit ed012b817d
3 changed files with 79 additions and 41 deletions

View File

@@ -1973,12 +1973,10 @@ int ml_line_alloced(void)
/// @param lnum append after this line (can be 0) /// @param lnum append after this line (can be 0)
/// @param line text of the new line /// @param line text of the new line
/// @param len length of line, including NUL, or 0 /// @param len length of line, including NUL, or 0
/// @param newfile flag, see above /// @param flags ML_APPEND_ flags
/// @param mark mark the new line
/// ///
/// @return FAIL for failure, OK otherwise /// @return FAIL for failure, OK otherwise
static int ml_append_int(buf_T *buf, linenr_T lnum, char *line, colnr_T len, bool newfile, static int ml_append_int(buf_T *buf, linenr_T lnum, char *line, colnr_T len, int flags)
bool mark)
FUNC_ATTR_NONNULL_ARG(1) FUNC_ATTR_NONNULL_ARG(1)
{ {
// lnum out of range // lnum out of range
@@ -2075,13 +2073,13 @@ static int ml_append_int(buf_T *buf, linenr_T lnum, char *line, colnr_T len, boo
// copy the text into the block // copy the text into the block
memmove((char *)dp + dp->db_index[db_idx + 1], line, (size_t)len); memmove((char *)dp + dp->db_index[db_idx + 1], line, (size_t)len);
if (mark) { if (flags & ML_APPEND_MARK) {
dp->db_index[db_idx + 1] |= DB_MARKED; dp->db_index[db_idx + 1] |= DB_MARKED;
} }
// Mark the block dirty. // Mark the block dirty.
buf->b_ml.ml_flags |= ML_LOCKED_DIRTY; buf->b_ml.ml_flags |= ML_LOCKED_DIRTY;
if (!newfile) { if (!(flags & ML_APPEND_NEW)) {
buf->b_ml.ml_flags |= ML_LOCKED_POS; buf->b_ml.ml_flags |= ML_LOCKED_POS;
} }
} else { // not enough space in data block } else { // not enough space in data block
@@ -2135,7 +2133,7 @@ static int ml_append_int(buf_T *buf, linenr_T lnum, char *line, colnr_T len, boo
} }
int page_count = ((space_needed + (int)HEADER_SIZE) + page_size - 1) / page_size; int page_count = ((space_needed + (int)HEADER_SIZE) + page_size - 1) / page_size;
hp_new = ml_new_data(mfp, newfile, page_count); hp_new = ml_new_data(mfp, flags & ML_APPEND_NEW, page_count);
if (db_idx < 0) { // left block is new if (db_idx < 0) { // left block is new
hp_left = hp_new; hp_left = hp_new;
hp_right = hp; hp_right = hp;
@@ -2159,7 +2157,7 @@ static int ml_append_int(buf_T *buf, linenr_T lnum, char *line, colnr_T len, boo
dp_right->db_txt_start -= (unsigned)len; dp_right->db_txt_start -= (unsigned)len;
dp_right->db_free -= (unsigned)len + (unsigned)INDEX_SIZE; dp_right->db_free -= (unsigned)len + (unsigned)INDEX_SIZE;
dp_right->db_index[0] = dp_right->db_txt_start; dp_right->db_index[0] = dp_right->db_txt_start;
if (mark) { if (flags & ML_APPEND_MARK) {
dp_right->db_index[0] |= DB_MARKED; dp_right->db_index[0] |= DB_MARKED;
} }
@@ -2192,7 +2190,7 @@ static int ml_append_int(buf_T *buf, linenr_T lnum, char *line, colnr_T len, boo
dp_left->db_txt_start -= (unsigned)len; dp_left->db_txt_start -= (unsigned)len;
dp_left->db_free -= (unsigned)len + (unsigned)INDEX_SIZE; dp_left->db_free -= (unsigned)len + (unsigned)INDEX_SIZE;
dp_left->db_index[line_count_left] = dp_left->db_txt_start; dp_left->db_index[line_count_left] = dp_left->db_txt_start;
if (mark) { if (flags & ML_APPEND_MARK) {
dp_left->db_index[line_count_left] |= DB_MARKED; dp_left->db_index[line_count_left] |= DB_MARKED;
} }
memmove((char *)dp_left + dp_left->db_txt_start, memmove((char *)dp_left + dp_left->db_txt_start,
@@ -2221,7 +2219,7 @@ static int ml_append_int(buf_T *buf, linenr_T lnum, char *line, colnr_T len, boo
if (lines_moved || in_left) { if (lines_moved || in_left) {
buf->b_ml.ml_flags |= ML_LOCKED_DIRTY; buf->b_ml.ml_flags |= ML_LOCKED_DIRTY;
} }
if (!newfile && db_idx >= 0 && in_left) { if (!(flags & ML_APPEND_NEW) && db_idx >= 0 && in_left) {
buf->b_ml.ml_flags |= ML_LOCKED_POS; buf->b_ml.ml_flags |= ML_LOCKED_POS;
} }
mf_put(mfp, hp_new, true, false); mf_put(mfp, hp_new, true, false);
@@ -2386,10 +2384,10 @@ static int ml_append_int(buf_T *buf, linenr_T lnum, char *line, colnr_T len, boo
/// @param lnum append after this line (can be 0) /// @param lnum append after this line (can be 0)
/// @param line text of the new line /// @param line text of the new line
/// @param len length of line, including NUL, or 0 /// @param len length of line, including NUL, or 0
/// @param newfile flag, see above /// @param flags ML_APPEND_ flags
/// ///
/// @return FAIL for failure, OK otherwise /// @return FAIL for failure, OK otherwise
static int ml_append_flush(buf_T *buf, linenr_T lnum, char *line, colnr_T len, bool newfile) static int ml_append_flush(buf_T *buf, linenr_T lnum, char *line, colnr_T len, int flags)
FUNC_ATTR_NONNULL_ARG(1) FUNC_ATTR_NONNULL_ARG(1)
{ {
if (lnum > buf->b_ml.ml_line_count) { if (lnum > buf->b_ml.ml_line_count) {
@@ -2400,14 +2398,14 @@ static int ml_append_flush(buf_T *buf, linenr_T lnum, char *line, colnr_T len, b
ml_flush_line(buf, false); ml_flush_line(buf, false);
} }
return ml_append_int(buf, lnum, line, len, newfile, false); return ml_append_int(buf, lnum, line, len, flags);
} }
/// Append a line after lnum (may be 0 to insert a line in front of the file). /// Append a line after lnum (may be 0 to insert a line in front of the file).
/// "line" does not need to be allocated, but can't be another line in a /// "line" does not need to be allocated, but can't be another line in a
/// buffer, unlocking may make it invalid. /// buffer, unlocking may make it invalid.
/// ///
/// newfile: true when starting to edit a new file, meaning that pe_old_lnum /// "newfile": true when starting to edit a new file, meaning that pe_old_lnum
/// will be set for recovery /// will be set for recovery
/// Check: The caller of this function should probably also call /// Check: The caller of this function should probably also call
/// appended_lines(). /// appended_lines().
@@ -2419,13 +2417,24 @@ static int ml_append_flush(buf_T *buf, linenr_T lnum, char *line, colnr_T len, b
/// ///
/// @return FAIL for failure, OK otherwise /// @return FAIL for failure, OK otherwise
int ml_append(linenr_T lnum, char *line, colnr_T len, bool newfile) int ml_append(linenr_T lnum, char *line, colnr_T len, bool newfile)
{
return ml_append_flags(lnum, line, len, newfile ? ML_APPEND_NEW : 0);
}
/// @param lnum append after this line (can be 0)
/// @param line text of the new line
/// @param len length of new line, including nul, or 0
/// @param flags ML_APPEND_ values
///
/// @return FAIL for failure, OK otherwise
int ml_append_flags(linenr_T lnum, char *line, colnr_T len, int flags)
{ {
// When starting up, we might still need to create the memfile // When starting up, we might still need to create the memfile
if (curbuf->b_ml.ml_mfp == NULL && open_buffer(false, NULL, 0) == FAIL) { if (curbuf->b_ml.ml_mfp == NULL && open_buffer(false, NULL, 0) == FAIL) {
return FAIL; return FAIL;
} }
return ml_append_flush(curbuf, lnum, line, len, newfile); return ml_append_flush(curbuf, lnum, line, len, flags);
} }
/// Like ml_append() but for an arbitrary buffer. The buffer must already have /// Like ml_append() but for an arbitrary buffer. The buffer must already have
@@ -2442,7 +2451,7 @@ int ml_append_buf(buf_T *buf, linenr_T lnum, char *line, colnr_T len, bool newfi
return FAIL; return FAIL;
} }
return ml_append_flush(buf, lnum, line, len, newfile); return ml_append_flush(buf, lnum, line, len, newfile ? ML_APPEND_NEW : 0);
} }
void ml_add_deleted_len(char *ptr, ssize_t len) void ml_add_deleted_len(char *ptr, ssize_t len)
@@ -2530,24 +2539,6 @@ int ml_replace_buf(buf_T *buf, linenr_T lnum, char *line, bool copy, bool noallo
return OK; return OK;
} }
/// Delete line `lnum` in the current buffer.
///
/// @note The caller of this function should probably also call
/// deleted_lines() after this.
///
/// @param message Show "--No lines in buffer--" message.
///
/// @return FAIL for failure, OK otherwise
int ml_delete(linenr_T lnum, bool message)
{
ml_flush_line(curbuf, false);
if (lnum < 1 || lnum > curbuf->b_ml.ml_line_count) {
return FAIL;
}
return ml_delete_int(curbuf, lnum, message);
}
/// Delete line `lnum` in buffer /// Delete line `lnum` in buffer
/// ///
/// @note The caller of this function should probably also call changed_lines() after this. /// @note The caller of this function should probably also call changed_lines() after this.
@@ -2556,12 +2547,20 @@ int ml_delete(linenr_T lnum, bool message)
/// ///
/// @return FAIL for failure, OK otherwise /// @return FAIL for failure, OK otherwise
int ml_delete_buf(buf_T *buf, linenr_T lnum, bool message) int ml_delete_buf(buf_T *buf, linenr_T lnum, bool message)
FUNC_ATTR_NONNULL_ALL
{ {
ml_flush_line(buf, false); ml_flush_line(buf, false);
return ml_delete_int(buf, lnum, message); return ml_delete_int(buf, lnum, message);
} }
static int ml_delete_int(buf_T *buf, linenr_T lnum, bool message) /// Delete line `lnum` in the current buffer.
///
/// @param flags ML_DEL_MESSAGE may give a "No lines in buffer" message.
/// ML_DEL_UNDO this is called from undo.
///
/// @return FAIL for failure, OK otherwise
static int ml_delete_int(buf_T *buf, linenr_T lnum, int flags)
FUNC_ATTR_NONNULL_ALL
{ {
if (lowest_marked && lowest_marked > lnum) { if (lowest_marked && lowest_marked > lnum) {
lowest_marked--; lowest_marked--;
@@ -2569,7 +2568,7 @@ static int ml_delete_int(buf_T *buf, linenr_T lnum, bool message)
// If the file becomes empty the last line is replaced by an empty line. // If the file becomes empty the last line is replaced by an empty line.
if (buf->b_ml.ml_line_count == 1) { // file becomes empty if (buf->b_ml.ml_line_count == 1) { // file becomes empty
if (message) { if (flags & ML_DEL_MESSAGE) {
set_keep_msg(_(no_lines_msg), 0); set_keep_msg(_(no_lines_msg), 0);
} }
@@ -2685,6 +2684,32 @@ static int ml_delete_int(buf_T *buf, linenr_T lnum, bool message)
return OK; return OK;
} }
/// Delete line "lnum" in the current buffer.
///
/// @note The caller of this function should probably also call
/// deleted_lines() after this.
///
/// @param message true may give a "No lines in buffer" message.
///
/// @return FAIL for failure, OK otherwise
int ml_delete(linenr_T lnum, bool message)
{
return ml_delete_flags(lnum, message ? ML_DEL_MESSAGE : 0);
}
/// Like ml_delete() but using flags (see ml_delete_int()).
///
/// @return FAIL for failure, OK otherwise
int ml_delete_flags(linenr_T lnum, int flags)
{
ml_flush_line(curbuf, false);
if (lnum < 1 || lnum > curbuf->b_ml.ml_line_count) {
return FAIL;
}
return ml_delete_int(curbuf, lnum, flags);
}
/// set the B_MARKED flag for line 'lnum' /// set the B_MARKED flag for line 'lnum'
void ml_setmarked(linenr_T lnum) void ml_setmarked(linenr_T lnum)
{ {
@@ -2853,9 +2878,9 @@ static void ml_flush_line(buf_T *buf, bool noalloc)
// that has only one line. // that has only one line.
// Don't forget to copy the mark! // Don't forget to copy the mark!
// How about handling errors??? // How about handling errors???
ml_append_int(buf, lnum, new_line, new_len, false, (void)ml_append_int(buf, lnum, new_line, new_len,
(int)(dp->db_index[idx] & DB_MARKED)); (dp->db_index[idx] & DB_MARKED) ? ML_APPEND_MARK : 0);
ml_delete_int(buf, lnum, false); (void)ml_delete_int(buf, lnum, 0);
} }
} }
if (!noalloc) { if (!noalloc) {

View File

@@ -10,3 +10,16 @@
/// LINEEMPTY() - return true if the line is empty /// LINEEMPTY() - return true if the line is empty
#define LINEEMPTY(p) (*ml_get(p) == NUL) #define LINEEMPTY(p) (*ml_get(p) == NUL)
// Values for the flags argument of ml_delete_flags().
enum {
ML_DEL_MESSAGE = 1, // may give a "No lines in buffer" message
// ML_DEL_UNDO = 2, // called from undo
};
// Values for the flags argument of ml_append_int().
enum {
ML_APPEND_NEW = 1, // starting to edit a new file
ML_APPEND_MARK = 2, // mark the new line
// ML_APPEND_UNDO = 4, // called from undo
};

View File

@@ -2341,7 +2341,7 @@ static void u_undoredo(bool undo, bool do_buf_event)
if (curbuf->b_ml.ml_line_count == 1) { if (curbuf->b_ml.ml_line_count == 1) {
empty_buffer = true; empty_buffer = true;
} }
ml_delete(lnum, false); ml_delete(lnum); // ML_DEL_UNDO
} }
} else { } else {
newarray = NULL; newarray = NULL;
@@ -2360,7 +2360,7 @@ static void u_undoredo(bool undo, bool do_buf_event)
if (empty_buffer && lnum == 0) { if (empty_buffer && lnum == 0) {
ml_replace(1, uep->ue_array[i], true); ml_replace(1, uep->ue_array[i], true);
} else { } else {
ml_append(lnum, uep->ue_array[i], 0, false); ml_append_flags(lnum, uep->ue_array[i], 0, 0); // ML_APPEND_UNDO
} }
xfree(uep->ue_array[i]); xfree(uep->ue_array[i]);
} }