Mercurial > vim
view src/textformat.c @ 33549:bfe07ef45143 v9.0.2022
patch 9.0.2022: getmousepos() returns wrong index for TAB char
Commit: https://github.com/vim/vim/commit/b583eda7031b1f6a3469a2537d0c10ca5fa5568e
Author: zeertzjq <zeertzjq@outlook.com>
Date: Sat Oct 14 11:32:28 2023 +0200
patch 9.0.2022: getmousepos() returns wrong index for TAB char
Problem: When clicking in the middle of a TAB, getmousepos() returns
the column of the next char instead of the TAB.
Solution: Break out of the loop when the vcol to find is inside current
char. Fix invalid memory access when calling virtcol2col() on
an empty line.
closes: #13321
Signed-off-by: Christian Brabandt <cb@256bit.org>
Co-authored-by: zeertzjq <zeertzjq@outlook.com>
author | Christian Brabandt <cb@256bit.org> |
---|---|
date | Sat, 14 Oct 2023 11:45:04 +0200 |
parents | 0d27ddce621d |
children | 0839c759c9d1 |
line wrap: on
line source
/* vi:set ts=8 sts=4 sw=4 noet: * * VIM - Vi IMproved by Bram Moolenaar * * Do ":help uganda" in Vim to read copying and usage conditions. * Do ":help credits" in Vim to see a list of people who contributed. * See README.txt for an overview of the Vim source code. */ /* * textformat.c: text formatting functions */ #include "vim.h" static int did_add_space = FALSE; // auto_format() added an extra space // under the cursor #define WHITECHAR(cc) (VIM_ISWHITE(cc) && (!enc_utf8 || !utf_iscomposing(utf_ptr2char(ml_get_cursor() + 1)))) /* * Return TRUE if format option 'x' is in effect. * Take care of no formatting when 'paste' is set. */ int has_format_option(int x) { if (p_paste) return FALSE; return (vim_strchr(curbuf->b_p_fo, x) != NULL); } /* * Format text at the current insert position. * * If the INSCHAR_COM_LIST flag is present, then the value of second_indent * will be the comment leader length sent to open_line(). */ void internal_format( int textwidth, int second_indent, int flags, int format_only, int c) // character to be inserted (can be NUL) { int cc; int skip_pos; int save_char = NUL; int haveto_redraw = FALSE; int fo_ins_blank = has_format_option(FO_INS_BLANK); int fo_multibyte = has_format_option(FO_MBYTE_BREAK); int fo_rigor_tw = has_format_option(FO_RIGOROUS_TW); int fo_white_par = has_format_option(FO_WHITE_PAR); int first_line = TRUE; colnr_T leader_len; int no_leader = FALSE; int do_comments = (flags & INSCHAR_DO_COM); #ifdef FEAT_LINEBREAK int has_lbr = curwin->w_p_lbr; // make sure win_lbr_chartabsize() counts correctly curwin->w_p_lbr = FALSE; #endif // When 'ai' is off we don't want a space under the cursor to be // deleted. Replace it with an 'x' temporarily. if (!curbuf->b_p_ai && !(State & VREPLACE_FLAG)) { cc = gchar_cursor(); if (VIM_ISWHITE(cc)) { save_char = cc; pchar_cursor('x'); } } // Repeat breaking lines, until the current line is not too long. while (!got_int) { int startcol; // Cursor column at entry int wantcol; // column at textwidth border int foundcol; // column for start of spaces int end_foundcol = 0; // column for start of word colnr_T len; colnr_T virtcol; int orig_col = 0; char_u *saved_text = NULL; colnr_T col; colnr_T end_col; int wcc; // counter for whitespace chars int did_do_comment = FALSE; virtcol = get_nolist_virtcol() + char2cells(c != NUL ? c : gchar_cursor()); if (virtcol <= (colnr_T)textwidth) break; if (no_leader) do_comments = FALSE; else if (!(flags & INSCHAR_FORMAT) && has_format_option(FO_WRAP_COMS)) do_comments = TRUE; // Don't break until after the comment leader if (do_comments) { char_u *line = ml_get_curline(); leader_len = get_leader_len(line, NULL, FALSE, TRUE); if (leader_len == 0 && curbuf->b_p_cin) { int comment_start; // Check for a line comment after code. comment_start = check_linecomment(line); if (comment_start != MAXCOL) { leader_len = get_leader_len( line + comment_start, NULL, FALSE, TRUE); if (leader_len != 0) leader_len += comment_start; } } } else leader_len = 0; // If the line doesn't start with a comment leader, then don't // start one in a following broken line. Avoids that a %word // moved to the start of the next line causes all following lines // to start with %. if (leader_len == 0) no_leader = TRUE; if (!(flags & INSCHAR_FORMAT) && leader_len == 0 && !has_format_option(FO_WRAP)) break; if ((startcol = curwin->w_cursor.col) == 0) break; // find column of textwidth border coladvance((colnr_T)textwidth); wantcol = curwin->w_cursor.col; curwin->w_cursor.col = startcol; foundcol = 0; skip_pos = 0; // Find position to break at. // Stop at first entered white when 'formatoptions' has 'v' while ((!fo_ins_blank && !has_format_option(FO_INS_VI)) || (flags & INSCHAR_FORMAT) || curwin->w_cursor.lnum != Insstart.lnum || curwin->w_cursor.col >= Insstart.col) { if (curwin->w_cursor.col == startcol && c != NUL) cc = c; else cc = gchar_cursor(); if (WHITECHAR(cc)) { // remember position of blank just before text end_col = curwin->w_cursor.col; // find start of sequence of blanks wcc = 0; while (curwin->w_cursor.col > 0 && WHITECHAR(cc)) { dec_cursor(); cc = gchar_cursor(); // Increment count of how many whitespace chars in this // group; we only need to know if it's more than one. if (wcc < 2) wcc++; } if (curwin->w_cursor.col == 0 && WHITECHAR(cc)) break; // only spaces in front of text // Don't break after a period when 'formatoptions' has 'p' and // there are less than two spaces. if (has_format_option(FO_PERIOD_ABBR) && cc == '.' && wcc < 2) continue; // Don't break until after the comment leader if (curwin->w_cursor.col < leader_len) break; if (has_format_option(FO_ONE_LETTER)) { // do not break after one-letter words if (curwin->w_cursor.col == 0) break; // one-letter word at begin // do not break "#a b" when 'tw' is 2 if (curwin->w_cursor.col <= leader_len) break; col = curwin->w_cursor.col; dec_cursor(); cc = gchar_cursor(); if (WHITECHAR(cc)) continue; // one-letter, continue curwin->w_cursor.col = col; } inc_cursor(); end_foundcol = end_col + 1; foundcol = curwin->w_cursor.col; if (curwin->w_cursor.col <= (colnr_T)wantcol) break; } else if ((cc >= 0x100 || !utf_allow_break_before(cc)) && fo_multibyte) { int ncc; int allow_break; // Break after or before a multi-byte character. if (curwin->w_cursor.col != startcol) { // Don't break until after the comment leader if (curwin->w_cursor.col < leader_len) break; col = curwin->w_cursor.col; inc_cursor(); ncc = gchar_cursor(); allow_break = (enc_utf8 && utf_allow_break(cc, ncc)) || enc_dbcs; // If we have already checked this position, skip! if (curwin->w_cursor.col != skip_pos && allow_break) { foundcol = curwin->w_cursor.col; end_foundcol = foundcol; if (curwin->w_cursor.col <= (colnr_T)wantcol) break; } curwin->w_cursor.col = col; } if (curwin->w_cursor.col == 0) break; ncc = cc; col = curwin->w_cursor.col; dec_cursor(); cc = gchar_cursor(); if (WHITECHAR(cc)) continue; // break with space // Don't break until after the comment leader. if (curwin->w_cursor.col < leader_len) break; curwin->w_cursor.col = col; skip_pos = curwin->w_cursor.col; allow_break = (enc_utf8 && utf_allow_break(cc, ncc)) || enc_dbcs; // Must handle this to respect line break prohibition. if (allow_break) { foundcol = curwin->w_cursor.col; end_foundcol = foundcol; } if (curwin->w_cursor.col <= (colnr_T)wantcol) { int ncc_allow_break = (enc_utf8 && utf_allow_break_before(ncc)) || enc_dbcs; if (allow_break) break; if (!ncc_allow_break && !fo_rigor_tw) { // Enable at most 1 punct hang outside of textwidth. if (curwin->w_cursor.col == startcol) { // We are inserting a non-breakable char, postpone // line break check to next insert. end_foundcol = foundcol = 0; break; } // Neither cc nor ncc is NUL if we are here, so // it's safe to inc_cursor. col = curwin->w_cursor.col; inc_cursor(); cc = ncc; ncc = gchar_cursor(); // handle insert ncc = (ncc != NUL) ? ncc : c; allow_break = (enc_utf8 && utf_allow_break(cc, ncc)) || enc_dbcs; if (allow_break) { // Break only when we are not at end of line. end_foundcol = foundcol = ncc == NUL? 0 : curwin->w_cursor.col; break; } curwin->w_cursor.col = col; } } } if (curwin->w_cursor.col == 0) break; dec_cursor(); } if (foundcol == 0) // no spaces, cannot break line { curwin->w_cursor.col = startcol; break; } // Going to break the line, remove any "$" now. undisplay_dollar(); // Offset between cursor position and line break is used by replace // stack functions. MODE_VREPLACE does not use this, and backspaces // over the text instead. if (State & VREPLACE_FLAG) orig_col = startcol; // Will start backspacing from here else replace_offset = startcol - end_foundcol; // adjust startcol for spaces that will be deleted and // characters that will remain on top line curwin->w_cursor.col = foundcol; while ((cc = gchar_cursor(), WHITECHAR(cc)) && (!fo_white_par || curwin->w_cursor.col < startcol)) inc_cursor(); startcol -= curwin->w_cursor.col; if (startcol < 0) startcol = 0; if (State & VREPLACE_FLAG) { // In MODE_VREPLACE state, we will backspace over the text to be // wrapped, so save a copy now to put on the next line. saved_text = vim_strsave(ml_get_cursor()); curwin->w_cursor.col = orig_col; if (saved_text == NULL) break; // Can't do it, out of memory saved_text[startcol] = NUL; // Backspace over characters that will move to the next line if (!fo_white_par) backspace_until_column(foundcol); } else { // put cursor after pos. to break line if (!fo_white_par) curwin->w_cursor.col = foundcol; } // Split the line just before the margin. // Only insert/delete lines, but don't really redraw the window. open_line(FORWARD, OPENLINE_DELSPACES + OPENLINE_MARKFIX + (fo_white_par ? OPENLINE_KEEPTRAIL : 0) + (do_comments ? OPENLINE_DO_COM : 0) + OPENLINE_FORMAT + ((flags & INSCHAR_COM_LIST) ? OPENLINE_COM_LIST : 0) , ((flags & INSCHAR_COM_LIST) ? second_indent : old_indent), &did_do_comment); if (!(flags & INSCHAR_COM_LIST)) old_indent = 0; // If a comment leader was inserted, may also do this on a following // line. if (did_do_comment) no_leader = FALSE; replace_offset = 0; if (first_line) { if (!(flags & INSCHAR_COM_LIST)) { // This section is for auto-wrap of numeric lists. When not // in insert mode (i.e. format_lines()), the INSCHAR_COM_LIST // flag will be set and open_line() will handle it (as seen // above). The code here (and in get_number_indent()) will // recognize comments if needed... if (second_indent < 0 && has_format_option(FO_Q_NUMBER)) second_indent = get_number_indent(curwin->w_cursor.lnum - 1); if (second_indent >= 0) { if (State & VREPLACE_FLAG) change_indent(INDENT_SET, second_indent, FALSE, NUL, TRUE); else if (leader_len > 0 && second_indent - leader_len > 0) { int i; int padding = second_indent - leader_len; // We started at the first_line of a numbered list // that has a comment. the open_line() function has // inserted the proper comment leader and positioned // the cursor at the end of the split line. Now we // add the additional whitespace needed after the // comment leader for the numbered list. for (i = 0; i < padding; i++) ins_str((char_u *)" "); } else { (void)set_indent(second_indent, SIN_CHANGED); } } } first_line = FALSE; } if (State & VREPLACE_FLAG) { // In MODE_VREPLACE state we have backspaced over the text to be // moved, now we re-insert it into the new line. ins_bytes(saved_text); vim_free(saved_text); } else { // Check if cursor is not past the NUL off the line, cindent // may have added or removed indent. curwin->w_cursor.col += startcol; len = (colnr_T)STRLEN(ml_get_curline()); if (curwin->w_cursor.col > len) curwin->w_cursor.col = len; } haveto_redraw = TRUE; set_can_cindent(TRUE); // moved the cursor, don't autoindent or cindent now did_ai = FALSE; did_si = FALSE; can_si = FALSE; can_si_back = FALSE; line_breakcheck(); } if (save_char != NUL) // put back space after cursor pchar_cursor(save_char); #ifdef FEAT_LINEBREAK curwin->w_p_lbr = has_lbr; #endif if (!format_only && haveto_redraw) { update_topline(); redraw_curbuf_later(UPD_VALID); } } /* * Blank lines, and lines containing only the comment leader, are left * untouched by the formatting. The function returns TRUE in this * case. It also returns TRUE when a line starts with the end of a comment * ('e' in comment flags), so that this line is skipped, and not joined to the * previous line. A new paragraph starts after a blank line, or when the * comment leader changes -- webb. */ static int fmt_check_par( linenr_T lnum, int *leader_len, char_u **leader_flags, int do_comments) { char_u *flags = NULL; // init for GCC char_u *ptr; ptr = ml_get(lnum); if (do_comments) *leader_len = get_leader_len(ptr, leader_flags, FALSE, TRUE); else *leader_len = 0; if (*leader_len > 0) { // Search for 'e' flag in comment leader flags. flags = *leader_flags; while (*flags && *flags != ':' && *flags != COM_END) ++flags; } return (*skipwhite(ptr + *leader_len) == NUL || (*leader_len > 0 && *flags == COM_END) || startPS(lnum, NUL, FALSE)); } /* * Return TRUE if line "lnum" ends in a white character. */ static int ends_in_white(linenr_T lnum) { char_u *s = ml_get(lnum); size_t l; if (*s == NUL) return FALSE; // Don't use STRLEN() inside VIM_ISWHITE(), SAS/C complains: "macro // invocation may call function multiple times". l = STRLEN(s) - 1; return VIM_ISWHITE(s[l]); } /* * Return TRUE if the two comment leaders given are the same. "lnum" is * the first line. White-space is ignored. Note that the whole of * 'leader1' must match 'leader2_len' characters from 'leader2' -- webb */ static int same_leader( linenr_T lnum, int leader1_len, char_u *leader1_flags, int leader2_len, char_u *leader2_flags) { int idx1 = 0, idx2 = 0; char_u *p; char_u *line1; char_u *line2; if (leader1_len == 0) return (leader2_len == 0); // If first leader has 'f' flag, the lines can be joined only if the // second line does not have a leader. // If first leader has 'e' flag, the lines can never be joined. // If first leader has 's' flag, the lines can only be joined if there is // some text after it and the second line has the 'm' flag. if (leader1_flags != NULL) { for (p = leader1_flags; *p && *p != ':'; ++p) { if (*p == COM_FIRST) return (leader2_len == 0); if (*p == COM_END) return FALSE; if (*p == COM_START) { int line_len = (int)STRLEN(ml_get(lnum)); if (line_len <= leader1_len) return FALSE; if (leader2_flags == NULL || leader2_len == 0) return FALSE; for (p = leader2_flags; *p && *p != ':'; ++p) if (*p == COM_MIDDLE) return TRUE; return FALSE; } } } // Get current line and next line, compare the leaders. // The first line has to be saved, only one line can be locked at a time. line1 = vim_strsave(ml_get(lnum)); if (line1 != NULL) { for (idx1 = 0; VIM_ISWHITE(line1[idx1]); ++idx1) ; line2 = ml_get(lnum + 1); for (idx2 = 0; idx2 < leader2_len; ++idx2) { if (!VIM_ISWHITE(line2[idx2])) { if (line1[idx1++] != line2[idx2]) break; } else while (VIM_ISWHITE(line1[idx1])) ++idx1; } vim_free(line1); } return (idx2 == leader2_len && idx1 == leader1_len); } /* * Return TRUE when a paragraph starts in line "lnum". Return FALSE when the * previous line is in the same paragraph. Used for auto-formatting. */ static int paragraph_start(linenr_T lnum) { char_u *p; int leader_len = 0; // leader len of current line char_u *leader_flags = NULL; // flags for leader of current line int next_leader_len; // leader len of next line char_u *next_leader_flags; // flags for leader of next line int do_comments; // format comments if (lnum <= 1) return TRUE; // start of the file p = ml_get(lnum - 1); if (*p == NUL) return TRUE; // after empty line do_comments = has_format_option(FO_Q_COMS); if (fmt_check_par(lnum - 1, &leader_len, &leader_flags, do_comments)) return TRUE; // after non-paragraph line if (fmt_check_par(lnum, &next_leader_len, &next_leader_flags, do_comments)) return TRUE; // "lnum" is not a paragraph line if (has_format_option(FO_WHITE_PAR) && !ends_in_white(lnum - 1)) return TRUE; // missing trailing space in previous line. if (has_format_option(FO_Q_NUMBER) && (get_number_indent(lnum) > 0)) return TRUE; // numbered item starts in "lnum". if (!same_leader(lnum - 1, leader_len, leader_flags, next_leader_len, next_leader_flags)) return TRUE; // change of comment leader. return FALSE; } /* * Called after inserting or deleting text: When 'formatoptions' includes the * 'a' flag format from the current line until the end of the paragraph. * Keep the cursor at the same position relative to the text. * The caller must have saved the cursor line for undo, following ones will be * saved here. */ void auto_format( int trailblank, // when TRUE also format with trailing blank int prev_line) // may start in previous line { pos_T pos; colnr_T len; char_u *old; char_u *new, *pnew; int wasatend; int cc; if (!has_format_option(FO_AUTO)) return; pos = curwin->w_cursor; old = ml_get_curline(); // may remove added space check_auto_format(FALSE); // Don't format in Insert mode when the cursor is on a trailing blank, the // user might insert normal text next. Also skip formatting when "1" is // in 'formatoptions' and there is a single character before the cursor. // Otherwise the line would be broken and when typing another non-white // next they are not joined back together. wasatend = (pos.col == (colnr_T)STRLEN(old)); if (*old != NUL && !trailblank && wasatend) { dec_cursor(); cc = gchar_cursor(); if (!WHITECHAR(cc) && curwin->w_cursor.col > 0 && has_format_option(FO_ONE_LETTER)) dec_cursor(); cc = gchar_cursor(); if (WHITECHAR(cc)) { curwin->w_cursor = pos; return; } curwin->w_cursor = pos; } // With the 'c' flag in 'formatoptions' and 't' missing: only format // comments. if (has_format_option(FO_WRAP_COMS) && !has_format_option(FO_WRAP) && get_leader_len(old, NULL, FALSE, TRUE) == 0) return; // May start formatting in a previous line, so that after "x" a word is // moved to the previous line if it fits there now. Only when this is not // the start of a paragraph. if (prev_line && !paragraph_start(curwin->w_cursor.lnum)) { --curwin->w_cursor.lnum; if (u_save_cursor() == FAIL) return; } // Do the formatting and restore the cursor position. "saved_cursor" will // be adjusted for the text formatting. saved_cursor = pos; format_lines((linenr_T)-1, FALSE); curwin->w_cursor = saved_cursor; saved_cursor.lnum = 0; if (curwin->w_cursor.lnum > curbuf->b_ml.ml_line_count) { // "cannot happen" curwin->w_cursor.lnum = curbuf->b_ml.ml_line_count; coladvance((colnr_T)MAXCOL); } else check_cursor_col(); // Insert mode: If the cursor is now after the end of the line while it // previously wasn't, the line was broken. Because of the rule above we // need to add a space when 'w' is in 'formatoptions' to keep a paragraph // formatted. if (!wasatend && has_format_option(FO_WHITE_PAR)) { new = ml_get_curline(); len = (colnr_T)STRLEN(new); if (curwin->w_cursor.col == len) { pnew = vim_strnsave(new, len + 2); pnew[len] = ' '; pnew[len + 1] = NUL; ml_replace(curwin->w_cursor.lnum, pnew, FALSE); // remove the space later did_add_space = TRUE; } else // may remove added space check_auto_format(FALSE); } check_cursor(); } /* * When an extra space was added to continue a paragraph for auto-formatting, * delete it now. The space must be under the cursor, just after the insert * position. */ void check_auto_format( int end_insert) // TRUE when ending Insert mode { int c = ' '; int cc; if (!did_add_space) return; cc = gchar_cursor(); if (!WHITECHAR(cc)) // Somehow the space was removed already. did_add_space = FALSE; else { if (!end_insert) { inc_cursor(); c = gchar_cursor(); dec_cursor(); } if (c != NUL) { // The space is no longer at the end of the line, delete it. del_char(FALSE); did_add_space = FALSE; } } } /* * Find out textwidth to be used for formatting: * if 'textwidth' option is set, use it * else if 'wrapmargin' option is set, use curwin->w_width - 'wrapmargin' * if invalid value, use 0. * Set default to window width (maximum 79) for "gq" operator. */ int comp_textwidth( int ff) // force formatting (for "gq" command) { int textwidth; textwidth = curbuf->b_p_tw; if (textwidth == 0 && curbuf->b_p_wm) { // The width is the window width minus 'wrapmargin' minus all the // things that add to the margin. textwidth = curwin->w_width - curbuf->b_p_wm; if (cmdwin_type != 0) textwidth -= 1; #ifdef FEAT_FOLDING textwidth -= curwin->w_p_fdc; #endif #ifdef FEAT_SIGNS if (signcolumn_on(curwin)) textwidth -= 1; #endif if (curwin->w_p_nu || curwin->w_p_rnu) textwidth -= 8; } if (textwidth < 0) textwidth = 0; if (ff && textwidth == 0) { textwidth = curwin->w_width - 1; if (textwidth > 79) textwidth = 79; } return textwidth; } /* * Implementation of the format operator 'gq'. */ void op_format( oparg_T *oap, int keep_cursor) // keep cursor on same text char { long old_line_count = curbuf->b_ml.ml_line_count; // Place the cursor where the "gq" or "gw" command was given, so that "u" // can put it back there. curwin->w_cursor = oap->cursor_start; if (u_save((linenr_T)(oap->start.lnum - 1), (linenr_T)(oap->end.lnum + 1)) == FAIL) return; curwin->w_cursor = oap->start; if (oap->is_VIsual) // When there is no change: need to remove the Visual selection redraw_curbuf_later(UPD_INVERTED); if ((cmdmod.cmod_flags & CMOD_LOCKMARKS) == 0) // Set '[ mark at the start of the formatted area curbuf->b_op_start = oap->start; // For "gw" remember the cursor position and put it back below (adjusted // for joined and split lines). if (keep_cursor) saved_cursor = oap->cursor_start; format_lines(oap->line_count, keep_cursor); // Leave the cursor at the first non-blank of the last formatted line. // If the cursor was moved one line back (e.g. with "Q}") go to the next // line, so "." will do the next lines. if (oap->end_adjusted && curwin->w_cursor.lnum < curbuf->b_ml.ml_line_count) ++curwin->w_cursor.lnum; beginline(BL_WHITE | BL_FIX); old_line_count = curbuf->b_ml.ml_line_count - old_line_count; msgmore(old_line_count); if ((cmdmod.cmod_flags & CMOD_LOCKMARKS) == 0) // put '] mark on the end of the formatted area curbuf->b_op_end = curwin->w_cursor; if (keep_cursor) { curwin->w_cursor = saved_cursor; saved_cursor.lnum = 0; // formatting may have made the cursor position invalid check_cursor(); } if (oap->is_VIsual) { win_T *wp; FOR_ALL_WINDOWS(wp) { if (wp->w_old_cursor_lnum != 0) { // When lines have been inserted or deleted, adjust the end of // the Visual area to be redrawn. if (wp->w_old_cursor_lnum > wp->w_old_visual_lnum) wp->w_old_cursor_lnum += old_line_count; else wp->w_old_visual_lnum += old_line_count; } } } } #if defined(FEAT_EVAL) || defined(PROTO) /* * Implementation of the format operator 'gq' for when using 'formatexpr'. */ void op_formatexpr(oparg_T *oap) { if (oap->is_VIsual) // When there is no change: need to remove the Visual selection redraw_curbuf_later(UPD_INVERTED); if (fex_format(oap->start.lnum, oap->line_count, NUL) != 0) // As documented: when 'formatexpr' returns non-zero fall back to // internal formatting. op_format(oap, FALSE); } int fex_format( linenr_T lnum, long count, int c) // character to be inserted { int use_sandbox = was_set_insecurely((char_u *)"formatexpr", OPT_LOCAL); int r; char_u *fex; sctx_T save_sctx = current_sctx; // Set v:lnum to the first line number and v:count to the number of lines. // Set v:char to the character to be inserted (can be NUL). set_vim_var_nr(VV_LNUM, lnum); set_vim_var_nr(VV_COUNT, count); set_vim_var_char(c); // Make a copy, the option could be changed while calling it. fex = vim_strsave(curbuf->b_p_fex); if (fex == NULL) return 0; current_sctx = curbuf->b_p_script_ctx[BV_FEX]; // Evaluate the function. if (use_sandbox) ++sandbox; r = (int)eval_to_number(fex, TRUE); if (use_sandbox) --sandbox; set_vim_var_string(VV_CHAR, NULL, -1); vim_free(fex); current_sctx = save_sctx; return r; } #endif /* * Format "line_count" lines, starting at the cursor position. * When "line_count" is negative, format until the end of the paragraph. * Lines after the cursor line are saved for undo, caller must have saved the * first line. */ void format_lines( linenr_T line_count, int avoid_fex) // don't use 'formatexpr' { int max_len; int is_not_par; // current line not part of parag. int next_is_not_par; // next line not part of paragraph int is_end_par; // at end of paragraph int prev_is_end_par = FALSE;// prev. line not part of parag. int next_is_start_par = FALSE; int leader_len = 0; // leader len of current line int next_leader_len; // leader len of next line char_u *leader_flags = NULL; // flags for leader of current line char_u *next_leader_flags = NULL; // flags for leader of next line int do_comments; // format comments int do_comments_list = 0; // format comments with 'n' or '2' int advance = TRUE; int second_indent = -1; // indent for second line (comment // aware) int do_second_indent; int do_number_indent; int do_trail_white; int first_par_line = TRUE; int smd_save; long count; int need_set_indent = TRUE; // set indent of next paragraph linenr_T first_line = curwin->w_cursor.lnum; int force_format = FALSE; int old_State = State; // length of a line to force formatting: 3 * 'tw' max_len = comp_textwidth(TRUE) * 3; // check for 'q', '2' and '1' in 'formatoptions' do_comments = has_format_option(FO_Q_COMS); do_second_indent = has_format_option(FO_Q_SECOND); do_number_indent = has_format_option(FO_Q_NUMBER); do_trail_white = has_format_option(FO_WHITE_PAR); // Get info about the previous and current line. if (curwin->w_cursor.lnum > 1) is_not_par = fmt_check_par(curwin->w_cursor.lnum - 1 , &leader_len, &leader_flags, do_comments); else is_not_par = TRUE; next_is_not_par = fmt_check_par(curwin->w_cursor.lnum , &next_leader_len, &next_leader_flags, do_comments); is_end_par = (is_not_par || next_is_not_par); if (!is_end_par && do_trail_white) is_end_par = !ends_in_white(curwin->w_cursor.lnum - 1); curwin->w_cursor.lnum--; for (count = line_count; count != 0 && !got_int; --count) { // Advance to next paragraph. if (advance) { curwin->w_cursor.lnum++; prev_is_end_par = is_end_par; is_not_par = next_is_not_par; leader_len = next_leader_len; leader_flags = next_leader_flags; } // The last line to be formatted. if (count == 1 || curwin->w_cursor.lnum == curbuf->b_ml.ml_line_count) { next_is_not_par = TRUE; next_leader_len = 0; next_leader_flags = NULL; } else { next_is_not_par = fmt_check_par(curwin->w_cursor.lnum + 1 , &next_leader_len, &next_leader_flags, do_comments); if (do_number_indent) next_is_start_par = (get_number_indent(curwin->w_cursor.lnum + 1) > 0); } advance = TRUE; is_end_par = (is_not_par || next_is_not_par || next_is_start_par); if (!is_end_par && do_trail_white) is_end_par = !ends_in_white(curwin->w_cursor.lnum); // Skip lines that are not in a paragraph. if (is_not_par) { if (line_count < 0) break; } else { // For the first line of a paragraph, check indent of second line. // Don't do this for comments and empty lines. if (first_par_line && (do_second_indent || do_number_indent) && prev_is_end_par && curwin->w_cursor.lnum < curbuf->b_ml.ml_line_count) { if (do_second_indent && !LINEEMPTY(curwin->w_cursor.lnum + 1)) { if (leader_len == 0 && next_leader_len == 0) { // no comment found second_indent = get_indent_lnum(curwin->w_cursor.lnum + 1); } else { second_indent = next_leader_len; do_comments_list = 1; } } else if (do_number_indent) { if (leader_len == 0 && next_leader_len == 0) { // no comment found second_indent = get_number_indent(curwin->w_cursor.lnum); } else { // get_number_indent() is now "comment aware"... second_indent = get_number_indent(curwin->w_cursor.lnum); do_comments_list = 1; } } } // When the comment leader changes, it's the end of the paragraph. if (curwin->w_cursor.lnum >= curbuf->b_ml.ml_line_count || !same_leader(curwin->w_cursor.lnum, leader_len, leader_flags, next_leader_len, next_leader_flags)) { // Special case: If the next line starts with a line comment // and this line has a line comment after some text, the // paragraph doesn't really end. if (next_leader_flags == NULL || STRNCMP(next_leader_flags, "://", 3) != 0 || check_linecomment(ml_get_curline()) == MAXCOL) is_end_par = TRUE; } // If we have got to the end of a paragraph, or the line is // getting long, format it. if (is_end_par || force_format) { if (need_set_indent) { int indent = 0; // amount of indent needed // Replace indent in first line of a paragraph with minimal // number of tabs and spaces, according to current options. // For the very first formatted line keep the current // indent. if (curwin->w_cursor.lnum == first_line) indent = get_indent(); else if (curbuf->b_p_lisp) indent = get_lisp_indent(); else { if (cindent_on()) { indent = # ifdef FEAT_EVAL *curbuf->b_p_inde != NUL ? get_expr_indent() : # endif get_c_indent(); } else indent = get_indent(); } (void)set_indent(indent, SIN_CHANGED); } // put cursor on last non-space State = MODE_NORMAL; // don't go past end-of-line coladvance((colnr_T)MAXCOL); while (curwin->w_cursor.col && vim_isspace(gchar_cursor())) dec_cursor(); // do the formatting, without 'showmode' State = MODE_INSERT; // for open_line() smd_save = p_smd; p_smd = FALSE; insertchar(NUL, INSCHAR_FORMAT + (do_comments ? INSCHAR_DO_COM : 0) + (do_comments && do_comments_list ? INSCHAR_COM_LIST : 0) + (avoid_fex ? INSCHAR_NO_FEX : 0), second_indent); State = old_State; p_smd = smd_save; second_indent = -1; // at end of par.: need to set indent of next par. need_set_indent = is_end_par; if (is_end_par) { // When called with a negative line count, break at the // end of the paragraph. if (line_count < 0) break; first_par_line = TRUE; } force_format = FALSE; } // When still in same paragraph, join the lines together. But // first delete the leader from the second line. if (!is_end_par) { advance = FALSE; curwin->w_cursor.lnum++; curwin->w_cursor.col = 0; if (line_count < 0 && u_save_cursor() == FAIL) break; if (next_leader_len > 0) { (void)del_bytes((long)next_leader_len, FALSE, FALSE); mark_col_adjust(curwin->w_cursor.lnum, (colnr_T)0, 0L, (long)-next_leader_len, 0); } else if (second_indent > 0) // the "leader" for FO_Q_SECOND { int indent = getwhitecols_curline(); if (indent > 0) { (void)del_bytes(indent, FALSE, FALSE); mark_col_adjust(curwin->w_cursor.lnum, (colnr_T)0, 0L, (long)-indent, 0); } } curwin->w_cursor.lnum--; if (do_join(2, TRUE, FALSE, FALSE, FALSE) == FAIL) { beep_flush(); break; } first_par_line = FALSE; // If the line is getting long, format it next time if (STRLEN(ml_get_curline()) > (size_t)max_len) force_format = TRUE; else force_format = FALSE; } } line_breakcheck(); } }