The attached patch (visual_block_shift.patch) fixes the problems with
shifting visual blocks when the blocks start and end inside one TAB
character.

My general impression about the relevant part of the code was that it
was very difficult to understand. This is why I allowed myself to start
from scratch.
I aimed for code clarity, hence the extensive comments, variables
declared inside the else {} block (I know Bram will probably not like
it, though).

I also added a test for visual block shift inside one TAB:
visual_block_shift_test.patch

I made changes to v_b_< and todo.txt, removing notes indicating the
problems: visual_block_shift_runtime.patch

-- 
Cheers,
Lech

--~--~---------~--~----~------------~-------~--~----~
You received this message from the "vim_dev" maillist.
For more information, visit http://www.vim.org/maillist.php
-~----------~----~----~----~------~----~------~--~---

Index: src/ops.c
===================================================================
--- src/ops.c	(revision 1365)
+++ src/ops.c	(working copy)
@@ -75,8 +75,8 @@
     int		startspaces;	/* 'extra' cols of first char */
     int		endspaces;	/* 'extra' cols of first char */
     int		textlen;	/* chars in block */
-    char_u	*textstart;	/* pointer to 1st char in block */
-    colnr_T	textcol;	/* cols of chars (at least part.) in block */
+    char_u	*textstart;	/* pointer to 1st (at least part.) char in block */
+    colnr_T	textcol;	/* index of chars (at least part.) in block */
     colnr_T	start_vcol;	/* start col of 1st char wholly inside block */
     colnr_T	end_vcol;	/* start col of 1st char wholly after block */
 #ifdef FEAT_VISUALEXTRA
@@ -87,7 +87,7 @@
     int		pre_whitesp_c;	/* chars of ws before block */
     colnr_T	end_char_vcols;	/* number of vcols of post-block char */
 #endif
-    colnr_T	start_char_vcols; /* number of vcols of pre-block char */
+    colnr_T	start_char_vcols; /* number of displayed cols of pre-block char */
 };
 
 #ifdef FEAT_VISUALEXTRA
@@ -382,15 +382,14 @@
 {
     int			left = (oap->op_type == OP_LSHIFT);
     int			oldstate = State;
-    int			total, split;
-    char_u		*newp, *oldp, *midp, *ptr;
+    int			total;
+    char_u		*newp, *oldp;
     int			oldcol = curwin->w_cursor.col;
     int			p_sw = (int)curbuf->b_p_sw;
     int			p_ts = (int)curbuf->b_p_ts;
     struct block_def	bd;
-    int			internal = 0;
     int			incr;
-    colnr_T		vcol, col = 0, ws_vcol;
+    colnr_T		ws_vcol;
     int			i = 0, j = 0;
     int			len;
 
@@ -456,67 +455,95 @@
     }
     else /* left */
     {
-	vcol = oap->start_vcol;
-	/* walk vcol past ws to be removed */
-	for (midp = oldp + bd.textcol;
-	      vcol < (oap->start_vcol + total) && vim_iswhite(*midp); )
+	colnr_T	    destination_col;	/* column to which text in block will
+					   be shifted */
+	char_u	    *verbatim_copy_end;	/* end of the part of the line which is
+					   copied verbatim */
+	colnr_T	    verbatim_copy_width;/* the (displayed) width of this part
+					   of line */
+	unsigned    fill;		/* number of spaces which replace a TAB */
+	unsigned    new_line_len;	/* the length of the line after the
+					   block shift */
+	size_t	    block_space_width;
+	size_t	    shift_amount;
+	char_u	    *non_white = bd.textstart;
+	colnr_T	    non_white_col;
+
+	/* Firstly, let's find the first non-whitespace character that is
+	 * displayed after the block's start column and the character's column
+	 * number. Also, let's calculate the width of all the whitespace
+	 * characters that are displayed in the block and precede the searched
+	 * non-whitespace character.
+	 */
+
+	/* If "bd.startspaces" is set, "bd.textstart" points to the character,
+	 * the part of which is displayed at the block's beginning. Let's start
+	 * searching from the next character.
+	 */
+	if (bd.startspaces)
+	    mb_ptr_adv(non_white);
+
+	/* The character's column is in "bd.start_vcol".
+	 */
+	non_white_col = bd.start_vcol;
+
+	while (vim_iswhite(*non_white))
 	{
-	    incr = lbr_chartabsize_adv(&midp, (colnr_T)vcol);
-	    vcol += incr;
+	    incr = lbr_chartabsize_adv(&non_white, non_white_col);
+	    non_white_col += incr;
 	}
-	/* internal is the block-internal ws replacing a split TAB */
-	if (vcol > (oap->start_vcol + total))
+
+	block_space_width = non_white_col - oap->start_vcol;
+	/* We will shift by "total" or "block_space_width", whichever is less.
+	 */
+	shift_amount = (block_space_width < total? block_space_width: total);
+
+	/* The column to which we will shift the text.
+	 */
+	destination_col = non_white_col - shift_amount;
+
+	/* Now let's find out how much of the beginning of the line we can
+	 * reuse without modification.
+	 */
+	verbatim_copy_end = bd.textstart;
+	verbatim_copy_width = bd.start_vcol;
+
+	/* If "bd.startspaces" is set, "bd.textstart" points to the character
+	 * preceding the block. We have to subtract its width to obtain its
+	 * column number.
+	 */
+	if (bd.startspaces)
+	    verbatim_copy_width -= bd.start_char_vcols;
+	while (verbatim_copy_width < destination_col)
 	{
-	    /* we have to split the TAB *(midp-1) */
-	    internal = vcol - (oap->start_vcol + total);
+	    incr = lbr_chartabsize(verbatim_copy_end, verbatim_copy_width);
+	    if (verbatim_copy_width + incr > destination_col)
+		break;
+	    verbatim_copy_width += incr;
+	    mb_ptr_adv(verbatim_copy_end);
 	}
-	/* if 'expandtab' is not set, use TABs */
 
-	split = bd.startspaces + internal;
-	if (split > 0)
-	{
-	    if (!curbuf->b_p_et)
-	    {
-		for (ptr = oldp, col = 0; ptr < oldp+bd.textcol; )
-		    col += lbr_chartabsize_adv(&ptr, (colnr_T)col);
+	/* If "destination_col" is different from the width of the initial
+	 * part of the line that will be copied, it means we encountered a tab
+	 * character, which we will have to partly replace with spaces.
+	 */
+	fill = destination_col - verbatim_copy_width;
 
-		/* col+1 now equals the start col of the first char of the
-		 * block (may be < oap.start_vcol if we're splitting a TAB) */
-		i = ((col % p_ts) + split) / p_ts; /* number of tabs */
-	    }
-	    if (i)
-		j = ((col % p_ts) + split) % p_ts; /* number of spp */
-	    else
-		j = split;
-	}
+	/* The replacement line will consist of:
+	 * - the beginning of the original line up to "verbatim_copy_end",
+	 * - "fill" number of spaces,
+	 * - the rest of the line, pointed to by non_white.
+	 */
+	new_line_len = (unsigned)(verbatim_copy_end - oldp)
+	    	       + fill
+	    	       + (unsigned)STRLEN(non_white) + 1;
 
-	newp = alloc_check(bd.textcol + i + j + (unsigned)STRLEN(midp) + 1);
+	newp = alloc_check(new_line_len);
 	if (newp == NULL)
 	    return;
-	vim_memset(newp, NUL, (size_t)(bd.textcol + i + j + STRLEN(midp) + 1));
-
-	/* copy first part we want to keep */
-	mch_memmove(newp, oldp, (size_t)bd.textcol);
-	/* Now copy any TABS and spp to ensure correct alignment! */
-	while (vim_iswhite(*midp))
-	{
-	    if (*midp == TAB)
-		i++;
-	    else /*space */
-		j++;
-	    midp++;
-	}
-	/* We might have an extra TAB worth of spp now! */
-	if (j / p_ts && !curbuf->b_p_et)
-	{
-	    i++;
-	    j -= p_ts;
-	}
-	copy_chars(newp + bd.textcol, (size_t)i, TAB);
-	copy_spaces(newp + bd.textcol + i, (size_t)j);
-
-	/* the end */
-	STRMOVE(newp + STRLEN(newp), midp);
+	mch_memmove(newp, oldp, (size_t)(verbatim_copy_end - oldp));
+	copy_spaces(newp + (verbatim_copy_end - oldp), (size_t)fill);
+	STRMOVE(newp + (verbatim_copy_end - oldp) + fill, non_white);
     }
     /* replace the line */
     ml_replace(curwin->w_cursor.lnum, newp, FALSE);
diff -Nacr src/testdir_orig/test66.in src/testdir/test66.in
*** src/testdir_orig/test66.in	1970-01-01 01:00:00.000000000 +0100
--- src/testdir/test66.in	2009-02-23 00:10:00.000000000 +0100
***************
*** 0 ****
--- 1,24 ----
+ 
+ Test for visual block shift and tab characters.
+ 
+ STARTTEST
+ /^abcdefgh
+ 4jI    j<<11|D
+ 7|a		
+ 7|a		   
+ 7|a	       	4k13|4j<
+ :$-4,$w! test.out
+ :$-4,$s/\s\+//g
+ 4kI    j<<
+ 7|a		
+ 7|a					
+ 7|a	       		4k13|4j3<
+ :$-4,$w >> test.out
+ :qa!
+ ENDTEST
+ 
+ abcdefghijklmnopqrstuvwxyz
+ abcdefghijklmnopqrstuvwxyz
+ abcdefghijklmnopqrstuvwxyz
+ abcdefghijklmnopqrstuvwxyz
+ abcdefghijklmnopqrstuvwxyz
diff -Nacr src/testdir_orig/test66.ok src/testdir/test66.ok
*** src/testdir_orig/test66.ok	1970-01-01 01:00:00.000000000 +0100
--- src/testdir/test66.ok	2009-02-23 00:10:00.000000000 +0100
***************
*** 0 ****
--- 1,10 ----
+     abcdefghijklmnopqrstuvwxyz
+ abcdefghij
+     abc	    defghijklmnopqrstuvwxyz
+     abc	    defghijklmnopqrstuvwxyz
+     abc	    defghijklmnopqrstuvwxyz
+     abcdefghijklmnopqrstuvwxyz
+ abcdefghij
+     abc	    defghijklmnopqrstuvwxyz
+     abc		defghijklmnopqrstuvwxyz
+     abc	    defghijklmnopqrstuvwxyz
diff -cr ./doc/todo.txt ../vim7_runtime_orig/doc/todo.txt
*** ./doc/todo.txt	2009-02-22 20:32:13.000000000 +0100
--- ../vim7_runtime_orig/doc/todo.txt	2009-02-22 20:30:27.000000000 +0100
***************
*** 1614,1619 ****
--- 1614,1621 ----
  8   With 'virtualedit' set and 'selection' "exclusive", a Visual selection
      that ends in or after a tab, "d" doesn't delete (part of) the tab.
      (Helmut Stiegler)
+ 8   With 'virtualedit' set, a blockwise Visual selection that starts and ends
+     in a tab, "<" shifts too much. (Helmut Stiegler)
  9   When jumping to a tag, the search pattern is put in the history.  When
      'magic' is on, the pattern may not work.  Translate the pattern depending
      on p_magic when putting it in the history?  Alternative: Store value of
diff -cr ./doc/visual.txt ../vim7_runtime_orig/doc/visual.txt
*** ./doc/visual.txt	2009-02-22 20:32:42.000000000 +0100
--- ../vim7_runtime_orig/doc/visual.txt	2009-02-22 20:30:27.000000000 +0100
***************
*** 309,314 ****
--- 309,316 ----
  LHS of the block determines the point from which to apply a right shift, and
  padding includes TABs optimally according to 'ts' and 'et'.  The LHS of the
  block determines the point upto which to shift left.
+     Note: v_< padding is buggy if the Visual Block starts and ends in the same
+     TAB. (Vim 5.4c)
  See |v_b_>_example|.
  See |v_b_<_example|.
  

Raspunde prin e-mail lui