mirror of
https://github.com/vim/vim.git
synced 2025-07-26 11:04:33 -04:00
Problem: cannot complete option values Solution: Add completion functions for several options Add cmdline tab-completion for setting string options Add tab-completion for setting string options on the cmdline using `:set=` (along with `:set+=` and `:set-=`). The existing tab completion for setting options currently only works when nothing is typed yet, and it only fills in with the existing value, e.g. when the user does `:set diffopt=<Tab>` it will be completed to `set diffopt=internal,filler,closeoff` and nothing else. This isn't too useful as a user usually wants auto-complete to suggest all the possible values, such as 'iblank', or 'algorithm:patience'. For set= and set+=, this adds a new optional callback function for each option that can be invoked when doing completion. This allows for each option to have control over how completion works. For example, in 'diffopt', it will suggest the default enumeration, but if `algorithm:` is selected, it will further suggest different algorithm types like 'meyers' and 'patience'. When using set=, the existing option value will be filled in as the first choice to preserve the existing behavior. When using set+= this won't happen as it doesn't make sense. For flag list options (e.g. 'mouse' and 'guioptions'), completion will take into account existing typed values (and in the case of set+=, the existing option value) to make sure it doesn't suggest duplicates. For set-=, there is a new `ExpandSettingSubtract` function which will handle flag list and comma-separated options smartly, by only suggesting values that currently exist in the option. Note that Vim has some existing code that adds special handling for 'filetype', 'syntax', and misc dir options like 'backupdir'. This change preserves them as they already work, instead of converting to the new callback API for each option. closes: #13182 Signed-off-by: Christian Brabandt <cb@256bit.org> Co-authored-by: Yee Cheng Chin <ychin.git@gmail.com>
2283 lines
53 KiB
C
2283 lines
53 KiB
C
/* 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.
|
|
*/
|
|
|
|
/*
|
|
* indent.c: Indentation related functions
|
|
*/
|
|
|
|
#include "vim.h"
|
|
|
|
#if defined(FEAT_VARTABS) || defined(PROTO)
|
|
|
|
/*
|
|
* Set the integer values corresponding to the string setting of 'vartabstop'.
|
|
* "array" will be set, caller must free it if needed.
|
|
* Return FAIL for an error.
|
|
*/
|
|
int
|
|
tabstop_set(char_u *var, int **array)
|
|
{
|
|
int valcount = 1;
|
|
int t;
|
|
char_u *cp;
|
|
|
|
if (var[0] == NUL || (var[0] == '0' && var[1] == NUL))
|
|
{
|
|
*array = NULL;
|
|
return OK;
|
|
}
|
|
|
|
for (cp = var; *cp != NUL; ++cp)
|
|
{
|
|
if (cp == var || cp[-1] == ',')
|
|
{
|
|
char_u *end;
|
|
|
|
if (strtol((char *)cp, (char **)&end, 10) <= 0)
|
|
{
|
|
if (cp != end)
|
|
emsg(_(e_argument_must_be_positive));
|
|
else
|
|
semsg(_(e_invalid_argument_str), cp);
|
|
return FAIL;
|
|
}
|
|
}
|
|
|
|
if (VIM_ISDIGIT(*cp))
|
|
continue;
|
|
if (cp[0] == ',' && cp > var && cp[-1] != ',' && cp[1] != NUL)
|
|
{
|
|
++valcount;
|
|
continue;
|
|
}
|
|
semsg(_(e_invalid_argument_str), var);
|
|
return FAIL;
|
|
}
|
|
|
|
*array = ALLOC_MULT(int, valcount + 1);
|
|
if (*array == NULL)
|
|
return FAIL;
|
|
(*array)[0] = valcount;
|
|
|
|
t = 1;
|
|
for (cp = var; *cp != NUL;)
|
|
{
|
|
int n = atoi((char *)cp);
|
|
|
|
// Catch negative values, overflow and ridiculous big values.
|
|
if (n <= 0 || n > TABSTOP_MAX)
|
|
{
|
|
semsg(_(e_invalid_argument_str), cp);
|
|
VIM_CLEAR(*array);
|
|
return FAIL;
|
|
}
|
|
(*array)[t++] = n;
|
|
while (*cp != NUL && *cp != ',')
|
|
++cp;
|
|
if (*cp != NUL)
|
|
++cp;
|
|
}
|
|
|
|
return OK;
|
|
}
|
|
|
|
/*
|
|
* Calculate the number of screen spaces a tab will occupy.
|
|
* If "vts" is set then the tab widths are taken from that array,
|
|
* otherwise the value of ts is used.
|
|
*/
|
|
int
|
|
tabstop_padding(colnr_T col, int ts_arg, int *vts)
|
|
{
|
|
int ts = ts_arg == 0 ? 8 : ts_arg;
|
|
int tabcount;
|
|
colnr_T tabcol = 0;
|
|
int t;
|
|
int padding = 0;
|
|
|
|
if (vts == NULL || vts[0] == 0)
|
|
return ts - (col % ts);
|
|
|
|
tabcount = vts[0];
|
|
|
|
for (t = 1; t <= tabcount; ++t)
|
|
{
|
|
tabcol += vts[t];
|
|
if (tabcol > col)
|
|
{
|
|
padding = (int)(tabcol - col);
|
|
break;
|
|
}
|
|
}
|
|
if (t > tabcount)
|
|
padding = vts[tabcount] - (int)((col - tabcol) % vts[tabcount]);
|
|
|
|
return padding;
|
|
}
|
|
|
|
/*
|
|
* Find the size of the tab that covers a particular column.
|
|
*/
|
|
int
|
|
tabstop_at(colnr_T col, int ts, int *vts)
|
|
{
|
|
int tabcount;
|
|
colnr_T tabcol = 0;
|
|
int t;
|
|
int tab_size = 0;
|
|
|
|
if (vts == 0 || vts[0] == 0)
|
|
return ts;
|
|
|
|
tabcount = vts[0];
|
|
for (t = 1; t <= tabcount; ++t)
|
|
{
|
|
tabcol += vts[t];
|
|
if (tabcol > col)
|
|
{
|
|
tab_size = vts[t];
|
|
break;
|
|
}
|
|
}
|
|
if (t > tabcount)
|
|
tab_size = vts[tabcount];
|
|
|
|
return tab_size;
|
|
}
|
|
|
|
/*
|
|
* Find the column on which a tab starts.
|
|
*/
|
|
colnr_T
|
|
tabstop_start(colnr_T col, int ts, int *vts)
|
|
{
|
|
int tabcount;
|
|
colnr_T tabcol = 0;
|
|
int t;
|
|
int excess;
|
|
|
|
if (vts == NULL || vts[0] == 0)
|
|
return (col / ts) * ts;
|
|
|
|
tabcount = vts[0];
|
|
for (t = 1; t <= tabcount; ++t)
|
|
{
|
|
tabcol += vts[t];
|
|
if (tabcol > col)
|
|
return tabcol - vts[t];
|
|
}
|
|
|
|
excess = tabcol % vts[tabcount];
|
|
return excess + ((col - excess) / vts[tabcount]) * vts[tabcount];
|
|
}
|
|
|
|
/*
|
|
* Find the number of tabs and spaces necessary to get from one column
|
|
* to another.
|
|
*/
|
|
void
|
|
tabstop_fromto(
|
|
colnr_T start_col,
|
|
colnr_T end_col,
|
|
int ts_arg,
|
|
int *vts,
|
|
int *ntabs,
|
|
int *nspcs)
|
|
{
|
|
int spaces = end_col - start_col;
|
|
colnr_T tabcol = 0;
|
|
int padding = 0;
|
|
int tabcount;
|
|
int t;
|
|
int ts = ts_arg == 0 ? curbuf->b_p_ts : ts_arg;
|
|
|
|
if (vts == NULL || vts[0] == 0)
|
|
{
|
|
int tabs = 0;
|
|
int initspc = 0;
|
|
|
|
initspc = ts - (start_col % ts);
|
|
if (spaces >= initspc)
|
|
{
|
|
spaces -= initspc;
|
|
tabs++;
|
|
}
|
|
tabs += spaces / ts;
|
|
spaces -= (spaces / ts) * ts;
|
|
|
|
*ntabs = tabs;
|
|
*nspcs = spaces;
|
|
return;
|
|
}
|
|
|
|
// Find the padding needed to reach the next tabstop.
|
|
tabcount = vts[0];
|
|
for (t = 1; t <= tabcount; ++t)
|
|
{
|
|
tabcol += vts[t];
|
|
if (tabcol > start_col)
|
|
{
|
|
padding = (int)(tabcol - start_col);
|
|
break;
|
|
}
|
|
}
|
|
if (t > tabcount)
|
|
padding = vts[tabcount] - (int)((start_col - tabcol) % vts[tabcount]);
|
|
|
|
// If the space needed is less than the padding no tabs can be used.
|
|
if (spaces < padding)
|
|
{
|
|
*ntabs = 0;
|
|
*nspcs = spaces;
|
|
return;
|
|
}
|
|
|
|
*ntabs = 1;
|
|
spaces -= padding;
|
|
|
|
// At least one tab has been used. See if any more will fit.
|
|
while (spaces != 0 && ++t <= tabcount)
|
|
{
|
|
padding = vts[t];
|
|
if (spaces < padding)
|
|
{
|
|
*nspcs = spaces;
|
|
return;
|
|
}
|
|
++*ntabs;
|
|
spaces -= padding;
|
|
}
|
|
|
|
*ntabs += spaces / vts[tabcount];
|
|
*nspcs = spaces % vts[tabcount];
|
|
}
|
|
|
|
/*
|
|
* See if two tabstop arrays contain the same values.
|
|
*/
|
|
static int
|
|
tabstop_eq(int *ts1, int *ts2)
|
|
{
|
|
int t;
|
|
|
|
if ((ts1 == 0 && ts2) || (ts1 && ts2 == 0))
|
|
return FALSE;
|
|
if (ts1 == ts2)
|
|
return TRUE;
|
|
if (ts1[0] != ts2[0])
|
|
return FALSE;
|
|
|
|
for (t = 1; t <= ts1[0]; ++t)
|
|
if (ts1[t] != ts2[t])
|
|
return FALSE;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
# if defined(FEAT_BEVAL) || defined(PROTO)
|
|
/*
|
|
* Copy a tabstop array, allocating space for the new array.
|
|
*/
|
|
int *
|
|
tabstop_copy(int *oldts)
|
|
{
|
|
int *newts;
|
|
int t;
|
|
|
|
if (oldts == NULL)
|
|
return NULL;
|
|
newts = ALLOC_MULT(int, oldts[0] + 1);
|
|
if (newts != NULL)
|
|
for (t = 0; t <= oldts[0]; ++t)
|
|
newts[t] = oldts[t];
|
|
return newts;
|
|
}
|
|
# endif
|
|
|
|
/*
|
|
* Return a count of the number of tabstops.
|
|
*/
|
|
int
|
|
tabstop_count(int *ts)
|
|
{
|
|
return ts != NULL ? ts[0] : 0;
|
|
}
|
|
|
|
/*
|
|
* Return the first tabstop, or 8 if there are no tabstops defined.
|
|
*/
|
|
int
|
|
tabstop_first(int *ts)
|
|
{
|
|
return ts != NULL ? ts[1] : 8;
|
|
}
|
|
|
|
#endif
|
|
|
|
/*
|
|
* Return the effective shiftwidth value for current buffer, using the
|
|
* 'tabstop' value when 'shiftwidth' is zero.
|
|
*/
|
|
long
|
|
get_sw_value(buf_T *buf)
|
|
{
|
|
return get_sw_value_col(buf, 0);
|
|
}
|
|
|
|
/*
|
|
* Idem, using "pos".
|
|
*/
|
|
static long
|
|
get_sw_value_pos(buf_T *buf, pos_T *pos)
|
|
{
|
|
pos_T save_cursor = curwin->w_cursor;
|
|
long sw_value;
|
|
|
|
curwin->w_cursor = *pos;
|
|
sw_value = get_sw_value_col(buf, get_nolist_virtcol());
|
|
curwin->w_cursor = save_cursor;
|
|
return sw_value;
|
|
}
|
|
|
|
/*
|
|
* Idem, using the first non-black in the current line.
|
|
*/
|
|
long
|
|
get_sw_value_indent(buf_T *buf)
|
|
{
|
|
pos_T pos = curwin->w_cursor;
|
|
|
|
pos.col = getwhitecols_curline();
|
|
return get_sw_value_pos(buf, &pos);
|
|
}
|
|
|
|
/*
|
|
* Idem, using virtual column "col".
|
|
*/
|
|
long
|
|
get_sw_value_col(buf_T *buf, colnr_T col UNUSED)
|
|
{
|
|
return buf->b_p_sw ? buf->b_p_sw :
|
|
#ifdef FEAT_VARTABS
|
|
tabstop_at(col, buf->b_p_ts, buf->b_p_vts_array);
|
|
#else
|
|
buf->b_p_ts;
|
|
#endif
|
|
}
|
|
|
|
/*
|
|
* Return the effective softtabstop value for the current buffer, using the
|
|
* 'shiftwidth' value when 'softtabstop' is negative.
|
|
*/
|
|
long
|
|
get_sts_value(void)
|
|
{
|
|
return curbuf->b_p_sts < 0 ? get_sw_value(curbuf) : curbuf->b_p_sts;
|
|
}
|
|
|
|
/*
|
|
* Count the size (in window cells) of the indent in the current line.
|
|
*/
|
|
int
|
|
get_indent(void)
|
|
{
|
|
#ifdef FEAT_VARTABS
|
|
return get_indent_str_vtab(ml_get_curline(), (int)curbuf->b_p_ts,
|
|
curbuf->b_p_vts_array, FALSE);
|
|
#else
|
|
return get_indent_str(ml_get_curline(), (int)curbuf->b_p_ts, FALSE);
|
|
#endif
|
|
}
|
|
|
|
/*
|
|
* Count the size (in window cells) of the indent in line "lnum".
|
|
*/
|
|
int
|
|
get_indent_lnum(linenr_T lnum)
|
|
{
|
|
#ifdef FEAT_VARTABS
|
|
return get_indent_str_vtab(ml_get(lnum), (int)curbuf->b_p_ts,
|
|
curbuf->b_p_vts_array, FALSE);
|
|
#else
|
|
return get_indent_str(ml_get(lnum), (int)curbuf->b_p_ts, FALSE);
|
|
#endif
|
|
}
|
|
|
|
#if defined(FEAT_FOLDING) || defined(PROTO)
|
|
/*
|
|
* Count the size (in window cells) of the indent in line "lnum" of buffer
|
|
* "buf".
|
|
*/
|
|
int
|
|
get_indent_buf(buf_T *buf, linenr_T lnum)
|
|
{
|
|
# ifdef FEAT_VARTABS
|
|
return get_indent_str_vtab(ml_get_buf(buf, lnum, FALSE),
|
|
(int)buf->b_p_ts, buf->b_p_vts_array, FALSE);
|
|
# else
|
|
return get_indent_str(ml_get_buf(buf, lnum, FALSE), (int)buf->b_p_ts, FALSE);
|
|
# endif
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
* count the size (in window cells) of the indent in line "ptr", with
|
|
* 'tabstop' at "ts"
|
|
*/
|
|
int
|
|
get_indent_str(
|
|
char_u *ptr,
|
|
int ts,
|
|
int list) // if TRUE, count only screen size for tabs
|
|
{
|
|
int count = 0;
|
|
|
|
for ( ; *ptr; ++ptr)
|
|
{
|
|
if (*ptr == TAB)
|
|
{
|
|
if (!list || curwin->w_lcs_chars.tab1)
|
|
// count a tab for what it is worth
|
|
count += ts - (count % ts);
|
|
else
|
|
// In list mode, when tab is not set, count screen char width
|
|
// for Tab, displays: ^I
|
|
count += ptr2cells(ptr);
|
|
}
|
|
else if (*ptr == ' ')
|
|
++count; // count a space for one
|
|
else
|
|
break;
|
|
}
|
|
return count;
|
|
}
|
|
|
|
#ifdef FEAT_VARTABS
|
|
/*
|
|
* Count the size (in window cells) of the indent in line "ptr", using
|
|
* variable tabstops.
|
|
* if "list" is TRUE, count only screen size for tabs.
|
|
*/
|
|
int
|
|
get_indent_str_vtab(char_u *ptr, int ts, int *vts, int list)
|
|
{
|
|
int count = 0;
|
|
|
|
for ( ; *ptr; ++ptr)
|
|
{
|
|
if (*ptr == TAB) // count a tab for what it is worth
|
|
{
|
|
if (!list || curwin->w_lcs_chars.tab1)
|
|
count += tabstop_padding(count, ts, vts);
|
|
else
|
|
// In list mode, when tab is not set, count screen char width
|
|
// for Tab, displays: ^I
|
|
count += ptr2cells(ptr);
|
|
}
|
|
else if (*ptr == ' ')
|
|
++count; // count a space for one
|
|
else
|
|
break;
|
|
}
|
|
return count;
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
* Set the indent of the current line.
|
|
* Leaves the cursor on the first non-blank in the line.
|
|
* Caller must take care of undo.
|
|
* "flags":
|
|
* SIN_CHANGED: call changed_bytes() if the line was changed.
|
|
* SIN_INSERT: insert the indent in front of the line.
|
|
* SIN_UNDO: save line for undo before changing it.
|
|
* Returns TRUE if the line was changed.
|
|
*/
|
|
int
|
|
set_indent(
|
|
int size, // measured in spaces
|
|
int flags)
|
|
{
|
|
char_u *p;
|
|
char_u *newline;
|
|
char_u *oldline;
|
|
char_u *s;
|
|
int todo;
|
|
int ind_len; // measured in characters
|
|
int line_len;
|
|
int doit = FALSE;
|
|
int ind_done = 0; // measured in spaces
|
|
#ifdef FEAT_VARTABS
|
|
int ind_col = 0;
|
|
#endif
|
|
int tab_pad;
|
|
int retval = FALSE;
|
|
int orig_char_len = -1; // number of initial whitespace chars when
|
|
// 'et' and 'pi' are both set
|
|
|
|
// First check if there is anything to do and compute the number of
|
|
// characters needed for the indent.
|
|
todo = size;
|
|
ind_len = 0;
|
|
p = oldline = ml_get_curline();
|
|
|
|
// Calculate the buffer size for the new indent, and check to see if it
|
|
// isn't already set
|
|
|
|
// if 'expandtab' isn't set: use TABs; if both 'expandtab' and
|
|
// 'preserveindent' are set count the number of characters at the
|
|
// beginning of the line to be copied
|
|
if (!curbuf->b_p_et || (!(flags & SIN_INSERT) && curbuf->b_p_pi))
|
|
{
|
|
// If 'preserveindent' is set then reuse as much as possible of
|
|
// the existing indent structure for the new indent
|
|
if (!(flags & SIN_INSERT) && curbuf->b_p_pi)
|
|
{
|
|
ind_done = 0;
|
|
|
|
// count as many characters as we can use
|
|
while (todo > 0 && VIM_ISWHITE(*p))
|
|
{
|
|
if (*p == TAB)
|
|
{
|
|
#ifdef FEAT_VARTABS
|
|
tab_pad = tabstop_padding(ind_done, curbuf->b_p_ts,
|
|
curbuf->b_p_vts_array);
|
|
#else
|
|
tab_pad = (int)curbuf->b_p_ts
|
|
- (ind_done % (int)curbuf->b_p_ts);
|
|
#endif
|
|
// stop if this tab will overshoot the target
|
|
if (todo < tab_pad)
|
|
break;
|
|
todo -= tab_pad;
|
|
++ind_len;
|
|
ind_done += tab_pad;
|
|
}
|
|
else
|
|
{
|
|
--todo;
|
|
++ind_len;
|
|
++ind_done;
|
|
}
|
|
++p;
|
|
}
|
|
|
|
#ifdef FEAT_VARTABS
|
|
// These diverge from this point.
|
|
ind_col = ind_done;
|
|
#endif
|
|
// Set initial number of whitespace chars to copy if we are
|
|
// preserving indent but expandtab is set
|
|
if (curbuf->b_p_et)
|
|
orig_char_len = ind_len;
|
|
|
|
// Fill to next tabstop with a tab, if possible
|
|
#ifdef FEAT_VARTABS
|
|
tab_pad = tabstop_padding(ind_done, curbuf->b_p_ts,
|
|
curbuf->b_p_vts_array);
|
|
#else
|
|
tab_pad = (int)curbuf->b_p_ts - (ind_done % (int)curbuf->b_p_ts);
|
|
#endif
|
|
if (todo >= tab_pad && orig_char_len == -1)
|
|
{
|
|
doit = TRUE;
|
|
todo -= tab_pad;
|
|
++ind_len;
|
|
// ind_done += tab_pad;
|
|
#ifdef FEAT_VARTABS
|
|
ind_col += tab_pad;
|
|
#endif
|
|
}
|
|
}
|
|
|
|
// count tabs required for indent
|
|
#ifdef FEAT_VARTABS
|
|
for (;;)
|
|
{
|
|
tab_pad = tabstop_padding(ind_col, curbuf->b_p_ts,
|
|
curbuf->b_p_vts_array);
|
|
if (todo < tab_pad)
|
|
break;
|
|
if (*p != TAB)
|
|
doit = TRUE;
|
|
else
|
|
++p;
|
|
todo -= tab_pad;
|
|
++ind_len;
|
|
ind_col += tab_pad;
|
|
}
|
|
#else
|
|
while (todo >= (int)curbuf->b_p_ts)
|
|
{
|
|
if (*p != TAB)
|
|
doit = TRUE;
|
|
else
|
|
++p;
|
|
todo -= (int)curbuf->b_p_ts;
|
|
++ind_len;
|
|
// ind_done += (int)curbuf->b_p_ts;
|
|
}
|
|
#endif
|
|
}
|
|
// count spaces required for indent
|
|
while (todo > 0)
|
|
{
|
|
if (*p != ' ')
|
|
doit = TRUE;
|
|
else
|
|
++p;
|
|
--todo;
|
|
++ind_len;
|
|
// ++ind_done;
|
|
}
|
|
|
|
// Return if the indent is OK already.
|
|
if (!doit && !VIM_ISWHITE(*p) && !(flags & SIN_INSERT))
|
|
return FALSE;
|
|
|
|
// Allocate memory for the new line.
|
|
if (flags & SIN_INSERT)
|
|
p = oldline;
|
|
else
|
|
p = skipwhite(p);
|
|
line_len = (int)STRLEN(p) + 1;
|
|
|
|
// If 'preserveindent' and 'expandtab' are both set keep the original
|
|
// characters and allocate accordingly. We will fill the rest with spaces
|
|
// after the if (!curbuf->b_p_et) below.
|
|
if (orig_char_len != -1)
|
|
{
|
|
newline = alloc(orig_char_len + size - ind_done + line_len);
|
|
if (newline == NULL)
|
|
return FALSE;
|
|
todo = size - ind_done;
|
|
ind_len = orig_char_len + todo; // Set total length of indent in
|
|
// characters, which may have been
|
|
// undercounted until now
|
|
p = oldline;
|
|
s = newline;
|
|
while (orig_char_len > 0)
|
|
{
|
|
*s++ = *p++;
|
|
orig_char_len--;
|
|
}
|
|
|
|
// Skip over any additional white space (useful when newindent is less
|
|
// than old)
|
|
while (VIM_ISWHITE(*p))
|
|
++p;
|
|
|
|
}
|
|
else
|
|
{
|
|
todo = size;
|
|
newline = alloc(ind_len + line_len);
|
|
if (newline == NULL)
|
|
return FALSE;
|
|
s = newline;
|
|
}
|
|
|
|
// Put the characters in the new line.
|
|
// if 'expandtab' isn't set: use TABs
|
|
if (!curbuf->b_p_et)
|
|
{
|
|
// If 'preserveindent' is set then reuse as much as possible of
|
|
// the existing indent structure for the new indent
|
|
if (!(flags & SIN_INSERT) && curbuf->b_p_pi)
|
|
{
|
|
p = oldline;
|
|
ind_done = 0;
|
|
|
|
while (todo > 0 && VIM_ISWHITE(*p))
|
|
{
|
|
if (*p == TAB)
|
|
{
|
|
#ifdef FEAT_VARTABS
|
|
tab_pad = tabstop_padding(ind_done, curbuf->b_p_ts,
|
|
curbuf->b_p_vts_array);
|
|
#else
|
|
tab_pad = (int)curbuf->b_p_ts
|
|
- (ind_done % (int)curbuf->b_p_ts);
|
|
#endif
|
|
// stop if this tab will overshoot the target
|
|
if (todo < tab_pad)
|
|
break;
|
|
todo -= tab_pad;
|
|
ind_done += tab_pad;
|
|
}
|
|
else
|
|
{
|
|
--todo;
|
|
++ind_done;
|
|
}
|
|
*s++ = *p++;
|
|
}
|
|
|
|
// Fill to next tabstop with a tab, if possible
|
|
#ifdef FEAT_VARTABS
|
|
tab_pad = tabstop_padding(ind_done, curbuf->b_p_ts,
|
|
curbuf->b_p_vts_array);
|
|
#else
|
|
tab_pad = (int)curbuf->b_p_ts - (ind_done % (int)curbuf->b_p_ts);
|
|
#endif
|
|
if (todo >= tab_pad)
|
|
{
|
|
*s++ = TAB;
|
|
todo -= tab_pad;
|
|
#ifdef FEAT_VARTABS
|
|
ind_done += tab_pad;
|
|
#endif
|
|
}
|
|
|
|
p = skipwhite(p);
|
|
}
|
|
|
|
#ifdef FEAT_VARTABS
|
|
for (;;)
|
|
{
|
|
tab_pad = tabstop_padding(ind_done, curbuf->b_p_ts,
|
|
curbuf->b_p_vts_array);
|
|
if (todo < tab_pad)
|
|
break;
|
|
*s++ = TAB;
|
|
todo -= tab_pad;
|
|
ind_done += tab_pad;
|
|
}
|
|
#else
|
|
while (todo >= (int)curbuf->b_p_ts)
|
|
{
|
|
*s++ = TAB;
|
|
todo -= (int)curbuf->b_p_ts;
|
|
}
|
|
#endif
|
|
}
|
|
while (todo > 0)
|
|
{
|
|
*s++ = ' ';
|
|
--todo;
|
|
}
|
|
mch_memmove(s, p, (size_t)line_len);
|
|
|
|
// Replace the line (unless undo fails).
|
|
if (!(flags & SIN_UNDO) || u_savesub(curwin->w_cursor.lnum) == OK)
|
|
{
|
|
colnr_T old_offset = (colnr_T)(p - oldline);
|
|
colnr_T new_offset = (colnr_T)(s - newline);
|
|
|
|
// this may free "newline"
|
|
ml_replace(curwin->w_cursor.lnum, newline, FALSE);
|
|
if (flags & SIN_CHANGED)
|
|
changed_bytes(curwin->w_cursor.lnum, 0);
|
|
|
|
// Correct saved cursor position if it is in this line.
|
|
if (saved_cursor.lnum == curwin->w_cursor.lnum)
|
|
{
|
|
if (saved_cursor.col >= old_offset)
|
|
// cursor was after the indent, adjust for the number of
|
|
// bytes added/removed
|
|
saved_cursor.col += ind_len - old_offset;
|
|
else if (saved_cursor.col >= new_offset)
|
|
// cursor was in the indent, and is now after it, put it back
|
|
// at the start of the indent (replacing spaces with TAB)
|
|
saved_cursor.col = new_offset;
|
|
}
|
|
#ifdef FEAT_PROP_POPUP
|
|
{
|
|
int added = ind_len - old_offset;
|
|
|
|
// When increasing indent this behaves like spaces were inserted at
|
|
// the old indent, when decreasing indent it behaves like spaces
|
|
// were deleted at the new indent.
|
|
adjust_prop_columns(curwin->w_cursor.lnum,
|
|
added > 0 ? old_offset : (colnr_T)ind_len,
|
|
added, APC_INDENT);
|
|
}
|
|
#endif
|
|
retval = TRUE;
|
|
}
|
|
else
|
|
vim_free(newline);
|
|
|
|
curwin->w_cursor.col = ind_len;
|
|
return retval;
|
|
}
|
|
|
|
/*
|
|
* Return the indent of the current line after a number. Return -1 if no
|
|
* number was found. Used for 'n' in 'formatoptions': numbered list.
|
|
* Since a pattern is used it can actually handle more than numbers.
|
|
*/
|
|
int
|
|
get_number_indent(linenr_T lnum)
|
|
{
|
|
colnr_T col;
|
|
pos_T pos;
|
|
|
|
regmatch_T regmatch;
|
|
int lead_len = 0; // length of comment leader
|
|
|
|
if (lnum > curbuf->b_ml.ml_line_count)
|
|
return -1;
|
|
pos.lnum = 0;
|
|
|
|
// In format_lines() (i.e. not insert mode), fo+=q is needed too...
|
|
if ((State & MODE_INSERT) || has_format_option(FO_Q_COMS))
|
|
lead_len = get_leader_len(ml_get(lnum), NULL, FALSE, TRUE);
|
|
|
|
regmatch.regprog = vim_regcomp(curbuf->b_p_flp, RE_MAGIC);
|
|
if (regmatch.regprog != NULL)
|
|
{
|
|
regmatch.rm_ic = FALSE;
|
|
|
|
// vim_regexec() expects a pointer to a line. This lets us
|
|
// start matching for the flp beyond any comment leader...
|
|
if (vim_regexec(®match, ml_get(lnum) + lead_len, (colnr_T)0))
|
|
{
|
|
pos.lnum = lnum;
|
|
pos.col = (colnr_T)(*regmatch.endp - ml_get(lnum));
|
|
pos.coladd = 0;
|
|
}
|
|
vim_regfree(regmatch.regprog);
|
|
}
|
|
|
|
if (pos.lnum == 0 || *ml_get_pos(&pos) == NUL)
|
|
return -1;
|
|
getvcol(curwin, &pos, &col, NULL, NULL);
|
|
return (int)col;
|
|
}
|
|
|
|
#if defined(FEAT_LINEBREAK) || defined(PROTO)
|
|
/*
|
|
* This is called when 'breakindentopt' is changed and when a window is
|
|
* initialized.
|
|
*/
|
|
int
|
|
briopt_check(win_T *wp)
|
|
{
|
|
char_u *p;
|
|
int bri_shift = 0;
|
|
long bri_min = 20;
|
|
int bri_sbr = FALSE;
|
|
int bri_list = 0;
|
|
int bri_vcol = 0;
|
|
|
|
p = wp->w_p_briopt;
|
|
while (*p != NUL)
|
|
{
|
|
// Note: Keep this in sync with p_briopt_values
|
|
if (STRNCMP(p, "shift:", 6) == 0
|
|
&& ((p[6] == '-' && VIM_ISDIGIT(p[7])) || VIM_ISDIGIT(p[6])))
|
|
{
|
|
p += 6;
|
|
bri_shift = getdigits(&p);
|
|
}
|
|
else if (STRNCMP(p, "min:", 4) == 0 && VIM_ISDIGIT(p[4]))
|
|
{
|
|
p += 4;
|
|
bri_min = getdigits(&p);
|
|
}
|
|
else if (STRNCMP(p, "sbr", 3) == 0)
|
|
{
|
|
p += 3;
|
|
bri_sbr = TRUE;
|
|
}
|
|
else if (STRNCMP(p, "list:", 5) == 0)
|
|
{
|
|
p += 5;
|
|
bri_list = getdigits(&p);
|
|
}
|
|
else if (STRNCMP(p, "column:", 7) == 0)
|
|
{
|
|
p += 7;
|
|
bri_vcol = getdigits(&p);
|
|
}
|
|
if (*p != ',' && *p != NUL)
|
|
return FAIL;
|
|
if (*p == ',')
|
|
++p;
|
|
}
|
|
|
|
wp->w_briopt_shift = bri_shift;
|
|
wp->w_briopt_min = bri_min;
|
|
wp->w_briopt_sbr = bri_sbr;
|
|
wp->w_briopt_list = bri_list;
|
|
wp->w_briopt_vcol = bri_vcol;
|
|
|
|
return OK;
|
|
}
|
|
|
|
/*
|
|
* Return appropriate space number for breakindent, taking influencing
|
|
* parameters into account. Window must be specified, since it is not
|
|
* necessarily always the current one.
|
|
*/
|
|
int
|
|
get_breakindent_win(
|
|
win_T *wp,
|
|
char_u *line) // start of the line
|
|
{
|
|
static int prev_indent = 0; // cached indent value
|
|
static long prev_ts = 0L; // cached tabstop value
|
|
static int prev_fnum = 0; // cached buffer number
|
|
static char_u *prev_line = NULL; // cached copy of "line"
|
|
static varnumber_T prev_tick = 0; // changedtick of cached value
|
|
# ifdef FEAT_VARTABS
|
|
static int *prev_vts = NULL; // cached vartabs values
|
|
# endif
|
|
static int prev_list = 0; // cached list value
|
|
static int prev_listopt = 0; // cached w_p_briopt_list value
|
|
// cached formatlistpat value
|
|
static char_u *prev_flp = NULL;
|
|
int bri = 0;
|
|
// window width minus window margin space, i.e. what rests for text
|
|
const int eff_wwidth = wp->w_width
|
|
- ((wp->w_p_nu || wp->w_p_rnu)
|
|
&& (vim_strchr(p_cpo, CPO_NUMCOL) == NULL)
|
|
? number_width(wp) + 1 : 0);
|
|
|
|
// used cached indent, unless
|
|
// - buffer changed
|
|
// - 'tabstop' changed
|
|
// - buffer was changed
|
|
// - 'briopt_list changed' changed or
|
|
// - 'formatlistpattern' changed
|
|
// - line changed
|
|
// - 'vartabs' changed
|
|
if (prev_fnum != wp->w_buffer->b_fnum
|
|
|| prev_ts != wp->w_buffer->b_p_ts
|
|
|| prev_tick != CHANGEDTICK(wp->w_buffer)
|
|
|| prev_listopt != wp->w_briopt_list
|
|
|| prev_flp == NULL
|
|
|| STRCMP(prev_flp, get_flp_value(wp->w_buffer)) != 0
|
|
|| prev_line == NULL || STRCMP(prev_line, line) != 0
|
|
# ifdef FEAT_VARTABS
|
|
|| prev_vts != wp->w_buffer->b_p_vts_array
|
|
# endif
|
|
)
|
|
{
|
|
prev_fnum = wp->w_buffer->b_fnum;
|
|
vim_free(prev_line);
|
|
prev_line = vim_strsave(line);
|
|
prev_ts = wp->w_buffer->b_p_ts;
|
|
prev_tick = CHANGEDTICK(wp->w_buffer);
|
|
# ifdef FEAT_VARTABS
|
|
prev_vts = wp->w_buffer->b_p_vts_array;
|
|
if (wp->w_briopt_vcol == 0)
|
|
prev_indent = get_indent_str_vtab(line,
|
|
(int)wp->w_buffer->b_p_ts,
|
|
wp->w_buffer->b_p_vts_array, wp->w_p_list);
|
|
# else
|
|
if (wp->w_briopt_vcol == 0)
|
|
prev_indent = get_indent_str(line,
|
|
(int)wp->w_buffer->b_p_ts, wp->w_p_list);
|
|
# endif
|
|
prev_listopt = wp->w_briopt_list;
|
|
prev_list = 0;
|
|
vim_free(prev_flp);
|
|
prev_flp = vim_strsave(get_flp_value(wp->w_buffer));
|
|
// add additional indent for numbered lists
|
|
if (wp->w_briopt_list != 0 && wp->w_briopt_vcol == 0)
|
|
{
|
|
regmatch_T regmatch;
|
|
|
|
regmatch.regprog = vim_regcomp(prev_flp,
|
|
RE_MAGIC + RE_STRING + RE_AUTO + RE_STRICT);
|
|
|
|
if (regmatch.regprog != NULL)
|
|
{
|
|
regmatch.rm_ic = FALSE;
|
|
if (vim_regexec(®match, line, 0))
|
|
{
|
|
if (wp->w_briopt_list > 0)
|
|
prev_list = wp->w_briopt_list;
|
|
else
|
|
prev_indent = (*regmatch.endp - *regmatch.startp);
|
|
}
|
|
vim_regfree(regmatch.regprog);
|
|
}
|
|
}
|
|
}
|
|
if (wp->w_briopt_vcol != 0)
|
|
{
|
|
// column value has priority
|
|
bri = wp->w_briopt_vcol;
|
|
prev_list = 0;
|
|
}
|
|
else
|
|
bri = prev_indent + wp->w_briopt_shift;
|
|
|
|
// Add offset for number column, if 'n' is in 'cpoptions'
|
|
bri += win_col_off2(wp);
|
|
|
|
// add additional indent for numbered lists
|
|
if (wp->w_briopt_list > 0)
|
|
bri += prev_list;
|
|
|
|
// indent minus the length of the showbreak string
|
|
if (wp->w_briopt_sbr)
|
|
bri -= vim_strsize(get_showbreak_value(wp));
|
|
|
|
|
|
// never indent past left window margin
|
|
if (bri < 0)
|
|
bri = 0;
|
|
|
|
// always leave at least bri_min characters on the left,
|
|
// if text width is sufficient
|
|
else if (bri > eff_wwidth - wp->w_briopt_min)
|
|
bri = (eff_wwidth - wp->w_briopt_min < 0)
|
|
? 0 : eff_wwidth - wp->w_briopt_min;
|
|
|
|
return bri;
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
* When extra == 0: Return TRUE if the cursor is before or on the first
|
|
* non-blank in the line.
|
|
* When extra == 1: Return TRUE if the cursor is before the first non-blank in
|
|
* the line.
|
|
*/
|
|
int
|
|
inindent(int extra)
|
|
{
|
|
char_u *ptr;
|
|
colnr_T col;
|
|
|
|
for (col = 0, ptr = ml_get_curline(); VIM_ISWHITE(*ptr); ++col)
|
|
++ptr;
|
|
if (col >= curwin->w_cursor.col + extra)
|
|
return TRUE;
|
|
else
|
|
return FALSE;
|
|
}
|
|
|
|
/*
|
|
* op_reindent - handle reindenting a block of lines.
|
|
*/
|
|
void
|
|
op_reindent(oparg_T *oap, int (*how)(void))
|
|
{
|
|
long i = 0;
|
|
char_u *l;
|
|
int amount;
|
|
linenr_T first_changed = 0;
|
|
linenr_T last_changed = 0;
|
|
linenr_T start_lnum = curwin->w_cursor.lnum;
|
|
|
|
// Don't even try when 'modifiable' is off.
|
|
if (!curbuf->b_p_ma)
|
|
{
|
|
emsg(_(e_cannot_make_changes_modifiable_is_off));
|
|
return;
|
|
}
|
|
|
|
// Save for undo. Do this once for all lines, much faster than doing this
|
|
// for each line separately, especially when undoing.
|
|
if (u_savecommon(start_lnum - 1, start_lnum + oap->line_count,
|
|
start_lnum + oap->line_count, FALSE) == OK)
|
|
for (i = oap->line_count; --i >= 0 && !got_int; )
|
|
{
|
|
// it's a slow thing to do, so give feedback so there's no worry
|
|
// that the computer's just hung.
|
|
|
|
if (i > 1
|
|
&& (i % 50 == 0 || i == oap->line_count - 1)
|
|
&& oap->line_count > p_report)
|
|
smsg(_("%ld lines to indent... "), i);
|
|
|
|
// Be vi-compatible: For lisp indenting the first line is not
|
|
// indented, unless there is only one line.
|
|
if (i != oap->line_count - 1 || oap->line_count == 1
|
|
|| how != get_lisp_indent)
|
|
{
|
|
l = skipwhite(ml_get_curline());
|
|
if (*l == NUL) // empty or blank line
|
|
amount = 0;
|
|
else
|
|
amount = how(); // get the indent for this line
|
|
|
|
if (amount >= 0 && set_indent(amount, 0))
|
|
{
|
|
// did change the indent, call changed_lines() later
|
|
if (first_changed == 0)
|
|
first_changed = curwin->w_cursor.lnum;
|
|
last_changed = curwin->w_cursor.lnum;
|
|
}
|
|
}
|
|
++curwin->w_cursor.lnum;
|
|
curwin->w_cursor.col = 0; // make sure it's valid
|
|
}
|
|
|
|
// put cursor on first non-blank of indented line
|
|
curwin->w_cursor.lnum = start_lnum;
|
|
beginline(BL_SOL | BL_FIX);
|
|
|
|
// Mark changed lines so that they will be redrawn. When Visual
|
|
// highlighting was present, need to continue until the last line. When
|
|
// there is no change still need to remove the Visual highlighting.
|
|
if (last_changed != 0)
|
|
changed_lines(first_changed, 0,
|
|
oap->is_VIsual ? start_lnum + oap->line_count :
|
|
last_changed + 1, 0L);
|
|
else if (oap->is_VIsual)
|
|
redraw_curbuf_later(UPD_INVERTED);
|
|
|
|
if (oap->line_count > p_report)
|
|
{
|
|
i = oap->line_count - (i + 1);
|
|
smsg(NGETTEXT("%ld line indented ",
|
|
"%ld lines indented ", i), i);
|
|
}
|
|
if ((cmdmod.cmod_flags & CMOD_LOCKMARKS) == 0)
|
|
{
|
|
// set '[ and '] marks
|
|
curbuf->b_op_start = oap->start;
|
|
curbuf->b_op_end = oap->end;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Return TRUE if lines starting with '#' should be left aligned.
|
|
*/
|
|
int
|
|
preprocs_left(void)
|
|
{
|
|
return
|
|
(curbuf->b_p_si && !curbuf->b_p_cin) ||
|
|
(curbuf->b_p_cin && in_cinkeys('#', ' ', TRUE)
|
|
&& curbuf->b_ind_hash_comment == 0)
|
|
;
|
|
}
|
|
|
|
/*
|
|
* Return TRUE if the conditions are OK for smart indenting.
|
|
*/
|
|
int
|
|
may_do_si(void)
|
|
{
|
|
return curbuf->b_p_si
|
|
&& !curbuf->b_p_cin
|
|
# ifdef FEAT_EVAL
|
|
&& *curbuf->b_p_inde == NUL
|
|
# endif
|
|
&& !p_paste;
|
|
}
|
|
|
|
/*
|
|
* Try to do some very smart auto-indenting.
|
|
* Used when inserting a "normal" character.
|
|
*/
|
|
void
|
|
ins_try_si(int c)
|
|
{
|
|
pos_T *pos, old_pos;
|
|
char_u *ptr;
|
|
int i;
|
|
int temp;
|
|
|
|
// do some very smart indenting when entering '{' or '}'
|
|
if (((did_si || can_si_back) && c == '{')
|
|
|| (can_si && c == '}' && inindent(0)))
|
|
{
|
|
// for '}' set indent equal to indent of line containing matching '{'
|
|
if (c == '}' && (pos = findmatch(NULL, '{')) != NULL)
|
|
{
|
|
old_pos = curwin->w_cursor;
|
|
// If the matching '{' has a ')' immediately before it (ignoring
|
|
// white-space), then line up with the start of the line
|
|
// containing the matching '(' if there is one. This handles the
|
|
// case where an "if (..\n..) {" statement continues over multiple
|
|
// lines -- webb
|
|
ptr = ml_get(pos->lnum);
|
|
i = pos->col;
|
|
if (i > 0) // skip blanks before '{'
|
|
while (--i > 0 && VIM_ISWHITE(ptr[i]))
|
|
;
|
|
curwin->w_cursor.lnum = pos->lnum;
|
|
curwin->w_cursor.col = i;
|
|
if (ptr[i] == ')' && (pos = findmatch(NULL, '(')) != NULL)
|
|
curwin->w_cursor = *pos;
|
|
i = get_indent();
|
|
curwin->w_cursor = old_pos;
|
|
if (State & VREPLACE_FLAG)
|
|
change_indent(INDENT_SET, i, FALSE, NUL, TRUE);
|
|
else
|
|
(void)set_indent(i, SIN_CHANGED);
|
|
}
|
|
else if (curwin->w_cursor.col > 0)
|
|
{
|
|
// when inserting '{' after "O" reduce indent, but not
|
|
// more than indent of previous line
|
|
temp = TRUE;
|
|
if (c == '{' && can_si_back && curwin->w_cursor.lnum > 1)
|
|
{
|
|
old_pos = curwin->w_cursor;
|
|
i = get_indent();
|
|
while (curwin->w_cursor.lnum > 1)
|
|
{
|
|
ptr = skipwhite(ml_get(--(curwin->w_cursor.lnum)));
|
|
|
|
// ignore empty lines and lines starting with '#'.
|
|
if (*ptr != '#' && *ptr != NUL)
|
|
break;
|
|
}
|
|
if (get_indent() >= i)
|
|
temp = FALSE;
|
|
curwin->w_cursor = old_pos;
|
|
}
|
|
if (temp)
|
|
shift_line(TRUE, FALSE, 1, TRUE);
|
|
}
|
|
}
|
|
|
|
// set indent of '#' always to 0
|
|
if (curwin->w_cursor.col > 0 && can_si && c == '#' && inindent(0))
|
|
{
|
|
// remember current indent for next line
|
|
old_indent = get_indent();
|
|
(void)set_indent(0, SIN_CHANGED);
|
|
}
|
|
|
|
// Adjust ai_col, the char at this position can be deleted.
|
|
if (ai_col > curwin->w_cursor.col)
|
|
ai_col = curwin->w_cursor.col;
|
|
}
|
|
|
|
/*
|
|
* Insert an indent (for <Tab> or CTRL-T) or delete an indent (for CTRL-D).
|
|
* Keep the cursor on the same character.
|
|
* type == INDENT_INC increase indent (for CTRL-T or <Tab>)
|
|
* type == INDENT_DEC decrease indent (for CTRL-D)
|
|
* type == INDENT_SET set indent to "amount"
|
|
* if round is TRUE, round the indent to 'shiftwidth' (only with _INC and _Dec).
|
|
*/
|
|
void
|
|
change_indent(
|
|
int type,
|
|
int amount,
|
|
int round,
|
|
int replaced, // replaced character, put on replace stack
|
|
int call_changed_bytes) // call changed_bytes()
|
|
{
|
|
int vcol;
|
|
int last_vcol;
|
|
int insstart_less; // reduction for Insstart.col
|
|
int new_cursor_col;
|
|
int i;
|
|
char_u *ptr;
|
|
int save_p_list;
|
|
int start_col;
|
|
colnr_T vc;
|
|
colnr_T orig_col = 0; // init for GCC
|
|
char_u *new_line, *orig_line = NULL; // init for GCC
|
|
|
|
// MODE_VREPLACE state needs to know what the line was like before changing
|
|
if (State & VREPLACE_FLAG)
|
|
{
|
|
orig_line = vim_strsave(ml_get_curline()); // Deal with NULL below
|
|
orig_col = curwin->w_cursor.col;
|
|
}
|
|
|
|
// for the following tricks we don't want list mode
|
|
save_p_list = curwin->w_p_list;
|
|
curwin->w_p_list = FALSE;
|
|
#ifdef FEAT_PROP_POPUP
|
|
ignore_text_props = TRUE;
|
|
#endif
|
|
vc = getvcol_nolist(&curwin->w_cursor);
|
|
vcol = vc;
|
|
|
|
// For Replace mode we need to fix the replace stack later, which is only
|
|
// possible when the cursor is in the indent. Remember the number of
|
|
// characters before the cursor if it's possible.
|
|
start_col = curwin->w_cursor.col;
|
|
|
|
// determine offset from first non-blank
|
|
new_cursor_col = curwin->w_cursor.col;
|
|
beginline(BL_WHITE);
|
|
new_cursor_col -= curwin->w_cursor.col;
|
|
|
|
insstart_less = curwin->w_cursor.col;
|
|
|
|
// If the cursor is in the indent, compute how many screen columns the
|
|
// cursor is to the left of the first non-blank.
|
|
if (new_cursor_col < 0)
|
|
vcol = get_indent() - vcol;
|
|
|
|
if (new_cursor_col > 0) // can't fix replace stack
|
|
start_col = -1;
|
|
|
|
// Set the new indent. The cursor will be put on the first non-blank.
|
|
if (type == INDENT_SET)
|
|
(void)set_indent(amount, call_changed_bytes ? SIN_CHANGED : 0);
|
|
else
|
|
{
|
|
int save_State = State;
|
|
|
|
// Avoid being called recursively.
|
|
if (State & VREPLACE_FLAG)
|
|
State = MODE_INSERT;
|
|
shift_line(type == INDENT_DEC, round, 1, call_changed_bytes);
|
|
State = save_State;
|
|
}
|
|
insstart_less -= curwin->w_cursor.col;
|
|
|
|
// Try to put cursor on same character.
|
|
// If the cursor is at or after the first non-blank in the line,
|
|
// compute the cursor column relative to the column of the first
|
|
// non-blank character.
|
|
// If we are not in insert mode, leave the cursor on the first non-blank.
|
|
// If the cursor is before the first non-blank, position it relative
|
|
// to the first non-blank, counted in screen columns.
|
|
if (new_cursor_col >= 0)
|
|
{
|
|
// When changing the indent while the cursor is touching it, reset
|
|
// Insstart_col to 0.
|
|
if (new_cursor_col == 0)
|
|
insstart_less = MAXCOL;
|
|
new_cursor_col += curwin->w_cursor.col;
|
|
}
|
|
else if (!(State & MODE_INSERT))
|
|
new_cursor_col = curwin->w_cursor.col;
|
|
else
|
|
{
|
|
chartabsize_T cts;
|
|
|
|
// Compute the screen column where the cursor should be.
|
|
vcol = get_indent() - vcol;
|
|
curwin->w_virtcol = (colnr_T)((vcol < 0) ? 0 : vcol);
|
|
|
|
// Advance the cursor until we reach the right screen column.
|
|
last_vcol = 0;
|
|
ptr = ml_get_curline();
|
|
init_chartabsize_arg(&cts, curwin, 0, 0, ptr, ptr);
|
|
while (cts.cts_vcol <= (int)curwin->w_virtcol)
|
|
{
|
|
last_vcol = cts.cts_vcol;
|
|
if (cts.cts_vcol > 0)
|
|
MB_PTR_ADV(cts.cts_ptr);
|
|
if (*cts.cts_ptr == NUL)
|
|
break;
|
|
cts.cts_vcol += lbr_chartabsize(&cts);
|
|
}
|
|
vcol = last_vcol;
|
|
new_cursor_col = cts.cts_ptr - cts.cts_line;
|
|
clear_chartabsize_arg(&cts);
|
|
|
|
// May need to insert spaces to be able to position the cursor on
|
|
// the right screen column.
|
|
if (vcol != (int)curwin->w_virtcol)
|
|
{
|
|
curwin->w_cursor.col = (colnr_T)new_cursor_col;
|
|
i = (int)curwin->w_virtcol - vcol;
|
|
ptr = alloc(i + 1);
|
|
if (ptr != NULL)
|
|
{
|
|
new_cursor_col += i;
|
|
ptr[i] = NUL;
|
|
while (--i >= 0)
|
|
ptr[i] = ' ';
|
|
ins_str(ptr);
|
|
vim_free(ptr);
|
|
}
|
|
}
|
|
|
|
// When changing the indent while the cursor is in it, reset
|
|
// Insstart_col to 0.
|
|
insstart_less = MAXCOL;
|
|
}
|
|
|
|
curwin->w_p_list = save_p_list;
|
|
|
|
if (new_cursor_col <= 0)
|
|
curwin->w_cursor.col = 0;
|
|
else
|
|
curwin->w_cursor.col = (colnr_T)new_cursor_col;
|
|
curwin->w_set_curswant = TRUE;
|
|
changed_cline_bef_curs();
|
|
|
|
// May have to adjust the start of the insert.
|
|
if (State & MODE_INSERT)
|
|
{
|
|
if (curwin->w_cursor.lnum == Insstart.lnum && Insstart.col != 0)
|
|
{
|
|
if ((int)Insstart.col <= insstart_less)
|
|
Insstart.col = 0;
|
|
else
|
|
Insstart.col -= insstart_less;
|
|
}
|
|
if ((int)ai_col <= insstart_less)
|
|
ai_col = 0;
|
|
else
|
|
ai_col -= insstart_less;
|
|
}
|
|
|
|
// For MODE_REPLACE state, may have to fix the replace stack, if it's
|
|
// possible. If the number of characters before the cursor decreased, need
|
|
// to pop a few characters from the replace stack.
|
|
// If the number of characters before the cursor increased, need to push a
|
|
// few NULs onto the replace stack.
|
|
if (REPLACE_NORMAL(State) && start_col >= 0)
|
|
{
|
|
while (start_col > (int)curwin->w_cursor.col)
|
|
{
|
|
replace_join(0); // remove a NUL from the replace stack
|
|
--start_col;
|
|
}
|
|
while (start_col < (int)curwin->w_cursor.col || replaced)
|
|
{
|
|
replace_push(NUL);
|
|
if (replaced)
|
|
{
|
|
replace_push(replaced);
|
|
replaced = NUL;
|
|
}
|
|
++start_col;
|
|
}
|
|
}
|
|
#ifdef FEAT_PROP_POPUP
|
|
ignore_text_props = FALSE;
|
|
#endif
|
|
|
|
// For MODE_VREPLACE state, we also have to fix the replace stack. In this
|
|
// case it is always possible because we backspace over the whole line and
|
|
// then put it back again the way we wanted it.
|
|
if (State & VREPLACE_FLAG)
|
|
{
|
|
// If orig_line didn't allocate, just return. At least we did the job,
|
|
// even if you can't backspace.
|
|
if (orig_line == NULL)
|
|
return;
|
|
|
|
// Save new line
|
|
new_line = vim_strsave(ml_get_curline());
|
|
if (new_line == NULL)
|
|
return;
|
|
|
|
// We only put back the new line up to the cursor
|
|
new_line[curwin->w_cursor.col] = NUL;
|
|
|
|
// Put back original line
|
|
ml_replace(curwin->w_cursor.lnum, orig_line, FALSE);
|
|
curwin->w_cursor.col = orig_col;
|
|
|
|
// Backspace from cursor to start of line
|
|
backspace_until_column(0);
|
|
|
|
// Insert new stuff into line again
|
|
ins_bytes(new_line);
|
|
|
|
vim_free(new_line);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Copy the indent from ptr to the current line (and fill to size)
|
|
* Leaves the cursor on the first non-blank in the line.
|
|
* Returns TRUE if the line was changed.
|
|
*/
|
|
int
|
|
copy_indent(int size, char_u *src)
|
|
{
|
|
char_u *p = NULL;
|
|
char_u *line = NULL;
|
|
char_u *s;
|
|
int todo;
|
|
int ind_len;
|
|
int line_len = 0;
|
|
int tab_pad;
|
|
int ind_done;
|
|
int round;
|
|
#ifdef FEAT_VARTABS
|
|
int ind_col;
|
|
#endif
|
|
|
|
// Round 1: compute the number of characters needed for the indent
|
|
// Round 2: copy the characters.
|
|
for (round = 1; round <= 2; ++round)
|
|
{
|
|
todo = size;
|
|
ind_len = 0;
|
|
ind_done = 0;
|
|
#ifdef FEAT_VARTABS
|
|
ind_col = 0;
|
|
#endif
|
|
s = src;
|
|
|
|
// Count/copy the usable portion of the source line
|
|
while (todo > 0 && VIM_ISWHITE(*s))
|
|
{
|
|
if (*s == TAB)
|
|
{
|
|
#ifdef FEAT_VARTABS
|
|
tab_pad = tabstop_padding(ind_done, curbuf->b_p_ts,
|
|
curbuf->b_p_vts_array);
|
|
#else
|
|
tab_pad = (int)curbuf->b_p_ts
|
|
- (ind_done % (int)curbuf->b_p_ts);
|
|
#endif
|
|
// Stop if this tab will overshoot the target
|
|
if (todo < tab_pad)
|
|
break;
|
|
todo -= tab_pad;
|
|
ind_done += tab_pad;
|
|
#ifdef FEAT_VARTABS
|
|
ind_col += tab_pad;
|
|
#endif
|
|
}
|
|
else
|
|
{
|
|
--todo;
|
|
++ind_done;
|
|
#ifdef FEAT_VARTABS
|
|
++ind_col;
|
|
#endif
|
|
}
|
|
++ind_len;
|
|
if (p != NULL)
|
|
*p++ = *s;
|
|
++s;
|
|
}
|
|
|
|
// Fill to next tabstop with a tab, if possible
|
|
#ifdef FEAT_VARTABS
|
|
tab_pad = tabstop_padding(ind_done, curbuf->b_p_ts,
|
|
curbuf->b_p_vts_array);
|
|
#else
|
|
tab_pad = (int)curbuf->b_p_ts - (ind_done % (int)curbuf->b_p_ts);
|
|
#endif
|
|
if (todo >= tab_pad && !curbuf->b_p_et)
|
|
{
|
|
todo -= tab_pad;
|
|
++ind_len;
|
|
#ifdef FEAT_VARTABS
|
|
ind_col += tab_pad;
|
|
#endif
|
|
if (p != NULL)
|
|
*p++ = TAB;
|
|
}
|
|
|
|
// Add tabs required for indent
|
|
if (!curbuf->b_p_et)
|
|
{
|
|
#ifdef FEAT_VARTABS
|
|
for (;;)
|
|
{
|
|
tab_pad = tabstop_padding(ind_col, curbuf->b_p_ts,
|
|
curbuf->b_p_vts_array);
|
|
if (todo < tab_pad)
|
|
break;
|
|
todo -= tab_pad;
|
|
++ind_len;
|
|
ind_col += tab_pad;
|
|
if (p != NULL)
|
|
*p++ = TAB;
|
|
}
|
|
#else
|
|
while (todo >= (int)curbuf->b_p_ts)
|
|
{
|
|
todo -= (int)curbuf->b_p_ts;
|
|
++ind_len;
|
|
if (p != NULL)
|
|
*p++ = TAB;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
// Count/add spaces required for indent
|
|
while (todo > 0)
|
|
{
|
|
--todo;
|
|
++ind_len;
|
|
if (p != NULL)
|
|
*p++ = ' ';
|
|
}
|
|
|
|
if (p == NULL)
|
|
{
|
|
// Allocate memory for the result: the copied indent, new indent
|
|
// and the rest of the line.
|
|
line_len = (int)STRLEN(ml_get_curline()) + 1;
|
|
line = alloc(ind_len + line_len);
|
|
if (line == NULL)
|
|
return FALSE;
|
|
p = line;
|
|
}
|
|
}
|
|
|
|
// Append the original line
|
|
mch_memmove(p, ml_get_curline(), (size_t)line_len);
|
|
|
|
// Replace the line
|
|
ml_replace(curwin->w_cursor.lnum, line, FALSE);
|
|
|
|
// Put the cursor after the indent.
|
|
curwin->w_cursor.col = ind_len;
|
|
return TRUE;
|
|
}
|
|
|
|
/*
|
|
* Give a "resulting text too long" error and maybe set got_int.
|
|
*/
|
|
static void
|
|
emsg_text_too_long(void)
|
|
{
|
|
emsg(_(e_resulting_text_too_long));
|
|
#ifdef FEAT_EVAL
|
|
// when not inside a try/catch set got_int to break out of any loop
|
|
if (trylevel == 0)
|
|
#endif
|
|
got_int = TRUE;
|
|
}
|
|
|
|
/*
|
|
* ":retab".
|
|
*/
|
|
void
|
|
ex_retab(exarg_T *eap)
|
|
{
|
|
linenr_T lnum;
|
|
int got_tab = FALSE;
|
|
long num_spaces = 0;
|
|
long num_tabs;
|
|
long len;
|
|
long col;
|
|
long vcol;
|
|
long start_col = 0; // For start of white-space string
|
|
long start_vcol = 0; // For start of white-space string
|
|
long old_len;
|
|
long new_len;
|
|
char_u *ptr;
|
|
char_u *new_line = (char_u *)1; // init to non-NULL
|
|
int did_undo; // called u_save for current line
|
|
#ifdef FEAT_VARTABS
|
|
int *new_vts_array = NULL;
|
|
char_u *new_ts_str; // string value of tab argument
|
|
#else
|
|
int temp;
|
|
int new_ts;
|
|
#endif
|
|
int save_list;
|
|
linenr_T first_line = 0; // first changed line
|
|
linenr_T last_line = 0; // last changed line
|
|
|
|
save_list = curwin->w_p_list;
|
|
curwin->w_p_list = 0; // don't want list mode here
|
|
|
|
#ifdef FEAT_VARTABS
|
|
new_ts_str = eap->arg;
|
|
if (tabstop_set(eap->arg, &new_vts_array) == FAIL)
|
|
return;
|
|
while (vim_isdigit(*(eap->arg)) || *(eap->arg) == ',')
|
|
++(eap->arg);
|
|
|
|
// This ensures that either new_vts_array and new_ts_str are freshly
|
|
// allocated, or new_vts_array points to an existing array and new_ts_str
|
|
// is null.
|
|
if (new_vts_array == NULL)
|
|
{
|
|
new_vts_array = curbuf->b_p_vts_array;
|
|
new_ts_str = NULL;
|
|
}
|
|
else
|
|
new_ts_str = vim_strnsave(new_ts_str, eap->arg - new_ts_str);
|
|
#else
|
|
ptr = eap->arg;
|
|
new_ts = getdigits(&ptr);
|
|
if (new_ts < 0 && *eap->arg == '-')
|
|
{
|
|
emsg(_(e_argument_must_be_positive));
|
|
return;
|
|
}
|
|
if (new_ts < 0 || new_ts > TABSTOP_MAX)
|
|
{
|
|
semsg(_(e_invalid_argument_str), eap->arg);
|
|
return;
|
|
}
|
|
if (new_ts == 0)
|
|
new_ts = curbuf->b_p_ts;
|
|
#endif
|
|
for (lnum = eap->line1; !got_int && lnum <= eap->line2; ++lnum)
|
|
{
|
|
ptr = ml_get(lnum);
|
|
col = 0;
|
|
vcol = 0;
|
|
did_undo = FALSE;
|
|
for (;;)
|
|
{
|
|
if (VIM_ISWHITE(ptr[col]))
|
|
{
|
|
if (!got_tab && num_spaces == 0)
|
|
{
|
|
// First consecutive white-space
|
|
start_vcol = vcol;
|
|
start_col = col;
|
|
}
|
|
if (ptr[col] == ' ')
|
|
num_spaces++;
|
|
else
|
|
got_tab = TRUE;
|
|
}
|
|
else
|
|
{
|
|
if (got_tab || (eap->forceit && num_spaces > 1))
|
|
{
|
|
// Retabulate this string of white-space
|
|
|
|
// len is virtual length of white string
|
|
len = num_spaces = vcol - start_vcol;
|
|
num_tabs = 0;
|
|
if (!curbuf->b_p_et)
|
|
{
|
|
#ifdef FEAT_VARTABS
|
|
int t, s;
|
|
|
|
tabstop_fromto(start_vcol, vcol,
|
|
curbuf->b_p_ts, new_vts_array, &t, &s);
|
|
num_tabs = t;
|
|
num_spaces = s;
|
|
#else
|
|
temp = new_ts - (start_vcol % new_ts);
|
|
if (num_spaces >= temp)
|
|
{
|
|
num_spaces -= temp;
|
|
num_tabs++;
|
|
}
|
|
num_tabs += num_spaces / new_ts;
|
|
num_spaces -= (num_spaces / new_ts) * new_ts;
|
|
#endif
|
|
}
|
|
if (curbuf->b_p_et || got_tab ||
|
|
(num_spaces + num_tabs < len))
|
|
{
|
|
if (did_undo == FALSE)
|
|
{
|
|
did_undo = TRUE;
|
|
if (u_save((linenr_T)(lnum - 1),
|
|
(linenr_T)(lnum + 1)) == FAIL)
|
|
{
|
|
new_line = NULL; // flag out-of-memory
|
|
break;
|
|
}
|
|
}
|
|
|
|
// len is actual number of white characters used
|
|
len = num_spaces + num_tabs;
|
|
old_len = (long)STRLEN(ptr);
|
|
new_len = old_len - col + start_col + len + 1;
|
|
if (new_len <= 0 || new_len >= MAXCOL)
|
|
{
|
|
emsg_text_too_long();
|
|
break;
|
|
}
|
|
new_line = alloc(new_len);
|
|
if (new_line == NULL)
|
|
break;
|
|
if (start_col > 0)
|
|
mch_memmove(new_line, ptr, (size_t)start_col);
|
|
mch_memmove(new_line + start_col + len,
|
|
ptr + col, (size_t)(old_len - col + 1));
|
|
ptr = new_line + start_col;
|
|
for (col = 0; col < len; col++)
|
|
ptr[col] = (col < num_tabs) ? '\t' : ' ';
|
|
if (ml_replace(lnum, new_line, FALSE) == OK)
|
|
// "new_line" may have been copied
|
|
new_line = curbuf->b_ml.ml_line_ptr;
|
|
if (first_line == 0)
|
|
first_line = lnum;
|
|
last_line = lnum;
|
|
ptr = new_line;
|
|
col = start_col + len;
|
|
}
|
|
}
|
|
got_tab = FALSE;
|
|
num_spaces = 0;
|
|
}
|
|
if (ptr[col] == NUL)
|
|
break;
|
|
vcol += chartabsize(ptr + col, (colnr_T)vcol);
|
|
if (vcol >= MAXCOL)
|
|
{
|
|
emsg_text_too_long();
|
|
break;
|
|
}
|
|
if (has_mbyte)
|
|
col += (*mb_ptr2len)(ptr + col);
|
|
else
|
|
++col;
|
|
}
|
|
if (new_line == NULL) // out of memory
|
|
break;
|
|
line_breakcheck();
|
|
}
|
|
if (got_int)
|
|
emsg(_(e_interrupted));
|
|
|
|
#ifdef FEAT_VARTABS
|
|
// If a single value was given then it can be considered equal to
|
|
// either the value of 'tabstop' or the value of 'vartabstop'.
|
|
if (tabstop_count(curbuf->b_p_vts_array) == 0
|
|
&& tabstop_count(new_vts_array) == 1
|
|
&& curbuf->b_p_ts == tabstop_first(new_vts_array))
|
|
; // not changed
|
|
else if (tabstop_count(curbuf->b_p_vts_array) > 0
|
|
&& tabstop_eq(curbuf->b_p_vts_array, new_vts_array))
|
|
; // not changed
|
|
else
|
|
redraw_curbuf_later(UPD_NOT_VALID);
|
|
#else
|
|
if (curbuf->b_p_ts != new_ts)
|
|
redraw_curbuf_later(UPD_NOT_VALID);
|
|
#endif
|
|
if (first_line != 0)
|
|
changed_lines(first_line, 0, last_line + 1, 0L);
|
|
|
|
curwin->w_p_list = save_list; // restore 'list'
|
|
|
|
#ifdef FEAT_VARTABS
|
|
if (new_ts_str != NULL) // set the new tabstop
|
|
{
|
|
// If 'vartabstop' is in use or if the value given to retab has more
|
|
// than one tabstop then update 'vartabstop'.
|
|
int *old_vts_ary = curbuf->b_p_vts_array;
|
|
|
|
if (tabstop_count(old_vts_ary) > 0 || tabstop_count(new_vts_array) > 1)
|
|
{
|
|
set_string_option_direct((char_u *)"vts", -1, new_ts_str,
|
|
OPT_FREE|OPT_LOCAL, 0);
|
|
curbuf->b_p_vts_array = new_vts_array;
|
|
vim_free(old_vts_ary);
|
|
}
|
|
else
|
|
{
|
|
// 'vartabstop' wasn't in use and a single value was given to
|
|
// retab then update 'tabstop'.
|
|
curbuf->b_p_ts = tabstop_first(new_vts_array);
|
|
vim_free(new_vts_array);
|
|
}
|
|
vim_free(new_ts_str);
|
|
}
|
|
#else
|
|
curbuf->b_p_ts = new_ts;
|
|
#endif
|
|
coladvance(curwin->w_curswant);
|
|
|
|
u_clearline();
|
|
}
|
|
|
|
#if defined(FEAT_EVAL) || defined(PROTO)
|
|
/*
|
|
* Get indent level from 'indentexpr'.
|
|
*/
|
|
int
|
|
get_expr_indent(void)
|
|
{
|
|
int indent = -1;
|
|
char_u *inde_copy;
|
|
pos_T save_pos;
|
|
colnr_T save_curswant;
|
|
int save_set_curswant;
|
|
int save_State;
|
|
int use_sandbox = was_set_insecurely((char_u *)"indentexpr",
|
|
OPT_LOCAL);
|
|
sctx_T save_sctx = current_sctx;
|
|
|
|
// Save and restore cursor position and curswant, in case it was changed
|
|
// via :normal commands
|
|
save_pos = curwin->w_cursor;
|
|
save_curswant = curwin->w_curswant;
|
|
save_set_curswant = curwin->w_set_curswant;
|
|
set_vim_var_nr(VV_LNUM, curwin->w_cursor.lnum);
|
|
if (use_sandbox)
|
|
++sandbox;
|
|
++textlock;
|
|
current_sctx = curbuf->b_p_script_ctx[BV_INDE];
|
|
|
|
// Need to make a copy, the 'indentexpr' option could be changed while
|
|
// evaluating it.
|
|
inde_copy = vim_strsave(curbuf->b_p_inde);
|
|
if (inde_copy != NULL)
|
|
{
|
|
indent = (int)eval_to_number(inde_copy, TRUE);
|
|
vim_free(inde_copy);
|
|
}
|
|
|
|
if (use_sandbox)
|
|
--sandbox;
|
|
--textlock;
|
|
current_sctx = save_sctx;
|
|
|
|
// Restore the cursor position so that 'indentexpr' doesn't need to.
|
|
// Pretend to be in Insert mode, allow cursor past end of line for "o"
|
|
// command.
|
|
save_State = State;
|
|
State = MODE_INSERT;
|
|
curwin->w_cursor = save_pos;
|
|
curwin->w_curswant = save_curswant;
|
|
curwin->w_set_curswant = save_set_curswant;
|
|
check_cursor();
|
|
State = save_State;
|
|
|
|
// Reset did_throw, unless 'debug' has "throw" and inside a try/catch.
|
|
if (did_throw && (vim_strchr(p_debug, 't') == NULL || trylevel == 0))
|
|
{
|
|
handle_did_throw();
|
|
did_throw = FALSE;
|
|
}
|
|
|
|
// If there is an error, just keep the current indent.
|
|
if (indent < 0)
|
|
indent = get_indent();
|
|
|
|
return indent;
|
|
}
|
|
#endif
|
|
|
|
static int
|
|
lisp_match(char_u *p)
|
|
{
|
|
char_u buf[LSIZE];
|
|
int len;
|
|
char_u *word = *curbuf->b_p_lw != NUL ? curbuf->b_p_lw : p_lispwords;
|
|
|
|
while (*word != NUL)
|
|
{
|
|
(void)copy_option_part(&word, buf, LSIZE, ",");
|
|
len = (int)STRLEN(buf);
|
|
if (STRNCMP(buf, p, len) == 0 && IS_WHITE_OR_NUL(p[len]))
|
|
return TRUE;
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
/*
|
|
* When 'p' is present in 'cpoptions, a Vi compatible method is used.
|
|
* The incompatible newer method is quite a bit better at indenting
|
|
* code in lisp-like languages than the traditional one; it's still
|
|
* mostly heuristics however -- Dirk van Deun, dirk@rave.org
|
|
*
|
|
* TODO:
|
|
* Findmatch() should be adapted for lisp, also to make showmatch
|
|
* work correctly: now (v5.3) it seems all C/C++ oriented:
|
|
* - it does not recognize the #\( and #\) notations as character literals
|
|
* - it doesn't know about comments starting with a semicolon
|
|
* - it incorrectly interprets '(' as a character literal
|
|
* All this messes up get_lisp_indent in some rare cases.
|
|
* Update from Sergey Khorev:
|
|
* I tried to fix the first two issues.
|
|
*/
|
|
int
|
|
get_lisp_indent(void)
|
|
{
|
|
pos_T *pos, realpos, paren;
|
|
int amount;
|
|
char_u *that;
|
|
colnr_T col;
|
|
colnr_T firsttry;
|
|
int parencount, quotecount;
|
|
int vi_lisp;
|
|
|
|
// Set vi_lisp to use the vi-compatible method
|
|
vi_lisp = (vim_strchr(p_cpo, CPO_LISP) != NULL);
|
|
|
|
realpos = curwin->w_cursor;
|
|
curwin->w_cursor.col = 0;
|
|
|
|
if ((pos = findmatch(NULL, '(')) == NULL)
|
|
pos = findmatch(NULL, '[');
|
|
else
|
|
{
|
|
paren = *pos;
|
|
pos = findmatch(NULL, '[');
|
|
if (pos == NULL || LT_POSP(pos, &paren))
|
|
pos = &paren;
|
|
}
|
|
if (pos != NULL)
|
|
{
|
|
// Extra trick: Take the indent of the first previous non-white
|
|
// line that is at the same () level.
|
|
amount = -1;
|
|
parencount = 0;
|
|
|
|
while (--curwin->w_cursor.lnum >= pos->lnum)
|
|
{
|
|
if (linewhite(curwin->w_cursor.lnum))
|
|
continue;
|
|
for (that = ml_get_curline(); *that != NUL; ++that)
|
|
{
|
|
if (*that == ';')
|
|
{
|
|
while (*(that + 1) != NUL)
|
|
++that;
|
|
continue;
|
|
}
|
|
if (*that == '\\')
|
|
{
|
|
if (*(that + 1) != NUL)
|
|
++that;
|
|
continue;
|
|
}
|
|
if (*that == '"' && *(that + 1) != NUL)
|
|
{
|
|
while (*++that && *that != '"')
|
|
{
|
|
// skipping escaped characters in the string
|
|
if (*that == '\\')
|
|
{
|
|
if (*++that == NUL)
|
|
break;
|
|
if (that[1] == NUL)
|
|
{
|
|
++that;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (*that == NUL)
|
|
break;
|
|
}
|
|
if (*that == '(' || *that == '[')
|
|
++parencount;
|
|
else if (*that == ')' || *that == ']')
|
|
--parencount;
|
|
}
|
|
if (parencount == 0)
|
|
{
|
|
amount = get_indent();
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (amount == -1)
|
|
{
|
|
curwin->w_cursor.lnum = pos->lnum;
|
|
curwin->w_cursor.col = pos->col;
|
|
col = pos->col;
|
|
|
|
that = ml_get_curline();
|
|
|
|
if (vi_lisp && get_indent() == 0)
|
|
amount = 2;
|
|
else
|
|
{
|
|
char_u *line = that;
|
|
chartabsize_T cts;
|
|
|
|
init_chartabsize_arg(&cts, curwin, pos->lnum, 0, line, line);
|
|
while (*cts.cts_ptr != NUL && col > 0)
|
|
{
|
|
cts.cts_vcol += lbr_chartabsize_adv(&cts);
|
|
col--;
|
|
}
|
|
amount = cts.cts_vcol;
|
|
that = cts.cts_ptr;
|
|
clear_chartabsize_arg(&cts);
|
|
|
|
// Some keywords require "body" indenting rules (the
|
|
// non-standard-lisp ones are Scheme special forms):
|
|
//
|
|
// (let ((a 1)) instead (let ((a 1))
|
|
// (...)) of (...))
|
|
|
|
if (!vi_lisp && (*that == '(' || *that == '[')
|
|
&& lisp_match(that + 1))
|
|
amount += 2;
|
|
else
|
|
{
|
|
if (*that != NUL)
|
|
{
|
|
that++;
|
|
amount++;
|
|
}
|
|
firsttry = amount;
|
|
|
|
init_chartabsize_arg(&cts, curwin, (colnr_T)(that - line),
|
|
amount, line, that);
|
|
while (VIM_ISWHITE(*cts.cts_ptr))
|
|
{
|
|
cts.cts_vcol += lbr_chartabsize(&cts);
|
|
++cts.cts_ptr;
|
|
}
|
|
that = cts.cts_ptr;
|
|
amount = cts.cts_vcol;
|
|
clear_chartabsize_arg(&cts);
|
|
|
|
if (*that && *that != ';') // not a comment line
|
|
{
|
|
// test *that != '(' to accommodate first let/do
|
|
// argument if it is more than one line
|
|
if (!vi_lisp && *that != '(' && *that != '[')
|
|
firsttry++;
|
|
|
|
parencount = 0;
|
|
quotecount = 0;
|
|
|
|
init_chartabsize_arg(&cts, curwin,
|
|
(colnr_T)(that - line), amount, line, that);
|
|
if (vi_lisp
|
|
|| (*that != '"'
|
|
&& *that != '\''
|
|
&& *that != '#'
|
|
&& (*that < '0' || *that > '9')))
|
|
{
|
|
while (*cts.cts_ptr
|
|
&& (!VIM_ISWHITE(*cts.cts_ptr)
|
|
|| quotecount
|
|
|| parencount)
|
|
&& (!((*cts.cts_ptr == '('
|
|
|| *cts.cts_ptr == '[')
|
|
&& !quotecount
|
|
&& !parencount
|
|
&& vi_lisp)))
|
|
{
|
|
if (*cts.cts_ptr == '"')
|
|
quotecount = !quotecount;
|
|
if ((*cts.cts_ptr == '(' || *cts.cts_ptr == '[')
|
|
&& !quotecount)
|
|
++parencount;
|
|
if ((*cts.cts_ptr == ')' || *cts.cts_ptr == ']')
|
|
&& !quotecount)
|
|
--parencount;
|
|
if (*cts.cts_ptr == '\\'
|
|
&& *(cts.cts_ptr+1) != NUL)
|
|
cts.cts_vcol += lbr_chartabsize_adv(&cts);
|
|
cts.cts_vcol += lbr_chartabsize_adv(&cts);
|
|
}
|
|
}
|
|
while (VIM_ISWHITE(*cts.cts_ptr))
|
|
{
|
|
cts.cts_vcol += lbr_chartabsize(&cts);
|
|
++cts.cts_ptr;
|
|
}
|
|
that = cts.cts_ptr;
|
|
amount = cts.cts_vcol;
|
|
clear_chartabsize_arg(&cts);
|
|
|
|
if (!*that || *that == ';')
|
|
amount = firsttry;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
amount = 0; // no matching '(' or '[' found, use zero indent
|
|
|
|
curwin->w_cursor = realpos;
|
|
|
|
return amount;
|
|
}
|
|
|
|
/*
|
|
* Re-indent the current line, based on the current contents of it and the
|
|
* surrounding lines. Fixing the cursor position seems really easy -- I'm very
|
|
* confused what all the part that handles Control-T is doing that I'm not.
|
|
* "get_the_indent" should be get_c_indent, get_expr_indent or get_lisp_indent.
|
|
*/
|
|
|
|
void
|
|
fixthisline(int (*get_the_indent)(void))
|
|
{
|
|
int amount = get_the_indent();
|
|
|
|
if (amount < 0)
|
|
return;
|
|
|
|
change_indent(INDENT_SET, amount, FALSE, 0, TRUE);
|
|
if (linewhite(curwin->w_cursor.lnum))
|
|
did_ai = TRUE; // delete the indent if the line stays empty
|
|
}
|
|
|
|
/*
|
|
* Return TRUE if 'indentexpr' should be used for Lisp indenting.
|
|
* Caller may want to check 'autoindent'.
|
|
*/
|
|
int
|
|
use_indentexpr_for_lisp(void)
|
|
{
|
|
#ifdef FEAT_EVAL
|
|
return curbuf->b_p_lisp
|
|
&& *curbuf->b_p_inde != NUL
|
|
&& STRCMP(curbuf->b_p_lop, "expr:1") == 0;
|
|
#else
|
|
return FALSE;
|
|
#endif
|
|
}
|
|
|
|
/*
|
|
* Fix indent for 'lisp' and 'cindent'.
|
|
*/
|
|
void
|
|
fix_indent(void)
|
|
{
|
|
if (p_paste)
|
|
return; // no auto-indenting when 'paste' is set
|
|
if (curbuf->b_p_lisp && curbuf->b_p_ai)
|
|
{
|
|
if (use_indentexpr_for_lisp())
|
|
do_c_expr_indent();
|
|
else
|
|
fixthisline(get_lisp_indent);
|
|
}
|
|
else if (cindent_on())
|
|
do_c_expr_indent();
|
|
}
|
|
|
|
#if defined(FEAT_EVAL) || defined(PROTO)
|
|
/*
|
|
* "indent()" function
|
|
*/
|
|
void
|
|
f_indent(typval_T *argvars, typval_T *rettv)
|
|
{
|
|
linenr_T lnum;
|
|
|
|
if (in_vim9script() && check_for_lnum_arg(argvars, 0) == FAIL)
|
|
return;
|
|
|
|
lnum = tv_get_lnum(argvars);
|
|
if (lnum >= 1 && lnum <= curbuf->b_ml.ml_line_count)
|
|
rettv->vval.v_number = get_indent_lnum(lnum);
|
|
else
|
|
{
|
|
if (in_vim9script())
|
|
semsg(_(e_invalid_line_number_nr), lnum);
|
|
rettv->vval.v_number = -1;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* "lispindent(lnum)" function
|
|
*/
|
|
void
|
|
f_lispindent(typval_T *argvars UNUSED, typval_T *rettv)
|
|
{
|
|
pos_T pos;
|
|
linenr_T lnum;
|
|
|
|
if (in_vim9script() && check_for_lnum_arg(argvars, 0) == FAIL)
|
|
return;
|
|
|
|
pos = curwin->w_cursor;
|
|
lnum = tv_get_lnum(argvars);
|
|
if (lnum >= 1 && lnum <= curbuf->b_ml.ml_line_count)
|
|
{
|
|
curwin->w_cursor.lnum = lnum;
|
|
rettv->vval.v_number = get_lisp_indent();
|
|
curwin->w_cursor = pos;
|
|
}
|
|
else if (in_vim9script())
|
|
semsg(_(e_invalid_line_number_nr), lnum);
|
|
else
|
|
rettv->vval.v_number = -1;
|
|
}
|
|
#endif
|