changeset 7574:b872724c37db v7.4.1087

commit https://github.com/vim/vim/commit/d79e55016cf8268cee935f1ac3b5b28712d1399e Author: Bram Moolenaar <Bram@vim.org> Date: Sun Jan 10 22:13:02 2016 +0100 patch 7.4.1087 Problem: CTRL-A and CTRL-X do not work properly with blockwise visual selection if there is a mix of Tab and spaces. Solution: Add OP_NR_ADD and OP_NR_SUB. (Hirohito Higashi)
author Christian Brabandt <cb@256bit.org>
date Sun, 10 Jan 2016 22:15:04 +0100
parents 4c8a7f265023
children 44b65a9cc81f
files src/normal.c src/ops.c src/proto/ops.pro src/testdir/test_increment.vim src/version.c src/vim.h
diffstat 6 files changed, 514 insertions(+), 408 deletions(-) [+]
line wrap: on
line diff
--- a/src/normal.c
+++ b/src/normal.c
@@ -40,7 +40,6 @@ static void	find_start_of_word __ARGS((p
 static void	find_end_of_word __ARGS((pos_T *));
 static int	get_mouse_class __ARGS((char_u *p));
 #endif
-static void	prep_redo_visual __ARGS((cmdarg_T *cap));
 static void	prep_redo_cmd __ARGS((cmdarg_T *cap));
 static void	prep_redo __ARGS((int regname, long, int, int, int, int, int));
 static int	checkclearop __ARGS((oparg_T *oap));
@@ -1392,6 +1391,7 @@ do_pending_operator(cap, old_col, gui_ya
     static linenr_T redo_VIsual_line_count; /* number of lines */
     static colnr_T  redo_VIsual_vcol;	    /* number of cols or end column */
     static long	    redo_VIsual_count;	    /* count for Visual operator */
+    static int	    redo_VIsual_arg;	    /* extra argument */
 #ifdef FEAT_VIRTUALEDIT
     int		    include_line_break = FALSE;
 #endif
@@ -1699,6 +1699,7 @@ do_pending_operator(cap, old_col, gui_ya
 		    redo_VIsual_vcol = resel_VIsual_vcol;
 		    redo_VIsual_line_count = resel_VIsual_line_count;
 		    redo_VIsual_count = cap->count0;
+		    redo_VIsual_arg = cap->arg;
 		}
 	    }
 
@@ -2108,6 +2109,24 @@ do_pending_operator(cap, old_col, gui_ya
 			       oap->op_type == OP_FOLDDELREC, oap->is_VIsual);
 	    break;
 #endif
+	case OP_NR_ADD:
+	case OP_NR_SUB:
+	    if (empty_region_error)
+	    {
+		vim_beep(BO_OPER);
+		CancelRedo();
+	    }
+	    else
+	    {
+		VIsual_active = TRUE;
+#ifdef FEAT_LINEBREAK
+		curwin->w_p_lbr = lbr_saved;
+#endif
+		op_addsub(oap, cap->count1, redo_VIsual_arg);
+		VIsual_active = FALSE;
+	    }
+	    check_cursor_col();
+	    break;
 	default:
 	    clearopbeep(oap);
 	}
@@ -3603,43 +3622,6 @@ find_ident_at_pos(wp, lnum, startcol, st
 }
 
 /*
- * Add commands to reselect Visual mode into the redo buffer.
- */
-    static void
-prep_redo_visual(cap)
-    cmdarg_T *cap;
-{
-    ResetRedobuff();
-    AppendCharToRedobuff(VIsual_mode);
-    if (VIsual_mode == 'V' && curbuf->b_visual.vi_end.lnum
-					    != curbuf->b_visual.vi_start.lnum)
-    {
-	AppendNumberToRedobuff(curbuf->b_visual.vi_end.lnum
-					    - curbuf->b_visual.vi_start.lnum);
-	AppendCharToRedobuff('j');
-    }
-    else if (VIsual_mode == 'v' || VIsual_mode == Ctrl_V)
-    {
-	/* block visual mode or char visual mmode*/
-	if (curbuf->b_visual.vi_end.lnum != curbuf->b_visual.vi_start.lnum)
-	{
-	    AppendNumberToRedobuff(curbuf->b_visual.vi_end.lnum -
-		    curbuf->b_visual.vi_start.lnum);
-	    AppendCharToRedobuff('j');
-	}
-	if (curbuf->b_visual.vi_curswant == MAXCOL)
-	    AppendCharToRedobuff('$');
-	else if (curbuf->b_visual.vi_end.col > curbuf->b_visual.vi_start.col)
-	{
-	    AppendNumberToRedobuff(curbuf->b_visual.vi_end.col
-					     - curbuf->b_visual.vi_start.col);
-	    AppendCharToRedobuff(' ');
-	}
-    }
-    AppendNumberToRedobuff(cap->count1);
-}
-
-/*
  * Prepare for redo of a normal command.
  */
     static void
@@ -4243,30 +4225,16 @@ nv_help(cap)
 nv_addsub(cap)
     cmdarg_T	*cap;
 {
-    int visual = VIsual_active;
-
-    if (cap->oap->op_type == OP_NOP
-	    && do_addsub((int)cap->cmdchar, cap->count1, cap->arg) == OK)
-    {
-	if (visual)
-	{
-	    prep_redo_visual(cap);
-	    if (cap->arg)
-		AppendCharToRedobuff('g');
-	    AppendCharToRedobuff(cap->cmdchar);
-	}
-	else
-	    prep_redo_cmd(cap);
-    }
+    if (!VIsual_active && cap->oap->op_type == OP_NOP)
+    {
+	cap->oap->op_type = cap->cmdchar == Ctrl_A ?  OP_NR_ADD : OP_NR_SUB;
+	op_addsub(cap->oap, cap->count1, cap->arg);
+	cap->oap->op_type = OP_NOP;
+    }
+    else if (VIsual_active)
+	nv_operator(cap);
     else
-	clearopbeep(cap->oap);
-    if (visual)
-    {
-	VIsual_active = FALSE;
-	redo_VIsual_busy = FALSE;
-	may_clear_cmdline();
-	redraw_later(INVERTED);
-    }
+	clearop(cap->oap);
 }
 
 /*
@@ -7924,6 +7892,7 @@ nv_g_cmd(cap)
 	{
 	    cap->arg = TRUE;
 	    cap->cmdchar = cap->nchar;
+	    cap->nchar = NUL;
 	    nv_addsub(cap);
 	}
 	else
--- a/src/ops.c
+++ b/src/ops.c
@@ -112,6 +112,7 @@ static void	dis_msg __ARGS((char_u *p, i
 static char_u	*skip_comment __ARGS((char_u *line, int process, int include_space, int *is_comment));
 #endif
 static void	block_prep __ARGS((oparg_T *oap, struct block_def *, linenr_T, int));
+static int	do_addsub __ARGS((int op_type, pos_T *pos, int length, linenr_T Prenum1));
 #if defined(FEAT_CLIPBOARD) || defined(FEAT_EVAL)
 static void	str_to_reg __ARGS((struct yankreg *y_ptr, int yank_type, char_u *str, long len, long blocklen, int str_list));
 #endif
@@ -158,6 +159,8 @@ static char opchars[][3] =
     {'z', 'D', TRUE},	/* OP_FOLDDELREC */
     {'g', 'w', TRUE},	/* OP_FORMAT2 */
     {'g', '@', FALSE},	/* OP_FUNCTION */
+    {Ctrl_A, NUL, FALSE},	/* OP_NR_ADD */
+    {Ctrl_X, NUL, FALSE},	/* OP_NR_SUB */
 };
 
 /*
@@ -175,6 +178,10 @@ get_op_type(char1, char2)
 	return OP_REPLACE;
     if (char1 == '~')		/* when tilde is an operator */
 	return OP_TILDE;
+    if (char1 == 'g' && char2 == Ctrl_A)	/* add */
+	return OP_NR_ADD;
+    if (char1 == 'g' && char2 == Ctrl_X)	/* subtract */
+	return OP_NR_SUB;
     for (i = 0; ; ++i)
 	if (opchars[i][0] == char1 && opchars[i][1] == char2)
 	    break;
@@ -5340,16 +5347,131 @@ block_prep(oap, bdp, lnum, is_del)
 }
 
 /*
- * add or subtract 'Prenum1' from a number in a line
- * 'command' is CTRL-A for add, CTRL-X for subtract
- *
- * return FAIL for failure, OK otherwise
+ * Handle the add/subtract operator.
  */
-    int
-do_addsub(command, Prenum1, g_cmd)
-    int		command;
+    void
+op_addsub(oap, Prenum1, g_cmd)
+    oparg_T	*oap;
+    linenr_T	Prenum1;	    /* Amount of add/subtract */
+    int		g_cmd;		    /* was g<c-a>/g<c-x> */
+{
+    pos_T		pos;
+    struct block_def	bd;
+    int			change_cnt = 0;
+    linenr_T		amount = Prenum1;
+
+    if (!VIsual_active)
+    {
+	pos = curwin->w_cursor;
+	if (u_save_cursor() == FAIL)
+	    return;
+	change_cnt = do_addsub(oap->op_type, &pos, 0, amount);
+	if (change_cnt)
+	    changed_lines(pos.lnum, 0, pos.lnum + 1, 0L);
+    }
+    else
+    {
+	int one_change;
+	int length;
+	pos_T startpos;
+
+	if (u_save((linenr_T)(oap->start.lnum - 1),
+					(linenr_T)(oap->end.lnum + 1)) == FAIL)
+	    return;
+
+	pos = oap->start;
+	for (; pos.lnum <= oap->end.lnum; ++pos.lnum)
+	{
+	    if (oap->block_mode)		    /* Visual block mode */
+	    {
+		block_prep(oap, &bd, pos.lnum, FALSE);
+		pos.col = bd.textcol;
+		length = bd.textlen;
+	    }
+	    else
+	    {
+		if (oap->motion_type == MLINE)
+		{
+		    curwin->w_cursor.col = 0;
+		    pos.col = 0;
+		    length = (colnr_T)STRLEN(ml_get(pos.lnum));
+		}
+		else if (oap->motion_type == MCHAR)
+		{
+		    if (!oap->inclusive)
+			dec(&(oap->end));
+		    length = (colnr_T)STRLEN(ml_get(pos.lnum));
+		    pos.col = 0;
+		    if (pos.lnum == oap->start.lnum)
+		    {
+			pos.col += oap->start.col;
+			length -= oap->start.col;
+		    }
+		    if (pos.lnum == oap->end.lnum)
+		    {
+			length = (int)STRLEN(ml_get(oap->end.lnum));
+			if (oap->end.col >= length)
+			    oap->end.col = length - 1;
+			length = oap->end.col - pos.col + 1;
+		    }
+		}
+	    }
+	    one_change = do_addsub(oap->op_type, &pos, length, amount);
+	    if (one_change)
+	    {
+		/* Remember the start position of the first change. */
+		if (change_cnt == 0)
+		    startpos = curbuf->b_op_start;
+		++change_cnt;
+	    }
+
+#ifdef FEAT_NETBEANS_INTG
+	    if (netbeans_active() && one_change)
+	    {
+		char_u *ptr = ml_get_buf(curbuf, pos.lnum, FALSE);
+
+		netbeans_removed(curbuf, pos.lnum, pos.col, (long)length);
+		netbeans_inserted(curbuf, pos.lnum, pos.col,
+						&ptr[pos.col], length);
+	    }
+#endif
+	    if (g_cmd && one_change)
+		amount += Prenum1;
+	}
+	if (change_cnt)
+	    changed_lines(oap->start.lnum, 0, oap->end.lnum + 1, 0L);
+
+	if (!change_cnt && oap->is_VIsual)
+	    /* No change: need to remove the Visual selection */
+	    redraw_curbuf_later(INVERTED);
+
+	/* Set '[ mark if something changed. Keep the last end
+	 * position from do_addsub(). */
+	if (change_cnt > 0)
+	    curbuf->b_op_start = startpos;
+
+	if (change_cnt > p_report)
+	{
+	    if (change_cnt == 1)
+		MSG(_("1 line changed"));
+	    else
+		smsg((char_u *)_("%ld lines changed"), change_cnt);
+	}
+    }
+}
+
+/*
+ * Add or subtract 'Prenum1' from a number in a line
+ * op_type is OP_NR_ADD or OP_NR_SUB
+ *
+ * Returns TRUE if some character was changed.
+ */
+    static int
+do_addsub(op_type, pos, length, Prenum1)
+    int		op_type;
+    pos_T	*pos;
+    int		length;
     linenr_T	Prenum1;
-    int		g_cmd;		    /* was g<c-a>/g<c-x> */
 {
     int		col;
     char_u	*buf1;
@@ -5357,11 +5479,9 @@ do_addsub(command, Prenum1, g_cmd)
     int		pre;		/* 'X'/'x': hex; '0': octal; 'B'/'b': bin */
     static int	hexupper = FALSE;	/* 0xABC */
     unsigned long n;
-    unsigned long offset = 0;		/* line offset for Ctrl_V mode */
     long_u	oldn;
     char_u	*ptr;
     int		c;
-    int		length = 0;		/* character length of the number */
     int		todel;
     int		dohex;
     int		dooct;
@@ -5372,16 +5492,9 @@ do_addsub(command, Prenum1, g_cmd)
     int		negative = FALSE;
     int		was_positive = TRUE;
     int		visual = VIsual_active;
-    int		i;
-    int		lnum = curwin->w_cursor.lnum;
-    int		lnume = curwin->w_cursor.lnum;
-    int		startcol = 0;
     int		did_change = FALSE;
     pos_T	t = curwin->w_cursor;
     int		maxlen = 0;
-    int		pos = 0;
-    int		bit = 0;
-    int		bits = sizeof(unsigned long) * 8;
     pos_T	startpos;
     pos_T	endpos;
 
@@ -5390,50 +5503,18 @@ do_addsub(command, Prenum1, g_cmd)
     dobin = (vim_strchr(curbuf->b_p_nf, 'b') != NULL);	/* "Bin" */
     doalp = (vim_strchr(curbuf->b_p_nf, 'p') != NULL);	/* "alPha" */
 
+    curwin->w_cursor = *pos;
+    ptr = ml_get(pos->lnum);
+    col = pos->col;
+
+    if (*ptr == NUL)
+	goto theend;
+
     /*
      * First check if we are on a hexadecimal number, after the "0x".
      */
-    col = curwin->w_cursor.col;
-    if (VIsual_active)
-    {
-	if (lt(curwin->w_cursor, VIsual))
-	{
-	    curwin->w_cursor = VIsual;
-	    VIsual = t;
-	}
-
-	ptr = ml_get(VIsual.lnum);
-	if (VIsual_mode == 'V')
-	{
-	    VIsual.col = 0;
-	    curwin->w_cursor.col = (colnr_T)STRLEN(ptr);
-	}
-	else if (VIsual_mode == Ctrl_V && VIsual.col > curwin->w_cursor.col)
-	{
-	    t = VIsual;
-	    VIsual.col = curwin->w_cursor.col;
-	    curwin->w_cursor.col = t.col;
-	}
-
-	/* store visual area for 'gv' */
-	curbuf->b_visual.vi_start = VIsual;
-	curbuf->b_visual.vi_end = curwin->w_cursor;
-	curbuf->b_visual.vi_mode = VIsual_mode;
-	curbuf->b_visual.vi_curswant = curwin->w_curswant;
-
-	if (VIsual_mode != 'v')
-	    startcol = VIsual.col < curwin->w_cursor.col ? VIsual.col
-						       : curwin->w_cursor.col;
-	else
-	    startcol = VIsual.col;
-	col = startcol;
-	lnum = VIsual.lnum;
-	lnume = curwin->w_cursor.lnum;
-    }
-    else
-    {
-	ptr = ml_get_curline();
-
+    if (!VIsual_active)
+    {
 	if (dobin)
 	    while (col > 0 && vim_isbdigit(ptr[col]))
 		--col;
@@ -5453,7 +5534,7 @@ do_addsub(command, Prenum1, g_cmd)
 
 	    /* In case of binary/hexadecimal pattern overlap match, rescan */
 
-	    col = curwin->w_cursor.col;
+	    col = pos->col;
 
 	    while (col > 0 && vim_isdigit(ptr[col]))
 		col--;
@@ -5480,7 +5561,7 @@ do_addsub(command, Prenum1, g_cmd)
 	    /*
 	     * Search forward and then backward to find the start of number.
 	     */
-	    col = curwin->w_cursor.col;
+	    col = pos->col;
 
 	    while (ptr[col] != NUL
 		    && !vim_isdigit(ptr[col])
@@ -5494,308 +5575,253 @@ do_addsub(command, Prenum1, g_cmd)
 	}
     }
 
-    for (i = lnum; i <= lnume; i++)
-    {
-	colnr_T stop = 0;
-
-	t = curwin->w_cursor;
-	curwin->w_cursor.lnum = i;
-	ptr = ml_get_curline();
-	if ((int)STRLEN(ptr) <= col)
-	    /* try again on next line */
-	    continue;
-	if (visual)
-	{
-	    if (VIsual_mode == 'v'
-		    && i == lnume)
-		stop = curwin->w_cursor.col;
-	    else if (VIsual_mode == Ctrl_V
-		    && curbuf->b_visual.vi_curswant != MAXCOL)
-		stop = curwin->w_cursor.col;
-
-	    while (ptr[col] != NUL
-		    && !vim_isdigit(ptr[col])
-		    && !(doalp && ASCII_ISALPHA(ptr[col])))
+    if (visual)
+    {
+	while (ptr[col] != NUL && length > 0
+		&& !vim_isdigit(ptr[col])
+		&& !(doalp && ASCII_ISALPHA(ptr[col])))
+	{
+	    ++col;
+	    --length;
+	}
+
+	if (length == 0)
+	    goto theend;
+
+	if (col > pos->col && ptr[col - 1] == '-')
+	{
+	    negative = TRUE;
+	    was_positive = FALSE;
+	}
+    }
+
+    /*
+     * If a number was found, and saving for undo works, replace the number.
+     */
+    firstdigit = ptr[col];
+    if (!VIM_ISDIGIT(firstdigit) && !(doalp && ASCII_ISALPHA(firstdigit)))
+    {
+	beep_flush();
+	goto theend;
+    }
+
+    if (doalp && ASCII_ISALPHA(firstdigit))
+    {
+	/* decrement or increment alphabetic character */
+	if (op_type == OP_NR_SUB)
+	{
+	    if (CharOrd(firstdigit) < Prenum1)
 	    {
-		if (col > 0  && col == stop)
-		    break;
-		++col;
+		if (isupper(firstdigit))
+		    firstdigit = 'A';
+		else
+		    firstdigit = 'a';
 	    }
-
-	    if (col > startcol && ptr[col - 1] == '-')
+	    else
+#ifdef EBCDIC
+		firstdigit = EBCDIC_CHAR_ADD(firstdigit, -Prenum1);
+#else
+		firstdigit -= Prenum1;
+#endif
+	}
+	else
+	{
+	    if (26 - CharOrd(firstdigit) - 1 < Prenum1)
 	    {
-		negative = TRUE;
-		was_positive = FALSE;
+		if (isupper(firstdigit))
+		    firstdigit = 'Z';
+		else
+		    firstdigit = 'z';
 	    }
-	}
-	/*
-	 * If a number was found, and saving for undo works, replace the number.
-	 */
-	firstdigit = ptr[col];
-	if ((!VIM_ISDIGIT(firstdigit) && !(doalp && ASCII_ISALPHA(firstdigit)))
-		|| u_save_cursor() != OK)
-	{
-	    if (lnum < lnume)
+	    else
+#ifdef EBCDIC
+		firstdigit = EBCDIC_CHAR_ADD(firstdigit, Prenum1);
+#else
+		firstdigit += Prenum1;
+#endif
+	}
+	curwin->w_cursor.col = col;
+	if (!did_change)
+	    startpos = curwin->w_cursor;
+	did_change = TRUE;
+	(void)del_char(FALSE);
+	ins_char(firstdigit);
+	endpos = curwin->w_cursor;
+	curwin->w_cursor.col = col;
+    }
+    else
+    {
+	if (col > 0 && ptr[col - 1] == '-' && !visual)
+	{
+	    /* negative number */
+	    --col;
+	    negative = TRUE;
+	}
+	/* get the number value (unsigned) */
+	if (visual && VIsual_mode != 'V')
+	    maxlen = (curbuf->b_visual.vi_curswant == MAXCOL
+		    ? (int)STRLEN(ptr) - col
+		    : length);
+
+	vim_str2nr(ptr + col, &pre, &length,
+		0 + (dobin ? STR2NR_BIN : 0)
+		    + (dooct ? STR2NR_OCT : 0)
+		    + (dohex ? STR2NR_HEX : 0),
+		NULL, &n, maxlen);
+
+	/* ignore leading '-' for hex and octal and bin numbers */
+	if (pre && negative)
+	{
+	    ++col;
+	    --length;
+	    negative = FALSE;
+	}
+	/* add or subtract */
+	subtract = FALSE;
+	if (op_type == OP_NR_SUB)
+	    subtract ^= TRUE;
+	if (negative)
+	    subtract ^= TRUE;
+
+	oldn = n;
+	if (subtract)
+	    n -= (unsigned long)Prenum1;
+	else
+	    n += (unsigned long)Prenum1;
+	/* handle wraparound for decimal numbers */
+	if (!pre)
+	{
+	    if (subtract)
 	    {
-		if (visual && VIsual_mode != Ctrl_V)
-		    col = 0;
-		else
-		    col = startcol;
-		/* Try again on next line */
-		continue;
-	    }
-	    beep_flush();
-	    return FAIL;
-	}
-
-	if (doalp && ASCII_ISALPHA(firstdigit))
-	{
-	    /* decrement or increment alphabetic character */
-	    if (command == Ctrl_X)
-	    {
-		if (CharOrd(firstdigit) < Prenum1)
+		if (n > oldn)
 		{
-		    if (isupper(firstdigit))
-			firstdigit = 'A';
-		    else
-			firstdigit = 'a';
+		    n = 1 + (n ^ (unsigned long)-1);
+		    negative ^= TRUE;
 		}
-		else
-#ifdef EBCDIC
-		    firstdigit = EBCDIC_CHAR_ADD(firstdigit, -Prenum1);
-#else
-		    firstdigit -= Prenum1;
-#endif
 	    }
 	    else
 	    {
-		if (26 - CharOrd(firstdigit) - 1 < Prenum1)
+		/* add */
+		if (n < oldn)
 		{
-		    if (isupper(firstdigit))
-			firstdigit = 'Z';
-		    else
-			firstdigit = 'z';
+		    n = (n ^ (unsigned long)-1);
+		    negative ^= TRUE;
 		}
-		else
-#ifdef EBCDIC
-		    firstdigit = EBCDIC_CHAR_ADD(firstdigit, Prenum1);
-#else
-		    firstdigit += Prenum1;
-#endif
-	    }
-	    curwin->w_cursor.col = col;
-	    if (!did_change)
-		startpos = curwin->w_cursor;
-	    did_change = TRUE;
-	    (void)del_char(FALSE);
-	    ins_char(firstdigit);
-	    endpos = curwin->w_cursor;
-	    curwin->w_cursor.col = col;
-	}
-	else
-	{
-	    if (col > 0 && ptr[col - 1] == '-' && !visual)
-	    {
-		/* negative number */
-		--col;
-		negative = TRUE;
-	    }
-	    /* get the number value (unsigned) */
-	    if (visual && VIsual_mode != 'V')
-	    {
-		if (VIsual_mode == 'v')
-		{
-		    if (i == lnum)
-			maxlen = (lnum == lnume
-					    ? curwin->w_cursor.col - col + 1
-					    : (int)STRLEN(ptr) - col);
-		    else
-			maxlen = (i == lnume ? curwin->w_cursor.col - col  + 1
-					     : (int)STRLEN(ptr) - col);
-		}
-		else if (VIsual_mode == Ctrl_V)
-		    maxlen = (curbuf->b_visual.vi_curswant == MAXCOL
-					?  (int)STRLEN(ptr) - col
-					: curwin->w_cursor.col - col + 1);
 	    }
-
-	    vim_str2nr(ptr + col, &pre, &length,
-		    0 + (dobin ? STR2NR_BIN : 0)
-		      + (dooct ? STR2NR_OCT : 0)
-		      + (dohex ? STR2NR_HEX : 0),
-		    NULL, &n, maxlen);
-
-	    /* ignore leading '-' for hex and octal and bin numbers */
-	    if (pre && negative)
-	    {
-		++col;
-		--length;
+	    if (n == 0)
 		negative = FALSE;
-	    }
-
-	    /* add or subtract */
-	    subtract = FALSE;
-	    if (command == Ctrl_X)
-		subtract ^= TRUE;
-	    if (negative)
-		subtract ^= TRUE;
-
-	    oldn = n;
-	    if (subtract)
-		n -= (unsigned long)Prenum1;
-	    else
-		n += (unsigned long)Prenum1;
-
-	    /* handle wraparound for decimal numbers */
-	    if (!pre)
+	}
+
+	if (visual && !was_positive && !negative && col > 0)
+	{
+	    /* need to remove the '-' */
+	    col--;
+	    length++;
+	}
+
+	/*
+	 * Delete the old number.
+	 */
+	curwin->w_cursor.col = col;
+	if (!did_change)
+	    startpos = curwin->w_cursor;
+	did_change = TRUE;
+	todel = length;
+	c = gchar_cursor();
+	/*
+	 * Don't include the '-' in the length, only the length of the
+	 * part after it is kept the same.
+	 */
+	if (c == '-')
+	    --length;
+	while (todel-- > 0)
+	{
+	    if (c < 0x100 && isalpha(c))
 	    {
-		if (subtract)
-		{
-		    if (n > oldn)
-		    {
-			n = 1 + (n ^ (unsigned long)-1);
-			negative ^= TRUE;
-		    }
-		}
+		if (isupper(c))
+		    hexupper = TRUE;
 		else
-		{
-		    /* add */
-		    if (n < oldn)
-		    {
-			n = (n ^ (unsigned long)-1);
-			negative ^= TRUE;
-		    }
-		}
-		if (n == 0)
-		    negative = FALSE;
-	    }
-
-	    if (visual && !was_positive && !negative && col > 0)
-	    {
-		/* need to remove the '-' */
-		col--;
-		length++;
+		    hexupper = FALSE;
 	    }
-
-
-	    /*
-	     * Delete the old number.
-	     */
-	    curwin->w_cursor.col = col;
-	    if (!did_change)
-		startpos = curwin->w_cursor;
-	    did_change = TRUE;
-	    todel = length;
+	    /* del_char() will mark line needing displaying */
+	    (void)del_char(FALSE);
 	    c = gchar_cursor();
-
-	    /*
-	     * Don't include the '-' in the length, only the length of the
-	     * part after it is kept the same.
-	     */
-	    if (c == '-')
-		--length;
-	    while (todel-- > 0)
-	    {
-		if (c < 0x100 && isalpha(c))
-		{
-		    if (isupper(c))
-			hexupper = TRUE;
-		    else
-			hexupper = FALSE;
-		}
-		/* del_char() will mark line needing displaying */
-		(void)del_char(FALSE);
-		c = gchar_cursor();
-	    }
-
-	    /*
-	     * Prepare the leading characters in buf1[].
-	     * When there are many leading zeros it could be very long.
-	     * Allocate a bit too much.
-	     */
-	    buf1 = alloc((unsigned)length + NUMBUFLEN);
-	    if (buf1 == NULL)
-		return FAIL;
-	    ptr = buf1;
-	    if (negative && (!visual || (visual && was_positive)))
-	    {
-		*ptr++ = '-';
-	    }
-	    if (pre)
-	    {
+	}
+
+	/*
+	 * Prepare the leading characters in buf1[].
+	 * When there are many leading zeros it could be very long.
+	 * Allocate a bit too much.
+	 */
+	buf1 = alloc((unsigned)length + NUMBUFLEN);
+	if (buf1 == NULL)
+	    goto theend;
+	ptr = buf1;
+	if (negative && (!visual || (visual && was_positive)))
+	{
+	    *ptr++ = '-';
+	}
+	if (pre)
+	{
+	    *ptr++ = '0';
+	    --length;
+	}
+	if (pre == 'b' || pre == 'B' ||
+	    pre == 'x' || pre == 'X')
+	{
+	    *ptr++ = pre;
+	    --length;
+	}
+
+	/*
+	 * Put the number characters in buf2[].
+	 */
+	if (pre == 'b' || pre == 'B')
+	{
+	    int i;
+	    int bit = 0;
+	    int bits = sizeof(unsigned long) * 8;
+
+	    /* leading zeros */
+	    for (bit = bits; bit > 0; bit--)
+		if ((n >> (bit - 1)) & 0x1) break;
+
+	    for (i = 0; bit > 0; bit--)
+		buf2[i++] = ((n >> (bit - 1)) & 0x1) ? '1' : '0';
+
+	    buf2[i] = '\0';
+	}
+	else if (pre == 0)
+	    sprintf((char *)buf2, "%lu", n);
+	else if (pre == '0')
+	    sprintf((char *)buf2, "%lo", n);
+	else if (pre && hexupper)
+	    sprintf((char *)buf2, "%lX", n);
+	else
+	    sprintf((char *)buf2, "%lx", n);
+	length -= (int)STRLEN(buf2);
+
+	/*
+	 * Adjust number of zeros to the new number of digits, so the
+	 * total length of the number remains the same.
+	 * Don't do this when
+	 * the result may look like an octal number.
+	 */
+	if (firstdigit == '0' && !(dooct && pre == 0))
+	    while (length-- > 0)
 		*ptr++ = '0';
-		--length;
-	    }
-	    if (pre == 'b' || pre == 'B' || 
-		pre == 'x' || pre == 'X')
-	    {
-		*ptr++ = pre;
-		--length;
-	    }
-
-	    /*
-	     * Put the number characters in buf2[].
-	     */
-	    if (pre == 'b' || pre == 'B')
-	    {
-		/* leading zeros */
-		for (bit = bits; bit > 0; bit--)
-		    if ((n >> (bit - 1)) & 0x1) break;
-
-		for (pos = 0; bit > 0; bit--)
-		    buf2[pos++] = ((n >> (bit - 1)) & 0x1) ? '1' : '0';
-
-		buf2[pos] = '\0';
-	    }
-	    else if (pre == 0)
-		sprintf((char *)buf2, "%lu", n);
-	    else if (pre == '0')
-		sprintf((char *)buf2, "%lo", n);
-	    else if (pre && hexupper)
-		sprintf((char *)buf2, "%lX", n);
-	    else
-		sprintf((char *)buf2, "%lx", n);
-	    length -= (int)STRLEN(buf2);
-
-	    /*
-	     * Adjust number of zeros to the new number of digits, so the
-	     * total length of the number remains the same.
-	     * Don't do this when
-	     * the result may look like an octal number.
-	     */
-	    if (firstdigit == '0' && !(dooct && pre == 0))
-		while (length-- > 0)
-		    *ptr++ = '0';
-	    *ptr = NUL;
-	    STRCAT(buf1, buf2);
-	    ins_str(buf1);		/* insert the new number */
-	    vim_free(buf1);
-	    endpos = curwin->w_cursor;
-	    if (lnum < lnume)
-		curwin->w_cursor.col = t.col;
-	    else if (did_change && curwin->w_cursor.col)
-		--curwin->w_cursor.col;
-	}
-
-	if (g_cmd)
-	{
-	    offset = (unsigned long)Prenum1;
-	    g_cmd = 0;
-	}
-	/* reset */
-	subtract = FALSE;
-	negative = FALSE;
-	was_positive = TRUE;
-	if (visual && VIsual_mode == Ctrl_V)
-	    col = startcol;
-	else
-	    col = 0;
-	Prenum1 += offset;
-	curwin->w_set_curswant = TRUE;
-    }
+	*ptr = NUL;
+	STRCAT(buf1, buf2);
+	ins_str(buf1);		/* insert the new number */
+	vim_free(buf1);
+	endpos = curwin->w_cursor;
+	if (did_change && curwin->w_cursor.col)
+	    --curwin->w_cursor.col;
+    }
+
+theend:
     if (visual)
-	/* cursor at the top of the selection */
-	curwin->w_cursor = VIsual;
+	curwin->w_cursor = t;
     if (did_change)
     {
 	/* set the '[ and '] marks */
@@ -5804,7 +5830,8 @@ do_addsub(command, Prenum1, g_cmd)
 	if (curbuf->b_op_end.col > 0)
 	    --curbuf->b_op_end.col;
     }
-    return OK;
+
+    return did_change;
 }
 
 #ifdef FEAT_VIMINFO
--- a/src/proto/ops.pro
+++ b/src/proto/ops.pro
@@ -43,7 +43,7 @@ void op_formatexpr __ARGS((oparg_T *oap)
 int fex_format __ARGS((linenr_T lnum, long count, int c));
 void format_lines __ARGS((linenr_T line_count, int avoid_fex));
 int paragraph_start __ARGS((linenr_T lnum));
-int do_addsub __ARGS((int command, linenr_T Prenum1, int g_cmd));
+void op_addsub __ARGS((oparg_T *oap, linenr_T Prenum1, int g_cmd));
 int read_viminfo_register __ARGS((vir_T *virp, int force));
 void write_viminfo_registers __ARGS((FILE *fp));
 void x11_export_final_selection __ARGS((void));
--- a/src/testdir/test_increment.vim
+++ b/src/testdir/test_increment.vim
@@ -133,7 +133,7 @@ func Test_visual_increment_04()
   exec "norm! vf-\<C-A>"
   call assert_equal(["foobar-10"], getline(1, '$'))
   " NOTE: I think this is correct behavior...
-  "call assert_equal([0, 1, 1, 0], getpos('.'))
+  call assert_equal([0, 1, 1, 0], getpos('.'))
 endfunc
 
 " 5) g<Ctrl-A> on letter
@@ -576,7 +576,111 @@ func Test_visual_increment_27()
   endif
 endfunc
 
-" 28) block-wise increment and dot-repeat
+" Tab code and linewise-visual inc/dec
+func Test_visual_increment_28()
+  call setline(1, ["x\<TAB>10", "\<TAB>-1"])
+  exec "norm! Vj\<C-A>"
+  call assert_equal(["x\<TAB>11", "\<TAB>0"], getline(1, '$'))
+  call assert_equal([0, 1, 1, 0], getpos('.'))
+
+  call setline(1, ["x\<TAB>10", "\<TAB>-1"])
+  exec "norm! ggVj\<C-X>"
+  call assert_equal(["x\<TAB>9", "\<TAB>-2"], getline(1, '$'))
+  call assert_equal([0, 1, 1, 0], getpos('.'))
+endfunc
+
+" Tab code and linewise-visual inc/dec with 'nrformats'+=alpha
+func Test_visual_increment_29()
+  set nrformats+=alpha
+  call setline(1, ["x\<TAB>10", "\<TAB>-1"])
+  exec "norm! Vj\<C-A>"
+  call assert_equal(["y\<TAB>10", "\<TAB>0"], getline(1, '$'))
+  call assert_equal([0, 1, 1, 0], getpos('.'))
+
+  call setline(1, ["x\<TAB>10", "\<TAB>-1"])
+  exec "norm! ggVj\<C-X>"
+  call assert_equal(["w\<TAB>10", "\<TAB>-2"], getline(1, '$'))
+  call assert_equal([0, 1, 1, 0], getpos('.'))
+endfunc
+
+" Tab code and character-visual inc/dec
+func Test_visual_increment_30()
+  call setline(1, ["x\<TAB>10", "\<TAB>-1"])
+  exec "norm! f1vjf1\<C-A>"
+  call assert_equal(["x\<TAB>11", "\<TAB>0"], getline(1, '$'))
+  call assert_equal([0, 1, 3, 0], getpos('.'))
+
+  call setline(1, ["x\<TAB>10", "\<TAB>-1"])
+  exec "norm! ggf1vjf1\<C-X>"
+  call assert_equal(["x\<TAB>9", "\<TAB>-2"], getline(1, '$'))
+  call assert_equal([0, 1, 3, 0], getpos('.'))
+endfunc
+
+" Tab code and blockwise-visual inc/dec
+func Test_visual_increment_31()
+  call setline(1, ["x\<TAB>10", "\<TAB>-1"])
+  exec "norm! f1\<C-V>jl\<C-A>"
+  call assert_equal(["x\<TAB>11", "\<TAB>0"], getline(1, '$'))
+  call assert_equal([0, 1, 3, 0], getpos('.'))
+
+  call setline(1, ["x\<TAB>10", "\<TAB>-1"])
+  exec "norm! ggf1\<C-V>jl\<C-X>"
+  call assert_equal(["x\<TAB>9", "\<TAB>-2"], getline(1, '$'))
+  call assert_equal([0, 1, 3, 0], getpos('.'))
+endfunc
+
+" Tab code and blockwise-visual decrement with 'linebreak' and 'showbreak'
+func Test_visual_increment_32()
+  28vnew dummy_31
+  set linebreak showbreak=+
+  call setline(1, ["x\<TAB>\<TAB>\<TAB>10", "\<TAB>\<TAB>\<TAB>\<TAB>-1"])
+  exec "norm! ggf0\<C-V>jg_\<C-X>"
+  call assert_equal(["x\<TAB>\<TAB>\<TAB>1-1", "\<TAB>\<TAB>\<TAB>\<TAB>-2"], getline(1, '$'))
+  call assert_equal([0, 1, 6, 0], getpos('.'))
+  bwipe!
+endfunc
+
+" Tab code and blockwise-visual increment with $
+func Test_visual_increment_33()
+  call setline(1, ["\<TAB>123", "456"])
+  exec "norm! gg0\<C-V>j$\<C-A>"
+  call assert_equal(["\<TAB>124", "457"], getline(1, '$'))
+  call assert_equal([0, 1, 1, 0], getpos('.'))
+endfunc
+
+" Tab code and blockwise-visual increment and redo
+func Test_visual_increment_34()
+  call setline(1, ["\<TAB>123", "     456789"])
+  exec "norm! gg0\<C-V>j\<C-A>"
+  call assert_equal(["\<TAB>123", "     457789"], getline(1, '$'))
+  call assert_equal([0, 1, 1, 0], getpos('.'))
+
+  exec "norm! .."
+  call assert_equal(["\<TAB>123", "     459789"], getline(1, '$'))
+  call assert_equal([0, 1, 1, 0], getpos('.'))
+endfunc
+
+" Tab code, spaces and character-visual increment and redo
+func Test_visual_increment_35()
+  call setline(1, ["\<TAB>123", "        123", "\<TAB>123", "\<TAB>123"])
+  exec "norm! ggvjf3\<C-A>..."
+  call assert_equal(["\<TAB>127", "        127", "\<TAB>123", "\<TAB>123"], getline(1, '$'))
+  call assert_equal([0, 1, 2, 0], getpos('.'))
+endfunc
+
+" Tab code, spaces and blockwise-visual increment and redo
+func Test_visual_increment_36()
+  call setline(1, ["           123", "\<TAB>456789"])
+  exec "norm! G0\<C-V>kl\<C-A>"
+  call assert_equal(["           123", "\<TAB>556789"], getline(1, '$'))
+  call assert_equal([0, 1, 1, 0], getpos('.'))
+
+  exec "norm! ..."
+  call assert_equal(["           123", "\<TAB>856789"], getline(1, '$'))
+  call assert_equal([0, 1, 1, 0], getpos('.'))
+endfunc
+
+" block-wise increment and dot-repeat
 " Text:
 "   1 23
 "   4 56
@@ -587,7 +691,7 @@ endfunc
 "   4 59
 "
 " Try with and without indent.
-func Test_visual_increment_28()
+func Test_visual_increment_37()
   call setline(1, ["  1 23", "  4 56"])
   exec "norm! ggf2\<C-V>jl\<C-A>.."
   call assert_equal(["  1 26", "  4 59"], getline(1, 2))
--- a/src/version.c
+++ b/src/version.c
@@ -742,6 +742,8 @@ static char *(features[]) =
 static int included_patches[] =
 {   /* Add new patch number below this line */
 /**/
+    1087,
+/**/
     1086,
 /**/
     1085,
--- a/src/vim.h
+++ b/src/vim.h
@@ -1457,6 +1457,10 @@ typedef UINT32_TYPEDEF UINT32_T;
 #define OP_FOLDDELREC	25	/* "zD" delete folds recursively */
 #define OP_FORMAT2	26	/* "gw" format operator, keeps cursor pos */
 #define OP_FUNCTION	27	/* "g@" call 'operatorfunc' */
+#define OP_NR_ADD	28	/* "<C-A>" Add to the number or alphabetic
+				   character (OP_ADD conflicts with Perl) */
+#define OP_NR_SUB	29	/* "<C-X>" Subtract from the number or
+				   alphabetic character */
 
 /*
  * Motion types, used for operators and for yank/delete registers.