To: vim_dev@googlegroups.com Subject: Patch 8.2.0844 Fcc: outbox From: Bram Moolenaar Mime-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ------------ Patch 8.2.0844 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 #5875) Files: src/memline.c, src/proto/memline.pro, src/undo.c, src/structs.h *** ../vim-8.2.0843/src/memline.c 2020-02-14 13:21:55.646197062 +0100 --- src/memline.c 2020-05-30 14:21:38.307387444 +0200 *************** *** 243,249 **** static void add_b0_fenc(ZERO_BL *b0p, buf_T *buf); static time_t swapfile_info(char_u *); static int recov_file_names(char_u **, char_u *, int prepend_dot); - static int ml_delete_int(buf_T *, linenr_T, int); static char_u *findswapname(buf_T *, char_u **, char_u *); static void ml_flush_line(buf_T *); static bhdr_T *ml_new_data(memfile_T *, int, int); --- 243,248 ---- *************** *** 2618,2623 **** --- 2617,2625 ---- } #ifdef FEAT_PROP_POPUP + /* + * Add text properties that continue from the previous line. + */ static void add_text_props_for_append( buf_T *buf, *************** *** 2657,2671 **** count = get_text_props(buf, lnum, &props, FALSE); for (n = 0; n < count; ++n) { ! mch_memmove(&prop, props + n * sizeof(textprop_T), sizeof(textprop_T)); if (prop.tp_flags & TP_FLAG_CONT_NEXT) { if (round == 2) { prop.tp_flags |= TP_FLAG_CONT_PREV; prop.tp_col = 1; ! prop.tp_len = *len; ! mch_memmove(new_line + *len + new_prop_count * sizeof(textprop_T), &prop, sizeof(textprop_T)); } ++new_prop_count; } --- 2659,2675 ---- count = get_text_props(buf, lnum, &props, FALSE); for (n = 0; n < count; ++n) { ! mch_memmove(&prop, props + n * sizeof(textprop_T), ! sizeof(textprop_T)); if (prop.tp_flags & TP_FLAG_CONT_NEXT) { if (round == 2) { prop.tp_flags |= TP_FLAG_CONT_PREV; prop.tp_col = 1; ! prop.tp_len = *len; // not exactly the right length ! mch_memmove(new_line + *len + new_prop_count ! * sizeof(textprop_T), &prop, sizeof(textprop_T)); } ++new_prop_count; } *************** *** 2683,2690 **** linenr_T lnum, // append after this line (can be 0) char_u *line_arg, // text of the new line colnr_T len_arg, // length of line, including NUL, or 0 ! int newfile, // flag, see above ! int mark) // mark the new line { char_u *line = line_arg; colnr_T len = len_arg; --- 2687,2693 ---- linenr_T lnum, // append after this line (can be 0) char_u *line_arg, // text of the new line colnr_T len_arg, // length of line, including NUL, or 0 ! int flags) // ML_APPEND_ flags { char_u *line = line_arg; colnr_T len = len_arg; *************** *** 2716,2722 **** len = (colnr_T)STRLEN(line) + 1; // space needed for the text #ifdef FEAT_PROP_POPUP ! if (curbuf->b_has_textprop && lnum > 0) // Add text properties that continue from the previous line. add_text_props_for_append(buf, lnum, &line, &len, &tofree); #endif --- 2719,2725 ---- len = (colnr_T)STRLEN(line) + 1; // space needed for the text #ifdef FEAT_PROP_POPUP ! if (curbuf->b_has_textprop && lnum > 0 && !(flags & ML_APPEND_UNDO)) // Add text properties that continue from the previous line. add_text_props_for_append(buf, lnum, &line, &len, &tofree); #endif *************** *** 2815,2828 **** * copy the text into the block */ mch_memmove((char *)dp + dp->db_index[db_idx + 1], line, (size_t)len); ! if (mark) dp->db_index[db_idx + 1] |= DB_MARKED; /* * Mark the block dirty. */ buf->b_ml.ml_flags |= ML_LOCKED_DIRTY; ! if (!newfile) buf->b_ml.ml_flags |= ML_LOCKED_POS; } else // not enough space in data block --- 2818,2831 ---- * copy the text into the block */ mch_memmove((char *)dp + dp->db_index[db_idx + 1], line, (size_t)len); ! if (flags & ML_APPEND_MARK) dp->db_index[db_idx + 1] |= DB_MARKED; /* * Mark the block dirty. */ buf->b_ml.ml_flags |= ML_LOCKED_DIRTY; ! if (!(flags & ML_APPEND_NEW)) buf->b_ml.ml_flags |= ML_LOCKED_POS; } else // not enough space in data block *************** *** 2891,2897 **** } page_count = ((space_needed + HEADER_SIZE) + page_size - 1) / page_size; ! if ((hp_new = ml_new_data(mfp, newfile, page_count)) == NULL) { // correct line counts in pointer blocks --(buf->b_ml.ml_locked_lineadd); --- 2894,2901 ---- } page_count = ((space_needed + HEADER_SIZE) + page_size - 1) / page_size; ! if ((hp_new = ml_new_data(mfp, flags & ML_APPEND_NEW, page_count)) ! == NULL) { // correct line counts in pointer blocks --(buf->b_ml.ml_locked_lineadd); *************** *** 2927,2933 **** dp_right->db_txt_start -= len; dp_right->db_free -= len + INDEX_SIZE; dp_right->db_index[0] = dp_right->db_txt_start; ! if (mark) dp_right->db_index[0] |= DB_MARKED; mch_memmove((char *)dp_right + dp_right->db_txt_start, --- 2931,2937 ---- dp_right->db_txt_start -= len; dp_right->db_free -= len + INDEX_SIZE; dp_right->db_index[0] = dp_right->db_txt_start; ! if (flags & ML_APPEND_MARK) dp_right->db_index[0] |= DB_MARKED; mch_memmove((char *)dp_right + dp_right->db_txt_start, *************** *** 2968,2974 **** dp_left->db_txt_start -= len; dp_left->db_free -= len + INDEX_SIZE; dp_left->db_index[line_count_left] = dp_left->db_txt_start; ! if (mark) dp_left->db_index[line_count_left] |= DB_MARKED; mch_memmove((char *)dp_left + dp_left->db_txt_start, line, (size_t)len); --- 2972,2978 ---- dp_left->db_txt_start -= len; dp_left->db_free -= len + INDEX_SIZE; dp_left->db_index[line_count_left] = dp_left->db_txt_start; ! if (flags & ML_APPEND_MARK) dp_left->db_index[line_count_left] |= DB_MARKED; mch_memmove((char *)dp_left + dp_left->db_txt_start, line, (size_t)len); *************** *** 2999,3005 **** */ if (lines_moved || in_left) buf->b_ml.ml_flags |= ML_LOCKED_DIRTY; ! if (!newfile && db_idx >= 0 && in_left) buf->b_ml.ml_flags |= ML_LOCKED_POS; mf_put(mfp, hp_new, TRUE, FALSE); --- 3003,3009 ---- */ if (lines_moved || in_left) buf->b_ml.ml_flags |= ML_LOCKED_DIRTY; ! if (!(flags & ML_APPEND_NEW) && db_idx >= 0 && in_left) buf->b_ml.ml_flags |= ML_LOCKED_POS; mf_put(mfp, hp_new, TRUE, FALSE); *************** *** 3207,3213 **** linenr_T lnum, // append after this line (can be 0) char_u *line, // text of the new line colnr_T len, // length of line, including NUL, or 0 ! int newfile) // flag, see above { if (lnum > buf->b_ml.ml_line_count) return FAIL; // lnum out of range --- 3211,3217 ---- linenr_T lnum, // append after this line (can be 0) char_u *line, // text of the new line colnr_T len, // length of line, including NUL, or 0 ! int flags) // ML_APPEND_ flags { if (lnum > buf->b_ml.ml_line_count) return FAIL; // lnum out of range *************** *** 3224,3230 **** ml_flush_line(buf); #endif ! return ml_append_int(buf, lnum, line, len, newfile, FALSE); } /* --- 3228,3234 ---- ml_flush_line(buf); #endif ! return ml_append_int(buf, lnum, line, len, flags); } /* *************** *** 3232,3238 **** * "line" does not need to be allocated, but can't be another line in a * buffer, unlocking may make it invalid. * ! * newfile: TRUE when starting to edit a new file, meaning that pe_old_lnum * will be set for recovery * Check: The caller of this function should probably also call * appended_lines(). --- 3236,3242 ---- * "line" does not need to be allocated, but can't be another line in a * buffer, unlocking may make it invalid. * ! * "newfile": TRUE when starting to edit a new file, meaning that pe_old_lnum * will be set for recovery * Check: The caller of this function should probably also call * appended_lines(). *************** *** 3246,3257 **** colnr_T len, // length of new line, including NUL, or 0 int newfile) // flag, see above { // When starting up, we might still need to create the memfile if (curbuf->b_ml.ml_mfp == NULL && open_buffer(FALSE, NULL, 0) == FAIL) return FAIL; ! return ml_append_flush(curbuf, lnum, line, len, newfile); } #if defined(FEAT_SPELL) || defined(FEAT_QUICKFIX) || defined(PROTO) /* * Like ml_append() but for an arbitrary buffer. The buffer must already have --- 3250,3272 ---- colnr_T len, // length of new line, including NUL, or 0 int newfile) // flag, see above { + return ml_append_flags(lnum, line, len, newfile ? ML_APPEND_NEW : 0); + } + + int + ml_append_flags( + linenr_T lnum, // append after this line (can be 0) + char_u *line, // text of the new line + colnr_T len, // length of new line, including NUL, or 0 + int flags) // ML_APPEND_ values + { // When starting up, we might still need to create the memfile if (curbuf->b_ml.ml_mfp == NULL && open_buffer(FALSE, NULL, 0) == FAIL) return FAIL; ! return ml_append_flush(curbuf, lnum, line, len, flags); } + #if defined(FEAT_SPELL) || defined(FEAT_QUICKFIX) || defined(PROTO) /* * Like ml_append() but for an arbitrary buffer. The buffer must already have *************** *** 3267,3273 **** { if (buf->b_ml.ml_mfp == NULL) return FAIL; ! return ml_append_flush(buf, lnum, line, len, newfile); } #endif --- 3282,3288 ---- { if (buf->b_ml.ml_mfp == NULL) return FAIL; ! return ml_append_flush(buf, lnum, line, len, newfile ? ML_APPEND_NEW : 0); } #endif *************** *** 3487,3517 **** /* * Delete line "lnum" in the current buffer. ! * When "message" is TRUE may give a "No lines in buffer" message. ! * ! * Check: The caller of this function should probably also call ! * deleted_lines() after this. * * return FAIL for failure, OK otherwise */ - int - ml_delete(linenr_T lnum, int message) - { - ml_flush_line(curbuf); - if (lnum < 1 || lnum > curbuf->b_ml.ml_line_count) - return FAIL; - - #ifdef FEAT_EVAL - // When inserting above recorded changes: flush the changes before changing - // the text. - may_invoke_listeners(curbuf, lnum, lnum + 1, -1); - #endif - - return ml_delete_int(curbuf, lnum, message); - } - static int ! ml_delete_int(buf_T *buf, linenr_T lnum, int message) { bhdr_T *hp; memfile_T *mfp; --- 3502,3514 ---- /* * Delete line "lnum" in the current buffer. ! * When "flags" has ML_DEL_MESSAGE may give a "No lines in buffer" message. ! * When "flags" has 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) { bhdr_T *hp; memfile_T *mfp; *************** *** 3539,3545 **** */ if (buf->b_ml.ml_line_count == 1) // file becomes empty { ! if (message #ifdef FEAT_NETBEANS_INTG && !netbeansSuppressNoLines #endif --- 3536,3542 ---- */ if (buf->b_ml.ml_line_count == 1) // file becomes empty { ! if ((flags & ML_DEL_MESSAGE) #ifdef FEAT_NETBEANS_INTG && !netbeansSuppressNoLines #endif *************** *** 3586,3592 **** #ifdef FEAT_PROP_POPUP // If there are text properties, make a copy, so that we can update // properties in preceding and following lines. ! if (buf->b_has_textprop) { size_t textlen = STRLEN((char_u *)dp + line_start) + 1; --- 3583,3589 ---- #ifdef FEAT_PROP_POPUP // If there are text properties, make a copy, so that we can update // properties in preceding and following lines. ! if (buf->b_has_textprop && !(flags & ML_DEL_UNDO)) { size_t textlen = STRLEN((char_u *)dp + line_start) + 1; *************** *** 3699,3704 **** --- 3696,3735 ---- } /* + * Delete line "lnum" in the current buffer. + * When "message" is TRUE may give a "No lines in buffer" message. + * + * Check: The caller of this function should probably also call + * deleted_lines() after this. + * + * return FAIL for failure, OK otherwise + */ + int + ml_delete(linenr_T lnum, int message) + { + return ml_delete_flags(lnum, message ? ML_DEL_MESSAGE : 0); + } + + /* + * Like ml_delete() but using flags (see ml_delete_int()). + */ + int + ml_delete_flags(linenr_T lnum, int flags) + { + ml_flush_line(curbuf); + if (lnum < 1 || lnum > curbuf->b_ml.ml_line_count) + return FAIL; + + #ifdef FEAT_EVAL + // When inserting above recorded changes: flush the changes before changing + // the text. + may_invoke_listeners(curbuf, lnum, lnum + 1, -1); + #endif + + return ml_delete_int(curbuf, lnum, flags); + } + + /* * set the DB_MARKED flag for line 'lnum' */ void *************** *** 3905,3913 **** * Don't forget to copy the mark! */ // How about handling errors??? ! (void)ml_append_int(buf, lnum, new_line, new_len, FALSE, ! (dp->db_index[idx] & DB_MARKED)); ! (void)ml_delete_int(buf, lnum, FALSE); } } vim_free(new_line); --- 3936,3944 ---- * Don't forget to copy the mark! */ // How about handling errors??? ! (void)ml_append_int(buf, lnum, new_line, new_len, ! (dp->db_index[idx] & DB_MARKED) ? ML_APPEND_MARK : 0); ! (void)ml_delete_int(buf, lnum, 0); } } vim_free(new_line); *** ../vim-8.2.0843/src/proto/memline.pro 2020-02-14 13:21:55.646197062 +0100 --- src/proto/memline.pro 2020-05-30 14:26:25.222250641 +0200 *************** *** 22,31 **** --- 22,33 ---- char_u *ml_get_buf(buf_T *buf, linenr_T lnum, int will_change); int ml_line_alloced(void); int ml_append(linenr_T lnum, char_u *line, colnr_T len, int newfile); + int ml_append_flags(linenr_T lnum, char_u *line, colnr_T len, int flags); int ml_append_buf(buf_T *buf, linenr_T lnum, char_u *line, colnr_T len, int newfile); int ml_replace(linenr_T lnum, char_u *line, int copy); int ml_replace_len(linenr_T lnum, char_u *line_arg, colnr_T len_arg, int has_props, int copy); int ml_delete(linenr_T lnum, int message); + int ml_delete_flags(linenr_T lnum, int flags); void ml_setmarked(linenr_T lnum); linenr_T ml_firstmarked(void); void ml_clearmarked(void); *** ../vim-8.2.0843/src/undo.c 2020-04-30 22:29:36.626024141 +0200 --- src/undo.c 2020-05-30 14:26:17.670280596 +0200 *************** *** 376,381 **** --- 376,402 ---- } /* + * return TRUE if line "lnum" has text property "flags". + */ + static int + has_prop_w_flags(linenr_T lnum, int flags) + { + char_u *props; + int i; + int proplen = get_text_props(curbuf, lnum, &props, FALSE); + + for (i = 0; i < proplen; ++i) + { + textprop_T prop; + + mch_memmove(&prop, props + i * sizeof prop, sizeof prop); + if (prop.tp_flags & flags) + return TRUE; + } + return FALSE; + } + + /* * Common code for various ways to save text before a change. * "top" is the line above the first changed line. * "bot" is the line below the last changed line. *************** *** 450,455 **** --- 471,493 ---- u_check(FALSE); #endif + #ifdef FEAT_PROP_POPUP + // Include the line above if a text property continues from it. + // Include the line below if a text property continues to it. + if (bot - top > 1) + { + if (top > 0 && has_prop_w_flags(top + 1, TP_FLAG_CONT_PREV)) + --top; + if (bot <= curbuf->b_ml.ml_line_count + && has_prop_w_flags(bot - 1, TP_FLAG_CONT_NEXT)) + { + ++bot; + if (newbot != 0) + ++newbot; + } + } + #endif + size = bot - top - 1; /* *************** *** 2745,2751 **** // dummy empty line will be inserted if (curbuf->b_ml.ml_line_count == 1) empty_buffer = TRUE; ! ml_delete(lnum, FALSE); } } else --- 2783,2789 ---- // dummy empty line will be inserted if (curbuf->b_ml.ml_line_count == 1) empty_buffer = TRUE; ! ml_delete_flags(lnum, ML_DEL_UNDO); } } else *************** *** 2767,2774 **** ml_replace_len((linenr_T)1, uep->ue_array[i].ul_line, uep->ue_array[i].ul_len, TRUE, TRUE); else ! ml_append(lnum, uep->ue_array[i].ul_line, ! (colnr_T)uep->ue_array[i].ul_len, FALSE); vim_free(uep->ue_array[i].ul_line); } vim_free((char_u *)uep->ue_array); --- 2805,2812 ---- ml_replace_len((linenr_T)1, uep->ue_array[i].ul_line, uep->ue_array[i].ul_len, TRUE, TRUE); else ! ml_append_flags(lnum, uep->ue_array[i].ul_line, ! (colnr_T)uep->ue_array[i].ul_len, ML_APPEND_UNDO); vim_free(uep->ue_array[i].ul_line); } vim_free((char_u *)uep->ue_array); *** ../vim-8.2.0843/src/structs.h 2020-05-25 22:36:46.629735032 +0200 --- src/structs.h 2020-05-30 14:19:49.331818479 +0200 *************** *** 742,747 **** --- 742,756 ---- #endif } memline_T; + // Values for the flags argument of ml_delete_flags(). + #define ML_DEL_MESSAGE 1 // may give a "No lines in buffer" message + #define ML_DEL_UNDO 2 // called from undo, do not update textprops + + // Values for the flags argument of ml_append_int(). + #define ML_APPEND_NEW 1 // starting to edit a new file + #define ML_APPEND_MARK 2 // mark the new line + #define ML_APPEND_UNDO 4 // called from undo + /* * Structure defining text properties. These stick with the text. *** ../vim-8.2.0843/src/version.c 2020-05-30 13:15:11.098875009 +0200 --- src/version.c 2020-05-30 14:46:21.329275749 +0200 *************** *** 748,749 **** --- 748,751 ---- { /* Add new patch number below this line */ + /**/ + 844, /**/ -- hundred-and-one symptoms of being an internet addict: 214. Your MCI "Circle of Friends" are all Hayes-compatible. /// Bram Moolenaar -- Bram@Moolenaar.net -- http://www.Moolenaar.net \\\ /// sponsor Vim, vote for features -- http://www.Vim.org/sponsor/ \\\ \\\ an exciting new programming language -- http://www.Zimbu.org /// \\\ help me help AIDS victims -- http://ICCF-Holland.org ///