Mercurial > vim
view src/textformat.c @ 34451:d06ffca91441 v9.1.0142
patch 9.1.0142: getregion() can be improved
Commit: https://github.com/vim/vim/commit/19b718828d8d5fab52d94c6cdba694641879ab38
Author: Shougo Matsushita <Shougo.Matsu@gmail.com>
Date: Wed Feb 28 22:48:12 2024 +0100
patch 9.1.0142: getregion() can be improved
Problem: getregion() can be improved (after v9.1.120)
Solution: change getregion() implementation to use pos as lists and
one optional {opt} dictionary (Shougo Matsushita)
Note: The following is a breaking change!
Currently, the getregion() function (included as of patch v9.1.120) takes
3 arguments: the first 2 arguments are strings, describing a position,
arg3 is the type string.
However, that is slightly inflexible, there is no way to specify
additional arguments. So let's instead change the function signature to:
getregion(pos1, pos2 [, {Dict}]) where both pos1 and pos2 are lists.
This is slightly cleaner, and gives us the flexibility to specify
additional arguments as key/value pairs to the optional Dict arg.
Now it supports the "type" key to specify the selection type
(characterwise, blockwise or linewise) and now in addition one can also
define the selection type, independently of what the 'selection' option
actually is.
Technically, this is a breaking change, but since the getregion()
Vimscript function is still quite new, this should be fine.
closes: #14090
Co-authored-by: zeertzjq <zeertzjq@outlook.com>
Signed-off-by: Shougo Matsushita <Shougo.Matsu@gmail.com>
Signed-off-by: Christian Brabandt <cb@256bit.org>
author | Christian Brabandt <cb@256bit.org> |
---|---|
date | Wed, 28 Feb 2024 23:00:03 +0100 |
parents | 19c88c3d8c43 |
children | 9e093c96dff6 |
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; int first_pass; // Cursor is currently at the end of line. No need to format // if line length is less than textwidth (8 * textwidth for // utf safety) if (curwin->w_cursor.col < 8 * textwidth) { 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; // If startcol is large (a long line), formatting takes too much // time. The algorithm is O(n^2), it walks from the end of the // line to textwidth border every time for each line break. // // Ceil to 8 * textwidth to optimize. curwin->w_cursor.col = startcol < 8 * textwidth ? startcol : 8 * textwidth; foundcol = 0; skip_pos = 0; first_pass = TRUE; // 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 (first_pass && c != NUL) { cc = c; first_pass = FALSE; } 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 (curbuf == cmdwin_buf) 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', 'n' and 'w' 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(); } }