aboutsummaryrefslogtreecommitdiff
path: root/src/nvim/change.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/nvim/change.c')
-rw-r--r--src/nvim/change.c414
1 files changed, 352 insertions, 62 deletions
diff --git a/src/nvim/change.c b/src/nvim/change.c
index c52d992fbe..024969415d 100644
--- a/src/nvim/change.c
+++ b/src/nvim/change.c
@@ -19,7 +19,6 @@
#include "nvim/indent_c.h"
#include "nvim/mark.h"
#include "nvim/memline.h"
-#include "nvim/misc1.h"
#include "nvim/move.h"
#include "nvim/option.h"
#include "nvim/plines.h"
@@ -43,7 +42,7 @@
/// Careful: may trigger autocommands that reload the buffer.
void change_warning(buf_T *buf, int col)
{
- static char *w_readonly = N_("W10: Warning: Changing a readonly file");
+ static const char *w_readonly = N_("W10: Warning: Changing a readonly file");
if (buf->b_did_warn == false
&& curbufIsChanged() == 0
@@ -131,7 +130,7 @@ void changed_internal(void)
curbuf->b_changed = true;
curbuf->b_changed_invalid = true;
ml_setflags(curbuf);
- check_status(curbuf);
+ redraw_buf_status_later(curbuf);
redraw_tabline = true;
need_maketitle = true; // set window title later
}
@@ -141,10 +140,6 @@ void changed_internal(void)
/// Careful: may trigger autocommands that reload the buffer.
static void changed_common(linenr_T lnum, colnr_T col, linenr_T lnume, long xtra)
{
- int i;
- pos_T *p;
- int add;
-
// mark the buffer as modified
changed();
@@ -159,13 +154,14 @@ static void changed_common(linenr_T lnum, colnr_T col, linenr_T lnume, long xtra
// Create a new entry if a new undo-able change was started or we
// don't have an entry yet.
if (curbuf->b_new_change || curbuf->b_changelistlen == 0) {
+ int add;
if (curbuf->b_changelistlen == 0) {
add = true;
} else {
// Don't create a new entry when the line number is the same
// as the last one and the column is not too far away. Avoids
// creating many entries for typing "xxxxx".
- p = &curbuf->b_changelist[curbuf->b_changelistlen - 1].mark;
+ pos_T *p = &curbuf->b_changelist[curbuf->b_changelistlen - 1].mark;
if (p->lnum != lnum) {
add = true;
} else {
@@ -224,26 +220,27 @@ static void changed_common(linenr_T lnum, colnr_T col, linenr_T lnume, long xtra
// values for the cursor.
// Update the folds for this window. Can't postpone this, because
// a following operator might work on the whole fold: ">>dd".
- foldUpdate(wp, lnum, lnume + xtra - 1);
+ linenr_T last = lnume + xtra - 1; // last line after the change
+ foldUpdate(wp, lnum, last);
// The change may cause lines above or below the change to become
// included in a fold. Set lnum/lnume to the first/last line that
// might be displayed differently.
// Set w_cline_folded here as an efficient way to update it when
- // inserting lines just above a closed fold. */
+ // inserting lines just above a closed fold.
bool folded = hasFoldingWin(wp, lnum, &lnum, NULL, false, NULL);
if (wp->w_cursor.lnum == lnum) {
wp->w_cline_folded = folded;
}
- folded = hasFoldingWin(wp, lnume, NULL, &lnume, false, NULL);
- if (wp->w_cursor.lnum == lnume) {
+ folded = hasFoldingWin(wp, last, NULL, &last, false, NULL);
+ if (wp->w_cursor.lnum == last) {
wp->w_cline_folded = folded;
}
// If the changed line is in a range of previously folded lines,
// compare with the first line in that range.
if (wp->w_cursor.lnum <= lnum) {
- i = find_wl_entry(wp, lnum);
+ int i = find_wl_entry(wp, lnum);
if (i >= 0 && wp->w_cursor.lnum > wp->w_lines[i].wl_lnum) {
changed_line_abv_curs_win(wp);
}
@@ -264,7 +261,7 @@ static void changed_common(linenr_T lnum, colnr_T col, linenr_T lnume, long xtra
// For entries below the change: Correct the lnums for
// inserted/deleted lines. Makes it possible to stop displaying
// after the change.
- for (i = 0; i < wp->w_lines_valid; i++) {
+ for (int i = 0; i < wp->w_lines_valid; i++) {
if (wp->w_lines[i].wl_valid) {
if (wp->w_lines[i].wl_lnum >= lnum) {
if (wp->w_lines[i].wl_lnum < lnume) {
@@ -289,9 +286,11 @@ static void changed_common(linenr_T lnum, colnr_T col, linenr_T lnume, long xtra
set_topline(wp, wp->w_topline);
}
- // Relative numbering may require updating more.
- if (wp->w_p_rnu) {
- redraw_later(wp, SOME_VALID);
+ // If lines have been added or removed, relative numbering always
+ // requires a redraw.
+ if (wp->w_p_rnu && xtra != 0) {
+ wp->w_last_cursor_lnum_rnu = 0;
+ redraw_later(wp, VALID);
}
// Cursor line highlighting probably need to be updated with
@@ -352,12 +351,10 @@ void changed_bytes(linenr_T lnum, colnr_T col)
// Diff highlighting in other diff windows may need to be updated too.
if (curwin->w_p_diff) {
- linenr_T wlnum;
-
FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
if (wp->w_p_diff && wp != curwin) {
redraw_later(wp, VALID);
- wlnum = diff_lnum_win(lnum, wp);
+ linenr_T wlnum = diff_lnum_win(lnum, wp);
if (wlnum > 0) {
changedOneline(wp->w_buffer, wlnum);
}
@@ -413,7 +410,7 @@ void deleted_lines(linenr_T lnum, long count)
/// be triggered to display the cursor.
void deleted_lines_mark(linenr_T lnum, long count)
{
- // if we deleted the entire buffer, we need to implicity add a new empty line
+ // if we deleted the entire buffer, we need to implicitly add a new empty line
bool made_empty = (count > 0) && curbuf->b_ml.ml_flags & ML_EMPTY;
mark_adjust(lnum, (linenr_T)(lnum + count - 1), (long)MAXLNUM,
@@ -517,7 +514,7 @@ void unchanged(buf_T *buf, int ff, bool always_inc_changedtick)
if (ff) {
save_file_ff(buf);
}
- check_status(buf);
+ redraw_buf_status_later(buf);
redraw_tabline = true;
need_maketitle = true; // set window title later
buf_inc_changedtick(buf);
@@ -625,7 +622,7 @@ void ins_char_bytes(char_u *buf, size_t charlen)
}
}
- char_u *newp = xmalloc((size_t)(linelen + newlen - oldlen));
+ char_u *newp = xmalloc(linelen + newlen - oldlen);
// Copy bytes before the cursor.
if (col > 0) {
@@ -673,21 +670,18 @@ void ins_char_bytes(char_u *buf, size_t charlen)
/// Caller must have prepared for undo.
void ins_str(char_u *s)
{
- char_u *oldp, *newp;
int newlen = (int)STRLEN(s);
- int oldlen;
- colnr_T col;
linenr_T lnum = curwin->w_cursor.lnum;
if (virtual_active() && curwin->w_cursor.coladd > 0) {
coladvance_force(getviscol());
}
- col = curwin->w_cursor.col;
- oldp = ml_get(lnum);
- oldlen = (int)STRLEN(oldp);
+ colnr_T col = curwin->w_cursor.col;
+ char_u *oldp = ml_get(lnum);
+ int oldlen = (int)STRLEN(oldp);
- newp = (char_u *)xmalloc((size_t)oldlen + (size_t)newlen + 1);
+ char_u *newp = (char_u *)xmalloc((size_t)oldlen + (size_t)newlen + 1);
if (col > 0) {
memmove(newp, oldp, (size_t)col);
}
@@ -719,13 +713,9 @@ int del_char(bool fixpos)
int del_chars(long count, int fixpos)
{
int bytes = 0;
- long i;
- char_u *p;
- int l;
-
- p = get_cursor_pos_ptr();
- for (i = 0; i < count && *p != NUL; i++) {
- l = utfc_ptr2len(p);
+ char_u *p = get_cursor_pos_ptr();
+ for (long i = 0; i < count && *p != NUL; i++) {
+ int l = utfc_ptr2len(p);
bytes += l;
p += l;
}
@@ -768,12 +758,11 @@ int del_bytes(colnr_T count, bool fixpos_arg, bool use_delcombine)
if (p_deco && use_delcombine
&& utfc_ptr2len(oldp + col) >= count) {
int cc[MAX_MCO];
- int n;
(void)utfc_ptr2char(oldp + col, cc);
if (cc[0] != NUL) {
// Find the last composing char, there can be several.
- n = col;
+ int n = col;
do {
col = n;
count = utf_ptr2len(oldp + n);
@@ -790,7 +779,7 @@ int del_bytes(colnr_T count, bool fixpos_arg, bool use_delcombine)
// fixpos is true, we don't want to end up positioned at the NUL,
// unless "restart_edit" is set or 'virtualedit' contains "onemore".
if (col > 0 && fixpos && restart_edit == 0
- && (ve_flags & VE_ONEMORE) == 0) {
+ && (get_ve_flags() & VE_ONEMORE) == 0) {
curwin->w_cursor.col--;
curwin->w_cursor.coladd = 0;
curwin->w_cursor.col -= utf_head_off(oldp, oldp + curwin->w_cursor.col);
@@ -828,23 +817,18 @@ int copy_indent(int size, char_u *src)
{
char_u *p = NULL;
char_u *line = NULL;
- char_u *s;
- int todo;
int ind_len;
int line_len = 0;
int tab_pad;
- int ind_done;
- int round;
- int ind_col;
// Round 1: compute the number of characters needed for the indent
// Round 2: copy the characters.
- for (round = 1; round <= 2; round++) {
- todo = size;
+ for (int round = 1; round <= 2; round++) {
+ int todo = size;
ind_len = 0;
- ind_done = 0;
- ind_col = 0;
- s = src;
+ int ind_done = 0;
+ int ind_col = 0;
+ char_u *s = src;
// Count/copy the usable portion of the source line.
while (todo > 0 && ascii_iswhite(*s)) {
@@ -953,11 +937,13 @@ int copy_indent(int size, char_u *src)
///
/// "second_line_indent": indent for after ^^D in Insert mode or if flag
/// OPENLINE_COM_LIST
+/// "did_do_comment" is set to true when intentionally putting the comment
+/// leader in fromt of the new line.
///
/// @param dir FORWARD or BACKWARD
///
/// @return true on success, false on failure
-int open_line(int dir, int flags, int second_line_indent)
+int open_line(int dir, int flags, int second_line_indent, bool *did_do_comment)
{
char_u *next_line = NULL; // copy of the next line
char_u *p_extra = NULL; // what goes to next line
@@ -970,6 +956,7 @@ int open_line(int dir, int flags, int second_line_indent)
bool retval = false; // return value
int extra_len = 0; // length of p_extra string
int lead_len; // length of comment leader
+ int comment_start = 0; // start index of the comment leader
char_u *lead_flags; // position in 'comments' for comment leader
char_u *leader = NULL; // copy of comment leader
char_u *allocated = NULL; // allocated memory
@@ -978,6 +965,7 @@ int open_line(int dir, int flags, int second_line_indent)
pos_T *pos;
bool do_si = (!p_paste && curbuf->b_p_si && !curbuf->b_p_cin
&& *curbuf->b_p_inde == NUL);
+ bool do_cindent;
bool no_si = false; // reset did_si afterwards
int first_char = NUL; // init for GCC
int vreplace_mode;
@@ -1061,7 +1049,6 @@ int open_line(int dir, int flags, int second_line_indent)
if (!trunc_line && do_si && *saved_line != NUL
&& (p_extra == NULL || first_char != '{')) {
char_u *ptr;
- char_u last_char;
old_cursor = curwin->w_cursor;
ptr = saved_line;
@@ -1071,8 +1058,7 @@ int open_line(int dir, int flags, int second_line_indent)
lead_len = 0;
}
if (dir == FORWARD) {
- // Skip preprocessor directives, unless they are
- // recognised as comments.
+ // Skip preprocessor directives, unless they are recognised as comments.
if (lead_len == 0 && ptr[0] == '#') {
while (ptr[0] == '#' && curwin->w_cursor.lnum > 1) {
ptr = ml_get(--curwin->w_cursor.lnum);
@@ -1115,7 +1101,7 @@ int open_line(int dir, int flags, int second_line_indent)
while (p > ptr && ascii_iswhite(*p)) {
p--;
}
- last_char = *p;
+ char_u last_char = *p;
// find the character just before the '{' or ';'
if (last_char == '{' || last_char == ';') {
@@ -1190,11 +1176,30 @@ int open_line(int dir, int flags, int second_line_indent)
did_ai = true;
}
+ // May do indenting after opening a new line.
+ do_cindent = !p_paste && (curbuf->b_p_cin || *curbuf->b_p_inde != NUL)
+ && in_cinkeys(dir == FORWARD ? KEY_OPEN_FORW : KEY_OPEN_BACK,
+ ' ', linewhite(curwin->w_cursor.lnum));
+
// Find out if the current line starts with a comment leader.
// This may then be inserted in front of the new line.
end_comment_pending = NUL;
if (flags & OPENLINE_DO_COM) {
lead_len = get_leader_len(saved_line, &lead_flags, dir == BACKWARD, true);
+ if (lead_len == 0 && curbuf->b_p_cin && do_cindent && dir == FORWARD) {
+ // Check for a line comment after code.
+ comment_start = check_linecomment(saved_line);
+ if (comment_start != MAXCOL) {
+ lead_len = get_leader_len(saved_line + comment_start,
+ &lead_flags, false, true);
+ if (lead_len != 0) {
+ lead_len += comment_start;
+ if (did_do_comment != NULL) {
+ *did_do_comment = true;
+ }
+ }
+ }
+ }
} else {
lead_len = 0;
}
@@ -1350,6 +1355,13 @@ int open_line(int dir, int flags, int second_line_indent)
STRLCPY(leader, saved_line, lead_len + 1);
+ // TODO(vim): handle multi-byte and double width chars
+ for (int li = 0; li < comment_start; li++) {
+ if (!ascii_iswhite(leader[li])) {
+ leader[li] = ' ';
+ }
+ }
+
// Replace leader with lead_repl, right or left adjusted
if (lead_repl != NULL) {
int c = 0;
@@ -1759,13 +1771,7 @@ int open_line(int dir, int flags, int second_line_indent)
ai_col = (colnr_T)getwhitecols_curline();
}
// May do indenting after opening a new line.
- if (!p_paste
- && (curbuf->b_p_cin
- || *curbuf->b_p_inde != NUL
- )
- && in_cinkeys(dir == FORWARD
- ? KEY_OPEN_FORW
- : KEY_OPEN_BACK, ' ', linewhite(curwin->w_cursor.lnum))) {
+ if (do_cindent) {
do_c_expr_indent();
ai_col = (colnr_T)getwhitecols_curline();
}
@@ -1863,3 +1869,287 @@ void del_lines(long nlines, bool undo)
// adjust marks, mark the buffer as changed and prepare for displaying
deleted_lines_mark(first, n);
}
+
+/// Returns the length in bytes of the prefix of the given string which introduces a comment.
+///
+/// If this string is not a comment then 0 is returned.
+/// When "flags" is not NULL, it is set to point to the flags of the recognized comment leader.
+/// "backward" must be true for the "O" command.
+/// If "include_space" is set, include trailing whitespace while calculating the length.
+int get_leader_len(char_u *line, char_u **flags, bool backward, bool include_space)
+{
+ int j;
+ int got_com = false;
+ char_u part_buf[COM_MAX_LEN]; // buffer for one option part
+ char_u *string; // pointer to comment string
+ char_u *list;
+ int middle_match_len = 0;
+ char_u *prev_list;
+ char_u *saved_flags = NULL;
+
+ int result = 0;
+ int i = 0;
+ while (ascii_iswhite(line[i])) { // leading white space is ignored
+ i++;
+ }
+
+ // Repeat to match several nested comment strings.
+ while (line[i] != NUL) {
+ // scan through the 'comments' option for a match
+ int found_one = false;
+ for (list = curbuf->b_p_com; *list;) {
+ // Get one option part into part_buf[]. Advance "list" to next
+ // one. Put "string" at start of string.
+ if (!got_com && flags != NULL) {
+ *flags = list; // remember where flags started
+ }
+ prev_list = list;
+ (void)copy_option_part(&list, part_buf, COM_MAX_LEN, ",");
+ string = vim_strchr(part_buf, ':');
+ if (string == NULL) { // missing ':', ignore this part
+ continue;
+ }
+ *string++ = NUL; // isolate flags from string
+
+ // If we found a middle match previously, use that match when this
+ // is not a middle or end.
+ if (middle_match_len != 0
+ && vim_strchr(part_buf, COM_MIDDLE) == NULL
+ && vim_strchr(part_buf, COM_END) == NULL) {
+ break;
+ }
+
+ // When we already found a nested comment, only accept further
+ // nested comments.
+ if (got_com && vim_strchr(part_buf, COM_NEST) == NULL) {
+ continue;
+ }
+
+ // When 'O' flag present and using "O" command skip this one.
+ if (backward && vim_strchr(part_buf, COM_NOBACK) != NULL) {
+ continue;
+ }
+
+ // Line contents and string must match.
+ // When string starts with white space, must have some white space
+ // (but the amount does not need to match, there might be a mix of
+ // TABs and spaces).
+ if (ascii_iswhite(string[0])) {
+ if (i == 0 || !ascii_iswhite(line[i - 1])) {
+ continue; // missing white space
+ }
+ while (ascii_iswhite(string[0])) {
+ string++;
+ }
+ }
+ for (j = 0; string[j] != NUL && string[j] == line[i + j]; j++) {
+ }
+ if (string[j] != NUL) {
+ continue; // string doesn't match
+ }
+ // When 'b' flag used, there must be white space or an
+ // end-of-line after the string in the line.
+ if (vim_strchr(part_buf, COM_BLANK) != NULL
+ && !ascii_iswhite(line[i + j]) && line[i + j] != NUL) {
+ continue;
+ }
+
+ // We have found a match, stop searching unless this is a middle
+ // comment. The middle comment can be a substring of the end
+ // comment in which case it's better to return the length of the
+ // end comment and its flags. Thus we keep searching with middle
+ // and end matches and use an end match if it matches better.
+ if (vim_strchr(part_buf, COM_MIDDLE) != NULL) {
+ if (middle_match_len == 0) {
+ middle_match_len = j;
+ saved_flags = prev_list;
+ }
+ continue;
+ }
+ if (middle_match_len != 0 && j > middle_match_len) {
+ // Use this match instead of the middle match, since it's a
+ // longer thus better match.
+ middle_match_len = 0;
+ }
+
+ if (middle_match_len == 0) {
+ i += j;
+ }
+ found_one = true;
+ break;
+ }
+
+ if (middle_match_len != 0) {
+ // Use the previously found middle match after failing to find a
+ // match with an end.
+ if (!got_com && flags != NULL) {
+ *flags = saved_flags;
+ }
+ i += middle_match_len;
+ found_one = true;
+ }
+
+ // No match found, stop scanning.
+ if (!found_one) {
+ break;
+ }
+
+ result = i;
+
+ // Include any trailing white space.
+ while (ascii_iswhite(line[i])) {
+ i++;
+ }
+
+ if (include_space) {
+ result = i;
+ }
+
+ // If this comment doesn't nest, stop here.
+ got_com = true;
+ if (vim_strchr(part_buf, COM_NEST) == NULL) {
+ break;
+ }
+ }
+ return result;
+}
+
+/// Return the offset at which the last comment in line starts. If there is no
+/// comment in the whole line, -1 is returned.
+///
+/// When "flags" is not null, it is set to point to the flags describing the
+/// recognized comment leader.
+int get_last_leader_offset(char_u *line, char_u **flags)
+{
+ int result = -1;
+ int j;
+ int lower_check_bound = 0;
+ char_u *string;
+ char_u *com_leader;
+ char_u *com_flags;
+ char_u *list;
+ char_u part_buf[COM_MAX_LEN]; // buffer for one option part
+
+ // Repeat to match several nested comment strings.
+ int i = (int)STRLEN(line);
+ while (--i >= lower_check_bound) {
+ // scan through the 'comments' option for a match
+ int found_one = false;
+ for (list = curbuf->b_p_com; *list;) {
+ char_u *flags_save = list;
+
+ // Get one option part into part_buf[]. Advance list to next one.
+ // put string at start of string.
+ (void)copy_option_part(&list, part_buf, COM_MAX_LEN, ",");
+ string = vim_strchr(part_buf, ':');
+ if (string == NULL) { // If everything is fine, this cannot actually
+ // happen.
+ continue;
+ }
+ *string++ = NUL; // Isolate flags from string.
+ com_leader = string;
+
+ // Line contents and string must match.
+ // When string starts with white space, must have some white space
+ // (but the amount does not need to match, there might be a mix of
+ // TABs and spaces).
+ if (ascii_iswhite(string[0])) {
+ if (i == 0 || !ascii_iswhite(line[i - 1])) {
+ continue;
+ }
+ while (ascii_iswhite(*string)) {
+ string++;
+ }
+ }
+ for (j = 0; string[j] != NUL && string[j] == line[i + j]; j++) {
+ // do nothing
+ }
+ if (string[j] != NUL) {
+ continue;
+ }
+
+ // When 'b' flag used, there must be white space or an
+ // end-of-line after the string in the line.
+ if (vim_strchr(part_buf, COM_BLANK) != NULL
+ && !ascii_iswhite(line[i + j]) && line[i + j] != NUL) {
+ continue;
+ }
+
+ if (vim_strchr(part_buf, COM_MIDDLE) != NULL) {
+ // For a middlepart comment, only consider it to match if
+ // everything before the current position in the line is
+ // whitespace. Otherwise we would think we are inside a
+ // comment if the middle part appears somewhere in the middle
+ // of the line. E.g. for C the "*" appears often.
+ for (j = 0; j <= i && ascii_iswhite(line[j]); j++) {
+ }
+ if (j < i) {
+ continue;
+ }
+ }
+
+ // We have found a match, stop searching.
+ found_one = true;
+
+ if (flags) {
+ *flags = flags_save;
+ }
+ com_flags = flags_save;
+
+ break;
+ }
+
+ if (found_one) {
+ char_u part_buf2[COM_MAX_LEN]; // buffer for one option part
+ int len1, len2, off;
+
+ result = i;
+ // If this comment nests, continue searching.
+ if (vim_strchr(part_buf, COM_NEST) != NULL) {
+ continue;
+ }
+
+ lower_check_bound = i;
+
+ // Let's verify whether the comment leader found is a substring
+ // of other comment leaders. If it is, let's adjust the
+ // lower_check_bound so that we make sure that we have determined
+ // the comment leader correctly.
+
+ while (ascii_iswhite(*com_leader)) {
+ com_leader++;
+ }
+ len1 = (int)STRLEN(com_leader);
+
+ for (list = curbuf->b_p_com; *list;) {
+ char_u *flags_save = list;
+
+ (void)copy_option_part(&list, part_buf2, COM_MAX_LEN, ",");
+ if (flags_save == com_flags) {
+ continue;
+ }
+ string = vim_strchr(part_buf2, ':');
+ string++;
+ while (ascii_iswhite(*string)) {
+ string++;
+ }
+ len2 = (int)STRLEN(string);
+ if (len2 == 0) {
+ continue;
+ }
+
+ // Now we have to verify whether string ends with a substring
+ // beginning the com_leader.
+ for (off = (len2 > i ? i : len2); off > 0 && off + len1 > len2;) {
+ off--;
+ if (!STRNCMP(string + off, com_leader, len2 - off)) {
+ if (i - off < lower_check_bound) {
+ lower_check_bound = i - off;
+ }
+ }
+ }
+ }
+ }
+ }
+ return result;
+}