From: Walter Alejandro Iglesias Subject: Improvement for vi(1) paste comand (updated diff) To: tech@openbsd.org Cc: Jeremy Mates Date: Fri, 6 Feb 2026 11:11:30 +0100 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