I implemented an option for Vim which modifies the way C indenting is performed
and the value returned by 'indentexpr' option affects indentation. The option
has been described in todo.txt:

7   Use Tabs for the indent of starting lines, pad with spaces for
    continuation lines.  Allows changing 'tabstop' without messing up the
    indents.

Basically, indenting the following piece of C code will yield:
- without the option set:
#v+
void func()
{
        if (opta)
                while (optb)
                        if (optc)
                                printf("opta == %d, optb == %d, optc == %d\n",
                                           opta,
                                           optb,
                                           optc);
}
// vim: ft=c ts=4 sw=4 cino=(0,l1
#v-

- with the option set:
#v+
void func2()
{
        if (opta)
                while (optb)
                        if (optc)
                                printf("opta == %d, optb == %d, optc == %d\n",
                                       opta,
                                       optb,
                                       optc);
// vim: ft=c ts=4 sw=4 cino=(0,l1 tabindent
}
#v-
If you setting of your tab stops, you will see how in the first example
the indentation becomes invalid.

If any readers of vim-dev find this option interesting, I would like to ask
them for help:
- Would you be so kind and review the code?
- I called the option 'tabindent' ('ti' for short). I don't like the name
        because it doesn't make obvious what the option does. At first I chose
        'tablevelindent', but this is hardly any better and seems long. What
        name would you suggest?
- There are so many options available for 'cinoptions'. It is probably
        impossible to test the interaction between 'ti' and all possible
        'cinoptions' values. Bug reports would be very appreciated.
- I wrote some documentation for 'ti'. I don't know if I should mention
        the option in any other place? Perhaps I mentioned it too many times?
        Perhaps the descriptions are unclear?
- It is possible to get indentation in Vim script by calling cindent()
        or lispindent(). Should the values returned by these functions change
        when 'ti' is set? Should I add new functions to the Vim scripting
        language to make the new indentation values available to the user?
- It is now possible to use the new indenting style when 'indentexpr' is
        set. I modified $VIMRUNTIME/indent/vim.vim to use the new indenting
        style if 'ti' is set. Perhaps you could update indent script for your
        favourite language and test it? For description refer to 'indentexpr'
        documentation.

What I know that remains to be done at this point:
- handle 'tabindent' for Lisp indenting,
- return lists when appropriate when cindent() or lispindent() is called
  from Vim script (?),
- add more tests.

The attached files include:
- tabindent-src.patch - patch against Vim 7.2.403 for the source code,
- tabindent-doc.patch - patch for the documentation,
- tabindent-runtime.patch - patch for script defining indentation for
        Vim scripting language indent/vim.vim,
- tabindent-tests.patch - tests for the new functionality.

Thank you in advance for your help!

-- 
Cheers,
Lech

-- 
You received this message from the "vim_dev" maillist.
Do not top-post! Type your reply below the text you are replying to.
For more information, visit http://www.vim.org/maillist.php

To unsubscribe from this group, send email to 
vim_dev+unsubscribegooglegroups.com or reply to this email with the words 
"REMOVE ME" as the subject.
diff --git a/src/edit.c b/src/edit.c
index 33e580f..42a42ad 100644
--- a/src/edit.c
+++ b/src/edit.c
@@ -1664,6 +1664,234 @@ undisplay_dollar()
 }
 
 /*
+ * A slightly simplified implementation of change_indent() for the case when
+ * 'tabindent' is set. Will try to set the indentation of a line to
+ * "level" tabs + ("amount" - "level" * 'ts') spaces.
+ */
+    static void
+change_indent_with_level(level, amount)
+    int		level;
+    int		amount;
+{
+    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;
+#ifdef FEAT_VREPLACE
+    colnr_T	orig_col = 0;		/* init for GCC */
+    char_u	*new_line, *orig_line = NULL;	/* init for GCC */
+
+    /* VREPLACE mode 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;
+    }
+#endif
+
+    /* for the following tricks we don't want list mode */
+    save_p_list = curwin->w_p_list;
+    curwin->w_p_list = FALSE;
+    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; /* first non-white character in line */
+
+    /*
+     * 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; /* distance from the original cursor position
+				     * to the first non-blank character in line */
+
+    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.
+     * NOTE: it seems that it will be on the last blank in the indent.
+     */
+    (void)set_indent_with_level(level, amount, 0);
+    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 & INSERT))
+	new_cursor_col = curwin->w_cursor.col;
+    else
+    {
+	/*
+	 * 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.
+	 */
+	vcol = last_vcol = 0;
+	new_cursor_col = -1;
+	ptr = ml_get_curline();
+	while (vcol <= (int)curwin->w_virtcol)
+	{
+	    last_vcol = vcol;
+#ifdef FEAT_MBYTE
+	    if (has_mbyte && new_cursor_col >= 0)
+		new_cursor_col += (*mb_ptr2len)(ptr + new_cursor_col);
+	    else
+#endif
+		++new_cursor_col;
+	    vcol += lbr_chartabsize(ptr + new_cursor_col, (colnr_T)vcol);
+	}
+	vcol = last_vcol;
+
+	/*
+	 * 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((unsigned)(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 & 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 REPLACE mode, 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)
+	{
+	    replace_push(NUL);
+	    ++start_col;
+	}
+    }
+
+#ifdef FEAT_VREPLACE
+    /*
+     * For VREPLACE mode, 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);
+    }
+#endif
+}
+
+/*
  * 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>)
@@ -7347,9 +7575,14 @@ cindent_on()
 
     void
 fixthisline(get_the_indent)
-    int (*get_the_indent) __ARGS((void));
+    int (*get_the_indent) __ARGS((int *plevel, int *pamount));
 {
-    change_indent(INDENT_SET, get_the_indent(), FALSE, 0, TRUE);
+    int level, amount;
+    if (INDENT_LVL_AMOUNT != get_the_indent(&level, &amount))
+	change_indent(INDENT_SET, amount, FALSE, 0, TRUE);
+    else
+	change_indent_with_level(level, amount);
+
     if (linewhite(curwin->w_cursor.lnum))
 	did_ai = TRUE;	    /* delete the indent if the line stays empty */
 }
diff --git a/src/eval.c b/src/eval.c
index ad127b5..98927c3 100644
--- a/src/eval.c
+++ b/src/eval.c
@@ -8889,8 +8889,13 @@ f_cindent(argvars, rettv)
     lnum = get_tv_lnum(argvars);
     if (lnum >= 1 && lnum <= curbuf->b_ml.ml_line_count)
     {
+	int level, amount;
 	curwin->w_cursor.lnum = lnum;
-	rettv->vval.v_number = get_c_indent();
+	get_c_indent(&level, &amount);
+	/* FIXME:2010-03-21:llorens: depending on the value returned by
+	 * get_c_indent() rettv->v_type should either be VAR_NUMBER or
+	 * VAR_LIST. */
+	rettv->vval.v_number = amount;
 	curwin->w_cursor = pos;
     }
     else
@@ -12924,8 +12929,13 @@ f_lispindent(argvars, rettv)
     lnum = get_tv_lnum(argvars);
     if (lnum >= 1 && lnum <= curbuf->b_ml.ml_line_count)
     {
+	int level, amount;
 	curwin->w_cursor.lnum = lnum;
-	rettv->vval.v_number = get_lisp_indent();
+	get_lisp_indent(&level, &amount);
+	/* FIXME:2010-03-21:llorens: depending on the value returned by
+	 * get_lisp_indent() rettv->v_type should either be VAR_NUMBER or
+	 * VAR_LIST. */
+	rettv->vval.v_number = amount;
 	curwin->w_cursor = pos;
     }
     else
diff --git a/src/misc1.c b/src/misc1.c
index f67f9c6..7440d65 100644
--- a/src/misc1.c
+++ b/src/misc1.c
@@ -75,6 +75,203 @@ get_indent_str(ptr, ts)
 }
 
 /*
+ * Set the indent of the current line to to "level" tabs + ("size" - "level" *
+ * 'ts') spaces unless 'preserveindent' indicates that some of the current
+ * indenting of the line should be retained.
+ * Leaves the cursor on the first non-blank in the line.
+ * Caller must take care of undo.
+ * "flags":
+ *	SIN_UNDO:	save line for undo before changing it.
+ * Returns TRUE if the line was changed.
+ */
+    int
+set_indent_with_level(level, size, flags)
+    int		level;
+    int		size;
+    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 */
+    int		tab_pad;
+    int		retval = FALSE;
+    int		orig_char_len = -1; /* number of initial whitespace chars when
+				       'et' and 'pi' are both set */
+    int		tabs_left = level;
+
+    /* NOTE that
+     *     (level * 'ts') > size
+     * is possible if the middle part of a 3-part comment has a negative offset
+     * relative to the opening part.
+     */
+
+    /*
+     * 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 'preserveindent' is set count the number of characters at the
+     * beginning of the line to be copied */
+    /* If 'preserveindent' is set then reuse as much as possible of
+     * the existing indent structure for the new indent */
+    if (curbuf->b_p_pi)
+    {
+	ind_done = 0;
+
+	/* count as many characters as we can use */
+	while (todo > 0 && vim_iswhite(*p))
+	{
+	    if (*p == TAB)
+	    {
+		tab_pad = (int)curbuf->b_p_ts
+		    - (ind_done % (int)curbuf->b_p_ts);
+		/* 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;
+	}
+
+	tabs_left -= ind_done / (int)curbuf->b_p_ts;
+	if (tabs_left < 0)
+	    tabs_left = 0;
+
+	orig_char_len = ind_len;
+
+	/* Fill to next tabstop with a tab, if possible. */
+	if (tabs_left)
+	{
+	    tab_pad = (int)curbuf->b_p_ts - (ind_done % (int)curbuf->b_p_ts);
+	    if (todo >= tab_pad)
+	    {
+		doit = TRUE;
+		todo -= tab_pad;
+		++ind_len;
+		--tabs_left;
+	    }
+	}
+    }
+
+    /* count tabs required for indent */
+    while (tabs_left && todo >= (int)curbuf->b_p_ts)
+    {
+	if (*p != TAB)
+	    doit = TRUE;
+	else
+	    ++p;
+	todo -= (int)curbuf->b_p_ts;
+	++ind_len;
+	--tabs_left;
+    }
+
+    /* count spaces required for indent */
+    while (todo > 0)
+    {
+	if (*p != ' ')
+	    doit = TRUE;
+	else
+	    ++p;
+	--todo;
+	++ind_len;
+    }
+
+    /* Return if the indent is OK already. */
+    if (!doit && !vim_iswhite(*p))
+	return FALSE;
+
+    /* Allocate memory for the new line. */
+    p = skipwhite(p);
+    line_len = (int)STRLEN(p) + 1;
+
+    newline = alloc(ind_len + line_len);
+    if (newline == NULL)
+	return FALSE;
+
+    /* If 'preserveindent' is set, keep the original characters. */
+    if (orig_char_len > 0)
+    {
+	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;
+	tabs_left = level - ind_done / (int)curbuf->b_p_ts;
+	if (tabs_left < 0)
+	    tabs_left = 0;
+    }
+    else
+    {
+	todo = size;
+	s = newline;
+	tabs_left = level;
+	if (tabs_left > size / (int)curbuf->b_p_ts)
+	    tabs_left = size / (int)curbuf->b_p_ts;
+    }
+
+    copy_chars(s, tabs_left, TAB);
+    s += tabs_left;
+    todo = size - tabs_left * (int)curbuf->b_p_ts;
+    if (todo > 0)
+    {
+	copy_spaces(s, todo);
+	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)
+    {
+	ml_replace(curwin->w_cursor.lnum, newline, FALSE);
+	changed_bytes(curwin->w_cursor.lnum, 0);
+	/* Correct saved cursor position if it's after the indent. */
+	if (saved_cursor.lnum == curwin->w_cursor.lnum
+	    && saved_cursor.col >= (colnr_T)(p - oldline))
+	{
+	    saved_cursor.col += ind_len - (colnr_T)(p - oldline);
+	}
+	retval = TRUE;
+    }
+    else
+	vim_free(newline);
+
+    curwin->w_cursor.col = ind_len;
+    return retval;
+}
+
+/*
  * Set the indent of the current line.
  * Leaves the cursor on the first non-blank in the line.
  * Caller must take care of undo.
@@ -117,11 +314,12 @@ set_indent(size, flags)
     /* 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 ((!curbuf->b_p_et && !curbuf->b_p_ti)
+	|| (!(flags & SIN_INSERT) && (curbuf->b_p_pi || curbuf->b_p_ti)))
     {
 	/* 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)
+	if (!(flags & SIN_INSERT) && (curbuf->b_p_pi || curbuf->b_p_ti))
 	{
 	    ind_done = 0;
 
@@ -150,7 +348,7 @@ set_indent(size, flags)
 
 	    /* Set initial number of whitespace chars to copy if we are
 	     * preserving indent but expandtab is set */
-	    if (curbuf->b_p_et)
+	    if (curbuf->b_p_et || curbuf->b_p_ti)
 		orig_char_len = ind_len;
 
 	    /* Fill to next tabstop with a tab, if possible */
@@ -236,7 +434,7 @@ set_indent(size, flags)
 
     /* Put the characters in the new line. */
     /* if 'expandtab' isn't set: use TABs */
-    if (!curbuf->b_p_et)
+    if (!curbuf->b_p_et && !curbuf->b_p_ti)
     {
 	/* If 'preserveindent' is set then reuse as much as possible of
 	 * the existing indent structure for the new indent */
@@ -5989,8 +6187,23 @@ find_last_paren(l, start, end)
     return retval;
 }
 
+    static int
+get_initial_tabs_count(lnum)
+    linenr_T	lnum;	/* line number */
+{
+    char_u	*ptr = ml_get_buf(curbuf, lnum, FALSE);
+    int		result = 0;
+
+    while (*ptr++ == TAB)
+	++result;
+
+    return result;
+}
+
     int
-get_c_indent()
+get_c_indent(plevel, pamount)
+    int		*plevel;
+    int		*pamount;
 {
     /*
      * spaces from a block's opening brace the prevailing indent for that
@@ -6161,6 +6374,7 @@ get_c_indent()
 
     pos_T	cur_curpos;
     int		amount;
+    int		level = 0;
     int		scope_amount;
     int		cur_amount = MAXCOL;
     colnr_T	col;
@@ -6280,7 +6494,10 @@ get_c_indent()
      * ml_get is valid! */
     linecopy = vim_strsave(ml_get(cur_curpos.lnum));
     if (linecopy == NULL)
-	return 0;
+    {
+	*pamount = 0;
+	return INDENT_ERROR;
+    }
 
     /*
      * In insert mode and the cursor is on a ')' truncate the line at the
@@ -6326,6 +6543,7 @@ get_c_indent()
 	/* find how indented the line beginning the comment is */
 	getvcol(curwin, trypos, &col, NULL, NULL);
 	amount = col;
+	level = get_initial_tabs_count(trypos->lnum);
     }
 
     /*
@@ -6348,6 +6566,7 @@ get_c_indent()
 	/* find how indented the line beginning the comment is */
 	getvcol(curwin, trypos, &col, NULL, NULL);
 	amount = col;
+	level = get_initial_tabs_count(trypos->lnum);
 
 	p = curbuf->b_p_com;
 	while (*p != NUL)
@@ -6399,11 +6618,15 @@ get_c_indent()
 			 * line, use the indent of that line.  XXX */
 			look = skipwhite(ml_get(curwin->w_cursor.lnum - 1));
 			if (STRNCMP(look, lead_start, lead_start_len) == 0)
+			{
 			    amount = get_indent_lnum(curwin->w_cursor.lnum - 1);
+			    level = get_initial_tabs_count(curwin->w_cursor.lnum - 1);
+			}
 			else if (STRNCMP(look, lead_middle,
 							lead_middle_len) == 0)
 			{
 			    amount = get_indent_lnum(curwin->w_cursor.lnum - 1);
+			    level = get_initial_tabs_count(curwin->w_cursor.lnum - 1);
 			    break;
 			}
 			/* If the start comment string doesn't match with the
@@ -6427,6 +6650,7 @@ get_c_indent()
 		{
 		    amount = get_indent_lnum(curwin->w_cursor.lnum - 1);
 								     /* XXX */
+		    level = get_initial_tabs_count(curwin->w_cursor.lnum - 1);
 		    if (off != 0)
 			amount += off;
 		    else if (align == COM_RIGHT)
@@ -6502,6 +6726,7 @@ get_c_indent()
 
       if (trypos != NULL)
       {
+	level = get_initial_tabs_count(trypos->lnum);
 	/*
 	 * If the matching paren is more than one line away, use the indent of
 	 * a previous non-empty line that matches the same paren.
@@ -6717,11 +6942,15 @@ get_c_indent()
        */
       else
       {
+	int scope_level = 0;
 	trypos = tryposBrace;
 
 	ourscope = trypos->lnum;
 	start = ml_get(ourscope);
 
+	level = get_initial_tabs_count(trypos->lnum) + 1;
+	scope_level = level;
+
 	/*
 	 * Now figure out how indented the line is in general.
 	 * If the brace was at the start of the line, we use that;
@@ -6763,9 +6992,15 @@ get_c_indent()
 	     *		    }
 	     */
 	    if (ind_keep_case_label && cin_iscase(skipwhite(ml_get_curline())))
+	    {
 		amount = get_indent();
+		level = get_initial_tabs_count(curwin->w_cursor.lnum) + 1;
+	    }
 	    else
+	    {
 		amount = skip_label(lnum, &l, ind_maxcomment);
+		level = get_initial_tabs_count(curwin->w_cursor.lnum);
+	    }
 
 	    start_brace = BRACE_AT_END;
 	}
@@ -6782,6 +7017,7 @@ get_c_indent()
 	     * other than the open brace.  indulge them, if so.
 	     */
 	    amount += ind_close_extra;
+	    level -= 1;
 	}
 	else
 	{
@@ -6804,6 +7040,7 @@ get_c_indent()
 							ind_maxcomment) == OK)
 		{
 		    amount = get_indent();	/* XXX */
+		    level = get_initial_tabs_count(curwin->w_cursor.lnum);
 		    goto theend;
 		}
 	    }
@@ -6844,11 +7081,15 @@ get_c_indent()
 	    {
 		lookfor = LOOKFOR_CASE;	/* find a previous switch() label */
 		amount += ind_case;
+		if (ind_case >= curbuf->b_p_sw)
+		    level += 1;
 	    }
 	    else if (cin_isscopedecl(theline))	/* private:, ... */
 	    {
 		lookfor = LOOKFOR_SCOPEDECL;	/* class decl is this block */
 		amount += ind_scopedecl;
+		if (ind_scopedecl >= curbuf->b_p_sw)
+		    level += 1;
 	    }
 	    else
 	    {
@@ -6859,6 +7100,7 @@ get_c_indent()
 		amount += ind_level;	/* ind_level from start of block */
 	    }
 	    scope_amount = amount;
+	    scope_level = level;
 	    whilelevel = 0;
 
 	    /*
@@ -7003,6 +7245,7 @@ get_c_indent()
 					  && lookfor != LOOKFOR_CPP_BASECLASS)
 		    {
 			amount = scope_amount;
+			level = scope_level;
 			if (theline[0] == '{')
 			    amount += ind_open_extra;
 		    }
@@ -7110,9 +7353,16 @@ get_c_indent()
 			if (l != NULL && cin_is_cinword(l))
 			{
 			    if (theline[0] == '{')
+			    {
 				amount += ind_open_extra;
+				if (ind_open_extra >= curbuf->b_p_sw)
+				    level += 1;
+			    }
 			    else
+			    {
+				level += 1;
 				amount += ind_level + ind_no_brace;
+			    }
 			}
 			break;
 		    }
@@ -7127,6 +7377,9 @@ get_c_indent()
 		     */
 		    scope_amount = get_indent() + (iscase    /* XXX */
 					? ind_case_code : ind_scopedecl_code);
+		    scope_level = get_initial_tabs_count(curwin->w_cursor.lnum);
+		    if ((iscase? ind_case_code: ind_scopedecl_code) >= curbuf->b_p_sw)
+			scope_level += 1;
 		    lookfor = ind_case_break ? LOOKFOR_NOBREAK : LOOKFOR_ANY;
 		    continue;
 		}
@@ -7344,7 +7597,10 @@ get_c_indent()
 			    if (cont_amount > 0)
 				amount = cont_amount;
 			    else
+			    {
 				amount += ind_continuation;
+				level = get_initial_tabs_count(curwin->w_cursor.lnum) + 1;
+			    }
 			    break;
 			}
 
@@ -7367,6 +7623,7 @@ get_c_indent()
 			if (lookfor != LOOKFOR_TERM)
 			{
 			    amount += ind_level + ind_no_brace;
+			    level = get_initial_tabs_count(curwin->w_cursor.lnum) + 1;
 			    break;
 			}
 
@@ -7644,13 +7901,22 @@ term_again:
 			 */
 			amount = skip_label(curwin->w_cursor.lnum,
 							  &l, ind_maxcomment);
+			level = get_initial_tabs_count(curwin->w_cursor.lnum);
 
 			if (theline[0] == '{')
+			{
 			    amount += ind_open_extra;
+			    if (ind_open_extra >= curbuf->b_p_sw)
+				level += 1;
+			}
 			/* See remark above: "Only add ind_open_extra.." */
 			l = skipwhite(l);
 			if (*l == '{')
+			{
 			    amount -= ind_open_extra;
+			    if (ind_open_extra >= curbuf->b_p_sw)
+				level -= 1;
+			}
 			lookfor = iscase ? LOOKFOR_ANY : LOOKFOR_TERM;
 
 			/*
@@ -7741,6 +8007,7 @@ term_again:
 		&& !cin_isterminated(theline, FALSE, TRUE))
 	{
 	    amount = ind_func_type;
+	    level = 1;
 	}
 	else
 	{
@@ -7938,8 +8205,11 @@ theend:
     vim_free(linecopy);
 
     if (amount < 0)
-	return 0;
-    return amount;
+	amount = 0;
+    *pamount = amount;
+    *plevel = level;
+
+    return (curbuf->b_p_ti? INDENT_LVL_AMOUNT: INDENT_AMOUNT);
 }
 
     static int
@@ -8063,20 +8333,78 @@ find_match(lookfor, ourscope, ind_maxparen, ind_maxcomment)
  * Get indent level from 'indentexpr'.
  */
     int
-get_expr_indent()
+get_expr_indent(plevel, pamount)
+    int		*plevel;
+    int		*pamount;
 {
+    int		level;
     int		indent;
     pos_T	pos;
     int		save_State;
     int		use_sandbox = was_set_insecurely((char_u *)"indentexpr",
 								   OPT_LOCAL);
+    typval_T	*rettv;
+    char_u	*p;
+    int		result = INDENT_ERROR;
 
     pos = curwin->w_cursor;
     set_vim_var_nr(VV_LNUM, curwin->w_cursor.lnum);
     if (use_sandbox)
 	++sandbox;
     ++textlock;
-    indent = eval_to_number(curbuf->b_p_inde);
+
+    p = skipwhite(curbuf->b_p_inde);
+    if ((rettv = eval_expr(p, NULL)) == NULL)
+	indent = -1;
+    else
+    {
+	int failed = TRUE;
+	if (rettv->v_type == VAR_LIST)
+	{
+
+	    listitem_T *li;
+	    if (rettv->vval.v_list->lv_len == 2)
+	    {
+		int idx = 0;
+		for (li = rettv->vval.v_list->lv_first; li != NULL; li = li->li_next)
+		{
+		    switch (idx)
+		    {
+			case 0:
+			    level = get_tv_number_chk(&li->li_tv, &failed);
+			    break;
+			case 1:
+			    indent = get_tv_number_chk(&li->li_tv, &failed);
+			    break;
+			default:
+			    break;
+		    }
+		    ++idx;
+		    if (failed)
+			break;
+		}
+		if (!failed)
+		    result = INDENT_LVL_AMOUNT;
+	    }
+	}
+	else
+	{
+	    indent = get_tv_number_chk(rettv, &failed);
+	    result = INDENT_AMOUNT;
+	}
+
+	if (failed)
+	{
+	    EMSG(_("E817: The value returned by 'indentexpr' should be a number or a list of two numbers"));
+	    level = -1;
+	    indent = -1;
+	    result = INDENT_AMOUNT;
+	}
+
+	clear_tv(rettv);
+	vim_free(rettv);
+    }
+
     if (use_sandbox)
 	--sandbox;
     --textlock;
@@ -8093,8 +8421,12 @@ get_expr_indent()
     /* If there is an error, just keep the current indent. */
     if (indent < 0)
 	indent = get_indent();
+    if (level < 0)
+	level = 0;
 
-    return indent;
+    *pamount = indent;
+    *plevel = level;
+    return result;
 }
 # endif
 
@@ -8139,7 +8471,9 @@ lisp_match(p)
  * I tried to fix the first two issues.
  */
     int
-get_lisp_indent()
+get_lisp_indent(plevel, pamount)
+    int		*plevel;
+    int		*pamount;
 {
     pos_T	*pos, realpos, paren;
     int		amount;
@@ -8317,7 +8651,10 @@ get_lisp_indent()
 
     curwin->w_cursor = realpos;
 
-    return amount;
+    /* FIXME:llorens:2010-03-21: get_lisp_indent() should return
+     * INDENT_LVL_AMOUNT if 'tabindent' is set. */
+    *pamount = amount;
+    return INDENT_AMOUNT;
 }
 #endif /* FEAT_LISP */
 
diff --git a/src/ops.c b/src/ops.c
index 606ce07..f5ced30 100644
--- a/src/ops.c
+++ b/src/ops.c
@@ -664,7 +664,7 @@ block_insert(oap, s, b_insert, bdp)
     void
 op_reindent(oap, how)
     oparg_T	*oap;
-    int		(*how) __ARGS((void));
+    int		(*how) __ARGS((int *plevel, int *pamount));
 {
     long	i;
     char_u	*l;
@@ -699,13 +699,16 @@ op_reindent(oap, how)
 						    || how != get_lisp_indent)
 #endif
 	{
+	    int result = INDENT_ERROR;
+	    int level;
 	    l = skipwhite(ml_get_curline());
 	    if (*l == NUL)		    /* empty or blank line */
 		count = 0;
 	    else
-		count = how();		    /* get the indent for this line */
+		result = how(&level, &count);	    /* get the indent for this line */
 
-	    if (set_indent(count, SIN_UNDO))
+	    if ((INDENT_LVL_AMOUNT == result && set_indent_with_level(level, count, SIN_UNDO))
+		|| (INDENT_LVL_AMOUNT != result && set_indent(count, SIN_UNDO)))
 	    {
 		/* did change the indent, call changed_lines() later */
 		if (first_changed == 0)
diff --git a/src/option.c b/src/option.c
index ba17c11..232e306 100644
--- a/src/option.c
+++ b/src/option.c
@@ -173,6 +173,7 @@
 #define PV_SW		OPT_BUF(BV_SW)
 #define PV_SWF		OPT_BUF(BV_SWF)
 #define PV_TAGS		OPT_BOTH(OPT_BUF(BV_TAGS))
+#define PV_TI		OPT_BUF(BV_TI)
 #define PV_TS		OPT_BUF(BV_TS)
 #define PV_TW		OPT_BUF(BV_TW)
 #define PV_TX		OPT_BUF(BV_TX)
@@ -354,6 +355,7 @@ static char_u	*p_spc;
 static char_u	*p_spf;
 static char_u	*p_spl;
 #endif
+static int	p_ti;
 static long	p_ts;
 static long	p_tw;
 static int	p_tx;
@@ -2407,6 +2409,9 @@ static struct vimoption
 			    {(char_u *)0L, (char_u *)0L}
 #endif
 			    SCRIPTID_INIT},
+    {"tabindent",   "ti",   P_BOOL|P_VI_DEF|P_VIM,
+			    (char_u *)&p_ti, PV_TI,
+			    {(char_u *)FALSE, (char_u *)0L} SCRIPTID_INIT},
     {"tabline",	    "tal",  P_STRING|P_VI_DEF|P_RALL,
 #ifdef FEAT_STL_OPT
 			    (char_u *)&p_tal, PV_NONE,
@@ -9333,6 +9338,7 @@ get_varp(p)
 	case PV_SPL:	return (char_u *)&(curbuf->b_p_spl);
 #endif
 	case PV_SW:	return (char_u *)&(curbuf->b_p_sw);
+	case PV_TI:	return (char_u *)&(curbuf->b_p_ti);
 	case PV_TS:	return (char_u *)&(curbuf->b_p_ts);
 	case PV_TW:	return (char_u *)&(curbuf->b_p_tw);
 	case PV_TX:	return (char_u *)&(curbuf->b_p_tx);
@@ -9637,6 +9643,7 @@ buf_copy_options(buf, flags)
 	    buf->b_p_oft = vim_strsave(p_oft);
 #endif
 	    buf->b_p_pi = p_pi;
+	    buf->b_p_ti = p_ti;
 #if defined(FEAT_SMARTINDENT) || defined(FEAT_CINDENT)
 	    buf->b_p_cinw = vim_strsave(p_cinw);
 #endif
diff --git a/src/option.h b/src/option.h
index cfa7692..ac78526 100644
--- a/src/option.h
+++ b/src/option.h
@@ -1000,6 +1000,7 @@ enum
     , BV_SW
     , BV_SWF
     , BV_TAGS
+    , BV_TI
     , BV_TS
     , BV_TW
     , BV_TX
diff --git a/src/proto/edit.pro b/src/proto/edit.pro
index e2398c4..c9f70d7 100644
--- a/src/proto/edit.pro
+++ b/src/proto/edit.pro
@@ -33,7 +33,7 @@ char_u *get_last_insert __ARGS((void));
 char_u *get_last_insert_save __ARGS((void));
 void replace_push __ARGS((int c));
 int replace_push_mb __ARGS((char_u *p));
-void fixthisline __ARGS((int (*get_the_indent)(void)));
+void fixthisline __ARGS((int (*get_the_indent)(int *plevel, int *pamount))); /* FIXME: I don't know how I should write this declaration. */
 void fix_indent __ARGS((void));
 int in_cinkeys __ARGS((int keytyped, int when, int line_is_empty));
 int hkmap __ARGS((int c));
diff --git a/src/proto/misc1.pro b/src/proto/misc1.pro
index a036e3e..fb210ff 100644
--- a/src/proto/misc1.pro
+++ b/src/proto/misc1.pro
@@ -3,6 +3,7 @@ int get_indent __ARGS((void));
 int get_indent_lnum __ARGS((linenr_T lnum));
 int get_indent_buf __ARGS((buf_T *buf, linenr_T lnum));
 int get_indent_str __ARGS((char_u *ptr, int ts));
+int set_indent_with_level __ARGS((int level, int size, int flags));
 int set_indent __ARGS((int size, int flags));
 int get_number_indent __ARGS((linenr_T lnum));
 int open_line __ARGS((int dir, int flags, int old_indent));
@@ -77,9 +78,9 @@ void do_c_expr_indent __ARGS((void));
 int cin_islabel __ARGS((int ind_maxcomment));
 int cin_iscase __ARGS((char_u *s));
 int cin_isscopedecl __ARGS((char_u *s));
-int get_c_indent __ARGS((void));
-int get_expr_indent __ARGS((void));
-int get_lisp_indent __ARGS((void));
+int get_c_indent __ARGS((int *plevel, int *pamount));
+int get_expr_indent __ARGS((int *plevel, int *pamount));
+int get_lisp_indent __ARGS((int *plevel, int *pamount));
 void prepare_to_exit __ARGS((void));
 void preserve_exit __ARGS((void));
 int vim_fexists __ARGS((char_u *fname));
diff --git a/src/proto/ops.pro b/src/proto/ops.pro
index 37c3194..2e5ddcd 100644
--- a/src/proto/ops.pro
+++ b/src/proto/ops.pro
@@ -5,7 +5,7 @@ int get_op_char __ARGS((int optype));
 int get_extra_op_char __ARGS((int optype));
 void op_shift __ARGS((oparg_T *oap, int curs_top, int amount));
 void shift_line __ARGS((int left, int round, int amount, int call_changed_bytes));
-void op_reindent __ARGS((oparg_T *oap, int (*how)(void)));
+void op_reindent __ARGS((oparg_T *oap, int (*how)(int *plevel, int *pamount)));
 int get_expr_register __ARGS((void));
 void set_expr_line __ARGS((char_u *new_line));
 char_u *get_expr_line __ARGS((void));
diff --git a/src/structs.h b/src/structs.h
index 99afecf..cd4a928 100644
--- a/src/structs.h
+++ b/src/structs.h
@@ -1434,6 +1434,7 @@ struct file_buffer
     char_u	*b_p_spf;	/* 'spellfile' */
     char_u	*b_p_spl;	/* 'spelllang' */
 #endif
+    int		b_p_ti;		/* 'tabindent' */
     long	b_p_ts;		/* 'tabstop' */
     int		b_p_tx;		/* 'textmode' */
     long	b_p_tw;		/* 'textwidth' */
diff --git a/src/vim.h b/src/vim.h
index b6b4e1e..d4031f8 100644
--- a/src/vim.h
+++ b/src/vim.h
@@ -901,6 +901,14 @@ extern char *(*dyn_libintl_textdomain)(const char *domainname);
 #define INDENT_INC	2	/* increase indent */
 #define INDENT_DEC	3	/* decrease indent */
 
+/* Values returned by get_c_indent(), get_lisp_indent() and get_expr_indent() */
+#define INDENT_ERROR		-1 /* something wrong happened while
+				    * calculating indentation */
+#define INDENT_AMOUNT		 0 /* the level indentation returned by the
+				    * function should be ignored */
+#define INDENT_LVL_AMOUNT	 1 /* the function returned both the indent
+				    * level and the amount of indentation */
+
 /* Values for flags argument for findmatchlimit() */
 #define FM_BACKWARD	0x01	/* search backwards */
 #define FM_FORWARD	0x02	/* search forwards */
diff --git a/runtime/doc/insert.txt b/runtime/doc/insert.txt
index 61360a4..f171fa7 100644
--- a/runtime/doc/insert.txt
+++ b/runtime/doc/insert.txt
@@ -463,8 +463,8 @@ mode: hit "v", move to the end of the block, and type "gq".  See also |gq|.
 4. 'expandtab', 'smarttab' and 'softtabstop' options	*ins-expandtab*
 
 If the 'expandtab' option is on, spaces will be used to fill the amount of
-whitespace of the tab.  If you want to enter a real <Tab>, type CTRL-V first
-(use CTRL-Q when CTRL-V is mapped |i_CTRL-Q|).
+whitespace of the tab (also see 'tabindent').  If you want to enter a real
+<Tab>, type CTRL-V first (use CTRL-Q when CTRL-V is mapped |i_CTRL-Q|).
 The 'expandtab' option is off by default.  Note that in Replace mode, a single
 character is replaced with several spaces.  The result of this is that the
 number of characters in the line increases.  Backspacing will delete one
diff --git a/runtime/doc/options.txt b/runtime/doc/options.txt
index e178613..c0a937d 100644
--- a/runtime/doc/options.txt
+++ b/runtime/doc/options.txt
@@ -1364,7 +1364,8 @@ A jump table for the options with a short description can be found at |Q_op|.
 			feature}
 	Enables automatic C program indenting.  See 'cinkeys' to set the keys
 	that trigger reindenting in insert mode and 'cinoptions' to set your
-	preferred indent style.
+	preferred indent style. Also, refer to 'tabindent' to see how its
+	value affects indenting.
 	If 'indentexpr' is not empty, it overrules 'cindent'.
 	If 'lisp' is not on and both 'indentexpr' and 'equalprg' are empty,
 	the "=" operator indents using this algorithm rather than calling an
@@ -2536,7 +2537,8 @@ A jump table for the options with a short description can be found at |Q_op|.
 	In Insert mode: Use the appropriate number of spaces to insert a
 	<Tab>.  Spaces are used in indents with the '>' and '<' commands and
 	when 'autoindent' is on.  To insert a real tab when 'expandtab' is
-	on, use CTRL-V<Tab>.  See also |:retab| and |ins-expandtab|.
+	on, use CTRL-V<Tab>.  See also |:retab|, |ins-expandtab| and
+	'tabindent'.
 	NOTE: This option is reset when 'compatible' is set.
 
 					*'exrc'* *'ex'* *'noexrc'* *'noex'*
@@ -3915,7 +3917,7 @@ A jump table for the options with a short description can be found at |Q_op|.
 	match, excluding the characters that were already typed.
 	NOTE: This option is reset when 'compatible' is set.
 
-						*'indentexpr'* *'inde'*
+						*'indentexpr'* *'inde'* *E817*
 'indentexpr' 'inde'	string	(default "")
 			local to buffer
 			{not in Vi}
@@ -3926,13 +3928,18 @@ A jump table for the options with a short description can be found at |Q_op|.
 	in Insert mode as specified with the 'indentkeys' option.
 	When this option is not empty, it overrules the 'cindent' and
 	'smartindent' indenting.
-	When 'paste' is set this option is not used for indenting.
+	When 'paste' is set, this option is not used for indenting.
 	The expression is evaluated with |v:lnum| set to the line number for
 	which the indent is to be computed.  The cursor is also in this line
 	when the expression is evaluated (but it may be moved around).
-	The expression must return the number of spaces worth of indent.  It
-	can return "-1" to keep the current indent (this means 'autoindent' is
-	used for the indent).
+	The expression must either return:
+	- the number of spaces worth of indent. It can return "-1" to keep the
+	  current indent (this means 'autoindent' is used for the indent),
+	- or a list of two numbers. In this case the first number will be
+	  treated as indent level and the second one will be the indent
+	  amount. This will trigger indent behaviour described under
+	  'tabindent'. See $VIMRUNTIME/indent/vim.vim for an example of indent
+	  script returning a list of two values.
 	Functions useful for computing the indent are |indent()|, |cindent()|
 	and |lispindent()|.
 	The evaluation of the expression must not have side effects!  It must
@@ -6583,6 +6590,22 @@ A jump table for the options with a short description can be found at |Q_op|.
 	'S' flag in 'cpoptions'.
 	Only normal file name characters can be used, "/\*?[|<>" are illegal.
 
+						*'tabindent'* *'ti'*
+'tabindent' 'ti'	boolean	(default off)
+			local to buffer
+			{not in Vi}
+	When on, makes Vim use tabs for indenting starting lines and pad with
+	spaces continuation lines while 'cindent' is set. This allows for
+	changing the value of 'tabstop' while viewing code indented this way
+	without affecting alignment.
+	Note that when 'tabindent' is set, you will probably want to set
+	'tabstop' and 'shiftwidth' to the same value, as otherwise it might
+	happen that the number of tabs in indentation does not reflect the
+	indentation level.
+	The value of 'expandtab' is ignored during indenting when 'tabindent'
+	is on.
+	NOTE: This option is reset when 'compatible' is set.
+
 						*'tabline'* *'tal'*
 'tabline' 'tal'		string	(default empty)
 			global
@@ -6644,6 +6667,8 @@ A jump table for the options with a short description can be found at |Q_op|.
 	   though.  Otherwise aligned comments will be wrong when 'tabstop' is
 	   changed.
 
+	Also see 'tabindent'.
+
 			*'tagbsearch'* *'tbs'* *'notagbsearch'* *'notbs'*
 'tagbsearch' 'tbs'	boolean	(default on)
 			global
diff --git a/runtime/doc/quickref.txt b/runtime/doc/quickref.txt
index 78599cf..969839f 100644
--- a/runtime/doc/quickref.txt
+++ b/runtime/doc/quickref.txt
@@ -878,6 +878,7 @@ Short explanation of each option:		*option-list*
 'switchbuf'	  'swb'     sets behavior when switching to another buffer
 'synmaxcol'	  'smc'     maximum column to find syntax items
 'syntax'	  'syn'     syntax to be loaded for current buffer
+'tabindent'	  'ti'	    indent C code so that changing 'ts' keeps alignment
 'tabstop'	  'ts'	    number of spaces that <Tab> in file uses
 'tabline'	  'tal'     custom format for the console tab pages line
 'tabpagemax'	  'tpm'     maximum number of tab pages for |-p| and "tab all"
diff --git a/src/testdir/Makefile b/src/testdir/Makefile
index 073a284..b26d1fb 100644
--- a/src/testdir/Makefile
+++ b/src/testdir/Makefile
@@ -23,7 +23,7 @@ SCRIPTS = test1.out test2.out test3.out test4.out test5.out test6.out \
 		test54.out test55.out test56.out test57.out test58.out \
 		test59.out test60.out test61.out test62.out test63.out \
 		test64.out test65.out test66.out test67.out test68.out \
-		test69.out test70.out
+		test69.out test70.out test-tabindent.out
 
 SCRIPTS_GUI = test16.out
 
diff --git a/src/testdir/test-tabindent.in b/src/testdir/test-tabindent.in
new file mode 100644
index 0000000..9b704b3
--- /dev/null
+++ b/src/testdir/test-tabindent.in
@@ -0,0 +1,188 @@
+/*
+This file tests the 'tabindent' option, which makes Vim
+indent text using tabs and pad it with spaces. This allows
+changing 'tabstop' without making the indentation incorrect.
+*/
+/*
+ * This tests for correct shifting of already indented lines.
+ * If this test fails, Vim probably violates the rules of 'tabindent'
+ * indenting when it changes indenting of lines.
+STARTTEST
+:so small.vim
+:set nocompatible cin ts=4 sw=4
+:set tabindent
+:set nopreserveindent noet shiftround
+/start of AUTO
+4jV<jV2<
+3jV>jV2>
+:set nopreserveindent noet noshiftround
+4jV<jV2<
+3jV>jV2>
+:set preserveindent et shiftround
+4jV<jV2<
+3jV>jV2>
+:set preserveindent et noshiftround
+4jV<jV2<
+3jV>jV2>
+ENDTEST */
+/* start of AUTOmatic test for changing indent */
+void func()
+{
+	for (int i = 0;
+	     i < 10;
+	     ++i)
+	{
+		if (i & 1)
+			printf("i is odd:"
+			       " %d\n",
+			       i);
+	}
+
+
+	for (int i = 0;
+	     i < 10;
+	     ++i)
+	{
+		if (i & 1)
+			printf("i is odd:"
+			       " %d\n",
+			       i);
+	}
+
+
+	for (int i = 0;
+	     i < 10;
+	     ++i)
+	{
+		if (i & 1)
+			printf("i is odd:"
+			       " %d\n",
+			       i);
+	}
+
+
+	for (int i = 0;
+	     i < 10;
+	     ++i)
+	{
+		if (i & 1)
+			printf("i is odd:"
+			       " %d\n",
+			       i);
+	}
+}
+
+/* end of AUTOmatic test for changing indent */
+
+/*
+ * This tests for correct indenting of C code.
+ * TODO: test indenting of switch statements and case labels.
+STARTTEST
+:set cinoptions=(0
+:set nopreserveindent et
+/start of AUTO
+=/end of AUTO
+n:set noet
+/start of AUTO
+=/end of AUTO
+ENDTEST */
+
+/* start of AUTOmatic test for indenting C code */
+	void
+func()
+{
+	for (int i = 0;
+		 i < 10;
+		 ++i)
+	{
+		if (i & 1)
+			printf("i is odd:"
+				   " %d\n",
+				   i);
+	}
+	for (int i = 0;
+		 i < 10;
+		 ++i)
+		if (!(i & 1)) printf("i is even: %d\n",
+							 i);
+		else
+			printf("i is odd: %d\n",
+				   i);
+}
+
+void
+func()
+{
+for (int i = 0;
+i < 10;
+++i)
+{
+if (i & 1)
+printf("i is odd:"
+" %d\n",
+i);
+}
+for (int i = 0;
+i < 10;
+++i)
+if (!(i & 1)) printf("i is even: %d\n",
+i);
+else
+printf("i is odd: %d\n",
+i);
+}
+/* end of AUTOmatic test for indenting C code */
+
+/* start of AUTOmatic test for indenting C code */
+	void
+func()
+{
+	for (int i = 0;
+		 i < 10;
+		 ++i)
+	{
+		if (i & 1)
+			printf("i is odd:"
+				   " %d\n",
+				   i);
+	}
+	for (int i = 0;
+		 i < 10;
+		 ++i)
+		if (!(i & 1)) printf("i is even: %d\n",
+							 i);
+		else
+			printf("i is odd: %d\n",
+				   i);
+}
+
+void
+func()
+{
+/* NOTE: if this code gets indented with spaces, this is a problem with 'tabindent' handling */
+for (int i = 0;
+i < 10;
+++i)
+{
+if (i & 1)
+printf("i is odd:"
+" %d\n",
+i);
+}
+for (int i = 0;
+i < 10;
+++i)
+if (!(i & 1)) printf("i is even: %d\n",
+i);
+else
+printf("i is odd: %d\n",
+i);
+/* NOTE: if this code gets indented with spaces, this is a problem with 'tabindent' handling */
+}
+/* end of AUTOmatic test for indenting C code */
+/*
+STARTTEST
+:%wq! test.out
+ENDTEST
+vim: ft=c ts=4 sw=4
+*/
diff --git a/src/testdir/test-tabindent.ok b/src/testdir/test-tabindent.ok
new file mode 100644
index 0000000..43d7237
--- /dev/null
+++ b/src/testdir/test-tabindent.ok
@@ -0,0 +1,188 @@
+/*
+This file tests the 'tabindent' option, which makes Vim
+indent text using tabs and pad it with spaces. This allows
+changing 'tabstop' without making the indentation incorrect.
+*/
+/*
+ * This tests for correct shifting of already indented lines.
+ * If this test fails, Vim probably violates the rules of 'tabindent'
+ * indenting when it changes indenting of lines.
+STARTTEST
+:so small.vim
+:set nocompatible cin ts=4 sw=4
+:set tabindent
+:set nopreserveindent noet shiftround
+/start of AUTO
+4jV<jV2<
+3jV>jV2>
+:set nopreserveindent noet noshiftround
+4jV<jV2<
+3jV>jV2>
+:set preserveindent et shiftround
+4jV<jV2<
+3jV>jV2>
+:set preserveindent et noshiftround
+4jV<jV2<
+3jV>jV2>
+ENDTEST */
+/* start of AUTOmatic test for changing indent */
+void func()
+{
+	for (int i = 0;
+	    i < 10;
+	++i)
+	{
+		if (i & 1)
+			printf("i is odd:"
+			        " %d\n",
+			            i);
+	}
+
+
+	for (int i = 0;
+	 i < 10;
+ ++i)
+	{
+		if (i & 1)
+			printf("i is odd:"
+			           " %d\n",
+			               i);
+	}
+
+
+	for (int i = 0;
+	    i < 10;
+	++i)
+	{
+		if (i & 1)
+			printf("i is odd:"
+			        " %d\n",
+			            i);
+	}
+
+
+	for (int i = 0;
+	 i < 10;
+ ++i)
+	{
+		if (i & 1)
+			printf("i is odd:"
+			           " %d\n",
+			               i);
+	}
+}
+
+/* end of AUTOmatic test for changing indent */
+
+/*
+ * This tests for correct indenting of C code.
+ * TODO: test indenting of switch statements and case labels.
+STARTTEST
+:set cinoptions=(0
+:set nopreserveindent et
+/start of AUTO
+=/end of AUTO
+n:set noet
+/start of AUTO
+=/end of AUTO
+ENDTEST */
+
+/* start of AUTOmatic test for indenting C code */
+	void
+func()
+{
+	for (int i = 0;
+	     i < 10;
+	     ++i)
+	{
+		if (i & 1)
+			printf("i is odd:"
+			       " %d\n",
+			       i);
+	}
+	for (int i = 0;
+	     i < 10;
+	     ++i)
+		if (!(i & 1)) printf("i is even: %d\n",
+		                     i);
+		else
+			printf("i is odd: %d\n",
+			       i);
+}
+
+	void
+func()
+{
+	for (int i = 0;
+	     i < 10;
+	     ++i)
+	{
+		if (i & 1)
+			printf("i is odd:"
+			       " %d\n",
+			       i);
+	}
+	for (int i = 0;
+	     i < 10;
+	     ++i)
+		if (!(i & 1)) printf("i is even: %d\n",
+		                     i);
+		else
+			printf("i is odd: %d\n",
+			       i);
+}
+/* end of AUTOmatic test for indenting C code */
+
+/* start of AUTOmatic test for indenting C code */
+	void
+func()
+{
+	for (int i = 0;
+	     i < 10;
+	     ++i)
+	{
+		if (i & 1)
+			printf("i is odd:"
+			       " %d\n",
+			       i);
+	}
+	for (int i = 0;
+	     i < 10;
+	     ++i)
+		if (!(i & 1)) printf("i is even: %d\n",
+		                     i);
+		else
+			printf("i is odd: %d\n",
+			       i);
+}
+
+	void
+func()
+{
+	/* NOTE: if this code gets indented with spaces, this is a problem with 'tabindent' handling */
+	for (int i = 0;
+	     i < 10;
+	     ++i)
+	{
+		if (i & 1)
+			printf("i is odd:"
+			       " %d\n",
+			       i);
+	}
+	for (int i = 0;
+	     i < 10;
+	     ++i)
+		if (!(i & 1)) printf("i is even: %d\n",
+		                     i);
+		else
+			printf("i is odd: %d\n",
+			       i);
+	/* NOTE: if this code gets indented with spaces, this is a problem with 'tabindent' handling */
+}
+/* end of AUTOmatic test for indenting C code */
+/*
+STARTTEST
+:%wq! test.out
+ENDTEST
+vim: ft=c ts=4 sw=4
+*/
diff --git a/runtime/indent/vim.vim b/runtime/indent/vim.vim
index 0a6dbc1..679b745 100644
--- a/runtime/indent/vim.vim
+++ b/runtime/indent/vim.vim
@@ -17,7 +17,22 @@ if exists("*GetVimIndent")
   finish
 endif
 
+function s:ReturnValue(level, amount)
+  if &tabindent
+    return [a:level, a:amount]
+  endif
+  return a:amount
+endfunction
+
+function s:GetLevel(lnum)
+  let line = getline(a:lnum)
+  let line = substitute(line, '^\(\t\+\).*$', '\1', '')
+  let line = substitute(line, '\t', 'x', 'g')
+  return strlen(line)
+endfunction
+
 function GetVimIndent()
+  let level = 0
   " Find a non-blank line above the current line.
   let lnum = prevnonblank(v:lnum - 1)
 
@@ -31,13 +46,14 @@ function GetVimIndent()
 
   " At the start of the file use zero indent.
   if lnum == 0
-    return 0
+    return s:ReturnValue(level, 0)
   endif
 
   " Add a 'shiftwidth' after :if, :while, :try, :catch, :finally, :function
   " and :else.  Add it three times for a line that starts with '\' after
   " a line that doesn't (or g:vim_indent_cont if it exists).
   let ind = indent(lnum)
+  let level = s:GetLevel(lnum)
   if getline(v:lnum) =~ '^\s*\\' && v:lnum > 1 && getline(lnum) !~ '^\s*\\'
     if exists("g:vim_indent_cont")
       let ind = ind + g:vim_indent_cont
@@ -46,8 +62,10 @@ function GetVimIndent()
     endif
   elseif getline(lnum) =~ '\(^\||\)\s*\(if\|wh\%[ile]\|for\|try\|cat\%[ch]\|fina\%[lly]\|fu\%[nction]\|el\%[seif]\)\>'
     let ind = ind + &sw
+    let level = level + 1
   elseif getline(lnum) =~ '^\s*aug\%[roup]' && getline(lnum) !~ '^\s*aug\%[roup]\s*!\=\s\+END'
     let ind = ind + &sw
+    let level = level + 1
   endif
 
   " If the previous line contains an "end" after a pipe, but not in an ":au"
@@ -58,6 +76,7 @@ function GetVimIndent()
   if i > 0 && line !~ '^\s*au\%[tocmd]'
     if !has('syntax_items') || synIDattr(synID(lnum, i + 2, 1), "name") !~ '\(Comment\|String\)$'
       let ind = ind - &sw
+      let level = level - 1
     endif
   endif
 
@@ -68,7 +87,7 @@ function GetVimIndent()
     let ind = ind - &sw
   endif
 
-  return ind
+  return s:ReturnValue(level, ind)
 endfunction
 
 " vim:sw=2

Raspunde prin e-mail lui