diff src/textformat.c @ 20237:918245588b50 v8.2.0674

patch 8.2.0674: some source files are too big Commit: https://github.com/vim/vim/commit/11abd095210fc84e5dcee87b9baed86061caefe4 Author: Bram Moolenaar <Bram@vim.org> Date: Fri May 1 14:26:37 2020 +0200 patch 8.2.0674: some source files are too big Problem: Some source files are too big. Solution: Move text formatting functions to a new file. (Yegappan Lakshmanan, closes #6021)
author Bram Moolenaar <Bram@vim.org>
date Fri, 01 May 2020 14:30:04 +0200
parents
children cea8ae407452
line wrap: on
line diff
new file mode 100644
--- /dev/null
+++ b/src/textformat.c
@@ -0,0 +1,1088 @@
+/* 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		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_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
+
+	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)
+	    leader_len = get_leader_len(ml_get_curline(), NULL, FALSE, TRUE);
+	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;
+
+	// 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 && fo_multibyte)
+	    {
+		// 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();
+		    // Don't change end_foundcol if already set.
+		    if (foundcol != curwin->w_cursor.col)
+		    {
+			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;
+
+		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;
+
+		foundcol = curwin->w_cursor.col;
+		end_foundcol = foundcol;
+		if (curwin->w_cursor.col <= (colnr_T)wantcol)
+		    break;
+	    }
+	    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.  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 VREPLACE mode, 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)
+		+ ((flags & INSCHAR_COM_LIST) ? OPENLINE_COM_LIST : 0)
+		, ((flags & INSCHAR_COM_LIST) ? second_indent : old_indent));
+	if (!(flags & INSCHAR_COM_LIST))
+	    old_indent = 0;
+
+	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 VREPLACE mode 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;
+#ifdef FEAT_CINDENT
+	set_can_cindent(TRUE);
+#endif
+	// moved the cursor, don't autoindent or cindent now
+	did_ai = FALSE;
+#ifdef FEAT_SMARTINDENT
+	did_si = FALSE;
+	can_si = FALSE;
+	can_si_back = FALSE;
+#endif
+	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(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 fist 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)
+	    {
+		if (*(ml_get(lnum) + leader1_len) == NUL)
+		    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)
+    {
+	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;
+#ifdef FEAT_CMDWIN
+	if (cmdwin_type != 0)
+	    textwidth -= 1;
+#endif
+#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(INVERTED);
+
+    if (!cmdmod.lockmarks)
+	// 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.lockmarks)
+	// 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;
+    }
+
+    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(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;
+
+    // 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;
+
+    // Evaluate the function.
+    if (use_sandbox)
+	++sandbox;
+    r = (int)eval_to_number(fex);
+    if (use_sandbox)
+	--sandbox;
+
+    set_vim_var_string(VV_CHAR, NULL, -1);
+    vim_free(fex);
+
+    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;	// 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
+    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))
+		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)
+		    // replace indent in first line with minimal number of
+		    // tabs and spaces, according to current options
+		    (void)set_indent(get_indent(), SIN_CHANGED);
+
+		// put cursor on last non-space
+		State = 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 = 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();
+    }
+}