mirror of
https://github.com/neovim/neovim.git
synced 2026-03-09 09:33:51 +10:00
Allow Include What You Use to remove unnecessary includes and only include what is necessary. This helps with reducing compilation times and makes it easier to visualise which dependencies are actually required. Work on https://github.com/neovim/neovim/issues/549, but doesn't close it since this only works fully for .c files and not headers.
1138 lines
36 KiB
C
1138 lines
36 KiB
C
// This is an open source non-commercial project. Dear PVS-Studio, please check
|
|
// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
|
|
|
|
// textformat.c: text formatting functions
|
|
|
|
#include <stdbool.h>
|
|
#include <stdint.h>
|
|
#include <string.h>
|
|
|
|
#include "nvim/ascii.h"
|
|
#include "nvim/buffer_defs.h"
|
|
#include "nvim/change.h"
|
|
#include "nvim/charset.h"
|
|
#include "nvim/cursor.h"
|
|
#include "nvim/drawscreen.h"
|
|
#include "nvim/edit.h"
|
|
#include "nvim/eval.h"
|
|
#include "nvim/eval/typval_defs.h"
|
|
#include "nvim/ex_cmds_defs.h"
|
|
#include "nvim/getchar.h"
|
|
#include "nvim/globals.h"
|
|
#include "nvim/indent.h"
|
|
#include "nvim/indent_c.h"
|
|
#include "nvim/macros.h"
|
|
#include "nvim/mark.h"
|
|
#include "nvim/mbyte.h"
|
|
#include "nvim/memline.h"
|
|
#include "nvim/memory.h"
|
|
#include "nvim/message.h"
|
|
#include "nvim/move.h"
|
|
#include "nvim/normal.h"
|
|
#include "nvim/ops.h"
|
|
#include "nvim/option.h"
|
|
#include "nvim/os/input.h"
|
|
#include "nvim/pos.h"
|
|
#include "nvim/search.h"
|
|
#include "nvim/strings.h"
|
|
#include "nvim/textformat.h"
|
|
#include "nvim/textobject.h"
|
|
#include "nvim/types.h"
|
|
#include "nvim/undo.h"
|
|
#include "nvim/vim.h"
|
|
#include "nvim/window.h"
|
|
|
|
#ifdef INCLUDE_GENERATED_DECLARATIONS
|
|
# include "textformat.c.generated.h"
|
|
#endif
|
|
|
|
static bool did_add_space = false; ///< auto_format() added an extra space
|
|
///< under the cursor
|
|
|
|
#define WHITECHAR(cc) (ascii_iswhite(cc) \
|
|
&& !utf_iscomposing(utf_ptr2char((char *)get_cursor_pos_ptr() + 1)))
|
|
|
|
/// Return true if format option 'x' is in effect.
|
|
/// Take care of no formatting when 'paste' is set.
|
|
bool has_format_option(int x)
|
|
FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
|
|
{
|
|
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().
|
|
///
|
|
/// @param c character to be inserted (can be NUL)
|
|
void internal_format(int textwidth, int second_indent, int flags, bool format_only, int c)
|
|
{
|
|
int cc;
|
|
int save_char = NUL;
|
|
bool haveto_redraw = false;
|
|
const bool fo_ins_blank = has_format_option(FO_INS_BLANK);
|
|
const bool fo_multibyte = has_format_option(FO_MBYTE_BREAK);
|
|
const bool fo_rigor_tw = has_format_option(FO_RIGOROUS_TW);
|
|
const bool fo_white_par = has_format_option(FO_WHITE_PAR);
|
|
bool first_line = true;
|
|
colnr_T leader_len;
|
|
bool no_leader = false;
|
|
int do_comments = (flags & INSCHAR_DO_COM);
|
|
int has_lbr = curwin->w_p_lbr;
|
|
|
|
// make sure win_lbr_chartabsize() counts correctly
|
|
curwin->w_p_lbr = false;
|
|
|
|
// 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 (ascii_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 *saved_text = NULL;
|
|
colnr_T col;
|
|
colnr_T end_col;
|
|
bool 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 = (char_u *)get_cursor_line_ptr();
|
|
leader_len = get_leader_len((char *)line, NULL, false, true);
|
|
if (leader_len == 0 && curbuf->b_p_cin) {
|
|
// Check for a line comment after code.
|
|
int comment_start = check_linecomment((char *)line);
|
|
if (comment_start != MAXCOL) {
|
|
leader_len = get_leader_len((char *)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;
|
|
int 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
|
|
int wcc = 0; // counter for whitespace chars
|
|
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;
|
|
bool 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 = utf_allow_break(cc, ncc);
|
|
|
|
// 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 = utf_allow_break(cc, ncc);
|
|
|
|
// 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) {
|
|
const bool ncc_allow_break = utf_allow_break_before(ncc);
|
|
|
|
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 = utf_allow_break(cc, ncc);
|
|
|
|
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 = xstrdup(get_cursor_pos_ptr());
|
|
curwin->w_cursor.col = orig_col;
|
|
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 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 (int i = 0; i < padding; i++) {
|
|
ins_str(" ");
|
|
}
|
|
changed_bytes(curwin->w_cursor.lnum, leader_len);
|
|
} 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);
|
|
xfree(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(get_cursor_line_ptr());
|
|
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((char_u)save_char);
|
|
}
|
|
|
|
curwin->w_p_lbr = has_lbr;
|
|
|
|
if (!format_only && haveto_redraw) {
|
|
update_topline(curwin);
|
|
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.
|
|
static int fmt_check_par(linenr_T lnum, int *leader_len, char_u **leader_flags, bool do_comments)
|
|
{
|
|
char_u *flags = NULL; // init for GCC
|
|
char_u *ptr;
|
|
|
|
ptr = (char_u *)ml_get(lnum);
|
|
if (do_comments) {
|
|
*leader_len = get_leader_len((char *)ptr, (char **)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((char *)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 bool ends_in_white(linenr_T lnum)
|
|
{
|
|
char *s = ml_get(lnum);
|
|
size_t l;
|
|
|
|
if (*s == NUL) {
|
|
return false;
|
|
}
|
|
l = strlen(s) - 1;
|
|
return ascii_iswhite((uint8_t)s[l]);
|
|
}
|
|
|
|
/// @return true if the two comment leaders given are the same.
|
|
///
|
|
/// @param lnum The first line. White-space is ignored.
|
|
///
|
|
/// @note the whole of 'leader1' must match 'leader2_len' characters from 'leader2'.
|
|
static bool same_leader(linenr_T lnum, int leader1_len, char *leader1_flags, int leader2_len,
|
|
char *leader2_flags)
|
|
{
|
|
int idx1 = 0, idx2 = 0;
|
|
char *p;
|
|
char *line1;
|
|
char *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) {
|
|
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 = xstrdup(ml_get(lnum));
|
|
for (idx1 = 0; ascii_iswhite(line1[idx1]); idx1++) {}
|
|
line2 = ml_get(lnum + 1);
|
|
for (idx2 = 0; idx2 < leader2_len; idx2++) {
|
|
if (!ascii_iswhite(line2[idx2])) {
|
|
if (line1[idx1++] != line2[idx2]) {
|
|
break;
|
|
}
|
|
} else {
|
|
while (ascii_iswhite(line1[idx1])) {
|
|
idx1++;
|
|
}
|
|
}
|
|
}
|
|
xfree(line1);
|
|
|
|
return idx2 == leader2_len && idx1 == leader1_len;
|
|
}
|
|
|
|
/// Used for auto-formatting.
|
|
///
|
|
/// @return true when a paragraph starts in line "lnum".
|
|
/// false when the previous line is in the same paragraph.
|
|
static bool 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 = 0; // leader len of next line
|
|
char_u *next_leader_flags = NULL; // flags for leader of next line
|
|
|
|
if (lnum <= 1) {
|
|
return true; // start of the file
|
|
}
|
|
p = (char_u *)ml_get(lnum - 1);
|
|
if (*p == NUL) {
|
|
return true; // after empty line
|
|
}
|
|
const bool do_comments = has_format_option(FO_Q_COMS); // format comments
|
|
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, (char *)leader_flags,
|
|
next_leader_len, (char *)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.
|
|
///
|
|
/// @param trailblank when true also format with trailing blank
|
|
/// @param prev_line may start in previous line
|
|
void auto_format(bool trailblank, bool prev_line)
|
|
{
|
|
pos_T pos;
|
|
colnr_T len;
|
|
char *old;
|
|
char *new, *pnew;
|
|
int wasatend;
|
|
int cc;
|
|
|
|
if (!has_format_option(FO_AUTO)) {
|
|
return;
|
|
}
|
|
|
|
pos = curwin->w_cursor;
|
|
old = get_cursor_line_ptr();
|
|
|
|
// 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(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 = get_cursor_line_ptr();
|
|
len = (colnr_T)strlen(new);
|
|
if (curwin->w_cursor.col == len) {
|
|
pnew = xstrnsave(new, (size_t)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.
|
|
///
|
|
/// @param end_insert true when ending Insert mode
|
|
void check_auto_format(bool end_insert)
|
|
{
|
|
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_inner-'wrapmargin'
|
|
/// if invalid value, use 0.
|
|
/// Set default to window width (maximum 79) for "gq" operator.
|
|
///
|
|
/// @param ff force formatting (for "gq" command)
|
|
int comp_textwidth(bool ff)
|
|
{
|
|
int textwidth = (int)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_inner - (int)curbuf->b_p_wm;
|
|
if (cmdwin_type != 0) {
|
|
textwidth -= 1;
|
|
}
|
|
textwidth -= win_fdccol_count(curwin);
|
|
textwidth -= win_signcol_count(curwin);
|
|
|
|
if (curwin->w_p_nu || curwin->w_p_rnu) {
|
|
textwidth -= 8;
|
|
}
|
|
}
|
|
if (textwidth < 0) {
|
|
textwidth = 0;
|
|
}
|
|
if (ff && textwidth == 0) {
|
|
textwidth = curwin->w_width_inner - 1;
|
|
if (textwidth > 79) {
|
|
textwidth = 79;
|
|
}
|
|
}
|
|
return textwidth;
|
|
}
|
|
|
|
/// Implementation of the format operator 'gq'.
|
|
///
|
|
/// @param keep_cursor keep cursor on same text char
|
|
void op_format(oparg_T *oap, bool keep_cursor)
|
|
{
|
|
linenr_T 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((linenr_T)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) {
|
|
FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// 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);
|
|
}
|
|
}
|
|
|
|
/// @param c character to be inserted
|
|
int fex_format(linenr_T lnum, long count, int c)
|
|
{
|
|
int use_sandbox = was_set_insecurely(curwin, "formatexpr", OPT_LOCAL);
|
|
int r;
|
|
|
|
// 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, (varnumber_T)lnum);
|
|
set_vim_var_nr(VV_COUNT, (varnumber_T)count);
|
|
set_vim_var_char(c);
|
|
|
|
// Make a copy, the option could be changed while calling it.
|
|
char *fex = xstrdup(curbuf->b_p_fex);
|
|
// 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);
|
|
xfree(fex);
|
|
|
|
return r;
|
|
}
|
|
|
|
/// @param line_count number of lines to format, starting at the cursor position.
|
|
/// when negative, format until the end of the paragraph.
|
|
///
|
|
/// Lines after the cursor line are saved for undo, caller must have saved the
|
|
/// first line.
|
|
///
|
|
/// @param avoid_fex don't use 'formatexpr'
|
|
void format_lines(linenr_T line_count, bool avoid_fex)
|
|
{
|
|
bool is_not_par; // current line not part of parag.
|
|
bool next_is_not_par; // next line not part of paragraph
|
|
bool is_end_par; // at end of paragraph
|
|
bool prev_is_end_par = false; // prev. line not part of parag.
|
|
bool 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
|
|
bool advance = true;
|
|
int second_indent = -1; // indent for second line (comment aware)
|
|
bool first_par_line = true;
|
|
int smd_save;
|
|
long count;
|
|
bool need_set_indent = true; // set indent of next paragraph
|
|
linenr_T first_line = curwin->w_cursor.lnum;
|
|
bool force_format = false;
|
|
const int old_State = State;
|
|
|
|
// length of a line to force formatting: 3 * 'tw'
|
|
const int max_len = comp_textwidth(true) * 3;
|
|
|
|
// check for 'q', '2' and '1' in 'formatoptions'
|
|
const bool do_comments = has_format_option(FO_Q_COMS); // format comments
|
|
int do_comments_list = 0; // format comments with 'n' or '2'
|
|
const bool do_second_indent = has_format_option(FO_Q_SECOND);
|
|
const bool do_number_indent = has_format_option(FO_Q_NUMBER);
|
|
const bool 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, (char *)leader_flags,
|
|
next_leader_len,
|
|
(char *)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(get_cursor_line_ptr()) == 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 = *curbuf->b_p_inde != NUL ? get_expr_indent() : 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(MAXCOL);
|
|
while (curwin->w_cursor.col && ascii_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(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 = (int)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(get_cursor_line_ptr()) > (size_t)max_len) {
|
|
force_format = true;
|
|
} else {
|
|
force_format = false;
|
|
}
|
|
}
|
|
}
|
|
line_breakcheck();
|
|
}
|
|
}
|