Index | Thread | Search

From:
Walter Alejandro Iglesias <wai@roquesor.com>
Subject:
Improvement for vi(1) paste comand (updated diff)
To:
tech@openbsd.org
Cc:
Jeremy Mates <jmates@thrig.me>
Date:
Fri, 6 Feb 2026 11:11:30 +0100

Download raw body.

Thread
Recently, I got the first useful feedback (in private) about this patch
of mine, so far ignored:

  https://marc.info/?l=openbsd-tech&m=175645383505284&w=2

Jeremy Mates made me notice the following:

On Wed, Feb 04, 2026 at 10:54:26PM +0000, Jeremy Mates wrote:
> a
> b
> c
> 1
> 2
> 3
> 4
> 5
> 6
> 
> With the cursor on 1 (the 4th line), and a vi with
> 
> https://marc.info/?l=openbsd-tech&m=175645383505284&w=2
> 
> applied the copy command of 1,3t. (copy lines 1..3 to the current line)
> results in the cursor on 3. The base OpenBSD vi (as of 7.7, I really
> should upgrade sometime) meanwhile instead puts the cursor on c, which
> seems a more sensible result.

What Jeremy describes happens because of the following workaround
present in ex_copy() (ex/ex_move.c:65):

       /*
        * Copy puts the cursor on the last line copied.  The cursor
        * returned by the put routine is the first line put, not the
        * last, because that's the historic semantic of vi.
        */
       cnt = (fm2.lno - fm1.lno) + 1;
       sp->lno = m.lno + (cnt - 1);
       sp->cno = 0;

That comment explains the detour they had to take just to be respectful
with the "historic semantic of vi."  The 't' command calculates the
position where the cursor has to land assuming the curren behaviour of
'p'.  Since my modification of the 'p' command puts the cursor in the
last line, the '+ (cnt - 1)' adding above becomes unnecessary.

Notice how, curiously, the 't' command does exactly what my patch
teaches the 'p' command to do.  This means I'm not the only one who
noticed that it's the more practical behavior.

So, the new version below removes that detour and its comment, resulting
in a more logical and *unified* behavior (eg. with this modification,
given the same Jeremy's example, :1,3y and then pasting with 'p' gives
you the same result than using '1,3t.'.)

I'd appreciate more testing and more useful feedback like Jeremy's.


Index: common/put.c
===================================================================
RCS file: /cvs/src/usr.bin/vi/common/put.c,v
diff -u -p -u -p -r1.17 put.c
--- common/put.c	23 Aug 2025 21:02:10 -0000	1.17
+++ common/put.c	5 Feb 2026 10:10:25 -0000
@@ -70,9 +70,6 @@ put(SCR *sp, CB *cbp, CHAR_T *namep, MAR
 	 * historic vi couldn't deal with a file that had no lines in it.  This
 	 * implementation treats that as a bug, and does not retain the blank
 	 * line.
-	 *
-	 * Historical practice is that the cursor ends at the first character
-	 * in the file.
 	 */
 	if (cp->lno == 1) {
 		if (db_last(sp, &lno))
@@ -86,7 +83,18 @@ put(SCR *sp, CB *cbp, CHAR_T *namep, MAR
 						return (1);
 				tp = TAILQ_FIRST(&cbp->textq);
 			}
-			rp->lno = 1;
+
+			/*
+			 * Historical practice was that the cursor ends at the
+			 * first character in the file.  This is preserved only
+			 * for the 'P' command.  With 'p', now the cursor is
+			 * moved to the last line pasted.
+			 */
+			if (append)
+				rp->lno = lno;
+			else
+				rp->lno = 1;
+
 			rp->cno = 0;
 			return (0);
 		}
@@ -104,6 +112,14 @@ put(SCR *sp, CB *cbp, CHAR_T *namep, MAR
 					return (1);
 			tp = TAILQ_FIRST(&cbp->textq);
 		}
+
+		/*
+		 * With the 'p' command move the cursor to the last line
+		 * pasted.
+		 */
+		if (append)
+			rp->lno = lno;
+
 		rp->cno = 0;
 		(void)nonblank(sp, rp->lno, &rp->cno);
 		return (0);
@@ -158,9 +174,19 @@ put(SCR *sp, CB *cbp, CHAR_T *namep, MAR
 	 * of vi, character mode puts that cross line boundaries leave the
 	 * cursor on the first character.  Nvi implements the System III/V
 	 * behavior, and expect POSIX.2 to do so as well.
+	 *
+	 * Update (Aug 24, 2025): Now, with the 'p' command, the cursor
+	 * moves to the last character of the pasted string.  The 'P'
+	 * command hasn't been modified; the cursor still rests on the
+	 * first character of the pasted string.  This is convenient when
+	 * running 'p' or 'P' consecutive times or by hitting '.' (dot).
 	 */
 	rp->lno = lno;
-	rp->cno = len == 0 ? 0 : sp->cno + (append && tp->len ? 1 : 0);
+	if (append == 0)
+		rp->cno = len == 0 ? 0 : sp->cno;
+	else
+		rp->cno = len == 0 ? (tp->len * cnt) - 1 : sp->cno +
+		    append + (tp->len * cnt) - 1;
 
 	/*
 	 * If no more lines in the CB, append the rest of the original
Index: ex/ex_move.c
===================================================================
RCS file: /cvs/src/usr.bin/vi/ex/ex_move.c,v
diff -u -p -u -p -r1.12 ex_move.c
--- ex/ex_move.c	23 Aug 2025 21:02:10 -0000	1.12
+++ ex/ex_move.c	5 Feb 2026 10:10:25 -0000
@@ -62,13 +62,7 @@ ex_copy(SCR *sp, EXCMD *cmdp)
 	if (put(sp, &cb, NULL, &tm, &m, 1, 1))
 		rval = 1;
 	else {
-		/*
-		 * Copy puts the cursor on the last line copied.  The cursor
-		 * returned by the put routine is the first line put, not the
-		 * last, because that's the historic semantic of vi.
-		 */
-		cnt = (fm2.lno - fm1.lno) + 1;
-		sp->lno = m.lno + (cnt - 1);
+		sp->lno = m.lno;
 		sp->cno = 0;
 	}
 err:	text_lfree(&cb.textq);


-- 
Walter